bedrock-converse-cache.recorded.test.ts 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
  1. import { describe, expect } from "bun:test"
  2. import { Effect } from "effect"
  3. import { CacheHint, LLM } from "../../src/index.js"
  4. import { LLMClient } from "../../src/route.js"
  5. import { AmazonBedrock } from "../../src/providers.js"
  6. import { LARGE_CACHEABLE_SYSTEM } from "../recorded-scenarios.js"
  7. import { recordedTests } from "../recorded-test.js"
  8. const RECORDING_REGION = process.env.BEDROCK_RECORDING_REGION ?? "us-east-1"
  9. // Use a Claude model on Bedrock — Nova has automatic prefix caching that
  10. // doesn't reliably surface `cacheRead`/`cacheWrite` in usage, so the second
  11. // call wouldn't deterministically prove cache mapping works. Override with
  12. // BEDROCK_CACHE_MODEL_ID if your account has access elsewhere.
  13. const model = AmazonBedrock.configure({
  14. apiKey: process.env.AWS_BEARER_TOKEN_BEDROCK ?? "fixture",
  15. region: RECORDING_REGION,
  16. }).model(process.env.BEDROCK_CACHE_MODEL_ID ?? "us.anthropic.claude-haiku-4-5-20251001-v1:0")
  17. const cacheRequest = LLM.request({
  18. id: "recorded_bedrock_cache",
  19. model,
  20. system: [{ type: "text", text: LARGE_CACHEABLE_SYSTEM, cache: new CacheHint({ type: "ephemeral" }) }],
  21. prompt: "Say hi.",
  22. // Manual hint on the system part is the only marker we want here — skip the
  23. // auto-policy's latest-user-message breakpoint so the cassette body matches.
  24. cache: "none",
  25. generation: { maxTokens: 16, temperature: 0 },
  26. })
  27. const recorded = recordedTests({
  28. prefix: "bedrock-converse-cache",
  29. provider: "amazon-bedrock",
  30. protocol: "bedrock-converse",
  31. requires: ["AWS_BEARER_TOKEN_BEDROCK"],
  32. // Two identical requests in one cassette — replay walks the cassette in
  33. // recording order so the second call replays the cached-hit interaction.
  34. })
  35. describe("Bedrock Converse cache recorded", () => {
  36. recorded.effect.with("writes then reads cachePoint on identical second call", { tags: ["cache"] }, () =>
  37. Effect.gen(function* () {
  38. const first = yield* LLMClient.generate(cacheRequest)
  39. expect(first.usage?.cacheWriteInputTokens ?? 0).toBeGreaterThan(0)
  40. expect(first.usage?.inputTokens).toBe(
  41. (first.usage?.nonCachedInputTokens ?? 0) +
  42. (first.usage?.cacheReadInputTokens ?? 0) +
  43. (first.usage?.cacheWriteInputTokens ?? 0),
  44. )
  45. const second = yield* LLMClient.generate(cacheRequest)
  46. expect(second.usage?.cacheReadInputTokens ?? 0).toBeGreaterThan(0)
  47. expect(second.usage?.inputTokens).toBe(
  48. (second.usage?.nonCachedInputTokens ?? 0) +
  49. (second.usage?.cacheReadInputTokens ?? 0) +
  50. (second.usage?.cacheWriteInputTokens ?? 0),
  51. )
  52. }),
  53. )
  54. })