Skip to content

Commit 081b7ee

Browse files
Add prompt-caching examples for AI SDK v5 (#43)
Add prompt-caching examples for AI SDK v5 - Add typescript/ai-sdk-v5/src/prompt-caching/user-message-cache.ts - Demonstrates cache_control using providerOptions.openrouter.cacheControl - Shows critical configuration: extraBody.stream_options.include_usage - Evidence-based verification via providerMetadata.openrouter.usage Run biome format and fix lint issues Simplify AI SDK v5 prompt-caching README to link to main docs Rename prompt caching examples with anthropic prefix
1 parent bf31620 commit 081b7ee

File tree

10 files changed

+583
-1
lines changed

10 files changed

+583
-1
lines changed

docs/prompt-caching.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ That guide covers provider-specific behavior, pricing, configuration requirement
1111
See ecosystem-specific examples in this repository for runnable reference implementations:
1212

1313
- **TypeScript + fetch**: [typescript/fetch/src/prompt-caching/](../typescript/fetch/src/prompt-caching/)
14+
- **AI SDK v5** (Vercel): [typescript/ai-sdk-v5/src/prompt-caching/](../typescript/ai-sdk-v5/src/prompt-caching/)

typescript/ai-sdk-v5/README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# AI SDK v5 Examples
2+
3+
Examples using Vercel AI SDK v5 with @openrouter/ai-sdk-provider.
4+
5+
## Prerequisites
6+
7+
- Bun runtime: `curl -fsSL https://bun.sh/install | bash`
8+
- `OPENROUTER_API_KEY` environment variable
9+
10+
## Running Examples
11+
12+
```bash
13+
# From monorepo root (typescript/)
14+
bun examples
15+
16+
# Or from this workspace
17+
cd ai-sdk-v5
18+
bun examples
19+
```
20+
21+
## Features
22+
23+
- [prompt-caching](./src/prompt-caching/) - Anthropic caching examples with AI SDK v5
24+
25+
### Key Configuration
26+
27+
**CRITICAL**: The AI SDK example requires:
28+
```typescript
29+
extraBody: {
30+
stream_options: { include_usage: true }
31+
}
32+
```
33+
34+
Without this, usage details (including cached_tokens) are not populated in the response.
35+
36+
## Dependencies
37+
38+
- `@openrouter-examples/shared` - Shared constants (LARGE_SYSTEM_PROMPT) and types
39+
- `@openrouter/ai-sdk-provider` - OpenRouter provider for AI SDK
40+
- `ai` v5.x - Vercel AI SDK

typescript/ai-sdk-v5/package.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "@openrouter-examples/ai-sdk-v5",
3+
"version": "1.0.0",
4+
"private": true,
5+
"type": "module",
6+
"scripts": {
7+
"examples": "bun run run-examples.ts",
8+
"typecheck": "tsc --noEmit"
9+
},
10+
"dependencies": {
11+
"@openrouter-examples/shared": "workspace:*",
12+
"@openrouter/ai-sdk-provider": "1.2.2",
13+
"ai": "^5.0.92"
14+
},
15+
"devDependencies": {
16+
"@types/bun": "latest"
17+
}
18+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#!/usr/bin/env bun
2+
/**
3+
* Run all example files in the src/ directory
4+
* Each example is run in a separate process to handle process.exit() calls
5+
*/
6+
7+
import { readdirSync, statSync } from 'fs';
8+
import { join } from 'path';
9+
import { $ } from 'bun';
10+
11+
const srcDir = join(import.meta.dir, 'src');
12+
13+
// Recursively find all .ts files in src/
14+
function findExamples(dir: string): string[] {
15+
const entries = readdirSync(dir);
16+
const files: string[] = [];
17+
18+
for (const entry of entries) {
19+
const fullPath = join(dir, entry);
20+
const stat = statSync(fullPath);
21+
22+
if (stat.isDirectory()) {
23+
files.push(...findExamples(fullPath));
24+
} else if (entry.endsWith('.ts')) {
25+
files.push(fullPath);
26+
}
27+
}
28+
29+
return files.sort();
30+
}
31+
32+
const examples = findExamples(srcDir);
33+
console.log(`Found ${examples.length} example(s)\n`);
34+
35+
let failed = 0;
36+
for (const example of examples) {
37+
const relativePath = example.replace(import.meta.dir + '/', '');
38+
console.log(`\n${'='.repeat(80)}`);
39+
console.log(`Running: ${relativePath}`);
40+
console.log('='.repeat(80));
41+
42+
try {
43+
await $`bun run ${example}`.quiet();
44+
console.log(`✅ ${relativePath} completed successfully`);
45+
} catch (error) {
46+
console.error(`❌ ${relativePath} failed`);
47+
failed++;
48+
}
49+
}
50+
51+
console.log(`\n${'='.repeat(80)}`);
52+
console.log(`Results: ${examples.length - failed}/${examples.length} passed`);
53+
console.log('='.repeat(80));
54+
55+
if (failed > 0) {
56+
process.exit(1);
57+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Prompt Caching Examples (AI SDK v5)
2+
3+
Examples demonstrating prompt caching with Vercel AI SDK v5.
4+
5+
## Documentation
6+
7+
For full prompt caching documentation including all providers, pricing, and configuration details, see:
8+
- **[OpenRouter Prompt Caching Guide](https://openrouter.ai/docs/features/prompt-caching)**
9+
10+
## Examples in This Directory
11+
12+
See the TypeScript files in this directory for specific examples with complete working code.
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/**
2+
* Example: Anthropic Prompt Caching - Multi-Message Conversation (AI SDK v5)
3+
*
4+
* This example demonstrates Anthropic prompt caching in a multi-message conversation
5+
* via OpenRouter using Vercel AI SDK v5.
6+
*
7+
* Pattern: User message cache in multi-turn conversation
8+
* - Cache large context in first user message
9+
* - Cache persists through conversation history
10+
*/
11+
12+
import { LARGE_SYSTEM_PROMPT } from '@openrouter-examples/shared/constants';
13+
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
14+
import { generateText } from 'ai';
15+
16+
const openrouter = createOpenRouter({
17+
apiKey: process.env.OPENROUTER_API_KEY,
18+
extraBody: {
19+
stream_options: { include_usage: true },
20+
},
21+
});
22+
23+
async function main() {
24+
console.log('╔════════════════════════════════════════════════════════════════════════════╗');
25+
console.log('║ Anthropic Prompt Caching - Multi-Message (AI SDK v5) ║');
26+
console.log('╚════════════════════════════════════════════════════════════════════════════╝');
27+
console.log();
28+
console.log('Testing cache_control in multi-turn conversation');
29+
console.log();
30+
31+
try {
32+
const testId = Date.now();
33+
const model = openrouter('anthropic/claude-3-5-sonnet');
34+
const largeContext = `Test ${testId}: Context:\n\n${LARGE_SYSTEM_PROMPT}`;
35+
36+
// First call with conversation history
37+
console.log('First Call (Cache Miss Expected)');
38+
const result1 = await generateText({
39+
model,
40+
messages: [
41+
{
42+
role: 'user',
43+
content: [
44+
{
45+
type: 'text',
46+
text: largeContext,
47+
providerOptions: {
48+
openrouter: {
49+
cacheControl: { type: 'ephemeral' },
50+
},
51+
},
52+
},
53+
{
54+
type: 'text',
55+
text: "Hello, what's your purpose?",
56+
},
57+
],
58+
},
59+
{
60+
role: 'assistant',
61+
content: "I'm an AI assistant designed to help with various tasks.",
62+
},
63+
{
64+
role: 'user',
65+
content: 'What programming languages do you know?',
66+
},
67+
],
68+
});
69+
70+
// FIXME: providerMetadata.openrouter.usage should have proper type with promptTokensDetails
71+
const cached1 =
72+
// @ts-expect-error - usage is typed as JSONValue but should be OpenRouterUsage
73+
result1.providerMetadata?.openrouter?.usage?.promptTokensDetails?.cachedTokens ?? 0;
74+
console.log(` Response: ${result1.text.substring(0, 80)}...`);
75+
console.log(` cached_tokens=${cached1}`);
76+
77+
await new Promise((resolve) => setTimeout(resolve, 1000));
78+
79+
// Second identical call - should hit cache
80+
console.log('\nSecond Call (Cache Hit Expected)');
81+
const result2 = await generateText({
82+
model,
83+
messages: [
84+
{
85+
role: 'user',
86+
content: [
87+
{
88+
type: 'text',
89+
text: largeContext,
90+
providerOptions: {
91+
openrouter: {
92+
cacheControl: { type: 'ephemeral' },
93+
},
94+
},
95+
},
96+
{
97+
type: 'text',
98+
text: "Hello, what's your purpose?",
99+
},
100+
],
101+
},
102+
{
103+
role: 'assistant',
104+
content: "I'm an AI assistant designed to help with various tasks.",
105+
},
106+
{
107+
role: 'user',
108+
content: 'What programming languages do you know?',
109+
},
110+
],
111+
});
112+
113+
// FIXME: providerMetadata.openrouter.usage should have proper type with promptTokensDetails
114+
const cached2 =
115+
// @ts-expect-error - usage is typed as JSONValue but should be OpenRouterUsage
116+
result2.providerMetadata?.openrouter?.usage?.promptTokensDetails?.cachedTokens ?? 0;
117+
console.log(` Response: ${result2.text.substring(0, 80)}...`);
118+
console.log(` cached_tokens=${cached2}`);
119+
120+
// Analysis
121+
console.log('\n' + '='.repeat(80));
122+
console.log('ANALYSIS');
123+
console.log('='.repeat(80));
124+
console.log(`First call: cached_tokens=${cached1} (expected: 0)`);
125+
console.log(`Second call: cached_tokens=${cached2} (expected: >0)`);
126+
127+
const success = cached1 === 0 && cached2 > 0;
128+
console.log(`\nResult: ${success ? '✓ CACHE WORKING' : '✗ CACHE NOT WORKING'}`);
129+
130+
if (success) {
131+
console.log('\n✓ SUCCESS - Multi-message caching is working correctly');
132+
} else {
133+
console.log('\n✗ FAILURE - Multi-message caching is not working as expected');
134+
}
135+
} catch (error) {
136+
console.error('\n❌ ERROR:', error);
137+
process.exit(1);
138+
}
139+
}
140+
141+
main();

0 commit comments

Comments
 (0)