Skip to content

Anthropic plugin

Go reaches Claude two ways. The plugins/anthropic plugin speaks Anthropic’s native Messages API and is the default choice. The plugins/compat_oai/anthropic plugin reaches the same models through Anthropic’s OpenAI-compatible endpoint, which Anthropic positions for testing and comparison. Choosing between the two plugins compares them field by field.

Unless stated otherwise, the sections below describe the native plugin.

import "github.com/firebase/genkit/go/plugins/anthropic"
g := genkit.Init(context.Background(), genkit.WithPlugins(&anthropic.Anthropic{}))

You need an API key from the Anthropic Console. The plugin reads ANTHROPIC_API_KEY from the environment, and ANTHROPIC_BASE_URL for the endpoint. Init panics when nothing authenticates: no APIKey field, no ANTHROPIC_API_KEY, no ANTHROPIC_AUTH_TOKEN, and an empty Opts.

The plugin struct carries four fields:

FieldTypeDescription
APIKeystringThe key requests are authenticated with. Falls back to ANTHROPIC_API_KEY.
BaseURLstringOverrides the endpoint. Falls back to ANTHROPIC_BASE_URL, then the SDK’s default.
Opts[]option.RequestOptionanthropic-sdk-go request options applied to every request.
Modelsmap[string]ai.ModelOptionsDescribes or corrects what the plugin knows about a model.

Opts passes the anthropic-sdk-go client configuration through untouched: retries, timeouts, extra headers, option.WithMiddleware, and the SDK’s Bedrock and Vertex routing helpers, which are request options too.

import (
"time"
"github.com/anthropics/anthropic-sdk-go/option"
"github.com/firebase/genkit/go/genkit"
"github.com/firebase/genkit/go/plugins/anthropic"
)
g := genkit.Init(ctx, genkit.WithPlugins(&anthropic.Anthropic{
Opts: []option.RequestOption{
option.WithMaxRetries(5),
option.WithRequestTimeout(30 * time.Second),
},
}))

Options in Opts are applied after the ones derived from APIKey and BaseURL, and the SDK applies options in order, so an option here wins when it sets the same thing. Distinct settings do not displace each other: the API key rides an X-Api-Key header, so a key-less setup such as Bedrock or Vertex routing needs the key unset everywhere, or an option.WithHeaderDel for X-Api-Key in Opts.

Init registers no models. Every Claude model resolves on first use, and the plugin lists Anthropic’s live catalog through the models API, cached for an hour. These IDs carry curated capabilities:

  • claude-fable-5
  • claude-opus-5
  • claude-sonnet-5
  • claude-opus-4-8
  • claude-opus-4-7
  • claude-opus-4-6
  • claude-opus-4-5
  • claude-sonnet-4-6
  • claude-sonnet-4-5
  • claude-haiku-4-5

The list is a starting point, not a limit. Any Claude ID resolves on demand, so a model released after this version of the plugin works before the plugin knows about it, and a dated snapshot such as claude-sonnet-4-5-20250929 has its date suffix stripped and picks up the description of the alias it points at. A model outside the curated list claims no native constrained generation, so Genkit puts schema instructions in the prompt for it instead.

Use Models to describe or correct a model:

g := genkit.Init(ctx, genkit.WithPlugins(&anthropic.Anthropic{
Models: map[string]ai.ModelOptions{
"claude-opus-6": {Label: "Claude Opus 6"},
},
}))

Entries overlay rather than replace: a field left at its zero value keeps what the plugin resolved, so an entry can pin one capability without restating the label or the config schema. They apply wherever a model is described, both to the catalog the Dev UI lists and to the model built to serve a request, so ordering does not matter.

resp, err := genkit.Generate(ctx, g,
ai.WithModelName("anthropic/claude-sonnet-4-5"),
ai.WithPrompt("Analyze this complex problem step by step."),
)
if err != nil {
return err
}
fmt.Println(resp.Text())

For a runnable streaming flow built on this plugin, see go/samples/anthropic.

