Files
SIA/sia/web_agent.py
2024-11-01 18:31:12 +01:00

143 lines
4.9 KiB
Python

from enum import Enum, auto
from typing import Callable, List, Optional
from .base_agent import BaseAgent
from .command import Command
from .command_result import CommandResult
from .llm_engine import LlmEngine
from .response_parser import ResponseParser
from .system_metrics import SystemMetrics
from .working_memory import WorkingMemory
from .xml_validator import XMLValidator
class WebAgentState(Enum):
"""
States for the web agent state machine.
States:
UPDATE: Updating system metrics and working memory entries
CONTEXT_APPROVAL: Waiting for human approval of context
INFERENCE: Processing context through LLM
RESPONSE_APPROVAL: Waiting for human approval of LLM response
"""
UPDATE = auto()
CONTEXT_APPROVAL = auto()
INFERENCE = auto()
RESPONSE_APPROVAL = auto()
class WebAgent(BaseAgent):
"""
Agent implementation for interactive web interface.
Uses a state machine to allow human intervention between steps.
Broadcasts state changes to registered handlers.
"""
def __init__(self,
system_prompt: str,
action_schema: str,
working_memory: WorkingMemory,
system_metrics: SystemMetrics,
llm: LlmEngine,
validator: XMLValidator,
parser: ResponseParser):
"""
Initialize web agent with required components.
"""
super().__init__(action_schema, working_memory, system_metrics, llm, validator, parser)
self.context = ""
self.response = ""
self._system_prompt = system_prompt
self._current_state = WebAgentState.UPDATE
self._validation_error: Optional[str] = None
self._state_change_handlers: List[Callable[[WebAgentState], None]] = []
self._response_change_handlers: List[Callable[[str], None]] = []
self._command_result: Optional[CommandResult] = None
@property
def current_state(self) -> WebAgentState:
"""Get the current state of the agent."""
return self._current_state
@property
def command_result(self) -> Optional[CommandResult]:
"""Get the result of the last command execution."""
return self._command_result
def add_state_change_handler(self, handler: Callable[[WebAgentState], None]) -> None:
"""
Add a callback for state changes.
Args:
handler: Function to call with new state
"""
if handler not in self._state_change_handlers:
self._state_change_handlers.append(handler)
def add_response_change_handler(self, handler: Callable[[str], None]) -> None:
"""
Add a callback for response changes.
Args:
handler: Function to call with new response
"""
if handler not in self._response_change_handlers:
self._response_change_handlers.append(handler)
def _notify_state_change(self) -> None:
"""Notify all handlers of state change."""
for handler in self._state_change_handlers:
handler(self._current_state)
def _notify_response_change(self) -> None:
"""Notify all handlers of response change."""
for handler in self._response_change_handlers:
handler(self._response)
def _set_response(self, response: str) -> None:
"""
Set the current response and notify handlers.
Args:
response: New response
"""
self._response = response
self._validation_error = self._validator.validate(self._response)
self._notify_response_change()
def approve_context(self) -> None:
if self._current_state != WebAgentState.CONTEXT_APPROVAL:
raise Exception("Not in CONTEXT_APPROVAL state")
self._set_response("")
self._current_state = WebAgentState.INFERENCE
self._notify_state_change()
response_token_iter = self._llm.infer(self._system_prompt, self.context)
for token in response_token_iter:
self._set_response(self._response + token)
self._current_state = WebAgentState.RESPONSE_APPROVAL
self._notify_state_change()
def approve_response(self) -> None:
if self._current_state != WebAgentState.RESPONSE_APPROVAL:
raise Exception("Not in RESPONSE_APPROVAL state")
self._current_state = WebAgentState.UPDATE
self._notify_state_change()
parse_result = self._parser.parse(self._response)
if isinstance(parse_result, Command):
result = parse_result.execute(self._working_memory)
self._command_result = result
else:
self._working_memory.add_entry(parse_result)
self._working_memory.update()
self._context = self._compile_context()
self._current_state = WebAgentState.CONTEXT_APPROVAL
self._notify_state_change()