JuliusBrussee/caveman · error
tools
Error message
tools: %w
What it means
parseGeminiGenerateContent optionally unmarshals "tools" into req.Tools. If the value exists but does not decode into the expected tool schema (wrong element types, missing/mistyped fields in function declarations), the unmarshal error is wrapped as "tools: %w" and TransformGemini fails.
Solutions
- Ensure "tools" is an array of Gemini tool objects (e.g. [{"functionDeclarations":[...]}]).
- Check the wrapped error message for the exact field/type mismatch inside the tools array.
- Convert tool schemas from other providers to Gemini's functionDeclarations format before sending.
Example fix
// before
{"tools": {"functionDeclarations": [...]}}
// after
{"tools": [{"functionDeclarations": [...]}]} Defensive patterns
Strategy: validation
Validate before calling
var probe struct {
Tools []map[string]json.RawMessage `json:"tools"`
}
if err := json.Unmarshal(body, &probe); err != nil {
return fmt.Errorf("tools must be an array of tool objects")
} Type guard
func validTools(raw map[string]json.RawMessage) bool {
v, ok := raw["tools"]
if !ok {
return true
}
var arr []map[string]json.RawMessage
return json.Unmarshal(v, &arr) == nil
} Try / catch
req, err := parseGeminiGenerateContent(body)
if err != nil {
if strings.Contains(err.Error(), "tools:") {
var ue *json.UnmarshalTypeError
if errors.As(err, &ue) {
return fmt.Errorf("tools[%s] type mismatch: %w", ue.Field, err)
}
}
return err
} Prevention
- Use Gemini's tools array format: [{"functionDeclarations":[...]}].
- Convert OpenAI-style tool schemas to Gemini's format before sending.
- Unwrap the error with errors.As to find the exact field path inside the tools array.
When it happens
Trigger: TransformGemini on a body where "tools" is present but malformed — e.g. a single object instead of an array of tools, or function declarations whose parameters schema has unexpected types.
Common situations: Tool definitions copied from OpenAI-style schemas that don't match Gemini's tool format; SDK version drift changing the tool JSON shape; fixtures with hand-edited tool arrays.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
Related errors
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/cae56c7be474e2cc.
Report an issue: GitHub.
Appendix: source
Thrown at engine/pixel/transform_gemini.go:256
if err := json.Unmarshal(body, &raw); err != nil {
return geminiGenerateContent{}, err
}
req := geminiGenerateContent{extra: raw}
if v, ok := raw["contents"]; ok {
if err := json.Unmarshal(v, &req.Contents); err != nil {
return geminiGenerateContent{}, fmt.Errorf("contents: %w", err)
}
}
if v, ok := raw["systemInstruction"]; ok && len(bytes.TrimSpace(v)) > 0 && !bytes.Equal(bytes.TrimSpace(v), []byte("null")) {
var inst geminiInstruction
if err := json.Unmarshal(v, &inst); err != nil {
return geminiGenerateContent{}, fmt.Errorf("systemInstruction: %w", err)
}
req.SystemInstruction = &inst
}
if v, ok := raw["tools"]; ok {
if err := json.Unmarshal(v, &req.Tools); err != nil {
return geminiGenerateContent{}, fmt.Errorf("tools: %w", err)
}
}
return req, nil
}
func geminiGlobalMediaResolution(raw map[string]json.RawMessage) (string, bool) {
var config json.RawMessage
for _, key := range []string{"generationConfig", "generation_config"} {
if value, found := raw[key]; found {
if config != nil {
return "", false
}
config = value
}
}
if config == nil {
return "default", true
}View on GitHub (pinned to 3ee70a1026)