fix(provider): improve prompt caching
This commit is contained in:
parent
0abbcddac2
commit
439a5a5281
19 changed files with 857 additions and 58 deletions
91
packages/core/test/provider-cache-key.test.ts
Normal file
91
packages/core/test/provider-cache-key.test.ts
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import { createCerebras } from "@ai-sdk/cerebras"
|
||||
import { createDeepInfra } from "@ai-sdk/deepinfra"
|
||||
import { createMistral } from "@ai-sdk/mistral"
|
||||
import { createOpenAI } from "@ai-sdk/openai"
|
||||
import { describe, expect, test } from "bun:test"
|
||||
|
||||
test("Mistral sends promptCacheKey as prompt_cache_key", async () => {
|
||||
let body: Record<string, unknown> | undefined
|
||||
const mockFetch = Object.assign(
|
||||
async (_input: Parameters<typeof fetch>[0], init?: RequestInit) => {
|
||||
body = JSON.parse(String(init?.body))
|
||||
return Response.json({
|
||||
id: "response-1",
|
||||
created: 0,
|
||||
model: "mistral-large-latest",
|
||||
object: "chat.completion",
|
||||
choices: [{ index: 0, message: { role: "assistant", content: "Hello" }, finish_reason: "stop" }],
|
||||
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
|
||||
})
|
||||
},
|
||||
{ preconnect: fetch.preconnect },
|
||||
)
|
||||
const model = createMistral({ apiKey: "test", fetch: mockFetch })("mistral-large-latest")
|
||||
|
||||
await model.doGenerate({
|
||||
prompt: [{ role: "user", content: [{ type: "text", text: "Hello" }] }],
|
||||
providerOptions: { mistral: { promptCacheKey: "session-123" } },
|
||||
})
|
||||
|
||||
expect(body?.prompt_cache_key).toBe("session-123")
|
||||
})
|
||||
|
||||
test("OpenAI Responses sends promptCacheKey as prompt_cache_key", async () => {
|
||||
let body: Record<string, unknown> | undefined
|
||||
const mockFetch = Object.assign(
|
||||
async (_input: Parameters<typeof fetch>[0], init?: RequestInit) => {
|
||||
body = JSON.parse(String(init?.body))
|
||||
return Response.json({
|
||||
id: "response-1",
|
||||
created_at: 0,
|
||||
model: "gpt-5",
|
||||
object: "response",
|
||||
output: [],
|
||||
usage: { input_tokens: 1, output_tokens: 0 },
|
||||
status: "completed",
|
||||
})
|
||||
},
|
||||
{ preconnect: fetch.preconnect },
|
||||
)
|
||||
const model = createOpenAI({ apiKey: "test", fetch: mockFetch }).responses("gpt-5")
|
||||
|
||||
await model.doGenerate({
|
||||
prompt: [{ role: "user", content: [{ type: "text", text: "Hello" }] }],
|
||||
providerOptions: { openai: { promptCacheKey: "session-123" } },
|
||||
})
|
||||
|
||||
expect(body?.prompt_cache_key).toBe("session-123")
|
||||
})
|
||||
|
||||
describe("OpenAI-compatible provider cache keys", () => {
|
||||
for (const provider of [
|
||||
{ name: "Cerebras", create: createCerebras, namespace: "cerebras" },
|
||||
{ name: "DeepInfra", create: createDeepInfra, namespace: "deepinfra" },
|
||||
]) {
|
||||
test(`${provider.name} passes prompt_cache_key through`, async () => {
|
||||
let body: Record<string, unknown> | undefined
|
||||
const mockFetch = Object.assign(
|
||||
async (_input: Parameters<typeof fetch>[0], init?: RequestInit) => {
|
||||
body = JSON.parse(String(init?.body))
|
||||
return Response.json({
|
||||
id: "response-1",
|
||||
created: 0,
|
||||
model: "test-model",
|
||||
object: "chat.completion",
|
||||
choices: [{ index: 0, message: { role: "assistant", content: "Hello" }, finish_reason: "stop" }],
|
||||
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
|
||||
})
|
||||
},
|
||||
{ preconnect: fetch.preconnect },
|
||||
)
|
||||
const model = provider.create({ apiKey: "test", fetch: mockFetch })("test-model")
|
||||
|
||||
await model.doGenerate({
|
||||
prompt: [{ role: "user", content: [{ type: "text", text: "Hello" }] }],
|
||||
providerOptions: { [provider.namespace]: { prompt_cache_key: "session-123" } },
|
||||
})
|
||||
|
||||
expect(body?.prompt_cache_key).toBe("session-123")
|
||||
})
|
||||
}
|
||||
})
|
||||
69
packages/core/test/provider-cache-usage.test.ts
Normal file
69
packages/core/test/provider-cache-usage.test.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import { createCohere } from "@ai-sdk/cohere"
|
||||
import { createGroq } from "@ai-sdk/groq"
|
||||
import { createTogetherAI } from "@ai-sdk/togetherai"
|
||||
import { describe, expect, test } from "bun:test"
|
||||
|
||||
const prompt = [{ role: "user" as const, content: [{ type: "text" as const, text: "Hello" }] }]
|
||||
|
||||
describe("provider cache usage", () => {
|
||||
test("Cohere reports cached input tokens", async () => {
|
||||
const model = createCohere({
|
||||
apiKey: "test",
|
||||
fetch: mockFetch({
|
||||
generation_id: "response-1",
|
||||
message: { role: "assistant", content: [{ type: "text", text: "Hello" }] },
|
||||
finish_reason: "COMPLETE",
|
||||
usage: {
|
||||
billed_units: { input_tokens: 500, output_tokens: 1 },
|
||||
tokens: { input_tokens: 500, output_tokens: 1, cached_tokens: 400 },
|
||||
},
|
||||
}),
|
||||
})("command-r")
|
||||
|
||||
const result = await model.doGenerate({ prompt })
|
||||
expect(result.usage.inputTokens).toEqual({ total: 500, noCache: 100, cacheRead: 400, cacheWrite: undefined })
|
||||
})
|
||||
|
||||
test("Groq reports cached input tokens", async () => {
|
||||
const model = createGroq({
|
||||
apiKey: "test",
|
||||
fetch: mockFetch({
|
||||
id: "response-1",
|
||||
created: 0,
|
||||
model: "openai/gpt-oss-20b",
|
||||
object: "chat.completion",
|
||||
choices: [{ index: 0, message: { role: "assistant", content: "Hello" }, finish_reason: "stop" }],
|
||||
usage: {
|
||||
prompt_tokens: 500,
|
||||
completion_tokens: 1,
|
||||
total_tokens: 501,
|
||||
prompt_tokens_details: { cached_tokens: 400 },
|
||||
},
|
||||
}),
|
||||
})("openai/gpt-oss-20b")
|
||||
|
||||
const result = await model.doGenerate({ prompt })
|
||||
expect(result.usage.inputTokens).toEqual({ total: 500, noCache: 100, cacheRead: 400, cacheWrite: undefined })
|
||||
})
|
||||
|
||||
test("Together AI reports flat cached input tokens", async () => {
|
||||
const model = createTogetherAI({
|
||||
apiKey: "test",
|
||||
fetch: mockFetch({
|
||||
id: "response-1",
|
||||
created: 0,
|
||||
model: "moonshotai/Kimi-K2.6",
|
||||
object: "chat.completion",
|
||||
choices: [{ index: 0, message: { role: "assistant", content: "Hello" }, finish_reason: "stop" }],
|
||||
usage: { prompt_tokens: 500, completion_tokens: 1, total_tokens: 501, cached_tokens: 400 },
|
||||
}),
|
||||
})("moonshotai/Kimi-K2.6")
|
||||
|
||||
const result = await model.doGenerate({ prompt })
|
||||
expect(result.usage.inputTokens).toEqual({ total: 500, noCache: 100, cacheRead: 400, cacheWrite: undefined })
|
||||
})
|
||||
})
|
||||
|
||||
function mockFetch(response: unknown) {
|
||||
return Object.assign(async () => Response.json(response), { preconnect: fetch.preconnect })
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue