在多智能体系统的知识库建设中,每一个技术选型都直接影响着系统的响应延迟、运行成本与数据隐私边界。本文围绕 Hermes-Agent 智能体框架的知识库架构,深度拆解 qmd、GGUF、Ollama、bge-m3 四大核心技术组件的协作机制与性能损耗,并给出可落地的最终选型方案。


回顾:关注我的读者应该知道,我此前在Hermes的知识库上折腾了ollama+bge-m3、karpathy的llm wiki、知识图谱等等内容,最后因为各种不满意,还是回滚到了官方最初的状态。接下来怎么办?凉拌是不可能的,今天这篇文章,不但是我自己进行知识库选型,也给各位重度使用智能体框架的读者一个参考。

忘了个很重要的点,就在这里补充吧。你要先在Hermes-Agent里创建一个qmd skill,然后再进行下面的搭建,并且这个skill(skill内置调用mcp)就是调用你即将要搭建的内容。

一、四个核心概念:它们到底是什么,为什么缺一不可

在讨论选型之前,必须先厘清四个技术概念的本质。它们分别位于知识库架构的不同层级:Markdown 知识文档是知识源载体,GGUF 是模型存储格式,Ollama 是模型服务框架,bge-m3 是语义检索引擎。四者是本地 RAG 体系中的关键技术组件。

1.1 qmd:Query Markup Documents,本地混合搜索引擎

qmd 的全称是 Query Markup Documents,不是一个文件格式,而是一个运行在本地的完整知识库系统。它通过 MCP 协议与 Hermes Agent 集成,内部集成了三层搜索能力,把传统 RAG 链路中需要外部拼接的多个组件(全文检索、向量语义检索、查询扩展、结果重排)全部打包进了一个服务。

qmd 在 ~/.hermes/config.yaml 中注册为一个 MCP server:

mcp_servers:
  qmd:
    command: /home/**/.nvm/versions/node/v22.22.2/bin/node
    args:
      - /home/**/.nvm/versions/node/v22.22.2/lib/node_modules/@tobilu/qmd/dist/cli/qmd.js
      - mcp
    timeout: 300

qmd 的核心竞争力在于三层混合搜索架构,这三层全部本地运行,不依赖任何远程 API:

层级 技术 作用
BM25 全文搜索 SQLite FTS5 关键词精确匹配,召回包含精确术语的文档
向量语义搜索 sqlite-vec + Gemma 300M GGUF 语义理解,捕捉同义词和概念关联
LLM 重排/扩展 node-llama-cpp + 1.7B Q4_K_M GGUF 查询扩展(生成同义/相关查询)+ 结果重排

这三层不是可选插件,而是默认同时启用的固定流水线。当 Agent 调用 mcp_qmd_query 时,qmd 内部自动完成:查询扩展 → BM25 检索 → 向量检索 → 结果融合 → LLM 重排 → 返回 Top-K 文档。Agent 不需要关心底层用了几个模型、几次数据库查询,只需要拿到结果即可。

qmd 加载的三个本地 GGUF 模型位于 ~/.cache/qmd/models/,全部通过 node-llama-cpp 在本地推理,无需 Ollama:

embeddinggemma-300M-Q8_0.gguf        (~319MB)  — 向量嵌入模型
qmd-query-expansion-1.7B-q4_k_m.gguf (~1.2GB)  — 查询扩展模型
qwen3-reranker-0.6b-q8_0.gguf        (~609MB)  — 结果重排模型

三个模型各司其职:

  • Gemma 300M:将文档和查询编码为向量,供 sqlite-vec 做语义检索
  • 1.7B 查询扩展模型:接收用户原始查询,生成语义等价的扩展查询,解决"用户说的词和文档里的词不一样"的问题
  • qwen3-reranker-0.6b:对 BM25 + 向量检索融合后的候选结果进行精排,解决"召回了很多,但哪个最相关"的问题

Hermes Agent 通过 MCP 协议调用 qmd 时,可用的工具包括:

  • mcp_qmd_query — 混合搜索(lexical + vector + rerank),一次调用完成完整检索链路
  • mcp_qmd_get — 按路径或 docid 获取单篇文档
  • mcp_qmd_multi_get — 批量获取多篇文档
  • mcp_qmd_status — 查看索引状态(文档数、向量索引是否启用、待嵌入数量)
  • mcp_qmd_list_resources — 列出所有已索引的资源

在 Hermes 的架构中,qmd 与 skill 文件、MCP 工具 server 形成三层分离:Markdown 文档承载知识内容,qmd 提供检索能力,工具 MCP server(如 filesystem、git)提供执行能力。Agent 的工作流程是:通过 mcp_qmd_query 检索找到相关知识 → 阅读 skill 文档理解操作步骤 → 通过 MCP 工具执行具体操作

1.2 GGUF:让大模型从云端"瘦身"到本地的关键格式

