Problem – Symptoms and Impact
In a production micro‑services environment a service that invokes the Google Gemini API began failing with HTTP 400/422 errors. The calling service crashed with an unhandled JSONDecodeError, triggering circuit‑breaker alerts and causing downstream AI‑driven features to become unavailable.
Typical log excerpts from the calling service:
2026-05-31T14:22:07.123Z ERROR Request to Gemini failed: status=400, body={
"error": {
"code": 400,
"message": "Invalid function call format",
"details": [
{"type":"validation_error","message":"Missing required field 'name' in function call"}
]
}
}
Traceback (most recent call last):
File "/app/ai_client.py", line 87, in invoke
payload = json.dumps(request_body)
File "/usr/local/lib/python3.11/json/__init__.py", line 238, in dumps
raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type function is not JSON serializable
Another instance from the API gateway:
2026-05-31T14:23:12.487Z WARN Gemini response: 422 Unprocessable Entity
{
"error": {
"code": 422,
"message": "Function call schema validation failed",
"details": [
{"type":"validation_error","message":"Invalid JSON payload: Unexpected token ',' at line 12 column 23"}
]
}
}
Impact includes:
- Increased latency as retries are attempted.
- Service degradation due to repeated circuit‑breaker trips.
- Loss of AI‑generated content for end‑users.
Root Cause Analysis
Google Gemini’s Function Calling feature expects a strict JSON schema defined in the Function Calling Guide. The required top‑level fields are:
| Field | Type | Required |
|---|---|---|
| name | string | yes |
| arguments | object | yes |
| description | string | no |
Several micro‑services deviated from this contract:
- Trailing commas in the JSON payload (see real incident “extra trailing comma”).
- Missing
namefield due to a refactor that renamed the internal function variable but did not update the outgoing request (see GitHub issue #87). - Incorrect field naming convention – using
camelCase(e.g.,userId) while Gemini expectssnake_case(user_id) (see real incident on naming mismatch). - Gateway stripping the
argumentsobject when serializing the request for transport, resulting in a 422 error (see inter‑service gateway incident). - Batch payloads not wrapped in an array as required for multi‑call requests, causing a 500 internal error (see batch request incident).
All of these violations cause the validation layer documented in the Error Handling Documentation to return the observed error codes.
Investigation and Debugging
1. Capture the outbound request
Use tcpdump or the service’s HTTP tracing middleware to dump the exact payload sent to Gemini.
sudo tcpdump -i any -s 0 -w /tmp/gemini_req.pcap port 443 and host gemini.googleapis.com
Then decode with tshark:
tshark -r /tmp/gemini_req.pcap -Y http.request -T fields -e http.file_data
2. Validate JSON locally
Pipe the captured payload into jq to surface syntax errors.
cat captured_payload.json | jq .
Typical output for a malformed payload:
parse error: Expected ',' or '}' after object key at line 12, column 23
3. Compare against the official schema
Download the JSON schema from the reference documentation and run a validation tool.
curl -s https://developers.google.com/gemini/api/function-calling/schema.json -o gemini_schema.json
python - <<'PY'
import json, sys, jsonschema
payload = json.load(open('captured_payload.json'))
schema = json.load(open('gemini_schema.json'))
try:
jsonschema.validate(payload, schema)
print("Payload is valid")
except jsonschema.ValidationError as e:
print("Validation error:", e.message)
PY
4. Review gateway transformations
If an API gateway (e.g., Envoy, Kong) sits between services, enable request‑body logging to ensure no fields are stripped.
# Envoy filter example
{
"name": "envoy.filters.http.lua",
"typed_config": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua",
"inline_code": "function envoy_on_request(request_handle) \
request_handle:logInfo('Body: ' .. request_handle:body():getBytes(0, request_handle:body():length())) \
end"
}
}
5. Reproduce the failure in isolation
Use curl with the exact payload to verify Gemini’s response outside of the micro‑service.
curl -X POST https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
--data @captured_payload.json -i
Resolution – Fixes Applied
1. Centralised request builder
Introduce a shared library (gemini_client.py) that constructs the function call payload using a strict Pydantic model. This guarantees required fields and correct naming.
# before (ad‑hoc dict construction)
payload = {
"name": func_name,
"arguments": args, # args may be None
"userId": user.id # camelCase, not expected
}
# after (Pydantic model)
from pydantic import BaseModel, Field
from typing import Dict, Any
class GeminiFunctionCall(BaseModel):
name: str = Field(..., description="Registered function name")
arguments: Dict[str, Any] = Field(..., description="Function arguments")
# optional description field omitted for brevity
def build_payload(func_name: str, args: Dict[str, Any]) -> dict:
call = GeminiFunctionCall(name=func_name, arguments=args)
return call.dict()
2. Enforce snake_case for argument keys
Convert incoming request fields to the expected naming convention before forwarding to Gemini.
def to_snake_case(d: dict) -> dict:
import re
def convert(key):
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', key)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
return {convert(k): v for k, v in d.items()}
3. Guard against trailing commas
Serialize with json.dumps and never hand‑craft JSON strings.
import json
payload_json = json.dumps(build_payload("summarize_text", {"text": article}))
# No trailing commas can appear
4. Preserve arguments through the gateway
Add an explicit allow‑list rule in the gateway configuration to forward the arguments object unchanged.
# Kong plugin configuration snippet
{
"name": "request-transformer",
"config": {
"remove": {
"json": ["unwanted_field"]
},
"preserve_body": true
}
}
5. Batch request wrapper
When sending multiple calls, wrap them in an array under the functionCalls field as specified.
{
"functionCalls": [
{"name":"extract_entities","arguments":{"text":"..."}},
{"name":"summarize_text","arguments":{"text":"..."}}
]
}
Validation – Confirming the Fix
- Unit test that serializes a valid payload and asserts
jsonschema.validatepasses. - Integration test using a mock Gemini endpoint (e.g.,
wiremock) that returns a successful 200 response for correctly formed calls. - Runtime verification – check logs for “status=200” and absence of “Invalid function call format”. Example successful log entry:
2026-05-31T14:45:12.009Z INFO Gemini response: 200 OK
{
"candidates": [...]
}
Additionally, monitor the circuit‑breaker metric (service_a.circuit_breaker.open) to ensure it stays at zero after deployment.
Operational Experience – Lessons Learned
- Ad‑hoc JSON construction is a common source of subtle syntax errors (trailing commas, missing braces). Centralising payload creation eliminates this class of bugs.
- Micro‑service gateways often rewrite request bodies for logging or security; explicit
preserve_bodyflags are required when forwarding complex objects. - Schema drift between environments (dev vs prod) caused a false sense of correctness; a contract test suite that runs against the live Gemini endpoint caught the mismatch early.
- Using Pydantic (or similar) not only validates the payload but also provides clear error messages that map directly to Gemini’s validation errors, reducing time‑to‑diagnose.
Best Practices and Prevention
- Contract testing: Deploy a nightly job that sends a minimal valid function call to Gemini and asserts a 200 response.
- Schema validation middleware: Insert a JSON‑schema validator in each service that produces Gemini calls; fail fast before the request leaves the process.
- Centralised client library: Keep the Gemini client in a shared repository versioned alongside the micro‑services that depend on it.
- Observability: Emit a structured log field
gemini_request_idand correlate it with Gemini’srequestIdfrom the response for end‑to‑end tracing. - Alerting: Trigger alerts on HTTP 4xx/5xx rates > 1% for Gemini calls, and on spikes in circuit‑breaker open events.
FAQ – Related Questions
- Why does Gemini return HTTP 422 instead of 400 for a missing
argumentsfield?
Gemini distinguishes between syntactic JSON errors (400) and schema validation errors (422). Missingargumentsviolates the function‑call schema, so the service returns 422 as documented in the Error Handling guide. - Can I use camelCase for function arguments if my internal code prefers it?
No. Gemini’s schema requires snake_case keys. Convert or map keys before sending the payload; otherwise Gemini will reject the request with “Invalid JSON payload: unexpected field 'userId'”. - How do I safely batch multiple function calls?
Wrap each call in an object withnameandarguments, then place the collection inside a top‑levelfunctionCallsarray. Ensure the outer JSON is an object, not a raw array. - What retry strategy should I use for 400 vs 422 errors?
400 indicates a client‑side format problem; retrying without fixing the payload will loop. 422 also signals a schema issue; only retry after correcting the payload. For transient 5xx errors, use exponential backoff with jitter as per the Gemini error handling guidelines. - Is there a way to get more detailed validation feedback from Gemini?
Gemini includes adetailsarray in the error body that lists each validation failure (e.g., missing field, unexpected token). Parse this array to surface precise messages to developers.
Related Topic Hub: LLM Systems Troubleshooting Hub