Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/anthropic-search-results-citations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@ai-sdk/anthropic": patch
---

feat(anthropic): search result blocks and custom content documents

This enables Claude to cite specific passages from search results and custom content documents. File parts can now use `type: 'search_result'` or `source: { type: 'content', content: [...] }` in provider options to provide citable content.

New citation types `search_result_location` and `content_block_location` are now properly parsed and returned as `source` parts with metadata including `citedText`, `source`, `searchResultIndex`, `startBlockIndex`, and `endBlockIndex`.
137 changes: 137 additions & 0 deletions content/providers/01-ai-sdk-providers/05-anthropic.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,143 @@ For more on prompt caching with Anthropic, see [Anthropic's Cache Control docume
[here](/docs/foundations/prompts#provider-options).
</Note>

### Search Results and Citations

Anthropic supports search result blocks and custom content documents that enable Claude to cite specific passages in its responses. You can provide these using file parts with the `providerOptions` property.

```ts highlight="12-18"
import { anthropic } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const result = await generateText({
model: anthropic('claude-sonnet-4-5-20250929'),
messages: [
{
role: 'user',
content: [
{
type: 'file',
mediaType: 'text/plain',
data: 'Search result snippet about quarterly revenue growth.',
providerOptions: {
anthropic: {
type: 'search_result',
source: 'https://example.com/search/result1',
citations: { enabled: true },
title: 'Quarterly Results',
},
},
},
{
type: 'text',
text: 'Summarize the search result and include citations.',
},
],
},
],
});
```

#### Configuration Options

The following options are available for search result blocks:

- **type** _'search_result'_

Required. Indicates this is a search result block.

- **source** _string_

Required. The URL or identifier for the search result.

- **citations** _object_

Set `{ enabled: true }` to enable citations. Anthropic's API requires this setting to be consistent across all search results in a request; inconsistent values will result in an API error.

- **title** _string_

A title for the search result. Prefer `title` over `filename`.

- **content** _array_

Custom content blocks. If not provided, the SDK derives a single text block from the file `data`.

#### Custom Content Documents

For pre-chunked text blocks that should be used as-is for citations without additional processing, use `source: { type: 'content', content: [...] }` with a `text/plain` file part:

```ts highlight="14-21"
import { anthropic } from '@ai-sdk/anthropic';
import { generateText } from 'ai';

const result = await generateText({
model: anthropic('claude-sonnet-4-5-20250929'),
messages: [
{
role: 'user',
content: [
{
type: 'file',
mediaType: 'text/plain',
data: 'Placeholder text for the file part.',
providerOptions: {
anthropic: {
source: {
type: 'content',
content: [
{ type: 'text', text: 'First custom chunk.' },
{ type: 'text', text: 'Second custom chunk.' },
],
},
citations: { enabled: true },
title: 'Custom Content Doc',
},
},
},
{
type: 'text',
text: 'Summarize the custom content and include citations.',
},
],
},
],
});
```

#### Citation Metadata

When citations are enabled, the response `content` array includes `source` parts with citation metadata:

```ts
const citations = result.content.filter(part => part.type === 'source');

for (const citation of citations) {
if (
citation.sourceType === 'document' &&
citation.providerMetadata?.anthropic
) {
const meta = citation.providerMetadata.anthropic;
console.log(`Cited: "${meta.citedText}"`);
console.log(`Title: ${citation.title}`);

// Search results include source URL and index
if ('searchResultIndex' in meta) {
console.log(`Source: ${meta.source}`);
console.log(`Search result index: ${meta.searchResultIndex}`);
}
}
}
```

The `providerMetadata.anthropic` object includes:

- **citedText** _string_ - The exact text that was cited
- **source** _string_ - The source URL (search results only)
- **searchResultIndex** _number_ - Index of the cited search result (search results only)
- **startBlockIndex** / **endBlockIndex** _number_ - Block range of the citation

For more details, see [Anthropic's Search Results documentation](https://docs.anthropic.com/en/docs/build-with-claude/search-results) and [Citations documentation](https://docs.anthropic.com/en/docs/build-with-claude/citations).

### Bash Tool

The Bash Tool allows running bash commands. Here's how to create and use it:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { anthropic } from '@ai-sdk/anthropic';
import { generateText } from 'ai';
import { run } from '../lib/run';

run(async () => {
const result = await generateText({
model: anthropic('claude-sonnet-4-5-20250929'),
messages: [
{
role: 'user',
content: [
{
type: 'file',
mediaType: 'text/plain',
data: '',
providerOptions: {
anthropic: {
source: {
type: 'content',
content: [
{ type: 'text' as const, text: 'First custom chunk.' },
{ type: 'text' as const, text: 'Second custom chunk.' },
],
},
citations: { enabled: true },
title: 'Custom Content Doc',
},
},
},
{
type: 'text',
text: 'Summarize the custom content and include citations.',
},
],
},
],
});

console.log('Response:', result.text);

const citations = result.content.filter(part => part.type === 'source');
citations.forEach((citation, i) => {
if (
citation.sourceType === 'document' &&
citation.providerMetadata?.anthropic
) {
const meta = citation.providerMetadata.anthropic;
console.log(
`\n[${i + 1}] "${meta.citedText}" (blocks ${meta.startBlockIndex}-${meta.endBlockIndex})`,
);
if (citation.title) {
console.log(`Title: ${citation.title}`);
}
}
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { anthropic } from '@ai-sdk/anthropic';
import { generateText } from 'ai';
import { run } from '../lib/run';

run(async () => {
const result = await generateText({
model: anthropic('claude-sonnet-4-5-20250929'),
messages: [
{
role: 'user',
content: [
{
type: 'file',
mediaType: 'text/plain',
data: 'Search result snippet about quarterly revenue growth.',
providerOptions: {
anthropic: {
type: 'search_result',
source: 'https://example.com/search/result1',
citations: { enabled: true },
title: 'Quarterly Results',
},
},
},
{
type: 'file',
mediaType: 'text/plain',
data: '',
providerOptions: {
anthropic: {
type: 'search_result',
source: 'https://example.com/search/result2',
content: [
{
type: 'text' as const,
text: 'Additional context from the same search result.',
},
],
citations: { enabled: true },
title: 'Additional Context',
},
},
},
{
type: 'text',
text: 'Summarize the search result and include citations.',
},
],
},
],
});

console.log('Response:', result.text);

const citations = result.content.filter(part => part.type === 'source');
citations.forEach((citation, i) => {
if (
citation.sourceType === 'document' &&
citation.providerMetadata?.anthropic
) {
const meta = citation.providerMetadata.anthropic;
console.log(
`\n[${i + 1}] "${meta.citedText}" (search_result ${meta.searchResultIndex}, blocks ${meta.startBlockIndex}-${meta.endBlockIndex})`,
);
console.log(`Source: ${meta.source}`);
if (citation.title) {
console.log(`Title: ${citation.title}`);
}
}
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { anthropic } from '@ai-sdk/anthropic';
import { streamText } from 'ai';
import { run } from '../lib/run';

run(async () => {
const result = streamText({
model: anthropic('claude-sonnet-4-5-20250929'),
messages: [
{
role: 'user',
content: [
{
type: 'file',
mediaType: 'text/plain',
data: '',
providerOptions: {
anthropic: {
source: {
type: 'content',
content: [
{ type: 'text' as const, text: 'First custom chunk.' },
{ type: 'text' as const, text: 'Second custom chunk.' },
],
},
citations: { enabled: true },
title: 'Custom Content Doc',
},
},
},
{
type: 'text',
text: 'Summarize the custom content and include citations.',
},
],
},
],
});

let citationCount = 0;

for await (const part of result.fullStream) {
switch (part.type) {
case 'text-delta':
process.stdout.write(part.text);
break;
case 'source':
if (
part.sourceType === 'document' &&
part.providerMetadata?.anthropic
) {
const meta = part.providerMetadata.anthropic;
console.log(
`\n\n[${++citationCount}] "${meta.citedText}" (blocks ${meta.startBlockIndex}-${meta.endBlockIndex})`,
);
if (part.title) {
console.log(`Title: ${part.title}`);
}
}
break;
case 'error':
console.error('Error:', part.error);
break;
}
}

console.log(`\n\nTotal citations: ${citationCount}`);
});
Loading