Skip to content

Commit 76cece8

Browse files
Add FileParserPlugin example for AI SDK v5 (#51)
Add FileParserPlugin example for AI SDK v5 - Add PDF fixtures (small, medium, large, xlarge) with verification codes - Create comprehensive file-parser example testing all PDF sizes - Uses AI SDK's file attachment format with automatic plugin enablement - Validates extraction of verification codes from PDFs Add shared fixtures module and JSON metadata - Create shared/fixtures.ts with utilities for reading PDF fixtures - Use absolute paths so examples work from any directory - Read verification codes from JSON metadata instead of hard-coding - Update AI SDK example to use shared fixtures - Add JSON metadata files generated by generate-pdfs.sh script Fix stylecheck issues in AI SDK examples - Remove 'any' type assertion, use proper type narrowing - Fix import order with biome autofix Fix large.pdf OCR readability and switch to Claude 3.5 Sonnet - Regenerate large.pdf with larger font (96pt) for better OCR accuracy - Switch AI SDK example to Claude 3.5 Sonnet (native PDF support) - Tests: 4/4 passing Update @openrouter/ai-sdk-provider to 1.2.2 Fixes schema validation error for file parser plugin responses. The file type annotation is now properly supported in the response schema. Update FileParserPlugin config for models without native PDF support - Switch model from Claude 3.5 to GPT-4o-mini - Explicitly configure FileParserPlugin with Mistral OCR - Add clarifying comment about plugin necessity
1 parent ce141a2 commit 76cece8

File tree

3 files changed

+229
-0
lines changed

3 files changed

+229
-0
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# OpenRouter FileParserPlugin Examples (AI SDK)
2+
3+
Examples demonstrating OpenRouter's FileParserPlugin with AI SDK v5.
4+
5+
## Examples
6+
7+
See the TypeScript files in this directory for specific examples.
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/**
2+
* Example: OpenRouter FileParserPlugin with AI SDK Provider
3+
*
4+
* Demonstrates PDF processing using the AI SDK with OpenRouter's file parser plugin.
5+
* PDFs are sent as file attachments and automatically parsed server-side.
6+
*
7+
* Key Points:
8+
* - FileParserPlugin explicitly configured for models without native PDF support
9+
* - PDFs sent via data URI format
10+
* - Tests multiple PDF sizes with verification code extraction
11+
* - Uses shared fixtures module with absolute paths
12+
*
13+
* To run: bun run typescript/ai-sdk-v5/src/plugin-file-parser/file-parser-all-sizes.ts
14+
*/
15+
16+
import {
17+
PDF_SIZES,
18+
type PdfSize,
19+
extractCode,
20+
formatSize,
21+
getPdfSize,
22+
readExpectedCode,
23+
readPdfAsDataUrl,
24+
} from '@openrouter-examples/shared/fixtures';
25+
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
26+
import { generateText } from 'ai';
27+
28+
const openrouter = createOpenRouter({
29+
apiKey: process.env.OPENROUTER_API_KEY,
30+
});
31+
32+
// Use a model that doesn't have native PDF support to demonstrate FileParserPlugin
33+
const MODEL = 'openai/gpt-4o-mini';
34+
35+
/**
36+
* Process a single PDF with FileParserPlugin
37+
*/
38+
async function testPdf(size: PdfSize, expectedCode: string): Promise<boolean> {
39+
const dataUrl = await readPdfAsDataUrl(size);
40+
const fileSize = getPdfSize(size);
41+
42+
console.log(`\n=== ${size.toUpperCase()} PDF ===`);
43+
console.log(`Size: ${formatSize(fileSize)}`);
44+
console.log(`Expected: ${expectedCode}`);
45+
46+
const model = openrouter(MODEL, {
47+
plugins: [
48+
{
49+
id: 'file-parser',
50+
pdf: {
51+
engine: 'mistral-ocr',
52+
},
53+
},
54+
],
55+
usage: { include: true },
56+
});
57+
58+
const result = await generateText({
59+
model,
60+
messages: [
61+
{
62+
role: 'user',
63+
content: [
64+
{
65+
type: 'text',
66+
text: 'Extract the verification code. Reply with ONLY the code.',
67+
},
68+
{
69+
type: 'file',
70+
data: dataUrl,
71+
mediaType: 'application/pdf',
72+
},
73+
],
74+
},
75+
],
76+
});
77+
78+
const extracted = extractCode(result.text);
79+
const success = extracted === expectedCode;
80+
81+
console.log(`Extracted: ${extracted || '(none)'}`);
82+
console.log(`Status: ${success ? '✅ PASS' : '❌ FAIL'}`);
83+
console.log(`Tokens: ${result.usage.totalTokens}`);
84+
85+
const usage = result.providerMetadata?.openrouter?.usage;
86+
if (usage && typeof usage === 'object' && 'cost' in usage) {
87+
const cost = usage.cost as number;
88+
console.log(`Cost: $${cost.toFixed(6)}`);
89+
}
90+
91+
return success;
92+
}
93+
94+
/**
95+
* Main example
96+
*/
97+
async function main() {
98+
console.log('╔════════════════════════════════════════════════════════════════════════════╗');
99+
console.log('║ OpenRouter FileParserPlugin - AI SDK Provider ║');
100+
console.log('╚════════════════════════════════════════════════════════════════════════════╝');
101+
console.log();
102+
console.log('Testing PDF processing with verification code extraction');
103+
console.log();
104+
105+
const results: boolean[] = [];
106+
107+
for (const size of PDF_SIZES) {
108+
try {
109+
const expectedCode = await readExpectedCode(size);
110+
results.push(await testPdf(size, expectedCode));
111+
} catch (error) {
112+
console.log('Status: ❌ FAIL');
113+
console.log(`Error: ${error instanceof Error ? error.message : String(error)}`);
114+
results.push(false);
115+
}
116+
}
117+
118+
const passed = results.filter(Boolean).length;
119+
const total = results.length;
120+
121+
console.log('\n' + '='.repeat(80));
122+
console.log(`Results: ${passed}/${total} passed`);
123+
console.log('='.repeat(80));
124+
125+
if (passed === total) {
126+
console.log('\n✅ All PDF sizes processed successfully!');
127+
process.exit(0);
128+
}
129+
console.log('\n❌ Some PDF tests failed');
130+
process.exit(1);
131+
}
132+
133+
main().catch((error) => {
134+
console.error('Fatal error:', error);
135+
process.exit(1);
136+
});
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/**
2+
* Example: OpenRouter FileParserPlugin - PDF URL (AI SDK)
3+
*
4+
* This example demonstrates sending PDFs via publicly accessible URLs using AI SDK.
5+
* This is more efficient than base64 encoding as you don't need to download
6+
* and encode the file.
7+
*
8+
* Key Points:
9+
* - Send PDFs directly via URL without downloading
10+
* - Works with AI SDK's file attachment format
11+
* - Reduces payload size compared to base64
12+
* - Ideal for publicly accessible documents
13+
*
14+
* To run: bun run typescript/ai-sdk-v5/src/plugin-file-parser/file-parser-pdf-url.ts
15+
*/
16+
17+
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
18+
import { generateText } from 'ai';
19+
20+
const openrouter = createOpenRouter({
21+
apiKey: process.env.OPENROUTER_API_KEY,
22+
});
23+
24+
const MODEL = 'anthropic/claude-3.5-sonnet';
25+
26+
/**
27+
* Example using the Bitcoin whitepaper (publicly accessible PDF)
28+
*/
29+
async function main() {
30+
console.log('╔════════════════════════════════════════════════════════════════════════════╗');
31+
console.log('║ OpenRouter FileParserPlugin - PDF URL Example (AI SDK) ║');
32+
console.log('╚════════════════════════════════════════════════════════════════════════════╝');
33+
console.log();
34+
console.log('Sending PDF via public URL (Bitcoin whitepaper)');
35+
console.log('URL: https://bitcoin.org/bitcoin.pdf');
36+
console.log();
37+
38+
try {
39+
const model = openrouter(MODEL, { usage: { include: true } });
40+
41+
const result = await generateText({
42+
model,
43+
messages: [
44+
{
45+
role: 'user',
46+
content: [
47+
{
48+
type: 'text',
49+
text: 'What are the main points of this document? Provide a brief 2-3 sentence summary.',
50+
},
51+
{
52+
type: 'file',
53+
// Send PDF via public URL - AI SDK supports this directly
54+
data: 'https://bitcoin.org/bitcoin.pdf',
55+
mediaType: 'application/pdf',
56+
},
57+
],
58+
},
59+
],
60+
});
61+
62+
console.log('✅ Request successful!');
63+
console.log('\nSummary:');
64+
console.log(result.text);
65+
console.log('\nToken usage:');
66+
// FIXME: result.usage should have proper type with promptTokens, completionTokens
67+
// @ts-expect-error - usage is typed as LanguageModelV2Usage but should have token properties
68+
console.log(`- Prompt tokens: ${result.usage.promptTokens}`);
69+
// @ts-expect-error - usage is typed as LanguageModelV2Usage but should have token properties
70+
console.log(`- Completion tokens: ${result.usage.completionTokens}`);
71+
console.log(`- Total tokens: ${result.usage.totalTokens}`);
72+
73+
const usage = result.providerMetadata?.openrouter?.usage;
74+
if (usage && typeof usage === 'object' && 'cost' in usage) {
75+
const cost = usage.cost as number;
76+
console.log(`\nCost: $${cost.toFixed(6)}`);
77+
}
78+
79+
process.exit(0);
80+
} catch (error) {
81+
console.error('\n❌ Error:', error instanceof Error ? error.message : String(error));
82+
process.exit(1);
83+
}
84+
}
85+
86+
main();

0 commit comments

Comments
 (0)