Skip to content

Commit 08d7254

Browse files
authored
Merge pull request #42 from DEEIX-AI/anthropic_tool
fix: repair Anthropic native tool traces bug
2 parents c13cd43 + 5564b62 commit 08d7254

8 files changed

Lines changed: 544 additions & 47 deletions

File tree

backend/internal/application/conversation/service_message_send.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -968,6 +968,10 @@ func (s *Service) sendMessageInternal(
968968
toolSpan.End()
969969
toolCallRows = append(toolCallRows, toolResult.Rows...)
970970
remainingToolCalls -= len(toolResult.Rows)
971+
if toolResult.FatalErr != nil {
972+
retErr = wrapUpstreamRequestError(toolResult.FatalErr)
973+
return nil, retErr
974+
}
971975
if len(toolResult.ToolResults) == 0 {
972976
break
973977
}

backend/internal/application/conversation/service_tool_execution.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package conversation
33
import (
44
"context"
55
"encoding/json"
6+
"fmt"
67
"strings"
78
"time"
89

@@ -31,6 +32,7 @@ type executeAssistantToolCallsResult struct {
3132
Rows []model.ToolCall
3233
ToolResults []llm.ToolResult
3334
ExecutedToolCalls []llm.ToolCall
35+
FatalErr error
3436
}
3537

3638
type toolExecutionRecord struct {
@@ -66,6 +68,7 @@ func (s *Service) executeAssistantToolCalls(ctx context.Context, input executeAs
6668
}
6769

6870
slots := make([]toolExecutionSlot, len(toolCalls))
71+
var fatalErr error
6972
for i, item := range toolCalls {
7073
modelToolName := strings.TrimSpace(item.ToolName)
7174
executionToolName := resolveExecutionToolName(modelToolName, input.ToolNameMap)
@@ -81,6 +84,23 @@ func (s *Service) executeAssistantToolCalls(ctx context.Context, input executeAs
8184
ErrorJSON: "",
8285
}
8386

87+
mcpConfig := resolveMCPConfig(modelToolName, input.MCPConfigs)
88+
if mcpConfig == nil {
89+
row.Status = "error"
90+
row.ErrorJSON = toolNotEnabledForRunMessage(modelToolName)
91+
slots[i] = toolExecutionSlot{
92+
row: row,
93+
result: buildToolResultForModel(row, modelToolName),
94+
}
95+
if fatalErr == nil {
96+
fatalErr = fmt.Errorf("model requested tool %q, but it is not enabled for this run", modelToolName)
97+
}
98+
if input.Ledger != nil {
99+
input.Ledger.store(row.ToolName, row.InputJSON, toolExecutionRecord{row: row, result: slots[i].result})
100+
}
101+
continue
102+
}
103+
84104
normalizedInput, validationErr := normalizeToolArguments(row.InputJSON, input.ToolSchemas[modelToolName])
85105
if validationErr != nil {
86106
row.Status = "error"
@@ -110,7 +130,7 @@ func (s *Service) executeAssistantToolCalls(ctx context.Context, input executeAs
110130
RequestID: strings.TrimSpace(input.RequestID),
111131
ToolName: row.ToolName,
112132
ArgumentsJSON: row.InputJSON,
113-
MCPConfig: resolveMCPConfig(modelToolName, input.MCPConfigs),
133+
MCPConfig: mcpConfig,
114134
})
115135
row.LatencyMS = time.Since(toolStartedAt).Milliseconds()
116136
if row.LatencyMS < 0 {
@@ -151,6 +171,7 @@ func (s *Service) executeAssistantToolCalls(ctx context.Context, input executeAs
151171
Rows: rows,
152172
ToolResults: toolResults,
153173
ExecutedToolCalls: executedToolCalls,
174+
FatalErr: fatalErr,
154175
}
155176
}
156177

@@ -218,6 +239,14 @@ func buildToolResultForModel(row model.ToolCall, modelToolName string) llm.ToolR
218239
}
219240
}
220241

242+
func toolNotEnabledForRunMessage(toolName string) string {
243+
name := strings.TrimSpace(toolName)
244+
if name == "" {
245+
return "tool is not enabled for this run"
246+
}
247+
return fmt.Sprintf("tool %s is not enabled for this run", name)
248+
}
249+
221250
func budgetToolOutputForModel(raw string, maxChars int) string {
222251
value := strings.TrimSpace(raw)
223252
if value == "" || maxChars <= 0 || len([]rune(value)) <= maxChars {

backend/internal/application/conversation/service_tool_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"testing"
77

88
"github.com/DEEIX-AI/DEEIX-Chat/backend/internal/infra/config"
9+
"github.com/DEEIX-AI/DEEIX-Chat/backend/internal/infra/llm"
910
)
1011

1112
func TestExecuteToolCallRejectsToolsNotEnabledForRun(t *testing.T) {
@@ -19,6 +20,30 @@ func TestExecuteToolCallRejectsToolsNotEnabledForRun(t *testing.T) {
1920
}
2021
}
2122

23+
func TestExecuteAssistantToolCallsStopsWhenToolNotEnabledForRun(t *testing.T) {
24+
svc := &Service{}
25+
result := svc.executeAssistantToolCalls(context.Background(), executeAssistantToolCallsInput{
26+
RunID: "run_1",
27+
ToolCalls: []llm.ToolCall{{
28+
ToolCallID: "toolu_1",
29+
ToolType: "function",
30+
ToolName: "web_search",
31+
ArgumentsJSON: `{"query":"weather"}`,
32+
Status: "requested",
33+
}},
34+
})
35+
36+
if result.FatalErr == nil || !strings.Contains(result.FatalErr.Error(), "not enabled for this run") {
37+
t.Fatalf("expected fatal disabled tool error, got %v", result.FatalErr)
38+
}
39+
if len(result.Rows) != 1 || result.Rows[0].Status != "error" || result.Rows[0].ToolName != "web_search" {
40+
t.Fatalf("expected one failed tool row, got %#v", result.Rows)
41+
}
42+
if len(result.ToolResults) != 1 || result.ToolResults[0].Status != "error" {
43+
t.Fatalf("expected failed model tool result, got %#v", result.ToolResults)
44+
}
45+
}
46+
2247
func TestResolveMaxLLMCallsPerRunRequiresFollowUpRound(t *testing.T) {
2348
svc := &Service{cfg: config.NewRuntime(config.Config{MCPMaxLLMCallsPerRun: 1})}
2449
if got := svc.resolveMaxLLMCallsPerRun(); got != 2 {

0 commit comments

Comments
 (0)