| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081 |
- """Actor LLM: simulates a user driving an agent session."""
- from __future__ import annotations
- from dataclasses import dataclass
- from pathlib import Path
- from typing import Any
- import anthropic
- from jinja2 import Template
- ACTOR_TOOL: dict[str, Any] = {
- "name": "terminal_action",
- "description": "Send an action to the terminal session.",
- "input_schema": {
- "type": "object",
- "properties": {
- "action": {
- "type": "string",
- "enum": ["type", "done", "stuck", "key"],
- "description": "The action to take.",
- },
- "text": {
- "type": "string",
- "description": "Text to type (only for 'type' action).",
- },
- "key": {
- "type": "string",
- "description": "Special key to send (only for 'key' action, e.g., 'ctrl-c').",
- },
- },
- "required": ["action"],
- },
- }
- @dataclass
- class ActorAction:
- action: str
- text: str | None = None
- key: str | None = None
- @classmethod
- def from_tool_result(cls, data: dict[str, Any]) -> ActorAction:
- return cls(action=data["action"], text=data.get("text"), key=data.get("key"))
- class Actor:
- def __init__(self, model: str = "claude-sonnet-4-6", temperature: float = 0.7) -> None:
- self.model = model
- self.temperature = temperature
- self.captures: list[str] = []
- self._system_prompt: str = ""
- self._client: anthropic.Anthropic = anthropic.Anthropic()
- def build_system_prompt(self, posture: str, intents: list[str]) -> str:
- template_path = Path(__file__).parent.parent / "prompts" / "actor.md"
- template = Template(template_path.read_text())
- self._system_prompt = template.render(posture=posture, intents=intents)
- return self._system_prompt
- def append_capture(self, terminal_output: str) -> None:
- self.captures.append(terminal_output)
- def build_messages(self) -> list[dict[str, str]]:
- return [{"role": "user", "content": capture} for capture in self.captures]
- def decide(self) -> ActorAction:
- response = self._client.messages.create(
- model=self.model,
- max_tokens=1024,
- temperature=self.temperature,
- system=self._system_prompt,
- tools=[ACTOR_TOOL], # ty: ignore[invalid-argument-type]
- tool_choice={"type": "tool", "name": "terminal_action"},
- messages=self.build_messages(), # ty: ignore[invalid-argument-type]
- )
- for block in response.content:
- if block.type == "tool_use":
- return ActorAction.from_tool_result(block.input)
- raise RuntimeError("Actor did not return a tool_use block")
|