UNPKG

@agentscope/studio

Version:

AgentScope Studio is a powerful local monitoring and visualization tool designed to provide real-time insights into your system's performance and behavior.

396 lines (395 loc) 16.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SpanProcessor = void 0; const trace_1 = require("../../../shared/src/types/trace"); const objectUtils_1 = require("../../../shared/src/utils/objectUtils"); const timeUtils_1 = require("../../../shared/src/utils/timeUtils"); class SpanProcessor { static validateOTLPSpan(span) { try { const spanObj = span; if (!spanObj.trace_id || !spanObj.span_id || !spanObj.name) { console.error('[SpanProcessor] Missing required span fields'); return false; } if (!spanObj.start_time_unix_nano || !spanObj.end_time_unix_nano) { console.error('[SpanProcessor] Missing span time fields'); return false; } if (isNaN(Number(spanObj.start_time_unix_nano)) || isNaN(Number(spanObj.end_time_unix_nano))) { console.error('[SpanProcessor] Invalid timestamp format'); return false; } return true; } catch (error) { console.error('[SpanProcessor] Validation error:', error); return false; } } static decodeOTLPSpan(span, resource, scope) { const spanObj = span; const traceId = this.decodeIdentifier(spanObj.trace_id); const spanId = this.decodeIdentifier(spanObj.span_id); const parentId = spanObj.parent_span_id ? this.decodeIdentifier(spanObj.parent_span_id) : undefined; const startTimeUnixNano = (0, timeUtils_1.decodeUnixNano)(spanObj.start_time_unix_nano); const endTimeUnixNano = (0, timeUtils_1.decodeUnixNano)(spanObj.end_time_unix_nano); // Decode attributes let attributes = this.decodeAttributes(spanObj.attributes); let spanName = typeof spanObj.name === 'string' ? spanObj.name : ''; if (scope.name.toLowerCase().includes('agentscope.tracing._trace')) { this.logOldProtocolWarning(); const newValues = this.convertOldProtocolToNew(attributes, { name: spanName, }); spanName = newValues.span_name; attributes = newValues.attributes; } const events = this.decodeArray(spanObj.events, (e) => this.decodeEvent(e)); const links = this.decodeArray(spanObj.links, (l) => this.decodeLink(l)); const status = this.decodeStatus(spanObj.status); return { traceId: traceId, spanId: spanId, traceState: typeof spanObj.trace_state === 'string' ? spanObj.trace_state : undefined, parentSpanId: parentId, flags: typeof spanObj.flags === 'number' ? spanObj.flags : undefined, name: spanName, kind: typeof spanObj.kind === 'number' ? spanObj.kind : 0, startTimeUnixNano: startTimeUnixNano, endTimeUnixNano: endTimeUnixNano, attributes: attributes, droppedAttributesCount: typeof spanObj.dropped_attributes_count === 'number' ? spanObj.dropped_attributes_count : 0, events: events, droppedEventsCount: typeof spanObj.dropped_events_count === 'number' ? spanObj.dropped_events_count : 0, links: links, droppedLinksCount: typeof spanObj.dropped_links_count === 'number' ? spanObj.dropped_links_count : 0, status: status, resource: resource, scope: scope, conversationId: this.getConversationId(attributes), latencyNs: (0, timeUtils_1.getTimeDifferenceNano)(startTimeUnixNano, endTimeUnixNano), }; } static decodeAttributes(attributes) { const attrs = Array.isArray(attributes) ? attributes : []; return this.unflattenAttributes(this.loadJsonStrings(this.decodeKeyValues(attrs))); } static decodeArray(value, mapper) { return Array.isArray(value) ? value.map(mapper) : []; } static getConversationId(attributes) { const conversationId = (0, objectUtils_1.getNestedValue)(attributes, 'gen_ai.conversation.id'); if (conversationId) return String(conversationId); const oldRunId = (0, objectUtils_1.getNestedValue)(attributes, 'project.run_id'); return oldRunId ? String(oldRunId) : 'unknown'; } /** * Log warning about old protocol format. * - Within 5 minutes: warn on each detection * - After 5 minutes: stop warning */ static logOldProtocolWarning() { const now = Date.now(); const timeSinceStart = now - this.warningPeriodStartTime; if (timeSinceStart < this.WARNING_PERIOD_MS) { console.warn('[Warning] Agentscope SDK version is too low. Please update to 1.0.9 or higher.'); } } /** * Convert old protocol format attributes to new format * * Old format -> New format mappings: * - project.run_id -> gen_ai.conversation.id * - output.usage.* -> gen_ai.usage.* * - output.model -> gen_ai.request.model * - output.response.* -> gen_ai.response.* * * @param attributes The attributes object to convert * @param span The original span object (for additional context if needed) * @returns Converted attributes in new format */ static convertOldProtocolToNew(attributes, span) { if (!attributes || typeof attributes !== 'object') { return { span_name: span.name || '', attributes: attributes || {} }; } // Check if already in new format by looking for gen_ai attributes if ((0, objectUtils_1.getNestedValue)(attributes, 'gen_ai')) { return { span_name: span.name || '', attributes: attributes }; } const newAttributes = { gen_ai: { conversation: {}, request: {}, operation: {}, agent: {}, tool: {}, }, agentscope: { function: { input: {}, output: {}, }, }, }; const genAi = newAttributes.gen_ai; const conversation = genAi.conversation; const operation = genAi.operation; const agentscope = newAttributes.agentscope; const request = genAi.request; const agent = genAi.agent; const tool = genAi.tool; const agentscopeFunction = agentscope.function; agentscopeFunction.name = span.name; conversation.id = (0, objectUtils_1.getNestedValue)(attributes, 'project.run_id'); const span_kind = (0, objectUtils_1.getNestedValue)(attributes, 'span.kind'); // Copy input, metadata, output const inputValue = (0, objectUtils_1.getNestedValue)(attributes, 'input'); if (inputValue) agentscopeFunction.input = inputValue; const metadataValue = (0, objectUtils_1.getNestedValue)(attributes, 'metadata'); const outputValue = (0, objectUtils_1.getNestedValue)(attributes, 'output'); if (outputValue) { agentscopeFunction.output = outputValue; if (outputValue.usage && typeof outputValue.usage === 'object') { if (!genAi.usage) { genAi.usage = {}; } const usage = genAi.usage; usage.input_tokens = outputValue.usage.input_tokens; usage.output_tokens = outputValue.usage.output_tokens; } } let span_name = span.name || ''; const metadataObj = metadataValue; if (span_kind === trace_1.OldSpanKind.AGENT) { operation.name = 'invoke_agent'; span_name = operation.name + ' ' + ((metadataObj === null || metadataObj === void 0 ? void 0 : metadataObj.name) || ''); agent.name = (metadataObj === null || metadataObj === void 0 ? void 0 : metadataObj.name) || ''; } else if (span_kind === trace_1.OldSpanKind.TOOL) { operation.name = 'execute_tool'; span_name = operation.name + ' ' + ((metadataObj === null || metadataObj === void 0 ? void 0 : metadataObj.name) || ''); tool.name = (metadataObj === null || metadataObj === void 0 ? void 0 : metadataObj.name) || ''; } else if (span_kind === trace_1.OldSpanKind.LLM) { operation.name = 'chat'; span_name = operation.name + ' ' + ((metadataObj === null || metadataObj === void 0 ? void 0 : metadataObj.model_name) || ''); request.model = (metadataObj === null || metadataObj === void 0 ? void 0 : metadataObj.model_name) || ''; } else if (span_kind === trace_1.OldSpanKind.EMBEDDING) { operation.name = 'embeddings'; span_name = operation.name + ' ' + ((metadataObj === null || metadataObj === void 0 ? void 0 : metadataObj.model_name) || ''); request.model = (metadataObj === null || metadataObj === void 0 ? void 0 : metadataObj.model_name) || ''; } else if (span_kind === trace_1.OldSpanKind.FORMATTER) { operation.name = 'format'; span_name = operation.name + ' ' + ((metadataObj === null || metadataObj === void 0 ? void 0 : metadataObj.name) || ''); } else { operation.name = 'unknown'; } return { span_name, attributes: newAttributes }; } static decodeIdentifier(identifier) { if (!identifier) return ''; if (typeof identifier === 'string') return identifier; if (identifier instanceof Uint8Array) { return Buffer.from(identifier).toString('hex'); } return ''; } static decodeKeyValues(keyValues) { const result = {}; for (const kv of keyValues) { const kvObj = kv; if (kvObj.key && kvObj.value) { result[String(kvObj.key)] = this.decodeAnyValue(kvObj.value); } } return result; } static decodeAnyValue(value) { const valueObj = value; if (valueObj.bool_value !== false && valueObj.bool_value !== undefined) return valueObj.bool_value; if (valueObj.int_value !== 0 && valueObj.int_value !== undefined) return valueObj.int_value; if (valueObj.double_value !== 0 && valueObj.double_value !== undefined) return valueObj.double_value; if (valueObj.string_value !== '' && valueObj.string_value !== undefined) return valueObj.string_value; const arrayValue = valueObj.array_value; if (arrayValue === null || arrayValue === void 0 ? void 0 : arrayValue.values) { return arrayValue.values.map((v) => this.decodeAnyValue(v)); } const kvlistValue = valueObj.kvlist_value; if (kvlistValue === null || kvlistValue === void 0 ? void 0 : kvlistValue.values) { return this.decodeKeyValues(kvlistValue.values); } if (valueObj.bytes_value && typeof valueObj.bytes_value === 'object' && Object.keys(valueObj.bytes_value).length > 0) { return valueObj.bytes_value; } if (valueObj.int_value !== undefined) return valueObj.int_value; if (valueObj.double_value !== undefined) return valueObj.double_value; if (valueObj.string_value !== undefined) return valueObj.string_value; if (valueObj.bool_value !== undefined) return valueObj.bool_value; return null; } static decodeStatus(status) { if (!status || typeof status !== 'object') { return { code: 0, message: '' }; // UNSET } const s = status; return { code: typeof s.code === 'number' ? s.code : 0, message: typeof s.message === 'string' ? s.message : '', }; } static decodeEvent(event) { const e = event; return { name: typeof e.name === 'string' ? e.name : '', time: (0, timeUtils_1.decodeUnixNano)(e.time_unix_nano), attributes: this.decodeAttributes(e.attributes), droppedAttributesCount: typeof e.dropped_attributes_count === 'number' ? e.dropped_attributes_count : 0, }; } static decodeLink(link) { const l = link; return { traceId: this.decodeIdentifier(l.trace_id), spanId: this.decodeIdentifier(l.span_id), traceState: typeof l.trace_state === 'string' ? l.trace_state : undefined, flags: typeof l.flags === 'number' ? l.flags : undefined, attributes: this.decodeAttributes(l.attributes), droppedAttributesCount: typeof l.dropped_attributes_count === 'number' ? l.dropped_attributes_count : 0, }; } static unflattenAttributes(flat) { return (0, objectUtils_1.unflattenObject)(flat); } static loadJsonStrings(attributes) { const result = {}; for (const [key, value] of Object.entries(attributes)) { if (typeof value === 'string') { try { result[key] = JSON.parse(value); } catch (_a) { result[key] = value; } } else { result[key] = value; } } return result; } static decodeResource(resource) { const r = resource; return { attributes: this.decodeAttributes(r.attributes), schemaUrl: typeof r.schema_url === 'string' ? r.schema_url : undefined, }; } static decodeScope(scope) { const s = scope; return { name: typeof s.name === 'string' ? s.name : '', version: typeof s.version === 'string' ? s.version : undefined, attributes: this.decodeAttributes(s.attributes), schemaUrl: typeof s.schema_url === 'string' ? s.schema_url : undefined, }; } static safeDecodeOTLPSpan(span, resource, scope) { try { if (!this.validateOTLPSpan(span)) { return null; } return this.decodeOTLPSpan(span, resource, scope); } catch (error) { console.error('[SpanProcessor] Failed to decode span:', error); throw error; } } static batchProcessOTLPTraces(resourceSpans) { const spans = []; try { for (const resourceSpan of resourceSpans) { const resourceSpanObj = resourceSpan; // Decode resource if (!resourceSpanObj.resource) { continue; } const resource = this.decodeResource(resourceSpanObj.resource); // console.debug('[SpanProcessor] resource', resource); const scopeSpansArray = resourceSpanObj.scope_spans; if (!Array.isArray(scopeSpansArray)) { continue; } for (const scopeSpan of scopeSpansArray) { const scopeSpanObj = scopeSpan; // Decode instrumentation scope if (!scopeSpanObj.scope) { continue; } const scope = this.decodeScope(scopeSpanObj.scope); // console.debug('[SpanProcessor] scope', scope); const spansArray = scopeSpanObj.spans; if (!Array.isArray(spansArray)) { continue; } for (const span of spansArray) { const processedSpan = SpanProcessor.safeDecodeOTLPSpan(span, resource, scope); if (processedSpan) { spans.push(processedSpan); } } } } } catch (error) { console.error('[SpanProcessor] Failed to batch process spans:', error); throw error; } return spans; } } exports.SpanProcessor = SpanProcessor; SpanProcessor.warningPeriodStartTime = Date.now(); SpanProcessor.WARNING_PERIOD_MS = 5 * 60 * 1000; // 5 minutes