GGUF(GPT-Generated Unified Format)是 llama.cpp 项目推出的模型文件格式,用于替代早期的 GGML。它的核心使命是将动辄几十 GB 的原始 PyTorch 模型压缩到单文件、自包含、可直接在消费级硬件上推理的形式

GGUF 的关键技术特性包括:

单文件自包含。 传统的 PyTorch 模型需要 model.safetensors + tokenizer.json + config.json 等多个文件配合,而 GGUF 把词汇表、权重张量、超参数、分词器配置全部打包进一个 .gguf 文件。这意味着 Ollama 只需要一个 URL 或文件路径就能完成模型加载。

多级量化体系。 GGUF 支持从 Q2_K 到 Q8_0 的多种量化级别,量化策略不是简单的"全部压缩到 N bit",而是对模型中不同类型的层采用差异化精度:

  • Q4_K_M:4-bit 量化,注意力层的 K、V 矩阵使用更高的 6-bit 精度,中间 FFN 层使用 4-bit。这是性价比最高的选项,模型体积压缩到原始的 30-40%,质量损失在大多数问答场景下几乎不可感知。
  • Q5_K_M:5-bit 量化,进一步提升了中间层的数值精度,体积约为原始的 45-50%,适合对生成质量要求更高的场景。
  • Q8_0:8-bit 量化,基本接近 FP16 的精度,体积约为原始的 80-90%,但推理速度明显慢于 4-bit/5-bit。

量化级别直接决定了三个核心指标:内存占用、推理速度、生成质量。后文会给出具体的量化对比数据。

CPU/GPU 混合推理。 GGUF 格式原生支持 llama.cpp 的 CPU/GPU 混合调度策略——将模型的 attention 层放在 GPU 上加速,将部分 FFN 层留在 CPU 上,从而在显存不足时最大化硬件利用率。这对于只有集成显卡(如 AMD Radeon 680M,也就是鄙人的老破旧机器)或低显存独显的机器至关重要。

1.3 Ollama:本地大模型的"Docker"

Ollama 的本质是一个模型管理与服务化框架,用 Go 语言编写,底层调用 llama.cpp 进行推理。它把本地大模型的部署体验简化到了"一条命令"的级别:

ollama run qwen2.5:7b

这条命令背后,Ollama 完成了模型下载(从官方 registry 或自定义源)、GGUF 格式解析、推理服务启动、REST API 暴露(默认 localhost:11434)的全流程。对上层应用而言,Ollama 提供了一个与 OpenAI API 兼容的接口,这意味着现有的 OpenAI SDK 代码几乎无需修改就能切换到本地模型。

Ollama 的核心价值不是推理加速(真正的加速来自 llama.cpp 的量化与 kernel 优化),而是将大模型从"需要手动编译、配置环境变量、处理依赖冲突"的复杂工程,变成了一个开箱即用的服务。在 Hermes 这样的智能体框架中,Ollama 让 Agent 可以通过标准 HTTP 调用本地模型,无需关心底层是 llama.cpp、vLLM 还是其他推理引擎。

1.4 bge-m3:检索质量的"多面手"

bge-m3 由北京智源人工智能研究院(BAAI)开发,是目前开源社区中功能最全面的嵌入模型之一。它的"m3"代表三种能力:多语言(Multi-lingual)、多粒度(Multi-granularity)、多表示(Multi-representation)

在技术实现上,bge-m3 的核心创新是同时输出三种向量表示:

Dense Embedding(稠密向量)。 将文本编码为固定长度的向量(默认 768 维或 1024 维),通过余弦相似度衡量语义接近程度。这是传统 RAG 系统的标准做法,优势是检索速度快(ANN 近似最近邻),劣势是面对罕见词或专有名词时可能召回不足。

Sparse Embedding(稀疏向量)。 输出类似 BM25 的词项权重向量,保留了原始词项级别的信息。优势是关键词匹配精准,面对"CVE-2024-XXXX"这种精确术语时不会漏检。劣势是向量维度高(等于词汇表大小),存储成本大。

Multi-vector(多向量,ColBERT 风格)。 为文本中的每个 token 都生成一个向量,检索时计算查询与文档的 token 级细粒度交互。优势是精度最高,能捕捉到细微的语义差异;劣势是计算量极大,不适合大规模检索。

在传统自组 RAG 方案中,bge-m3 通常采用Dense + Sparse 混合检索的策略:先用 Dense 向量快速召回 Top-100 候选,再用 Sparse 向量补充关键词层面的漏检,最后用轻量级重排模型筛选 Top-5 送入 LLM 生成。这种"粗排+精排"的两段式架构在理论上是成熟的,但在实际落地时需要自行拼接嵌入、存储、重排等多个组件,维护成本较高。QMD 选择了一条不同的路线——用更轻量的专用模型(Gemma 300M + qwen3-reranker-0.6b)和更紧密集成的架构(sqlite-vec + FTS5 共用 SQLite)来降低组合复杂度。


