n4nAI

What changes when you migrate from Claude to Gemini

A practitioner's analysis of Claude to Gemini migration differences: API schema, tool use, multimodal, caching, and tradeoffs for engineers switching models.

n4n Team4 min read919 words

Audio narration

Coming soon — every post will get a voice note here.

Swapping Claude for Gemini in a production LLM pipeline breaks more than the model name. The Claude to Gemini migration differences span request formatting, tool-calling contracts, multimodal input handling, and safety controls, so a naive proxy swap will silently degrade output quality. You need to rewrite your orchestration layer, not just change an environment variable.

API Shape and Request Format

Claude’s Anthropic API expects a top-level system parameter and a messages array where each message has role (user or assistant) and content (string or structured blocks). Gemini’s REST API uses contents with role (user/model) and parts containing text or inline data. There is no separate system field in the core request; you pass systemInstruction as a distinct object or prepend to the first user turn.

# Claude (Anthropic SDK)
import anthropic
client = anthropic.Anthropic()
resp = client.messages.create(
    model="claude-3-5-sonnet-20241022",
    max_tokens=1024,
    system="You are a terse debugger.",
    messages=[{"role": "user", "content": "Fix this traceback"}]
)
# Gemini (google-generativeai)
import google.generativeai as genai
genai.configure(api_key="...")
model = genai.GenerativeModel(
    model_name="gemini-1.5-pro",
    system_instruction="You are a terse debugger."
)
resp = model.generate_content("Fix this traceback")

Raw REST shows the divergence more clearly:

{
  "systemInstruction": {"parts":[{"text":"You are a terse debugger."}]},
  "contents": [{"role":"user","parts":[{"text":"Fix this traceback"}]}],
  "generationConfig": {"maxOutputTokens": 1024, "temperature": 0.2}
}

An OpenRouter-class gateway like n4n.ai can normalize to an OpenAI-compatible chat completions shape, but you still lose provider-specific fields if you don’t map them explicitly.

System Prompts and Conversation Structure

Claude treats the system prompt as a privileged context that persists across the conversation and can be split into blocks with cache_control. Gemini’s systemInstruction is similar but gets packed into the model’s prefix; long system prompts count against context and may be less rigidly obeyed in multi-turn chats. In Claude, you can vary system per call freely. In Gemini, changing system instruction mid-session requires a new GenerativeModel instance or a fresh chat.

If you rely on Claude’s ability to follow nuanced constraints (“never use markdown, always return XML”), test Gemini thoroughly. Our experience: Gemini 1.5 obeys format constraints when you set responseMimeType: "application/json" and supply a schema, but free-form style instructions drift more than Claude’s.

Tool Use and Function Calling

This is where Claude to Gemini migration differences bite hardest. Claude defines tools with input_schema:

{
  "name": "get_weather",
  "description": "Get weather for a city",
  "input_schema": {
    "type": "object",
    "properties": {"city": {"type": "string"}},
    "required": ["city"]
  }
}

Gemini uses functionDeclarations with parameters:

{
  "name": "get_weather",
  "description": "Get weather for a city",
  "parameters": {
    "type": "object",
    "properties": {"city": {"type": "string"}},
    "required": ["city"]
  }
}

Claude returns tool calls as a tool_use content block with an id you must echo in a tool_result message. Gemini returns functionCall parts inside a candidate; you respond with a functionResponse part under role tool. The conversation threading differs: Claude expects strict pairing; Gemini allows parallel calls in one turn but you must map them back.

Forced tool use also diverges. Claude uses tool_choice: {type:"tool", name:"get_weather"}. Gemini uses tool_config: {function_calling_config: {mode:"ANY", allowed_function_names:["get_weather"]}}.

A minimal Gemini tool loop:

chat = model.start_chat()
resp = chat.send_message("Weather in SF?")
part = resp.candidates[0].content.parts[0]
if hasattr(part, "function_call"):
    fn = part.function_call
    result = call_local(fn.name, dict(fn.args))
    chat.send_message({
        "role": "tool",
        "parts": [{"function_response": {"name": fn.name, "response": result}}]
    })

Claude’s equivalent requires constructing a follow-up messages entry with role:"user" and content:[{type:"tool_result", tool_use_id:..., content:...}].

Multimodal Inputs

Another of the Claude to Gemini migration differences is multimodal scope. Claude accepts images as base64 blocks with type:"image" and supported media types. It does not accept audio or video. Gemini ingests text, images, audio, and video natively through parts with inline_data or file_data. You can pass a long video and ask for chapter markers.

