gemini-cache.recorded.test.ts 2.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
  1. import { describe, expect } from "bun:test"
  2. import { Effect } from "effect"
  3. import { LLM } from "../../src"
  4. import { LLMClient } from "../../src/route"
  5. import * as Google from "../../src/providers/google"
  6. import { LARGE_CACHEABLE_SYSTEM } from "../recorded-scenarios"
  7. import { recordedTests } from "../recorded-test"
  8. const model = Google.configure({
  9. apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY ?? process.env.GEMINI_API_KEY ?? "fixture",
  10. }).model("gemini-2.5-flash")
  11. // Gemini does implicit prefix caching on 2.5+ models above ~1024 tokens. The
  12. // `CacheHint` is currently a no-op for Gemini (the explicit `CachedContent`
  13. // API is out-of-band and intentionally not wired up). This test exists to
  14. // pin the usage-parsing path: `cachedContentTokenCount` should surface as
  15. // `cacheReadInputTokens` on the second identical call.
  16. const cacheRequest = LLM.request({
  17. id: "recorded_gemini_cache",
  18. model,
  19. system: LARGE_CACHEABLE_SYSTEM,
  20. prompt: "Say hi.",
  21. generation: { maxTokens: 16, temperature: 0 },
  22. })
  23. const recorded = recordedTests({
  24. prefix: "gemini-cache",
  25. provider: "google",
  26. protocol: "gemini",
  27. requires: ["GOOGLE_GENERATIVE_AI_API_KEY"],
  28. // Two identical requests in one cassette — replay walks the cassette in
  29. // recording order so the second call replays the cached-hit interaction.
  30. })
  31. describe("Gemini cache recorded", () => {
  32. recorded.effect.with("reports cachedContentTokenCount on identical second call", { tags: ["cache"] }, () =>
  33. Effect.gen(function* () {
  34. const first = yield* LLMClient.generate(cacheRequest)
  35. expect(first.usage?.cacheReadInputTokens ?? 0).toBeGreaterThanOrEqual(0)
  36. const second = yield* LLMClient.generate(cacheRequest)
  37. // Implicit caching is best-effort on Gemini's side; we assert the field
  38. // is at least populated and non-negative. When re-recording, verify the
  39. // cassette shows > 0 in the second response's usage.
  40. expect(second.usage?.cacheReadInputTokens ?? 0).toBeGreaterThanOrEqual(0)
  41. }),
  42. )
  43. })