@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
JavaScript
"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