# KnowHub 决策记录 记录设计过程中的关键决策及推演依据。 --- ## 0. Resource存储系统:分离公开/敏感内容 **日期**:2026-03-06 **背景**:用户需要存储有价值的工具资源,包括: - 复杂代码片段 - 特定网站的账号密码 - 登录状态的Cookie(含获取时间) **问题**: - 原有resources表只有body字段,明文存储不安全 - 如果整体加密,会失去搜索能力 - 缺少元数据(获取时间、过期时间、内容类型) **决策**:扩展resources表,分离公开/敏感内容 - `body`: 公开内容(明文,可搜索) - `secure_body`: 敏感内容(AES-256-GCM加密) - `content_type`: 内容类型(text|code|credential|cookie) - `metadata`: JSON元数据(language, acquired_at, expires_at) **加密机制**: - 使用AES-256-GCM加密 - 密钥从环境变量ORG_KEYS读取(格式:org1:key1_base64,org2:key2_base64) - resource_id前缀决定使用哪个组织密钥(如test/tools/...使用test的密钥) - 访问时需提供X-Org-Key头,验证通过才解密 **实现位置**:`knowhub/server.py` - `encrypt_resource()`: 加密函数 - `decrypt_resource()`: 解密函数 - `POST /api/resource`: 提交资源 - `GET /api/resource/{id}`: 获取资源(支持X-Org-Key头) - `PATCH /api/resource/{id}`: 更新资源 - `GET /api/resource`: 列出资源 **文档**:`knowhub/docs/resource-storage.md` --- ## 1. 定位:经验层而非工具目录 **背景**:调研发现现有生态(详见 `docs/knowledge/`)已充分覆盖工具发现: | 已解决的问题 | 谁在做 | |-------------|--------| | 工具有哪些 | Glama(17k+ MCP)、Smithery(125k+ Skills)、awesome-mcp-servers(81k Stars) | | 哪个热门 | Smithery 安装量排行、Glama 下载量 | | 静态质量指标 | Glama 质量评分、GitHub Stars、License | | 安装方式 | Smithery CLI、npx、pip/uvx | | 在线测试 | Glama MCP Inspector | **缺失**:Agent 在特定任务中使用工具后的真实体验。 **决策**:KnowHub 不做工具目录,只做经验层。发现交给现有平台,KnowHub 收集"用了之后怎么样"。 --- ## 2. 核心实体:experience 而非 tool **曾考虑的方案**:三张表(tools + reports + reviews),以 tool 为中心。 **问题**: - tools 表复制 Glama/Smithery 已有的目录 - reports(调研发现)的价值有限——Agent 的调研结论不比 Glama 的质量评分更可靠 - 只有 reviews(实际使用评价)是独有价值 **关键洞察**:Agent 的问题不是"pymupdf 怎么样"(工具中心),而是"我要从 PDF 提取表格,谁做过"(任务中心)。 **决策**:单一实体 `experience`(任务 + 工具 + 结果 + 建议)。工具信息从 experiences 中按 tool_name 聚合得出,不单独维护。取消 reports——没用过不评价。 --- ## 3. 搜索:任务驱动 **决策**:LIKE 拆词搜索 task + tips + outcome + name 字段,结果按 name 分组,每组返回相关 experiences 和聚合分数。 Server 只做检索 + GROUP BY,不做智能处理。Agent 拿到 JSON 后自行判断。体现"端侧算力"原则。 **搜索范围**:仅命中 experience。Content 是 experience 的深入层,通过 resource_id 关联获取,不参与搜索。如果后续发现 content 中有大量有价值信息未被 experience 覆盖,再扩展搜索范围。 --- ## 4. 接口:curl 直调 API(MVP) **曾考虑的方案**: | 方案 | 优势 | 劣势 | |------|------|------| | CLI(mpk 命令) | 封装 HTTP 调用,管理 agent_id | 需要 pip install,多一个组件 | | MCP Server | Agent 原生工具调用 | 需 MCP SDK + 本地进程,多一跳 | | curl 直调 API | 零安装,Agent 和人都能用 | 命令略长,无本地状态管理 | CLI 和 MCP 都在 HTTP API 外面包了一层。MVP 阶段这层封装提供的价值(agent_id 管理、输出格式化、错误处理)Agent 自身都能做。 **决策**:MVP 不做客户端,skill.md 直接引导 Agent 用 curl 调 API。CLI 和 MCP Server 作为后续接入方式。 --- ## 5. skill.md:两条路径而非线性流程 **曾考虑的方案**:四步线性流程(查→找→评→报),发现渠道与查经验并列。 **问题**:有经验时不需要去外部平台找工具,经验中已包含工具名、URL 和使用建议。 **决策**:分叉流程——有结果直接用,无结果才 fallback 到外部平台。发现渠道从主流程降级为 fallback。 --- ## 6. 冷启动:有机增长而非种子数据 **曾考虑的方案**:从调研成果导入精选工具目录作为种子数据。 **问题**:导入没有 experience 的工具条目和空数据库无区别。空壳结果反而降低信任感。 **决策**:数据库从零开始。冷启动路径: 1. Agent 搜索 → 空结果 2. skill.md 引导去 Smithery/PyPI 找工具 3. Agent 使用后 `POST /api/experience` 上报 4. 下一个 Agent 搜索时看到前人经验 空结果不是问题,假数据才是。加速冷启动通过运营(自己先用热门工具并提交真实经验),不通过技术手段。 --- ## 7. 身份标识:可选 submitted_by **曾考虑的方案**: - 自动生成 agent_id 持久化到本地文件 → 需要客户端组件 - 要求注册/登录 → 摩擦太大 - 要求邮箱 → 如何零成本获取? **决策**:可选字段 `submitted_by`,自由格式(邮箱、用户名、团队名均可)。skill.md 建议用 `git config user.email` 自动填充——几乎所有开发者已配置,零额外设置。 内部项目设置后方便 debug 追溯;外部用户可不填,零摩擦。 --- ## 8. 算力分布 | 工作 | 执行方 | 理由 | |------|--------|------| | 搜索工具 | Agent(Smithery/PyPI/GitHub) | 利用 Agent 搜索和浏览器能力 | | 评估工具质量 | Agent | Agent 有判断力 | | 决定用哪个 | Agent | 结合经验 + 自身评估 | | 总结使用经验 | Agent | LLM 能力 | | 存储/检索/聚合 | Server | 简单 SQL 操作 | Server 零 LLM 成本。 --- ## 9. 资源范围:从工具扩展到一般性知识 **背景**:最初定位仅覆盖工具(mcp/skill/library/api)。但 Agent 的集体记忆不应限于工具——论文、博客、电子书、课程等知识资源同样有"用了之后怎么样"的经验价值。 **决策**:`category` 从工具类型扩展为资源类型(`mcp | skill | library | api | paper | blog | book | course` 等)。数据模型不变,只是语义范围扩大。字段从 `tool_*` 重命名为通用名(`name` / `url` / `category`)。 --- ## 10. 触发条件:从"需要工具"到更广的场景 **背景**:skill.md 原始触发条件是"需要外部工具但不确定选哪个时",将 KnowHub 限定为工具推荐器。 **决策**:扩展为更广的触发条件——遇到复杂任务、可能超出自身能力、多次失败、可能需要外部资源时。KnowHub 是 Agent 面对不确定性时的第一反应,不只是找工具时才想起来。 --- ## 11. 内容层:按需积累而非预设结构 **背景**:Experience(tips/outcome)能承载一条经验的关键信息,但有些资源需要更深入的内容(论文章节、API 文档、书籍目录)。 **曾考虑的方案**: - 固定三层结构(experience → resource detail → sections):预设层级,用 section_key 约定 - 通用内容图:每个内容有 ID 和 body,可以引用其他内容 **决策**:通用的 contents 表,路径式 ID(`pymupdf` 或 `pymupdf/find-tables`),扁平二级结构(根 + 叶),body 为 Markdown。内容由 Agent 按需提交——没人需要的资源停留在 experience 层,高频使用的资源自然积累出完整内容。 **内容格式**:Markdown。Agent 原生理解 markdown,图片用 URL 引用,不存文件。Resonote 的 HTML + span_id 体系是为人类阅读设计的,KnowHub 不需要。 **观测性**:Server 记录搜索日志。Agent 对不存在内容的 GET 请求频率本身就是"需要更深内容"的信号。 --- ## 12. KnowHub 与 Resonote:独立演进 **背景**:两者在内容处理上有潜在协同(内容去重、分析管线、向量检索)。曾讨论 Resonote 依赖 KnowHub 内容层。 **分析**: - Library 的 HTML + span_id + 虚拟切分管线对人类阅读有价值,对 Agent 也有潜在价值(精确引用、结构化内容) - 但 KnowHub 在 MVP 阶段,数据模型会频繁变动;Library 已稳定,不应因 KnowHub 迭代而受影响 - 合并内容层的工程收益小于耦合成本 **决策**:两个项目独立推进。唯一预留的接口:两边使用相同的 content hash 方案(sha256),未来如果整合,内容去重天然可行。等 KnowHub 验证"集体记忆"方向后再讨论整合。 --- ## 13. 向量检索:Milvus Lite 单一存储架构 **日期**:2026-03-09 **背景**:现有检索方案使用 LLM 语义路由(gemini-2.0-flash-001),从所有知识中挑选候选。存在以下问题: - 每次检索都需要调用 LLM,成本和延迟较高 - 无法利用向量相似度进行精确的语义匹配 - 难以支持大规模知识库(需要将所有知识元数据传给 LLM) **方案对比**: | 方案 | 部署复杂度 | 性能 | 功能完整性 | 迁移成本 | |------|-----------|------|-----------|---------| | sqlite-vec | 低(单文件) | 中 | 基础向量检索 | 低 | | **Milvus Lite** | **低(pip install)** | **高** | **完整(标量过滤+向量检索)** | **中** | | Qdrant | 中(需 Docker) | 高 | 完整 | 低 | | 完整 Milvus | 高(多组件) | 极高 | 完整 | 高 | **决策**:采用 Milvus Lite 单一存储架构 **为什么不用 SQLite + Milvus Lite 双存储?** - Milvus Lite 支持标量字段存储,可以存储所有知识数据 - 维护两个数据库增加同步复杂度和一致性风险 - Milvus Lite 数据也是本地文件存储,备份和迁移同样简单 - 单一存储简化架构,降低维护成本 **架构设计**: ``` ┌─────────────────────────────────────────────────────┐ │ KnowHub Server │ ├─────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────┐ │ │ │ Milvus Lite │ │ │ │ (单一存储) │ │ │ ├─────────────────────┤ │ │ │ knowledge 集合 │ │ │ │ ├─ id (PK) │ │ │ │ ├─ embedding (向量) │ │ │ │ ├─ task │ │ │ │ ├─ content │ │ │ │ ├─ types (JSON) │ │ │ │ ├─ tags (JSON) │ │ │ │ ├─ scopes (JSON) │ │ │ │ ├─ owner │ │ │ │ ├─ resource_ids │ │ │ │ ├─ source (JSON) │ │ │ │ ├─ eval (JSON) │ │ │ │ ├─ created_at │ │ │ │ └─ updated_at │ │ │ └─────────────────────┘ │ │ │ │ 向量索引:HNSW (COSINE) │ │ 参数:M=16, efConstruction=200 │ │ │ │ ┌──────────────────────────────────────────────┐ │ │ │ 检索流程 │ │ │ ├──────────────────────────────────────────────┤ │ │ │ 1. 向量召回:Milvus 检索 top 3*k 候选 │ │ │ │ 2. LLM 精排:Gemini 对候选重新排序 │ │ │ │ 3. Fallback:LLM 失败时直接返回向量 top k │ │ │ └──────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────┘ ``` **检索流程**(向量召回 + LLM 精排): ```python async def knowledge_search(query: str, filters: dict, top_k: int = 5): # 1. 生成查询向量 query_embedding = await get_embedding(query) # 2. 向量召回(快速、便宜) candidates = await milvus_store.search( embedding=query_embedding, filters=filters, # types, owner, scopes limit=top_k * 3 # 召回 3*k 个候选 ) # 3. LLM 精排(准确、贵) try: ranked = await llm_rerank( query=query, candidates=candidates, top_k=top_k ) return ranked except Exception as e: # 4. Fallback:LLM 失败时直接返回向量 top k logger.warning(f"LLM rerank failed: {e}, fallback to vector top-k") return candidates[:top_k] async def llm_rerank(query: str, candidates: List[dict], top_k: int): """使用 LLM 对候选进行精排""" # 构造 prompt candidates_text = "\n".join([ f"[{i+1}] ID: {c['id']}\nTask: {c['task']}\nContent: {c['content'][:200]}..." for i, c in enumerate(candidates) ]) prompt = f"""你是知识检索专家。根据用户查询,从候选知识中选出最相关的 {top_k} 条。 用户查询:"{query}" 候选知识: {candidates_text} 请输出最相关的 {top_k} 个知识 ID,按相关性从高到低排序,用逗号分隔。 只输出 ID,不要其他内容。""" response = await openrouter_llm_call( messages=[{"role": "user", "content": prompt}], model="google/gemini-2.5-flash-lite" ) # 解析 LLM 输出 selected_ids = parse_ids(response["content"]) # 按 LLM 排序返回 id_to_candidate = {c["id"]: c for c in candidates} return [id_to_candidate[id] for id in selected_ids if id in id_to_candidate] ``` **Embedding 模型选择**: 优先级: 1. **OpenAI text-embedding-3-small**(推荐) - 1536 维,性能好,成本低($0.02/1M tokens) - 支持中英文 2. **本地模型**(备选) - paraphrase-multilingual-MiniLM-L12-v2 - 零成本,但需要本地计算资源 **成本分析**: 假设 1000 条知识,每条平均 200 tokens: | 操作 | 旧方案(纯 LLM 路由) | 新方案(向量召回 + LLM 精排) | |------|---------------------|---------------------------| | 每次检索 | 200k tokens → $0.04 | 召回:0 成本
精排:3k tokens → $0.0006 | | 1000 次检索 | $40 | $0.60 | | 节省 | - | **98.5%** | **迁移路径**: 阶段 1:实现 Milvus Lite 存储(2-3 周) - 从 SQLite 迁移数据到 Milvus Lite - 实现向量召回 + LLM 精排 - 保留旧 API 兼容性 阶段 2:效果评估(1-2 个月) - 对比新旧方案的准确率和成本 - 收集用户反馈 - 调优召回倍数(3*k)和精排策略 阶段 3:可能的演进方向 - 方向 A:优化精排 prompt,提升准确率 - 方向 B:引入混合检索(向量 + 关键词) - 方向 C:升级到完整 Milvus(如果数据量暴增) **实现位置**: - Milvus 封装:`knowhub/vector_store.py` - Embedding 生成:`knowhub/embeddings.py` - 检索逻辑:`knowhub/server.py:knowledge_search` - LLM 精排:`knowhub/server.py:llm_rerank` **优势**: 1. 单一存储,架构简单 2. 向量召回快速且便宜 3. LLM 精排保证准确性 4. Fallback 机制保证可用性 5. 成本降低 98.5% **权衡**: 1. 从 SQLite 迁移需要一次性工作 2. Milvus Lite 的标量查询不如 SQL 灵活(但够用) 3. 存储空间增加(向量数据)