25 KiB
当前知识库自动检索增强设计
状态:已完成 · 实施于 2026-05-28 · 对应 ADR-19 · 阶段 3.5 收尾补强
本文档是 codex 实施手册。先读 §0 决策摘要,再按 §9 顺序动手。 任何与 PRODUCT.md 冲突的描述以 PRODUCT.md 为准;本文档需要破例的地方在 §12 明确写出并对应到 ADR-19。
0. 决策摘要(codex 起手必读)
| 决策 | 选择 | 理由速记 |
|---|---|---|
| 架构路线 | 双轨:新工具 query_knowledge_base + /api/prompt 预调用同一份代码 |
工具路径符合 ADR-7;预调用兜底弱模型不调工具的情况 |
| 触发策略 | 黑名单:默认触发,特定场景跳过 | 漏触发的代价是当前 bug;误触发的代价只是浪费 token |
| 多轮检索 | 每轮 user turn 都判断 + 检索 | 用户切话题时能立刻拿到新页面,本地 grep 成本可接受 |
| 失败降级 | 静默降级裸 prompt + SSE error 事件 + 日志 | 用户体验不中断,问题可溯源 |
| 检索 cache | 复用 pages.ts 的 getCachedPages |
避免两套 cache 失效时机错位 |
@ 格式契约 |
解析消息中的 [[wiki/...]](前端 ChatPanel.tsx:218-230 已插入此格式) |
不发明新语法 |
| 上下文上限 | 按 main 模型 contextWindow 的 20% 动态算;模型未知时 fallback 4000 字符 | 兼顾 glm(4k 级)与 Claude(200k 级) |
| 验收硬标志 | 日志 + 字符串命中双重客观可查 | 防止"看起来对实际没注入"的 hallucination 蒙混过关 |
1. 问题
阶段 3.5 已实现非 wiki 目录初始化 + 批量消化,但用户进入新知识库直接问"这是我自媒体创作的文章,总结一下",主模型反问用户提供文章内容。
已确认事实:
- 批量消化结果已写入
wiki/synthesis/sessions/ /api/refs能列出这些新页面(pages.ts PAGE_DIRS 含synthesis)- 失败对话的会话日志显示模型未调用任何 KB 工具
/api/prompt(index.ts:745-832)当前是裸session.prompt(message),无任何检索 / 注入- 现有 Extension 工具
current_knowledge_base/list_knowledge_base_pages(extensions/knowledge-base.ts)描述写得够强,但 agent 实际不调
根因:主对话缺少一层"系统兜底检索"。ADR-7 设计的"靠 Extension 工具让 agent 自觉调用"在弱模型场景下不可靠。
2. 设计目标
- 用户在当前库提问,系统默认把当前库作为上下文来源
@显式引用优先级高于自动检索- 回答末尾列参考页面;空库 / 无命中时明确说明
- 不把整个库塞进每次对话;不引入新依赖
- 不改变
@引用 //命令 / 子代理批量消化的现有心智
3. 核心思路:双轨
用户提问
↓
后端解析消息中的 [[wiki/...]] → 显式引用列表 R_explicit
↓
shouldUseKnowledgeBase(message) 判断是否触发自动检索(黑名单)
↓
若触发 → 调用同一份 searchKnowledgeBase(kbPath, query, R_explicit)
↓
拼成隐藏上下文 + 原始消息 → session.prompt(wrapped)
↓
模型输出(包含末尾"参考页面")
同一份 searchKnowledgeBase 同时暴露为:
- 新工具
query_knowledge_base(query):让强模型 / 未来强 agent 显式调用,符合 ADR-7 - 后端预调用:
/api/prompt在判断需要时主动调,兜底弱模型不调工具的失败模式
这条路径只覆盖主对话。子代理批量消化(digest/batch.ts)走的是裸 prompt + only read tool,本设计不涉及它。
4. 不采用的方案
4.1 纯改 prompt / 加强 system prompt
不稳定。同一模型不同问法、不同 provider 表现差异巨大。
4.2 每次塞整库
KB 大了就慢、贵、乱,污染普通聊天。
4.3 把批量消化结果写进聊天历史
消化结果属于知识库,不属于某一次对话。塞进历史会让切会话 / 切库 / 长期使用语义错乱。
4.4 立即引入向量库
本地文本检索足够覆盖当前阶段。未来 KB 规模 > 100 篇再升级。
4.5 纯工具路径(无后端预调用)
ADR-7 的精神路线,但已被你这次"反问要文章"的对话验证为不稳。本设计保留工具(双轨的一条腿),但不依赖它。
5. 解法方案
5.1 检索函数 searchKnowledgeBase
签名(伪代码,仅说明契约):
searchKnowledgeBase(kbPath, query, options) → SearchResult[]
options:
explicitRefs: string[] // 来自 parseExplicitPageRefs,必含
maxPages: number // 默认 6
snippetMaxChars: number // 默认 600
totalBudgetChars: number // 由 /api/prompt 按模型 contextWindow 算后传入
SearchResult:
path: string // 相对 KB 根,如 wiki/synthesis/sessions/xxx.md
title: string
snippet: string
mtime: number
hitReason: 'explicit' | 'title' | 'filename' | 'body' | 'recent_synthesis' | 'kb_meta'
score: number
实现要点:
- 复用 pages.ts:调
getCachedPages(kbPath)拿候选,不另建 cache。score 函数在 pages.ts 现有 title 2 / name 1 / path 0.5 基础上扩展(见下)。 - 检索范围:
wiki/下所有 markdown(已被 pages.ts 覆盖)- 额外加 KB 元信息文件:
purpose.md/index.md/overview.md(如果存在)—— 这些是"总结这个库"类问题的最强答案源 - 现阶段不读
raw/下的原始素材(批量消化后用户应基于消化页问答)
- 排序规则(高到低):
- hitReason='explicit'(来自
[[wiki/...]]显式引用,固定排首位) - hitReason='title'(标题命中)
- hitReason='kb_meta'(purpose/index/overview 在泛化问题下加权)
- hitReason='recent_synthesis'(
wiki/synthesis/sessions/在"这些文章 / 刚刚消化 / 总结"类问题下加权,按 mtime 降序) - hitReason='filename'
- hitReason='body'
- hitReason='explicit'(来自
- 片段提取:命中 body 时,从命中位置前后各取 ~300 字符;标题/文件名命中时取页面开头 ~600 字符
- 总预算控制:累加每个 snippet 字符数,达到
totalBudgetChars立即停止;至少保证 explicitRefs 全部入选(哪怕超 budget 也强保留显式引用的截断版本) - 空 KB 返回
[],不抛错 - cache 隔离:pages.ts 的 cache 按 kbPath 隔离,retrieval 直接复用,无需自己维护
5.2 parseExplicitPageRefs
签名:
parseExplicitPageRefs(message) → string[] // 相对路径数组,如 ['wiki/synthesis/sessions/x.md']
实现要点:
- 正则匹配
\[\[(wiki\/[^\]\n]+\.md)\]\] - 去重,保持出现顺序
- 不验证文件是否存在(验证交给 searchKnowledgeBase)
5.3 shouldUseKnowledgeBase(黑名单策略)
签名:
shouldUseKnowledgeBase(message, kbSelected) → boolean
实现要点(按顺序判断,命中即返回):
- 没选中 KB →
false - 消息 trim 后以
/开头(命令)→false - 消息已含
[[wiki/...]](显式引用)→true(短路,必须检索) - 消息 trim 后字符数 < 3 →
false - 命中寒暄白名单(精确匹配,忽略标点)→
false- 集合:
你好/hi/hello/在吗/谢谢/thanks/thx/ok/好的
- 集合:
- 命中元问询白名单(含其一即跳过)→
false- 关键词:
当前模型/模型是什么/怎么设置/怎么用/界面/快捷键/登录/API key
- 关键词:
- 其余 →
true(默认触发)
关键:本策略激进地默认触发。误触发的代价只是 token,漏触发的代价是当前观察到的 bug。
5.4 buildKnowledgeContextPrompt
签名:
buildKnowledgeContextPrompt(originalMessage, kb, results) → wrappedMessage
输出格式(results 非空时):
{用户原始消息原样保留在最前}
---
[系统检索上下文 / 用户不可见]
当前知识库:
- 名称: {kb.name}
- 路径: {kb.path}
系统已从当前知识库检索到以下页面(按相关度排序):
[1] {results[0].title}
路径: {results[0].path}
{results[0].snippet}
[2] ...
回答约束:
- 必须基于以上页面内容回答;不得编造未在页面中出现的事实
- 如果以上页面不足以回答,明确说明"当前知识库未找到相关内容",禁止反问用户提供文章
- 回答末尾必须列出"参考页面",格式为 `- 《标题》:路径`
- 参考页面只能从上述检索结果中选取,禁止编造路径
results 为空时:仍包装一段简短上下文:
{用户原始消息}
---
[系统检索上下文]
当前知识库:{kb.name}
系统已尝试检索,但未在当前知识库找到与该问题相关的页面。
请明确告诉用户"当前知识库未找到相关内容",禁止反问用户提供文章,也禁止编造来源。
关键:空结果也包装,因为弱模型一旦完全没拿到上下文就会回到"反问要文章"的失败模式。强制告诉它"找过了,没有"。
5.5 主对话 /api/prompt 改造
index.ts:745-832 当前逻辑:
session.prompt(message)
改造后:
1. 解析: explicitRefs = parseExplicitPageRefs(message)
2. 判断: shouldUse = shouldUseKnowledgeBase(message, kbSelected)
3. 若 !shouldUse → 直接 session.prompt(message)(与今日行为一致)
4. 若 shouldUse:
a. 算 totalBudgetChars = floor(activeModel.contextWindow * 0.2) || 4000
b. SSE 推送 knowledge_search_start
c. try {
results = searchKnowledgeBase(kbPath, message, { explicitRefs, totalBudgetChars })
wrapped = buildKnowledgeContextPrompt(message, kb, results)
SSE 推送 knowledge_search_done { count: results.length, paths: results.map(r => r.path) }
写日志(见 §5.7)
session.prompt(wrapped)
} catch (err) {
SSE 推送 knowledge_search_error { message: err.message }
写日志
session.prompt(message) // 静默降级
}
每轮 user turn 独立判断 + 检索。不缓存判断结果。
5.6 失败降级
任何一环抛异常(IO 失败、parseExplicitPageRefs throw、buildKnowledgeContextPrompt 字符数爆掉):
- 不中断 prompt
- 降级为裸
session.prompt(message) - SSE 推
knowledge_search_error事件,前端在状态栏轻量显示"知识库检索失败,已按普通对话处理" - 日志(见 §5.7)写完整 error stack
5.7 检索日志
写入 ~/.llm-wiki-agent/logs/retrieval/<YYYY-MM-DD>.jsonl,每行:
{
"ts": 1735000000000,
"sessionId": "abc12345",
"kbPath": "~/my-kb",
"messagePreview": "这是我自媒体创作的文章...",
"triggered": true,
"explicitRefs": [],
"results": [{"path": "wiki/synthesis/sessions/x.md", "hitReason": "recent_synthesis", "score": 3.2}],
"wrappedCharCount": 4823,
"error": null
}
作用:验收脚本、用户反馈复现、长期效果分析都依赖这份日志。
5.8 新工具 query_knowledge_base
在 extensions/knowledge-base.ts 注册第三个工具:
pi.registerTool({
name: 'query_knowledge_base',
parameters: { query: string },
execute: ({ query }) => {
results = searchKnowledgeBase(currentKbPath, query, { explicitRefs: [], totalBudgetChars: 4000 })
return formatted text for agent
}
})
作用:让强模型在主对话流中也能继续追问 KB;未来若改为"工具优先 + 注入降级"也只是改 /api/prompt 的判断条件,retrieval 函数不动。
工具描述要点:"Search the user's current knowledge base for pages relevant to a query. Use this when the user asks a follow-up question that may require pulling additional context from the knowledge base beyond what's already in the conversation."
5.9 前端反馈
复用现有 tool 状态显示样式。新增三个 SSE 事件渲染:
knowledge_search_start→ 显示"正在检索当前知识库…"knowledge_search_done { count, paths }→ 显示"已检索到 N 个相关页面"knowledge_search_empty(count=0 时)→ 显示"当前知识库未找到相关内容"knowledge_search_error→ 显示"知识库检索失败,已按普通对话处理"
不新增大面板。
6. API 与模块边界
6.1 新增文件
server/src/retrieval.ts:导出searchKnowledgeBase/parseExplicitPageRefs/shouldUseKnowledgeBase/buildKnowledgeContextPrompt- 单测
server/src/retrieval.test.ts:触发策略、[[...]]解析、空 KB、cache 隔离、budget 控制
6.2 复用与改动
- 复用 pages.ts 的
getCachedPages—— retrieval 在其上加片段提取与扩展 score - 改动 index.ts 的
/api/prompt:按 §5.5 改造,新增 SSE 事件类型 - 改动 extensions/knowledge-base.ts:新增
query_knowledge_base工具 - 不动:digest/ 目录全部(子代理批量消化不受影响)
6.3 不新增对外 HTTP API
第一版只在 /api/prompt 内部使用。
7. 验收标准
7.1 客观可查标志(所有验收的硬底线)
每条主对话验收必须同时满足:
- 日志可查:
~/.llm-wiki-agent/logs/retrieval/*.jsonl中存在对应记录,triggered/results/wrappedCharCount字段符合预期 - 字符串命中:模型回答中至少出现一次检索结果的页面标题或独特短语(grep 可验)
无法同时通过这两条的验收一律不算过。
7.2 检索函数 searchKnowledgeBase
- 对"总结这些文章"类问题,能命中
wiki/synthesis/sessions/下最新页面 - 对具体关键词(如
OpenClaw),能命中标题或正文相关页面 - 返回结果含 path / title / snippet / mtime / hitReason / score
- 默认 maxPages=6,可配置
- 单页 snippet 不超过 snippetMaxChars
- 总字符数不超过 totalBudgetChars
- explicitRefs 即使超 budget 也强保留(可截断片段)
- 空 KB 返回
[],不抛错 - 同一 query 在 cache 未失效时多次调用,结果一致
- 批量消化新文件后,调用 pages.ts cache 失效路径,下一次检索能拿到新页
7.3 parseExplicitPageRefs
这是什么 [[wiki/synthesis/sessions/x.md]]→['wiki/synthesis/sessions/x.md'][[wiki/a.md]] 和 [[wiki/b.md]]→['wiki/a.md', 'wiki/b.md'][[wiki/a.md]] [[wiki/a.md]]→['wiki/a.md'](去重)普通文本无引用→[][[notwiki.md]]→[](必须以 wiki/ 开头)
7.4 shouldUseKnowledgeBase(黑名单)
- 未选 KB → false
/sediment//pdf→ false你好/谢谢/ok→ false当前模型是什么→ false[[wiki/x.md]] 说了啥→ true(含显式引用,短路)总结一下→ true这是我自媒体创作的文章,总结一下→ true(本 bug 的原始 case)OpenClaw 相关页面讲了什么→ true- 长度 < 3 → false(如
嗯)
7.5 /api/prompt 端到端
- 触发路径:SSE 顺序为
knowledge_search_start → knowledge_search_done → text_delta* → done - 不触发路径:SSE 不出现任何 knowledge_search_* 事件
- 触发但检索抛错:SSE 出现
knowledge_search_error,但text_delta仍正常流出(裸 prompt 降级) - 注入字符数符合模型 contextWindow * 20% 上限
- 模型回答末尾出现"参考页面"列表,所有路径都在
results.paths中(禁止幻觉路径) - 多轮:第 1 轮触发后第 2 轮再问新话题,第 2 轮独立判断 + 独立检索
7.6 @ 显式引用
@A选中后输入框变成[[wiki/.../A.md]],发送后 A 在 results 首位(hitReason='explicit')[[wiki/.../A.md]] [[wiki/.../B.md]] 对比→ results 前两个是 A 和 B[[wiki/.../A.md]] 再结合全库看看→ results[0]=A,后续是自动检索补充[[wiki/不存在.md]] 说了啥→ results 中无该路径,buildKnowledgeContextPrompt 在末尾加一行"用户引用了 wiki/不存在.md,但该页面不存在"
7.7 切库隔离
- A 库批量消化后,问"总结" → results 全部属于 A 库
- 切到 B 库(B 库为空)→ 问"总结" → results 为空 / 模型说"未找到"
- 切回 A 库 → 问"总结" → 仍能命中 A 库的页面
7.8 失败降级
- mock fs read 失败 → SSE 推 error 事件、回答仍能流式输出、日志写 error stack
- 注入字符数超 contextWindow → 自动截断到 budget 内,不抛错
- shouldUseKnowledgeBase 抛错(不应发生,但兜底)→ 走裸 prompt
8. 端到端验收剧本
场景 1:批量消化后总结(本 bug 原始 case)
- 选择普通目录 → 初始化为 KB → 批量消化 7 篇文章
- 进入该 KB
- 问:"这是我自媒体创作的文章,总结一下。"
必须结果:
- 不反问用户要文章
- 回答覆盖多篇文章主题
- 末尾"参考页面"列出 ≥ 3 篇 sessions/ 下的页面
- 日志 jsonl 中
triggered=true、results.length ≥ 3 - 模型回答中 grep 到至少 1 个 results.paths 中页面的标题
场景 2:具体主题检索
- 同一库问:"OpenClaw 相关的文章讲了什么?"
必须结果:
- results 中至少 1 篇含
OpenClaw - 回答基于这些页面,无幻觉
场景 3:切库隔离
- 切到另一个 KB(已知不含相关内容)
- 问:"这些文章总结一下。"
必须结果:
- results 为空 or 全部属于新 KB
- 模型明确说"当前知识库未找到相关内容"
- 不出现上一 KB 的页面路径
场景 4:@ 显式引用
- 输入
@某篇文章 总结它的传播亮点(前端会替换为[[wiki/.../某篇.md]] 总结它的传播亮点)
必须结果:
- results[0] 是该指定页面
- 自动检索补充 ≤ 3 篇
- 回答主要基于指定页面
场景 5:普通聊天不检索
- 问:"你好。"
- 问:"当前模型是什么?"
- 输入
/sediment
必须结果:
- 三次都不触发 knowledge_search_* 事件
- 日志 jsonl 中
triggered=false
场景 6:多轮切话题
- 第 1 轮:"总结一下这批文章"
- 第 2 轮(在同一对话):"其中讲 AI 的那篇具体说了什么?"
必须结果:
- 两轮独立检索,各自有日志记录
- 第 2 轮 results 与第 1 轮不必相同(query 变了)
场景 7:失败降级
- 用
chmod 000临时让 KB 某文件不可读 - 问:"总结一下"
必须结果:
- SSE 出现
knowledge_search_error - 但
text_delta仍正常输出 - 日志 jsonl 中
error字段非空 - 前端状态栏短暂显示"知识库检索失败,已按普通对话处理"
9. 实施顺序(codex 起手必读)
| 步骤 | 任务 | 验收 |
|---|---|---|
| 1 | 新建 server/src/retrieval.ts + 单测;实现 parseExplicitPageRefs / shouldUseKnowledgeBase |
§7.3 §7.4 全过 |
| 2 | 在 retrieval.ts 实现 searchKnowledgeBase(复用 pages.ts getCachedPages) |
§7.2 全过 |
| 3 | 实现 buildKnowledgeContextPrompt + 日志写入 |
单测覆盖空 results / 含 explicit / 含 error 三种形态 |
| 4 | 改造 index.ts /api/prompt:增加判断 + 调用 + SSE 新事件 + 失败降级 |
§7.5 §7.8 全过 |
| 5 | 在 extensions/knowledge-base.ts 注册 query_knowledge_base 工具 |
单测:工具签名正确、execute 返回格式符合 §5.8 |
| 6 | 前端新增 3 个 SSE 事件渲染(knowledge_search_start/done/error) | 手动验:剧本 1 能看到检索状态 |
| 7 | 跑完 §8 全部 7 个端到端剧本 | 所有剧本通过 §7.1 双重客观标志 |
每步动手前先在对话里说"准备改 X,影响 Y",作者确认后再动。
10. 风险与边界
10.1 模型不读注入内容
弱模型(glm 等)可能拿到注入仍反问。
- 缓解:§5.4 wrappedMessage 末尾加强约束句("如未基于以下页面回答而再次反问用户提供文章,视为错误")
- 监控:日志可统计"注入了但模型反问"的发生率
10.2 cache 失效窗口
批量消化刚跑完、pages.ts cache 还指向旧 fingerprint。
- 缓解:pages.ts 已用 mtime+size fingerprint,每次 retrieval 都会触发重新扫描
- 验收:场景 1 必须在批量消化结束后立即问(不等手动刷新)
10.3 切库 cache 串库
pages.ts cache 按 kbPath 隔离,retrieval 复用,不会串库。
- 验收:场景 3 必须通过
10.4 检索误触发
黑名单策略激进。
- 缓解:寒暄白名单 + 元问询白名单 +
/命令短路 + 长度 < 3 短路 - 监控:日志可统计触发率,过高时回头收紧
10.5 来源不真实
模型可能编造路径作为来源。
- 缓解:§5.4 prompt 中强约束"参考页面只能从上述检索结果中选取"
- 验收:§7.5.5 grep 检验回答中所有路径都在 results.paths 中
10.6 与 @ 心智冲突
用户老心智:"想引用必须 @"。新行为:"不 @ 系统也可能引用"。
- 缓解:§5.9 前端轻量状态显示让用户知道何时检索了
- 长期:如果用户反馈混乱,提供"关闭自动检索"开关(本轮不做)
10.7 中文路径 / emoji
PRODUCT.md §6.6 UTF-8 铁律。
- retrieval 所有字符串处理用 raw UTF-8,不 normalize
- cache key 用原始 absolute path
10.8 多轮成本
每轮都检索 = 每轮多 100-300ms IO。
- 本地 grep + cache 命中下可接受
- 监控:日志记 retrieval 耗时,> 1s 时考虑优化
11. 本轮不做
- 不引入向量数据库
- 不引入新 npm 包
- 不做复杂搜索 UI
- 不把整个知识库塞进每次 prompt
- 不把批量消化结果写进聊天历史
- 不改变
@/// 子代理批量消化的现有心智 - 不动 digest/ 目录任何代码
- 不做"关闭自动检索"开关(等用户反馈再说)
12. ADR 影响
本设计正面破例 PRODUCT.md ADR-7("知识库上下文用 Extension 注入,不拼 prompt")。需要在 PRODUCT.md 第 7 节追加:
ADR-19:主对话引入"系统检索 + 上下文注入"
背景:ADR-7 设计的"靠 Extension 工具让 agent 自觉调用"在阶段 3.5 批量消化后的真实场景下不稳——弱模型不调 list_knowledge_base_pages / read,直接反问用户提供文章。
决策:
- 主对话
/api/prompt路径破例采用"后端检索 + 拼隐藏上下文"模式 - ADR-7 的"应用状态用 Extension 注入"原则在
currentKnowledgeBase等状态查询上仍然成立,本破例只针对"问答类知识库检索" - 同一份
searchKnowledgeBase同时暴露为新工具query_knowledge_base,保留 ADR-7 路径供未来强模型使用 - 子代理批量消化(digest/ 目录)不受影响,继续走裸 prompt + only read tool
- 主对话流式输出过程中,每个 user turn 独立判断 + 独立检索,不跨轮缓存
- 失败降级为裸 prompt,SSE 推 error 事件 + 写日志,绝不中断对话
与既有 ADR 的关系:
- 破例 ADR-7:仅限主对话问答检索,状态查询工具保留
- 兼容 ADR-3(SSE):新增 3 个轻量事件类型
- 兼容 ADR-16(Skill 优先 / agent 元能力用 Extension):检索是 agent 工作台元能力,落 server/ 端合理
- 兼容 ADR-18(子代理路径):不影响 digest/ 目录
何时重新评估:
- 主流模型工具调用稳定性显著提升 → 考虑改回纯工具路径
- 用户大量反馈"参考页面被编造" → 强化 prompt 约束 + 引入后置校验
- KB 规模 > 100 篇时检索耗时不可接受 → 引入向量检索
13. 完成情况
本设计已在阶段 3.5 收尾中落地。
/api/prompt已接入当前知识库自动检索;每轮用户消息独立判断,触发时推送knowledge_search_start/knowledge_search_done/knowledge_search_empty/knowledge_search_errorquery_knowledge_base工具已注册,和主对话预检索共用同一套检索函数- 检索结果复用
pages.ts的缓存与扫描路径,覆盖wiki/synthesis/sessions/、purpose.md、index.md、wiki/overview.md [[wiki/...]]显式引用会优先进入结果;缺失页面会进入包装提示,不静默吞掉- 检索日志写入
~/.llm-wiki-agent/logs/retrieval/<YYYY-MM-DD>.jsonl - 普通寒暄、
/命令、模型/设置类问题、导出产物指令不会触发知识库检索
验收实况:
node --import tsx --test server/src/retrieval.test.ts server/src/digest/concurrency.test.ts通过npm run --silent typecheck通过- 真实接口验证通过:
这是我自媒体创作的文章,总结一下触发检索并返回参考页面;你好不触发检索;导出 PDF 指令不触发检索