ai-functions
Version:
Core AI primitives for building intelligent applications
285 lines • 9.67 kB
JavaScript
/**
* OpenTelemetry Integration for ai-functions
*
* Provides instrumented wrappers and telemetry utilities for AI function calls.
*
* @example
* ```ts
* import { withTelemetry, instrumentGenerate } from 'ai-functions/telemetry'
*
* // Enable telemetry globally
* withTelemetry({ provider: createConsoleTelemetryProvider() }, async () => {
* const text = await generate('Explain quantum computing')
* })
*
* // Or instrument individual functions
* const traced = instrumentGenerate(generate)
* const text = await traced('Explain quantum computing')
* ```
*
* @packageDocumentation
*/
import { getTracer, getMeter, getLogger, setTelemetryProvider, getTelemetryProvider, createAIMetrics, SemanticAttributes, MetricNames, } from '@org.ai/types';
// Package info
const PACKAGE_NAME = 'ai-functions';
const PACKAGE_VERSION = '2.1.4';
// ============================================================================
// Package-level Telemetry
// ============================================================================
let packageTracer;
let packageMeter;
let packageLogger;
let aiMetrics;
/**
* Get the tracer for ai-functions
*/
export function getFunctionsTracer() {
if (!packageTracer) {
packageTracer = getTracer(PACKAGE_NAME, PACKAGE_VERSION);
}
return packageTracer;
}
/**
* Get the meter for ai-functions
*/
export function getFunctionsMeter() {
if (!packageMeter) {
packageMeter = getMeter(PACKAGE_NAME, PACKAGE_VERSION);
}
return packageMeter;
}
/**
* Get the logger for ai-functions
*/
export function getFunctionsLogger() {
if (!packageLogger) {
packageLogger = getLogger(PACKAGE_NAME);
}
return packageLogger;
}
/**
* Get AI metrics for the package
*/
export function getAIMetrics() {
if (!aiMetrics) {
aiMetrics = createAIMetrics(getFunctionsMeter());
}
return aiMetrics;
}
/**
* Reset cached telemetry instances (useful after changing provider)
*/
export function resetTelemetry() {
packageTracer = undefined;
packageMeter = undefined;
packageLogger = undefined;
aiMetrics = undefined;
}
/**
* Execute a function with telemetry enabled
*
* @example
* ```ts
* import { createConsoleTelemetryProvider } from '@org.ai/types'
*
* await withTelemetry({
* provider: createConsoleTelemetryProvider()
* }, async () => {
* const text = await generate('Hello')
* })
* ```
*/
export async function withTelemetry(options, fn) {
const previousProvider = getTelemetryProvider();
if (options.provider) {
setTelemetryProvider(options.provider);
resetTelemetry();
}
try {
return await fn();
}
finally {
if (options.provider) {
setTelemetryProvider(previousProvider);
resetTelemetry();
}
}
}
// ============================================================================
// Instrumentation Wrappers
// ============================================================================
/**
* Record AI request metrics
*/
export function recordAIRequest(params) {
const metrics = getAIMetrics();
const labels = {
model: params.model,
provider: params.provider,
status: params.success ? 'success' : 'error',
};
metrics.requestTotal.add(1, labels);
metrics.requestDuration.record(params.durationMs, labels);
metrics.tokensUsed.add(params.inputTokens + params.outputTokens, {
...labels,
type: 'total',
});
if (!params.success) {
metrics.requestErrors.add(1, labels);
}
if (params.costUsd !== undefined) {
metrics.costTotal.add(params.costUsd, labels);
}
}
/**
* Create a traced version of any async function
*/
export function traced(name, fn, options = {}) {
return async (...args) => {
const tracer = getFunctionsTracer();
const logger = getFunctionsLogger();
const attributes = {
[SemanticAttributes.AI_FUNCTION_NAME]: name,
...(options.getAttributes ? options.getAttributes(...args) : {}),
};
return tracer.withSpan(name, { kind: options.kind || 'internal', attributes }, async (span) => {
logger.debug(`Executing ${name}`, { attributes });
try {
const result = await fn(...args);
span.setStatus('ok');
return result;
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
span.setStatus('error', message);
span.setAttribute(SemanticAttributes.EXCEPTION_MESSAGE, message);
if (error instanceof Error && error.stack) {
span.setAttribute(SemanticAttributes.EXCEPTION_STACKTRACE, error.stack);
}
logger.error(`Error in ${name}`, error instanceof Error ? error : undefined, {
attributes,
});
throw error;
}
});
};
}
/**
* Create instrumented generate function
*
* @example
* ```ts
* import { generate } from 'ai-functions'
* import { instrumentGenerate } from 'ai-functions/telemetry'
*
* const tracedGenerate = instrumentGenerate(generate)
* const result = await tracedGenerate('text', 'Hello world')
* ```
*/
export function instrumentGenerate(generateFn) {
return traced('ai.generate', generateFn, {
kind: 'client',
getAttributes: (type) => ({
[SemanticAttributes.AI_FUNCTION_TYPE]: type || 'text',
}),
});
}
/**
* Create instrumented AI function with full metrics
*/
export function instrumentAIFunction(name, fn, options = {}) {
return async (input) => {
const tracer = getFunctionsTracer();
const logger = getFunctionsLogger();
const startTime = Date.now();
const attributes = {
[SemanticAttributes.AI_FUNCTION_NAME]: name,
...(options.model && { [SemanticAttributes.AI_MODEL]: options.model }),
...(options.provider && { [SemanticAttributes.AI_PROVIDER]: options.provider }),
};
return tracer.withSpan(`ai.function.${name}`, { kind: 'client', attributes }, async (span) => {
logger.info(`AI function ${name} started`, { model: options.model });
try {
const output = await fn(input);
const durationMs = Date.now() - startTime;
// Record metrics
const tokens = options.getTokens?.(input, output);
const cost = options.getCost?.(input, output);
if (tokens) {
span.setAttribute(SemanticAttributes.AI_INPUT_TOKENS, tokens.input);
span.setAttribute(SemanticAttributes.AI_OUTPUT_TOKENS, tokens.output);
span.setAttribute(SemanticAttributes.AI_TOTAL_TOKENS, tokens.input + tokens.output);
}
if (cost !== undefined) {
span.setAttribute(SemanticAttributes.AI_COST_USD, cost);
}
recordAIRequest({
model: options.model || 'unknown',
provider: options.provider || 'unknown',
inputTokens: tokens?.input || 0,
outputTokens: tokens?.output || 0,
durationMs,
success: true,
costUsd: cost,
});
span.setStatus('ok');
logger.info(`AI function ${name} completed`, {
durationMs,
tokens: tokens?.input !== undefined ? tokens.input + (tokens.output || 0) : undefined,
});
return output;
}
catch (error) {
const durationMs = Date.now() - startTime;
const message = error instanceof Error ? error.message : String(error);
recordAIRequest({
model: options.model || 'unknown',
provider: options.provider || 'unknown',
inputTokens: 0,
outputTokens: 0,
durationMs,
success: false,
});
span.setStatus('error', message);
logger.error(`AI function ${name} failed`, error instanceof Error ? error : undefined, {
durationMs,
});
throw error;
}
});
};
}
// ============================================================================
// Span Helpers
// ============================================================================
/**
* Start a span for an AI operation
*/
export function startAISpan(name, attributes) {
const tracer = getFunctionsTracer();
return tracer.startSpan(name, {
kind: 'client',
attributes: {
[SemanticAttributes.SERVICE_NAME]: PACKAGE_NAME,
...attributes,
},
});
}
/**
* Execute code within a span
*/
export async function withAISpan(name, fn, attributes) {
const tracer = getFunctionsTracer();
return tracer.withSpan(name, { kind: 'client', attributes }, fn);
}
// ============================================================================
// Re-exports from @org.ai/types
// ============================================================================
export {
// Global functions
getTracer, getMeter, getLogger, setTelemetryProvider, getTelemetryProvider,
// Constants
SemanticAttributes, MetricNames,
// Utilities
createAIMetrics, } from '@org.ai/types';
//# sourceMappingURL=telemetry.js.map