MCP

OpenShelf:让 AI 在你的知识边界内思考

一篇关于个人知识库与 Agent 推理边界的设计说明:为什么知识库不应该只包含搜索,还应该约束它使用什么知识、方法和经验解决问题。

kaiweizhang
#MCP#Agent#知识库边界
OpenShelf 知识边界与 Agent 推理设计研究视觉图
OpenShelf 将知识库从答案仓库推进为 Agent 生成、检查和重写方案时使用的推理边界。

OpenShelf 希望知识库直接参与 AI 的思考过程:让 AI 根据用户的知识范围、编程习惯与风格,优先探索更可能被接受的解法,并在偏离边界时主动换一种思路。

摘要

OpenShelf 是一个本地优先的 MCP Server,用于从 PDF 和本地资料构建可查询的 DuckDB 知识库。

传统知识库主要帮助模型回答“资料中有什么”。OpenShelf 更关心另一个问题:知识库能否改变 AI 寻找答案的方向,让它根据用户的知识范围、历史选择和软偏好,主动寻找更可能被接受的解决方案?

一套高中数学教材不一定包含某道难题的答案,却定义了学生可以使用的知识原语。困难的高中数学题也许可以通过超纲知识解答,但重要的能力则是在高中知识框架内进行长链条的思考。AI 个人助手的历史记录不一定包含下一次编程任务的实现,却记录了主人在类似情境中接受或拒绝过哪些方案。暴力求解也许可行,但是好的代码应该结合时间和空间的优化,在硬件条件约束下提出最优的算法。AI 能力日益强大,除了应该提出一个正确的答案,AI 还应该提出能被接受的答案。

简单的提示词工程也许不能妥善解决此类问题。“使用高中知识回答”是模糊的,因为世界各地不同的高中有不同的知识框架。“单次代码执行不能运行超过两个小时,不能使用超过 16G 内存”对用户提出了较专业的要求,面向非专业用户的场景更多的应该是“用户上次手动停止了运行了一个小时的代码并询问是否可以加速”,或者“用户拒绝了我提出的新开一个 32G 内存计算机的请求”。

因此,OpenShelf 的目标不是让 AI 只会复述知识库,也不只是等答案生成后再做一次合规检查。检索到的知识、方法和历史先例应该进入 AI 的思考过程,影响候选方案的生成与排序;如果一个方向超纲或不符合用户习惯,AI 就应该带着具体原因切换方向,继续寻找更可能被接受的方案。

这是一种“知识原语闭合、推理组合开放”的工作方式。

1. AI 要解决的不只是“什么答案正确”,还包括“什么答案会被接受”

无论 AI 的能力有多强,真实任务都不是一个没有规则的答案搜索问题。用户通常不只在意结果是否正确,也在意答案使用了什么知识、采用了什么方法,以及这个方案是否适合自己的背景和习惯。

考虑一道高中数学难题。

它可能有一个很短的大学数学解法:求导、拉格朗日乘子、泰勒展开,或者某个学生从未学过的定理。答案在数学上完全正确,却不是用户真正需要的答案。

AI 很可能知道多个正确答案。但用户真正需要的是:

AI 能否只使用这个学生真正学过的知识,把题目解出来?

简单地在提示词中写“请使用高中知识”并不能完整解决问题。“高中知识”并不是一个全球统一的集合:中国理科高中生可能学过导数,文科生的课程不同;美国不同州、学校和课程体系是否教授微积分也不一样。

因此,真正可靠的边界不是一个模糊标签,而是这个用户实际使用过的教材、课程资料和学习记录。

把这些资料放进个人知识库后,知识库应该在解题开始时就影响 AI 的思考模式:微积分方法不在当前知识范围内,就不应该成为优先候选;教材中反复出现的配方法、基本不等式和几何变换,则应该获得更高优先级。

这并不是限制 AI 的能力,而是给它的能力提供方向。难题的价值恰恰可能在于:允许使用的知识很初等,但需要把这些知识组合得足够深。AI 应该把计算和推理能力花在这个被用户接受的解法空间里。

2. OpenShelf 与“只能搜索知识库”的系统有什么不同

普通知识库或 RAG 通常执行一条较短的链路:

