Source code for jeevesagent.model.scripted

"""A test-only model that replays a fixed sequence of turns.

Each :class:`ScriptedTurn` describes one model response: optional text
followed by zero or more tool calls, terminated by a finish chunk. The
model advances through the script with each call to :meth:`stream`,
which lets a single agent ``run()`` exercise multi-turn flows end-to-end.
"""

from __future__ import annotations

from collections.abc import AsyncIterator
from dataclasses import dataclass, field

from ..core.types import Message, ModelChunk, ToolCall, ToolDef, Usage


[docs] @dataclass class ScriptedTurn: text: str = "" tool_calls: list[ToolCall] = field(default_factory=list) usage: Usage = field(default_factory=Usage)
[docs] class ScriptedModel: """Model that emits canned responses, one per call to :meth:`stream`.""" name: str = "scripted" def __init__(self, turns: list[ScriptedTurn]) -> None: self._turns = list(turns) self._idx = 0 @property def remaining(self) -> int: return max(0, len(self._turns) - self._idx)
[docs] async def complete( self, messages: list[Message], *, tools: list[ToolDef] | None = None, temperature: float = 1.0, max_tokens: int | None = None, ) -> tuple[str, list[ToolCall], Usage, str]: """Single-shot replay of the next scripted turn. Mirrors :meth:`stream` but returns the turn's text + tool_calls + usage in one tuple. Used by the non-streaming hot path (``agent.run()``); ``agent.stream()`` keeps using :meth:`stream` for per-chunk replay. """ if self._idx >= len(self._turns): return "", [], Usage(), "stop" turn = self._turns[self._idx] self._idx += 1 finish_reason = "tool_use" if turn.tool_calls else "stop" return ( turn.text, list(turn.tool_calls), turn.usage, finish_reason, )
[docs] async def stream( self, messages: list[Message], *, tools: list[ToolDef] | None = None, temperature: float = 1.0, max_tokens: int | None = None, ) -> AsyncIterator[ModelChunk]: if self._idx >= len(self._turns): yield ModelChunk(kind="text", text="") yield ModelChunk( kind="finish", finish_reason="stop", usage=Usage(), ) return turn = self._turns[self._idx] self._idx += 1 if turn.text: for i, word in enumerate(turn.text.split(" ")): piece = word if i == 0 else " " + word yield ModelChunk(kind="text", text=piece) for tc in turn.tool_calls: yield ModelChunk(kind="tool_call", tool_call=tc) finish_reason = "tool_use" if turn.tool_calls else "stop" yield ModelChunk( kind="finish", finish_reason=finish_reason, usage=turn.usage, )