From a6924ebf483400222fe3d92243c7c5ac2a59f31d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Fri, 3 Jul 2026 17:23:53 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E7=9F=A5=E8=AF=86?= =?UTF-8?q?=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- knowledge/llm-to-agent-guide.md | 60 +++++++++++++++ src/index.ts | 2 + src/knowledge/knowledge-base.ts | 128 ++++++++++++++++++++++++++++++++ src/knowledge/search-tool.ts | 53 +++++++++++++ src/tools/registry.ts | 3 +- 5 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 knowledge/llm-to-agent-guide.md create mode 100644 src/knowledge/knowledge-base.ts create mode 100644 src/knowledge/search-tool.ts diff --git a/knowledge/llm-to-agent-guide.md b/knowledge/llm-to-agent-guide.md new file mode 100644 index 0000000..9a41a5a --- /dev/null +++ b/knowledge/llm-to-agent-guide.md @@ -0,0 +1,60 @@ +# LLM-to-Agent 框架知识库 + +## Agent 工具调用流程 + +Agent 的 `run()` 方法执行以下循环: +1. 将用户消息加入上下文 +2. 调用 LLM,传入可用工具列表 +3. 如果 LLM 返回 `tool_calls`,逐个执行工具,将结果加入上下文,回到步骤 2 +4. 如果 LLM 直接返回文本,结束循环,返回最终回复 + +工具定义需包含 `name`、`description`、`parameters`(JSON Schema)和 `execute` 函数。 + +## 子 Agent 管理 + +通过 `spawn_agent` 工具可以创建具有独立人设的子 Agent(如销售、顾客)。 +每个子 Agent 拥有独立的上下文和基础工具集(不含 sub-agent 管理工具,防止无限递归)。 +通过 `run_conversation` 工具可以在两个子 Agent 之间运行多轮对话。 + +## 知识库功能 + +知识库位于 `knowledge/` 目录,支持 `.md` 格式文档。 +文档按 `##` 二级标题自动分块,通过 `search_knowledge` 工具进行关键词检索。 +检索算法基于关键词命中次数,标题命中加权 ×3。 +也提供 `list_knowledge` 工具查看知识库全貌。 + +## 常用命令 + +- 启动框架:`pnpm dev` +- 指定提示词模式:`pnpm dev orchestrator` +- 可用模式:`default`、`toxic`、`json`、`agent`、`reAct`、`orchestrator` + +## 项目结构 + +``` +src/ + index.ts -- CLI 入口 + llm.ts -- LLM 客户端(OpenAI 兼容) + context.ts -- 上下文管理器 + types/index.ts -- 类型定义 + agents/ + agent.ts -- Agent 核心类 + manager.ts -- 子 Agent 管理器 + tools.ts -- spawn_agent / run_conversation 工具 + tools/ + registry.ts -- 工具注册表 + weather.ts -- 天气查询工具 + calculator.ts -- 计算器工具 + guess.ts -- 猜数字游戏工具 + knowledge/ + knowledge-base.ts -- 知识库类 + search-tool.ts -- 知识库搜索工具 + hooks/ + index.ts -- Hook 总线 + log.hooks.ts -- 日志 Hook + registry.ts -- Hook 注册 + prompts/ + system.ts -- 系统提示词 + orchestrator.ts -- Orchestrator 提示词 + reAct.ts -- ReAct 提示词 +``` diff --git a/src/index.ts b/src/index.ts index 0a06b67..560954b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,8 +4,10 @@ import { PROMPTS } from './prompts/system.js'; import * as readline from 'node:readline/promises'; import { hooks } from './hooks/index.js'; import { registerHooks } from './hooks/registry.js'; +import { knowledgeBase } from './knowledge/knowledge-base.js'; await registerHooks(); +await knowledgeBase.load(); const promptName = process.argv[2] || 'orchestrator'; const systemPrompt = PROMPTS[promptName as keyof typeof PROMPTS]; diff --git a/src/knowledge/knowledge-base.ts b/src/knowledge/knowledge-base.ts new file mode 100644 index 0000000..afd6b64 --- /dev/null +++ b/src/knowledge/knowledge-base.ts @@ -0,0 +1,128 @@ +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'), +); diff --git a/src/knowledge/search-tool.ts b/src/knowledge/search-tool.ts new file mode 100644 index 0000000..8d4cdc8 --- /dev/null +++ b/src/knowledge/search-tool.ts @@ -0,0 +1,53 @@ +import { Tool } from '../types/index.js'; +import { knowledgeBase } from './knowledge-base.js'; + +/** + * search_knowledge — 在知识库中检索相关信息 + */ +export const searchKnowledgeTool: Tool = { + name: 'search_knowledge', + description: + '在本地知识库中搜索与查询相关的文档片段。当你需要查找项目文档、技术说明、业务规则等存储在知识库中的信息时使用此工具。', + parameters: { + type: 'object', + properties: { + query: { + type: 'string', + description: '搜索关键词或问题,如"Agent 工具调用流程"、"如何创建子Agent"', + }, + }, + required: ['query'], + }, + execute: async (args) => { + const { query } = args; + const results = knowledgeBase.search(query as string, 3); + if (results.length === 0) { + return `未找到与 "${query}" 相关的知识。当前知识库摘要:\n${knowledgeBase.summary()}`; + } + return results + .map( + (r, i) => + `--- 结果 ${i + 1} (来源: ${r.source}) ---\n${r.content}`, + ) + .join('\n\n'); + }, +}; + +/** + * list_knowledge — 列出知识库中所有文档和章节 + */ +export const listKnowledgeTool: Tool = { + name: 'list_knowledge', + description: '列出知识库中所有文档及其章节标题,用于了解知识库包含哪些内容。', + parameters: { + type: 'object', + properties: {}, + required: [], + }, + execute: async () => { + return knowledgeBase.summary(); + }, +}; + +/** 知识库相关工具集 */ +export const knowledgeTools: Tool[] = [searchKnowledgeTool, listKnowledgeTool]; diff --git a/src/tools/registry.ts b/src/tools/registry.ts index 2356b9c..381c425 100644 --- a/src/tools/registry.ts +++ b/src/tools/registry.ts @@ -3,8 +3,9 @@ import { calculatorTool } from './calculator.js'; import { guessTool } from './guess.js'; import { Tool } from '../types/index.js'; import { subAgentTools } from '../agents/tools.js'; +import { knowledgeTools } from '../knowledge/search-tool.js'; -const baseTools: Tool[] = [weatherTool, calculatorTool, guessTool]; +const baseTools: Tool[] = [weatherTool, calculatorTool, guessTool, ...knowledgeTools]; /** 所有工具(基础工具 + sub-agent 管理工具),供主 orchestrator Agent 使用 */ const allTools: Tool[] = [...baseTools, ...subAgentTools];