VernLLMVernLLM
Adapters

Anthropic

Use the fromAnthropic adapter with the Anthropic API

anthropic-basic-setup.ts
import Anthropic from '@anthropic-ai/sdk';
import { VernLLM, fromAnthropic } from 'vern-llm';

const llm = new VernLLM({
  client: fromAnthropic(new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY })),
  model: 'claude-sonnet-4-6',
});

AnthropicClient is the public client type exported by VernLLM and accepted by fromAnthropic. It allows TypeScript users to type their Anthropic SDK client without depending on adapter internals.

The Anthropic SDK retries on its own too

@anthropic-ai/sdk retries transient failures, including 429s, internally by default (maxRetries: 2), before VernLLM's own retry loop ever sees them. Pass maxRetries: 0 to the Anthropic client if you want VernLLM to be the sole retry authority. See SDK-internal retries.

Anthropic does not support OpenAI's response_format directly. JSON schema responses are mapped to Anthropic's native tool-use mechanism: the schema becomes the tool input_schema, and tool_choice forces the model to call that tool. description and strict are forwarded when provided. Tool input is converted back into JSON text for the standard LLMClient response shape. Plain JSON mode (json_object) is not supported as a real constraint: Anthropic has no field that mechanically guarantees valid JSON output for this mode. reasoning_effort is dropped (no direct Anthropic equivalent).

Without jsonSchema, whether an unset json_object mode throws or is silently downgraded to plain text depends on what else the call is asking for:

  • A plain call (llm.call({ userContent }), no schema, no explicit jsonMode) is silently treated as plain text: json_object is never sent, and the response comes back unparsed. This keeps the common, no-config case working exactly as before.
  • An explicit jsonMode: true throws LLMError('invalid_params') before the request is sent, since that's a caller deliberately asking for a guarantee Anthropic can't provide.
  • schema (Zod-style client-side validation) without jsonSchema, whether or not jsonMode is set explicitly, also throws LLMError('invalid_params'): schema needs JSON output to validate against, so an implicit request for JSON is treated the same as an explicit one rather than silently downgraded, which would skip validation instead of running it. Use jsonSchema instead: it maps to a real constraint and still runs schema against its parsed result.

Tool calling

Anthropic doesn't support tool calling and thinking at the same time when tool_choice forces tool use. Setting budgetTokens/reasoningEffort alongside a forced toolChoice (a specific tool, or 'required') throws LLMError('invalid_params') locally before the request is sent, Anthropic rejects the combination outright with a 400. toolChoice: 'auto' (the default when tools is set) works fine alongside thinking, the model just needs to stay able to reply with plain text. Pass budgetTokens: null/reasoningEffort: null to opt one call out of an instance-level reasoning default when it needs a forced choice. See Call Params → Reasoning for the full detail.

tools and tool_calls map onto Anthropic's native tool_use / tool_result content blocks. Since Anthropic requires strict user/assistant alternation, fromAnthropic merges consecutive tool result messages from a multi-tool turn back into a single user turn with multiple tool_result blocks. ToolResult.isError is forwarded as tool_result.is_error. See Tool Calling for the full reference.

For the Anthropic adapter, tool parameter schemas and response_format: { type: 'json_schema' } schemas must have a root type: 'object'. The public VernLLM request types remain broadly typed for JSON Schema, so this restriction is validated at runtime. A schema with a non-object root causes fromAnthropic to throw an LLMError with category validation before the request is sent to Anthropic. When migrating to the Anthropic adapter, ensure all tool and JSON Schema parameter schemas use an object root.

Combining tools with jsonSchema

By default, jsonSchema and tools cannot be used in the same call: jsonSchema is emulated as a forced single tool call, which occupies the same tools/tool_choice field real tools need.

On unsupported models, this throws LLMError('invalid_params') with code: 'unsupported_capability' and issues.capability: 'tools_with_json_schema', so the default fallback policy can try the next target.

Anthropic also supports native, schema-constrained output, output_config.format, a request field independent of tools/tool_choice, so it composes with real tools on models that support it. This is opt-in, pass nativeStructuredOutputModels as the second argument to fromAnthropic. See Anthropic's Structured outputs → Compatibility page for the current list of model families and platforms that support it; that list isn't hardcoded here since it varies by exact model and can change.

anthropic-native-structured-output.ts
const llm = new VernLLM({
  client: fromAnthropic(new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY }), {
    nativeStructuredOutputModels: ['claude-native-model-id'],
    // ...or a predicate, e.g. by model family:
    // nativeStructuredOutputModels: (model) => model.startsWith('claude-4'),
  }),
  model: 'claude-native-model-id',
});

There is no built-in default list, see Structured Output → Combining with tools for why. On a covered model, jsonSchema is sent as output_config.format instead of a forced tool call, and the schema-conforming JSON arrives as ordinary text content, no unwrapping needed, while any real tools you pass are built and sent exactly as they would be without jsonSchema present. On a model not covered by nativeStructuredOutputModels, combining tools with jsonSchema throws LLMError('invalid_params') with code: 'unsupported_capability' and issues.capability: 'tools_with_json_schema', naming the model and pointing at this option. The default fallback policy can then try the next target.

output_config.format only sends type and schema, matching the real Anthropic API exactly. Unlike the legacy forced-tool-call path (where jsonSchema's description and strict become real Tool fields), neither has an equivalent here, and jsonSchema.name never reaches this particular request at all.

fromAnthropic's second argument also accepts reasoningEffortTokens, an override for the token counts reasoningEffort tiers map onto when budgetTokens isn't set directly. See the Call Params reference for the full table and how it works across every adapter.

Streaming

fromAnthropic implements createStream on top of the Messages API's own streaming mode. Anthropic's content_block_delta events map onto text-delta chunks, and input_json_delta events on a tool_use block map onto tool_call_delta chunks. No extra setup is needed beyond the client already passed to fromAnthropic:

anthropic-streaming.ts
const { chunks, finalResult } = await llm.call({
  userContent: 'Write a short poem about the ocean.',
  stream: true,
});

for await (const chunk of chunks) {
  if (chunk.type === 'text-delta') process.stdout.write(chunk.delta);
}

When jsonSchema forces a single tool call, fromAnthropic streams the tool's JSON payload as text-delta chunks and suppresses genuine preamble text, keeping the streamed output consistent with what non-streaming create() returns for the same request.

Anthropic's documented ping events, sent periodically to keep long-running streams alive (e.g. extended thinking), are recognized and reset VernLLM's chunkIdleTimeoutMs clock. They carry no content and are never surfaced through chunks. See Per-chunk idle timeout.

See Streaming for the full stream: true contract, none of which changes per-provider beyond how each adapter maps its own streaming events.

Multimodal input

fromAnthropic supports VernLLM's provider-independent ContentBlock[] format for image input. Image blocks are translated into Anthropic's native content blocks automatically. The selected Anthropic model must support vision/image input for multimodal requests to succeed.

anthropic-multimodal-input.ts
const result = await llm.call({
  userContent: [
    {
      type: 'text',
      text: 'Describe this image.',
    },
    {
      type: 'image',
      mimeType: 'image/png',
      data: imageBase64,
    },
  ],
});

Image data must be base64-encoded bytes without a data: URL prefix. Supported image types are image/png, image/jpeg, image/gif, and image/webp.

Plain string userContent remains unchanged for text-only requests.

On this page