image

LLM Wiki 技术分析报告

分析对象:nashsu/llm_wiki: LLM Wiki is a cross-platform desktop application that turns your documents into an organized, interlinked knowledge base — automatically. Instead of traditional RAG (retrieve-and-answer from scratch every time), the LLM incrementally builds and maintains a persistent wiki from your sources。
版本:v0.6.9(git tag v0.6.9,commit 723e259
分析日期:2026-08-18
说明:本文结论均基于源码阅读,文中标注的 文件:行号 可直接定位。与 README 描述不一致处已单独标注。


目录


一、项目定位与核心命题

LLM Wiki 是 Karpathy 的 llm-wiki.md 方法论(仓库内保留原文 llm-wiki.md)的完整工程化实现,由 nash_su 开发,GPL-3.0 许可,基于 Tauri v2 的跨平台桌面应用。

核心命题在原文 llm-wiki.md:9-13 表述得最清楚,也是理解整个代码库的钥匙:

传统 RAG 在查询时从原始文档重新检索并拼装答案 —— 知识没有累积,每次提问都在重新发现。
LLM Wiki 把 LLM 算力前移到摄入时:读源文档 → 抽取 → 增量整合进一个持久化、互链的 Markdown Wiki。知识编译一次、持续维护,而不是每次查询重新推导。

理念层到代码层的映射

理念层 代码层落地
三层架构:raw(不可变)→ wiki(LLM 生成)→ schema(规则) 项目目录结构 + purpose.md / schema.md 作为控制平面注入所有 prompt
三操作:Ingest / Query / Lint src/lib/ingest.ts、Rust Agent runtime、src/lib/lint.ts + sweep-reviews.ts
index.md / log.md 作为导航与时间线 应用确定性维护,而非模型重写(关键工程决策,见 §3.1)
[[wikilink]] + YAML frontmatter 检索、图谱、级联删除三处都依赖这两个结构信号

相对原方法论的最大扩展是:把一份"复制给 Agent 的抽象设计文档"变成了带完整检索管线、知识图谱引擎、Agent runtime、本地 API 与浏览器扩展的产品级实现。


二、技术栈

来源:package.json / src-tauri/Cargo.toml / mcp-server/package.json(非 README 转述)。

2.1 前端(WebView 层)

类别 选型
框架 React 19 + TypeScript 5.7 + Vite 8
样式 / 组件 Tailwind CSS v4、shadcn、@base-ui/reactlucide-react
状态管理 Zustand 5(src/stores/ 下 11 个 store)
编辑器 Milkdown 7.20(ProseMirror 内核)+ @milkdown/plugin-math + theme-nord
图谱 sigma.js 3 + graphology + graphology-layout-forceatlas2 + graphology-communities-louvain
Markdown 渲染 react-markdown 10 + remark-gfm / remark-math + rehype-katex + KaTeX + mermaid 11
其它 pdfjs-dist 5、i18next / react-i18next、jszip、react-resizable-panels、js-yaml
测试 Vitest 4 + fast-check(属性测试)

2.2 桌面 / 后端(Rust)

类别 选型
运行时 Tauri 2(protocol-assettray-icon)、tokio、rust-version = "1.88"
网络 reqwest 0.12(rustls-tls、stream)、tauri-plugin-httpunsafe-headers(为透传 Origin
向量库 LanceDB 0.27 + arrow-array/arrow-schema 57(嵌入式,无独立服务进程)
文档解析 pdfium-render 0.9、docx-rs 0.4、calamine 0.34(xlsx/xls/ods)、office_oxide、anydoc、epub 2.1、mobi 0.8、html2text
内嵌 HTTP tiny_http 0.12(剪藏服务)
其它 notify 8(文件监听)、walkdir、sha2 / md-5、uuid v4、which、image(PNG)、base64、zip 2、chrono
插件 store、dialog、opener、autostart、http
发布配置 lto = truecodegen-units = 1opt-level = "s"panic = "unwind"strip = true

2.3 外围组件

组件 技术
MCP Server 独立 Node ≥20 包(mcp-server/),@modelcontextprotocol/sdk,stdio 传输,11 个工具
Chrome 扩展 Manifest V3 + Mozilla Readability.js + Turndown.js → 本地 19827 端口
LLM 接入 HTTP 三种 wire(OpenAI chat/completions、Anthropic Messages、Gemini generateContent)+ 子进程 transport(Claude Code CLI、Codex CLI)
网络搜索 Tavily / SerpApi / SearXNG / Firecrawl / Brave / Bocha / Ollama Search
本地文件搜索 AnyTXT JSON-RPC(默认 http://127.0.0.1:9920
CI/CD GitHub Actions,产出 macOS(ARM/Intel) .dmg、Windows .msi、Linux .deb/.AppImage

三、技术实现路线与方法

3.0 进程与边界划分

1
2
3
4
5
6
7
┌ WebView (React) ─ UI + Ingest 编排 + 设置/密钥管理
│ ↕ Tauri invoke(约 90 个命令,src-tauri/src/lib.rs:620+)
├ Rust core ─ 文件/解析/检索/向量/Agent runtime/文件监听/托盘
│ ├→ 本地 HTTP API :19828(Token 鉴权、限流、SSE 流式)
│ ├→ 剪藏服务 :19827(Chrome 扩展)
│ └→ 系统托盘、开机自启
└ MCP Server (Node, stdio) ──HTTP──→ :19828

一条被写进代码注释的架构宪法(src-tauri/src/agent/mod.rs:1-6):

“Keep routing, retrieval, tool execution, context assembly, sessions, and cancellation in this Rust module. The React/TypeScript side may render UI state … but it should not reimplement the Agent core; otherwise API/MCP/UI behavior will drift.

Agent 内核下沉 Rust,UI / HTTP API / MCP 三个入口共享同一实现。这是同类项目中少见的架构自觉:任何一个入口的行为改进自动惠及另外两个。


3.1 摄入管线(Ingest)—— 两阶段思维链 + 严格块协议

主流程:src/lib/ingest.ts:659 autoIngestImpl

1
2
3
4
5
6
7
8
9
10
① MinerU 预处理(仅 PDF,可选)→ 失败自动回退 pdfium
② 多格式解析 → 文本 + 内嵌图片抽取
③ SHA256 内容哈希缓存命中检查 → 命中则只跑图片级联,跳过 LLM
④ 阶段一 · 分析(buildAnalysisPrompt, ingest.ts:2147)
关键实体 / 关键概念 / 主张与证据强度 / 与现有 Wiki 的关联
/ 矛盾与张力 / 页面创建建议
⑤ 阶段二 · 生成(buildGenerationPrompt, ingest.ts:2209)
FILE 块(wiki 页面)+ REVIEW 块(待人工判断项)
⑥ 解析块 → 路径安全校验 → 写盘(项目锁串行提交)
⑦ 确定性更新 index.md / log.md → 自动 embedding → 图片描述(VLM)

值得学习的设计决策

(1) 自定义块协议而非 JSON

1
2
3
4
5
6
7
8
9
10
---FILE: wiki/entities/foo.md---
(完整文件内容,含 YAML frontmatter)
---END FILE---

---REVIEW: contradiction | 标题---
描述
OPTIONS: Create Page | Skip
PAGES: wiki/a.md, wiki/b.md
SEARCH: query1 | query2 | query3
---END REVIEW---

正则见 ingest.ts:339 FILE_BLOCK_REGEX / ingest.ts:2068 REVIEW_BLOCK_REGEX。理由很实际:生成内容本身包含代码围栏、YAML、Markdown 表格,JSON 转义会让模型频繁出错;块协议对流式输出和局部失败也更宽容。配套的 parseFileBlocksingest.ts:456)还需处理围栏内出现的假开闭标记。

(2) index.md / overview.md 不交给模型重写

Prompt 中明确写着(ingest.ts:2252):

“Do not generate wiki/index.md or wiki/overview.md. The application maintains aggregate navigation separately so large wikis are never rewritten through model output.

updateWikiIndexDeterministicallyingest.ts:1554)在代码侧增量维护。这是"把模型不可靠的环节从系统关键路径上摘出去"的典型手法 —— 一个 500 页的 wiki 索引,交给模型重写一次就可能丢失几十条。

(3) Prompt 工程的近因技巧

语言指令 languageRule() 在 prompt 开头和最末尾各出现一次,注释直言(ingest.ts:2370-2372)是为了赢得"最近指令优先"的权重博弈,防止中小模型漂回训练语言。同理,输出格式规范被刻意放在整个 prompt 的最后一节,并附带硬约束:

“The FIRST character of your response MUST be -… If you start with anything other than ---FILE:, the entire response will be discarded.”

(4) 长文档 map-reduce

LONG_SOURCE_CHUNK_MIN/MAX = 12K/60KLONG_SOURCE_DIGEST_MAX = 15KLONG_SOURCE_MAX_SINGLE_PASS_BUDGET = 300Kingest.ts:47-53)。超长源先分块摘要再汇总,避免单次上下文溢出。

