Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/silver-falcons-count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ai-sdk/amazon-bedrock': patch
---

Support citations in amazon-bedrock-provider
45 changes: 45 additions & 0 deletions content/providers/01-ai-sdk-providers/08-amazon-bedrock.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,51 @@ if (result.providerMetadata?.bedrock.trace) {

See the [Amazon Bedrock Guardrails documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails.html) for more information.

### Citations

Amazon Bedrock supports citations for document-based inputs across compatible models. When enabled:

- Some models can read documents with visual understanding, not just extracting text
- Models can cite specific parts of documents you provide, making it easier to trace information back to its source (Not Supported Yet)
Copy link
Contributor Author

@horita-yuya horita-yuya Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accessing citation parameters in response is not supported yet. May be another PR.

visual understanding reference:
https://docs.claude.com/en/docs/build-with-claude/pdf-support#amazon-bedrock-pdf-support


```ts
import { bedrock } from '@ai-sdk/amazon-bedrock';
import { generateObject } from 'ai';
import { z } from 'zod';
import fs from 'fs';

const result = await generateObject({
model: bedrock('apac.anthropic.claude-sonnet-4-20250514-v1:0'),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

schema: z.object({
summary: z.string().describe('Summary of the PDF document'),
keyPoints: z.array(z.string()).describe('Key points from the PDF'),
}),
messages: [
{
role: 'user',
content: [
{
type: 'text',
text: 'Summarize this PDF and provide key points.',
},
{
type: 'file',
data: fs.readFileSync('./document.pdf'),
mediaType: 'application/pdf',
providerOptions: {
bedrock: {
citations: { enabled: true },
},
},
},
],
},
],
});

console.log('Response:', result.object);
```

### Cache Points

<Note>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { bedrock, BedrockProviderOptions } from '@ai-sdk/amazon-bedrock';
import { generateObject } from 'ai';
import { z } from 'zod';
import fs from 'fs';
import 'dotenv/config';

async function main() {
const result = await generateObject({
model: bedrock('us.anthropic.claude-sonnet-4-20250514-v1:0'),
schema: z.object({
summary: z.string().describe('Summary of the PDF document'),
keyPoints: z.array(z.string()).describe('Key points from the PDF'),
}),
messages: [
{
role: 'user',
content: [
{
type: 'text',
text: 'Summarize this PDF and provide key points.',
},
{
type: 'file',
data: fs.readFileSync('./data/ai.pdf'),
mediaType: 'application/pdf',
providerOptions: {
bedrock: {
citations: { enabled: true },
},
},
Comment on lines +26 to +30
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the example behaves the same to me when I remove the providerOptions. What effect should it have? Does it need a PDF with certain content for it to work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.
PDF documents whose content is mainly texts works without this option.
But contents like graphs or images, you need this option to analyze correctly.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have such a pdf that we could use for this example?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course. Try this one.

vercel-graph.pdf

citations: true

スクリーンショット 2025-09-26 9 13 32

citations: false

スクリーンショット 2025-09-26 9 13 48

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gr2m
Sorry for rushing you.
Could you try with this pdf?

},
],
},
],
});

console.log('Response:', JSON.stringify(result, null, 2));
}

main().catch(console.error);
3 changes: 3 additions & 0 deletions packages/amazon-bedrock/src/bedrock-api-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ export interface BedrockDocumentBlock {
source: {
bytes: string;
};
citations?: {
enabled: boolean;
};
};
}

Expand Down
23 changes: 23 additions & 0 deletions packages/amazon-bedrock/src/bedrock-chat-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,29 @@ export type BedrockChatModelId =
| 'us.meta.llama4-maverick-17b-instruct-v1:0'
| (string & {});

/**
* Bedrock file part provider options for document-specific features.
* These options apply to individual file parts (documents).
*/
export const bedrockFilePartProviderOptions = z.object({
/**
* Citation configuration for this document.
* When enabled, this document will generate citations in the response.
*/
citations: z
.object({
/**
* Enable citations for this document
*/
enabled: z.boolean(),
})
.optional(),
});

export type BedrockFilePartProviderOptions = z.infer<
typeof bedrockFilePartProviderOptions
>;