二、四种搭配方案的性能损耗全景对比

理解了四个组件的独立特性后,下一步是分析它们组合在一起时的性能损耗。不同的组合方式直接决定了系统能否在"本地单机"的约束下跑起来。

2.1 方案一:全本地闭环(理想方案,非我当前实际选型)

适用场景:对隐私要求极高、需要完全离线运行、愿意牺牲生成质量换取零网络依赖的环境。如果你每天花 8–12 小时自主填充高质量知识内容,且对生成模型的质量要求可以用本地 7B 模型满足,这是理论上的最优解。但搭建繁琐,生成质量与商业 API 有明显差距。

架构:Markdown 知识文档 → bge-m3 本地嵌入 → 本地向量库(Chroma/Qdrant)→ Ollama(GGUF) 本地生成

指标 数值 说明
内存占用 8–12 GB bge-m3 约 2G + 7B GGUF(Q4_K_M) 约 4–5G + Ollama 运行时 + 系统开销
嵌入速度 ~500 ms/文档 纯 CPU 推理,文档长度 1000 tokens
查询延迟 2–3 秒 含检索 + 重排 + 生成完整链路
网络依赖 首次下载模型后完全离线运行
额外步骤 3 步 ①安装 Ollama ②拉取模型 ③启动嵌入服务

性能损耗点分析

最大的损耗来自量化精度。Q4_K_M 的 7B 模型在推理时,激活值和权重的数值范围被压缩到 4-bit,这意味着模型对复杂推理链(如多步数学推导、长距离指代消解)的处理能力会弱于 FP16 版本。但在知识库问答这种"检索事实 + 组织语言"的场景中,量化带来的质量下降通常在 5–10% 以内,远低于通用对话基准的下降幅度。原因是知识库问答严重依赖检索阶段的质量,而生成阶段主要是"复述和归纳",对模型的深层推理能力要求不高。

第二个损耗点是CPU 推理的 token 速度。以 AMD Ryzen 7 7735HS(8C16T)为例(鄙人的老破旧),Q4_K_M 的 7B 模型在纯 CPU 推理时约 8–12 tokens/s,而同等模型在 RTX 3060 上可达 40–60 tokens/s。对于生成 200–300 tokens 的回答,CPU 需要 20–30 秒,这个延迟在交互式场景中是不可接受的。但 Hermes 的优化点在于:查询链路的大部分时间消耗在检索(2–3 秒),而非生成。如果生成阶段被控制在 100 tokens 以内(通过 prompt 工程限制输出长度),纯 CPU 的响应时间仍在可接受范围内。

2.2 方案二:本地检索 + 远程生成(混合路线)

架构:Markdown 知识文档 → bge-m3 本地嵌入 → 本地向量库 → 远程 API(OpenAI/Claude/DeepSeek)生成

指标 数值 说明
内存占用 ~2 GB 仅需 bge-m3 嵌入模型
查询延迟 1–5 秒 本地检索 1–2s + 网络往返 + API 生成
网络依赖 每次查询必须调用远程 API
数据隐私 检索阶段本地完成,但生成阶段 prompt 需外发
额外步骤 4 步 本地服务 + API Key 配置 + 网络稳定性保障

性能损耗点

混合方案的核心损耗不是计算,而是网络不确定性。本地检索的 1–2 秒是稳定的,但 API 调用的延迟波动极大:OpenAI 的 GPT-4 在高峰期可能延迟 5 秒以上,而 DeepSeek 的 API 在低价时段可能排队数十秒。对于 Hermes 这种需要 Agent 快速响应的工具调用场景(如 /harness status 需要秒级返回),网络波动会直接破坏用户体验。

另一个隐性损耗是上下文隐私。即使只发送检索出的 Top-K 文档片段,这些片段中仍可能包含敏感信息(如服务器配置、内部代码结构)。对于安全审计、渗透测试等场景,数据外发是不可接受的。

其实也还有缺点,要花钱。

2.3 方案三:全远程(云端路线)

架构:Markdown 知识文档 → 远程 Embedding API(OpenAI text-embedding-3/bge via 云)→ 远程向量库(Pinecone/Milvus Cloud)→ 远程 LLM API

指标 数值 说明
内存占用 ~0 GB 所有计算在云端
查询延迟 1–3 秒 取决于云服务商和网络质量
月度成本 $50–200+ 按 token 和存储量计费
数据隐私 全部数据流经第三方服务器

性能损耗点

全远程方案没有计算性能损耗(云端的 GPU 集群远比本地强大),但存在持续的经济损耗和隐私损耗。对于一个日活 100 次查询的知识库系统,Embedding + LLM API 的月费用轻松超过 100 美元。更重要的是,多智能体系统处理的往往是高敏感信息(SSH 密钥、内网拓扑、漏洞详情),全远程方案在合规层面几乎不可行。