Claude models take the Anthropic SDK’s own request type, anthropic.MessageNewParams, as their config, so config keys are Anthropic’s wire names. Genkit validates a request’s config against the model’s advertised schema and deserializes it into that struct before the request goes out. ModelRef types the config at the call site. This package and the SDK are both named anthropic, so one of them needs an import alias:

import (
sdk "github.com/anthropics/anthropic-sdk-go"
"github.com/firebase/genkit/go/ai"
"github.com/firebase/genkit/go/genkit"
"github.com/firebase/genkit/go/plugins/anthropic"
)
resp, err := genkit.Generate(ctx, g,
ai.WithModel(anthropic.ModelRef("claude-sonnet-4-5", &sdk.MessageNewParams{
MaxTokens: 4096,
Thinking: sdk.ThinkingConfigParamUnion{
OfEnabled: &sdk.ThinkingConfigEnabledParam{BudgetTokens: 2048},
},
})),
ai.WithPrompt("Answer concisely."),
)
if err != nil {
return err
}
fmt.Println(resp.Reasoning())
fmt.Println(resp.Text())

MaxTokens is an int64 and falls back to 4096 when left unset. temperature, top_k, and top_p are deprecated on Claude Opus 4.7 and later, which reject any value set.

The advertised schema is reflected from the anthropic-sdk-go version your build links, so a field Anthropic ships later becomes usable, and validated, by bumping the SDK in your own go.mod.

A config that carries a field Genkit builds from the request is rejected with INVALID_ARGUMENT, and the error names the option to use instead:

Config fieldUse instead
messagesai.WithMessages(), ai.WithPrompt()
systemai.WithSystem()
modelai.WithModel(), ai.WithModelName()
output_config.formatai.WithOutputType(), ai.WithOutputSchema()
a custom function tool in toolsai.WithTools()

output_config.effort is unaffected, and the config-level tools field stays open for Anthropic’s server-side tools. Genkit tools are appended to whatever the config carries rather than replacing it.

Thinking is a union with three arms:

  • OfEnabled takes a fixed BudgetTokens, which must be at least 1024 and below MaxTokens.
  • OfDisabled turns thinking off.
  • OfAdaptive lets the model pick its own budget per request, so a fixed budget is rejected on a model that thinks adaptively. OutputConfig.Effort is the knob there, one of sdk.OutputConfigEffortLow, Medium, High, or Max.
var model = anthropic.ModelRef("claude-sonnet-5", &sdk.MessageNewParams{
MaxTokens: 4000,
Thinking: sdk.ThinkingConfigParamUnion{
OfAdaptive: &sdk.ThinkingConfigAdaptiveParam{},
},
OutputConfig: sdk.OutputConfigParam{Effort: sdk.OutputConfigEffortLow},
})

Thinking comes back as Genkit reasoning parts, readable through resp.Reasoning(), and each block’s signature is preserved, so a multi-turn conversation replays its thinking intact even after the history has been serialized and reloaded.

The config-level tools field reaches Anthropic’s server-side tools, such as web search, web fetch, code execution, the text editor, and memory:

resp, err := genkit.Generate(ctx, g,
ai.WithModel(anthropic.ModelRef("claude-sonnet-4-5", &sdk.MessageNewParams{
MaxTokens: 2048,
Tools: []sdk.ToolUnionParam{{
OfWebSearchTool20250305: &sdk.WebSearchTool20250305Param{MaxUses: sdk.Int(3)},
}},
})),
ai.WithPrompt("What shipped in Go 1.25?"),
)