export const bedrockProviderOptions = z.object({
/**
* Additional inference parameters that the model supports,
Expand Down
161 changes: 161 additions & 0 deletions packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,167 @@ describe('tool messages', () => {
});
});

describe('citations', () => {
it('should handle citations enabled for PDF', async () => {
const pdfData = new Uint8Array([0, 1, 2, 3]);

const result = await convertToBedrockChatMessages([
{
role: 'user',
content: [
{
type: 'file',
data: Buffer.from(pdfData).toString('base64'),
mediaType: 'application/pdf',
providerOptions: {
bedrock: {
citations: {
enabled: true,
},
},
},
},
],
},
]);

expect(result.messages[0].content[0]).toEqual({
document: {
format: 'pdf',
name: 'document-1',
source: {
bytes: 'AAECAw==',
},
citations: {
enabled: true,
},
},
});
});

it('should handle citations disabled for PDF', async () => {
const pdfData = new Uint8Array([0, 1, 2, 3]);

const result = await convertToBedrockChatMessages([
{
role: 'user',
content: [
{
type: 'file',
data: Buffer.from(pdfData).toString('base64'),
mediaType: 'application/pdf',
providerOptions: {
bedrock: {
citations: {
enabled: false,
},
},
},
},
],
},
]);

expect(result.messages[0].content[0]).toEqual({
document: {
format: 'pdf',
name: 'document-1',
source: {
bytes: 'AAECAw==',
},
},
});
});

it('should handle no citations specified for PDF (default)', async () => {
const pdfData = new Uint8Array([0, 1, 2, 3]);

const result = await convertToBedrockChatMessages([
{
role: 'user',
content: [
{
type: 'file',
data: Buffer.from(pdfData).toString('base64'),
mediaType: 'application/pdf',
},
],
},
]);

expect(result.messages[0].content[0]).toEqual({
document: {
format: 'pdf',
name: 'document-1',
source: {
bytes: 'AAECAw==',
},
},
});
});

it('should handle multiple PDFs with different citation settings', async () => {
const pdfData1 = new Uint8Array([0, 1, 2, 3]);
const pdfData2 = new Uint8Array([4, 5, 6, 7]);

const result = await convertToBedrockChatMessages([
{
role: 'user',
content: [
{
type: 'file',
data: Buffer.from(pdfData1).toString('base64'),
mediaType: 'application/pdf',
providerOptions: {
bedrock: {
citations: {
enabled: true,
},
},
},
},
{
type: 'file',
data: Buffer.from(pdfData2).toString('base64'),
mediaType: 'application/pdf',
providerOptions: {
bedrock: {
citations: {
enabled: false,
},
},
},
},
],
},
]);

expect(result.messages[0].content).toEqual([
{
document: {
format: 'pdf',
name: 'document-1',
source: {
bytes: 'AAECAw==',
},
citations: {
enabled: true,
},
},
},
{
document: {
format: 'pdf',
name: 'document-2',
source: {
bytes: 'BAUGBw==',
},
},
},
]);
});
});

describe('additional file format tests', () => {
it('should throw an error for unsupported file mime type in user message content', async () => {
await expect(
Expand Down
20 changes: 20 additions & 0 deletions packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,26 @@ import {
BedrockUserMessage,
} from './bedrock-api-types';
import { bedrockReasoningMetadataSchema } from './bedrock-chat-language-model';
import { bedrockFilePartProviderOptions } from './bedrock-chat-options';

function getCachePoint(
providerMetadata: SharedV2ProviderMetadata | undefined,
): BedrockCachePoint | undefined {
return providerMetadata?.bedrock?.cachePoint as BedrockCachePoint | undefined;
}

async function shouldEnableCitations(
providerMetadata: SharedV2ProviderMetadata | undefined,
): Promise<boolean> {
const bedrockOptions = await parseProviderOptions({
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I followed anthropic's implementation.

provider: 'bedrock',
providerOptions: providerMetadata,
schema: bedrockFilePartProviderOptions,
});

return bedrockOptions?.citations?.enabled ?? false;
}

export async function convertToBedrockChatMessages(
prompt: LanguageModelV3Prompt,
): Promise<{
Expand Down Expand Up @@ -108,11 +121,18 @@ export async function convertToBedrockChatMessages(
});
}

const enableCitations = await shouldEnableCitations(
part.providerOptions,
);

bedrockContent.push({
document: {
format: getBedrockDocumentFormat(part.mediaType),
name: generateDocumentName(),
source: { bytes: convertToBase64(part.data) },
...(enableCitations && {
citations: { enabled: true },
}),
},
});
}
Expand Down
Loading