diff --git a/.changeset/silver-falcons-count.md b/.changeset/silver-falcons-count.md new file mode 100644 index 000000000000..cfdbd09a8198 --- /dev/null +++ b/.changeset/silver-falcons-count.md @@ -0,0 +1,5 @@ +--- +'@ai-sdk/amazon-bedrock': patch +--- + +Support citations in amazon-bedrock-provider diff --git a/content/providers/01-ai-sdk-providers/08-amazon-bedrock.mdx b/content/providers/01-ai-sdk-providers/08-amazon-bedrock.mdx index 8fe252d2d175..f08ad2aff397 100644 --- a/content/providers/01-ai-sdk-providers/08-amazon-bedrock.mdx +++ b/content/providers/01-ai-sdk-providers/08-amazon-bedrock.mdx @@ -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) + +```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'), + 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 diff --git a/examples/ai-core/src/generate-object/amazon-bedrock-document-citations.ts b/examples/ai-core/src/generate-object/amazon-bedrock-document-citations.ts new file mode 100644 index 000000000000..520605cc0d7e --- /dev/null +++ b/examples/ai-core/src/generate-object/amazon-bedrock-document-citations.ts @@ -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 }, + }, + }, + }, + ], + }, + ], + }); + + console.log('Response:', JSON.stringify(result, null, 2)); +} + +main().catch(console.error); diff --git a/packages/amazon-bedrock/src/bedrock-api-types.ts b/packages/amazon-bedrock/src/bedrock-api-types.ts index cfe3965f86c6..6e75f32bfe98 100644 --- a/packages/amazon-bedrock/src/bedrock-api-types.ts +++ b/packages/amazon-bedrock/src/bedrock-api-types.ts @@ -127,6 +127,9 @@ export interface BedrockDocumentBlock { source: { bytes: string; }; + citations?: { + enabled: boolean; + }; }; } diff --git a/packages/amazon-bedrock/src/bedrock-chat-options.ts b/packages/amazon-bedrock/src/bedrock-chat-options.ts index 847d197af005..7b2532e68e12 100644 --- a/packages/amazon-bedrock/src/bedrock-chat-options.ts +++ b/packages/amazon-bedrock/src/bedrock-chat-options.ts @@ -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, diff --git a/packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.test.ts b/packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.test.ts index f44f77ea91cb..95f4a13c13be 100644 --- a/packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.test.ts +++ b/packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.test.ts @@ -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( diff --git a/packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.ts b/packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.ts index 51c4318e1549..3f945e62e8e8 100644 --- a/packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.ts +++ b/packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.ts @@ -21,6 +21,7 @@ import { BedrockUserMessage, } from './bedrock-api-types'; import { bedrockReasoningMetadataSchema } from './bedrock-chat-language-model'; +import { bedrockFilePartProviderOptions } from './bedrock-chat-options'; function getCachePoint( providerMetadata: SharedV2ProviderMetadata | undefined, @@ -28,6 +29,18 @@ function getCachePoint( return providerMetadata?.bedrock?.cachePoint as BedrockCachePoint | undefined; } +async function shouldEnableCitations( + providerMetadata: SharedV2ProviderMetadata | undefined, +): Promise { + const bedrockOptions = await parseProviderOptions({ + provider: 'bedrock', + providerOptions: providerMetadata, + schema: bedrockFilePartProviderOptions, + }); + + return bedrockOptions?.citations?.enabled ?? false; +} + export async function convertToBedrockChatMessages( prompt: LanguageModelV2Prompt, ): Promise<{ @@ -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 }, + }), }, }); }