Files
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

417 lines
14 KiB
JavaScript

// provider-simple-traced.js
// RAG/Agent provider with intricate OpenTelemetry tracing
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-base'); // Use BatchSpanProcessor
const { resourceFromAttributes } = require('@opentelemetry/resources');
const { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } = require('@opentelemetry/semantic-conventions');
// Configure OTLP exporter
const exporterUrl = process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4318/v1/traces';
console.log('[Provider] Configuring OTLP exporter with URL:', exporterUrl);
const exporter = new OTLPTraceExporter({
url: exporterUrl,
});
// Use BatchSpanProcessor for better timing handling
const spanProcessor = new BatchSpanProcessor(exporter, {
maxQueueSize: 100,
maxExportBatchSize: 10,
scheduledDelayMillis: 500, // Wait 500ms before exporting
exportTimeoutMillis: 30000,
});
// Initialize OpenTelemetry with span processor in constructor (v2.x API)
const provider = new NodeTracerProvider({
resource: resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'simple-traced-provider',
[ATTR_SERVICE_VERSION]: '1.0.0',
}),
spanProcessors: [spanProcessor],
});
provider.register();
// Get a tracer
const tracer = trace.getTracer('simple-traced-provider', '1.0.0');
// Fixed helper function that properly manages span lifecycle
async function runInSpan(spanOrName, attributesOrFn, maybeFn) {
let span;
let fn;
let attributes = {};
// Handle overloaded parameters
if (typeof spanOrName === 'string') {
// Called with (name, attributes, fn) or (name, fn)
if (typeof attributesOrFn === 'function') {
fn = attributesOrFn;
} else {
attributes = attributesOrFn || {};
fn = maybeFn;
}
span = tracer.startSpan(spanOrName, { attributes });
} else {
// Called with (span, fn) - original pattern
span = spanOrName;
fn = attributesOrFn;
}
const ctx = trace.setSpan(context.active(), span);
try {
const result = await context.with(ctx, fn);
span.setStatus({ code: SpanStatusCode.OK });
return result;
} catch (error) {
span.recordException(error);
span.setStatus({
code: SpanStatusCode.ERROR,
message: error.message,
});
throw error;
} finally {
span.end();
}
}
// Provider implementation
class SimpleTracedProvider {
id() {
return 'simple-traced-provider';
}
async callApi(prompt, promptfooContext) {
console.log('[Provider] Called with:', {
traceparent: promptfooContext?.traceparent,
evaluationId: promptfooContext?.evaluationId,
testCaseId: promptfooContext?.testCaseId,
});
// Check if we have trace context from Promptfoo
if (promptfooContext?.traceparent) {
// Parse W3C trace context
const matches = promptfooContext.traceparent.match(
/^(\d{2})-([a-f0-9]{32})-([a-f0-9]{16})-(\d{2})$/,
);
if (matches) {
const [, _version, traceId, parentId, traceFlags] = matches;
console.log('[Provider] Using trace context:', { traceId, parentId });
// Create parent context from Promptfoo's trace
const parentCtx = trace.setSpanContext(context.active(), {
traceId,
spanId: parentId,
traceFlags: Number.parseInt(traceFlags, 16),
isRemote: true,
});
// Run our operations within the parent context
return context.with(parentCtx, () => this._tracedCallApi(prompt, promptfooContext));
}
}
console.log('[Provider] No trace context, running without tracing');
return this._untracedCallApi(prompt, promptfooContext);
}
async _tracedCallApi(prompt, promptfooContext) {
// Use the improved runInSpan for the main workflow
return runInSpan(
'rag_agent_workflow',
{
'promptfoo.evaluation_id': promptfooContext.evaluationId,
'promptfoo.test_case_id': promptfooContext.testCaseId,
'prompt.text': prompt,
'prompt.length': prompt.length,
'agent.type': 'rag_assistant',
'agent.version': '2.0',
},
async () => {
const span = trace.getSpan(context.active());
const startTime = Date.now();
let totalTokens = 0;
let userIntent;
const documents = [];
// Step 1: Query Analysis
await runInSpan(
'query_analysis',
{
'step.type': 'preprocessing',
'model.name': 'gpt-3.5-turbo',
},
async () => {
const span = trace.getSpan(context.active());
span.addEvent('analyzing_user_intent');
const analysisDelay = 250 + Math.random() * 100;
await new Promise((resolve) => setTimeout(resolve, analysisDelay));
userIntent = {
type: prompt.toLowerCase().includes('compare')
? 'comparison'
: prompt.toLowerCase().includes('explain')
? 'explanation'
: 'general',
entities: ['quantum computing', 'classical computing'],
complexity: 'medium',
};
span.setAttributes({
'intent.type': userIntent.type,
'intent.entities': JSON.stringify(userIntent.entities),
'intent.complexity': userIntent.complexity,
'tokens.used': 120,
});
totalTokens += 120;
},
);
// Step 2: Document Retrieval
await runInSpan(
'document_retrieval',
{
'retrieval.method': 'vector_similarity',
'retrieval.index': 'technical_docs',
},
async () => {
const retrievalSpan = trace.getSpan(context.active());
// Simulate multiple retrieval attempts
for (let i = 0; i < 3; i++) {
await runInSpan(
`retrieve_document_${i}`,
{
'document.index': i,
'search.query': userIntent.entities.join(' '),
'tool.name': 'search_corpus',
'tool.arguments': JSON.stringify({
query: userIntent.entities.join(' '),
document_index: i,
}),
},
async () => {
const docSpan = trace.getSpan(context.active());
const retrievalDelay = 150 + i * 50 + Math.random() * 50;
await new Promise((resolve) => setTimeout(resolve, retrievalDelay));
const doc = {
id: `doc_${i + 1}`,
title: `Technical Document ${i + 1}`,
relevance_score: 0.95 - i * 0.1,
chunk_count: 5,
source: i === 0 ? 'arxiv' : i === 1 ? 'wikipedia' : 'textbook',
};
docSpan.setAttributes({
'document.id': doc.id,
'document.title': doc.title,
'document.relevance_score': doc.relevance_score,
'document.source': doc.source,
});
docSpan.addEvent('document_retrieved', {
chunk_count: doc.chunk_count,
processing_time_ms: retrievalDelay,
});
documents.push(doc);
},
);
}
retrievalSpan.setAttributes({
'retrieval.document_count': documents.length,
'retrieval.top_score': Math.max(...documents.map((d) => d.relevance_score)),
});
},
);
// Step 3: Context Augmentation
await runInSpan(
'context_augmentation',
{
'augmentation.strategy': 'rerank_and_merge',
},
async () => {
const span = trace.getSpan(context.active());
const augmentationDelay = 180 + Math.random() * 70;
await new Promise((resolve) => setTimeout(resolve, augmentationDelay));
span.addEvent('reranking_documents', {
original_count: documents.length,
strategy: 'cross_encoder',
});
span.addEvent('merging_contexts', {
merge_strategy: 'weighted_concatenation',
max_context_length: 4096,
});
span.setAttributes({
'context.final_length': 3500,
'context.document_count': 2,
'tokens.used': 250,
});
totalTokens += 250;
},
);
// Step 4: Reasoning Chain
await runInSpan(
'reasoning_chain',
{
'reasoning.type': 'chain_of_thought',
'model.name': 'gpt-4',
},
async () => {
const reasoningSpan = trace.getSpan(context.active());
// Simulate multiple reasoning steps
const reasoningSteps = [
{ step: 'identify_key_concepts', duration: 320, tokens: 180 },
{ step: 'analyze_relationships', duration: 450, tokens: 220 },
{ step: 'synthesize_answer', duration: 580, tokens: 350 },
];
for (const step of reasoningSteps) {
await runInSpan(`reasoning_${step.step}`, async () => {
const stepSpan = trace.getSpan(context.active());
await new Promise((resolve) => setTimeout(resolve, step.duration));
stepSpan.addEvent(`${step.step}_completed`, {
processing_time_ms: step.duration,
confidence_score: 0.85 + Math.random() * 0.1,
});
stepSpan.setAttributes({
'step.name': step.step,
'step.duration_ms': step.duration,
'step.tokens': step.tokens,
});
totalTokens += step.tokens;
});
}
reasoningSpan.setAttributes({
'reasoning.total_steps': reasoningSteps.length,
'reasoning.total_tokens': reasoningSteps.reduce((sum, s) => sum + s.tokens, 0),
});
},
);
// Step 5: Response Generation
let response;
await runInSpan(
'response_generation',
{
'generation.type': 'augmented_response',
'model.name': 'gpt-4',
'tool.name': 'compose_answer',
'tool.arguments': JSON.stringify({
citation_count: documents.length,
tone: 'explanatory',
}),
},
async () => {
const span = trace.getSpan(context.active());
const generationDelay = 750 + Math.random() * 200;
await new Promise((resolve) => setTimeout(resolve, generationDelay));
response = {
text:
`Based on my analysis of ${documents.length} technical documents, here's a comprehensive explanation:\n\n` +
`${userIntent.entities.join(' and ')} are fascinating topics in computer science. ` +
`After analyzing multiple sources including arxiv papers and textbooks, I can provide the following insights:\n\n` +
`1. Core Concepts: The fundamental principles involve...\n` +
`2. Key Differences: When comparing these technologies...\n` +
`3. Practical Applications: In real-world scenarios...\n\n` +
`This synthesis is based on recent research and established knowledge in the field.`,
citations: documents.map((d) => ({
id: d.id,
title: d.title,
relevance: d.relevance_score,
})),
confidence: 0.92,
};
span.setAttributes({
'response.length': response.text.length,
'response.citations_count': response.citations.length,
'response.confidence': response.confidence,
'tokens.used': 450,
});
span.addEvent('response_finalized', {
word_count: response.text.split(' ').length,
paragraph_count: response.text.split('\n\n').length,
});
totalTokens += 450;
},
);
// Add final span attributes
span.setAttributes({
'workflow.total_duration_ms': Date.now() - startTime,
'workflow.total_steps': 5,
'workflow.total_tokens': totalTokens,
'workflow.success': true,
'response.confidence': response.confidence,
});
span.addEvent('workflow_completed', {
total_processing_time_ms: Date.now() - startTime,
documents_used: documents.length,
reasoning_steps: 3,
});
// Force flush to ensure spans are sent
try {
console.log('[Provider] Flushing spans...');
await spanProcessor.forceFlush();
console.log('[Provider] Spans exported successfully');
} catch (error) {
console.error('[Provider] Failed to flush spans:', error.message);
}
return {
output: response.text,
tokenUsage: {
total: totalTokens,
prompt: Math.floor(totalTokens * 0.4),
completion: Math.floor(totalTokens * 0.6),
},
metadata: {
citations: response.citations,
confidence: response.confidence,
workflow_duration_ms: Date.now() - startTime,
},
};
},
);
}
async _untracedCallApi(prompt, promptfooContext) {
// Simple implementation without tracing
await new Promise((resolve) => setTimeout(resolve, 100));
const topic = prompt.toLowerCase().includes('quantum')
? 'quantum computing'
: 'machine learning';
return {
output: `Here's a simple explanation of ${topic}: It's a fascinating field that involves...`,
tokenUsage: {
total: 50,
prompt: 30,
completion: 20,
},
};
}
}
module.exports = SimpleTracedProvider;