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

  1. Ensure "tools" is an array of Gemini tool objects (e.g. [{"functionDeclarations":[...]}]).
  2. Check the wrapped error message for the exact field/type mismatch inside the tools array.
  3. 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

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)