Skip to content

Commit c0000c8

Browse files
horita-yuyagr2m
authored andcommitted
feat(provider/amazon-bedrock): Support citations in amazon-bedrock-provider (#8861)
## Summary I added "citations" option as bedrock part provider options We need this to analyze PDF using anthropic model through bedrock converse api. https://docs.claude.com/en/docs/build-with-claude/pdf-support#amazon-bedrock-pdf-support Reference: Official type definition - https://github.com/aws/aws-sdk-js-v3/blob/281c666796e62691a6af521d21a87ad788b54c68/clients/client-bedrock-runtime/src/commands/ConverseCommand.ts#L80 ## Manual Verification 1. I prepared pdf whose contents is only image, no text. 2. I called bedrock anthropic model to extract information from it with citations: true and citations: false 3. I checked if citations is enabled I could extract information, but not with citations disabled. ## Related Issues close #8511 --------- Co-authored-by: Gregor Martynus <[email protected]>
1 parent 2a5edd3 commit c0000c8

File tree

7 files changed

+297
-0
lines changed

7 files changed

+297
-0
lines changed

.changeset/silver-falcons-count.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@ai-sdk/amazon-bedrock': patch
3+
---
4+
5+
Support citations in amazon-bedrock-provider

content/providers/01-ai-sdk-providers/08-amazon-bedrock.mdx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,51 @@ if (result.providerMetadata?.bedrock.trace) {
272272

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

275+
### Citations
276+
277+
Amazon Bedrock supports citations for document-based inputs across compatible models. When enabled:
278+
279+
- Some models can read documents with visual understanding, not just extracting text
280+
- Models can cite specific parts of documents you provide, making it easier to trace information back to its source (Not Supported Yet)
281+
282+
```ts
283+
import { bedrock } from '@ai-sdk/amazon-bedrock';
284+
import { generateObject } from 'ai';
285+
import { z } from 'zod';
286+
import fs from 'fs';
287+
288+
const result = await generateObject({
289+
model: bedrock('apac.anthropic.claude-sonnet-4-20250514-v1:0'),
290+
schema: z.object({
291+
summary: z.string().describe('Summary of the PDF document'),
292+
keyPoints: z.array(z.string()).describe('Key points from the PDF'),
293+
}),
294+
messages: [
295+
{
296+
role: 'user',
297+
content: [
298+
{
299+
type: 'text',
300+
text: 'Summarize this PDF and provide key points.',
301+
},
302+
{
303+
type: 'file',
304+
data: fs.readFileSync('./document.pdf'),
305+
mediaType: 'application/pdf',
306+
providerOptions: {
307+
bedrock: {
308+
citations: { enabled: true },
309+
},
310+
},
311+
},
312+
],
313+
},
314+
],
315+
});
316+
317+
console.log('Response:', result.object);
318+
```
319+
275320
### Cache Points
276321

277322
<Note>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { bedrock, BedrockProviderOptions } from '@ai-sdk/amazon-bedrock';
2+
import { generateObject } from 'ai';
3+
import { z } from 'zod';
4+
import fs from 'fs';
5+
import 'dotenv/config';
6+
7+
async function main() {
8+
const result = await generateObject({
9+
model: bedrock('us.anthropic.claude-sonnet-4-20250514-v1:0'),
10+
schema: z.object({
11+
summary: z.string().describe('Summary of the PDF document'),
12+
keyPoints: z.array(z.string()).describe('Key points from the PDF'),
13+
}),
14+
messages: [
15+
{
16+
role: 'user',
17+
content: [
18+
{
19+
type: 'text',
20+
text: 'Summarize this PDF and provide key points.',
21+
},
22+
{
23+
type: 'file',
24+
data: fs.readFileSync('./data/ai.pdf'),
25+
mediaType: 'application/pdf',
26+
providerOptions: {
27+
bedrock: {
28+
citations: { enabled: true },
29+
},
30+
},
31+
},
32+
],
33+
},
34+
],
35+
});
36+
37+
console.log('Response:', JSON.stringify(result, null, 2));
38+
}
39+
40+
main().catch(console.error);

packages/amazon-bedrock/src/bedrock-api-types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ export interface BedrockDocumentBlock {
127127
source: {
128128
bytes: string;
129129
};
130+
citations?: {
131+
enabled: boolean;
132+
};
130133
};
131134
}
132135

packages/amazon-bedrock/src/bedrock-chat-options.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,29 @@ export type BedrockChatModelId =
6565
| 'us.meta.llama4-maverick-17b-instruct-v1:0'
6666
| (string & {});
6767

68+
/**
69+
* Bedrock file part provider options for document-specific features.
70+
* These options apply to individual file parts (documents).
71+
*/
72+
export const bedrockFilePartProviderOptions = z.object({
73+
/**
74+
* Citation configuration for this document.
75+
* When enabled, this document will generate citations in the response.
76+
*/
77+
citations: z
78+
.object({
79+
/**
80+
* Enable citations for this document
81+
*/
82+
enabled: z.boolean(),
83+
})
84+
.optional(),
85+
});
86+
87+
export type BedrockFilePartProviderOptions = z.infer<
88+
typeof bedrockFilePartProviderOptions
89+
>;
90+
6891
export const bedrockProviderOptions = z.object({
6992
/**
7093
* Additional inference parameters that the model supports,

packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.test.ts

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,167 @@ describe('tool messages', () => {
749749
});
750750
});
751751

752+
describe('citations', () => {
753+
it('should handle citations enabled for PDF', async () => {
754+
const pdfData = new Uint8Array([0, 1, 2, 3]);
755+
756+
const result = await convertToBedrockChatMessages([
757+
{
758+
role: 'user',
759+
content: [
760+
{
761+
type: 'file',
762+
data: Buffer.from(pdfData).toString('base64'),
763+
mediaType: 'application/pdf',
764+
providerOptions: {
765+
bedrock: {
766+
citations: {
767+
enabled: true,
768+
},
769+
},
770+
},
771+
},
772+
],
773+
},
774+
]);
775+
776+
expect(result.messages[0].content[0]).toEqual({
777+
document: {
778+
format: 'pdf',
779+
name: 'document-1',
780+
source: {
781+
bytes: 'AAECAw==',
782+
},
783+
citations: {
784+
enabled: true,
785+
},
786+
},
787+
});
788+
});
789+
790+
it('should handle citations disabled for PDF', async () => {
791+
const pdfData = new Uint8Array([0, 1, 2, 3]);
792+
793+
const result = await convertToBedrockChatMessages([
794+
{
795+
role: 'user',
796+
content: [
797+
{
798+
type: 'file',
799+
data: Buffer.from(pdfData).toString('base64'),
800+
mediaType: 'application/pdf',
801+
providerOptions: {
802+
bedrock: {
803+
citations: {
804+
enabled: false,
805+
},
806+
},
807+
},
808+
},
809+
],
810+
},
811+
]);
812+
813+
expect(result.messages[0].content[0]).toEqual({
814+
document: {
815+
format: 'pdf',
816+
name: 'document-1',
817+
source: {
818+
bytes: 'AAECAw==',
819+
},
820+
},
821+
});
822+
});
823+
824+
it('should handle no citations specified for PDF (default)', async () => {
825+
const pdfData = new Uint8Array([0, 1, 2, 3]);
826+
827+
const result = await convertToBedrockChatMessages([
828+
{
829+
role: 'user',
830+
content: [
831+
{
832+
type: 'file',
833+
data: Buffer.from(pdfData).toString('base64'),
834+
mediaType: 'application/pdf',
835+
},
836+
],
837+
},
838+
]);
839+
840+
expect(result.messages[0].content[0]).toEqual({
841+
document: {
842+
format: 'pdf',
843+
name: 'document-1',
844+
source: {
845+
bytes: 'AAECAw==',
846+
},
847+
},
848+
});
849+
});
850+
851+
it('should handle multiple PDFs with different citation settings', async () => {
852+
const pdfData1 = new Uint8Array([0, 1, 2, 3]);
853+
const pdfData2 = new Uint8Array([4, 5, 6, 7]);
854+
855+
const result = await convertToBedrockChatMessages([
856+
{
857+
role: 'user',
858+
content: [
859+
{
860+
type: 'file',
861+
data: Buffer.from(pdfData1).toString('base64'),
862+
mediaType: 'application/pdf',
863+
providerOptions: {
864+
bedrock: {
865+
citations: {
866+
enabled: true,
867+
},
868+
},
869+
},
870+
},
871+
{
872+
type: 'file',
873+
data: Buffer.from(pdfData2).toString('base64'),
874+
mediaType: 'application/pdf',
875+
providerOptions: {
876+
bedrock: {
877+
citations: {
878+
enabled: false,
879+
},
880+
},
881+
},
882+
},
883+
],
884+
},
885+
]);
886+
887+
expect(result.messages[0].content).toEqual([
888+
{
889+
document: {
890+
format: 'pdf',
891+
name: 'document-1',
892+
source: {
893+
bytes: 'AAECAw==',
894+
},
895+
citations: {
896+
enabled: true,
897+
},
898+
},
899+
},
900+
{
901+
document: {
902+
format: 'pdf',
903+
name: 'document-2',
904+
source: {
905+
bytes: 'BAUGBw==',
906+
},
907+
},
908+
},
909+
]);
910+
});
911+
});
912+
752913
describe('additional file format tests', () => {
753914
it('should throw an error for unsupported file mime type in user message content', async () => {
754915
await expect(

packages/amazon-bedrock/src/convert-to-bedrock-chat-messages.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,26 @@ import {
2121
BedrockUserMessage,
2222
} from './bedrock-api-types';
2323
import { bedrockReasoningMetadataSchema } from './bedrock-chat-language-model';
24+
import { bedrockFilePartProviderOptions } from './bedrock-chat-options';
2425

2526
function getCachePoint(
2627
providerMetadata: SharedV2ProviderMetadata | undefined,
2728
): BedrockCachePoint | undefined {
2829
return providerMetadata?.bedrock?.cachePoint as BedrockCachePoint | undefined;
2930
}
3031

32+
async function shouldEnableCitations(
33+
providerMetadata: SharedV2ProviderMetadata | undefined,
34+
): Promise<boolean> {
35+
const bedrockOptions = await parseProviderOptions({
36+
provider: 'bedrock',
37+
providerOptions: providerMetadata,
38+
schema: bedrockFilePartProviderOptions,
39+
});
40+
41+
return bedrockOptions?.citations?.enabled ?? false;
42+
}
43+
3144
export async function convertToBedrockChatMessages(
3245
prompt: LanguageModelV2Prompt,
3346
): Promise<{
@@ -108,11 +121,18 @@ export async function convertToBedrockChatMessages(
108121
});
109122
}
110123

124+
const enableCitations = await shouldEnableCitations(
125+
part.providerOptions,
126+
);
127+
111128
bedrockContent.push({
112129
document: {
113130
format: getBedrockDocumentFormat(part.mediaType),
114131
name: generateDocumentName(),
115132
source: { bytes: convertToBase64(part.data) },
133+
...(enableCitations && {
134+
citations: { enabled: true },
135+
}),
116136
},
117137
});
118138
}

0 commit comments

Comments
 (0)