2.4 哪个更好?一张图说清

维度 全本地 混合 全远程
数据隐私 ★★★★★ ★★★☆☆ ★☆☆☆☆
长期成本 ★★★★★ ★★★☆☆ ★☆☆☆☆
响应稳定性 ★★★★☆ ★★☆☆☆ ★★★☆☆
生成质量天花板 ★★★☆☆ ★★★★☆ ★★★★★
硬件门槛 ★★★☆☆ ★★★★☆ ★★★★★
运维复杂度 ★★★☆☆ ★★★★☆ ★★★★★

结论:三种方案没有绝对优劣,取决于约束条件。对于本文讨论的场景——检索侧必须完全本地(隐私+稳定)、生成侧可用远程 API(质量优先)——"本地检索 + 远程生成"的混合架构是最务实的选择。第二章的方案一(全本地)是理想参照,第六章会说明我实际落地的配置。


三、Hermes 官方 skill + MCP 方案的深度利弊分析

在确定本地 RAG 是主线后,下一个关键决策是:知识如何被组织成 Agent 可调用的能力单元? Hermes 的答案是:用 Markdown 文档(SKILL.md)沉淀领域知识与操作指南,通过 MCP(Model Context Protocol)接入工具服务,由 qmd MCP server 提供知识检索能力。

3.1 这套方案的核心逻辑

MCP 是 Anthropic 在 2024 年底推出的开放协议,目标是让 LLM 与外部工具的交互标准化。在 MCP 之前,每个 Agent 框架都有自己的工具调用格式(OpenAI Function Calling、LangChain Tools、AutoGPT Plugins 等),导致工具生态严重碎片化。MCP 的出现相当于在工具层建立了一个"USB-C 接口"——只要工具实现了 MCP server,任何支持 MCP client 的 Agent 都能调用它。

Hermes 的 skill 体系与 MCP 工具体系是两个平行系统。skill 文件存放在 ~/.hermes/skills/ 下,格式为带 YAML frontmatter 的 Markdown(SKILL.md),内容是自然语言的操作指南与领域知识,供 Agent 阅读理解。MCP server 则在 ~/.hermes/config.yaml 的 mcp_servers: 段中注册,其工具能力(名称、描述、参数 schema)通过 MCP 协议自身的 tools/list JSON-RPC 方法在运行时动态暴露——Agent 读取的是 MCP server 返回的结构化 schema,而非 skill 文件。此外,Hermes 还部署了一个独立的 qmd MCP server(@tobilu/qmd),专门负责本地知识库的语义检索,与 skill 文件和工具 MCP server 形成互补。

3.2 利:标准化带来的生态红利

第一,工具复用性极大提升。 Hermes 系统集成了 11 个 MCP server(filesystem、git、github、memory、fetch、time、duckduckgo 等)。这些 server 不是 Hermes 团队自己写的,而是直接复用社区生态中的现成实现。例如 filesystem MCP server 可以被任何支持 MCP 的 Agent 使用,Hermes 不需要重新造轮子。

第二,skill 与工具的互补性。 skill 文件中的自然语言内容帮助 Agent 理解"在什么场景下应该调用什么工具",而 MCP 协议提供的结构化 schema 则确保工具调用的参数格式正确。例如当 Agent 阅读了 hermes-mcp-debug/SKILL.md 中关于 MCP 配置错误的排查步骤后,它会知道在出现 name 'StdioServerParameters' is not defined 时应使用 terminal 工具安装 mcp 包;而 read_file 工具所需的 pathoffsetlimit 等参数格式,则由 MCP server 通过 tools/list 动态暴露的 schema 精确约束。skill 解决"何时做",MCP schema 解决"怎么做"。

第三,动态发现与热更新。 MCP server 可以在运行时通过 notifications/tools/list_changed 通知 Hermes 工具列表变更,Agent 不需要重启就能感知新工具的可用性。与此同时,知识库侧通过 qmd MCP server 的语义检索能力,新写入的 Markdown 知识文档在索引后立即可被检索。两个系统各自独立热更新:工具层由 MCP 协议驱动,知识层由 qmd 检索服务驱动。这种"知识即代码、工具即服务"的分离架构,让知识库和工具集的迭代互不阻塞。

3.3 弊:协议层叠加的隐性成本

第一,延迟叠加。 一次 MCP 工具调用至少包含三个网络/进程间通信步骤:Agent → MCP Client → MCP Server → 目标服务 → 返回。即使所有组件都在本地运行,进程间通信(IPC)或 localhost HTTP 的延迟也在 5–20ms 级别。如果一次任务需要调用 5–10 个工具,延迟会累积到 50–200ms。对于用户感知的"秒级响应"而言,这不是问题;但对于高频批处理任务(如 RalphLoop 中的大规模任务队列),这种延迟会成为瓶颈。

