Prompt

How do I standardize request and response formats across OpenAI, Anthropic, and Gemini?

Artificial Intelligence / AI Infrastructure1 observationLast seen Oct 10, 2026

Latest observation

Oct 10, 2026OpenAI APIWeb search: off

A good way to standardize across OpenAI, Anthropic, and Gemini is to define your own internal canonical request/response schema, then build thin provider adapters that translate to/from each vendor’s API.

1) Pick a canonical schema

Keep it simple and model-agnostic:

Canonical request

{
  "model": "optional-string",
  "messages": [
    {
      "role": "system|user|assistant|tool",
      "content": [
        {
          "type": "text",
          "text": "Hello"
        }
      ]
    }
  ],
  "tools": [
    {
      "name": "get_weather",
      "description": "Fetch weather",
      "input_schema": {
        "type": "object",
        "properties": {
          "city": { "type": "string" }
        },
        "required": ["city"]
      }
    }
  ],
  "tool_choice": "auto|none|specific",
  "temperature": 0.7,
  "max_tokens": 1000,
  "stream": false,
  "metadata": {}
}

Canonical response

{
  "id": "req_123",
  "provider": "openai|anthropic|gemini",
  "model": "string",
  "output": [
    {
      "type": "text",
      "text": "Final answer"
    }
  ],
  "tool_calls": [
    {
      "name": "get_weather",
      "arguments": { "city": "Paris" }
    }
  ],
  "usage": {
    "input_tokens": 0,
    "output_tokens": 0,
    "total_tokens": 0
  },
  "finish_reason": "stop|length|tool_calls|content_filter|error",
  "raw": {}
}

2) Normalize the concepts, not the exact wire format

The three APIs differ in structure:

OpenAI

  • Chat/Responses-style messages
  • Tool calls are explicit
  • Supports structured output / JSON schema in some endpoints

Anthropic

  • Uses messages plus separate system
  • Tool use is represented differently
  • Content blocks are common (text, tool_use, etc.)

Gemini

  • Uses contents, parts, and systemInstruction
  • Tool calling is represented via function declarations / function calls
  • Response structure is different again

Instead of trying to make them identical, map them into your canonical layer.


3) Use adapters for each provider

Adapter responsibilities

Each provider adapter should:

  1. Convert canonical request → provider request
  2. Call the API
  3. Convert provider response → canonical response
  4. Preserve raw response for debugging

Example pseudo-interface

interface LLMProviderAdapter {
  buildRequest(canonical: CanonicalRequest): unknown;
  parseResponse(providerResponse: unknown): CanonicalResponse;
}

4) Standardize message format internally

A practical internal message shape is:

type CanonicalMessage = {
  role: "system" | "user" | "assistant" | "tool";
  content: Array<
    | { type: "text"; text: string }
    | { type: "image"; url?: string; base64?: string; mimeType?: string }
    | { type: "tool_result"; toolCallId?: string; text: string }
  >;
};

Why this helps:

  • Supports multimodal inputs
  • Makes tool results explicit
  • Avoids provider-specific assumptions

5) Standardize tools/function calling

Use one internal tool schema, preferably JSON Schema:

{
  "name": "search_docs",
  "description": "Search internal docs",
  "input_schema": {
    "type": "object",
    "properties": {
      "query": { "type": "string" }
    },
    "required": ["query"],
    "additionalProperties": false
  }
}

Then translate:

  • OpenAI → function/tool schema
  • Anthropic → tool definitions
  • Gemini → function declarations

Also standardize tool call output internally as:

{
  "name": "search_docs",
  "arguments": { "query": "pricing" }
}

6) Standardize streaming if you need it

Streaming is where providers differ the most.

Recommended approach:

  • Internally define your own event types:
    • message.delta
    • tool_call.delta
    • message დასრულված
    • error
  • Convert each provider’s streaming events into those

Example canonical stream event:

{
  "type": "message.delta",
  "text": "Hello"
}

This makes UI and orchestration code provider-agnostic.


7) Keep metadata and usage consistent

Define internal usage and metadata fields:

{
  "usage": {
    "input_tokens": 123,
    "output_tokens": 456,
    "total_tokens": 579
  },
  "metadata": {
    "request_id": "abc",
    "latency_ms": 1200,
    "cache_hit": false
  }
}

Each provider may report different token accounting, so store both:

  • normalized usage
  • raw provider usage

8) Recommended architecture

Layer 1: App code

Uses only canonical request/response objects.

Layer 2: Provider adapter

One adapter each for OpenAI, Anthropic, Gemini.

Layer 3: Provider SDK/API

Actual vendor calls.

This keeps vendor lock-in low and makes testing easier.


9) A minimal example flow

  1. Your app creates a canonical request.
  2. Adapter converts it to OpenAI/Anthropic/Gemini format.
  3. Provider returns a response.
  4. Adapter parses response into canonical response.
  5. App processes output the same way regardless of provider.

10) Practical tips

  • Store raw payloads for debugging and audits.
  • Use JSON Schema for tool inputs and structured outputs.
  • Make your canonical schema strict and validate it.
  • Write contract tests for each adapter.
  • Expect edge cases:
    • multiple assistant parts
    • partial tool calls in streaming
    • different finish reasons
    • provider-specific safety blocks

11) If you want the simplest possible standard

If you don’t need full multimodal/tool complexity, standardize on:

{
  "input": [
    { "role": "system", "text": "..." },
    { "role": "user", "text": "..." }
  ],
  "output": {
    "text": "...",
    "tool_calls": []
  }
}

That’s enough for many apps, and you can expand later.


If you want, I can also provide:

  1. a TypeScript canonical schema + adapters, or
  2. a Python implementation for OpenAI/Anthropic/Gemini normalization.

Observation history

How did Obsurfable measure this prompt?

Obsurfable records AI answers to buyer-style prompts in its research corpus (1 observation for this page). Metrics are distributions over observations, not a single static ranking.

Which AI systems does Obsurfable collect answers from?

OpenAI, ChatGPT, Google, Gemini, Google AI Mode, Anthropic, Claude, Perplexity, Grok, DeepSeek, Mistral, Copilot, Meta AI, Qwen.

How can brands improve their AEO?

Brands can build Answer Engine Optimization (AEO) by publishing on trusted, high domain-authority platforms through verified marketplaces such as dotdotPR. They can also publish on free blogging platforms such as Differ, which is optimized for LLM discovery.

Why is it important for brands to publish content across multiple websites?

Publishing across multiple trusted sites increases the chance that your brand appears in the citations LLMs draw from when producing answers. Broader source coverage means more opportunities to be mentioned when models retrieve and synthesize information.

Want this interpreted for your brand?

Explorer is the free public corpus. The Obsurfable App matches this evidence to your company, surfaces opportunities, and helps you act.