resp = model.generate_content([
    "Summarize this clip",
    {"mime_type":"video/mp4","data": open("clip.mp4","rb").read()}
])

But watch latency: large binary inputs inflate token count via internal encoding; you can’t assume the same cost profile as text. Claude’s image handling is mature but limited to static visuals.

Context Windows and Caching

Claude’s 200K context is generous; Gemini 1.5 Pro offers 1M+ tokens. That changes architecture: with Claude you might summarize older history; with Gemini you can dump entire repos. However, caching differs. Claude supports prompt caching via cache_control on content blocks. Gemini has a Context Cache API: you create a CachedContent object and reference it, with TTL and separate storage billing.

# Claude caching
client.messages.create(
    model="claude-3-5-sonnet-20241022",
    system=[{"type":"text","text":"LONG POLICY DOC"},
            {"type":"text","text":"","cache_control":{"type":"ephemeral"}}],
    messages=messages
)
# Gemini cached content
cache = genai.CachedContent.create(
    model="gemini-1.5-pro",
    contents="LONG POLICY DOC"
)
model = genai.GenerativeModel(model_name="gemini-1.5-pro", cached_content=cache)

When using n4n.ai, it honors client routing directives and forwards provider cache-control hints, but you still must map Claude’s cache_control to Gemini’s cached content lifecycle. The Claude to Gemini migration differences in caching semantics mean you can’t reuse the same abstraction.

Safety Filters and Output Controls

Gemini ships aggressive default safety settings (HARM_CATEGORY_* thresholds). Claude has constitutional safeguards but fewer knobs exposed via API. If your app generates edgy but legitimate content (security reports, medical text), Gemini may block with FINISH_REASON.SAFETY. You must set safetySettings per call.

model.safety_settings = [
    {"category":"HARM_CATEGORY_DANGEROUS_CONTENT","threshold":"BLOCK_NONE"}
]

Claude rarely blocks on similar prompts but may refuse via text. Plan for different fallback logic: Gemini can block on input (promptFeedback.blockReason), so validate before sending.

Streaming and Error Handling

Claude streams content_block_delta events with delta.text. Gemini streams GenerateContentResponse chunks with candidates[0].content.parts. Your SSE parser must adapt. Error shapes differ: Anthropic returns error with type and message; Gemini returns error with code and status. Rate-limit handling: Claude uses retry-after headers; Gemini uses 429 with error.details. If you use a gateway that auto-falls back when a provider is degraded, you still need to normalize these into your retry budget.

Cost and Latency Tradeoffs

Without quoting exact prices, Gemini’s token pricing is generally lower per million tokens for proportional context, especially for large inputs, but Claude’s latency on small prompts is often tighter. Gemini’s large context can paradoxically increase bill if you stuff redundant text. Measure p50/p95 latency on your real traffic before committing. Gemini charges for cached content storage separately; Claude’s prompt caching has minimum token thresholds.

Migration Strategy

  1. Abstract your model client behind an interface that emits normalized chat messages.
  2. Write provider-specific adapters for request mapping (system prompt, tools, multimodal parts).
  3. Port caching logic; don’t assume prefix caching works identically.
  4. Add safety-setting overrides for Gemini.
  5. Run side-by-side evals on a golden set; compare tool-call accuracy and format adherence.
  6. Log per-token usage per provider to validate cost assumptions.

Example adapter snippet (TypeScript):

interface ChatReq { system: string; messages: {role:string; content:string}[]; tools?: any[] }
function toGemini(req: ChatReq) {
  return {
    systemInstruction: { parts: [{ text: req.system }] },
    contents: req.messages.map(m => ({
      role: m.role === 'assistant' ? 'model' : 'user',
      parts: [{ text: m.content }]
    })),
    tools: req.tools?.map(t => ({ functionDeclarations: [t] }))
  };
}

Decision: When to Switch

Migrate to Gemini when you need million-token context, native video/audio understanding, or lower unit cost at scale, and you can invest in rewriting tool orchestration. Stay on Claude if your product depends on its particular instruction-following style, long-form coherence, or you have deep validated tool-calling flows. The Claude to Gemini migration differences are solvable but not trivial; treat it as a re-platform of your prompt layer, not a config change. If you must support both, put a normalization gateway in front and keep model-specific test suites.

Tagsclaudegeminimigrationanalysis

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All migrating between llm providers posts →