moonmuse

Formats

The same conversation, four documents

System prompt “Answer in one sentence.”, one user question, temperature 0.2, a 256-token cap. Below is where each field lands in each specification, then the four bodies themselves — editable, and rewritten from whichever one you touch by this library, running in the page.

01 · Field map

Read a row across

One concept per row; one specification per column. Red marks a value that decides whether the request is accepted at all.

Four columns wide — scroll sideways on a narrow screen.

Field OpenAIPOST /v1/chat/completions AnthropicPOST /v1/messages GeminiPOST /v1beta/models/{model}:generateContent OllamaPOST /api/chat
Model "model": "gpt-4o-mini" "model": "claude-sonnet-4-20250514" In the URL: /models/gemini-2.0-flashno model field in the body "model": "qwen2.5"
System prompt messages[0] with role: "system" top-level "system": "…"kept out of the message list "systemInstruction": {"parts": [{"text": …}]} messages[0] with role: "system"
Messages "messages": [{role, content: "…"}]content is a string "messages": [{role, content: [{type:"text", text:"…"}]}]content is an array of blocks "contents": [{role:"user", parts:[{text:"…"}]}] "messages": [{role, content: "…"}]
Roles system / user / assistant user / assistantno system role at all user / modelthe assistant is called model system / user / assistant
Temperature "temperature": 0.2 "temperature": 0.2 generationConfig.temperature options.temperature
Output limit "max_tokens": 256 "max_tokens": 256required — a request without it is rejected generationConfig.maxOutputTokens options.num_predict
Stop sequences "stop": ["\n\n"] "stop_sequences": ["\n\n"] generationConfig.stopSequences options.stop
Streaming "stream": true "stream": true a different method: :streamGenerateContent?alt=ssenot a flag in the body "stream": false turns it offon by default
Usage usage.prompt_tokens / completion_tokens usage.input_tokens / output_tokens usageMetadata.promptTokenCount / candidatesTokenCount prompt_eval_count / eval_count
Finish reason choices[0].finish_reasonstop / length / tool_calls stop_reasonend_turn / max_tokens / tool_use candidates[0].finishReasonSTOP / MAX_TOKENS done_reasonstop / length

02 · Four bodies

Edit one, and the rest follow

Every pane is a text field. Change a value, add a message, or press ↻ to make that pane the source: the other three are read and written by the four dialects, not by this page.

The model travels with the request: convert a body and the same model name appears where the target format keeps it — for Gemini, in the URL.

03 · House rules

What breaks a second integration

Four things, in the order they usually bite. None of them is a bug in either side; each is what the specification decides.

  • Anthropic requires max_tokens. Omit it and the request is rejected; there is no default. OpenAI and Ollama fill one in.
  • Gemini puts the model in the path and the knobs under generationConfig. Temperature, output cap and stop sequences are renamed and relocated at the same time.
  • Ollama nests generation options under options, and streams by default. Ask for one response with "stream": false.
  • OpenAI has no top-level system field. The system prompt is the first message, so order carries meaning.

04 · Streams

Four shapes of the same event

The deltas are one type. What arrives on the socket is not: a JSON chunk, seven named events, a candidate array, or one object per line.

OpenAI · server-sent events
data: {"choices":[{"delta":{"content":"Be"}}]}

data: {"choices":[{"delta":{"content":"cause"}}]}

data: [DONE]

One unnamed event per chunk; the stream closes with a sentinel.

Anthropic · named events
event: message_start
data: {"type":"message_start",...}

event: content_block_delta
data: {"type":"content_block_delta","index":0,
       "delta":{"type":"text_delta","text":"Be"}}

Seven event names, and a block index that has to be tracked.

Gemini · candidate array
data: {"candidates":[{"content":{"parts":
       [{"text":"Be"}],"role":"model"}}]}

The same SSE frame, with the text nested three levels down.

Ollama · newline-delimited JSON
{"message":{"role":"assistant","content":"Be"},"done":false}
{"message":{"role":"assistant","content":"cause"},"done":false}
{"done":true,"done_reason":"stop"}

No event names, no sentinel: one object per line, ending with done.

05 · Serving

And the same four, outward

The dialects that write a request also read one, so a provider can answer in all four formats. Two formats claim /v1/models; the anthropic-version header decides which answers.

FormatEndpoints
OpenAI/v1/chat/completions /v1/completions /v1/responses /v1/embeddings /v1/models
Anthropic/v1/messages /v1/messages/count_tokens /v1/models
Ollama/api/chat /api/generate /api/embed /api/tags /api/show /api/version
Gemini/v1beta/models/*:generateContent :streamGenerateContent :embedContent :batchEmbedContents

Writing one more format adds one dialect, and the client and the gateway both have it: the direction you did not write is the one you already had.