(5) 并发与崩溃恢复

  • withProjectLock 项目级互斥
  • ingest-commit-coordinator.ts 串行提交,防止并发 LLM 调用写冲突
  • 队列持久化至 .llm-wiki/file-change-queue.json,应用重启后恢复
  • 失败任务自动重试最多 3 次

(6) 结构化数据保真

Prompt 中反复强调(ingest.ts:2178ingest.ts:2303):SQL DDL、CREATE TABLE、API 签名、配置、表格必须以代码块/Markdown 表格逐字保留,不得降级为散文。这是一条从实际问题反推出来的规则 —— 用户导入 schema 文档就是为了保留字段名、类型、约束、索引。

(7) 主体边界保护

多处 prompt 约束(ingest.ts:2298-2300):当一份源文档讨论多个实体/模型/产品时,评价、局限、benchmark 结果必须绑定到准确的主体,不得因为共享关键词(上下文窗口大小、数据集名、架构名)而在页面之间迁移。这是知识库特有的正确性风险。


3.2 检索管线 —— 三路召回 + RRF 融合

核心实现:src-tauri/src/commands/search.rs:327 search_project_inner

路 1:关键词打分(score_file, search.rs:818

信号 权重
文件名精确命中 +200
短语出现在标题 +50
短语出现在正文 +20 / 次(最多计 10 次)
标题 token 命中数 ×5
正文 token 命中数 ×1

分词器 tokenize_querysearch.rs:885)对 CJK 做 bigram + 单字 + 原词 三路展开,并带中英双语停用词表。同一套逻辑在 TS 侧 src/lib/search.ts:38 有镜像实现(两份需手工对齐)。

⚠️ 客观说明:这不是 BM25。没有 IDF、没有文档长度归一,统计的是"命中的 token 种类数"而非词频。src/lib/embedding.ts:36 的注释写作 “fall back to BM25”,与实际实现不符,属陈旧注释。

路 2:向量召回(chunk 级 → 页级聚合)

1
2
3
query → embedding → LanceDB ANN 取 topK×3(≥30)个 chunk
→ 按 page_id 分组
→ 页分 = max(chunk分) + min(0.3 × Σ其余chunk分, 1 − max)

实现见 search.rs:710,TS 镜像 src/lib/embedding.ts:710。这个"主分 + 有界尾部加成"公式让"两个中等相关段落的页"能压过"一个强段落 + 一个弱段落的页",同时通过上界 1 − max 防止碎片页靠数量刷分。

融合:RRF(apply_rrf_scores, search.rs:484

1
score = 1/(60 + rank_keyword) + 1/(60 + rank_vector)     // RRF_K = 60

标准 Reciprocal Rank Fusion。注意:一旦有向量命中,关键词的绝对分数被完全丢弃,只保留名次 —— 这正是 RRF 的设计意图(跨异构打分尺度融合,无需归一化)。

路 3:图谱一跳扩展(blend_graph_results, search.rs:521

  • 以 top-N(≤20)结果为种子,沿 [[wikilink]] 邻接扩散一跳

  • 邻居得分 = Σ 1/(seed_rank + 1)

  • 自适应配额graph_result_quota, search.rs:511):为图谱结果预留 15%–30% 的结果窗口

    1
    ratio = 0.30 − (0.30 − 0.15) × (向量命中数 / 窗口大小)

    向量召回越充分,图谱配额越低;向量稀疏时配额升到上限;没有候选时名额自动退还给前两路。

这条路是纯结构信号,能召回"关键词不重合、语义也不特别近,但被知识网络显式关联"的页面 —— 是"编译式知识库"相对纯 RAG 的结构性红利。返回结果携带 graphRelatedTo 字段说明它因哪个种子页被召回。

检索模式标注

search_mode()search.rs:690)返回 keyword / vector / hybrid 三态,API 与 UI 共享同一契约,便于调试与结果徽标展示。


3.3 向量层(LanceDB)

实现:src-tauri/src/commands/vectorstore.rs + src/lib/embedding.ts

  • 表结构演进:v1 wiki_vectors(一页一行)→ v2 wiki_chunks_v2(一 chunk 一行)vectorstore.rs:62-66),保留 legacy 表的行数检测与清理路径,支持平滑迁移
  • Arrow schemachunk_id: Utf8 / page_id: Utf8 / embedding: FixedSizeList<Float32, dim> / chunk_text / heading_path
  • 嵌入文本富化embedding.ts:299 enrichChunkForEmbedding):实际编码的是 页标题 + 标题面包屑 + chunk 正文。注释说明理由:一个 300 字的孤立片段,只有显式带上"它属于哪几层小节"才具备可检索性
  • 增量优化:每写入 20 页触发一次 optimizeINCREMENTAL_OPTIMIZE_PAGE_THRESHOLD = 20
  • 重建索引采用 prepare-all-then-swapembedding.ts:563-660):全部 chunk 先算好,任一失败则保留旧索引不动;只有全部成功才 clear + 批量写入,避免"重建到一半炸掉、索引变空"
  • 写入并发策略:出站 embedding HTTP 按 cfg.concurrency 并发(上限 32),LanceDB 写入串行化createAsyncLimiter(1))—— 注释明确区分"网络并发"与"数据库写者并发"
  • 降级链:批量 embedding 失败 → 自动逐条重试;oversize 错误 → 文本折半重试最多 3 次(fetch_embedding_with_retry, search.rs:1066

分块器(src/lib/text-chunker.ts,Rust 侧 page_embedding.rs 有对应实现)

  • Langchain 式递归切分阶梯:标题节 → 段落(\n\n)→ 换行 → 句末(含 。!?; 等 CJK 标点)→ 空白 → 硬切
  • 代码块与表格永不切分(宁可产出 oversized chunk 也不撕碎语义)
  • YAML frontmatter 在分块前剥离,避免元数据污染向量
  • 小块合并(< minChars 200)避免向量库被 50 字符碎片淹没
  • 重叠对齐到句/词边界snapOverlapHead() 前向查找分隔符,注释记录了一个真实 bug —— 早期版本后向查找,当 tail 恰好结束在句末时重叠会坍缩为近乎零
  • 纯函数、确定性、无 I/O,配套完整单测

默认参数:targetChars 1000 / maxChars 1500 / minChars 200 / overlapChars 200


3.4 Agent Runtime(Rust)—— 本项目最复杂的子系统

目录:src-tauri/src/agent/runtime.rs 约 4000 行)

