Files
promptfoo--promptfoo/examples/integration-opentelemetry/javascript/README.md
T
wehub-resource-sync 0d3cb498a3
CI / Shell Format Check (push) Has been cancelled
CI / Check Ruby (3.4) (push) Has been cancelled
CI / CI Config (push) Has been cancelled
CI / Test on Node ${{ matrix.node }} and ${{ matrix.os }}${{ matrix.shard && format(' (shard {0}/3)', matrix.shard) || '' }} (push) Has been cancelled
CI / Build on Node ${{ matrix.node }} (push) Has been cancelled
CI / Style Check (push) Has been cancelled
CI / Generate Assets (push) Has been cancelled
CI / Check Python (3.14) (push) Has been cancelled
CI / Check Python (3.9) (push) Has been cancelled
CI / Build Docs (push) Has been cancelled
CI / Code Scan Action (push) Has been cancelled
CI / Site tests (push) Has been cancelled
CI / webui tests (push) Has been cancelled
CI / Run Integration Tests (push) Has been cancelled
CI / Run Smoke Tests (push) Has been cancelled
CI / Go Tests (push) Has been cancelled
CI / Share Test (push) Has been cancelled
CI / Redteam (Production API) (push) Has been cancelled
CI / Redteam (Staging API) (push) Has been cancelled
CI / GitHub Actions Lint (push) Has been cancelled
CI / Check Ruby (3.0) (push) Has been cancelled
release-please / release-please (push) Has been cancelled
release-please / build (push) Has been cancelled
release-please / publish-npm (push) Has been cancelled
release-please / publish-npm-backfill (push) Has been cancelled
release-please / docker (push) Has been cancelled
release-please / publish-code-scan-action (push) Has been cancelled
release-please / attest-code-scan-action (push) Has been cancelled
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Has been cancelled
Test and Publish Multi-arch Docker Image / test (push) Has been cancelled
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Has been cancelled
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-arm64 platform:linux/arm64 runner:ubuntu-24.04-arm]) (push) Has been cancelled
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Has been cancelled
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Has been cancelled
Validate Renovate Config / Validate Renovate Configuration (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:24:08 +08:00

243 lines
7.4 KiB
Markdown

# integration-opentelemetry/javascript (OpenTelemetry Tracing Example)
This example demonstrates how to use OpenTelemetry to trace the internal operations of your LLM providers during Promptfoo evaluations.
## Quick Start
```bash
npx promptfoo@latest init --example integration-opentelemetry/javascript
cd integration-opentelemetry/javascript
npm install
npx promptfoo@latest eval
npx promptfoo@latest view
```
To run the trajectory assertion variant from this directory, use:
```bash
npx promptfoo@latest eval -c promptfooconfig.trajectory.yaml --no-cache
```
## Environment Variables
This example requires no API keys - it uses a simulated provider that demonstrates tracing patterns.
## Overview
Promptfoo's OpenTelemetry integration allows you to:
- Trace internal operations of your providers without a custom SDK
- Use standard OpenTelemetry libraries in any language
- Send traces to any OpenTelemetry-compatible backend
- Correlate traces with specific test cases and evaluations
## How It Works
1. **OTLP receiver starts automatically** - Promptfoo ensures the receiver is ready before evaluations begin
2. **Promptfoo generates a trace context** for each test case evaluation
3. **The trace context is passed to providers** via the `traceparent` field
4. **Providers create child spans** using standard OpenTelemetry SDKs
5. **Traces are sent to Promptfoo's OTLP endpoint** (port 4318 by default)
6. **Promptfoo correlates traces** with evaluations for analysis
## Files in This Example
| File | Description |
| --------------------------- | ----------------------------------------------------- |
| `promptfooconfig.yaml` | Evaluation config with tracing enabled and assertions |
| `provider-simple-traced.js` | Simulated RAG provider with comprehensive tracing |
| `trace-assertions.js` | Custom JavaScript assertion for trace validation |
| `package.json` | OpenTelemetry dependencies (v2.x API) |
## Tracing Configuration
Enable tracing in your `promptfooconfig.yaml`:
```yaml
tracing:
enabled: true
otlp:
http:
enabled: true
port: 4318
host: '0.0.0.0'
```
## Instrumenting Your Provider
The provider receives trace context from Promptfoo via the `traceparent` field. Here's the pattern used in this example:
```javascript
const { trace, context, SpanStatusCode } = require('@opentelemetry/api');
const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-node');
const { resourceFromAttributes } = require('@opentelemetry/resources');
const { ATTR_SERVICE_NAME } = require('@opentelemetry/semantic-conventions');
// Initialize OpenTelemetry (v2.x API)
const exporter = new OTLPTraceExporter({
url: 'http://localhost:4318/v1/traces',
});
const provider = new NodeTracerProvider({
resource: resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'my-provider',
}),
spanProcessors: [new BatchSpanProcessor(exporter)],
});
provider.register();
const tracer = trace.getTracer('my-provider');
module.exports = {
async callApi(prompt, promptfooContext) {
// Parse trace context from Promptfoo
if (promptfooContext?.traceparent) {
const matches = promptfooContext.traceparent.match(
/^(\d{2})-([a-f0-9]{32})-([a-f0-9]{16})-(\d{2})$/,
);
if (matches) {
const [, , traceId, parentId, traceFlags] = matches;
// Create parent context
const parentCtx = trace.setSpanContext(context.active(), {
traceId,
spanId: parentId,
traceFlags: parseInt(traceFlags, 16),
isRemote: true,
});
// Run operations within parent context
return context.with(parentCtx, async () => {
const span = tracer.startSpan('my_operation');
try {
// Your provider logic here...
span.setStatus({ code: SpanStatusCode.OK });
return { output: 'result' };
} catch (error) {
span.recordException(error);
span.setStatus({ code: SpanStatusCode.ERROR });
throw error;
} finally {
span.end();
}
});
}
}
return { output: 'result without tracing' };
},
};
```
## Trace-Based Assertions
This example demonstrates several trace assertion types:
```yaml
assert:
# Count spans matching a pattern
- type: trace-span-count
value:
pattern: 'retrieve_document_*'
min: 3
max: 3
# Check span duration
- type: trace-span-duration
value:
pattern: 'rag_agent_workflow'
max: 5000 # milliseconds
# Check for error spans
- type: trace-error-spans
value:
max_count: 0
```
The trajectory-specific config at `promptfooconfig.trajectory.yaml` adds:
- `trajectory:tool-used`
- `trajectory:tool-args-match`
- `trajectory:tool-sequence`
- `trajectory:step-count`
Promptfoo accepts generic tool span attributes such as `tool.name` and `tool.arguments`, and it also recognizes Vercel AI SDK telemetry attributes such as `ai.toolCall.name`, `ai.toolCall.args`, `ai.toolCall.arguments`, and `ai.toolCall.input`.
## Viewing Traces
After running an evaluation, view traces in the web UI:
```bash
npx promptfoo@latest view
```
Click on any test result to see the "Trace Timeline" section showing:
- Hierarchical span visualization
- Duration bars showing relative timing
- Status indicators (OK/ERROR)
- Span attributes and events
## Environment Variables
Configure OpenTelemetry using standard environment variables:
```bash
# Custom endpoint (defaults to Promptfoo's receiver)
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4318"
# Headers for authentication with external collectors
export OTEL_EXPORTER_OTLP_HEADERS="api-key=your-key"
# Enable tracing via environment variable
export PROMPTFOO_TRACING_ENABLED=true
```
## Forward to External Collectors
Send traces to Jaeger, Honeycomb, or other OTLP-compatible backends:
```yaml
tracing:
enabled: true
forwarding:
enabled: true
endpoint: 'http://jaeger:4318'
headers:
'api-key': '${JAEGER_API_KEY}'
```
## Troubleshooting
### Context Naming Conflicts
If you see `context.active is not a function`, the OpenTelemetry `context` API conflicts with Promptfoo's context parameter. Rename the parameter:
```javascript
async callApi(prompt, promptfooContext) {
// Use promptfooContext for Promptfoo's context
// Use context from @opentelemetry/api for tracing
}
```
### Traces Not Appearing
1. Verify `tracing.enabled: true` in config
2. Check OTLP receiver is running (look for port 4318 in logs)
3. Ensure trace context is properly parsed from `promptfooContext.traceparent`
4. Call `spanProcessor.forceFlush()` before returning from provider
## Dependencies
This example uses OpenTelemetry v2.x packages:
| Package | Version | Purpose |
| ----------------------------------------- | -------- | ------------------------ |
| `@opentelemetry/api` | ^1.9.0 | Core tracing API |
| `@opentelemetry/sdk-trace-node` | ^2.0.0 | Node.js tracer provider |
| `@opentelemetry/exporter-trace-otlp-http` | ^0.200.0 | OTLP HTTP exporter |
| `@opentelemetry/resources` | ^2.0.0 | Resource attributes |
| `@opentelemetry/semantic-conventions` | ^1.28.0 | Standard attribute names |