원본 캡처
verified · type: reference · [meta, raw]

I now have precise, verified v6 API shapes. I have everything I need to write the reference. Let me compile the structured markdown research output.

Based on my cached Anthropic API skill knowledge (authoritative for Anthropic shapes) plus verified web research for the Vercel AI SDK v6, AI Gateway, and OpenAI parity, here is the practical LLM API integration reference.


Integrating LLMs via API — Practical Reference (2026)

Covers the Anthropic Messages API, OpenAI parity notes, the Vercel AI SDK v6 + AI Gateway as a provider-agnostic layer, and common SDK patterns. Code snippets in TypeScript/Python. See the VOLATILE CLAIMS section at the end for every version number and endpoint — those drift fast and are pinned to what I verified on 2026-07-23.


1. Anthropic Messages API

Everything goes through a single endpoint: POST /v1/messages. Tools, structured output, streaming, caching, and thinking are all features of that one endpoint — not separate APIs.

1.1 Auth

# Required headers on every request
x-api-key: $ANTHROPIC_API_KEY
anthropic-version: 2023-06-01
content-type: application/json
anthropic-beta: <feature-flag>   # only when using a beta feature

SDKs read ANTHROPIC_API_KEY from the environment automatically. An unset key does not mean no credentials — the SDK/CLI resolution order is ANTHROPIC_API_KEYANTHROPIC_AUTH_TOKEN → an OAuth profile from ant auth login (stored under ~/.config/anthropic/) → Workload Identity Federation env vars. A bare Anthropic() works after ant auth login with no env var set. For raw HTTP with an OAuth token, use Authorization: Bearer <token> plus anthropic-beta: oauth-2025-04-20 — not x-api-key.

1.2 Message structure

The API is stateless — you resend the full conversation history each turn. Messages alternate user/assistant (first must be user; consecutive same-role messages are merged). Content is either a string or a list of typed content blocks (text, image, document, tool_use, tool_result, thinking).

Python:

import anthropic
client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=16000,
    system="You are a helpful assistant.",  # top-level, not a message
    messages=[{"role": "user", "content": "What is the capital of France?"}],
)
# response.content is a list of blocks — check .type before reading .text
for block in response.content:
    if block.type == "text":
        print(block.text)

TypeScript:

import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();

const response = await client.messages.create({
  model: "claude-opus-4-8",
  max_tokens: 16000,
  system: "You are a helpful assistant.",
  messages: [{ role: "user", content: "What is the capital of France?" }],
});
for (const block of response.content) {
  if (block.type === "text") console.log(block.text);
}

Key response fields: content (block list), stop_reason (end_turn | max_tokens | tool_use | pause_turn | refusal), usage (token counts). Always branch on stop_reason before reading content[0] — a refusal can leave content empty.

Model note: claude-opus-4-8 is the current default. Thinking is controlled by thinking: {type: "adaptive"} on 4.6+ models — the old budget_tokens is rejected with a 400 on Opus 4.7/4.8, Sonnet 5, and Fable 5. Effort goes in output_config: {effort: "low"|"medium"|"high"|"xhigh"|"max"}.

1.3 Streaming

Use streaming for any long input/output or high max_tokens (the SDK raises a ValueError on non-streaming requests it estimates will exceed ~10 min). The .stream() helper accumulates state and exposes get_final_message().

Python:

