diff --git a/backend/pkg/cast/chain_ast.go b/backend/pkg/cast/chain_ast.go index 5a84763a2..33fa6bf09 100644 --- a/backend/pkg/cast/chain_ast.go +++ b/backend/pkg/cast/chain_ast.go @@ -453,7 +453,11 @@ func NewBodyPair(aiMsg *llms.MessageContent, toolMsgs []*llms.MessageContent) *B break } } - for _, id := range partsToDelete { + // Delete in reverse so each removal does not shift the indices of the + // entries still pending deletion (a forward loop with the pre-collected + // ascending indices would delete the wrong elements once there are 2+). + for i := len(partsToDelete) - 1; i >= 0; i-- { + id := partsToDelete[i] aiMsg.Parts = append(aiMsg.Parts[:id], aiMsg.Parts[id+1:]...) } } diff --git a/backend/pkg/cast/newbodypair_reg_test.go b/backend/pkg/cast/newbodypair_reg_test.go new file mode 100644 index 000000000..d1ebe5645 --- /dev/null +++ b/backend/pkg/cast/newbodypair_reg_test.go @@ -0,0 +1,28 @@ +package cast + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/vxcontrol/langchaingo/llms" +) + +func TestNewBodyPair_DropsMultipleNilFunctionCallToolCalls(t *testing.T) { + aiMsg := &llms.MessageContent{ + Role: llms.ChatMessageTypeAI, + Parts: []llms.ContentPart{ + llms.ToolCall{ID: "a", Type: "function", FunctionCall: nil}, + llms.ToolCall{ID: "b", Type: "function", FunctionCall: nil}, + llms.TextContent{Text: "keep me"}, + }, + } + + NewBodyPair(aiMsg, nil) + + assert.Len(t, aiMsg.Parts, 1, "both nil-FunctionCall tool calls should be removed, text kept") + if assert.Len(t, aiMsg.Parts, 1) { + txt, ok := aiMsg.Parts[0].(llms.TextContent) + assert.True(t, ok, "surviving part should be the TextContent, got %T", aiMsg.Parts[0]) + assert.Equal(t, "keep me", txt.Text) + } +}