Media parts route by content type: image/* becomes an image block, application/pdf and text/plain become document blocks. Any other type, audio for example, fails before the request is sent, with an error naming the accepted set.

resp, err := genkit.Generate(ctx, g,
ai.WithModelName("anthropic/claude-sonnet-4-5"),
ai.WithMessages(
ai.NewUserMessage(
ai.NewTextPart("What do you see in this image?"),
ai.NewMediaPart("image/jpeg", imageData),
),
),
)

A PDF is sent the same way, as a document block:

resp, err := genkit.Generate(ctx, g,
ai.WithModelName("anthropic/claude-sonnet-4-5"),
ai.WithMessages(
ai.NewUserMessage(
ai.NewTextPart("Summarize this document."),
ai.NewMediaPart("application/pdf", pdfData),
),
),
)

Define tools with genkit.DefineTool and pass them with ai.WithTools(). ai.ToolChoice is honored: auto maps to auto, required to any, and none to none. Leaving it unset does not disturb a tool_choice set in the config.

A tool that attaches content parts to its response sends them inside the Anthropic tool_result block, alongside the structured output, using the same content-type mapping as user media. A part Anthropic cannot carry in a tool result, reasoning or a nested tool request for example, fails rather than being dropped silently. See Tool calling for how to define tools, and go/samples/basic-tools for a runnable example.

resp, err := genkit.Generate(ctx, g,
ai.WithModelName("anthropic/claude-sonnet-4-5"),
ai.WithPrompt("Write a long explanation."),
ai.WithStreaming(func(ctx context.Context, chunk *ai.ModelResponseChunk) error {
for _, content := range chunk.Content {
fmt.Print(content.Text)
}
return nil
}),
)

Neither is available in Go. The plugin never emits a cache_control breakpoint, and system and messages are fields the config cannot carry, so there is no way to mark content for caching from Genkit. Anthropic’s cache-read count is still reported as resp.Usage.CachedContentTokens, but with no breakpoint to create the cache entry it stays at zero. There is no citations handling either: a document block is sent without citation support, and no citation metadata is read back.

Both plugins reach the same Claude models. The native plugins/anthropic plugin is the default choice, because it speaks the Messages API directly:

  • Thinking replays across turns. The native plugin sends a thinking block back with its signature intact. The compatible endpoint carries reasoning as a plain string with no signature field, and does not return the thinking content at all, only the tokens it cost.
  • Documents. The native plugin sends application/pdf and text/plain as Anthropic document blocks. The compatible endpoint has only image_url, so a PDF goes out as an image data URI.
  • Multipart tool results. The native plugin puts a tool’s extra text and image parts in the tool_result content array. The compatible endpoint sends the structured output only, and the extra parts are dropped.
  • Cached-token accounting. Only the native plugin reports cache_read_input_tokens.
  • Config surface. The native config is the whole anthropic.MessageNewParams, so server-side tools, thinking, output effort, and service tier are all reachable. The compatible plugin exposes a small curated ChatConfig.

Reach for plugins/compat_oai/anthropic when the wire shape is what matters: an existing OpenAI-shaped proxy or gateway, or a comparison harness. It also offers a per-request apiKey and an extra passthrough that the native plugin has no equivalent for, and it registers its curated models at Init, so they appear in the Dev UI without a network call.

import (
"github.com/firebase/genkit/go/ai"
"github.com/firebase/genkit/go/genkit"
"github.com/firebase/genkit/go/plugins/compat_oai/anthropic"
)
g := genkit.Init(ctx, genkit.WithPlugins(&anthropic.Anthropic{}))
resp, err := genkit.Generate(ctx, g,
ai.WithModel(anthropic.ModelRef("claude-sonnet-4-5-20250929", &anthropic.ChatConfig{
MaxOutputTokens: 2048,
Thinking: &anthropic.ThinkingConfig{
Type: "enabled",
BudgetTokens: 2000,
},
})),
ai.WithPrompt("Answer concisely."),
)

The compatible plugin reads ANTHROPIC_API_KEY and ANTHROPIC_BASE_URL, defaulting to https://api.anthropic.com/v1; a base URL without the /v1 segment gets it appended. Unlike the rest of the compat_oai family, it does not panic when no key is configured. It registers 16 models at Init, keyed mostly by dated snapshot: claude-fable-5, claude-opus-5, claude-sonnet-5, claude-opus-4-8, claude-opus-4-7, claude-opus-4-6, claude-sonnet-4-6, claude-opus-4-5-20251101, claude-sonnet-4-5-20250929, claude-haiku-4-5-20251001, claude-opus-4-1-20250805, claude-3-7-sonnet-20250219, claude-3-5-haiku-20241022, claude-3-5-sonnet-20240620, claude-3-opus-20240229, and claude-3-haiku-20240307. Dateless aliases ride along as versions of the dated entries, and any other Claude ID still resolves on demand. The Claude 3 Opus, Claude 3 Haiku, and Claude 3.5 Sonnet entries are marked as not supporting the system role.

Generation config for the compatible plugin

Section titled “Generation config for the compatible plugin”

compat_oai/anthropic.ChatConfig carries only the fields Anthropic’s OpenAI-compatible endpoint honors:

FieldTypeNotes
Temperature*float64Randomness of token selection, 0 to 1. The endpoint caps a larger value at 1 rather than honoring it, so the schema stops where the behavior does.
MaxOutputTokensintSent as the API’s max_tokens. Minimum 1.
TopP*float64Nucleus sampling threshold. Anthropic documents no range, so the schema declares none.
StopSequences[]stringStop generation when produced by the model. The endpoint rejects whitespace-only sequences.
Thinking*anthropic.ThinkingConfigExtended thinking controls. See the next table.

anthropic.ThinkingConfig:

FieldTypeNotes
Typestring"enabled" or "disabled". Not a closed enum in the schema: Anthropic documents no fixed set, and the native API’s set has grown, so a list here would reject a value the endpoint accepts.
BudgetTokensintSent as the API’s budget_tokens. At least 1024, and below MaxOutputTokens.

ChatConfig also embeds compat_oai.RequestConfig, which every plugin in the family shares:

FieldTypeNotes
APIKeystringOverrides the plugin’s key for this request alone. It never serializes, so it stays out of the advertised schema, recorded traces, and the outgoing body, and it can only be set from a typed config in code.
VersionstringPins the exact model version the request is served by, for example claude-sonnet-4-5-20250929. Overrides the model ID the request would otherwise carry.
Extramap[string]anyRequest body fields sent verbatim, keyed by the provider’s wire names (usually snake_case). A colliding key wins over the declared field. Fields Genkit builds from the request, such as messages and tools, are rejected.

Penalties, log probabilities, seed, and response_format are deliberately absent, because Anthropic documents the OpenAI-compatible endpoint as ignoring them.

The two samples are written to the same shape so the difference is the plugin and its config: go/samples/anthropic and go/samples/compat_oai/anthropic.

You can use OpenAI and Anthropic in the same application. Each plugin takes its own SDK’s request type as config, so the OpenAI reference takes openaiGo.ChatCompletionNewParams while the Claude reference takes sdk.MessageNewParams:

import (
sdk "github.com/anthropics/anthropic-sdk-go"
"github.com/firebase/genkit/go/ai"
"github.com/firebase/genkit/go/genkit"
"github.com/firebase/genkit/go/plugins/anthropic"
"github.com/firebase/genkit/go/plugins/compat_oai/openai"
openaiGo "github.com/openai/openai-go"
)
g := genkit.Init(ctx, genkit.WithPlugins(
&openai.OpenAI{APIKey: "YOUR_OPENAI_KEY"},
&anthropic.Anthropic{},
))
gpt, err := genkit.Generate(ctx, g,
ai.WithModel(openai.ModelRef("gpt-5.5", &openaiGo.ChatCompletionNewParams{
Temperature: openaiGo.Float(0.7),
})),
ai.WithPrompt("Draft a product summary."),
)
if err != nil {
return err
}
claude, err := genkit.Generate(ctx, g,
ai.WithModel(anthropic.ModelRef("claude-sonnet-4-5", &sdk.MessageNewParams{MaxTokens: 1024})),
ai.WithPrompt("Critique this summary: %s", gpt.Text()),
)
if err != nil {
return err
}
fmt.Println(claude.Text())