问题 -> 检索相关文本 -> 把文本交给模型 -> 生成答案

OpenShelf 想支持的是另一条链路:

问题 -> 判断知识库是否适用 -> 提出候选解法
     -> 提取解法使用的知识与方法 -> 检查是否越界
     -> 若越界则重新求解 -> 返回边界内的答案

知识库在这里不只是答案仓库,而是影响候选方案生成、排序和修改的推理参照系。

这一区别很重要。一个新问题通常不会原样出现在教材或历史记录里,因此“没有直接答案”不能等同于“知识库没有作用”。在大量真实任务中,知识库最重要的作用不是提供结论,而是提供允许使用的定义、方法、先例与判断依据。

传统知识库检索与 OpenShelf 希望支持的模式有几个关键差异:

  • 知识库的角色。 传统检索提供与问题相关的文本;OpenShelf 让知识库影响 AI 应该沿哪些方向思考。
  • 搜到直接答案。 二者都可以引用原文回答。
  • 只有相关材料。 传统检索通常把相关材料作为上下文交给模型,或者报告证据不足;OpenShelf 将相关知识、方法和先例作为候选方案的生成依据与审核边界。
  • 没有现成答案。 传统知识库的工作基本结束;OpenShelf 仍可让 AI 组合库内原语继续求解,并根据审核结果反复调整。
  • 最终目标。 传统知识库寻找知识库里的答案;OpenShelf 寻找用户更可能接受的答案。

因此,OpenShelf 的关键不是把搜索做得更像聊天,而是让搜索结果能够改变后续推理。related_only 不是一个消极的失败状态,而是一个控制信号:知识库无法直接给出答案,但它已经提供了思考这个问题时应该参考的边界。

3. 三种检索状态,三种不同的推理方式

OpenShelf 使用 supportedrelated_onlynot_found 区分知识库在当前任务中的作用。

supported:知识库可以直接回答

知识库中存在足够直接的文本证据。例如:

  • “教材如何定义导数?”
  • “这篇论文的 Proposition 3 说了什么?”
  • “主人上次为什么拒绝这个计算计划?”

此时,Agent 应该基于原文、页码和引用回答。这与标准的数据库问答产品无异。

related_only:没有现成答案,但知识库定义了推理边界

一道新数学题通常属于这种情况。教材没有写出最终答案,却包含可以使用的定义、定理和解题方法。

此时,related_only 不应该意味着停止工作,也不意味着马上抛开知识库自由作答。它意味着 Agent 应该进入“边界内推理”模式:

  1. 生成候选解法;
  2. 提取解法使用的关键技能;
  3. 在知识库中检查这些技能;
  4. 如果全部得到支持,返回解法;
  5. 如果某些技能超出边界,根据违规项重新求解。

技能在解题和科研场景下指定理和解题思路,而在编程场景下则代表了用户的习惯和风格。

not_found:知识库与当前任务没有实质关系

如果数学教材知识库面对的是“帮我写一封生日邀请函”,强行从教材中寻找证据没有意义。此时 Agent 可以回到普通推理模式。

not_found 不能只表示一次关键词检索没有命中。系统需要避免因为同义表达、翻译或召回失败而错误绕过知识边界。更准确地说,它应该表示:经过查询改写和必要的补充检索后,仍然没有迹象表明这个知识库适用于当前问题。

4. 为什么同时需要 page chunk 和 technical chunk

OpenShelf 不是简单地把 PDF 切成一种固定大小的文本块。它需要同时回答两个不同的问题:

  1. 原文在哪里说了什么?
  2. Agent 可以使用哪些知识原语进行推理?

这正是 page chunk 和 technical chunk 分工的原因。

  • Page chunk。 解决“原文在哪里、上下文是什么”的问题,保存页码、正文片段、相邻 chunk 和文档信息,典型用途是 BM25 召回、直接证据、引用和页面回看。
  • Technical chunk / result。 解决“这里定义或证明了什么可复用对象”的问题,保存类型、编号、陈述、假设、结论、证明、公式引用和相关定义,典型用途是方法检索、知识原语识别和候选解法的越界检查。

4.1 Page chunk 是证据定位层

