Reading the wire · 2025–2026

The Anatomy
of an LLM Message

Every chat with GPT or Claude is just JSON going back and forth. This page takes those messages apart, piece by piece — and shows where OpenAI and Anthropic agree, where they quietly differ, and how tool calls actually work under the hood.

OpenAI· Chat Completions Anthropic· Messages API Shared· same idea, both APIs
Click any highlighted field to disassemble it
§ 01 / THE MENTAL MODEL

Both APIs are the same shape, underneath.

Before the differences, the agreement. OpenAI and Anthropic both speak a turn-based, stateless dialect: you POST the whole conversation so far, you get one reply back, and you bill by the token. Three ideas carry the whole model.

idea 01

Stateless turns

The server remembers nothing. Each request resends the full message history — system instructions, every user line, every assistant reply. The "conversation" lives in your array, not on their server.

idea 02

Role + content

A message is a role (who is speaking) plus content (what they said). Roles — system · user · assistant · tool — tell the model how to read each line.

idea 03

The agent loop

When the model wants data it doesn't have, it asks to call a tool, pauses, and waits. You run the code, hand back the result, and it continues. Repeat until it answers.

§ 02 / THE REQUEST

What you send: the envelope.

A request is one JSON object. Most fields line up across providers — but the system prompt and a couple of required fields are where they first diverge. Switch tabs and click the highlighted keys.

click a field
{
  "model": "gpt-4o",
  "messages": [
    { "role": "system",    "content": "You are concise." },
    { "role": "user",      "content": "What's the weather in Oslo?" }
  ],
  "tools": [ … ],            // defined in §04
  "tool_choice": "auto",
  "temperature": 0.7,
  "max_tokens": 1024,
  "stream": false
}
{
  "model": "claude-opus-4-6",
  "max_tokens": 1024,
  "system": "You are concise.",
  "messages": [
    { "role": "user", "content": "What's the weather in Oslo?" }
  ],
  "tools": [ … ],            // defined in §04
  "tool_choice": { "type": "auto" },
  "temperature": 0.7,
  "stream": false
}
Pick a field
to read it.
Each highlighted key explains what it does and whether the two APIs treat it the same.
§ 03 / MESSAGE CONTENT

Inside a message: blocks of content.

A plain text message is just a string. But content can also be a list of typed blocks — text, images, tool calls, tool results. This is the real unit of an LLM conversation, and where Anthropic's "content blocks" and OpenAI's "parts" differ most.

click a field
// content is a string…
{ "role": "user", "content": "Hello, Claude" }

// …or a list of typed blocks
{
  "role": "user",
  "content": [
    { "type": "text",  "text": "Describe this:" },
    {
      "type": "image",
      "source": {
        "type": "base64",
        "media_type": "image/png",
        "data": "iVBORw0KGgo…"
      }
    },
    { "type": "text", "text": "…long doc…",
      "cache_control": { "type": "ephemeral" } }
  ]
}
// content is a string…
{ "role": "user", "content": "Hello, GPT" }

// …or a list of typed parts
{
  "role": "user",
  "content": [
    { "type": "text", "text": "Describe this:" },
    {
      "type": "image_url",
      "image_url": {
        "url": "data:image/png;base64,iVBORw0…"
      }
    }
  ]
}

// the "developer" role = newer name
// for "system" (high-priority rules)
{ "role": "developer", "content": "…" }
A message
is a stack
of blocks.
Click a block type to see what it carries and how the two APIs name it.
§ 04 / TOOL CALLS — THE FULL LOOP

How a model reaches into the real world.

The model can't check the weather, query your DB, or send an email — but it can ask you to. A tool call is a five-beat dance between three parties: your app, the model, and the tool. Step through it.

§ 05 / THE RESPONSE

What comes back: the reply object.

The response mirrors the request, but the wrappers differ. OpenAI nests the reply inside choices[]; Anthropic returns content[] directly. Both tell you why they stopped and how many tokens it cost.

click a field
{
  "id": "chatcmpl-AbC123",
  "object": "chat.completion",
  "model": "gpt-4o",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "It's 4°C and clear in Oslo."
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 82,
    "completion_tokens": 17,
    "total_tokens": 99
  }
}
{
  "id": "msg_013Zva2CMH…",
  "type": "message",
  "role": "assistant",
  "model": "claude-opus-4-6",
  "content": [
    { "type": "text", "text": "It's 4°C and clear in Oslo." }
  ],
  "stop_reason": "end_turn",
  "stop_sequence": null,
  "usage": {
    "input_tokens": 82,
    "output_tokens": 17,
    "cache_read_input_tokens": 0
  }
}
Why it
stopped,
what it cost.
Click a field to compare how each API wraps the reply, signals completion, and reports tokens.
§ 06 / THE DIFFERENCES

Same job, different wiring.

If you've internalised one API, here's the translation table for the other. These are the gotchas that bite when porting code between them.

Concept
OpenAI · Chat Completions
Anthropic · Messages
System prompt
A message with role: "system" (or "developer")
A top-level system string — not a message
max_tokens
Optional
Required — request fails without it
Tool schema key
parameters nested under function
input_schema, flat on the tool
Tool args from model
arguments — a JSON string to parse
input — already a JSON object
Returning a tool result
A new message with role: "tool"
A tool_result block inside a user message
Call ↔ result link
tool_call_idcall_…
tool_use_idtoolu_…
Assistant reply shape
choices[0].message (single string content)
content[] array of typed blocks
Stop signal
finish_reason: stop · length · tool_calls
stop_reason: end_turn · max_tokens · tool_use
Token counts
prompt_tokens / completion_tokens
input_tokens / output_tokens
Prompt caching
Automatic (reported in cached_tokens)
Explicit cache_control breakpoints you place
⬢ One more wrinkle · OpenAI now has two APIs

Everything above used Chat Completions — the classic, still-supported endpoint that maps most directly to Anthropic's Messages API. But OpenAI's newer Responses API (POST /v1/responses) is the recommended path for new agentic apps. It reshapes the same ideas:

  • The system prompt becomes a top-level instructions field (like Anthropic's system).
  • messages becomes input; the reply is a typed output[] array of items — closer to Anthropic's content blocks.
  • Tools are flat: { "type":"function", "name", "parameters" } — no nested function object.
  • A tool call is a function_call item; you reply with a function_call_output item carrying a call_id.
  • It can be stateful: pass previous_response_id and the server keeps the history for you — the one place the "stateless" rule bends.