第二,上下文膨胀。 MCP 协议要求将工具的完整描述(名称、描述、参数 schema、示例)放入 LLM 的 system prompt 中。当可用的 MCP server 超过 10 个时,工具描述的总 token 数可能达到 2000–5000 tokens。对于 7B 的本地模型,上下文窗口通常只有 8K–32K,工具描述会挤占用户输入和检索结果的可用空间。Hermes 的缓解策略是按需加载——只将当前阶段需要的 MCP server 描述放入上下文,而非全部加载。

第三,调试复杂度指数级上升。 当一次任务执行失败时,问题可能出现在 MCP Client 的 JSON 序列化、MCP Server 的参数校验、目标服务的业务逻辑、或者 LLM 生成的调用格式中任意一环。相比直接调用本地函数的单一调用栈,MCP 的调用链路增加了至少两层抽象,日志分散在多个进程中,问题定位的难度显著增加。Hermes 的 harness 约束层通过状态机编辑历史追踪来缓解这个问题,但无法完全消除。

第四,过度工程化风险。 MCP 的价值在"工具生态互通"的场景下才能体现。如果 Hermes 的所有工具都是自己内部维护的、不会与外部系统共享,那么 MCP 协议带来的标准化收益会被其协议开销抵消。在 Hermes 的当前架构中,MCP 的主要价值在于复用社区工具(如 playwright、sequential-thinking)和标准化工具描述格式,而非连接外部第三方系统。


四、Hermes-agent 本地 RAG 完整流程拆解

现在进入最核心的部分:Hermes-agent 官方推荐的本地 RAG 流程。这个流程的设计目标非常明确:查询延迟 2–3 秒、内存占用约 2G(嵌入阶段)、无需任何远程 API、完全 HTTP 本地通信

4.1 知识准备:Markdown 文档作为统一知识源

Hermes 的知识库以 Markdown 文档为核心载体,分布在 harness-knowledge/~/.hermes/skills/ 等目录中。这些文档分为两类:

静态文档类:如 hermes-docs/AGENTS.mdknowledge_base/sage/论文写作指南.md,包含领域知识、操作规范、最佳实践。这类文档的特点是内容稳定、更新频率低,适合一次性切分后长期索引。

动态 skill 类:如 ~/.hermes/skills/post-exploitation-skill/SKILL.md~/.hermes/skills/hermes-mcp-debug/SKILL.md,包含特定任务的操作指南、故障排查步骤、最佳实践。这类文档的特点是与 Agent 的执行场景强关联,帮助 Agent 判断"在什么情况下应该调用什么工具"。

需要注意的是,skill 文件是自然语言知识文档,不是机器可读的工具 schema。实际的 SKILL.md 文件结构如下(以渗透测试 skill 为例):

---
pinned: true
---

# Post-Exploitation Skill

Local enumeration toolkit for compromised systems during penetration testing engagements.

## Usage

```bash
python3 scripts/postexploit.py --all
python3 scripts/postexploit.py --user-info --network --processes
``` {data-source-line="239"}

## Features

- **System Information**: Gather hostname, OS, architecture
- **User Enumeration**: Current user, privileges, UID/GID
- **Network Discovery**: Interfaces, IP addresses, routing table

## Arguments

| Argument | Description |
|----------|-------------|
| `--all, -a` | Run all enumeration checks |
| `--user-info, -u` | Gather user information |

## Output

The script displays collected information in organized sections, with optional JSON output.

这份 Markdown 文档同时服务于两个角色:人类可读的操作手册嵌入阶段的文本输入。QMD 在索引阶段会提取 Markdown 正文部分,由内置的 Gemma 300M 模型生成嵌入向量存入 sqlite-vec;Agent 在检索到该文档后,阅读其中的自然语言内容来理解何时以及如何执行相关操作。工具的具体参数格式则由 MCP server 通过 tools/list 协议动态提供,两者各司其职。

4.2 文档切分:不是按字符切,而是按语义结构切

naive 的文档切分方式是按固定长度(如 500 tokens)切分,但这种方式会破坏文档的语义结构——一个完整的"执行步骤"段落可能被拦腰切断。Hermes 采用基于文档结构的分块策略

  1. 标题层级切分:以 ##### 等标题为边界,每个区块作为一个独立 chunk。这样"执行步骤"和"注意事项"不会混在一起。
  2. 代码块保护:Markdown 中的代码块(如 nmap 命令示例)被视为不可分割单元,避免切分后代码失去可执行性。
  3. 元数据继承:每个 chunk 继承原始文档的 YAML 元数据(如 pinnedcategory),确保检索出的 chunk 带有完整的上下文信息。

4.3 QMD 内部检索链路:一次 MCP 调用背后的三层流水线

与需要自己拼接 bge-m3 + sqlite-vec + 外部重排模型的方案不同,qmd 把完整的检索流水线封装在了一个 MCP 工具调用内部。当 Agent 调用 mcp_qmd_query 时,qmd 内部执行以下步骤:

