UNPKG

mcp-magma-handbook

Version:

Enhanced MCP server with multi-query search, hybrid search, and collections for MAGMA computational algebra system

251 lines 9.87 kB
import { ChromaClient } from 'chromadb'; import { OpenAIEmbeddings } from '@langchain/openai'; import { PDFLoader } from '@langchain/community/document_loaders/fs/pdf'; import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters'; export class MagmaKnowledgeBase { client; collection; embeddings; textSplitter; isInitialized = false; constructor() { this.client = new ChromaClient(); this.embeddings = new OpenAIEmbeddings({ modelName: 'text-embedding-3-small', dimensions: 1536, // Advanced와 동일한 차원 }); // MAGMA 코드에 최적화된 텍스트 분할기 this.textSplitter = new RecursiveCharacterTextSplitter({ chunkSize: 1500, chunkOverlap: 200, separators: [ '\n\n\n', // 섹션 구분 '\n\n', // 단락 구분 '\n', // 줄 구분 ';', // MAGMA 문장 종료 '.', // 일반 문장 ' ', // 공백 '' ], }); } async initialize() { if (this.isInitialized) return; try { // Create or get collection this.collection = await this.client.getOrCreateCollection({ name: 'magma_handbook', embeddingFunction: { generate: async (texts) => { const embeddings = await this.embeddings.embedDocuments(texts); return embeddings; } } }); // Check if indexing is needed const count = await this.collection.count(); if (count === 0) { console.error('No documents in collection. Please run indexing first.'); console.error('Place MAGMA_HANDBOOK.pdf in data/pdfs/ and run: npm run index'); } else { console.error(`Loaded collection with ${count} documents`); } this.isInitialized = true; } catch (error) { console.error('Failed to initialize knowledge base:', error); throw error; } } async indexPDF(pdfPath) { console.log(`Indexing PDF: ${pdfPath}`); // Load PDF const loader = new PDFLoader(pdfPath); const docs = await loader.load(); // Split documents const splitDocs = await this.textSplitter.splitDocuments(docs); // Process and categorize documents const processedDocs = splitDocs.map((doc, index) => ({ id: `magma_${index}`, content: doc.pageContent, metadata: { ...doc.metadata, category: this.categorizeContent(doc.pageContent), indexed_at: new Date().toISOString(), } })); // Add to collection if (this.collection) { await this.collection.add({ ids: processedDocs.map(d => d.id), documents: processedDocs.map(d => d.content), metadatas: processedDocs.map(d => d.metadata), }); } console.log(`Indexed ${processedDocs.length} documents from ${pdfPath}`); } categorizeContent(content) { const lowerContent = content.toLowerCase(); // MAGMA 특화 카테고리 분류 if (lowerContent.includes('syntax') || lowerContent.includes('::=')) { return 'syntax'; } else if (lowerContent.includes('function') || lowerContent.includes('intrinsic')) { return 'function'; } else if (lowerContent.includes('algorithm') || lowerContent.includes('procedure')) { return 'algorithm'; } else if (lowerContent.includes('example') || content.includes('>')) { return 'example'; } else if (lowerContent.includes('theorem') || lowerContent.includes('lemma')) { return 'theory'; } return 'general'; } async search(query, limit = 5, category = 'all') { if (!this.collection) { throw new Error('Knowledge base not initialized'); } // Build filter const where = category !== 'all' ? { category: category } : undefined; // Search const results = await this.collection.query({ queryTexts: [query], nResults: limit, where: where, }); // Format results return results.ids[0].map((id, index) => ({ content: results.documents[0][index] || '', metadata: results.metadatas[0][index], score: results.distances ? 1 - results.distances[0][index] : 0.5, })); } async getExamples(topic, complexity = 'basic') { // Search for examples related to the topic const searchResults = await this.search(`${topic} example code`, 10, 'example'); // Extract and format MAGMA code examples const examples = searchResults .map(result => { const codeMatches = result.content.match(/```magma([\s\S]*?)```|>(.*?)$/gm); if (codeMatches) { return { title: this.extractTitle(result.content), code: this.cleanMagmaCode(codeMatches[0]), explanation: this.extractExplanation(result.content), complexity: this.assessComplexity(codeMatches[0]), source: result.metadata.source, page: result.metadata.page, }; } return null; }) .filter(Boolean) .filter(ex => complexity === 'all' || ex?.complexity === complexity); return examples; } async explainCode(code, context) { // Search for relevant documentation about the functions used in the code const functions = this.extractFunctions(code); const searchPromises = functions.map(func => this.search(`${func} function syntax usage`, 3, 'function')); const searchResults = await Promise.all(searchPromises); const relevantDocs = searchResults.flat(); // Build explanation let explanation = '# MAGMA Code Explanation\n\n'; explanation += '## Code:\n```magma\n' + code + '\n```\n\n'; if (context) { explanation += `## Context:\n${context}\n\n`; } explanation += '## Analysis:\n'; // Parse and explain the code structure const lines = code.split('\n').filter(line => line.trim()); for (const line of lines) { if (line.trim().startsWith('//')) continue; const analysis = this.analyzeLine(line, relevantDocs); if (analysis) { explanation += `- ${analysis}\n`; } } // Add relevant function documentation if (functions.length > 0) { explanation += '\n## Related Functions:\n'; const uniqueFunctions = [...new Set(functions)]; for (const func of uniqueFunctions) { const funcDocs = relevantDocs.filter(doc => doc.content.toLowerCase().includes(func.toLowerCase())); if (funcDocs.length > 0) { explanation += `\n### ${func}\n`; explanation += funcDocs[0].content.substring(0, 300) + '...\n'; } } } return explanation; } extractFunctions(code) { // MAGMA 함수 패턴 매칭 const functionPattern = /([A-Z][a-zA-Z0-9]*)\s*\(/g; const matches = code.match(functionPattern) || []; return matches.map(m => m.replace('(', '').trim()); } analyzeLine(line, docs) { const trimmed = line.trim(); // 변수 할당 if (trimmed.includes(':=')) { const [varName, value] = trimmed.split(':=').map(s => s.trim()); return `\`${varName}\` is assigned the value/result of \`${value}\``; } // 함수 호출 const funcMatch = trimmed.match(/([A-Z][a-zA-Z0-9]*)\s*\(/); if (funcMatch) { return `Calls function \`${funcMatch[1]}\``; } // 제어 구조 if (trimmed.startsWith('if') || trimmed.startsWith('while') || trimmed.startsWith('for')) { return `Control structure: ${trimmed.split(' ')[0]}`; } return null; } extractTitle(content) { const lines = content.split('\n'); for (const line of lines) { if (line.trim() && !line.startsWith('>') && line.length < 100) { return line.trim(); } } return 'MAGMA Example'; } cleanMagmaCode(code) { return code .replace(/```magma/g, '') .replace(/```/g, '') .replace(/^>\s*/gm, '') .trim(); } extractExplanation(content) { // Extract explanation text between code blocks const parts = content.split(/```magma[\s\S]*?```|>.*$/gm); return parts .map(p => p.trim()) .filter(p => p.length > 20) .join(' ') .substring(0, 200); } assessComplexity(code) { const lines = code.split('\n').length; const hasLoops = /for|while/.test(code); const hasFunctions = /function|procedure/.test(code); const complexFunctions = /Factorization|IsIrreducible|GaloisGroup/.test(code); if (complexFunctions || (hasLoops && hasFunctions) || lines > 20) { return 'advanced'; } else if (hasLoops || hasFunctions || lines > 10) { return 'intermediate'; } return 'basic'; } } //# sourceMappingURL=knowledge-base.js.map