.agents/skills/llmobs-testing/references/vcr-cassettes.md
Using VCR for recording and replaying API calls in LLMObs tests.
VCR (Video Cassette Recorder) records real HTTP requests/responses and replays them in tests.
Benefits:
Configure client to use VCR proxy:
const client = new MyLLMClient({
apiKey: process.env.PROVIDER_API_KEY ?? 'test-api-key', // Real key needed for first recording; replays work without one
baseURL: 'http://127.0.0.1:9126/vcr/{provider-name}' // VCR proxy URL
})
URL Format: http://127.0.0.1:9126/vcr/{provider-name}
Examples:
http://127.0.0.1:9126/vcr/openaihttp://127.0.0.1:9126/vcr/anthropichttp://127.0.0.1:9126/vcr/google-genaiexport OPENAI_API_KEY="your-real-key"
export ANTHROPIC_API_KEY="your-real-key"
npm test packages/dd-trace/test/llmobs/plugins/my-provider/
Cassettes saved to: test/llmobs/plugins/{provider}/cassettes/
git add packages/dd-trace/test/llmobs/plugins/my-provider/cassettes/
git commit -m "Add VCR cassettes for my-provider"
packages/dd-trace/test/llmobs/plugins/
├── openai/
│ ├── index.spec.js
│ └── cassettes/
│ ├── chat-completion.yaml
│ ├── chat-streaming.yaml
│ └── embeddings.yaml
├── anthropic/
│ ├── index.spec.js
│ └── cassettes/
│ ├── messages-create.yaml
│ └── messages-streaming.yaml
└── google-genai/
├── index.spec.js
└── cassettes/
└── generate-content.yaml
Cassettes are YAML files with request/response data:
interactions:
- request:
method: POST
uri: https://api.openai.com/v1/chat/completions
body:
messages:
- role: user
content: Hello
response:
status: 200
body:
choices:
- message:
role: assistant
content: Hi there!
When to re-record:
Process:
export PROVIDER_API_KEY="..."npm test ...Once cassettes exist, tests replay automatically:
Use VCR for:
Don't use VCR for:
VCR proxy typically runs as part of test infrastructure:
# Start VCR proxy (usually handled by test framework)
node test/vcr-proxy.js
Proxy listens on http://127.0.0.1:9126 and routes to real APIs.
Solution: Ensure VCR proxy is running and API key is set.
Solution: Check cassette format matches expected response structure.
Solution: Response format may have changed. Update assertions to match new format.
Solution: Ensure cassettes are committed to git.
describe('openai LLMObs', () => {
const { getEvents } = useLlmObs({ plugin: 'openai' })
let openai
beforeEach(() => {
const OpenAI = require('openai')
openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY ?? 'test-api-key',
baseURL: 'http://127.0.0.1:9126/vcr/openai' // VCR proxy
})
})
it('instruments chat (uses VCR)', async () => {
// First run: Records to cassette
// Subsequent runs: Replays from cassette
const response = await openai.chat.completions.create({
model: 'gpt-4',
messages: [{ role: 'user', content: 'Hello' }]
})
const events = getEvents()
assertLlmObsSpanEvent(events[0], {
spanKind: 'llm',
modelName: 'gpt-4',
// Response data comes from cassette
outputMessages: [{ content: MOCK_STRING, role: 'assistant' }]
})
})
})
| Aspect | VCR | Manual Mocks |
|---|---|---|
| Authenticity | Real API responses | Synthetic data |
| Setup | Minimal | Requires mock implementation |
| Maintenance | Re-record when API changes | Update mock code |
| Coverage | Full response structure | Only mocked fields |
| Performance | Fast (playback) | Fast |
Recommendation: Use VCR for API clients (Categories 1 & 2), manual mocks for orchestration (Category 3).