步骤 1:查询扩展(Query Expansion)

1.7B Q4_K_M 模型接收用户原始查询,生成语义等价的扩展查询。例如将"怎么审计 SSH 配置"扩展为包含 sshd_configauthorized_keys端口检查 等关键概念的多个查询变体。这一步的价值在于弥合用户词汇与文档词汇之间的鸿沟——用户可能说"看 SSH 有没有问题",而文档中的关键词是"SSH 安全审计检查清单"。

步骤 2:BM25 全文检索(Lexical Search)

使用 SQLite FTS5 对扩展后的查询进行关键词匹配,召回包含精确术语的候选文档。BM25 的优势在于对罕见词、专有名词(如 CVE 编号、端口号、文件名)的精准召回,不会漏掉"CVE-2024-XXXX"这种精确匹配。

步骤 3:向量语义检索(Vector Search)

Gemma 300M 将扩展查询编码为向量,在 sqlite-vec 索引中做近似最近邻搜索(ANN)。向量检索负责语义层面的召回——即使查询中没有出现"sqlmap"这个词,也能匹配到包含"SQL 注入自动化检测"的文档片段。

步骤 4:结果融合(Fusion)

将 BM25 和向量检索的结果合并,使用类似 Reciprocal Rank Fusion(RRF)的算法综合排序,得到统一的 Top-K 候选集。两路检索互补:BM25 解决"精确术语"问题,向量检索解决"同义词"问题。

步骤 5:LLM 重排(Re-ranking)

qwen3-reranker-0.6b 模型对融合后的 Top-K 候选进行精排。0.6B 的参数量虽然是三个模型中最小的,但它的计算模式与 embedding 和 query expansion 完全不同——reranker 不是只处理 1 个 query,而是要对 40 个候选文档各做一次前向推理

QMD 的默认配置是取 RRF 融合后的 Top-30 候选进入 reranking,每个候选文档的 chunk 长度约 800 tokens,reranker 的 context size 为 1024。这意味着单次查询中 reranker 需要执行约 40 次 attention(query, doc_chunk) 的前向计算(含 query expansion 产生的扩展查询)。在纯 CPU 环境下(GPU: none),这会增加 2–5 秒 的额外延迟。

那么 reranker 值得这 2–5 秒吗? 取决于查询类型:

  • 客观/事实类查询(如"如何配置 Hermes MCP"):BM25 已经能精准命中关键词,向量检索补充了同义表达,reranking 对 Top-3 结果顺序的改变很有限。这种情况下 reranker 的增量价值较低。
  • 主观/模糊类查询(如"Feynman Engine 的设计取舍是什么"):查询本身没有明确的关键词,BM25 召回的文档可能相关性参差不齐,reranker 通过语义级别的精细打分能显著改变 Top-3 的最终排序,把真正相关的文档推到前面。

QMD 的融合策略也做了针对性设计:RRF rank 1–3 的文档只让 reranker 影响 25% 的权重(保护高置信度的精确匹配),rank 4–10 影响 40%,rank 11+ 影响 60%(让 reranker 在低置信度区域发挥更大作用)。这种位置感知的混合策略避免了 reranker 过度干预已经由高置信度检索确认的结果。

如果检索质量已经让你满意,可以通过在查询参数中设置 rerank: false 来禁用 reranker,省去这 2–5 秒的 CPU 开销。但对于模糊查询占比较高的使用场景,建议保留。

步骤 6:返回结果

qmd 将重排后的 Top-5(或 Top-10)文档片段通过 MCP 协议返回给 Agent。Agent 随后阅读这些片段中的自然语言内容,理解操作步骤,再通过 MCP 工具执行具体操作。

整个链路从 Agent 视角看就是一次 mcp_qmd_query 调用。不含 reranker 时延迟约 1–1.5 秒(查询扩展 + BM25 + 向量检索);启用 reranker 后延迟约 3–6 秒(reranker 在纯 CPU 上处理 40 个候选文档需要额外 2–5 秒)。reranker 刚补齐,含 reranker 的精确延迟还需实测验证。

4.4 索引与存储:SQLite 一统天下

qmd 的索引架构极简:所有数据都存在 SQLite 中,没有外部数据库依赖。

FTS5 全文索引:用于 BM25 检索,支持中文分词和关键词高亮。

sqlite-vec 向量索引:用于语义检索,将 Gemma 300M 生成的向量以二进制形式存储在 SQLite 表中,通过 VSS(Vector Similarity Search)扩展实现近似最近邻查询。sqlite-vec 的优势是零外部依赖——整个向量库就是一个 .db 文件,备份、迁移、版本控制都极为简单。

文档元数据表:存储文档路径、标题、切分边界、嵌入状态等信息。当前索引状态示例:

