记录设计过程中的关键决策及推演依据。
日期:2026-03-06
背景:用户需要存储有价值的工具资源,包括:
问题:
决策:扩展resources表,分离公开/敏感内容
body: 公开内容(明文,可搜索)secure_body: 敏感内容(AES-256-GCM加密)content_type: 内容类型(text|code|credential|cookie)metadata: JSON元数据(language, acquired_at, expires_at)加密机制:
实现位置: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
背景:调研发现现有生态(详见 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 收集"用了之后怎么样"。
曾考虑的方案:三张表(tools + reports + reviews),以 tool 为中心。
问题:
关键洞察:Agent 的问题不是"pymupdf 怎么样"(工具中心),而是"我要从 PDF 提取表格,谁做过"(任务中心)。
决策:单一实体 experience(任务 + 工具 + 结果 + 建议)。工具信息从 experiences 中按 tool_name 聚合得出,不单独维护。取消 reports——没用过不评价。
决策:LIKE 拆词搜索 task + tips + outcome + name 字段,结果按 name 分组,每组返回相关 experiences 和聚合分数。
Server 只做检索 + GROUP BY,不做智能处理。Agent 拿到 JSON 后自行判断。体现"端侧算力"原则。
搜索范围:仅命中 experience。Content 是 experience 的深入层,通过 resource_id 关联获取,不参与搜索。如果后续发现 content 中有大量有价值信息未被 experience 覆盖,再扩展搜索范围。
曾考虑的方案:
| 方案 | 优势 | 劣势 |
|---|---|---|
| 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 作为后续接入方式。
曾考虑的方案:四步线性流程(查→找→评→报),发现渠道与查经验并列。
问题:有经验时不需要去外部平台找工具,经验中已包含工具名、URL 和使用建议。
决策:分叉流程——有结果直接用,无结果才 fallback 到外部平台。发现渠道从主流程降级为 fallback。
曾考虑的方案:从调研成果导入精选工具目录作为种子数据。
问题:导入没有 experience 的工具条目和空数据库无区别。空壳结果反而降低信任感。
决策:数据库从零开始。冷启动路径:
POST /api/experience 上报空结果不是问题,假数据才是。加速冷启动通过运营(自己先用热门工具并提交真实经验),不通过技术手段。
曾考虑的方案:
决策:可选字段 submitted_by,自由格式(邮箱、用户名、团队名均可)。skill.md 建议用 git config user.email 自动填充——几乎所有开发者已配置,零额外设置。
内部项目设置后方便 debug 追溯;外部用户可不填,零摩擦。
| 工作 | 执行方 | 理由 |
|---|---|---|
| 搜索工具 | Agent(Smithery/PyPI/GitHub) | 利用 Agent 搜索和浏览器能力 |
| 评估工具质量 | Agent | Agent 有判断力 |
| 决定用哪个 | Agent | 结合经验 + 自身评估 |
| 总结使用经验 | Agent | LLM 能力 |
| 存储/检索/聚合 | Server | 简单 SQL 操作 |
Server 零 LLM 成本。
背景:最初定位仅覆盖工具(mcp/skill/library/api)。但 Agent 的集体记忆不应限于工具——论文、博客、电子书、课程等知识资源同样有"用了之后怎么样"的经验价值。
决策:category 从工具类型扩展为资源类型(mcp | skill | library | api | paper | blog | book | course 等)。数据模型不变,只是语义范围扩大。字段从 tool_* 重命名为通用名(name / url / category)。
背景:skill.md 原始触发条件是"需要外部工具但不确定选哪个时",将 KnowHub 限定为工具推荐器。
决策:扩展为更广的触发条件——遇到复杂任务、可能超出自身能力、多次失败、可能需要外部资源时。KnowHub 是 Agent 面对不确定性时的第一反应,不只是找工具时才想起来。
背景:Experience(tips/outcome)能承载一条经验的关键信息,但有些资源需要更深入的内容(论文章节、API 文档、书籍目录)。
曾考虑的方案:
决策:通用的 contents 表,路径式 ID(pymupdf 或 pymupdf/find-tables),扁平二级结构(根 + 叶),body 为 Markdown。内容由 Agent 按需提交——没人需要的资源停留在 experience 层,高频使用的资源自然积累出完整内容。
内容格式:Markdown。Agent 原生理解 markdown,图片用 URL 引用,不存文件。Resonote 的 HTML + span_id 体系是为人类阅读设计的,KnowHub 不需要。
观测性:Server 记录搜索日志。Agent 对不存在内容的 GET 请求频率本身就是"需要更深内容"的信号。
背景:两者在内容处理上有潜在协同(内容去重、分析管线、向量检索)。曾讨论 Resonote 依赖 KnowHub 内容层。
分析:
决策:两个项目独立推进。唯一预留的接口:两边使用相同的 content hash 方案(sha256),未来如果整合,内容去重天然可行。等 KnowHub 验证"集体记忆"方向后再讨论整合。
日期:2026-03-09
背景:现有检索方案使用 LLM 语义路由(gemini-2.0-flash-001),从所有知识中挑选候选。存在以下问题:
方案对比:
| 方案 | 部署复杂度 | 性能 | 功能完整性 | 迁移成本 |
|---|---|---|---|---|
| sqlite-vec | 低(单文件) | 中 | 基础向量检索 | 低 |
| Milvus Lite | 低(pip install) | 高 | 完整(标量过滤+向量检索) | 中 |
| Qdrant | 中(需 Docker) | 高 | 完整 | 低 |
| 完整 Milvus | 高(多组件) | 极高 | 完整 | 高 |
决策:采用 Milvus Lite 单一存储架构
为什么不用 SQLite + 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 精排):
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 模型选择:
优先级:
成本分析:
假设 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 周)
阶段 2:效果评估(1-2 个月)
阶段 3:可能的演进方向
实现位置:
knowhub/vector_store.pyknowhub/embeddings.pyknowhub/server.py:knowledge_searchknowhub/server.py:llm_rerank优势:
权衡: