Log task planner conversations locally
Tests / Test apps.device-host-agent.tests.test_mcp_token.test_load_or_create_concurrent_calls_do_not_corrupt failed
Tests / Test apps.device-host-agent.tests.test_mcp_token.test_load_or_create_concurrent_calls_do_not_corrupt failed
This commit is contained in:
@@ -231,12 +231,14 @@ def create_application(
|
||||
"MCP token generated at %s", mcp_token_path
|
||||
)
|
||||
mcp_busy_tracker = McpBusyTracker(ttl_seconds=20.0)
|
||||
conversation_log = ConversationLogStore(resolved_config.conversation_log_path) if resolved_config.mode == "local" else None
|
||||
executor = AssignmentExecutor(
|
||||
create_execution_factories(
|
||||
resolved_manager,
|
||||
metadata_store=metadata_store,
|
||||
timeline=timeline,
|
||||
host_agent_config=resolved_config,
|
||||
conversation_log=conversation_log,
|
||||
),
|
||||
mcp_busy_tracker=mcp_busy_tracker,
|
||||
)
|
||||
@@ -246,7 +248,6 @@ def create_application(
|
||||
status_tracker=status_tracker,
|
||||
)
|
||||
planner_config = load_planner_config()
|
||||
conversation_log = ConversationLogStore(resolved_config.conversation_log_path) if resolved_config.mode == "local" else None
|
||||
conversation_agent = (
|
||||
ConversationAgent(
|
||||
config=planner_config,
|
||||
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
import os
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass, replace
|
||||
from typing import Any
|
||||
|
||||
from device.manager import DeviceManager
|
||||
from host_agent.cloud_planner_client import CloudProxyToolCallingClient
|
||||
@@ -35,6 +36,7 @@ def create_execution_factories(
|
||||
metadata_store: TaskMetadataStore | None = None,
|
||||
timeline: Timeline | None = None,
|
||||
host_agent_config: HostAgentConfig | None = None,
|
||||
conversation_log: Any | None = None,
|
||||
) -> ExecutionFactories:
|
||||
shared_workflow_store = workflow_store or WorkflowStore()
|
||||
resolved_host_agent_config = host_agent_config
|
||||
@@ -48,7 +50,10 @@ def create_execution_factories(
|
||||
),
|
||||
metadata_store=metadata_store,
|
||||
timeline=timeline,
|
||||
planner=_host_agent_planner(resolved_host_agent_config),
|
||||
planner=_host_agent_planner(
|
||||
resolved_host_agent_config,
|
||||
event_logger=conversation_log.append if conversation_log is not None else None,
|
||||
),
|
||||
planner_config=_host_agent_planner_config(),
|
||||
device_platform_provider=lambda device_id: _device_platform(
|
||||
manager, device_id
|
||||
@@ -86,6 +91,8 @@ def _host_agent_planner_config() -> PlannerConfig:
|
||||
|
||||
def _host_agent_planner(
|
||||
host_agent_config: HostAgentConfig | None,
|
||||
*,
|
||||
event_logger: Callable[[dict[str, Any]], None] | None = None,
|
||||
) -> Planner | None:
|
||||
"""Build the `AIPlanner` explicitly when the cloud-proxy transport is
|
||||
selected, so its `ToolCallingClient` is a `CloudProxyToolCallingClient`
|
||||
@@ -100,11 +107,12 @@ def _host_agent_planner(
|
||||
|
||||
resolved_config = host_agent_config or load_host_agent_config()
|
||||
if resolved_config.ai_planner_transport != "cloud":
|
||||
return None
|
||||
return AIPlanner(config=planner_config, event_logger=event_logger)
|
||||
|
||||
return AIPlanner(
|
||||
client=CloudProxyToolCallingClient(resolved_config),
|
||||
config=planner_config,
|
||||
event_logger=event_logger,
|
||||
)
|
||||
|
||||
|
||||
|
||||
+28
-7
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from core.errors import TaskFailedError
|
||||
@@ -23,9 +24,11 @@ class AIPlanner(Planner):
|
||||
*,
|
||||
client: ToolCallingClient | None = None,
|
||||
config: PlannerConfig | None = None,
|
||||
event_logger: Callable[[dict[str, Any]], None] | None = None,
|
||||
) -> None:
|
||||
self.config = config or load_config()
|
||||
self.client = client or build_client(self.config)
|
||||
self.event_logger = event_logger
|
||||
|
||||
def plan(
|
||||
self,
|
||||
@@ -49,13 +52,24 @@ class AIPlanner(Planner):
|
||||
"corrected action or finish the task if it cannot proceed.\n"
|
||||
f"Previous failure: {context.step_results[-1].error or 'unknown error'}"
|
||||
)
|
||||
decision = self.client.decide(
|
||||
system_prompt=PLANNER_SYSTEM_PROMPT,
|
||||
user_prompt=user_prompt,
|
||||
screenshot=screenshot,
|
||||
tools=ALL_TOOL_SPECS,
|
||||
timeout=self.config.timeout,
|
||||
)
|
||||
self._log({"type": "llm_request", "task_id": context.task_id, "goal": goal,
|
||||
"system_prompt": PLANNER_SYSTEM_PROMPT, "user_prompt": user_prompt,
|
||||
"has_screenshot": screenshot is not None})
|
||||
try:
|
||||
decision = self.client.decide(
|
||||
system_prompt=PLANNER_SYSTEM_PROMPT,
|
||||
user_prompt=user_prompt,
|
||||
screenshot=screenshot,
|
||||
tools=ALL_TOOL_SPECS,
|
||||
timeout=self.config.timeout,
|
||||
)
|
||||
except Exception as exc:
|
||||
self._log({"type": "agent_error", "task_id": context.task_id, "error": str(exc)})
|
||||
raise
|
||||
self._log({"type": "llm_response", "task_id": context.task_id,
|
||||
"content": decision.text_output, "thinking": decision.thinking,
|
||||
"tool_name": decision.tool_name, "arguments": decision.arguments,
|
||||
"purpose": decision.purpose, "expected_outcome": decision.expected_outcome})
|
||||
|
||||
if decision.tool_name == FINISH_TASK_TOOL:
|
||||
if decision.arguments.get("success"):
|
||||
@@ -85,6 +99,13 @@ class AIPlanner(Planner):
|
||||
# (mapped to an empty plan above), never via this hook.
|
||||
return False
|
||||
|
||||
def _log(self, event: dict[str, Any]) -> None:
|
||||
if self.event_logger is not None:
|
||||
try:
|
||||
self.event_logger(event)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _history_summary(world: "WorldState | None") -> list[dict[str, Any]]:
|
||||
if world is None:
|
||||
|
||||
Reference in New Issue
Block a user