C.W.K.
Stream
Lesson 05 of 05 · published

Comparison Matrix

~20 min · comparison, matrix, decision, portability

Level 0Curious Reader
0 XP0/48 lessons0/14 achievements
0/100 XP to next level100 XP to go0% complete

Pull the three provider envelopes side by side and the matrix gets short fast. Most differences are cosmetic; a few are real.

ConceptOpenAIAnthropicGemini
Tool list fieldtoolstoolstools (in config)
Schema fieldparametersinput_schemaparameters (Schema object)
Args type backJSON stringdictdict
Tool choice"auto"|"required"|spectype: "auto"|"any"|"tool"FunctionCallingMode enum
Stop signalresponse.completed + output[].typestop_reasonpresence/absence of function_call part
Streaming tool argsDeltasDeltas (per content block)All-at-once
Pre-built native toolsweb_search, code_interpreter, file_searchcomputer_use, text_editor, bashcode_execution, google_search
MCP integrationvia Apps SDKvia mcp_servers in Messages APIvia Vertex AI tool config

The four cells where the difference is real, not cosmetic, are: the args type (parse vs use), the streaming behavior (deltas vs all-at-once), the pre-built tools (each provider has unique offerings), and the MCP integration shape. Build a thin translation layer for the cosmetic ones and accept that the real ones require provider-specific code.

The temptation is to write a giant abstraction that pretends all three are the same. Resist it for as long as you can. The shape of "send messages and tools, get tool calls back, send results in" is already a small abstraction; over-abstracting hides the real differences and produces SDK-like layers nobody else wants to maintain.

Code

Tiny portability shim — only what you actually need·python
def normalize_tool_calls(provider, response):
    if provider == "openai":
        return [(item.name, json.loads(item.arguments)) for item in response.output if item.type == "function_call"]
    if provider == "anthropic":
        return [(b.name, b.input) for b in response.content if b.type == "tool_use"]
    if provider == "gemini":
        return [(p.function_call.name, dict(p.function_call.args))
                for p in response.candidates[0].content.parts
                if p.function_call]
    raise ValueError(f"Unknown provider {provider!r}")

External links

Exercise

Take the matrix above and shrink it to the four 'real differences' rows. Pin those four rows above your editor (or in a comment at the top of your loop module). Whenever you find yourself writing a portability hack, check if it is one of those four. If yes, accept the divergence. If no, your shim is leaking too much.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.