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

This commit is contained in:
showtan001
2026-08-30 22:38:28 +08:00
parent 5458f3b8a4
commit dd8df33910
3 changed files with 40 additions and 10 deletions
+2 -1
View File
@@ -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,
+10 -2
View File
@@ -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
View File
@@ -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: