Refactor agent architecture, improve heuristics logic, enhance parser robustness, simplify config, and optimize Docker setup for HF deployment
ad80dda | """Transcript Store: the shared memory spine for environment, questioners, and grader.""" | |
| from typing import List, Optional | |
| from models import Speaker, Turn | |
| class TranscriptStore: | |
| """ | |
| Stores the full episode transcript. | |
| Important design: | |
| - Full transcript is always retained internally. | |
| - Questioners may receive lagged witness history through get_witness_turns(). | |
| - Graders can use get_all_witness_turns() when they need full ground truth. | |
| """ | |
| def __init__(self, data_lag_turns: int = 0) -> None: | |
| self._turns: List[Turn] = [] | |
| self._witness_turns: List[Turn] = [] | |
| self._questioner_turns: List[Turn] = [] | |
| self._data_lag_turns = max(0, data_lag_turns) | |
| self._last_questioner: Optional[Turn] = None | |
| self._last_witness: Optional[Turn] = None | |
| def append(self, turn: Turn) -> None: | |
| """Appends one turn and updates speaker-specific caches.""" | |
| self._turns.append(turn) | |
| if turn.speaker == Speaker.QUESTIONER: | |
| self._questioner_turns.append(turn) | |
| self._last_questioner = turn | |
| elif turn.speaker == Speaker.WITNESS: | |
| self._witness_turns.append(turn) | |
| self._last_witness = turn | |
| def get_all(self) -> List[Turn]: | |
| """Returns a copy of the full transcript.""" | |
| return list(self._turns) | |
| def turns(self) -> List[Turn]: | |
| """ | |
| Returns the internal transcript list. | |
| Kept for backward compatibility. Prefer get_all() unless mutation is intentional. | |
| """ | |
| return self._turns | |
| def snapshot(self) -> List[Turn]: | |
| """Returns a copy of the full transcript at this point in time.""" | |
| return self.get_all() | |
| def get_witness_turns(self, exclude_recent: int = 0) -> List[Turn]: | |
| """ | |
| Returns witness turns visible under data-lag rules. | |
| This is intentionally lag-aware and is mainly used by questioners/agents. | |
| For full grading truth, use get_all_witness_turns(). | |
| """ | |
| lag = max(exclude_recent, self._data_lag_turns) | |
| if lag <= 0: | |
| return list(self._witness_turns) | |
| if len(self._witness_turns) <= lag: | |
| return [] | |
| return list(self._witness_turns[:-lag]) | |
| def get_visible_witness_turns(self, exclude_recent: int = 0) -> List[Turn]: | |
| """Explicit alias for lag-aware witness history.""" | |
| return self.get_witness_turns(exclude_recent=exclude_recent) | |
| def get_all_witness_turns(self) -> List[Turn]: | |
| """Returns all witness turns without applying data lag.""" | |
| return list(self._witness_turns) | |
| def get_questioner_turns(self) -> List[Turn]: | |
| """Returns all questioner turns.""" | |
| return list(self._questioner_turns) | |
| def last_questioner_turn(self) -> Optional[str]: | |
| """Returns the text of the most recent questioner turn.""" | |
| turn = self.last_questioner_turn_obj() | |
| return turn.text if turn else None | |
| def last_questioner_turn_obj(self) -> Optional[Turn]: | |
| """Returns the most recent questioner Turn object in O(1).""" | |
| return self._last_questioner | |
| def last_witness_turn_obj(self) -> Optional[Turn]: | |
| """Returns the most recent witness Turn object in O(1).""" | |
| return self._last_witness | |
| def get_turn(self, turn_no: int, speaker: Optional[Speaker] = None) -> Optional[Turn]: | |
| """ | |
| Returns the first matching turn by turn number, optionally filtered by speaker. | |
| Useful for audit reconstruction and debugging. | |
| """ | |
| for turn in self._turns: | |
| if turn.turn_no == turn_no and (speaker is None or turn.speaker == speaker): | |
| return turn | |
| return None | |
| def get_info_state(self, up_to_turn: int) -> List[str]: | |
| """ | |
| Returns unique document/evidence IDs available up to a given turn. | |
| Preserves first-seen order while removing duplicates. | |
| """ | |
| seen: set[str] = set() | |
| docs: List[str] = [] | |
| for turn in self._turns: | |
| if turn.turn_no > up_to_turn: | |
| continue | |
| for doc_id in turn.info_state: | |
| if doc_id not in seen: | |
| docs.append(doc_id) | |
| seen.add(doc_id) | |
| return docs | |
| def __len__(self) -> int: | |
| return len(self._turns) |