UNPKG

procedure-memory-mcp-server

Version:

Procedure memory management system with MCP integration using Zep

274 lines • 13.7 kB
import { ZepClient } from "@getzep/zep-cloud"; const client = new ZepClient({ apiKey: process.env.ZEP_API_KEY || "z_1dWlkIjoiMTA5OTk0MTItOGQ0MS00ZTcwLTlhOWItMzVhNTYyNWJiMDcwIn0.Belvq0qUi40Fqg2pNDkLKYhRod5OeMnZh2SMe4TlgH_vllqqnKBdl4vCdUFX47JIK9Wluu6jnPpYL6SZEtEUIg" }); const RELEVANCE_THRESHOLD = 0.3; // Minimum score to consider const HIGH_QUALITY_THRESHOLD = 0.5; // Threshold for strong references export async function findMostRelevantEpisodes(query, options = {}) { const { n = 3, limit = 20, relevanceThreshold = RELEVANCE_THRESHOLD, includeMetrics = false } = options; console.log(`\nšŸ” Searching for top ${n} most relevant episodes for query: "${query}"\n`); try { // Step 1: Search all three scopes in parallel for better coverage console.log("šŸ“Š Step 1: Searching across edges, nodes, and episodes..."); const [edgeResults, nodeResults, episodeResults] = await Promise.all([ client.graph.search({ query: query, userId: process.env.USER_ID || "syiaOpsMemory", scope: "edges", limit: limit }), client.graph.search({ query: query, userId: process.env.USER_ID || "syiaOpsMemory", scope: "nodes", limit: limit }), client.graph.search({ query: query, userId: process.env.USER_ID || "syiaOpsMemory", scope: "episodes", limit: limit }) ]); console.log(`Found: ${edgeResults.edges?.length || 0} edges, ${nodeResults.nodes?.length || 0} nodes, ${episodeResults.episodes?.length || 0} episodes`); // Step 2: Build episode map with improved scoring const episodeMap = new Map(); // Process direct episode matches first (these are most relevant) episodeResults.episodes?.forEach((episode, index) => { const rankScore = Math.exp(-2 * index / limit); // Exponential decay if (rankScore >= relevanceThreshold) { episodeMap.set(episode.uuid, { uuid: episode.uuid, referenceCount: 1, rank: rankScore * 1.5, // Boost direct matches maxSingleScore: rankScore * 1.5, avgScore: rankScore * 1.5, content: episode.content, source: episode.source, createdAt: episode.createdAt, sources: { fromEdges: [], fromNodes: [], fromDirect: [{ content: episode.content?.substring(0, 100) || '', rank: rankScore * 1.5 }] } }); } }); // Process edges with threshold filtering edgeResults.edges?.forEach((edge, index) => { const rankScore = Math.exp(-2 * index / limit); // Exponential decay if (rankScore < relevanceThreshold) return; // Skip low-relevance matches if (edge.episodes && Array.isArray(edge.episodes)) { edge.episodes.forEach((episodeUuid) => { if (!episodeMap.has(episodeUuid)) { episodeMap.set(episodeUuid, { uuid: episodeUuid, referenceCount: 0, rank: 0, maxSingleScore: 0, avgScore: 0, sources: { fromEdges: [], fromNodes: [], fromDirect: [] } }); } const episode = episodeMap.get(episodeUuid); episode.referenceCount++; episode.rank += rankScore; episode.maxSingleScore = Math.max(episode.maxSingleScore, rankScore); episode.sources.fromEdges.push({ type: edge.name, fact: edge.fact, rank: rankScore }); }); } }); // Process nodes with threshold filtering nodeResults.nodes?.forEach((node, index) => { const rankScore = Math.exp(-2 * index / limit); // Exponential decay if (rankScore < relevanceThreshold) return; // Skip low-relevance matches const nodeEpisodes = node.episodes || node.attributes?.episodes || []; if (Array.isArray(nodeEpisodes)) { nodeEpisodes.forEach((episodeUuid) => { if (!episodeMap.has(episodeUuid)) { episodeMap.set(episodeUuid, { uuid: episodeUuid, referenceCount: 0, rank: 0, maxSingleScore: 0, avgScore: 0, sources: { fromEdges: [], fromNodes: [], fromDirect: [] } }); } const episode = episodeMap.get(episodeUuid); episode.referenceCount++; episode.rank += rankScore; episode.maxSingleScore = Math.max(episode.maxSingleScore, rankScore); episode.sources.fromNodes.push({ name: node.name, summary: node.summary || '', rank: rankScore }); }); } }); // Step 3: Calculate average scores and apply improved ranking episodeMap.forEach(episode => { const totalSources = episode.sources.fromEdges.length + episode.sources.fromNodes.length + episode.sources.fromDirect.length; episode.avgScore = totalSources > 0 ? episode.rank / totalSources : 0; }); // Sort by quality-first scoring const sortedEpisodes = Array.from(episodeMap.values()) .map(episode => { // Calculate logarithmic dampening for reference count const logCount = Math.log(episode.referenceCount + 1); // Quality-first composite score const qualityScore = episode.maxSingleScore * 0.5 + // Best match quality (50%) episode.avgScore * 0.3 + // Average quality (30%) Math.min(logCount / 3, 1) * 0.2; // Dampened quantity (20%) return { ...episode, qualityScore }; }) .sort((a, b) => b.qualityScore - a.qualityScore); if (sortedEpisodes.length === 0) { console.log("\nāŒ No episodes found for this query"); const markdownContent = { type: "text", text: `# Procedure Search Results\n\n**Query**: "${query}"\n\n## No Results Found\n\nNo procedures were found matching your query with a relevance score above ${(relevanceThreshold * 100).toFixed(0)}%.\n\n### Suggestions:\n- Try using different keywords\n- Use more general terms\n- Check for spelling errors`, title: `Procedure Search: ${query}`, format: "markdown" }; const jsonContent = { type: "text", text: JSON.stringify({ query: query, message: "No procedures found for this query", relevanceThreshold: relevanceThreshold, episodes: [] }, null, 2), title: "Procedure Search Results (JSON)", format: "json" }; return [markdownContent, jsonContent]; } // Get top N episodes const topEpisodes = sortedEpisodes.slice(0, Math.min(n, sortedEpisodes.length)); // Display results with quality metrics console.log(`\nšŸ† TOP ${topEpisodes.length} MOST RELEVANT EPISODES (Quality-First Ranking):`); topEpisodes.forEach((episode, index) => { console.log(`\n${index + 1}. Episode ${episode.uuid}`); console.log(` Quality Score: ${episode.qualityScore.toFixed(3)}`); console.log(` Best Match: ${episode.maxSingleScore.toFixed(3)}`); console.log(` Avg Score: ${episode.avgScore.toFixed(3)}`); console.log(` References: ${episode.referenceCount} (above threshold)`); // Show only high-quality references const topRefs = [...episode.sources.fromEdges, ...episode.sources.fromNodes] .filter(ref => ref.rank >= HIGH_QUALITY_THRESHOLD) .sort((a, b) => b.rank - a.rank) .slice(0, 2); if (topRefs.length > 0) { console.log(` Strong References (≄0.5):`); topRefs.forEach(ref => { const preview = 'fact' in ref ? ref.fact : ref.summary; console.log(` • ${preview.substring(0, 60)}... (${ref.rank.toFixed(2)})`); }); } }); // Enrich episodes with any missing content const enrichedEpisodes = []; for (const episode of topEpisodes) { // If we don't have content from direct search, try to fetch it if (!episode.content && episodeResults.episodes) { const directMatch = episodeResults.episodes.find((ep) => ep.uuid === episode.uuid); if (directMatch) { episode.content = directMatch.content; episode.source = directMatch.source; episode.createdAt = directMatch.createdAt; } } enrichedEpisodes.push({ uuid: episode.uuid, rank: episode.qualityScore, referenceCount: episode.referenceCount, content: episode.content, source: episode.source, createdAt: episode.createdAt, topReferences: [...episode.sources.fromEdges, ...episode.sources.fromNodes] .sort((a, b) => b.rank - a.rank) .slice(0, 3), metrics: { maxSingleScore: episode.maxSingleScore, avgScore: episode.avgScore, directMatch: episode.sources.fromDirect.length > 0 } }); } // Format results based on content availability const formattedResults = enrichedEpisodes.map((episode, index) => { const sections = []; // Episode header sections.push(`## ${index + 1}. Episode ${episode.uuid}`); sections.push(`**Relevance Score**: ${(episode.rank * 100).toFixed(1)}%`); // Content section if (episode.content) { sections.push("\n### Procedure Content"); sections.push(episode.content); } // Metadata section if (episode.source || episode.createdAt) { sections.push("\n### Metadata"); if (episode.source) sections.push(`- **Source**: ${episode.source}`); if (episode.createdAt) sections.push(`- **Created**: ${new Date(episode.createdAt).toLocaleString()}`); } // References section (only show if high-quality) const strongRefs = episode.topReferences.filter((ref) => ref.rank >= HIGH_QUALITY_THRESHOLD); if (strongRefs.length > 0) { sections.push("\n### Key References"); strongRefs.forEach((ref) => { const preview = ref.fact || ref.summary || ""; sections.push(`- ${preview.substring(0, 100)}${preview.length > 100 ? '...' : ''}`); }); } // Quality metrics (only if includeMetrics is true or if no content) if (!episode.content || includeMetrics) { sections.push("\n### Quality Metrics"); sections.push(`- Direct match: ${episode.metrics.directMatch ? 'Yes' : 'No'}`); sections.push(`- Best match score: ${(episode.metrics.maxSingleScore * 100).toFixed(1)}%`); sections.push(`- Average score: ${(episode.metrics.avgScore * 100).toFixed(1)}%`); sections.push(`- Reference count: ${episode.referenceCount}`); } return sections.join("\n"); }); // Create both formatted text and structured JSON responses const markdownContent = { type: "text", text: `# Procedure Search Results\n\n**Query**: "${query}"\n**Total Episodes Found**: ${sortedEpisodes.length}\n**Showing Top**: ${enrichedEpisodes.length}\n\n${formattedResults.join("\n\n---\n\n")}`, title: `Procedure Search: ${query}`, format: "markdown" }; const jsonContent = { type: "text", text: JSON.stringify({ query: query, totalEpisodesFound: sortedEpisodes.length, relevanceThreshold: relevanceThreshold, topEpisodes: enrichedEpisodes }, null, 2), title: `Procedure Search Results (JSON): ${query}`, format: "json" }; return [markdownContent, jsonContent]; } catch (error) { console.error("āŒ Error:", error); throw error; } } //# sourceMappingURL=procedureSearch.js.map