Skip to main content
Glama
graph-of-thought.js19.6 kB
/** * Graph of Thought Pattern Handler Implementation * * Implements non-hierarchical connections between thoughts with support * for complex reasoning patterns including cycles and multiple connections. */ import { v4 as uuidv4 } from 'uuid'; export class GraphOfThoughtHandler { defaultConfig = { maxNodes: 100, maxEdges: 300, allowCycles: true, edgeWeightThreshold: 0.1, analysisAlgorithms: ['centrality', 'clustering'], defaultNodeType: 'hypothesis', autoPruneWeakEdges: false }; /** * Initialize a new Graph of Thought session */ initializeSession(config) { const sessionId = uuidv4(); const timestamp = new Date().toISOString(); return { sessionId, patternType: 'graph', iteration: 0, nextStepNeeded: true, createdAt: timestamp, updatedAt: timestamp, nodes: new Map(), edges: new Map(), entryNodeIds: [], explorationPath: [], config: { ...this.defaultConfig, ...config }, stats: { totalNodes: 0, totalEdges: 0, nodesByType: {}, edgesByType: {}, averageDegree: 0, density: 0, connectedComponents: 0 } }; } /** * Add a node to the graph */ addNode(nodeData, session) { if (session.config.maxNodes && session.nodes.size >= session.config.maxNodes) { throw new Error(`Maximum nodes limit ${session.config.maxNodes} reached`); } const nodeId = uuidv4(); const node = { id: nodeId, content: nodeData.content, timestamp: new Date().toISOString(), nodeType: nodeData.nodeType, strength: nodeData.strength, incomingEdges: [], outgoingEdges: [], metadata: nodeData.metadata }; session.nodes.set(nodeId, node); // Update stats session.stats.totalNodes++; session.stats.nodesByType[node.nodeType] = (session.stats.nodesByType[node.nodeType] || 0) + 1; // Add to entry nodes if it's the first or explicitly marked if (session.entryNodeIds.length === 0) { session.entryNodeIds.push(nodeId); } return node; } /** * Connect two nodes with an edge */ connectNodes(sourceId, targetId, edgeType, weight, session) { const sourceNode = session.nodes.get(sourceId); const targetNode = session.nodes.get(targetId); if (!sourceNode || !targetNode) { throw new Error('Source or target node not found'); } if (session.config.maxEdges && session.edges.size >= session.config.maxEdges) { throw new Error(`Maximum edges limit ${session.config.maxEdges} reached`); } // Check for cycles if not allowed if (!session.config.allowCycles && this.wouldCreateCycle(sourceId, targetId, session)) { throw new Error('Edge would create a cycle, which is not allowed'); } const edgeId = uuidv4(); const edge = { id: edgeId, sourceId, targetId, edgeType, weight, metadata: { createdAt: new Date().toISOString() } }; session.edges.set(edgeId, edge); // Update node connections sourceNode.outgoingEdges.push(edgeId); targetNode.incomingEdges.push(edgeId); // Update stats session.stats.totalEdges++; session.stats.edgesByType[edgeType] = (session.stats.edgesByType[edgeType] || 0) + 1; this.updateGraphStats(session); // Auto-prune weak edges if enabled if (session.config.autoPruneWeakEdges && session.config.edgeWeightThreshold && weight < session.config.edgeWeightThreshold) { this.removeEdge(edgeId, session); throw new Error(`Edge weight ${weight} below threshold ${session.config.edgeWeightThreshold}`); } return edge; } /** * Find paths between two nodes */ findPaths(startId, endId, session, maxPaths = 10) { const paths = []; const visited = new Set(); const dfs = (currentId, path) => { if (paths.length >= maxPaths) return; if (currentId === endId) { paths.push([...path]); return; } visited.add(currentId); const node = session.nodes.get(currentId); if (node) { for (const edgeId of node.outgoingEdges) { const edge = session.edges.get(edgeId); if (edge && !visited.has(edge.targetId)) { dfs(edge.targetId, [...path, edge.targetId]); } } } visited.delete(currentId); }; dfs(startId, [startId]); return paths; } /** * Get node neighbors */ getNeighbors(nodeId, direction, session) { const node = session.nodes.get(nodeId); if (!node) return []; const neighbors = new Set(); if (direction === 'incoming' || direction === 'both') { node.incomingEdges.forEach(edgeId => { const edge = session.edges.get(edgeId); if (edge) neighbors.add(edge.sourceId); }); } if (direction === 'outgoing' || direction === 'both') { node.outgoingEdges.forEach(edgeId => { const edge = session.edges.get(edgeId); if (edge) neighbors.add(edge.targetId); }); } return Array.from(neighbors); } /** * Calculate node centrality (PageRank-like algorithm) */ calculateCentrality(session) { const centrality = new Map(); const damping = 0.85; const iterations = 100; // Initialize all nodes with equal centrality session.nodes.forEach((_, nodeId) => { centrality.set(nodeId, 1.0 / session.nodes.size); }); // Power iteration for (let i = 0; i < iterations; i++) { const newCentrality = new Map(); session.nodes.forEach((node, nodeId) => { let rank = (1 - damping) / session.nodes.size; // Sum contributions from incoming nodes node.incomingEdges.forEach(edgeId => { const edge = session.edges.get(edgeId); if (edge) { const sourceNode = session.nodes.get(edge.sourceId); if (sourceNode) { const sourceRank = centrality.get(edge.sourceId) || 0; const outDegree = sourceNode.outgoingEdges.length || 1; rank += damping * edge.weight * sourceRank / outDegree; } } }); newCentrality.set(nodeId, rank); }); centrality.clear(); newCentrality.forEach((value, key) => centrality.set(key, value)); } // Store in session metrics if (!session.analysisMetrics) { session.analysisMetrics = { centrality: new Map() }; } session.analysisMetrics.centrality = centrality; return centrality; } /** * Detect communities/clusters using simple connected components */ detectCommunities(session) { const clusters = []; const visited = new Set(); let clusterId = 0; session.nodes.forEach((_, nodeId) => { if (!visited.has(nodeId)) { const cluster = this.exploreCluster(nodeId, session, visited); if (cluster.length > 0) { clusters.push({ id: `cluster-${clusterId++}`, nodeIds: cluster, cohesion: this.calculateClusterCohesion(cluster, session), centroidNodeId: this.findClusterCentroid(cluster, session) }); } } }); // Store in session metrics if (!session.analysisMetrics) { session.analysisMetrics = { centrality: new Map() }; } session.analysisMetrics.clusters = clusters; return clusters; } /** * Find contradictions in the graph */ findContradictions(session) { const contradictions = []; session.edges.forEach(edge => { if (edge.edgeType === 'contradicts') { contradictions.push({ nodeIds: [edge.sourceId, edge.targetId], description: `Node ${edge.sourceId} contradicts ${edge.targetId}` }); } }); // Find cycles that might indicate contradictions const cycles = this.findCycles(session); cycles.forEach(cycle => { if (cycle.length > 2) { contradictions.push({ nodeIds: cycle, description: `Potential circular reasoning detected` }); } }); return contradictions; } /** * Merge similar nodes */ mergeNodes(nodeIds, session) { if (nodeIds.length < 2) { throw new Error('Need at least 2 nodes to merge'); } const nodes = nodeIds.map(id => session.nodes.get(id)).filter(Boolean); if (nodes.length !== nodeIds.length) { throw new Error('Some nodes not found'); } // Create merged node const mergedContent = nodes.map(n => n.content).join(' | '); const avgStrength = nodes.reduce((sum, n) => sum + n.strength, 0) / nodes.length; const mergedNode = this.addNode({ content: `Merged: ${mergedContent}`, nodeType: nodes[0].nodeType, strength: avgStrength, metadata: {} }, session); // Redirect all edges nodeIds.forEach(nodeId => { const node = session.nodes.get(nodeId); if (node) { // Redirect incoming edges node.incomingEdges.forEach(edgeId => { const edge = session.edges.get(edgeId); if (edge && edge.sourceId !== mergedNode.id) { this.connectNodes(edge.sourceId, mergedNode.id, edge.edgeType, edge.weight, session); } }); // Redirect outgoing edges node.outgoingEdges.forEach(edgeId => { const edge = session.edges.get(edgeId); if (edge && edge.targetId !== mergedNode.id) { this.connectNodes(mergedNode.id, edge.targetId, edge.edgeType, edge.weight, session); } }); // Remove original node this.removeNode(nodeId, session); } }); return mergedNode; } /** * Export to sequential thinking format */ exportToSequentialFormat(session) { const thoughts = []; // Find a path through the graph (topological sort-like) const visited = new Set(); const sortedNodes = []; const visit = (nodeId) => { if (visited.has(nodeId)) return; visited.add(nodeId); const node = session.nodes.get(nodeId); if (node) { // Visit dependencies first node.incomingEdges.forEach(edgeId => { const edge = session.edges.get(edgeId); if (edge) visit(edge.sourceId); }); sortedNodes.push(node); } }; // Start from entry nodes session.entryNodeIds.forEach(visit); // Visit any unvisited nodes session.nodes.forEach((_, nodeId) => visit(nodeId)); // Convert to thoughts sortedNodes.forEach((node, index) => { thoughts.push({ thought: `[${node.nodeType}] ${node.content}`, thoughtNumber: index + 1, totalThoughts: sortedNodes.length, nextThoughtNeeded: index < sortedNodes.length - 1 }); }); return thoughts; } /** * Import from sequential thinking format */ importFromSequentialFormat(thoughts) { const session = this.initializeSession(); const nodeMap = new Map(); thoughts.forEach((thought, index) => { const node = this.addNode({ content: thought.thought, nodeType: 'hypothesis', strength: 0.5 }, session); nodeMap.set(index, node.id); // Connect to previous node if (index > 0) { const prevNodeId = nodeMap.get(index - 1); if (prevNodeId) { this.connectNodes(prevNodeId, node.id, 'leads-to', 0.8, session); } } }); return session; } // Helper methods wouldCreateCycle(sourceId, targetId, session) { // Simple DFS to check if targetId can reach sourceId const visited = new Set(); const canReach = (fromId, toId) => { if (fromId === toId) return true; if (visited.has(fromId)) return false; visited.add(fromId); const node = session.nodes.get(fromId); if (node) { for (const edgeId of node.outgoingEdges) { const edge = session.edges.get(edgeId); if (edge && canReach(edge.targetId, toId)) { return true; } } } return false; }; return canReach(targetId, sourceId); } updateGraphStats(session) { const nodeCount = session.nodes.size; const edgeCount = session.edges.size; if (nodeCount > 0) { // Average degree let totalDegree = 0; session.nodes.forEach(node => { totalDegree += node.incomingEdges.length + node.outgoingEdges.length; }); session.stats.averageDegree = totalDegree / nodeCount; // Density const maxPossibleEdges = nodeCount * (nodeCount - 1); session.stats.density = maxPossibleEdges > 0 ? edgeCount / maxPossibleEdges : 0; } } exploreCluster(startNodeId, session, visited) { const cluster = []; const queue = [startNodeId]; while (queue.length > 0) { const nodeId = queue.shift(); if (visited.has(nodeId)) continue; visited.add(nodeId); cluster.push(nodeId); const neighbors = this.getNeighbors(nodeId, 'both', session); neighbors.forEach(neighbor => { if (!visited.has(neighbor)) { queue.push(neighbor); } }); } return cluster; } calculateClusterCohesion(nodeIds, session) { if (nodeIds.length <= 1) return 1; let internalEdges = 0; let possibleEdges = nodeIds.length * (nodeIds.length - 1); const nodeSet = new Set(nodeIds); nodeIds.forEach(nodeId => { const node = session.nodes.get(nodeId); if (node) { node.outgoingEdges.forEach(edgeId => { const edge = session.edges.get(edgeId); if (edge && nodeSet.has(edge.targetId)) { internalEdges++; } }); } }); return possibleEdges > 0 ? internalEdges / possibleEdges : 0; } findClusterCentroid(nodeIds, session) { const centrality = this.calculateCentrality(session); let maxCentrality = -1; let centroid = nodeIds[0]; nodeIds.forEach(nodeId => { const score = centrality.get(nodeId) || 0; if (score > maxCentrality) { maxCentrality = score; centroid = nodeId; } }); return centroid; } findCycles(session) { const cycles = []; const visited = new Set(); const recursionStack = new Set(); const dfs = (nodeId, path) => { visited.add(nodeId); recursionStack.add(nodeId); const node = session.nodes.get(nodeId); if (node) { for (const edgeId of node.outgoingEdges) { const edge = session.edges.get(edgeId); if (edge) { if (recursionStack.has(edge.targetId)) { // Found a cycle const cycleStart = path.indexOf(edge.targetId); if (cycleStart !== -1) { cycles.push([...path.slice(cycleStart), edge.targetId]); } } else if (!visited.has(edge.targetId)) { dfs(edge.targetId, [...path, edge.targetId]); } } } } recursionStack.delete(nodeId); }; session.nodes.forEach((_, nodeId) => { if (!visited.has(nodeId)) { dfs(nodeId, [nodeId]); } }); return cycles; } removeNode(nodeId, session) { const node = session.nodes.get(nodeId); if (!node) return; // Remove all connected edges [...node.incomingEdges, ...node.outgoingEdges].forEach(edgeId => { this.removeEdge(edgeId, session); }); // Remove node session.nodes.delete(nodeId); session.stats.totalNodes--; // Update entry nodes if needed const entryIndex = session.entryNodeIds.indexOf(nodeId); if (entryIndex !== -1) { session.entryNodeIds.splice(entryIndex, 1); } } removeEdge(edgeId, session) { const edge = session.edges.get(edgeId); if (!edge) return; // Remove from source node const sourceNode = session.nodes.get(edge.sourceId); if (sourceNode) { const index = sourceNode.outgoingEdges.indexOf(edgeId); if (index !== -1) { sourceNode.outgoingEdges.splice(index, 1); } } // Remove from target node const targetNode = session.nodes.get(edge.targetId); if (targetNode) { const index = targetNode.incomingEdges.indexOf(edgeId); if (index !== -1) { targetNode.incomingEdges.splice(index, 1); } } // Remove edge session.edges.delete(edgeId); session.stats.totalEdges--; } } export default GraphOfThoughtHandler; //# sourceMappingURL=graph-of-thought.js.map

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/waldzellai/clearthought-onepointfive'

If you have feedback or need assistance with the MCP directory API, please join our Discord server