anthropic-messages-cache.recorded.test.ts 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. import { describe, expect } from "bun:test"
  2. import { Effect } from "effect"
  3. import { CacheHint, LLM } from "../../src"
  4. import { LLMClient } from "../../src/route"
  5. import * as Anthropic from "../../src/providers/anthropic"
  6. import { LARGE_CACHEABLE_SYSTEM } from "../recorded-scenarios"
  7. import { recordedTests } from "../recorded-test"
  8. const model = Anthropic.configure({
  9. apiKey: process.env.ANTHROPIC_API_KEY ?? "fixture",
  10. }).model("claude-haiku-4-5-20251001")
  11. // Two identical generations in a row. The first call writes the prefix into
  12. // Anthropic's cache; the second should report a cache read against the same
  13. // prefix. Cassette captures both interactions in order.
  14. const cacheRequest = LLM.request({
  15. id: "recorded_anthropic_cache",
  16. model,
  17. system: [{ type: "text", text: LARGE_CACHEABLE_SYSTEM, cache: new CacheHint({ type: "ephemeral" }) }],
  18. prompt: "Say hi.",
  19. // Manual hint on the system part is the only marker we want here — skip the
  20. // auto-policy's latest-user-message breakpoint so the cassette body matches.
  21. cache: "none",
  22. generation: { maxTokens: 16, temperature: 0 },
  23. })
  24. const recorded = recordedTests({
  25. prefix: "anthropic-messages-cache",
  26. provider: "anthropic",
  27. protocol: "anthropic-messages",
  28. requires: ["ANTHROPIC_API_KEY"],
  29. // Two identical requests in one cassette — replay walks the cassette in
  30. // recording order so the second call replays the cached-hit interaction.
  31. options: {
  32. redact: { allowRequestHeaders: ["anthropic-version"] },
  33. },
  34. })
  35. describe("Anthropic Messages cache recorded", () => {
  36. recorded.effect.with("writes then reads cache_control on identical second call", { tags: ["cache"] }, () =>
  37. Effect.gen(function* () {
  38. const first = yield* LLMClient.generate(cacheRequest)
  39. // The first call may write the cache (cacheWriteInputTokens > 0) or it
  40. // may be a fresh miss (both fields 0) depending on whether the prefix is
  41. // already warm on Anthropic's side. The assertion that matters is that
  42. // the SECOND call reports a non-zero cache read.
  43. expect(first.usage?.cacheReadInputTokens ?? 0).toBeGreaterThanOrEqual(0)
  44. const second = yield* LLMClient.generate(cacheRequest)
  45. expect(second.usage?.cacheReadInputTokens ?? 0).toBeGreaterThan(0)
  46. }),
  47. )
  48. })