工具集(14 个,tools.rs:421 builtin_tool_specs

按副作用分类为 Read / Write / Network / Process

类别 工具
检索 wiki.searchwiki.read_pagesource.searchgraph.searchweb.searchanytxt.search
研究 deep_research.run
写入 wiki.write_page(默认 create-only,覆盖需显式 allowOverwrite)、workspace.write_fileworkspace.append_file
技能 skills.loadskill.read_file
执行 shell.exec(需审批)
生成 llm.generate

循环协议(build_agent_loop_system, runtime.rs:2915

模型每轮只返回一个紧凑 JSON action({"action":"tool",...}{"action":"final","answer":"..."})。Prompt 中的约束高度"实战化":

  • 禁止返回"我需要先读一下……"这类自然语言计划 —— 想读就直接发工具调用
  • 不许在观察确认前声称文件已生成,最终答案只能提及已被观察确认的路径
  • 大 HTML/PPT 必须 write_file + 多次 append_file 分块写,不准塞进 shell heredoc
  • “Converge quickly” —— 交付物写完就 final,不要继续读可选参考、跑可选校验或打磨

预算与防呆机制

机制 实现 取值
迭代预算 agent_loop_iteration_budgetruntime.rs:2798 Fast 4 / Standard·LocalFirst 8 / Deep 12;带 skills 时 8 / 16 / 20
检索预算 agent_loop_retrieval_budgetruntime.rs:2814 Standard 2/4/8;Smart 3/4/6;Faithful 2/3/5
重复检索抑制 retrieval_signature() 对 query 做小写化 + 标点归一后生成指纹去重
无进展保护 retrieval_added_evidence() 判定本轮是否真的新增了引用或页面内容
Planner 兜底 should_fallback_wiki_search() planner 不可用/失败 → 回退单次 wiki.search
迭代耗尽兜底 agent_iteration_limit_answer() 强制产出 final
上下文裁剪 fit_context_to_model() 按模型窗口裁剪观察记录

三种检索模式(AgentRetrievalModetypes.rs:20

  • Standard —— 单遍 / planner 驱动,保持既有行为

  • Smart —— 有界证据闭环:每轮只针对"未闭合的证据缺口"发一次精化检索,优先跟进已发现的页面而非扩大查询范围

  • Faithful(只读原文) —— 实现不是靠 prompt 说"请只用原文",而是在上下文装配前物理移除 overview.mdschema.mdruntime.rs:2783-2796),注释明说:

    “asking the model to ignore either would be a soft guarantee rather than source-only context isolation.”

安全模型

  • 能力白名单 PermissionPolicypermissions.rs):ReadProject / ReadSource / SearchWiki / SearchWeb / SearchAnyTxt / WriteWiki / RunDeepResearch / Network / Process
  • Process 能力默认存在,但必须配合调用方显式传入的精确 shell 命令白名单才生效
  • AgentChatRequest.approved_shell_commands 带醒目注释(types.rs:125-129):该字段绝不允许由模型输出、历史会话或 skill 指令填充,运行时使用精确 trim 后字符串匹配
  • 命令若只涉及 agent-workspace/ 内路径可免审批;一旦提到外部路径、家目录、下载目录、临时目录或网络 URL 就必须弹审批(is_shell_command_scoped_to_agent_workspace, runtime.rs:3664
  • 硬性限额:SHELL_EXEC_TIMEOUT_SECS 30 / MAX_SHELL_COMMAND_CHARS 4000 / MAX_SHELL_OUTPUT_CHARS 20000 / MAX_SHELL_GENERATED_FILES 50

结构化用户交互

user.ask 工具允许 Skill 请求单选 / 多选 / 自由文本表单(AgentUserInputRequest),而不需要为每个 Skill 硬编码专用 UI。输入字段经 sanitize_user_input_request() 严格清洗(最多 12 字段、8 选项、400 字符)。

知识上下文附着

AgentKnowledgeContexttypes.rs:193)为每条 wiki 检索结果附加轻量图谱与溯源简报:related_to / tags / outgoing_links / backlinks / link_count / latest_version。注释强调必须保持有界,避免复制整张图浪费上下文。


3.5 知识图谱与四信号关联度模型

实现:src/lib/graph-relevance.ts:30

信号 权重 算法
直接链接 3.0 双向 [[wikilink]],正反向各计 1
来源重叠 4.0 frontmatter sources[] 交集大小
共同邻居 1.5 Adamic-Adar:Σ 1/ln(max(deg, 2))
类型亲和 1.0 5×5 类型矩阵

类型亲和矩阵节选(graph-relevance.ts:37):

1
2
3
entity   → concept 1.2 | entity 0.8 | source 1.0 | synthesis 1.0 | query 0.8
concept → entity 1.2 | concept 0.8 | source 1.0 | synthesis 1.2 | query 1.0
source → entity 1.0 | concept 1.0 | source 0.5 | query 0.8 | synthesis 1.0

设计上最有意思的一点:权重最高的不是显式链接,而是"来源重叠"(4.0 > 3.0)。 两个页面被同一份原始资料生成,是比模型随手写的 wikilink 更强的关联证据。这是"编译式知识库"独有的信号 —— 纯 RAG 系统拿不到这个信息。

📌 实现现状澄清:README 把四信号模型描述为检索管线的"阶段 2",但代码中 calculateRelevance 的实际消费方是 src/lib/wiki-graph.ts:285(图谱可视化的边权重计算),getRelatedNodes 未被检索路径调用。线上检索的图扩展走的是 Rust 侧更轻量的一跳 wikilink 邻接(见 §3.2 路 3)。README 描述与当前代码存在漂移。

Louvain 社区检测(wiki-graph-analysis.ts

  • graphology-communities-louvain,resolution = 1
  • 内聚度 = 社区内实际边数 / C(n, 2)< 0.15 标记为稀疏社区警告
  • 注释特意说明用 O(E) 遍历边而非 O(N²) 遍历点对
  • 社区按规模排序后重编号,保证配色稳定

图谱洞察

  • 惊奇连接:跨社区边、跨类型链接、边缘↔核心耦合,按复合惊奇度排序,可标记为已查看后消除
  • 知识空白:孤立页(度 ≤ 1)、稀疏社区(cohesion < 0.15 且 ≥ 3 页)、桥接节点(连接 3+ 集群)
  • 每条洞察可一键触发 Deep Research(LLM 读 overview.md + purpose.md 生成领域精准主题,用户确认后执行)

3.6 上下文预算分配

src/lib/context-budget.ts —— 纯函数 + 独立单测,单位是字符而非 token:

1
2
3
4
5
6
maxCtx(默认 204800)
├ 15% responseReserve ← 留给模型写回答
├ 5% indexBudget ← wiki 索引摘要
├ 50% pageBudget ← 检索到的页面正文
│ └ 单页上限 = min(pageBudget, max(5000, pageBudget × 30%))
└ 余量 ← 系统提示 + 历史(历史按条数而非字节控制)

一个漂亮的设计闭环:responseReserve 不只用于"少填内容",还被 deriveAnthropicMaxTokens() 换算成 Anthropic wire 的 max_tokens = min(16384, reserve / 3)llm-providers.ts:897)—— 保证"我给你留了空间"和"协议层真的允许你写这么多"是同一个数字推导出来的。

单页上限的三条规则(context-budget.ts:80-91)也值得记:下限 5000 保证小配置至少能塞一页;上限不超过 pageBudget 本身,否则单页会被下游整体拒绝;中间按 30% 线性缩放。历史上曾硬编码 30000 上限,导致长上下文模型浪费预算。


3.7 多 Provider 适配层(最具工程参考价值的一块)

src/lib/llm-providers.ts(1100+ 行)用一个统一接口收敛所有差异:

1
2
3
4
5
6
7
8
9
interface ProviderConfig {
url: string
headers: Record<string, string>
buildBody(messages, overrides): unknown
parseStream(line): string | null // SSE 增量
parseResponse(payload): string // 非流式
streaming: boolean
}

支持三种 HTTP wire(OpenAI chat/completions、Anthropic Messages、Gemini generateContent)+ 两种子进程 transport(claude-cli / codex-cli)。多模态 ContentBlock 在此层翻译成各家格式:OpenAI 用 image_url 内嵌 data URL,Anthropic 用 source.media_type + source.data,Gemini 用 inline_data.mime_type / inline_data.data

真正的价值在积累下来的端点特例表,每一条都带现场排查记录:

特例 处理 代码位置
Ollama 在 Windows 上 403 强制 Origin: http://localhost;注释详述为何不能用真实 origin(跨机 LAN 会被 OLLAMA_ORIGINS 拒绝),需 Tauri unsafe-headers 才能透传 llm-providers.ts:150
GPT-5 / o 系列 max_tokensmax_completion_tokens,并删除 temperature / top_p / top_k llm-providers.ts:393
Kimi / Moonshot 删除 temperature(服务端只接受默认值 1) llm-providers.ts:410
DeepSeek V4 thinking:{type:"disabled"};注释指出这是最关键的一条 —— 否则模型把预算全花在 reasoning_contentcontent 返回空 llm-providers.ts:466
Ollama 思考模型 reasoning_effort: "none";同样为避免"产出 N 字推理但无正文"的失败模式 llm-providers.ts:484
MiniMax / Kimi Coding / 阿里百炼 / 小米 MiMo 的 Anthropic 网关 Authorization: Bearer 而非 x-api-key(这些网关的 CORS 预检也不放行 x-api-key llm-providers.ts:669
智谱 BigModel 非 GLM 视觉模型传图直接抛出可读错误,避免服务端返回费解报错 llm-providers.ts:743
Gemini 2.5/3.x 流式解析必须拼接所有 part 并跳过 thought: true;早期只取 parts[0].text 会静默丢答案 llm-providers.ts:262
Anthropic 系统提示 用 text block 数组并打 cache_control: ephemeral 走 prompt cache llm-providers.ts:564
用户自定义 header 大小写不敏感去重 + 协议 header 优先 + 拒绝含 CRLF 的值 llm-providers.ts:84

Rust 侧的 embedding 客户端有对称的一套(search.rs):Google :embedContent 端点重写、火山引擎 /embeddings/embeddings/multimodal 路径纠正、Doubao 多模态响应结构差异、保留 header 名黑名单。

这张表本身就是一份可复用的"生产环境 LLM 网关兼容性知识库"。


3.8 本地 HTTP API 与 MCP

HTTP API(src-tauri/src/api_server.rs

监听 127.0.0.1:19828,前缀 /api/v1,Bearer Token 鉴权(/health 除外)。

端点 说明
GET /health 服务状态、mcpEnabled 标志,无需鉴权
GET /projects 项目列表 + currentProject
GET /projects/{id}/files · files/content 文件树与内容(限于 wiki/raw/sources/ 等公开路径)
POST /projects/{id}/search Hybrid 检索,返回 mode / tokenHits / vectorHits / 每条 vectorScore
POST /projects/{id}/chat Rust Agent 聊天(支持 SSE 流式),返回消息、引用、用量、工具事件
POST /projects/{id}/chat/{sid}/cancel 取消进行中的会话
GET /projects/{id}/graph Wikilinks 知识图谱
GET /projects/{id}/reviews · PATCH 审核项读取与批量处理
POST /projects/{id}/sources/rescan 触发源目录重扫
POST /projects/{id}/pages/embed 单页向量索引,供外部工具增量维护,无需全库重建

限额设计(api_server.rs:22-41):请求体 1 MB(chat 放宽至 40 MB)、文件内容 2 MB、限流 120 req/s、在途请求 64、并发聊天流 8、并发单页嵌入 4、SSE 心跳 10 s。绑定失败重试 3 次。

MCP Server(mcp-server/

Node 独立包,stdio 传输,11 个工具:llm_wiki_status / projects / set_project / files / read_file / reviews / search / chat / graph / rescan_sources / embed_page

一个值得注意的安全设计:会话级项目 pinproject-binding.js)。调用 llm_wiki_set_project 后,任何指向其他项目的 project_id 都会被拒绝而非静默切换;所有输出与错误信息统一带 [activeProject: name (id)] 前缀,使调用方不可能混淆答案来源。


四、实现功能清单

能力
摄入 两阶段 CoT;SHA256 增量缓存;持久化队列(串行 / 崩溃恢复 / 重试 / 取消);文件夹递归导入(目录名作分类上下文);raw/sources/ 外部变更自动监听;URL 批量导入;定时导入
解析 PDF(pdfium 内置 / MinerU 云端·Local API·Pipeline 三模式)、DOCX、PPTX、XLSX/XLS/ODS、EPUB/MOBI、Org、图片、音视频、网页剪藏
多模态 PDF 内嵌图片抽取 → VLM 生成事实性描述 → 写回源摘要页 → 重新嵌入使图片内容可被搜索;图片哈希去重避免重复 VLM 调用;搜索结果图文分区 + lightbox + 跳原文定位
检索 关键词 + 向量 + 图谱三路,RRF 融合;CJK bigram 分词;结果含 mode / tokenHits / vectorHits / vectorScore / graphRelatedTo
Chat Rust Agent(14 工具);多会话持久化(.llm-wiki/chats/{id}.json);引用面板;重新生成;答案存回 wiki/queries/ 并自动摄入;<think> 折叠展示;KaTeX + Mermaid 渲染
Skills 扫描项目级 / 用户级 SKILL.md/skill 补全选择;skill.read_file 读取技能引用文件;user.ask 结构化表单;agent-workspace/ 生成物预览与打开目录
图谱 sigma.js 可视化 + ForceAtlas2;四信号边权;Louvain 社区 + 内聚度评分;惊奇连接 / 孤立页 / 稀疏社区 / 桥接节点洞察;一键触发 Deep Research;位置缓存防跳动
审核 摄入时 LLM 产出 REVIEW 块(contradiction / duplicate / missing-page / suggestion);选项被枚举限定为 Create Page | Skip,防止模型编造动作;预生成 2–3 条搜索查询;异步不阻塞摄入
Lint 结构性检查(orphan / broken-link / no-outlinks,lint-structural-core.ts:10,Web Worker 执行)+ 语义检查(LLM 驱动,检测矛盾与过时论断);一键修复
深度研究 Tavily / SerpApi / SearXNG / Firecrawl / Brave / Bocha / Ollama 多 provider;LLM 生成研究主题(读 overview + purpose);可编辑确认对话框;结果自动进入两步摄入;最多 3 并发;流式进度面板
删除级联 三重匹配定位相关页(frontmatter sources[] / 摘要页名 / 章节引用);共享实体保护(多源页面只从 sources[] 移除条目而非删页);索引清理;失效 wikilink 清除
集成 本地 HTTP API :19828;MCP Server(11 工具,会话级项目 pin);Chrome MV3 剪藏扩展 :19827;配套 agent skill 一行命令接入 Claude Code / Codex
模型配置 项目级模型覆盖;Chat / Ingest 独立路由;自定义 provider + 自定义 header;流式开关;推理强度分级(off/low/medium/high/max/custom);可配置超时;代理配置
工程 项目 ZIP 导入导出;确定性重建 wiki/index.md;文件历史与回滚;i18n(中/英/日/韩);托盘 / 自启动;场景模板(研究 / 阅读 / 个人成长 / 商业 / 通用)

五、使用方法与工作流程

5.1 环境准备

方式一:预编译二进制(普通用户)
从 Releases 下载 macOS .dmg(ARM/Intel)、Windows .msi、Linux .deb / .AppImage。Windows 便携版需保持 pdfium/mcp-server/ 目录与 exe 同级。

方式二:源码构建(开发者)
前置:Node.js 20+、Rust 1.88+Cargo.toml 显式声明 rust-version,低于此版本会在 anydoc 依赖处报错)。

1
2
3
4
5
6
7
8
9
10
11
12
13
npm install
npm run tauri dev # 开发模式(Vite :1420 + Tauri 窗口)
npm run tauri build # 生产打包

# 完整桌面构建链(含 MCP Server)
npm run build:desktop # = mcp-server ci + mcp:build + typecheck + vite build

# 测试
npm run test:mocks # 无网络,CI 用;排除 real-llm 与 mcp-server
npm run test:llm # 真实 LLM,需 .env.test.local,串行执行
npm run mcp:test # MCP Server 单测
npm run typecheck # tsc --build

.env.test.localsrc/test-helpers/load-test-env.ts 加载,文件缺失时该 setup 为 no-op,所以 mock 测试可以无条件运行。


5.2 首次配置(5 步)

步骤 操作 产生什么
① 创建项目 欢迎页 → 新建 → 选场景模板 templates.ts:640 的 5 套模板生成 purpose.md + schema.md + wiki/ 目录骨架 + raw/sourcesraw/assets + .obsidian/ 推荐配置 + .llm-wiki/project.json(UUID)
② 写 purpose.md 填目标 / 关键问题 / 范围 / 当前论点 被注入每一次摄入与查询的 prompt,是整个项目的方向控制平面
③ 配置 LLM 设置 → LLM Provider:API Key + 模型 + 上下文窗口(字符) 可选:Chat / Ingest 分别路由到不同模型;项目级模型覆盖
④ 开启向量检索(可选) 设置 → Embedding:endpoint + key + model 首次开启会提示全量索引;关闭时检索自动降级为关键词 + 图谱
⑤ 按需开启外围(可选) Web Search / MinerU / 多模态图片 / Source Watch / API+MCP 各自独立开关,互不依赖

5 套场景模板templates.ts):research(研究深挖)、reading(读书)、personal(个人成长)、business(商业/团队)、general(空白)。每套模板的 schema.md 定义了各自的页面类型与目录 —— 例如 personal 模板会定义 goal / habit / reflection 类型,摄入 prompt 会据此路由(ingest.ts:2190 明确要求"schema 定义了 entity/concept 之外的类型时,按 schema 走")。


5.3 日常主循环:Ingest → Query → Lint

界面为三栏 + 图标侧边栏,导航项(icon-sidebar.tsx:20):
Chat / Wiki / Sources / Search / Graph / Lint / Review(带待办徽标) / Skills / Settings

① 摄入(Ingest)—— 四种入口

入口 操作 适用场景
Sources 面板 拖入 / 选择文件或整个文件夹 常规导入;文件夹路径会作为分类上下文传给 LLM
文件系统 直接把文件丢进 raw/sources/ 配合 Source Watch 自动检测并摄入(可配扩展名白/黑名单、排除目录、glob、最大文件大小)
Chrome 扩展 Alt+Shift+L(macOS Cmd+Shift+L)剪藏当前网页 网页文章;Readability 提取正文 → Turndown 转 Markdown → 自动摄入
URL / 定时导入 设置 → Scheduled Import;或 URL 批量导入 定期抓取的信息源

摄入过程在活动面板实时可见:排队 → 解析 → 分析 → 生成 → 写盘,逐文件显示。可取消、可重试。产出:

1
2
3
4
5
6
7
wiki/sources/<源文件名>.md      ← 源摘要页(必定生成,有兜底机制)
wiki/entities/*.md ← 新增/更新的实体页
wiki/concepts/*.md ← 新增/更新的概念页
wiki/index.md ← 应用确定性追加
wiki/log.md ← ## [YYYY-MM-DD] ingest | 标题
Review 队列 ← 需人工判断的项,侧边栏出现数字徽标

② 查询(Query)—— Chat 面板

发问前可组合三组开关:

维度 选项 作用
Agent 模式 fast / standard / deep / local_first 决定迭代预算(4/8/12)与检索预算(2/4/8)
检索模式 standard / smart / faithful smart = 有界证据闭环;faithful = 只读原文,物理移除 overview/schema
工具 wiki / web / anytxt 是否允许联网搜索、是否查询 AnyTXT 本地索引
Skills /skill 补全选择 让 Agent 按需加载技能指令

回答下方是可折叠引用面板,按类型分组列出用到的页面;引用直接存进消息数据,重启后稳定。有价值的回答点「保存到 Wiki」→ 归档到 wiki/queries/自动走一遍摄入,把其中的实体/概念抽进知识网络 —— 这是"探索也能复利"的关键操作。

③ 维护(Lint / Review / Graph)

面板 做什么 可执行动作
Review 处理摄入时 LLM 标记的待判断项(矛盾 / 疑似重复 / 缺失页面 / 建议) Create Page / Skip / Deep Research(用预生成的搜索查询)
Lint 结构检查(孤立页 / 死链 / 无出链)+ 语义检查(矛盾、过时论断) 一键修复;结构检查跑在 Web Worker 不卡 UI
Graph 看知识拓扑:类型/社区着色、内聚度、惊奇连接、知识空白、桥接节点 点击洞察高亮子图;知识空白一键触发 Deep Research(LLM 读 overview+purpose 生成精准主题,弹出可编辑确认框
设置 → 维护 项目 ZIP 导入导出(跨设备迁移)、确定性重建 wiki/index.md 换机、索引损坏后恢复

5.4 三种检索模式怎么选

模式 机制 适用 代价
standard planner 单遍决定工具,检索预算 2/4/8 日常问答,默认 最快
smart 每轮只针对未闭合的证据缺口发一次精化检索,查询指纹去重 + 无进展保护 复杂问题、需要跨页拼证据 多 1–2 轮 LLM 调用
faithful 只用 source.search 的原文片段作证据,逐句标注来源路径,证据不足时明说 需要可核对原文的场合(引用、法务、论文写作) 召回窄,拒答率高

选择建议:写作/汇报用 standard;调研/对比用 smart + deep;引用原文用 faithful。


5.5 Skills 使用

技能发现路径(skills.rs:81-94项目级覆盖用户级同名技能):

1
2
3
4
5
<项目>/.llm-wiki/skills/     ← 项目专属
~/.claude/skills/ ← 与 Claude Code 共享
~/.codex/skills/ ← 与 Codex 共享
~/.agents/skills/ ← 通用约定

每个技能是一个含 SKILL.md 的目录。只有 SKILL.md 会被注入 prompt,其余参考文件由 Agent 按需通过 skill.read_file 懒加载(单文件上限 256 KB)。技能可以:

  • user.ask 弹出结构化表单(单选/多选/文本,最多 12 字段)收集用户选择,不需要为每个技能写专用 UI
  • workspace.write_file / append_file 生成产物到 agent-workspace/,在聊天中作为生成物展示、预览、打开目录
  • shell.exec 跑命令 —— 工作区内路径免审批,涉及外部路径/家目录/临时目录/URL 必须审批

5.6 让外部 AI Agent 接入你的知识库

1
2
3
4
① 设置 → API + MCP → 开启 API,生成 Token(可选是否允许本机免鉴权 / LAN 访问)
② npm run mcp:build(源码构建时)→ 设置页展示可复制的 MCP 客户端配置,路径已填好
③ 把配置粘贴进 Claude Code / Codex 等 MCP 客户端

或直接用官方 agent skill 一行接入:

1
2
npx skills add https://github.com/nashsu/llm_wiki_skill.git --skill llm-wiki

接入后可用自然语言驱动:「我的 LLM Wiki 里关于 X 是怎么说的」「搜我的知识库 Y」「展示 Z 的图谱邻居」「重新扫描我的资料源」。该 skill 刻意不响应"搜我的笔记 / 我的 Obsidian"这类泛指请求,必须明确提到 LLM Wiki / 我的 wiki / 我的知识库。

不用 MCP 也可以直接打 HTTP:

1
2
3
4
curl -H "Authorization: Bearer $TOKEN" \
-d '{"query":"混合检索","topK":10}' \
http://127.0.0.1:19828/api/v1/projects/current/search


5.7 与 Obsidian 并用(推荐工作流)

项目目录本身就是合法 Obsidian 仓库(创建时自动生成 .obsidian/,附件目录设为 raw/assets,忽略 .llm-wiki.cache.superpowers)。原方法论推荐的姿势是:

Obsidian 是 IDE,LLM 是程序员,wiki 是代码库。

左边 LLM Wiki 做摄入与问答,右边 Obsidian 浏览结果 —— 跟链接、看图谱视图、读被更新的页面。整个 wiki 是纯 Markdown,直接 git init 就获得版本历史、分支与协作能力。


六、具体实现的效果与用户价值

这一节把 §三 的技术决策翻译成用户能感知的结果。

6.1 技术实现 → 用户可感知效果

技术实现 用户实际感受到的效果
两阶段思维链摄入(分析 → 生成) 生成的页面有"读懂了"的结构感:实体和概念被拆开、论点带证据强度、与已有页面的关系被写出来,而不是一段流水摘要
SHA256 内容缓存(ingest-cache.ts 重复导入同一文件秒过,不烧 token;改了内容才重跑
index/log 由代码确定性维护 wiki 长到几百页后,索引仍不会被模型"重写时丢条目"
持久化摄入队列 + 3 次重试 导入 50 个文件中途关掉应用,重开后继续;单个文件失败不影响其余
RRF 三路融合 + 图谱配额 能搜到"没写你输入的关键词、语义也不特别近,但被知识网络显式关联"的页面 —— 这是纯向量 RAG 搜不出来的
chunk 嵌入带标题面包屑 短片段也能被准确召回,不会因为"这段没提主语"而检索不到
CJK bigram 分词 中文查询不必和页面用词完全一致
graphRelatedTo 字段 搜索结果会标出"它是因为 XX 页面被带出来的",可解释
Faithful 只读原文模式 输出的每句话能对回 raw/sources/ 的具体文件,可逐条核对;证据不足时明确说"不够"而不是编
引用面板 + 引用持久化 每条回答都能点开看"用了哪几页",重启后不变
「保存到 Wiki」+ 自动摄入 一次好的提问不会沉进聊天记录,而是变成新页面并被拆解进知识网络 —— 探索本身产生复利
异步 Review 队列 导入 30 篇论文不需要全程盯着;矛盾和疑似重复攒成待办,有空再处理
Review 选项被枚举死(Create Page / Skip) 不会出现模型自创的、点了不知道会发生什么的按钮
级联删除 + 共享实体保护 删掉一份资料,它的摘要页跟着走,但被多份资料共同支撑的实体页只减少一条 sources,不会误删
Louvain 社区 + 内聚度 + 知识空白 能直观看到"我的知识在哪聚成团"“哪块是孤岛”“哪几页是维系多个领域的枢纽”,并一键补研究
四信号里"来源重叠"权重最高(4.0) 图谱连线反映的是真实资料共现,而不只是模型随手写的链接
shell 工作区边界 + 外部路径审批 技能可以真正跑命令生成产物,但不会在你没同意时碰工作区外的东西
MCP 会话级项目 pin 外接 Agent 不会串项目;每条输出都带 [activeProject: …] 前缀
panic = "unwind" + panic_guard 一个畸形 PDF 只会让那一个文件失败并给出错误,不会整个应用崩溃
全链路降级(MinerU→pdfium / 向量→关键词 / 批量→逐条 / planner→单次搜索) 只配一个 LLM API Key 就能用起来;配得越全效果越好,但缺哪块都不会卡死
端点特例表(Ollama Origin、DeepSeek thinking、GPT-5 参数剥离等) 换模型/换网关时"直接能用"的概率高得多,不用自己排查为什么返回空
本地 API + MCP + agent skill 知识库不锁在这个应用里:Claude Code、Codex、任意脚本都能查
纯 Markdown + Obsidian 兼容 + git 产出物不依赖本应用存活;换工具、做版本管理、协作都成立

6.2 效果的可验证性说明

为避免夸大,区分三类:

类别 内容
代码可验证 上表绝大多数条目 —— 逻辑在源码中可直接读到(已标注文件行号)
需实测确认 检索质量提升幅度、摄入耗时、大 wiki(>500 页)下的响应延迟 —— 仓库内无基准脚本
仅为声称 README 的"向量检索使整体召回率 58.2% → 71.4%"—— 无可复现基准,引用时应注明来源

6.3 成本感知(用户需要预期的)

操作 大致成本量级
摄入 1 篇普通文章 2 次 LLM 调用(分析 + 生成)
摄入 1 本书 / 长 PDF 分块 map-reduce,调用次数随长度增长;另加每张图 1 次 VLM
首次全量向量索引 页数 × chunk 数次 embedding 请求(默认串行,见 §9 P0-3)
一次 standard 问答 1 次 planner + N 次工具观察 + 1 次终答,N ≤ 检索预算
一次 deep 问答 迭代预算 12、检索预算 8,成本可达 standard 的 2–3 倍

结论:这是一个"摄入贵、查询便宜"的系统。适合长期积累的主题,不适合一次性问答。


七、可提炼的方法论(理论积累)

7.1 编译式知识库 vs 检索式 RAG

维度 传统 RAG LLM Wiki 模式
LLM 算力位置 查询时 摄入时(每源 2+ 次调用)
中间表示 向量 + 原文切片 人可读的 Markdown 页面网络
跨文档综合 每次现场拼装 摄入时已固化为 synthesis / comparison 页
矛盾处理 不处理,或答案里同时出现两种说法 摄入时显式标记 → 审核队列 → synthesis 页收敛
关联信号 仅语义相似度 语义 + 显式链接 + 来源共现 + 类型亲和
边际成本 查询贵、摄入便宜 摄入贵、查询便宜
可审计性 向量不可读 每页 sources[] 可溯源,git 可 diff
失败模式 答案漂移、幻觉难追溯 页面质量问题可定位、可 lint、可重建

本质总结:用一次性的高成本换取可累积、可审计、可被其他工具消费的中间产物。

中间表示选 Markdown + wikilink + frontmatter 而不是私有数据库,直接带来三个免费收益:Obsidian 兼容、git 版本管理、任意工具可读。这是一个被低估的架构选择 —— 它让整个系统的产出物在应用消亡后依然有价值。

7.2 值得迁移到其他系统的具体手法

  1. 把模型不可靠的环节移出关键路径
    index / log 由代码确定性维护,模型只负责它擅长的内容生成。判断标准:这个操作出错的代价是"局部瑕疵"还是"整体损坏"?后者一律代码化。
  2. 结构约束优于文本约束
    审核项 OPTIONS 被枚举死;Faithful 模式靠物理删除上下文而非 prompt 请求。凡是能用类型 / 枚举 / 数据结构表达的约束,都不要写进 prompt。
  3. 块协议 > JSON
    当模型输出本身包含大量代码围栏、YAML、表格时,自定义分隔块的容错性远高于 JSON。
  4. RRF 是异构召回融合的最小可用解
    不需要归一化分数尺度,只用名次,1/(K + rank),K=60 是文献推荐值。多路召回系统的首选融合方案。
  5. 多路召回要留固定配额
    图谱路占 15–30% 结果窗口且随向量覆盖率自适应,否则结构信号永远被语义信号挤掉。弱信号需要制度性保护。
  6. chunk 嵌入必须带结构面包屑
    title + heading path + text 是低成本高收益的召回改进 —— 短 chunk 的语义歧义主要来自缺失的层级上下文。
  7. 降级链要显式设计并可观测
    MinerU→pdfium、向量→关键词、批量嵌入→逐条、planner 失败→单次搜索、oversize→文本折半。每一级降级都要留下用户可见的原因(getLastEmbeddingError() 就是为此存在)。
  8. 异步人机协作
    把 human-in-the-loop 从阻塞式改成队列式,是让知识库能规模化的关键。原方法论建议全程参与,本实现改成事后处理的 Review 队列 —— 这是从"个人玩具"到"可持续系统"的分水岭。
  9. 同一内核多入口
    Agent 内核下沉到 Rust,UI / HTTP API / MCP 共享。避免"三套实现三种行为"是长期维护成本最大的一笔节省。
  10. 端点兼容性知识要沉淀成代码注释
    llm-providers.ts 中每条特例都记录了触发版本、用户报告、抓包证据。这类知识极易丢失,写在注释里比写在 issue 里有效得多。

7.3 工程质量实践

  • 测试分层npm run test:mocks(无网络,CI 默认)与 npm run test:llm(真实 LLM,--no-file-parallelism)分离;.real-llm.test.ts / .scenarios.test.ts / .property.test.ts / .race.test.ts 命名约定;fast-check 属性测试覆盖路径处理、语言检测、审核工具等纯函数
  • panic 边界panic = "unwind" + panic_guard::run_guarded_async 包裹命令入口 —— 第三方解析器(PDF/Office)在畸形文件上 panic 只会变成一条错误信息,不会杀掉整个应用。Cargo.toml 注释明确解释了这个权衡
  • 安全纵深:路径 canonicalize + 项目包含校验、HTTP header 名白名单 + 保留名拦截、API 限流 + 在途上限、MCP 会话 pin、shell 命令工作区边界、模型输出与审批通道严格隔离
  • 双实现对齐文化:TS / Rust 各有一份分块器、分词器、LLM 配置解析,代码里用注释显式声明"改一处必须同步另一处"(text-chunker.ts:4-7llm-task-routing.ts:20-22search.rssearch.ts
  • 并发原语自建且带注释createAsyncLimiter 的许可转移逻辑、parallelForEach 的 worker 模型、withProjectLock 的锁粒度,都有解释为什么不用现成库
  • 错误信息面向用户"Endpoint rejected input even at N chars. Lower Settings -> Embedding -> Max Chunk Chars." —— 错误信息直接给出操作路径

八、局限与风险

以下为客观评估,非贬低:

  1. 关键词打分是启发式加权,不是 BM25
    无 IDF、无长度归一,统计 token 种类数而非词频,长文档天然占优。相关注释还写着 “BM25”,属陈旧信息。改进空间明确。
  2. README 与代码存在漂移
    • 四信号模型被描述为检索阶段 2,实际只服务图谱可视化
    • "2 跳衰减遍历"在当前 Rust 检索路径中是一跳
    • 引用的召回率 58.2% → 71.4% 无仓库内可复现基准支撑
  3. 摄入成本随源数线性增长
    每个源至少 2 次 LLM 调用(长文档 map-reduce 更多,加上图片 VLM 描述)。SHA256 缓存只能防重复摄入,防不了首次成本。大批量导入的 token 开销可观。
  4. 输出协议解析仍是脆弱环节
    靠严格 prompt + 多重兜底(buildFallbackSourceSummary、文件名重写、frontmatter 日期补盖、语言一致性检查)维持。小模型下失败率会明显上升 —— 这也是为什么代码里有大量"兜底生成源摘要页"的逻辑。
  5. 图谱质量依赖模型写链接的纪律
    wikilink 由 LLM 生成,链接密度与准确性直接决定图扩展召回和社区检测质量,无独立校验机制(仅 lint 事后发现 broken-link / orphan)。
  6. 单机单用户
    LanceDB 嵌入式无多写者支持,项目锁是进程内互斥,不支持团队协作 —— 尽管原方法论把 business/team 列为典型用例。
  7. overview.md 仍由模型重写
    index / log 已代码化,但 overview 每次摄入后由 LLM 重新生成,在大 wiki 上仍有内容丢失风险。
  8. 版本漂移提醒
    D:\LLM_wiki\ 下的便携包 LLM Wiki.exe(内含 MCP server 0.4.26)明显早于源码 v0.6.9,两者行为存在差异。本分析基于源码。

九、可优化项(分优先级)

前置声明:以下建议基于源码阅读,未做基准测试。落地前建议先量化现状(尤其是 P0-1 的检索延迟),用数据决定优先级。每条给出:现状 → 问题 → 建议 → 预期收益 → 风险。

P0 — 影响核心体验,建议优先处理

P0-1 检索每次全量遍历并读取整个 wiki 目录

  • 现状search.rs:352 每次查询都 WalkDir 遍历 wiki/,对每个 .md 执行 fs::read_to_string + to_lowercase() + 打分 + 抽 wikilink + 抽图片引用,同时构建全量图谱邻接表。无任何缓存。get_page_links_innersearch.rs:168)和 build_knowledge_context_indextools.rs:2085)也各自重复一遍同样的全量扫描。
  • 问题:单次查询复杂度 O(N 文件 × 文件大小)。500 页 × 平均 8 KB ≈ 每次查询读 4 MB 并做多次全文小写化。Agent 一轮对话可能触发 3–6 次检索,成本线性叠加。
  • 建议
    1. 内存索引缓存{path → (mtime, size, title, tokens, wikilinks, images)},用 mtime+size 做失效判断。项目已有 .llm-wiki/file-snapshot.json(md5+size+mtime)这套基础设施可复用。
    2. 进一步可引入 tantivy(Rust 原生全文索引),同时解决 P1-4 的 BM25 问题。
    3. 图谱邻接表单独缓存,随 dataVersion 失效(前端已有 dataVersion 信号机制)。
  • 预期收益:中大型 wiki 的查询延迟从"随规模线性增长"变为近似常数;Agent 多轮检索的体感改善最明显。
  • 风险:需处理外部编辑(Obsidian 写入)的缓存失效 —— 已有 notify 文件监听可复用。

P0-2 向量库 page_id 使用文件名 stem,跨目录同名页冲突

  • 现状page_id = 文件名去扩展名(search.rs:369-379)。当 wiki/entities/transformer.mdwiki/concepts/transformer.md 同时存在时,两者共用同一个向量 page_id,后写入者覆盖前者。代码已经发现了这个问题,但只打了一行 eprintln!

    duplicate wiki page stem '{stem}': '{previous}' and '{}' share one vector page_id

  • 问题:静默的检索正确性损失。中文项目尤其容易撞名(“概述”“方法”“总结”)。用户完全无感知。

  • 建议

    1. page_id 改为项目相对路径规范化后的值(如 wiki/entities/transformer),validate_page_id 放宽允许 /
    2. 提供一次性迁移:检测 legacy id 格式 → 触发重建(已有 vector_legacy_row_count / vector_drop_legacy 的先例可参照)。
    3. 过渡期至少把该警告透出到 UI(设置 → Embedding 状态区),而不是只进 stderr。
  • 预期收益:消除一类静默错误。

  • 风险:需要全量重建索引;建议做成"检测到冲突时提示用户重建"而非强制。

P0-3 嵌入默认串行(batchSize=1、concurrency=1)

  • 现状EmbeddingConfig.batchSize 注释写明 “Defaults to 1”,concurrencycreateAsyncLimiter(cfg.concurrency)?? 1embedding.ts:143-165)。即默认情况下,全量索引是一次一个 chunk 串行请求
  • 问题:一个 300 页的 wiki 可能有 3000+ chunk,串行 + 每请求 100–300 ms ≈ 5–15 分钟。而代码里 batch 与并发的能力已经写好了fetchBatchEmbeddingssupportsOpenAiCompatibleBatch、批量失败自动逐条回退),只是默认没开。
  • 建议:默认 batchSize = 8~16concurrency = 4(与图片描述的 concurrency: 4 默认值保持一致);Google :embedContent 与 Doubao 多模态端点已有排除判断,会自动走单条路径。
  • 预期收益:首次索引与全量重建耗时数倍缩短,几乎零改动成本。
  • 风险:小型本地端点(llama.cpp 默认 512 token 上下文)可能过载 —— 已有 oversize 自动折半重试与批量失败逐条回退兜底;保守做法是按端点类型分档默认值。

P1 — 明显收益,可排期

P1-4 关键词打分升级为 BM25

  • 现状score_filesearch.rs:818)用固定加权(文件名 200 / 标题短语 50 / 正文短语 20×次 / 标题 token ×5 / 正文 token ×1),统计的是 token 种类数,无 IDF、无长度归一。
  • 问题:高频通用词与低频专有名词等权;长文档天然占优;embedding.ts:36 的注释还错误地写作 “BM25”。
  • 建议:实现真正的 BM25(k1≈1.2, b≈0.75)+ 保留现有的文件名/标题短语 bonus 作为 boost 项;或直接上 tantivy 与 P0-1 一并解决。同时修正陈旧注释。

P1-5 RRF 融合无权重、无信号区分

  • 现状score = 1/(60+rank_kw) + 1/(60+rank_vec),两路等权(search.rs:484)。
  • 建议:把 K 与每路权重做成可配置(甚至按 query 特征自适应 —— 纯专有名词查询提高关键词权重,长自然语言问句提高向量权重);文件名精确命中可考虑短路置顶(当前 200 分的 bonus 在 RRF 阶段被丢弃,只剩名次)。

P1-6 四信号关联度模型未接入检索(文档与代码漂移)

  • 现状calculateRelevancegraph-relevance.ts:247)只服务图谱可视化边权(wiki-graph.ts:285);getRelatedNodes 全仓库无调用方;线上检索的图扩展是 Rust 侧简单的一跳 wikilink 邻接。
  • 建议:二选一 ——
    • 接入:把四信号(尤其权重最高的"来源重叠")作为图扩展的排序依据替换 1/(rank+1)。来源共现是本系统独有的强信号,不用可惜。
    • 删除并更新 README:移除死代码,修正 README §7 的"阶段 2 四信号 / 2 跳衰减"描述。
  • 预期收益:前者提升图路召回质量;后者消除文档误导。当前状态(保留死代码 + README 描述不符)是最差的组合。

P1-7 MAX_SEARCH_FILES = 10_000 静默截断

  • 现状:超过 10000 个 md 文件后 breakeprintln,但响应体中没有任何截断标志search.rs:359-364)。
  • 建议:在 ProjectSearchResponse 增加 truncated: bool(文件树接口 files 已有 truncated 字段的先例),UI 与 API 均提示。

P1-8 overview.md 仍由模型整页重写

  • 现状:index/log 已代码化,但 overview 每次摄入后由 LLM 重新生成整页。
  • 问题:大 wiki 上存在与 index 相同的"重写丢内容"风险,只是暴露面小一些。
  • 建议:改为分节增量更新(模型只产出"本次新增段落"),或生成后做 diff 校验(内容缩水超过阈值时保留旧版并转 Review)。

P1-9 TS/Rust 双实现漂移风险

  • 现状:分块器、分词器、LLM 配置解析各有 TS + Rust 两份,靠注释约定同步(text-chunker.ts:4-7llm-task-routing.ts:20-22)。
  • 建议:抽取 golden 测试向量(一组输入 + 期望输出 JSON),两侧测试各自加载同一份 fixture 断言。CI 同时跑 npm run test:mockscargo test,任一侧改动导致偏离即失败。
  • 预期收益:把"靠人记得同步"变成"CI 强制"。

P1-10 Prompt 缓存只用在 Anthropic

  • 现状:只有 Anthropic 系统块打了 cache_control: ephemeralllm-providers.ts:564)。
  • 问题:摄入 prompt 的前缀(schema + purpose + index)在同一批次内高度稳定,是理想的缓存对象;OpenAI 自动前缀缓存要求前缀完全一致且足够长,当前 prompt 把变动内容(源文件名、日期)穿插在中部,削弱了缓存命中。
  • 建议:重排 prompt,把完全稳定的部分前置(schema/purpose/格式规范),变动部分后置;Gemini 可考虑显式 context caching。
  • 预期收益:批量摄入的 token 成本可观下降。

P2 — 增强方向,视需求决定

编号 项目 说明
P2-11 缺少重排(re-rank)阶段 当前是"召回即结果"。可加可选的 LLM / cross-encoder 重排:top-20 → top-5,尤其对 deep 模式性价比高
P2-12 缺少查询改写 / HyDE 中文长问句直接分词效果有限;可加一次轻量 LLM 查询扩写(同义词、实体归一),或 HyDE 生成假想答案再检索
P2-13 摄入无批量合并 每个小文件各自 2 次 LLM 调用。可按 token 预算把多个短源合并进一次分析调用(如聊天记录、日报),成本可降数倍
P2-14 图谱质量无写入期守卫 wikilink 全由 LLM 生成,只有 lint 事后发现死链。可在 writeFileBlocks 阶段校验链接目标是否存在,不存在则自动转成 missing-page Review 项
P2-15 无检索质量回归测试 .scenarios.test.ts 但无检索黄金集。建议建 20–50 条 (query, 期望命中页) 的固定集,改动检索逻辑时跑 recall@k,避免调参凭感觉
P2-16 可观测性偏弱 console.log / eprintln! 为主。建议加结构化指标:检索命中率、各路召回贡献、摄入耗时分布、降级触发次数、embedding 失败率,写入 .llm-wiki/metrics.jsonl 供用户与开发者诊断
P2-17 real-llm 测试成本高、CI 不跑 建议加录制/回放层(首次真实调用落盘 fixture,后续回放),让"真实响应形状"的回归也能进 CI
P2-18 单写者限制 LanceDB 嵌入式 + 进程内项目锁,不支持团队协作。若要支持,最现实的路径是"git 同步 Markdown + 各端本地重建向量索引",而非引入服务端
P2-19 便携包与源码版本漂移 D:\LLM_wiki\LLM Wiki.exe 内含 MCP server 0.4.26,源码已 0.6.9。建议在设置页显式展示 MCP server 版本并提示不一致
P2-20 agent-workspace/ 无清理策略 Agent 生成物持续累积,无 TTL 或容量上限。建议加"保留最近 N 次运行 / 超过 X MB 提示清理"

落地顺序建议

1
2
3
4
5
第一轮(低风险高收益):P0-3 调默认值 → P1-7 截断标志 → P1-6 消除文档漂移
第二轮(核心性能): P0-1 索引缓存 → P1-4 BM25(或一并上 tantivy)
第三轮(正确性): P0-2 page_id 迁移 → P1-8 overview 增量 → P2-14 链接守卫
第四轮(工程化): P1-9 golden 向量 → P2-15 检索黄金集 → P2-16 指标

其中 P0-3 只需改两个默认值,是投入产出比最高的一条;P0-1 与 P1-4 建议合并考虑(若决定引入 tantivy,两个问题一次解决)。


使用体验

直接评价:食之无味,弃之可惜;你很优秀,但是我们不合适。

设计方向

LLM-wiki正如其命名,专注于使用RAG查询时只是没有积累,每次提问重新发现,质量低且token算力开销大。通过维护一个持久化、链接明确的Markdown wiki 使得知识只须编译一次且可持续维护,维护可见的知识库。

其中的设计理念参考了LLM-维基中的思想,简单来说,就是Wiki作为一种可持续存在不断累积的“作品”,非常贴合当前LLM问答持续积累记忆的特点。

你从不(或很少)自己写维基——大语言模型负责编写和维护所有内容。你负责寻找、探索并提出正确的问题。LLM负责所有繁重的工作——总结、交叉引用、归档和簿记,这些都让知识库随着时间变得真正有用。实际上,我一边开着LLM代理,另一边开着Obsidian。LLM会根据我们的对话进行编辑,我则实时浏览结果——点击链接、查看图表视图、阅读更新后的页面。Obsidian 是集成开发环境(IDE);LLM是程序员;维基就是代码库。

这适用于很多不同的情境。举几个例子:

  • **个人:**追踪自己的目标、健康、心理、自我提升——整理日记、文章、播客笔记,并逐步构建一个结构化的自我形象。
  • 研究:在数周或数月内深入探讨一个主题——阅读论文、文章、报告,逐步构建一个带有不断发展论点的综合维基。
  • 读书:边读边整理每一章,构建角色、主题、情节线索及其关联的页面。到最后你就拥有了一个丰富的同伴维基。可以想象一下像托尔金之门这样的粉丝维基——成千上万个相互关联的页面,涵盖角色、地点、事件、语言,由志愿者社区多年建立。你可以边读边自己搭建类似的东西,由大型语言模型负责所有交叉引用和维护。
  • 业务/团队:由大型语言模型维护的内部维基,通过Slack线程、会议记录、项目文档、客户电话提供信息。可能会有人工参与审核更新。维基之所以保持最新,是因为大语言模型负责团队里没人愿意做的维护。
  • 竞争分析、尽职调查、行程规划、课程笔记、兴趣深入探讨——任何你在积累知识、希望它有条理而非零散的项目。

其详细内容可参考原仓库README。我们重点讲究其设计:

核心架构忠实遵循 Karpathy 的方法论:

  • 三层架构:原始资料(不可变)→ Wiki(LLM 生成)→ Schema(规则和配置)
  • 三个核心操作:Ingest(摄入)、Query(查询)、Lint(检查)
  • index.md 作为内容目录和 LLM 导航入口
  • log.md 作为可解析格式的时序操作记录
  • [[wikilink]] 语法用于交叉引用
  • YAML frontmatter 存在于每个 Wiki 页面
  • Obsidian 兼容 —— Wiki 目录可直接作为 Obsidian 仓库使用
  • 人类策展,LLM 维护 —— 基本角色分工

在这基础上增加了

  • 桌面应用:可实时观察Wiki资料,活动面板,markdown渲染等
  • 知识图谱:聚类,调色等等
  • Purpose.md:正式定义 为什么 这个 Wiki 存在。
  • 两步思维链:LLM阅读分析->生成wiki写入。
  • 多个对话支持:
  • Rust后端Agent+Skills支持:你i可以直接对话APP内的Agent,且拥有Skill管理能力。
  • 各类工程加强:文件查出,上下文配置,跨平台国际化等

体验截图和分析

image 20260828144530084

直接在APP中可上传原始资料包括md,pdf等。实现了比较方便的wiki初始构建能力。但说实话这些功能其实让Claude或者codex做总结也能达到类似的效果。

image 20260828143857031

能够将概念与实体作为wiki,对于想要尝试在一个新的领域使用AI做项目或者知识探索时非常合适,省去一次次询问和总结的过程。能很好的避免AI对进度不断推进,而用户的知识面却泛而浅没有及时更进的问题。

image 20260828144306390

知识图谱在我心目中基本算是炫技的东西。一个是实际开发环境,各个实体和概念混杂,并不是简单的分类和连线就可以解决。其次就是,你做的那么好看,对于现在主力的开发者:AI 来说,也只是一个链接嵌套的事情。而且匹配出了问题你还得自己问题

image

可灵活配置工具使用的LLM,使用更便宜简单的模型做LLM-wiki中的模型。可视化界面和及时测通等人性化设置很舒服,至少我觉得他非常优化了小白用户的配置体验。

**总结:**对于正常Agent使用,对于公司当前的AI使用配置,可以使得AI的描述和知识更加精确,好处是不需要复杂的配置和与其他工具不冲突,只需要挂一个skill和监听即可。

推荐优化方向:

  • 1 更加轻便的删除记忆或者回退操作功能。当前版本误选入无关文档会导致Wiki和图谱混乱。
  • 2 同名文件不同路径的选取问题。
  • 3 评分体系优化/中英文召回策略调整。
  • 4 冷热知识权重调整/cache设置等,在用户实际使用中往往项目趋向于某个模块时,会对部分知识常召回。可以使用cache/优先检索队列等方式降低召回消耗。
  • 5 其贪婪的将几乎所有知识都进行了wiki存储,我觉得是不太合适的,好的记忆不仅要全也应该更加精简。

推荐使用人群:

  • 行业新手,知识面窄的用户。
  • 加强冷门领域AI的鲁棒性的用户。
  • 研究型用户

不推荐使用人群:

  • 中大型项目开发者。
  • 日常使用。
  • CLI用户。

附录:关键文件索引

摄入

文件 作用
src/lib/ingest.ts 主管线,2400+ 行,含两阶段 prompt 构建
src/lib/ingest-queue.ts 持久化队列、串行处理、重试
src/lib/ingest-commit-coordinator.ts 写盘提交协调
src/lib/ingest-cache.ts SHA256 增量缓存
src/lib/ingest-sanitize.ts / ingest-parse.test.ts 块协议解析与清洗
src/lib/mineru.ts MinerU 三种后端
src/lib/image-caption-pipeline.ts / vision-caption.ts 多模态图片描述

检索与向量

文件 作用
src-tauri/src/commands/search.rs 三路混合检索 + RRF + 图扩展 + embedding HTTP,2264 行
src-tauri/src/commands/vectorstore.rs LanceDB v1/v2 表操作
src-tauri/src/commands/page_embedding.rs 单页嵌入(API 入口用),含 Rust 版分块器
src/lib/embedding.ts 嵌入编排、重建策略、并发限流
src/lib/text-chunker.ts Markdown 感知递归分块器
src/lib/search.ts 前端检索入口 + TS 版分词器

Agent

文件 作用
src-tauri/src/agent/runtime.rs Agent 主循环,约 4000 行
src-tauri/src/agent/tools.rs 14 个工具实现 + web/anytxt provider
src-tauri/src/agent/types.rs 请求/响应/模式类型定义
src-tauri/src/agent/permissions.rs 能力白名单
src-tauri/src/agent/skills.rs Skill 扫描与加载
src-tauri/src/agent/context.rs 上下文装配
src-tauri/src/agent/session.rs / cancel.rs 会话存储与取消注册表

图谱与分析

文件 作用
src/lib/graph-relevance.ts 四信号关联度模型
src/lib/wiki-graph.ts 图构建与边权
src/lib/wiki-graph-analysis.ts Louvain 社区 + 内聚度
src/lib/graph-insights.ts 惊奇连接 / 知识空白
src/lib/lint-structural-core.ts orphan / broken-link / no-outlinks

模型接入

文件 作用
src/lib/llm-providers.ts Provider 抽象 + 端点特例表,1100+ 行
src/lib/llm-client.ts 流式客户端
src/lib/llm-task-routing.ts Chat / Ingest 模型路由
src/lib/reasoning-capabilities.ts 推理强度归一化
src/lib/claude-cli-transport.ts / codex-cli-transport.ts 子进程传输
src/lib/context-budget.ts 上下文预算分配

集成

文件 作用
src-tauri/src/api_server.rs 本地 HTTP API :19828,3000+ 行
src-tauri/src/clip_server.rs 剪藏服务 :19827
mcp-server/src/index.ts MCP 11 工具
mcp-server/src/project-binding.ts 会话级项目 pin
extension/ Chrome MV3 剪藏扩展