Skip to main content
Glama
ai-tracing.ts6.62 kB
/** * AI Provider Tracing Utilities * * Generic wrapper for instrumenting AI provider calls with OpenTelemetry. * Uses official GenAI semantic conventions for AI/LLM operations. * * Supports: * - chat operations (sendMessage) * - tool_loop operations (toolLoop with agentic tool calling) * - embeddings operations (generateEmbedding, generateEmbeddings) * * Reference: https://opentelemetry.io/docs/specs/semconv/gen-ai/ */ import { trace, context, SpanStatusCode, SpanKind } from '@opentelemetry/api'; /** * Configuration for AI operation tracing */ export interface AITracingOptions { /** AI provider name (e.g., 'anthropic', 'openai', 'google') */ provider: string; /** Model identifier (e.g., 'claude-3-5-sonnet', 'gpt-4o', 'text-embedding-3-small') */ model: string; /** Operation type: 'chat', 'tool_loop', 'embeddings' */ operation: 'chat' | 'tool_loop' | 'embeddings'; /** Optional max tokens parameter (only for chat/tool_loop operations) */ maxTokens?: number; } /** * Metrics extracted from AI operation result * Fields vary by operation type: * - chat/tool_loop: inputTokens, outputTokens, cache tokens * - embeddings: embeddingCount, embeddingDimensions */ export interface AITracingResult { /** Input tokens consumed (chat/tool_loop only) */ inputTokens?: number; /** Output tokens generated (chat/tool_loop only) */ outputTokens?: number; /** Cache read tokens (if provider supports caching) */ cacheReadTokens?: number; /** Cache creation tokens (if provider supports caching) */ cacheCreationTokens?: number; /** Number of embeddings generated (embeddings only) */ embeddingCount?: number; /** Dimension size of embeddings (embeddings only) */ embeddingDimensions?: number; } /** * Generic wrapper for AI provider calls * * Creates CLIENT spans with official gen_ai.* semantic conventions. * The auto-instrumented HTTP span becomes a child of this span. * * @param options AI operation configuration * @param handler Function that performs the actual AI call * @param extractMetrics Function to extract metrics from the result * @returns Result from the handler function * * @example Chat operation * const response = await withAITracing( * { provider: 'anthropic', model: 'claude-3-5-sonnet', operation: 'chat' }, * async () => await client.messages.create(...), * (result) => ({ inputTokens: result.usage.input_tokens, outputTokens: result.usage.output_tokens }) * ); * * @example Tool loop operation * const result = await withAITracing( * { provider: 'anthropic', model: 'claude-3-5-sonnet', operation: 'tool_loop' }, * async () => await provider.toolLoop(...), * (result) => ({ inputTokens: result.totalTokens.input, outputTokens: result.totalTokens.output }) * ); * * @example Embeddings operation * const embedding = await withAITracing( * { provider: 'openai', model: 'text-embedding-3-small', operation: 'embeddings' }, * async () => await client.embeddings.create(...), * (result) => ({ embeddingCount: 1, embeddingDimensions: 1536 }) * ); */ export async function withAITracing<T>( options: AITracingOptions, handler: () => Promise<T>, extractMetrics: (result: T) => AITracingResult ): Promise<T> { // Get tracer (returns no-op if tracing disabled) const tracer = trace.getTracer('dot-ai-mcp'); // Span name format: "{operation} {model}" // Examples: "chat claude-3-5-sonnet", "tool_loop claude-3-5-sonnet", "embeddings text-embedding-3-small" const spanName = `${options.operation} ${options.model}`; return await tracer.startActiveSpan( spanName, { kind: SpanKind.CLIENT, attributes: { // Required GenAI attributes (per OpenTelemetry spec) 'gen_ai.operation.name': options.operation, 'gen_ai.provider.name': options.provider, 'gen_ai.request.model': options.model, // Optional request parameters (only for chat/tool_loop) ...(options.maxTokens !== undefined && { 'gen_ai.request.max_tokens': options.maxTokens, }), }, }, async (span) => { const startTime = Date.now(); try { // Execute the actual AI call within this span's context // Auto-instrumented HTTP spans will be children of this span const result = await context.with( trace.setSpan(context.active(), span), handler ); // Extract metrics from the result const metrics = extractMetrics(result); // Add response model (usually same as request) span.setAttribute('gen_ai.response.model', options.model); span.setAttribute('gen_ai.ai.duration_ms', Date.now() - startTime); // Add operation-specific metrics if (options.operation === 'chat' || options.operation === 'tool_loop') { // Token-based metrics for chat/tool_loop operations if (metrics.inputTokens !== undefined) { span.setAttribute('gen_ai.usage.input_tokens', metrics.inputTokens); } if (metrics.outputTokens !== undefined) { span.setAttribute('gen_ai.usage.output_tokens', metrics.outputTokens); } // Cache metrics (Anthropic-specific for chat operations) if (metrics.cacheReadTokens !== undefined && metrics.cacheReadTokens > 0) { span.setAttribute('gen_ai.usage.cache_read_tokens', metrics.cacheReadTokens); } if (metrics.cacheCreationTokens !== undefined && metrics.cacheCreationTokens > 0) { span.setAttribute('gen_ai.usage.cache_creation_tokens', metrics.cacheCreationTokens); } } else if (options.operation === 'embeddings') { // Embedding-specific metrics if (metrics.embeddingCount !== undefined) { span.setAttribute('gen_ai.embeddings.count', metrics.embeddingCount); } if (metrics.embeddingDimensions !== undefined) { span.setAttribute('gen_ai.embeddings.dimensions', metrics.embeddingDimensions); } } span.setStatus({ code: SpanStatusCode.OK }); return result; } catch (error) { // Record exception with full details span.recordException(error as Error); span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error), }); span.setAttribute( 'error.type', error instanceof Error ? error.constructor.name : 'unknown' ); throw error; } finally { span.end(); } } ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/vfarcic/dot-ai'

If you have feedback or need assistance with the MCP directory API, please join our Discord server