New web interface, move llm engine to separate process

This commit is contained in:
2025-05-20 09:43:17 +02:00
parent 895a533e01
commit d4a4902b94
137 changed files with 4850 additions and 3503 deletions

View File

@@ -1,13 +1,14 @@
from dataclasses import dataclass
from threading import Thread, Event
from typing import Callable, TypeAlias, TypedDict
from .web_agent import WebAgent, LlmState
from typing import Callable, TypeAlias
from .web_agent import WebAgent, AgentState
class AutoApproverConfig(TypedDict):
@dataclass
class AutoApproverConfig:
context_enabled: bool
response_enabled: bool
context_timeout: float
response_timeout: float
llm_name: str
ConfigChangeHandler: TypeAlias = Callable[[AutoApproverConfig], None]
@@ -21,48 +22,47 @@ class AutoApprover:
Initialize auto approver with a WebAgent instance.
"""
self.agent = agent
self._llm_name = next(iter(agent.llms.keys()))
self._context_timeout = 5.0
self._response_timeout = 10.0
self._context_enabled = False
self._response_enabled = False
self._config = AutoApproverConfig(
context_enabled=False,
response_enabled=False,
context_timeout=1.0,
response_timeout=1.0,
)
self._stop_event = Event()
self._context_thread: Thread | None = None
self._response_thread: Thread | None = None
self._config_change_handlers: list[ConfigChangeHandler] = []
self.agent.add_llm_change_handler(self._handle_llm_state_change)
self.agent.add_context_change_handler(self._handle_context_change)
self._previous_state = agent.state
self.agent.add_state_change_handler(self._handle_state_change)
@property
def config(self) -> AutoApproverConfig:
return AutoApproverConfig(
context_enabled=self._context_enabled,
response_enabled=self._response_enabled,
context_timeout=self._context_timeout,
response_timeout=self._response_timeout,
llm_name=self._llm_name
)
return self._config
def set_config(self, config: AutoApproverConfig) -> None:
if config['llm_name'] not in self.agent.llms:
raise ValueError(f"Unknown LLM: {config['llm_name']}")
notify_config_change = self._notify_config_change
self._notify_config_change = lambda: None
try:
self.context_enabled = False
self.response_enabled = False
self.context_timeout = config['context_timeout']
self.response_timeout = config['response_timeout']
self.llm_name = config['llm_name']
self.context_enabled = config['context_enabled']
self.response_enabled = config['response_enabled']
finally:
self._notify_config_change = notify_config_change
@config.setter
def config(self, config: AutoApproverConfig) -> None:
if config == self._config:
return
if config.context_timeout != self._config.context_timeout:
if config.context_enabled and self._config.context_enabled:
raise ValueError("Cannot change context timeout while enabled")
if config.response_timeout != self._config.response_timeout:
if config.response_enabled and self._config.response_enabled:
raise ValueError("Cannot change response timeout while enabled")
self._stop_context_thread()
self._stop_response_thread()
# If both context and response are enabled, start response approval only.
# Better to run inference with a full prefix than approve an emmpty response.
if config.response_enabled and not self._config.response_enabled:
# response approval was just enabled
self._config = config
self._start_response_thread()
elif config.context_enabled and not self._config.context_enabled:
# context approval was just enabled
self._config = config
self._start_context_thread()
self._config = config
self._notify_config_change()
def add_config_change_handler(self, handler: ConfigChangeHandler) -> None:
@@ -73,81 +73,17 @@ class AutoApprover:
for handler in self._config_change_handlers:
handler(current_config)
@property
def context_timeout(self) -> float:
return self._context_timeout
@context_timeout.setter
def context_timeout(self, timeout: float) -> None:
if self._context_enabled:
raise ValueError("Cannot change timeout while auto-approval is enabled")
self._context_timeout = timeout
self._notify_config_change()
@property
def response_timeout(self) -> float:
return self._response_timeout
@response_timeout.setter
def response_timeout(self, timeout: float) -> None:
if self._response_enabled:
raise ValueError("Cannot change timeout while auto-approval is enabled")
self._response_timeout = timeout
self._notify_config_change()
@property
def context_enabled(self) -> bool:
return self._context_enabled
@context_enabled.setter
def context_enabled(self, enabled: bool) -> None:
if enabled == self._context_enabled:
return
self._context_enabled = enabled
self._stop_context_thread()
if enabled and self.agent.llms[self._llm_name] == LlmState.IDLE:
self._start_context_thread()
self._notify_config_change()
@property
def response_enabled(self) -> bool:
return self._response_enabled
@response_enabled.setter
def response_enabled(self, enabled: bool) -> None:
if enabled == self._response_enabled:
return
self._response_enabled = enabled
self._stop_response_thread()
if enabled and self.agent.llms[self._llm_name] == LlmState.IDLE:
self._start_response_thread()
self._notify_config_change()
@property
def llm_name(self) -> str:
return self._llm_name
@llm_name.setter
def llm_name(self, name: str) -> None:
if name not in self.agent.llms:
raise ValueError(f"Unknown LLM: {name}")
self._llm_name = name
self._notify_config_change()
def _handle_llm_state_change(self, llm_name: str, state: LlmState) -> None:
if llm_name != self._llm_name:
return
if state == LlmState.IDLE and self._response_enabled:
self._start_response_thread()
else:
self._stop_response_thread()
def _handle_context_change(self, context: str, generated: bool) -> None:
if generated and self._context_enabled:
self._start_context_thread()
else:
self._stop_context_thread()
def _handle_state_change(self, state: AgentState) -> None:
if state == AgentState.IDLE:
if self._previous_state == AgentState.INFERENCE:
# finished inference
if self._config.response_enabled:
self._start_response_thread()
elif self._previous_state == AgentState.PROCESSING_RESPONSE:
# finished processing response
if self._config.context_enabled:
self._start_context_thread()
self._previous_state = state
def _stop_context_thread(self) -> None:
if self._context_thread:
@@ -170,13 +106,13 @@ class AutoApprover:
self._response_thread.start()
def _context_approval_thread(self) -> None:
if self._stop_event.wait(self._context_timeout):
if self._stop_event.wait(self._config.context_timeout):
return
if self._context_enabled:
self.agent.run_inference(self._llm_name)
if self._config.context_enabled:
self.agent.run_inference()
def _response_approval_thread(self) -> None:
if self._stop_event.wait(self._response_timeout):
if self._stop_event.wait(self._config.response_timeout):
return
if self._response_enabled:
self.agent.approve_response()
if self._config.response_enabled:
self.agent.approve_response()