Source code for factor_agent.llm.replay

"""The replay backend: a recorded run's replies served back, so a campaign reproduces without a provider."""

from collections import defaultdict, deque

from factor_agent.llm.base import Reply, Request
from factor_agent.llm.log import LLMLog


[docs] class ReplayExhausted(RuntimeError): """The recorded run holds no reply for a request: it ran out of calls, or the run asked something else."""
[docs] class ReplayBackend: """Answer each request with the recorded reply to the same role, model and prompt. A request asked several times gets its recorded replies in order. Raises ------ ValueError If the log holds no call (a wrong path, or a run that made none). """ def __init__(self, log: LLMLog): self.log = log self._replies: dict[tuple[str, str, str], deque] = defaultdict(deque) calls = log.calls() if not calls: raise ValueError(f"replay: no recorded call in {log.path}") for call in calls: request = call["request"] self._replies[request["role"], request["model"], request["prompt"]].append(call) #: The options a role's config may give besides ``backend`` and ``model``; ``log`` is needed. OPTIONS = ("log",)
[docs] @classmethod def from_config(cls, spec: dict) -> "ReplayBackend": """Build from ``{"log": <the llm_log directory of the recorded run>}``.""" return cls(LLMLog(spec["log"]))
[docs] def complete(self, request: Request) -> Reply: """Return the recorded reply to ``request``. Raises ------ ReplayExhausted If the log holds no reply left for this role, model and prompt. """ queue = self._replies.get((request.role, request.model, request.prompt)) if not queue: raise ReplayExhausted( f"replay of {self.log.path}: no recorded reply left for this {request.role} request " f"(the recorded run ran out of calls, or this run asked something it never did); " f"the prompt begins {request.prompt[:200]!r}" ) call = queue.popleft() reply = call["reply"] return Reply( text=reply["text"], usage=reply["usage"], model_id=reply["model_id"], params=call["request"]["params"], raw=reply["raw"], )