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
243 lines
7.4 KiB
Markdown
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 |
|