From 837e41459a2665228c714e66ab49f32ca60c7f0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Fri, 24 Jul 2026 15:58:55 +0800 Subject: [PATCH] Add files via upload --- internal/agent/agent.go | 36 +++++++---------- internal/agent/agent_test.go | 75 ++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 22 deletions(-) diff --git a/internal/agent/agent.go b/internal/agent/agent.go index 1a06fe56..c793e129 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -514,6 +514,14 @@ type ToolExecutionResult struct { IsError bool } +func buildToolFailureMessage(toolName, detail string, err error) string { + var b strings.Builder + fmt.Fprintf(&b, "工具调用失败\n\n") + fmt.Fprintf(&b, "工具名称: %s\n", toolName) + fmt.Fprintf(&b, "错误详情: %s", detail) + return strings.TrimRight(b.String(), "\n") +} + // executeToolViaMCP 通过MCP执行工具 // 即使工具执行失败,也返回结果而不是错误,让AI能够处理错误情况 func (a *Agent) executeToolViaMCP(ctx context.Context, toolName string, args map[string]interface{}) (*ToolExecutionResult, error) { @@ -573,32 +581,16 @@ func (a *Agent) executeToolViaMCP(ctx context.Context, toolName string, args map // 如果调用失败(如工具不存在、超时),返回友好的错误信息而不是抛出异常 if err != nil { detail := err.Error() + timeoutMinutes := 10 + if a.agentConfig != nil && a.agentConfig.ToolTimeoutMinutes > 0 { + timeoutMinutes = a.agentConfig.ToolTimeoutMinutes + } if errors.Is(err, context.Canceled) { detail = "工具调用已被手动终止(MCP 监控页)。智能体将携带此结果继续后续步骤,整条任务不会因此被停止。" } else if errors.Is(err, context.DeadlineExceeded) { - min := 10 - if a.agentConfig != nil && a.agentConfig.ToolTimeoutMinutes > 0 { - min = a.agentConfig.ToolTimeoutMinutes - } - detail = fmt.Sprintf("工具执行超过 %d 分钟被自动终止(可在 config.yaml 的 agent.tool_timeout_minutes 中调整)", min) + detail = fmt.Sprintf("工具执行超过 %d 分钟被自动终止(可在 config.yaml 的 agent.tool_timeout_minutes 中调整)", timeoutMinutes) } - errorMsg := fmt.Sprintf(`工具调用失败 - -工具名称: %s -错误类型: 系统错误 -错误详情: %s - -可能的原因: -- 工具 "%s" 不存在或未启用 -- 单次执行超时(agent.tool_timeout_minutes) -- 系统配置问题 -- 网络或权限问题 - -建议: -- 检查工具名称是否正确 -- 若需更长执行时间,可适当增大 agent.tool_timeout_minutes -- 尝试使用其他替代工具 -- 如果这是必需的工具,请向用户说明情况`, toolName, detail, toolName) + errorMsg := buildToolFailureMessage(toolName, detail, err) return &ToolExecutionResult{ Result: errorMsg, diff --git a/internal/agent/agent_test.go b/internal/agent/agent_test.go index 34e40512..b2f04571 100644 --- a/internal/agent/agent_test.go +++ b/internal/agent/agent_test.go @@ -2,6 +2,7 @@ package agent import ( "context" + "errors" "strings" "sync" "testing" @@ -71,6 +72,80 @@ func TestAgent_NewAgent_CustomConfig(t *testing.T) { } } +func TestBuildToolFailureMessageAuthorizationDenied(t *testing.T) { + msg := buildToolFailureMessage( + "list_project_facts", + "tool authorization denied: no access to project", + errors.New("tool authorization denied: no access to project"), + ) + for _, want := range []string{ + "工具名称: list_project_facts", + "错误详情: tool authorization denied: no access to project", + } { + if !strings.Contains(msg, want) { + t.Fatalf("message missing %q:\n%s", want, msg) + } + } + for _, notWant := range []string{ + "可能的原因", + "建议", + "错误类型", + "工具 \"list_project_facts\" 不存在或未启用", + "单次执行超时", + } { + if strings.Contains(msg, notWant) { + t.Fatalf("message should not include generic hint %q:\n%s", notWant, msg) + } + } +} + +func TestBuildToolFailureMessageCanceled(t *testing.T) { + msg := buildToolFailureMessage( + "long_running_tool", + "工具调用已被手动终止(MCP 监控页)。智能体将携带此结果继续后续步骤,整条任务不会因此被停止。", + context.Canceled, + ) + + for _, want := range []string{ + "工具名称: long_running_tool", + "错误详情: 工具调用已被手动终止", + } { + if !strings.Contains(msg, want) { + t.Fatalf("message missing %q:\n%s", want, msg) + } + } +} + +func TestBuildToolFailureMessageDeadlineExceeded(t *testing.T) { + msg := buildToolFailureMessage( + "nmap", + "工具执行超过 15 分钟被自动终止(可在 config.yaml 的 agent.tool_timeout_minutes 中调整)", + context.DeadlineExceeded, + ) + + for _, want := range []string{ + "工具名称: nmap", + "错误详情: 工具执行超过 15 分钟被自动终止", + } { + if !strings.Contains(msg, want) { + t.Fatalf("message missing %q:\n%s", want, msg) + } + } +} + +func TestBuildToolFailureMessageUnknownKeepsGenericFallback(t *testing.T) { + msg := buildToolFailureMessage("custom_tool", "dial tcp: connection reset by peer", errors.New("dial tcp: connection reset by peer")) + + for _, want := range []string{ + "工具名称: custom_tool", + "错误详情: dial tcp: connection reset by peer", + } { + if !strings.Contains(msg, want) { + t.Fatalf("message missing %q:\n%s", want, msg) + } + } +} + func TestAgentCancelRunningMCPToolsForConversation(t *testing.T) { ag := setupTestAgent(t) ag.mcpServer.ConfigureToolWaitTimeoutSeconds(1)