JuliusBrussee/caveman · error

systemInstruction

Error message

systemInstruction: %w

What it means

parseGeminiGenerateContent optionally unmarshals "systemInstruction" into a geminiInstruction when present, non-empty, and not literal null. If the value has the wrong shape (e.g. a bare string, or an object with unexpected field types), the unmarshal error is wrapped as "systemInstruction: %w" and TransformGemini fails.

Solutions

  1. Send systemInstruction as an object: {"parts":[{"text":"..."}]} rather than a bare string.
  2. Read the wrapped error to identify which field type did not match.
  3. If your SDK emits the string form, transform it to the parts-object form before calling TransformGemini.

Example fix

// before
{"systemInstruction": "You are helpful."}
// after
{"systemInstruction": {"parts": [{"text": "You are helpful."}]}}
Defensive patterns

Strategy: validation

Validate before calling

var probe struct {
    SystemInstruction *struct {
        Parts []map[string]json.RawMessage `json:"parts"`
    } `json:"systemInstruction"`
}
if err := json.Unmarshal(body, &probe); err != nil {
    return fmt.Errorf("systemInstruction must be {parts:[{text:...}]}")
}

Type guard

func validSystemInstruction(raw map[string]json.RawMessage) bool {
    v, ok := raw["systemInstruction"]
    if !ok || len(bytes.TrimSpace(v)) == 0 || bytes.Equal(bytes.TrimSpace(v), []byte("null")) {
        return true
    }
    var inst map[string]json.RawMessage
    return json.Unmarshal(v, &inst) == nil && inst["parts"] != nil
}

Try / catch

req, err := parseGeminiGenerateContent(body)
if err != nil {
    if strings.Contains(err.Error(), "systemInstruction:") {
        return fmt.Errorf("send systemInstruction as {parts:[{text:...}]}, not a bare string")
    }
    return err
}

Prevention

When it happens

Trigger: TransformGemini on a body where "systemInstruction" is present but not a valid instruction object — e.g. a plain string (some Gemini API variants accept string, this struct does not) or an object whose parts field has the wrong type.

Common situations: Clients built against a different Gemini API revision that sends systemInstruction as a string; templates that inject a raw string instead of {parts:[{text:...}]}; migrated prompts from other providers.

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/fb06d5ca6a5b40a2. Report an issue: GitHub.

Appendix: source

Thrown at engine/pixel/transform_gemini.go:250

	def.EmitRecoverable = opts.EmitRecoverable
	return def
}

func parseGeminiGenerateContent(body []byte) (geminiGenerateContent, error) {
	var raw map[string]json.RawMessage
	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
			}

View on GitHub (pinned to 3ee70a1026)