Emit tool-result for post-handoff tool calls
When the LLM returns tool calls after a handoff in the same assistant message, they were silently dropped. This left orphaned tool_call entries without matching tool-result messages, causing protocol errors on the next LLM turn. Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
@@ -2746,6 +2746,90 @@ func TestRun_HandoffWithPreHandoffTools(t *testing.T) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
t.Run(
|
||||||
|
"tools after handoff get tool-result messages",
|
||||||
|
func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
type Params struct{}
|
||||||
|
tool1 := agent.FunctionTool[Params](
|
||||||
|
"prepare",
|
||||||
|
"Prepare data",
|
||||||
|
func(_ context.Context, _ Params) (agent.ToolResult, error) {
|
||||||
|
return agent.ToolResult{Content: "prepared"}, nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
tool2 := agent.FunctionTool[Params](
|
||||||
|
"finalize",
|
||||||
|
"Finalize data",
|
||||||
|
func(_ context.Context, _ Params) (agent.ToolResult, error) {
|
||||||
|
t.Fatal("tool after handoff should not be executed")
|
||||||
|
return agent.ToolResult{}, nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
provider := &mockProvider{
|
||||||
|
responses: []*llm.ChatCompletionResponse{
|
||||||
|
toolCallResponse(
|
||||||
|
llm.ToolCall{
|
||||||
|
ID: "tc_1",
|
||||||
|
Function: llm.FunctionCall{Name: "prepare", Arguments: `{}`},
|
||||||
|
},
|
||||||
|
llm.ToolCall{
|
||||||
|
ID: "tc_2",
|
||||||
|
Function: llm.FunctionCall{Name: "transfer_to_specialist", Arguments: `{}`},
|
||||||
|
},
|
||||||
|
llm.ToolCall{
|
||||||
|
ID: "tc_3",
|
||||||
|
Function: llm.FunctionCall{Name: "finalize", Arguments: `{}`},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
stopResponse("Specialist here."),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
client := newTestClient(provider)
|
||||||
|
|
||||||
|
specialist := agent.New(
|
||||||
|
"specialist",
|
||||||
|
client,
|
||||||
|
agent.WithModel("test-model"),
|
||||||
|
)
|
||||||
|
|
||||||
|
router := agent.New(
|
||||||
|
"router",
|
||||||
|
client,
|
||||||
|
agent.WithModel("test-model"),
|
||||||
|
agent.WithTools(tool1, tool2),
|
||||||
|
agent.WithHandoffs(specialist),
|
||||||
|
)
|
||||||
|
|
||||||
|
result, err := router.Run(
|
||||||
|
context.Background(),
|
||||||
|
[]llm.Message{userMessage("prepare, transfer, and finalize")},
|
||||||
|
)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "Specialist here.", result.FinalMessage().Text())
|
||||||
|
assert.Equal(t, "specialist", result.LastAgent.Name())
|
||||||
|
|
||||||
|
var toolMsgs []llm.Message
|
||||||
|
for _, m := range result.Messages {
|
||||||
|
if m.Role == llm.RoleTool {
|
||||||
|
toolMsgs = append(toolMsgs, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Len(t, toolMsgs, 3)
|
||||||
|
assert.Equal(t, "tc_1", toolMsgs[0].ToolCallID)
|
||||||
|
assert.Equal(t, "prepared", toolMsgs[0].Text())
|
||||||
|
assert.Equal(t, "tc_2", toolMsgs[1].ToolCallID)
|
||||||
|
assert.Contains(t, toolMsgs[1].Text(), "Transferred to specialist")
|
||||||
|
assert.Equal(t, "tc_3", toolMsgs[2].ToolCallID)
|
||||||
|
assert.Contains(t, toolMsgs[2].Text(), "not executed")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
t.Run(
|
t.Run(
|
||||||
"pre-handoff tool error aborts handoff",
|
"pre-handoff tool error aborts handoff",
|
||||||
func(t *testing.T) {
|
func(t *testing.T) {
|
||||||
|
|||||||
@@ -645,6 +645,19 @@ func executeWithHandoff(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for i := handoffIdx + 1; i < len(toolCalls); i++ {
|
||||||
|
msgs = append(
|
||||||
|
msgs,
|
||||||
|
llm.Message{
|
||||||
|
Role: llm.RoleTool,
|
||||||
|
ToolCallID: toolCalls[i].ID,
|
||||||
|
Parts: []llm.Part{
|
||||||
|
llm.TextPart{Text: "Tool call was not executed because a handoff occurred."},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return ht.handoff, results, msgs, nil
|
return ht.handoff, results, msgs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user