fix(tools): preserve context memory and thought signatures in toolloop

- Retain  and  during multi-turn tool calls to resolve API 400 errors (missing a thought_signature).
- Append the final assistant message to the session history before exiting the loop, fixing the state memory loss issue in the  team strategy.
- Add fallback to  when standard content is empty (e.g., for Gemini 2.0 Pro Thinking).
- Preserve  during token budget exhaustion and truncation recovery to prevent broken chain-of-thought.
This commit is contained in:
Administrator 2026-03-12 18:31:41 +08:00
parent bac9a1c84b
commit d22ca85481

View file

@ -94,6 +94,7 @@ func RunToolLoop(
messages = append(messages, providers.Message{ messages = append(messages, providers.Message{
Role: "assistant", Role: "assistant",
Content: response.Content, Content: response.Content,
ReasoningContent: response.ReasoningContent, // [Fix] Preserve reasoning content to maintain context
}) })
messages = append(messages, providers.Message{ messages = append(messages, providers.Message{
Role: "user", Role: "user",
@ -123,6 +124,7 @@ func RunToolLoop(
messages = append(messages, providers.Message{ messages = append(messages, providers.Message{
Role: "assistant", Role: "assistant",
Content: response.Content, Content: response.Content,
ReasoningContent: response.ReasoningContent, // [Fix] Preserve reasoning content to prevent broken chain of thought
}) })
messages = append(messages, providers.Message{ messages = append(messages, providers.Message{
Role: "user", Role: "user",
@ -134,11 +136,25 @@ func RunToolLoop(
// 4. If no tool calls, we're done // 4. If no tool calls, we're done
if len(response.ToolCalls) == 0 { if len(response.ToolCalls) == 0 {
finalContent = response.Content finalContent = response.Content
// [Fix] Fallback for models (like Gemini 2.0 Pro Thinking) that put output in reasoning block
if finalContent == "" && response.ReasoningContent != "" {
finalContent = response.ReasoningContent
}
logger.InfoCF("toolloop", "LLM response without tool calls (direct answer)", logger.InfoCF("toolloop", "LLM response without tool calls (direct answer)",
map[string]any{ map[string]any{
"iteration": iteration, "iteration": iteration,
"content_chars": len(finalContent), "content_chars": len(finalContent),
}) })
// [Fix] Append the final answer to the messages array!
// Essential for Team's evaluator_optimizer strategy to retain state in the next loop.
messages = append(messages, providers.Message{
Role: "assistant",
Content: finalContent,
ReasoningContent: response.ReasoningContent,
})
break break
} }
@ -163,9 +179,18 @@ func RunToolLoop(
assistantMsg := providers.Message{ assistantMsg := providers.Message{
Role: "assistant", Role: "assistant",
Content: response.Content, Content: response.Content,
ReasoningContent: response.ReasoningContent, // [Fix] Include ReasoningContent
} }
for _, tc := range normalizedToolCalls { for _, tc := range normalizedToolCalls {
argumentsJSON, _ := json.Marshal(tc.Arguments) argumentsJSON, _ := json.Marshal(tc.Arguments)
// [Fix] Preserve ThoughtSignature and ExtraContent for compatibility with models like Gemini 2.0/3.0
extraContent := tc.ExtraContent
thoughtSignature := ""
if tc.Function != nil {
thoughtSignature = tc.Function.ThoughtSignature
}
assistantMsg.ToolCalls = append(assistantMsg.ToolCalls, providers.ToolCall{ assistantMsg.ToolCalls = append(assistantMsg.ToolCalls, providers.ToolCall{
ID: tc.ID, ID: tc.ID,
Type: "function", Type: "function",
@ -174,7 +199,10 @@ func RunToolLoop(
Function: &providers.FunctionCall{ Function: &providers.FunctionCall{
Name: tc.Name, Name: tc.Name,
Arguments: string(argumentsJSON), Arguments: string(argumentsJSON),
ThoughtSignature: thoughtSignature, // [Fix] Preserve thought signature
}, },
ExtraContent: extraContent, // [Fix] Preserve extra content
ThoughtSignature: thoughtSignature, // [Fix] Preserve thought signature
}) })
} }
messages = append(messages, assistantMsg) messages = append(messages, assistantMsg)