import fs from 'node:fs'; import path from 'node:path'; /** 知识库中的一个文档块 */ interface Chunk { /** 来源文件名 */ source: string; /** 块内文本 */ content: string; } /** * 轻量知识库:从 knowledge/ 目录加载 .md 文件,按 ## 标题分块, * 提供基于关键词匹配的检索能力。 * * 用法: * const kb = new KnowledgeBase('./knowledge'); * await kb.load(); * const results = kb.search('Agent 工具调用'); */ export class KnowledgeBase { private chunks: Chunk[] = []; private knowledgeDir: string; constructor(knowledgeDir: string) { this.knowledgeDir = knowledgeDir; } /** 加载 knowledgeDir 下所有 .md 文件并分块 */ async load(): Promise { this.chunks = []; if (!fs.existsSync(this.knowledgeDir)) { console.warn(`知识库目录不存在: ${this.knowledgeDir}`); return; } const files = fs .readdirSync(this.knowledgeDir) .filter((f) => f.endsWith('.md')); for (const file of files) { const filePath = path.join(this.knowledgeDir, file); const raw = fs.readFileSync(filePath, 'utf-8'); const fileChunks = this.splitChunks(raw, file); this.chunks.push(...fileChunks); } console.log(`知识库已加载: ${this.chunks.length} 个块,来自 ${files.length} 个文件`); } /** 按 ## 标题将文档拆分为块 */ private splitChunks(raw: string, source: string): Chunk[] { const blocks = raw.split(/(?=^## )/m); return blocks .map((b) => b.trim()) .filter(Boolean) .map((content) => ({ source, content })); } /** * 基于关键词匹配搜索,返回相关块(按相关性降序) * * 算法:将查询分词,统计每个块命中关键词的次数, * 同时给标题匹配额外加权。 */ search(query: string, topK: number = 3): Chunk[] { const keywords = this.tokenize(query); if (keywords.length === 0) return []; const scored = this.chunks.map((chunk) => { const lower = chunk.content.toLowerCase(); let score = 0; for (const kw of keywords) { // 标题行命中加权 ×3 const headlineRegex = /^## .+$/gm; let match: RegExpExecArray | null; while ((match = headlineRegex.exec(chunk.content)) !== null) { if (match[0].toLowerCase().includes(kw)) { score += 3; } } // 正文命中 const count = (lower.match(new RegExp(this.escapeRegex(kw), 'gi')) || []).length; score += count; } // 标题匹配额外加分 return { chunk, score }; }); return scored .filter((s) => s.score > 0) .sort((a, b) => b.score - a.score) .slice(0, topK) .map((s) => s.chunk); } /** 中文 + 英文简单分词 */ private tokenize(text: string): string[] { // 按空白/标点拆分,过滤长度 ≤1 的词 return text .split(/[\s,,。.!!??::;;、]+/) .map((t) => t.toLowerCase().trim()) .filter((t) => t.length > 1); } private escapeRegex(s: string): string { return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } /** 获取知识库摘要(供 Agent 概览) */ summary(): string { if (this.chunks.length === 0) return '知识库为空'; const sources = [...new Set(this.chunks.map((c) => c.source))]; const titles = this.chunks .map((c) => { const m = c.content.match(/^## (.+)$/m); return m ? ` - ${m[1]} (${c.source})` : null; }) .filter(Boolean); return `知识库包含 ${sources.length} 个文档:\n${titles.join('\n')}`; } } /** 全局单例 */ export const knowledgeBase = new KnowledgeBase( path.join(process.cwd(), 'knowledge'), );