mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-06-10 00:13:42 +00:00
a565ad8402
Three layered bugs caused OpenAI-compatible providers to silently produce empty tool call arguments when max_tokens was hit mid-JSON: 1. FinishReason override: all providers unconditionally overwrote "length" → "tool_calls" when tool calls existed, preventing the agent loop's truncation guard from firing. 2. Silent parse failure: JSON unmarshal errors were logged but args stayed as empty map with no signal to the caller. 3. No fallback for unreliable providers: some proxies don't emit finish_reason:"length" at all, leaving no detection path. Fixes: - Add ParseError field to ToolCall struct for explicit error signal - Guard FinishReason override with `!= "length"` in OpenAI, Codex - Set ParseError in all provider parsers (OpenAI, Anthropic, Codex) - Add hasParseErrors() fallback guard in agent loop for EC-5 scenario - Cap consecutive truncation retries (maxTruncationRetries=3) to prevent burning all iterations when max_tokens is persistently low Closes #605
47 lines
1.2 KiB
Go
47 lines
1.2 KiB
Go
package agent
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/providers"
|
|
)
|
|
|
|
func TestHasParseErrors_NoCalls(t *testing.T) {
|
|
if hasParseErrors(nil) {
|
|
t.Error("nil slice should return false")
|
|
}
|
|
if hasParseErrors([]providers.ToolCall{}) {
|
|
t.Error("empty slice should return false")
|
|
}
|
|
}
|
|
|
|
func TestHasParseErrors_AllValid(t *testing.T) {
|
|
calls := []providers.ToolCall{
|
|
{ID: "1", Name: "read_file", Arguments: map[string]any{"path": "/tmp"}},
|
|
{ID: "2", Name: "exec", Arguments: map[string]any{"cmd": "ls"}},
|
|
}
|
|
if hasParseErrors(calls) {
|
|
t.Error("valid tool calls should return false")
|
|
}
|
|
}
|
|
|
|
func TestHasParseErrors_OneError(t *testing.T) {
|
|
calls := []providers.ToolCall{
|
|
{ID: "1", Name: "read_file", Arguments: map[string]any{"path": "/tmp"}},
|
|
{ID: "2", Name: "write_file", ParseError: "malformed JSON (42 chars): unexpected end of JSON input"},
|
|
}
|
|
if !hasParseErrors(calls) {
|
|
t.Error("should detect ParseError in second tool call")
|
|
}
|
|
}
|
|
|
|
func TestHasParseErrors_AllErrors(t *testing.T) {
|
|
calls := []providers.ToolCall{
|
|
{ID: "1", Name: "write_file", ParseError: "truncated"},
|
|
{ID: "2", Name: "exec", ParseError: "truncated"},
|
|
}
|
|
if !hasParseErrors(calls) {
|
|
t.Error("should detect ParseError when all calls have errors")
|
|
}
|
|
}
|