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.
Configuration
Section titled “Configuration”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:
| Field | Type | Description |
|---|---|---|
APIKey | string | The key requests are authenticated with. Falls back to ANTHROPIC_API_KEY. |
BaseURL | string | Overrides the endpoint. Falls back to ANTHROPIC_BASE_URL, then the SDK’s default. |
Opts | []option.RequestOption | anthropic-sdk-go request options applied to every request. |
Models | map[string]ai.ModelOptions | Describes 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.
Models
Section titled “Models”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-5claude-opus-5claude-sonnet-5claude-opus-4-8claude-opus-4-7claude-opus-4-6claude-opus-4-5claude-sonnet-4-6claude-sonnet-4-5claude-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.
Usage example
Section titled “Usage example”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.
Configuration options
Section titled “Configuration options”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.
Fields Genkit owns
Section titled “Fields Genkit owns”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 field | Use instead |
|---|---|
messages | ai.WithMessages(), ai.WithPrompt() |
system | ai.WithSystem() |
model | ai.WithModel(), ai.WithModelName() |
output_config.format | ai.WithOutputType(), ai.WithOutputSchema() |
a custom function tool in tools | ai.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 and reasoning
Section titled “Thinking and reasoning”Thinking is a union with three arms:
OfEnabledtakes a fixedBudgetTokens, which must be at least 1024 and belowMaxTokens.OfDisabledturns thinking off.OfAdaptivelets the model pick its own budget per request, so a fixed budget is rejected on a model that thinks adaptively.OutputConfig.Effortis the knob there, one ofsdk.OutputConfigEffortLow,Medium,High, orMax.
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.
Server-side tools
Section titled “Server-side tools”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?"),)Multimodal input
Section titled “Multimodal input”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), ), ),)Tool calling
Section titled “Tool calling”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.
Streaming
Section titled “Streaming”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 }),)Prompt caching and citations
Section titled “Prompt caching and citations”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.
Choosing between the two plugins
Section titled “Choosing between the two plugins”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/pdfandtext/plainas Anthropic document blocks. The compatible endpoint has onlyimage_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_resultcontent 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 curatedChatConfig.
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:
| Field | Type | Notes |
|---|---|---|
Temperature | *float64 | Randomness 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. |
MaxOutputTokens | int | Sent as the API’s max_tokens. Minimum 1. |
TopP | *float64 | Nucleus sampling threshold. Anthropic documents no range, so the schema declares none. |
StopSequences | []string | Stop generation when produced by the model. The endpoint rejects whitespace-only sequences. |
Thinking | *anthropic.ThinkingConfig | Extended thinking controls. See the next table. |
anthropic.ThinkingConfig:
| Field | Type | Notes |
|---|---|---|
Type | string | "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. |
BudgetTokens | int | Sent 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:
| Field | Type | Notes |
|---|---|---|
APIKey | string | Overrides 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. |
Version | string | Pins 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. |
Extra | map[string]any | Request 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.
Using multiple providers
Section titled “Using multiple providers”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())Learn more
Section titled “Learn more”- Generating content with AI models
- Tool calling
- Middleware for retry and fallback around a Claude model