-
Notifications
You must be signed in to change notification settings - Fork 43
Add FileParserPlugin example for AI SDK v5 #51
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
subtleGradient
wants to merge
8
commits into
03-prompt-caching-aisdk
Choose a base branch
from
pdf-example-aisdk
base: 03-prompt-caching-aisdk
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+229
−0
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
421a179
Add FileParserPlugin example for AI SDK v5
subtleGradient 1654fc5
Add shared fixtures module and JSON metadata
subtleGradient 9d7879c
Fix stylecheck issues in AI SDK examples
subtleGradient c8ed7ab
Update FileParserPlugin config for models without native PDF support
subtleGradient 2120638
Fix type errors in file-parser-pdf-url.ts
subtleGradient 3f8c58f
Add file-parser-pdf-url.ts documentation to README
subtleGradient eaf512d
Remove incomplete plugin behavior from README
subtleGradient 34d735c
Remove filename references from docs to prevent sync issues
subtleGradient File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| # OpenRouter FileParserPlugin Examples (AI SDK) | ||
|
|
||
| Examples demonstrating OpenRouter's FileParserPlugin with AI SDK v5. | ||
|
|
||
| ## Examples | ||
|
|
||
| See the TypeScript files in this directory for specific examples. |
136 changes: 136 additions & 0 deletions
136
typescript/ai-sdk-v5/src/plugin-file-parser/file-parser-all-sizes.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| /** | ||
| * Example: OpenRouter FileParserPlugin with AI SDK Provider | ||
| * | ||
| * Demonstrates PDF processing using the AI SDK with OpenRouter's file parser plugin. | ||
| * PDFs are sent as file attachments and automatically parsed server-side. | ||
| * | ||
| * Key Points: | ||
| * - FileParserPlugin explicitly configured for models without native PDF support | ||
| * - PDFs sent via data URI format | ||
| * - Tests multiple PDF sizes with verification code extraction | ||
| * - Uses shared fixtures module with absolute paths | ||
| * | ||
| * To run: bun run typescript/ai-sdk-v5/src/plugin-file-parser/file-parser-all-sizes.ts | ||
| */ | ||
|
|
||
| import { | ||
| PDF_SIZES, | ||
| type PdfSize, | ||
| extractCode, | ||
| formatSize, | ||
| getPdfSize, | ||
| readExpectedCode, | ||
| readPdfAsDataUrl, | ||
| } from '@openrouter-examples/shared/fixtures'; | ||
| import { createOpenRouter } from '@openrouter/ai-sdk-provider'; | ||
| import { generateText } from 'ai'; | ||
|
|
||
| const openrouter = createOpenRouter({ | ||
| apiKey: process.env.OPENROUTER_API_KEY, | ||
| }); | ||
|
|
||
| // Use a model that doesn't have native PDF support to demonstrate FileParserPlugin | ||
| const MODEL = 'openai/gpt-4o-mini'; | ||
|
|
||
| /** | ||
| * Process a single PDF with FileParserPlugin | ||
| */ | ||
| async function testPdf(size: PdfSize, expectedCode: string): Promise<boolean> { | ||
| const dataUrl = await readPdfAsDataUrl(size); | ||
| const fileSize = getPdfSize(size); | ||
|
|
||
| console.log(`\n=== ${size.toUpperCase()} PDF ===`); | ||
| console.log(`Size: ${formatSize(fileSize)}`); | ||
| console.log(`Expected: ${expectedCode}`); | ||
|
|
||
| const model = openrouter(MODEL, { | ||
| plugins: [ | ||
| { | ||
| id: 'file-parser', | ||
| pdf: { | ||
| engine: 'mistral-ocr', | ||
| }, | ||
| }, | ||
| ], | ||
| usage: { include: true }, | ||
| }); | ||
|
|
||
| const result = await generateText({ | ||
| model, | ||
| messages: [ | ||
| { | ||
| role: 'user', | ||
| content: [ | ||
| { | ||
| type: 'text', | ||
| text: 'Extract the verification code. Reply with ONLY the code.', | ||
| }, | ||
| { | ||
| type: 'file', | ||
| data: dataUrl, | ||
| mediaType: 'application/pdf', | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| }); | ||
|
|
||
| const extracted = extractCode(result.text); | ||
| const success = extracted === expectedCode; | ||
|
|
||
| console.log(`Extracted: ${extracted || '(none)'}`); | ||
| console.log(`Status: ${success ? '✅ PASS' : '❌ FAIL'}`); | ||
| console.log(`Tokens: ${result.usage.totalTokens}`); | ||
|
|
||
| const usage = result.providerMetadata?.openrouter?.usage; | ||
| if (usage && typeof usage === 'object' && 'cost' in usage) { | ||
| const cost = usage.cost as number; | ||
| console.log(`Cost: $${cost.toFixed(6)}`); | ||
| } | ||
|
|
||
| return success; | ||
| } | ||
|
|
||
| /** | ||
| * Main example | ||
| */ | ||
| async function main() { | ||
| console.log('╔════════════════════════════════════════════════════════════════════════════╗'); | ||
| console.log('║ OpenRouter FileParserPlugin - AI SDK Provider ║'); | ||
| console.log('╚════════════════════════════════════════════════════════════════════════════╝'); | ||
| console.log(); | ||
| console.log('Testing PDF processing with verification code extraction'); | ||
| console.log(); | ||
|
|
||
| const results: boolean[] = []; | ||
|
|
||
| for (const size of PDF_SIZES) { | ||
| try { | ||
| const expectedCode = await readExpectedCode(size); | ||
| results.push(await testPdf(size, expectedCode)); | ||
| } catch (error) { | ||
| console.log('Status: ❌ FAIL'); | ||
| console.log(`Error: ${error instanceof Error ? error.message : String(error)}`); | ||
| results.push(false); | ||
| } | ||
| } | ||
|
|
||
| const passed = results.filter(Boolean).length; | ||
| const total = results.length; | ||
|
|
||
| console.log('\n' + '='.repeat(80)); | ||
| console.log(`Results: ${passed}/${total} passed`); | ||
| console.log('='.repeat(80)); | ||
|
|
||
| if (passed === total) { | ||
| console.log('\n✅ All PDF sizes processed successfully!'); | ||
| process.exit(0); | ||
| } | ||
| console.log('\n❌ Some PDF tests failed'); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| main().catch((error) => { | ||
| console.error('Fatal error:', error); | ||
| process.exit(1); | ||
| }); | ||
86 changes: 86 additions & 0 deletions
86
typescript/ai-sdk-v5/src/plugin-file-parser/file-parser-pdf-url.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| /** | ||
| * Example: OpenRouter FileParserPlugin - PDF URL (AI SDK) | ||
| * | ||
| * This example demonstrates sending PDFs via publicly accessible URLs using AI SDK. | ||
| * This is more efficient than base64 encoding as you don't need to download | ||
| * and encode the file. | ||
| * | ||
| * Key Points: | ||
| * - Send PDFs directly via URL without downloading | ||
| * - Works with AI SDK's file attachment format | ||
| * - Reduces payload size compared to base64 | ||
| * - Ideal for publicly accessible documents | ||
| * | ||
| * To run: bun run typescript/ai-sdk-v5/src/plugin-file-parser/file-parser-pdf-url.ts | ||
| */ | ||
|
|
||
| import { createOpenRouter } from '@openrouter/ai-sdk-provider'; | ||
| import { generateText } from 'ai'; | ||
|
|
||
| const openrouter = createOpenRouter({ | ||
| apiKey: process.env.OPENROUTER_API_KEY, | ||
| }); | ||
|
|
||
| const MODEL = 'anthropic/claude-3.5-sonnet'; | ||
|
|
||
| /** | ||
| * Example using the Bitcoin whitepaper (publicly accessible PDF) | ||
| */ | ||
| async function main() { | ||
| console.log('╔════════════════════════════════════════════════════════════════════════════╗'); | ||
| console.log('║ OpenRouter FileParserPlugin - PDF URL Example (AI SDK) ║'); | ||
| console.log('╚════════════════════════════════════════════════════════════════════════════╝'); | ||
| console.log(); | ||
| console.log('Sending PDF via public URL (Bitcoin whitepaper)'); | ||
| console.log('URL: https://bitcoin.org/bitcoin.pdf'); | ||
| console.log(); | ||
|
|
||
| try { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need try/catch here btw if the entry crash it will simply crash right? |
||
| const model = openrouter(MODEL, { usage: { include: true } }); | ||
|
|
||
| const result = await generateText({ | ||
| model, | ||
| messages: [ | ||
| { | ||
| role: 'user', | ||
| content: [ | ||
| { | ||
| type: 'text', | ||
| text: 'What are the main points of this document? Provide a brief 2-3 sentence summary.', | ||
| }, | ||
| { | ||
| type: 'file', | ||
| // Send PDF via public URL - AI SDK supports this directly | ||
| data: 'https://bitcoin.org/bitcoin.pdf', | ||
| mediaType: 'application/pdf', | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| }); | ||
|
|
||
| console.log('✅ Request successful!'); | ||
| console.log('\nSummary:'); | ||
| console.log(result.text); | ||
| console.log('\nToken usage:'); | ||
| // FIXME: result.usage should have proper type with promptTokens, completionTokens | ||
| // @ts-expect-error - usage is typed as LanguageModelV2Usage but should have token properties | ||
| console.log(`- Prompt tokens: ${result.usage.promptTokens}`); | ||
| // @ts-expect-error - usage is typed as LanguageModelV2Usage but should have token properties | ||
| console.log(`- Completion tokens: ${result.usage.completionTokens}`); | ||
| console.log(`- Total tokens: ${result.usage.totalTokens}`); | ||
|
|
||
| const usage = result.providerMetadata?.openrouter?.usage; | ||
| if (usage && typeof usage === 'object' && 'cost' in usage) { | ||
| const cost = usage.cost as number; | ||
| console.log(`\nCost: $${cost.toFixed(6)}`); | ||
| } | ||
|
|
||
| process.exit(0); | ||
| } catch (error) { | ||
| console.error('\n❌ Error:', error instanceof Error ? error.message : String(error)); | ||
| process.exit(1); | ||
| } | ||
| } | ||
|
|
||
| main(); | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it should assert the type
assert(typeof usage.cost === 'number')
Or something like that, I think