with client.messages.stream(
    model="claude-opus-4-8", max_tokens=64000,
    messages=[{"role": "user", "content": "Write a story"}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)
    final = stream.get_final_message()  # complete Message object

TypeScript:

const stream = client.messages.stream({
  model: "claude-opus-4-8", max_tokens: 64000,
  messages: [{ role: "user", content: "Write a story" }],
});
for await (const event of stream) {
  if (event.type === "content_block_delta" && event.delta.type === "text_delta")
    process.stdout.write(event.delta.text);
}
const final = await stream.finalMessage();

Raw SSE event types: message_startcontent_block_startcontent_block_deltacontent_block_stopmessage_delta (carries stop_reason, usage) → message_stop.

1.4 Tool use

Define tools with a JSON Schema; loop until stop_reason != "tool_use". The SDK tool runner (beta) drives the loop for you.

Tool runner (Python, recommended):

from anthropic import beta_tool

@beta_tool
def get_weather(location: str) -> str:
    """Get current weather. Args: location: City and state."""
    return f"72°F and sunny in {location}"

runner = client.beta.messages.tool_runner(
    model="claude-opus-4-8", max_tokens=16000,
    tools=[get_weather],
    messages=[{"role": "user", "content": "Weather in Paris?"}],
)
for message in runner:  # loop stops automatically when Claude is done
    print(message)

Manual loop shape (any language): send request → if stop_reason == "tool_use", execute each tool_use block, append the assistant's full response.content then a user message with one tool_result block per tool_use (matching tool_use_id), and repeat. Return all parallel tool results in a single user message. On failure, return tool_result with is_error: true — don't drop it.

Server-side tools (run on Anthropic's infra, no client execution): declare in tools and read the result blocks in the same response — web_search_20260209, web_fetch_20260209 (dynamic filtering, Opus 4.6+/Sonnet 4.6+), code_execution_20260521.

1.5 Structured output

Two mechanisms, both on the single endpoint: output_config.format (constrain the response to a JSON schema) and strict: true (validate tool params). Prefer client.messages.parse() with a Pydantic/Zod model.

from pydantic import BaseModel

class Contact(BaseModel):
    name: str
    email: str

response = client.messages.parse(
    model="claude-opus-4-8", max_tokens=16000,
    messages=[{"role": "user", "content": "Extract: Jane (jane@co.com)"}],
    output_format=Contact,
)
contact = response.parsed_output  # validated Contact instance

Note: output_config.format is the canonical parameter; the old top-level output_format on messages.create() is deprecated. Incompatible with citations and message prefill. Prefilling the assistant turn is rejected with a 400 on Opus 4.6+/Sonnet 4.6+/Fable 5 — use structured output instead.

1.6 Prompt caching

Caching is a prefix match — render order is toolssystemmessages, and any byte change in the prefix invalidates everything after it. Put stable content first, volatile content (timestamps, per-request IDs) after the last breakpoint.

response = client.messages.create(
    model="claude-opus-4-8", max_tokens=16000,
    system=[{
        "type": "text", "text": large_document,
        "cache_control": {"type": "ephemeral"},  # or {"type": "ephemeral", "ttl": "1h"}
    }],
    messages=[{"role": "user", "content": "Summarize"}],
)

Top-level cache_control={"type": "ephemeral"} auto-caches the last cacheable block. Max 4 breakpoints; minimum cacheable prefix is model-dependent (4096 tokens on Opus 4.8, 1024 on Sonnet 4.5). Verify with usage.cache_read_input_tokens — if it's zero across repeated identical-prefix requests, a silent invalidator (datetime.now() in the system prompt, unsorted json.dumps, varying tool set) is at work. Cache reads cost ~0.1×; writes cost 1.25× (5-min TTL) or 2× (1-hour TTL).

1.7 Token counting

Use POST /v1/messages/count_tokensnever tiktoken (it's OpenAI's tokenizer and undercounts Claude by ~15–20%). Counts are model-specific.

n = client.messages.count_tokens(
    model="claude-opus-4-8",
    messages=[{"role": "user", "content": open("doc.md").read()}],
).input_tokens

The endpoint is stateless — to diff a file across versions, count each and subtract.


2. OpenAI API — parity notes

OpenAI now has two primary interfaces, and this matters for integration decisions:

  • Chat Completions (/v1/chat/completions) — the long-standing, message-based API. Still supported, and it's the shape every third-party "OpenAI-compatible" endpoint implements.
  • Responses API (/v1/responses) — the newer, item/response-based API. As of 2026 OpenAI positions it as the default/recommended for all new projects, especially agentic and reasoning-model (GPT-5.x) workloads: better cache utilization and reasoning-state persistence across turns. The Assistants API is being sunset in favor of it.

Mapping to Anthropic concepts:

Concept Anthropic OpenAI Chat Completions OpenAI Responses
Endpoint POST /v1/messages POST /v1/chat/completions POST /v1/responses
System prompt top-level system {role: "system"} (or developer) message instructions field
Conversation state stateless, resend history stateless, resend messages can persist server-side via previous_response_id
Auth header x-api-key Authorization: Bearer Authorization: Bearer
Tool result role user msg w/ tool_result block {role: "tool"} message function-call output item
Streaming SSE content-block deltas SSE chat.completion.chunk SSE typed events
Structured output output_config.format / strict response_format: {type: "json_schema"} text.format json_schema
Token field usage.input_tokens usage.prompt_tokens usage.input_tokens

Practical takeaways for integration: - If you target OpenAI-compatible gateways (many open-model hosts, older tooling), you're speaking Chat Completions. Message roles and usage.prompt_tokens/completion_tokens differ from Anthropic's input_tokens/output_tokens. - Anthropic's SDK is not OpenAI-compatible by default — don't reach for openai-shaped shims against Anthropic; use @anthropic-ai/sdk / anthropic. - The cleanest way to stay provider-agnostic across both is a normalizing layer (§3): the Vercel AI SDK abstracts Chat-Completions vs Responses vs Messages behind one interface.


3. Vercel AI SDK v6 + AI Gateway (provider-agnostic layer)

The AI SDK is a TypeScript toolkit that normalizes text generation, structured output, tool calling, embeddings, and streaming across providers behind one interface. AI Gateway is a routing layer: one API key, plain creator/model strings, provider failover and cost/latency routing.

Version reality (verified 2026-07-23): ai@latest on npm is now v7 (7.0.35). v6 is still actively maintained — the ai-v6 dist-tag resolves to 6.0.234, and provider packages (@ai-sdk/openai, @ai-sdk/anthropic, @ai-sdk/gateway, @ai-sdk/react) are on the 4.x line. Since this reference targets v6, pin explicitly: npm i ai@6 (or ai@ai-v6). Everything below is v6 shape; note where it changed from v5.

3.1 Text generation

import { generateText, streamText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";

// One-shot
const { text } = await generateText({
  model: anthropic("claude-opus-4-8"),
  prompt: "Explain quantum computing in one paragraph.",
});

// Streaming
const result = streamText({
  model: anthropic("claude-opus-4-8"),
  prompt: "Write a story",
});
for await (const chunk of result.textStream) process.stdout.write(chunk);

3.2 AI Gateway (provider-agnostic routing)

The gateway provider is importable from the main ai package — pass a plain creator/model-name string and it routes automatically:

import { generateText, gateway } from "ai";

// Plain string routes through the Gateway automatically
const { text } = await generateText({
  model: "anthropic/claude-opus-4.8",   // creator/model-name format
  prompt: "Hello world",
});

// Explicit, with routing control
const { text: t2 } = await generateText({
  model: gateway("openai/gpt-5.6-terra"),
  prompt: "Hello",
  providerOptions: {
    gateway: {
      order: ["vertex", "anthropic"],  // try in this order
      only: ["vertex", "anthropic"],   // restrict to these providers
      sort: "cost",                     // rank by 'cost' | 'ttft' | 'tps'
    },
  },
});

Auth: set AI_GATEWAY_API_KEY in the environment (or createGateway({ apiKey }) for a custom instance). Model strings are anthropic/claude-sonnet-4.6, openai/gpt-5.4, xai/grok-4.5, etc. — note the Gateway uses dotted names (claude-opus-4.8), distinct from Anthropic's first-party dashed IDs (claude-opus-4-8).

3.3 Structured output — changed in v6

generateObject/streamObject are deprecated in v6. Use generateText/streamText with an output setting via Output:

import { generateText, Output } from "ai";
import { z } from "zod";

const { output } = await generateText({
  model: "anthropic/claude-opus-4.8",
  output: Output.object({
    schema: z.object({
      name: z.string(),
      ingredients: z.array(z.object({ name: z.string(), amount: z.string() })),
    }),
  }),
  prompt: "Generate a lasagna recipe.",
});
// streaming: use streamText + partialOutputStream (was partialObjectStream in v5)

3.4 Tool calling — changed in v6

Tool names now derive from object keys (drop the name property); the schema field is inputSchema (was parameters in older versions):

import { generateText, tool, stepCountIs } from "ai";
import { z } from "zod";

const { text } = await generateText({
  model: "anthropic/claude-opus-4.8",
  tools: {
    getWeather: tool({
      description: "Get current weather",
      inputSchema: z.object({ location: z.string() }),
      execute: async ({ location }) => `72°F in ${location}`,
    }),
  },
  stopWhen: stepCountIs(5),  // multi-step tool loop
  prompt: "Weather in Paris?",
});

3.5 Agents — new/renamed in v6

The agent abstraction is ToolLoopAgent (was Experimental_Agent in v5). Note systeminstructions, and the default stopWhen is now isStepCount(20):

import { ToolLoopAgent } from "ai";

const agent = new ToolLoopAgent({
  model: "anthropic/claude-opus-4.8",
  instructions: "You are a helpful assistant.",
  tools: { /* ... */ },
});

3.6 Embeddings — renamed in v6

Provider methods changed from textEmbeddingModel/textEmbedding to embeddingModel/embedding; generics were dropped from EmbeddingModel, embed, embedMany:

import { embed, embedMany } from "ai";
import { openai } from "@ai-sdk/openai";

const { embedding } = await embed({
  model: openai.embedding("text-embedding-3-small"),
  value: "sunny day at the beach",
});

const { embeddings } = await embedMany({
  model: openai.embedding("text-embedding-3-small"),
  values: ["doc one", "doc two", "doc three"],  // auto-chunks large batches
});

3.7 Other v6 migration notes (if coming from v5)

  • CoreMessageModelMessage; convertToCoreMessages() → async convertToModelMessages().
  • UI helpers renamed: isToolUIPartisStaticToolUIPart, getToolNamegetStaticToolName, etc.
  • Run npx @ai-sdk/codemod v6 to migrate v5 code automatically.

VOLATILE CLAIMS

Everything here drifts. All values verified against live registries/docs on 2026-07-23; re-check before relying on them.

SDK / package versions

Package Version (2026-07-23) Source
ai (latest dist-tag) 7.0.35 — v7 is now the default latest npm registry
ai (ai-v6 dist-tag) 6.0.234 — v6 still actively maintained; install with ai@6 npm registry
ai (ai-v5 dist-tag) 5.0.219 npm registry
@ai-sdk/anthropic 4.0.18 npm registry
@ai-sdk/openai 4.0.18 npm registry
@ai-sdk/gateway 4.0.27 npm registry
@ai-sdk/react 4.0.38 npm registry
openai (Node) 6.48.0 npm registry
openai (Python) 2.47.0 PyPI
anthropic (Python) 0.118.0 PyPI

⚠️ v6 vs v7 caveat: The task specified v6, but ai@latest has advanced to v7. The v6 API shapes documented above (Output.object, ToolLoopAgent, embeddingModel, inputSchema) are current for v6; v7 may differ. If you're starting fresh, decide deliberately between pinning ai@6 and adopting ai@7. I did not audit v7's surface here.

Endpoints

Purpose Endpoint Notes
Anthropic messages POST https://api.anthropic.com/v1/messages + streaming via stream: true / .stream()
Anthropic token count POST /v1/messages/count_tokens model-specific
Anthropic version header anthropic-version: 2023-06-01 current stable
OpenAI (legacy) POST /v1/chat/completions message-based; the "OpenAI-compatible" shape
OpenAI (recommended new) POST /v1/responses item/response-based; default for new projects in 2026
Vercel AI Gateway key env AI_GATEWAY_API_KEY plain creator/model strings

Model IDs (change frequently — verify before use)

Provider Model ID
Anthropic (first-party, dashed) Opus 4.8 (current default) claude-opus-4-8
Anthropic (first-party) Sonnet 5 claude-sonnet-5
Anthropic (first-party) Haiku 4.5 claude-haiku-4-5
Anthropic (first-party) Fable 5 (most capable) claude-fable-5
Anthropic via AI Gateway (dotted) Opus 4.8 anthropic/claude-opus-4.8
OpenAI GPT-5.6 tiers (released 2026-07-09) gpt-5.6-sol ($5/$30), gpt-5.6-terra ($2.50/$15), gpt-5.6-luna (~$1 in)
OpenAI (older, still live) GPT-5.4 / GPT-5.1 gpt-5.4 ($2.50/$15), gpt-5.1 ($1.25/$10)

Beta feature flags / tool versions (Anthropic)

Feature Flag / type string
Web search (dynamic filtering) web_search_20260209
Web fetch web_fetch_20260209
Code execution code_execution_20260521
Files API files-api-2025-04-14
Fast mode (Opus 4.8/4.7) fast-mode-2026-02-01
Task budgets task-budgets-2026-03-13

API-shape drift to watch

  • AI SDK v6: generateObject/streamObject deprecated → use Output.object on generateText/streamText; partialObjectStreampartialOutputStream; Experimental_AgentToolLoopAgent (systeminstructions, default stopWhen: isStepCount(20)); embeddings textEmbeddingModelembeddingModel; tools drop name, use inputSchema; CoreMessageModelMessage.
  • Anthropic: budget_tokens rejected (400) on Opus 4.7/4.8, Sonnet 5, Fable 5 → use thinking: {type: "adaptive"} + output_config.effort; assistant prefill rejected (400) on 4.6+/Fable 5; output_formatoutput_config.format.
  • OpenAI: Responses API (/v1/responses) is the 2026 default recommendation over Chat Completions; Assistants API sunsetting.

Sources: ai — npm · AI SDK 6 — Vercel · AI SDK 6 migration guide · AI Gateway Provider · AI Gateway routing rules · OpenAI: Migrate to the Responses API · OpenAI Chat Completions reference · OpenAI Models · OpenAI API Pricing 2026 (DevTk) · Anthropic Messages API shapes from the bundled claude-api skill (SKILL v2.1.216, models cached 2026-06-24); npm/PyPI registry versions queried live 2026-07-23.