总文档数: 1972
向量索引: 已启用
需要嵌入: 0(全部完成)

集合:
  hermes-kb:     /home/**/Hermes/knowledge_base (986 docs)
  hermes-topics: (986 docs)

4.5 性能基准实测数据

以下数据来自实际运行环境:

指标 数值 说明
嵌入模型 Gemma 300M Q8_0 ~319MB,负责文档/查询向量化
查询扩展模型 1.7B Q4_K_M ~1.2GB,负责查询扩展
重排模型 qwen3-reranker-0.6b Q8_0 轻量级,负责结果精排
推理引擎 node-llama-cpp 本地加载 GGUF,无需 Ollama
全文检索 SQLite FTS5 BM25 关键词匹配
向量检索 sqlite-vec ANN 近似最近邻
总文档数 1972 两个集合各约 986 篇
单次查询延迟(无 reranker) ~1–1.5 秒 查询扩展 + BM25 + 向量检索
单次查询延迟(含 reranker) ~3–6 秒 reranker 刚补齐,待实测精确值
其中 reranker 增量 +2–5 秒 40 候选 × 800 tokens,纯 CPU 前向
运行内存 ~2.5–3 GB 三个 GGUF 模型(2.1GB)+ node-llama-cpp 运行时 + sqlite 缓存
远程 API 依赖 全链路本地运行

这个性能指标的关键在于模型选型的精准。Gemma 300M 虽然参数只有 300M,但在嵌入任务上的表现足以支撑知识库检索;1.7B Q4_K_M 作为查询扩展模型,参数量刚好卡在"能完成语义理解"和"不会拖慢查询"的平衡点上;qwen3-reranker-0.6b 作为独立重排模型,在 0.6B 的轻量体积下提供了语义级别的精排能力。如果用更大的模型(如 7B)做嵌入或重排,查询延迟会暴涨到 10 秒以上,失去实用价值。

六、我的老破旧笔记本最终选型

以上所有理论分析,最终都要落地到一台具体的机器上。我用的设备是一台搭载了 AMD Ryzen 7 7735HS(8C16T)处理器、32GB DDR5 内存、AMD Radeon 680M 集成显卡的笔记本——没有独显,没有 NPU,纯靠 CPU 硬跑。

6.1 实际部署配置

先澄清一个容易混淆的点:QMD 负责检索,Hermes 负责生成,两者是独立的。

层级 组件 实际选型 体积/占用
知识源 Markdown 文档 harness-knowledge/ + ~/.hermes/skills/ ~200MB(1972 篇文档)
全文检索 SQLite FTS5 qmd 内置 零额外占用
向量检索 sqlite-vec + Gemma 300M qmd 内置 ~319MB(模型)
查询扩展 1.7B Q4_K_M qmd 内置 ~1.2GB(模型)
结果重排 qwen3-reranker-0.6b Q8_0 qmd 内置 轻量级
检索侧总内存 qmd ~2.5–3GB
生成模型 MiniMax-M2.7-highspeed 远程 API 零本地占用
生成 fallback kimi-k2.6 远程 API 零本地占用

关键发现:我没有用 Ollama。

当前 Hermes 的 config.yaml 中,model.provider 配置为 minimax-cn,默认模型是 MiniMax-M2.7-highspeed,base_url 指向 https://api.minimax.chat/v1。Ollama 没有在运行,系统中也未配置任何本地 GGUF 生成模型。

这意味着我的实际架构是**"本地检索 + 远程生成"**:

  • 检索侧:QMD 完全本地,2GB 内存,零网络依赖,零 API 费用
  • 生成侧:MiniMax 远程 API,有网络依赖,有 API 费用(但成本低)

6.2 为什么检索侧坚持本地,生成侧可以接受远程?

检索侧必须本地的原因

  1. 隐私刚性约束。知识库中存放的是 skill 文档、操作指南、系统配置说明。这些文档虽然不如 SSH 密钥敏感,但一旦通过远程 Embedding API 外发,就存在数据泄露风险。QMD 的本地 embedding(Gemma 300M)确保文档内容不出本机。
  2. 延迟稳定性。检索是高频操作——每次 Agent 执行任务前都要查一次知识库。如果检索走远程 API,网络波动会直接拖慢整个工作流。QMD 的 2–3 秒延迟虽然不算快,但方差极小。
  3. 成本可控。1972 篇文档的嵌入如果走 API,按 token 计费轻松超过几十美元。本地一次性嵌入后,后续查询零费用。

生成侧接受远程的原因

  1. 质量差距。7B 级别的本地模型(如 qwen2.5:7b)在复杂推理、代码生成、多语言理解上与 MiniMax-M2.7 这类商业模型仍有明显差距。对于 Hermes 这种需要处理渗透测试报告、代码重构方案、学术论文等复杂任务的智能体系统,生成质量是硬需求。
  2. 成本可接受。MiniMax API 的价格远低于 OpenAI/Claude,日常使用费用在可承受范围内。
  3. 检索结果脱敏。生成阶段的 prompt 只包含检索出的 Top-K 文档片段,而非完整知识库。即使走远程 API,外发的数据量也远小于直接上传全部文档做嵌入。

