Spaces:
Running
Running
| export interface RealEntityData { | |
| text: string; | |
| frequency: number; | |
| type: "person" | "system" | "concept" | "action" | "object"; | |
| context: string[]; | |
| traces: string[]; // trace IDs containing this entity | |
| } | |
| export interface ContentInsightData { | |
| commonPatterns: string[]; | |
| questionCount: number; | |
| commandCount: number; | |
| averageResponseLength: number; | |
| contentThemes: { theme: string; weight: number }[]; | |
| } | |
| export interface TraceContentAnalysis { | |
| entities: RealEntityData[]; | |
| relations: { from: string; to: string; type: string; traces: string[] }[]; | |
| insights: ContentInsightData; | |
| readingTime: number; | |
| complexity: number; | |
| wordCount: number; | |
| characterCount: number; | |
| timeWithoutAgentGraph: number; // Estimated time to understand without visualization | |
| timeSavedPercentage: number; // Percentage of time saved with AgentGraph | |
| comprehensionScore: number; // How well structured the information is (0-100) | |
| } | |
| // Entity detection patterns | |
| const ENTITY_PATTERNS = { | |
| person: | |
| /\b(user|customer|client|person|individual|member|admin|administrator|operator)\b/gi, | |
| system: | |
| /\b(system|api|database|service|server|application|platform|framework|engine|model|ai|bot|agent)\b/gi, | |
| action: | |
| /\b(process|analyze|generate|create|execute|run|handle|manage|respond|query|search|filter|transform|validate)\b/gi, | |
| concept: | |
| /\b(data|information|content|message|request|response|task|job|workflow|pipeline|session|context)\b/gi, | |
| object: | |
| /\b(file|document|record|item|entity|node|graph|trace|log|report|output|input|result)\b/gi, | |
| }; | |
| // Enhanced relation detection patterns for agent systems | |
| const RELATION_PATTERNS = [ | |
| // Action-based relations | |
| { | |
| pattern: /(\w+)\s+(processes|handles|executes|runs|performs)\s+(\w+)/gi, | |
| type: "processes", | |
| }, | |
| { | |
| pattern: /(\w+)\s+(generates|creates|produces|builds|constructs)\s+(\w+)/gi, | |
| type: "generates", | |
| }, | |
| { | |
| pattern: /(\w+)\s+(analyzes|examines|evaluates|reviews|assesses)\s+(\w+)/gi, | |
| type: "analyzes", | |
| }, | |
| { | |
| pattern: /(\w+)\s+(sends|transmits|delivers|provides|supplies)\s+(\w+)/gi, | |
| type: "sends", | |
| }, | |
| { | |
| pattern: /(\w+)\s+(receives|gets|obtains|retrieves|fetches)\s+(\w+)/gi, | |
| type: "receives", | |
| }, | |
| { | |
| pattern: /(\w+)\s+(transforms|converts|modifies|changes|updates)\s+(\w+)/gi, | |
| type: "transforms", | |
| }, | |
| // Dependency relations | |
| { | |
| pattern: /(\w+)\s+(depends on|requires|needs|relies on)\s+(\w+)/gi, | |
| type: "depends_on", | |
| }, | |
| { | |
| pattern: /(\w+)\s+(uses|utilizes|employs|leverages)\s+(\w+)/gi, | |
| type: "uses", | |
| }, | |
| { | |
| pattern: /(\w+)\s+(calls|invokes|triggers|activates)\s+(\w+)/gi, | |
| type: "calls", | |
| }, | |
| // Containment and hierarchy | |
| { | |
| pattern: /(\w+)\s+(contains|includes|encompasses|comprises)\s+(\w+)/gi, | |
| type: "contains", | |
| }, | |
| { | |
| pattern: /(\w+)\s+(belongs to|is part of|is within|is in)\s+(\w+)/gi, | |
| type: "belongs_to", | |
| }, | |
| { | |
| pattern: /(\w+)\s+(manages|controls|oversees|supervises)\s+(\w+)/gi, | |
| type: "manages", | |
| }, | |
| // Communication patterns | |
| { | |
| pattern: /(\w+)\s+(responds to|replies to|answers)\s+(\w+)/gi, | |
| type: "responds_to", | |
| }, | |
| { | |
| pattern: /(\w+)\s+(requests|asks for|queries|seeks)\s+(\w+)/gi, | |
| type: "requests", | |
| }, | |
| { | |
| pattern: /(\w+)\s+(notifies|informs|alerts|tells)\s+(\w+)/gi, | |
| type: "notifies", | |
| }, | |
| // Flow relations | |
| { | |
| pattern: /(\w+)\s+(flows to|goes to|moves to|passes to)\s+(\w+)/gi, | |
| type: "flows_to", | |
| }, | |
| { | |
| pattern: /(\w+)\s+(comes from|originates from|starts from)\s+(\w+)/gi, | |
| type: "comes_from", | |
| }, | |
| // Agent-specific patterns | |
| { | |
| pattern: | |
| /(user|customer)\s+(interacts with|talks to|communicates with)\s+(\w+)/gi, | |
| type: "interacts_with", | |
| }, | |
| { | |
| pattern: | |
| /(agent|system|bot)\s+(assists|helps|supports)\s+(user|customer)/gi, | |
| type: "assists", | |
| }, | |
| { | |
| pattern: | |
| /(agent|system)\s+(understands|interprets|processes)\s+(query|request|input)/gi, | |
| type: "understands", | |
| }, | |
| // Simple verb-based patterns for broader coverage | |
| { | |
| pattern: /(\w+)\s+(to|into|from|with|via|through)\s+(\w+)/gi, | |
| type: "connects_to", | |
| }, | |
| ]; | |
| const STOPWORDS = new Set([ | |
| "the", | |
| "be", | |
| "to", | |
| "of", | |
| "and", | |
| "a", | |
| "in", | |
| "that", | |
| "have", | |
| "i", | |
| "it", | |
| "for", | |
| "not", | |
| "on", | |
| "with", | |
| "he", | |
| "as", | |
| "you", | |
| "do", | |
| "at", | |
| "this", | |
| "but", | |
| "his", | |
| "by", | |
| "from", | |
| "they", | |
| "we", | |
| "say", | |
| "her", | |
| "she", | |
| "or", | |
| "an", | |
| "will", | |
| "my", | |
| "one", | |
| "all", | |
| "would", | |
| "there", | |
| "their", | |
| "what", | |
| "so", | |
| "up", | |
| "out", | |
| "if", | |
| "about", | |
| "who", | |
| "get", | |
| "which", | |
| "go", | |
| "me", | |
| "when", | |
| "make", | |
| "can", | |
| "like", | |
| "time", | |
| "no", | |
| "just", | |
| "him", | |
| "know", | |
| "take", | |
| "people", | |
| "into", | |
| "year", | |
| "your", | |
| "good", | |
| "some", | |
| "could", | |
| "them", | |
| "see", | |
| "other", | |
| "than", | |
| "then", | |
| "now", | |
| "look", | |
| "only", | |
| "come", | |
| "its", | |
| "over", | |
| "think", | |
| "also", | |
| "back", | |
| "after", | |
| "use", | |
| "two", | |
| "how", | |
| "our", | |
| "work", | |
| "first", | |
| "well", | |
| "way", | |
| "even", | |
| "new", | |
| "want", | |
| "because", | |
| "any", | |
| "these", | |
| "give", | |
| "day", | |
| "most", | |
| "us", | |
| "is", | |
| "was", | |
| "are", | |
| "been", | |
| "has", | |
| "had", | |
| "were", | |
| "said", | |
| "each", | |
| "which", | |
| "their", | |
| "them", | |
| "am", | |
| "being", | |
| "having", | |
| "does", | |
| "did", | |
| "doing", | |
| "will", | |
| "would", | |
| "should", | |
| "could", | |
| "can", | |
| "may", | |
| "might", | |
| "must", | |
| "shall", | |
| ]); | |
| export function extractEntitiesFromText( | |
| text: string, | |
| traceId: string | |
| ): RealEntityData[] { | |
| if (!text || typeof text !== "string") return []; | |
| const entities: Map<string, RealEntityData> = new Map(); | |
| Object.entries(ENTITY_PATTERNS).forEach(([type, pattern]) => { | |
| const matches = text.match(pattern); | |
| if (matches) { | |
| matches.forEach((match) => { | |
| const cleanMatch = match.toLowerCase().trim(); | |
| if (cleanMatch.length > 1 && !STOPWORDS.has(cleanMatch)) { | |
| const key = `${cleanMatch}-${type}`; | |
| if (entities.has(key)) { | |
| const entity = entities.get(key)!; | |
| entity.frequency += 1; | |
| if (!entity.traces.includes(traceId)) { | |
| entity.traces.push(traceId); | |
| } | |
| } else { | |
| entities.set(key, { | |
| text: cleanMatch, | |
| frequency: 1, | |
| type: type as RealEntityData["type"], | |
| context: [ | |
| text.substring( | |
| Math.max(0, text.indexOf(match) - 50), | |
| text.indexOf(match) + match.length + 50 | |
| ), | |
| ], | |
| traces: [traceId], | |
| }); | |
| } | |
| } | |
| }); | |
| } | |
| }); | |
| return Array.from(entities.values()); | |
| } | |
| export function analyzeTraceContent(trace: any): TraceContentAnalysis { | |
| const traceId = trace.id || trace.trace_id || "unknown"; | |
| const content = [ | |
| trace.title || "", | |
| trace.description || "", | |
| ...(trace.knowledge_graphs | |
| ?.map((kg: any) => [kg.system_name || "", kg.system_summary || ""]) | |
| .flat() || []), | |
| ] | |
| .filter(Boolean) | |
| .join(" "); | |
| if (!content) { | |
| return { | |
| entities: [], | |
| relations: [], | |
| insights: { | |
| commonPatterns: [], | |
| questionCount: 0, | |
| commandCount: 0, | |
| averageResponseLength: 0, | |
| contentThemes: [], | |
| }, | |
| readingTime: 0, | |
| complexity: 0, | |
| wordCount: 0, | |
| characterCount: 0, | |
| timeWithoutAgentGraph: 0, | |
| timeSavedPercentage: 0, | |
| comprehensionScore: 0, | |
| }; | |
| } | |
| const entities = extractEntitiesFromText(content, traceId); | |
| const relations = extractActualRelations(content, traceId); | |
| const wordCount = content | |
| .split(/\s+/) | |
| .filter((word) => word.length > 0).length; | |
| const characterCount = content.length; | |
| // Safe calculations to avoid NaN | |
| const readingTime = | |
| wordCount > 0 ? Math.max(1, Math.ceil(wordCount / 200)) : 0; | |
| const complexity = calculateTextComplexity(content); | |
| // Calculate AgentGraph value metrics | |
| const timeWithoutAgentGraph = Math.max( | |
| readingTime * 3, | |
| Math.ceil(wordCount / 100) | |
| ); // 3x longer without visualization | |
| const timeSavedPercentage = | |
| readingTime > 0 | |
| ? Math.round( | |
| ((timeWithoutAgentGraph - readingTime) / timeWithoutAgentGraph) * 100 | |
| ) | |
| : 0; | |
| // Comprehension score based on structure and entity/relation density | |
| const entityDensity = | |
| wordCount > 0 ? (entities.length / wordCount) * 1000 : 0; | |
| const relationDensity = | |
| wordCount > 0 ? (relations.length / wordCount) * 1000 : 0; | |
| const structureScore = Math.min(100, (entityDensity + relationDensity) * 10); | |
| const comprehensionScore = Math.min( | |
| 100, | |
| Math.max(0, 100 - complexity + structureScore) | |
| ); | |
| return { | |
| entities, | |
| relations, | |
| insights: analyzeContentInsights(content), | |
| readingTime, | |
| complexity, | |
| wordCount, | |
| characterCount, | |
| timeWithoutAgentGraph, | |
| timeSavedPercentage, | |
| comprehensionScore, | |
| }; | |
| } | |
| export function buildEntityFrequencyMap(traces: any[]): RealEntityData[] { | |
| const entityMap: Map<string, RealEntityData> = new Map(); | |
| traces.forEach((trace) => { | |
| const analysis = analyzeTraceContent(trace); | |
| analysis.entities.forEach((entity) => { | |
| const key = `${entity.text}-${entity.type}`; | |
| if (entityMap.has(key)) { | |
| const existing = entityMap.get(key)!; | |
| existing.frequency += entity.frequency; | |
| existing.context.push(...entity.context); | |
| entity.traces.forEach((traceId) => { | |
| if (!existing.traces.includes(traceId)) { | |
| existing.traces.push(traceId); | |
| } | |
| }); | |
| } else { | |
| entityMap.set(key, { ...entity }); | |
| } | |
| }); | |
| }); | |
| return Array.from(entityMap.values()) | |
| .sort((a, b) => b.frequency - a.frequency) | |
| .slice(0, 50); | |
| } | |
| export function extractActualRelations( | |
| text: string, | |
| traceId: string | |
| ): { from: string; to: string; type: string; traces: string[] }[] { | |
| if (!text || typeof text !== "string") return []; | |
| const relations: { | |
| from: string; | |
| to: string; | |
| type: string; | |
| traces: string[]; | |
| }[] = []; | |
| RELATION_PATTERNS.forEach(({ pattern, type }) => { | |
| let match; | |
| pattern.lastIndex = 0; // Reset regex state | |
| while ((match = pattern.exec(text)) !== null) { | |
| const from = match[1]?.toLowerCase().trim(); | |
| const to = match[3]?.toLowerCase().trim(); | |
| if ( | |
| from && | |
| to && | |
| from !== to && | |
| !STOPWORDS.has(from) && | |
| !STOPWORDS.has(to) | |
| ) { | |
| relations.push({ | |
| from, | |
| to, | |
| type, | |
| traces: [traceId], | |
| }); | |
| } | |
| } | |
| }); | |
| return relations; | |
| } | |
| function analyzeContentInsights(text: string): ContentInsightData { | |
| if (!text) { | |
| return { | |
| commonPatterns: [], | |
| questionCount: 0, | |
| commandCount: 0, | |
| averageResponseLength: 0, | |
| contentThemes: [], | |
| }; | |
| } | |
| const sentences = text.split(/[.!?]+/).filter((s) => s.trim().length > 0); | |
| const questionCount = (text.match(/\?/g) || []).length; | |
| const commandCount = sentences.filter((s) => | |
| /^(please|run|execute|start|stop|create|delete|update|process)/i.test( | |
| s.trim() | |
| ) | |
| ).length; | |
| const averageResponseLength = | |
| sentences.length > 0 | |
| ? Math.round( | |
| sentences.reduce((sum, s) => sum + s.length, 0) / sentences.length | |
| ) | |
| : 0; | |
| const themes = extractThemes(text); | |
| return { | |
| commonPatterns: findCommonPatterns(text), | |
| questionCount, | |
| commandCount, | |
| averageResponseLength, | |
| contentThemes: themes, | |
| }; | |
| } | |
| function extractThemes(text: string): { theme: string; weight: number }[] { | |
| if (!text) return []; | |
| const words = text | |
| .toLowerCase() | |
| .replace(/[^\w\s]/g, " ") | |
| .split(/\s+/) | |
| .filter((word) => word.length > 3 && !STOPWORDS.has(word)); | |
| const frequency: Record<string, number> = {}; | |
| words.forEach((word) => { | |
| frequency[word] = (frequency[word] || 0) + 1; | |
| }); | |
| const totalWords = words.length; | |
| return Object.entries(frequency) | |
| .filter(([_, count]) => count > 1) | |
| .map(([theme, count]) => ({ | |
| theme, | |
| weight: totalWords > 0 ? Math.round((count / totalWords) * 100) : 0, | |
| })) | |
| .sort((a, b) => b.weight - a.weight) | |
| .slice(0, 10); | |
| } | |
| function findCommonPatterns(text: string): string[] { | |
| if (!text) return []; | |
| const patterns = [ | |
| { pattern: /user\s+(asks|requests|wants|needs)/gi, name: "user_requests" }, | |
| { | |
| pattern: /system\s+(processes|analyzes|generates|creates)/gi, | |
| name: "system_actions", | |
| }, | |
| { pattern: /error\s+(occurred|found|detected)/gi, name: "error_handling" }, | |
| { | |
| pattern: /data\s+(processed|analyzed|transformed)/gi, | |
| name: "data_processing", | |
| }, | |
| ]; | |
| return patterns.filter((p) => p.pattern.test(text)).map((p) => p.name); | |
| } | |
| function calculateTextComplexity(text: string): number { | |
| if (!text || text.length === 0) return 0; | |
| const sentences = text.split(/[.!?]+/).filter((s) => s.trim().length > 0); | |
| const words = text.split(/\s+/).filter((w) => w.length > 0); | |
| if (sentences.length === 0 || words.length === 0) return 0; | |
| const avgWordsPerSentence = words.length / sentences.length; | |
| const avgCharsPerWord = text.replace(/\s+/g, "").length / words.length; | |
| // Ensure we don't get NaN by providing safe defaults | |
| const wordsPerSentence = isNaN(avgWordsPerSentence) ? 0 : avgWordsPerSentence; | |
| const charsPerWord = isNaN(avgCharsPerWord) ? 0 : avgCharsPerWord; | |
| const complexity = Math.min( | |
| 100, | |
| Math.round(wordsPerSentence * 0.5 + charsPerWord * 2) | |
| ); | |
| return isNaN(complexity) ? 0 : complexity; | |
| } | |