fix(agent): normalize legacy tool failures

This commit is contained in:
jxxghp
2026-09-12 12:30:37 +08:00
parent c3eb884d8f
commit 7389be5694
2 changed files with 29 additions and 2 deletions

View File

@@ -130,6 +130,23 @@ def serialize_tool_result_for_agent(result: Any) -> str:
return str(result)
def normalize_tool_failure_for_agent(result: Any, *, tool_name: str) -> Any:
"""将旧工具返回的裸错误文本统一成模型可恢复的结构化失败回执。"""
if not isinstance(result, str):
return result
text = result.strip()
if not text or text.startswith("{") or text.startswith("["):
return result
markers = ("错误", "操作失败", "浏览器操作失败", "工具执行异常")
if not text.startswith(markers):
return result
return json.dumps({
"success": False,
"execution_outcome": "failed",
"tool": tool_name,
"error": text,
"recovery": "根据错误信息修正输入或改用正确工具后重试;不要重复未确认的写入。",
}, ensure_ascii=False)
TOOL_RESULT_RECORDER: ContextVar[Optional[Callable[[str, str], dict[str, Any]]]] = ContextVar(
"agent_tool_result_recorder", default=None,
)
@@ -537,7 +554,9 @@ class MoviePilotTool(BaseTool, metaclass=ABCMeta):
# 执行具体工具逻辑
try:
result = await self.run_with_timeout(**kwargs)
formatted_result = self.format_agent_result(result, **kwargs)
formatted_result = normalize_tool_failure_for_agent(
self.format_agent_result(result, **kwargs), tool_name=self.name,
)
logger.info(
f"Agent工具 {self.name} 返回结果,状态: {inspect_tool_result(formatted_result).value}"

View File

@@ -16,7 +16,7 @@ from app.agent.api.executor import ApiExecutionContext, MoviePilotApiExecutor
from app.agent.middleware.policy import AgentPolicyMiddleware
from app.agent.policy.contracts import AuthSource, ExecutionOutcome, PrincipalType, ToolOrigin, ToolPolicyContext
from app.agent.policy.orchestrator import AgentToolPolicyOrchestrator
from app.agent.tools.base import MoviePilotTool
from app.agent.tools.base import MoviePilotTool, normalize_tool_failure_for_agent
from app.agent.tools.impl.api import MoviePilotApiTool
from app.agent.tools.impl.mcp import McpExternalTool
from app.agent.tools.manager import MoviePilotToolsManager
@@ -182,6 +182,14 @@ def test_mcp_error_with_text_content_does_not_lose_error_flag():
assert McpExternalTool._format_mcp_result({"content": payload["content"]}) == "operation rejected"
def test_legacy_tool_error_text_gets_structured_recovery_contract():
"""旧浏览器、文件和命令工具的裸错误在 Agent 入口统一可恢复。"""
payload = json.loads(normalize_tool_failure_for_agent("错误:文件不存在", tool_name="read_file"))
assert payload["execution_outcome"] == "failed"
assert payload["tool"] == "read_file"
assert "修正输入" in payload["recovery"]
def _api_tool(request: AsyncMock) -> MoviePilotApiTool:
"""把内存 HTTP 请求替身接到真实 API 工具,覆盖异常映射全链路。"""
executor = MoviePilotApiExecutor(