6.3 为什么没有选 bge-m3 + 外部重排的自组方案

在折腾知识库的过程中,我尝试过 ollama+bge-m3、karpathy 的 llm wiki、知识图谱等方案,最后全部回滚。核心原因是组合复杂度——每增加一个外部组件(bge-m3 做嵌入、flan-t5 做扩展、cross-encoder 做重排、Chroma 做向量存储),就多了一层配置、多了一种故障模式、多了一份维护负担。

qmd 的价值在于把嵌入、扩展、重排、存储全部打包成了一个黑箱。三个 GGUF 模型(300M + 1.7B + 0.6B)由 node-llama-cpp 统一加载,FTS5 和 sqlite-vec 共用同一个 SQLite 文件,Agent 只需要调用 mcp_qmd_query 就能拿到结果。对于个人使用场景,"能工作"比"完全可控"更重要。

6.4 性能与质量的实测感受

检索质量:够用,且 reranker 在模糊查询上有明显价值。 对于"SSH 安全配置审计"、"MCP 调试方法"这类有明确关键词的查询,BM25 + 向量检索已经能稳定召回正确文档,reranker 对结果顺序的微调增量有限。但对于"Feynman Engine 的设计取舍"这种没有明确关键词的模糊查询,reranker 能把语义最相关的文档从中间位置推到 Top-3,改善幅度明显。

Reranker 的取舍建议:如果你 80% 的查询都是客观事实类(有明确关键词),可以考虑在查询参数中禁用 reranker(rerank: false),把单次查询延迟从 2–3 秒降到 1–1.5 秒。如果查询类型比较杂、模糊查询占比不低,保留 reranker 的收益大于成本。

响应延迟:可接受。 检索侧 2–3 秒(纯本地,方差极小),生成侧 1–3 秒(取决于远程 API 负载)。总端到端延迟约 3–6 秒,对于 Agent 的交互式使用来说在可接受范围内。

资源占用:检索侧可控。 QMD 三个模型合计约 2.1GB,加上 node-llama-cpp 运行时和 sqlite 缓存,总内存占用约 2.5–3GB。在 32GB 的机器上完全无压力。如果未来想完全离线运行,可以在同一台机器上加装 Ollama(约 5GB 内存),切换生成模型为本地 qwen2.5:7b——但当前远程生成的质量让我暂时没有这个动力。

6.5 自动维护机制(非常重要)

知识库不是建好了就一劳永逸的。文档在更新、skill 在增加、索引会过时,手动维护很快就会变成负担。我的实现方式是:监控知识库目录的总大小,当变化量超过 20MB 时自动触发 qmd 重新索引

这个阈值的选择逻辑:20MB 大约对应几十篇新增或大幅修改的文档,既不会因为小幅修改频繁重建索引,也不会让大量新增文档长时间处于未索引状态。QMD 支持增量索引,只有新增/修改的文档会被重新嵌入,不会每次都全量重建。

如果你没有自动维护机制,至少养成一个习惯:每次大规模更新知识库后,执行一次 mcp_qmd_status 检查待嵌入数量,确保所有文档都已入库。

6.6 最终结论

我的实际选型可以概括为一句话:检索本地化,生成云化

QMD 把全文检索、向量语义检索、查询扩展、结果重排四个环节打包成了一个开箱即用的 MCP 服务,用三个轻量级 GGUF 模型(300M + 1.7B + 0.6B)在 2GB 内存内完成了传统方案需要 bge-m3 + cross-encoder + 外部 seq2seq 才能实现的检索质量。检索侧完全本地、零网络依赖、零 API 费用,这是隐私和成本的底线。

生成侧目前使用 MiniMax 远程 API,是因为本地 7B 模型的质量尚不足以替代商业模型。如果未来本地模型能力进一步提升(或者我对生成质量的要求降低),随时可以加装 Ollama 切换到全本地闭环——QMD 的检索侧已经为那一天做好了准备。

对于"老破旧笔记本"这个约束条件,这套"本地检索 + 远程生成"的混合架构是当前最务实的平衡点:用最小的本地资源占用(2GB)保住了知识库的核心价值(隐私、稳定、低成本),同时用远程 API 的边际成本换来了生成质量的硬保障。

还有啊,你可别忘了搭建好了要校验,MINIMAX经常缺斤少两的,少了哪个模型也不跟你说的,qwen3-reranker-0.6b是我后来校验发现的。


本文基于 Hermes 多智能体系统的实际架构与本地实测数据撰写。qmd 的索引状态、模型体积、查询延迟等数据均来自实际运行环境,非理论推算。

Logo

更多推荐