Skip to content
Draft
146 changes: 111 additions & 35 deletions js/plugins/anthropic/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@

<h4 align="center">Anthropic AI plugin for Google Firebase Genkit</h4>

`@genkit-ai/anthropic` is the official Anthropic plugin for [Firebase Genkit](https://github.com/firebase/genkit). It supersedes the earlier community package `genkitx-anthropic` and is now maintained by Google.
`@genkit-ai/anthropic` is the official Anthropic plugin for
[Firebase Genkit](https://github.com/firebase/genkit). It supersedes the earlier
community package `genkitx-anthropic` and is now maintained by Google.

## Supported models

The plugin supports the most recent Anthropic models: **Claude Haiku 4.5**, **Claude Sonnet 4.5**, and **Claude Opus 4.5**. Additionally, the plugin supports all of the [non-retired older models](https://platform.claude.com/docs/en/about-claude/model-deprecations#model-status).
The plugin supports the most recent Anthropic models: **Claude Haiku 4.5**,
**Claude Sonnet 4.5**, and **Claude Opus 4.5**. Additionally, the plugin
supports all of the
[non-retired older models](https://platform.claude.com/docs/en/about-claude/model-deprecations#model-status).

## Installation

Expand All @@ -23,13 +28,13 @@ Install the plugin in your project with your favorite package manager:
### Initialize

```typescript
import { genkit } from 'genkit';
import { anthropic } from '@genkit-ai/anthropic';
import { genkit } from "genkit";
import { anthropic } from "@genkit-ai/anthropic";

const ai = genkit({
plugins: [anthropic({ apiKey: process.env.ANTHROPIC_API_KEY })],
// specify a default model for generate here if you wish:
model: anthropic.model('claude-sonnet-4-5'),
model: anthropic.model("claude-sonnet-4-5"),
});
```

Expand All @@ -39,8 +44,8 @@ The simplest way to generate text is by using the `generate` method:

```typescript
const response = await ai.generate({
model: anthropic.model('claude-haiku-4-5'),
prompt: 'Tell me a joke.',
model: anthropic.model("claude-haiku-4-5"),
prompt: "Tell me a joke.",
});

console.log(response.text);
Expand All @@ -53,7 +58,7 @@ console.log(response.text);

const response = await ai.generate({
prompt: [
{ text: 'What animal is in the photo?' },
{ text: "What animal is in the photo?" },
{ media: { url: imageUrl } },
],
});
Expand All @@ -62,11 +67,12 @@ console.log(response.text);

### Extended thinking

Claude 4.5 models can expose their internal reasoning. Enable it per-request with the Anthropic thinking config and read the reasoning from the response:
Claude 4.5 models can expose their internal reasoning. Enable it per-request
with the Anthropic thinking config and read the reasoning from the response:

```typescript
const response = await ai.generate({
prompt: 'Walk me through your reasoning for Fermat’s little theorem.',
prompt: "Walk me through your reasoning for Fermat’s little theorem.",
config: {
thinking: {
enabled: true,
Expand All @@ -75,15 +81,76 @@ const response = await ai.generate({
},
});

console.log(response.text); // Final assistant answer
console.log(response.reasoning); // Summarized thinking steps
console.log(response.text); // Final assistant answer
console.log(response.reasoning); // Summarized thinking steps
```

When thinking is enabled, request bodies sent through the plugin include the `thinking` payload (`{ type: 'enabled', budget_tokens: … }`) that Anthropic's API expects, and streamed responses deliver `reasoning` parts as they arrive so you can render the chain-of-thought incrementally.
When thinking is enabled, request bodies sent through the plugin include the
`thinking` payload (`{ type: 'enabled', budget_tokens: … }`) that Anthropic's
API expects, and streamed responses deliver `reasoning` parts as they arrive so
you can render the chain-of-thought incrementally.

### Document Citations

Claude can cite specific parts of documents you provide, making it easy to trace
where information in the response came from. Use the `anthropicDocument()`
helper to create citable documents. For more details, see the
[Anthropic Citations documentation](https://platform.claude.com/docs/en/build-with-claude/citations).

```typescript
import { anthropic, anthropicDocument } from "@genkit-ai/anthropic";

const response = await ai.generate({
model: anthropic.model("claude-sonnet-4-5"),
messages: [
{
role: "user",
content: [
anthropicDocument({
source: {
type: "text",
data: "The grass is green. The sky is blue. Water is wet.",
},
title: "Basic Facts",
citations: { enabled: true },
}),
{ text: "What color is the grass? Cite your source." },
],
},
],
});

// Access citations from response parts
const citations = response.message?.content?.flatMap(
(part) => part.metadata?.citations || [],
) ?? [];

console.log("Citations:", citations);
```

**Important:** Citations must be enabled on all documents in a request, or on
none of them. You cannot mix documents with citations enabled and disabled in
the same request.

Supported document source types:

- `text` - Plain text documents (returns `char_location` citations)
- `base64` - Base64-encoded PDFs (returns `page_location` citations)
- `url` - PDFs accessible via URL (returns `page_location` citations)
- `content` - Custom content blocks with text/images (returns
`content_block_location` citations)
- `file` - File references from Anthropic's Files API, beta API only (returns
`page_location` citations)

Citations are returned in the response parts' metadata and include information
about the document index, cited text, and location (character indices, page
numbers, or block indices depending on the source type).

### Beta API Limitations

The beta API surface provides access to experimental features, but some server-managed tool blocks are not yet supported by this plugin. The following beta API features will cause an error if encountered:
The beta API surface provides access to experimental features, but some
server-managed tool blocks are not yet supported by this plugin. The following
beta API features will cause an error if encountered:

- `web_fetch_tool_result`
- `code_execution_tool_result`
Expand All @@ -93,18 +160,19 @@ The beta API surface provides access to experimental features, but some server-m
- `mcp_tool_use`
- `container_upload`

Note that `server_tool_use` and `web_search_tool_result` ARE supported and work with both stable and beta APIs.
Note that `server_tool_use` and `web_search_tool_result` ARE supported and work
with both stable and beta APIs.

### Within a flow

```typescript
import { z } from 'genkit';
import { z } from "genkit";

// ...initialize Genkit instance (as shown above)...

export const jokeFlow = ai.defineFlow(
{
name: 'jokeFlow',
name: "jokeFlow",
inputSchema: z.string(),
outputSchema: z.string(),
},
Expand All @@ -113,26 +181,27 @@ export const jokeFlow = ai.defineFlow(
prompt: `tell me a joke about ${subject}`,
});
return llmResponse.text;
}
},
);
```

### Direct model usage (without Genkit instance)

The plugin supports Genkit Plugin API v2, which allows you to use models directly without initializing the full Genkit framework:
The plugin supports Genkit Plugin API v2, which allows you to use models
directly without initializing the full Genkit framework:

```typescript
import { anthropic } from '@genkit-ai/anthropic';
import { anthropic } from "@genkit-ai/anthropic";

// Create a model reference directly
const claude = anthropic.model('claude-sonnet-4-5');
const claude = anthropic.model("claude-sonnet-4-5");

// Use the model directly
const response = await claude({
messages: [
{
role: 'user',
content: [{ text: 'Tell me a joke.' }],
role: "user",
content: [{ text: "Tell me a joke." }],
},
],
});
Expand All @@ -143,19 +212,19 @@ console.log(response);
You can also create model references using the plugin's `model()` method:

```typescript
import { anthropic } from '@genkit-ai/anthropic';
import { anthropic } from "@genkit-ai/anthropic";

// Create model references
const claudeHaiku45 = anthropic.model('claude-haiku-4-5');
const claudeSonnet45 = anthropic.model('claude-sonnet-4-5');
const claudeOpus45 = anthropic.model('claude-opus-4-5');
const claudeHaiku45 = anthropic.model("claude-haiku-4-5");
const claudeSonnet45 = anthropic.model("claude-sonnet-4-5");
const claudeOpus45 = anthropic.model("claude-opus-4-5");

// Use the model reference directly
const response = await claudeSonnet45({
messages: [
{
role: 'user',
content: [{ text: 'Hello!' }],
role: "user",
content: [{ text: "Hello!" }],
},
],
});
Expand All @@ -169,22 +238,29 @@ This approach is useful for:

## Acknowledgements

This plugin builds on the community work published as [`genkitx-anthropic`](https://github.com/BloomLabsInc/genkit-plugins/blob/main/plugins/anthropic/README.md) by Bloom Labs Inc. Their Apache 2.0–licensed implementation provided the foundation for this maintained package.
This plugin builds on the community work published as
[`genkitx-anthropic`](https://github.com/BloomLabsInc/genkit-plugins/blob/main/plugins/anthropic/README.md)
by Bloom Labs Inc. Their Apache 2.0–licensed implementation provided the
foundation for this maintained package.

## Contributing

Want to contribute to the project? That's awesome! Head over to our [Contribution Guidelines](CONTRIBUTING.md).
Want to contribute to the project? That's awesome! Head over to our
[Contribution Guidelines](CONTRIBUTING.md).

## Need support?

> [!NOTE]
> This repository depends on Google's Firebase Genkit. For issues and questions related to Genkit, please refer to instructions available in [Genkit's repository](https://github.com/firebase/genkit).

> This repository depends on Google's Firebase Genkit. For issues and questions
> related to Genkit, please refer to instructions available in
> [Genkit's repository](https://github.com/firebase/genkit).

## Credits

This plugin is maintained by Google with acknowledgement to the community contributions from [Bloom Labs Inc](https://github.com/BloomLabsInc).
This plugin is maintained by Google with acknowledgement to the community
contributions from [Bloom Labs Inc](https://github.com/BloomLabsInc).

## License

This project is licensed under the [Apache 2.0 License](https://github.com/BloomLabsInc/genkit-plugins/blob/main/LICENSE).
This project is licensed under the
[Apache 2.0 License](https://github.com/BloomLabsInc/genkit-plugins/blob/main/LICENSE).
45 changes: 44 additions & 1 deletion js/plugins/anthropic/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import Anthropic from '@anthropic-ai/sdk';
import { genkitPluginV2, type GenkitPluginV2 } from 'genkit/plugin';

import type { Part } from 'genkit';
import { ActionMetadata, ModelReference, z } from 'genkit';
import { ModelAction } from 'genkit/model';
import { ActionType } from 'genkit/registry';
Expand All @@ -31,7 +32,15 @@ import {
claudeModel,
claudeModelReference,
} from './models.js';
import { InternalPluginOptions, PluginOptions, __testClient } from './types.js';
import {
InternalPluginOptions,
PluginOptions,
__testClient,
type AnthropicDocumentOptions,
} from './types.js';

// Re-export citation type for consumers (AnthropicDocumentOptions is inferred via anthropicDocument())
export type { AnthropicCitation } from './types.js';

const PROMPT_CACHING_BETA_HEADER_VALUE = 'prompt-caching-2024-07-31';

Expand Down Expand Up @@ -152,4 +161,38 @@ export const anthropic = anthropicPlugin as AnthropicPlugin;
return claudeModelReference(name, config);
};

/**
* Creates a custom part representing an Anthropic document with optional citations support.
*
* Use this to provide documents to Claude that can be cited in responses.
* Citations must be enabled on all or none of the documents in a request.
*
* @example
* ```ts
* import { anthropic, anthropicDocument } from '@genkit-ai/anthropic';
*
* const { text } = await ai.generate({
* model: anthropic.model('claude-sonnet-4-5'),
* messages: [{
* role: 'user',
* content: [
* anthropicDocument({
* source: { type: 'text', data: 'The grass is green. The sky is blue.' },
* title: 'Nature Facts',
* citations: { enabled: true }
* }),
* { text: 'What color is the grass?' }
* ]
* }]
* });
* ```
*/
export function anthropicDocument(options: AnthropicDocumentOptions): Part {
return {
custom: {
anthropicDocument: options,
},
};
}

export default anthropic;
18 changes: 17 additions & 1 deletion js/plugins/anthropic/src/runner/beta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,20 @@ import type {
import { logger } from 'genkit/logging';

import { KNOWN_CLAUDE_MODELS, extractVersion } from '../models.js';
import { AnthropicConfigSchema, type ClaudeRunnerParams } from '../types.js';
import {
AnthropicConfigSchema,
type AnthropicDocumentOptions,
type ClaudeRunnerParams,
} from '../types.js';
import { removeUndefinedProperties } from '../utils.js';
import { BaseRunner } from './base.js';
import {
betaServerToolUseBlockToPart,
toBetaDocumentBlock,
unsupportedServerToolError,
} from './converters/beta.js';
import {
citationsDeltaToPart,
inputJsonDeltaError,
redactedThinkingBlockToPart,
textBlockToPart,
Expand Down Expand Up @@ -182,6 +188,13 @@ export class BetaRunner extends BaseRunner<BetaRunnerTypes> {
return { type: 'text', text: part.text };
}

// Custom document (for citations support)
if (part.custom?.anthropicDocument) {
return toBetaDocumentBlock(
part.custom.anthropicDocument as AnthropicDocumentOptions
);
}

// Media
if (part.media) {
if (part.media.contentType === 'anthropic/file') {
Expand Down Expand Up @@ -463,6 +476,9 @@ export class BetaRunner extends BaseRunner<BetaRunnerTypes> {
if (event.delta.type === 'thinking_delta') {
return thinkingDeltaToPart(event.delta);
}
if (event.delta.type === 'citations_delta') {
return citationsDeltaToPart(event.delta);
}
if (event.delta.type === 'input_json_delta') {
throw inputJsonDeltaError();
}
Expand Down
Loading