普通入库首先保留 PDF 的页面文本,并生成带页码的重叠 chunk。它适合回答:

  • 哪个页面出现了这些概念?
  • 原文的前后语境是什么?
  • 这段回答应该引用哪一页?
  • 检索结果能否回到页面文本或页面图片验证?

Page chunk 的核心价值是忠实保留原文和位置。它不试图理解一段文字在逻辑结构中究竟是定义、假设、定理还是证明。

4.2 Technical chunk 是推理原语层

对于数学、经济学和其他理论文档,固定长度切块会破坏知识对象的边界:一个定理的假设可能在上一段,结论在下一段,证明跨越多个普通 chunk;“Proposition 3”在正文中的一次引用,也不能被误认为命题本身。

因此,OpenShelf 可以从已经入库的页面和 chunk 中进一步恢复结构化 technical result,包括:

{
  "result_type": "theorem | lemma | proposition | definition | assumption",
  "result_label": "Proposition 3",
  "page_start": 18,
  "page_end": 19,
  "statement_text": "...",
  "assumption_text": "...",
  "conclusion_text": "...",
  "proof_text": "...",
  "formula_refs": ["(6)", "(21)"],
  "related_definitions": ["Definition 2"],
  "chunk_ids": ["...", "..."]
}

它不是“更小的 page chunk”,而是另一种数据对象:一个能够被检索、引用、检查前提,并用于后续推理的知识单元。

Technical result 同时保留 page_startpage_end 和原始 chunk_ids,所以结构化并没有切断证据链。Agent 可以先找到一个定理,再回到对应 page chunk、完整页面文本甚至页面图片检查原文。

当前实现还为 source_linksderived_fromrelation_candidates 和 technical-result links 预留了 lineage 接口,使未来可以表达“某个论文命题使用了哪条教材定理”“一个结论依赖什么定义或公式”。

4.3 两层索引为什么缺一不可

如果只有 page chunk,系统可以找到“谈到导数的页面”,却不容易判断候选答案究竟使用了哪个定理、该定理的适用条件是什么。

如果只有 technical chunk,系统又会丢失完整上下文、普通叙述和可靠的页面定位。

因此,两层索引分别承担:

Page chunk       = 证据、上下文与出处
Technical result = 可使用和可审计的知识原语

OpenShelf 的推理边界依赖二者之间的连接,而不是依赖某一种切块策略单独完成全部工作。

5. 从检索到“生成、审计、重写”

OpenShelf 面向的是带 MCP 工具的 Agent。MCP 负责检索、返回结构化知识对象并审核方法边界;候选解法的生成和重写仍由 Agent 完成。

flowchart TD
    A["用户问题"] --> B["检索 OpenShelf"]
    B --> C{"知识库与问题的关系"}

    C -->|"supported"| D["根据直接证据回答"]
    C -->|"not_found"| E["进入普通推理模式"]
    C -->|"related_only"| P["取回可用知识原语与相似先例"]
    P --> F["据此生成并排序候选方案"]

    F --> G["提取方法与资源清单"]
    G --> H["对照知识库进行边界审计"]

    H --> I{"审计结果"}
    I -->|"passed"| J["返回框架内的解答"]
    I -->|"failed"| K["列出超纲知识或不符合偏好的方法"]
    K --> L["降低违规方向的优先级并重写方案"]
    L --> F
    I -->|"uncertain"| M["向用户请求澄清"]

6. 硬约束应该写进 prompt,知识库主要保存难以言表的软边界

知识库并不是所有约束的最佳载体。

如果一条规则明确、稳定且必须执行,最直接的方法就是把它写进 system prompt、Agent policy 或当前任务提示词:

未经确认不得调用付费 GPU。
单次任务最多运行 30 分钟。
回答中不得泄露客户数据。

这类硬约束不应该依赖检索是否命中。它们应该在每次任务中稳定生效。

OpenShelf 更有价值的地方,是保存那些难以一次说清楚的软偏好、习惯和情境化判断。它们通常具有以下特点:

  • 用户自己也很难总结成一条简洁规则;
  • 如果写成提示词,需要附加大量定语和例外;
  • 同一个选择在不同数据规模、期限和资源条件下含义不同;
  • 用户没有明确陈述偏好,但在多次批准、拒绝和修改中表现出稳定倾向。

