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.

639 lines (638 loc) 26 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.MigrateSpanTable1740000000000 = void 0; const typeorm_1 = require("typeorm"); const objectUtils_1 = require("../../../shared/src/utils/objectUtils"); const timeUtils_1 = require("../../../shared/src/utils/timeUtils"); const Trace_1 = require("../models/Trace"); const processor_1 = require("../otel/processor"); const asString = (value, fallback = '') => typeof value === 'string' ? value : fallback; const asNumber = (value, fallback = 0) => typeof value === 'number' ? value : fallback; const asOptionalString = (value) => typeof value === 'string' ? value : undefined; const asOptionalNumber = (value) => typeof value === 'number' ? value : undefined; const toStringOrUndefined = (value) => value !== undefined && value !== null ? String(value) : undefined; function parseJsonOrObject(value) { if (typeof value === 'string') { try { return JSON.parse(value); } catch (_a) { return {}; } } if (value && typeof value === 'object') { return value; } return {}; } function decodeStatus(status) { var _a; if (typeof status === 'string') { const statusMap = { OK: 1, ERROR: 2, UNSET: 0, }; const upperStatus = status.toUpperCase(); return { code: (_a = statusMap[upperStatus]) !== null && _a !== void 0 ? _a : 0, message: '', }; } if (status && typeof status === 'object') { const s = status; if ('code' in s && typeof s.code === 'number') { return { code: s.code, message: typeof s.message === 'string' ? s.message : '', }; } } return { code: 0, message: '' }; } function decodeEvents(eventsValue) { if (!eventsValue) return []; let eventsArray = []; if (typeof eventsValue === 'string') { try { eventsArray = JSON.parse(eventsValue); } catch (_a) { eventsArray = []; } } else if (Array.isArray(eventsValue)) { eventsArray = eventsValue; } return eventsArray.map((event) => { const e = event; const timeUnixNano = asString(e.timestamp) ? (0, timeUtils_1.encodeUnixNano)(asString(e.timestamp)) : asString(e.timeUnixNano) || asString(e.time) || '0'; return { name: asString(e.name), time: timeUnixNano, attributes: (e.attributes && typeof e.attributes === 'object' && e.attributes !== null ? e.attributes : {}), droppedAttributesCount: asNumber(e.droppedAttributesCount, 0), }; }); } function decodeResource(attributes) { const serviceName = (0, objectUtils_1.getNestedValue)(attributes, 'service.name') || (0, objectUtils_1.getNestedValue)(attributes, 'project.service_name'); const resourceAttributes = {}; if (serviceName) { resourceAttributes['service.name'] = serviceName; } const resourceKeys = [ 'service.namespace', 'service.version', 'service.instance.id', ]; for (const key of resourceKeys) { const value = (0, objectUtils_1.getNestedValue)(attributes, key); if (value !== undefined) { resourceAttributes[key] = value; } } return { attributes: resourceAttributes, }; } function decodeScope() { return { name: 'agentscope', version: '1.0.7', attributes: {}, }; } function getConversationId(attributes, record) { return (toStringOrUndefined((0, objectUtils_1.getNestedValue)(attributes, 'gen_ai.conversation.id')) || toStringOrUndefined((0, objectUtils_1.getNestedValue)(attributes, 'project.run_id')) || toStringOrUndefined(record.conversationId) || toStringOrUndefined(record.conversation_id) || 'unknown'); } function getSpanId(record, attributes) { const spanId = toStringOrUndefined(record.id) || toStringOrUndefined(record.spanId) || toStringOrUndefined((0, objectUtils_1.getNestedValue)(attributes, 'span.id')) || toStringOrUndefined((0, objectUtils_1.getNestedValue)(attributes, 'spanId')); if (!spanId) { throw new Error(`Cannot determine spanId for record. Record has no 'id' field: ${JSON.stringify(record)}`); } return spanId; } // Helper methods to extract key fields (similar to SpanDao) function extractServiceName(resource) { const value = (0, objectUtils_1.getNestedValue)(resource.attributes, 'service.name'); return typeof value === 'string' ? value : undefined; } function extractOperationName(attributes) { const value = (0, objectUtils_1.getNestedValue)(attributes, 'gen_ai.operation.name'); return typeof value === 'string' ? value : undefined; } function extractInstrumentationName(scope) { // Try to get from attributes first (for backward compatibility) const valueFromAttributes = (0, objectUtils_1.getNestedValue)(scope.attributes, 'server.name'); if (typeof valueFromAttributes === 'string') { return valueFromAttributes; } // Fallback to scope.name return scope.name; } function extractInstrumentationVersion(scope) { // Try to get from attributes first (for backward compatibility) const valueFromAttributes = (0, objectUtils_1.getNestedValue)(scope.attributes, 'server.version'); if (typeof valueFromAttributes === 'string') { return valueFromAttributes; } // Fallback to scope.version return scope.version; } function extractModel(attributes) { const value = (0, objectUtils_1.getNestedValue)(attributes, 'gen_ai.request.model'); return typeof value === 'string' ? value : undefined; } function extractInputTokens(attributes) { const value = (0, objectUtils_1.getNestedValue)(attributes, 'gen_ai.usage.input_tokens'); return typeof value === 'number' ? value : undefined; } function extractOutputTokens(attributes) { const value = (0, objectUtils_1.getNestedValue)(attributes, 'gen_ai.usage.output_tokens'); return typeof value === 'number' ? value : undefined; } function calculateTotalTokens(inputTokens, outputTokens) { // If both are numbers, return their sum if (typeof inputTokens === 'number' && typeof outputTokens === 'number') { return inputTokens + outputTokens; } // If only inputTokens is available, return it if (typeof inputTokens === 'number') { return inputTokens; } // If only outputTokens is available, return it if (typeof outputTokens === 'number') { return outputTokens; } // If neither is available, return undefined return undefined; } function convertOldRecordToSpanTable(oldRecord) { const r = oldRecord; let attributes = parseJsonOrObject(r.attributes); const convertedResult = processor_1.SpanProcessor.convertOldProtocolToNew(attributes, { name: asString(r.name), }); const spanName = convertedResult.span_name || asString(r.name); attributes = convertedResult.attributes || attributes; const startTimeUnixNano = asString(r.startTime) ? (0, timeUtils_1.encodeUnixNano)(asString(r.startTime)) : asString(r.startTimeUnixNano, '0'); const endTimeUnixNano = asString(r.endTime) ? (0, timeUtils_1.encodeUnixNano)(asString(r.endTime)) : asString(r.endTimeUnixNano, '0'); const latencyNs = asNumber(r.latencyMs, 0) > 0 ? asNumber(r.latencyMs) * 1000000 : asNumber(r.latencyNs, 0) > 0 ? asNumber(r.latencyNs) : (0, timeUtils_1.getTimeDifferenceNano)(startTimeUnixNano, endTimeUnixNano); const statusObj = decodeStatus(r.status); const statusMessage = asOptionalString(r.statusMessage); const status = statusMessage ? Object.assign(Object.assign({}, statusObj), { message: statusMessage }) : statusObj; const events = decodeEvents(r.events); const resource = decodeResource(attributes); const scope = decodeScope(); const conversationId = getConversationId(attributes, r); const spanId = getSpanId(r, attributes); // Extract key fields for indexing const serviceName = extractServiceName(resource); const operationName = extractOperationName(attributes); const instrumentationName = extractInstrumentationName(scope); const instrumentationVersion = extractInstrumentationVersion(scope); const model = extractModel(attributes); const inputTokens = extractInputTokens(attributes); const outputTokens = extractOutputTokens(attributes); const totalTokens = calculateTotalTokens(inputTokens, outputTokens); const statusCode = statusObj.code || 0; const span = new Trace_1.SpanTable(); Object.assign(span, { id: String(spanId), traceId: toStringOrUndefined(r.traceId) || '', spanId: String(spanId), traceState: toStringOrUndefined(r.traceState), parentSpanId: toStringOrUndefined(r.parentSpanId), flags: asOptionalNumber(r.flags), name: spanName, kind: asNumber(r.kind, 0), startTimeUnixNano: startTimeUnixNano, endTimeUnixNano: endTimeUnixNano, attributes: attributes, droppedAttributesCount: 0, events: events, droppedEventsCount: 0, links: [], droppedLinksCount: 0, status: status, resource: resource, scope: scope, statusCode: statusCode, serviceName: serviceName, operationName: operationName, instrumentationName: instrumentationName, instrumentationVersion: instrumentationVersion, model: model, inputTokens: inputTokens, outputTokens: outputTokens, totalTokens: totalTokens, conversationId: conversationId, latencyNs: latencyNs, }); return span; } /** * Migration: Migrate old span_table structure to new structure * * Tasks: * 1. Check if table exists and if migration has already been completed * 2. Drop related views * 3. Backup old table * 4. Create new table structure * 5. Migrate historical data * 6. Delete backup table */ class MigrateSpanTable1740000000000 { constructor() { this.name = 'MigrateSpanTable1740000000000'; } up(queryRunner) { return __awaiter(this, void 0, void 0, function* () { var _a, _b; console.log('Starting migration: Migrating SpanTable structure...'); const tableName = 'span_table'; const viewName = 'model_invocation_view'; const oldTableName = 'span_table_old_backup'; // ======================================== // Step 1: Check if table exists // ======================================== console.log('Step 1: Checking table structure...'); if (!(yield queryRunner.hasTable(tableName))) { console.log('⏭️ span_table does not exist. Skipping migration.'); return; } const table = yield queryRunner.getTable(tableName); if (!table) { console.log('Unable to get table structure. Skipping migration.'); return; } // Check if migration has already been completed // New structure has spanId and instrumentationVersion columns const hasSpanIdColumn = table.findColumnByName('spanId') !== undefined; const hasInstrumentationVersion = table.findColumnByName('instrumentationVersion') !== undefined; if (hasSpanIdColumn && hasInstrumentationVersion) { console.log('✅ Table already has new structure. Migration already completed.'); return; } // ======================================== // Step 2: Drop related views // ======================================== console.log('Step 2: Dropping related views...'); try { yield queryRunner.query(`DROP VIEW IF EXISTS ${viewName}`); console.log(`Dropped view ${viewName}`); } catch (error) { console.warn(`Error dropping view (may not exist):`, error); } try { yield queryRunner.query(`DELETE FROM typeorm_metadata WHERE type = 'VIEW' AND name = ?`, [viewName]); } catch (_c) { // Ignore error, table may not exist } // ======================================== // Step 3: Backup old table // ======================================== console.log('Step 3: Backing up old table...'); // If backup table already exists, drop it first if (yield queryRunner.hasTable(oldTableName)) { yield queryRunner.dropTable(oldTableName, true); } yield queryRunner.query(`ALTER TABLE "${tableName}" RENAME TO "${oldTableName}"`); console.log(`Renamed table ${tableName} -> ${oldTableName}`); // ======================================== // Step 4: Create new table structure // ======================================== console.log('Step 4: Creating new table structure...'); yield queryRunner.createTable(new typeorm_1.Table({ name: tableName, columns: [ { name: 'id', type: 'varchar', isPrimary: true, isNullable: false, }, { name: 'traceId', type: 'varchar', isNullable: false, }, { name: 'spanId', type: 'varchar', isNullable: false, }, { name: 'traceState', type: 'varchar', isNullable: true, }, { name: 'parentSpanId', type: 'varchar', isNullable: true, }, { name: 'flags', type: 'integer', isNullable: true, }, { name: 'name', type: 'varchar', isNullable: false, }, { name: 'kind', type: 'integer', isNullable: false, }, { name: 'startTimeUnixNano', type: 'varchar', isNullable: false, }, { name: 'endTimeUnixNano', type: 'varchar', isNullable: false, }, { name: 'attributes', type: 'json', isNullable: false, }, { name: 'droppedAttributesCount', type: 'integer', isNullable: true, }, { name: 'events', type: 'json', isNullable: true, }, { name: 'droppedEventsCount', type: 'integer', isNullable: true, }, { name: 'links', type: 'json', isNullable: true, }, { name: 'droppedLinksCount', type: 'integer', isNullable: true, }, { name: 'status', type: 'json', isNullable: false, }, { name: 'resource', type: 'json', isNullable: false, }, { name: 'scope', type: 'json', isNullable: false, }, { name: 'statusCode', type: 'integer', isNullable: true, }, { name: 'serviceName', type: 'varchar', isNullable: true, }, { name: 'operationName', type: 'varchar', isNullable: true, }, { name: 'instrumentationName', type: 'varchar', isNullable: true, }, { name: 'instrumentationVersion', type: 'varchar', isNullable: true, }, { name: 'model', type: 'varchar', isNullable: true, }, { name: 'inputTokens', type: 'integer', isNullable: true, }, { name: 'outputTokens', type: 'integer', isNullable: true, }, { name: 'totalTokens', type: 'integer', isNullable: true, }, { name: 'conversationId', type: 'varchar', isNullable: true, }, { name: 'latencyNs', type: 'float', isNullable: false, }, ], indices: [ { name: 'IDX_span_traceId', columnNames: ['traceId'], }, { name: 'IDX_span_spanId', columnNames: ['spanId'], }, { name: 'IDX_span_parentSpanId', columnNames: ['parentSpanId'], }, { name: 'IDX_span_startTimeUnixNano', columnNames: ['startTimeUnixNano'], }, { name: 'IDX_span_statusCode', columnNames: ['statusCode'], }, { name: 'IDX_span_latencyNs', columnNames: ['latencyNs'], }, { name: 'IDX_span_serviceName', columnNames: ['serviceName'], }, { name: 'IDX_span_operationName', columnNames: ['operationName'], }, { name: 'IDX_span_instrumentationName', columnNames: ['instrumentationName'], }, { name: 'IDX_span_model', columnNames: ['model'], }, { name: 'IDX_span_inputTokens', columnNames: ['inputTokens'], }, { name: 'IDX_span_outputTokens', columnNames: ['outputTokens'], }, { name: 'IDX_span_totalTokens', columnNames: ['totalTokens'], }, { name: 'IDX_span_conversationId', columnNames: ['conversationId'], }, ], }), true); console.log('New table structure created successfully'); // ======================================== // Step 5: Migrate historical data // ======================================== console.log('Step 5: Migrating historical data...'); const oldRecords = yield queryRunner.query(`SELECT * FROM ${oldTableName}`); if (oldRecords.length === 0) { console.log('No data to migrate. Dropping backup table'); yield queryRunner.dropTable(oldTableName, true); console.log('✅ Migration completed!'); return; } console.log(`Found ${oldRecords.length} records to migrate`); let migratedCount = 0; let errorCount = 0; const batchSize = 100; for (let i = 0; i < oldRecords.length; i += batchSize) { const batch = oldRecords.slice(i, i + batchSize); const spanTableArray = []; for (const oldRecord of batch) { try { const spanTable = convertOldRecordToSpanTable(oldRecord); spanTableArray.push(spanTable); } catch (error) { console.error(`Failed to convert record (id: ${(oldRecord === null || oldRecord === void 0 ? void 0 : oldRecord.id) || 'unknown'}):`, error); errorCount++; } } if (spanTableArray.length > 0) { try { // Use queryRunner.manager to save data yield queryRunner.manager.save(Trace_1.SpanTable, spanTableArray); migratedCount += spanTableArray.length; } catch (error) { console.error(`Batch save failed:`, error); errorCount += spanTableArray.length; } } if ((i + batchSize) % 1000 === 0 || i + batchSize >= oldRecords.length) { console.log(`Progress: Migrated ${Math.min(i + batchSize, oldRecords.length)}/${oldRecords.length} records`); } } console.log(`Data migration completed. Success: ${migratedCount}, Failed: ${errorCount}`); // ======================================== // Step 6: Validate and drop backup table // ======================================== console.log('Step 6: Validating data and dropping backup table...'); const newTableCount = yield queryRunner.query(`SELECT COUNT(*) as count FROM ${tableName}`); const count = ((_a = newTableCount[0]) === null || _a === void 0 ? void 0 : _a.count) || ((_b = newTableCount[0]) === null || _b === void 0 ? void 0 : _b.COUNT) || 0; if (count !== migratedCount) { console.warn(`Warning: New table record count (${count}) does not match migrated count (${migratedCount})`); } yield queryRunner.dropTable(oldTableName, true); console.log('Backup table dropped'); console.log('✅ Migration completed!'); }); } down(queryRunner) { return __awaiter(this, void 0, void 0, function* () { console.log('Starting migration rollback...'); const tableName = 'span_table'; const oldTableName = 'span_table_old_backup'; // If backup table exists, restore it if (yield queryRunner.hasTable(oldTableName)) { // Drop new table if (yield queryRunner.hasTable(tableName)) { yield queryRunner.dropTable(tableName, true); } // Restore old table yield queryRunner.query(`ALTER TABLE "${oldTableName}" RENAME TO "${tableName}"`); console.log('Restored old table structure'); } else { console.warn('Backup table does not exist. Cannot rollback'); } console.log('✅ Rollback completed'); }); } } exports.MigrateSpanTable1740000000000 = MigrateSpanTable1740000000000;