例如,用户曾经拒绝一个使用 Python for loop、预计运行四小时的方案,并要求改成并行实现。这并不等于“用户永远禁止循环”。更合理的记忆是:

{
  "task_context": "大规模数据处理",
  "proposed_plan": "Python serial for loop",
  "estimated_runtime": "4 hours",
  "user_decision": "rejected",
  "requested_revision": "parallelize the computation",
  "preference_signal": "在大规模、长运行任务中偏好并行或向量化方案"
}

下一次面对相似任务,Agent 可以检索这些历史先例,在提交计划之前主动检查:当前串行实现是否又会产生过长运行时间?是否应当先改成批处理、向量化或并行方案?

如果用户主要通过 Vecbase 与 AI 工作,任务、候选方案、资源估计、用户批准或拒绝、修改要求以及最后采用的方案,都可以被持续提炼为这类结构化记忆。时间越长,这个知识库越接近一份难以通过一次提示词写出的个人工作方式模型。

它不是一张静态的“用户偏好清单”,而是一组带上下文的决策先例。历史批准可以帮助预测用户偏好,但不能自动构成新的资源授权。Agent 可以根据历史先例优化计划;如果新任务涉及新的成本、权限或风险,仍然请求用户明确批准。

7. OpenShelf 当前如何实现这套基础设施

OpenShelf 当前的公开实现聚焦于本地 PDF 知识库:

  • 一个知识库对应一个物理 DuckDB 文件;
  • 多个知识库通过 db_name 硬隔离,不做隐式 union search;
  • PDF 页面文本、page chunk 和词项统计保存在 DuckDB;
  • 默认使用 DuckDB-backed BM25 进行可解释的文本检索;
  • searchsearch_terms 返回 supportedrelated_onlynot_found
  • build_technical_index 可以预览或写入结构化 technical results;
  • search_technical_results 可以检索定理、定义、命题、假设、证明、公式上下文和附近定义;
  • check_reasonable 可以提取候选答案中的技术方法,并检查这些方法是否存在于 closed corpus;
  • get_chunkget_page_textget_page_image 用于回到原始证据。

普通入库自动创建 page chunk。Technical result 是按需构建的第二层索引:默认可以动态抽取并只读搜索;只有同时显式设置 dry_run: falsewrite: true 时才写入可选的 technical_results 表。

7.1 一个 DuckDB 文件,就是一套可以迁移的知识边界

OpenShelf 选择 DuckDB 的一个重要原因,是知识库不需要依赖持续运行的数据库服务。文档元数据、页面文本、page chunk、词项统计和可选的 technical results 可以集中保存在一个 .duckdb 文件中。

这使知识库接近于一个可以一键迁移和注册的产品单元:把 .duckdb 文件复制到另一台机器或交给另一个用户,再用 create_db_from_exist 注册,就可以直接使用,不需要重新解析和索引整套资料。

知识边界因此可以被标准化、组合和分发。例如,可以制作:

  • “中国人教版高中数学”知识库;
  • 针对文科、理科或不同地区课程范围的版本;
  • AP Calculus、IB Mathematics 等课程知识库;
  • 某个行业的标准操作手册知识库;
  • 某一研究方向的经典论文与工具书知识库。

用户既可以直接使用一套标准知识库,也可以根据自己的真实经历制作个人版本:把自己读过的教材、工具书、论文集和工作资料入库。这样,同一个问题在不同知识库下可以得到不同但都合理的答案。差异不是来自随机 prompt,而是来自用户选择的知识范围。

8. 结论

个人知识库不应该只是让 AI 搜索和记住更多事实。硬约束可以直接写进 prompt。真正需要长期知识库的,是那些带上下文、难以言表、通过多次互动才能逐渐显现的软边界。

Page chunk 让系统能够回到原文,technical result 让系统能够识别和审核可复用的知识原语。二者结合后,知识库才不只是一个搜索组件,而能够参与 Agent 的生成、检查和重写过程。

OpenShelf 建立在一个简单的判断上:

知识库不只应该告诉 AI“这里有什么”,还应该影响 AI“接下来往哪里想”。