LLM_Wiki_技术分析和个人体验

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 tagv0.6.9,commit723e259)
分析日期: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/react、lucide-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-asset、tray-icon)、tokio、rust-version = "1.88" |
| 网络 | reqwest 0.12(rustls-tls、stream)、tauri-plugin-http 带 unsafe-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 = true、codegen-units = 1、opt-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 | ┌ WebView (React) ─ UI + Ingest 编排 + 设置/密钥管理 |
一条被写进代码注释的架构宪法(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 | ① MinerU 预处理(仅 PDF,可选)→ 失败自动回退 pdfium |
值得学习的设计决策
(1) 自定义块协议而非 JSON
1 | ---FILE: wiki/entities/foo.md--- |
正则见 ingest.ts:339 FILE_BLOCK_REGEX / ingest.ts:2068 REVIEW_BLOCK_REGEX。理由很实际:生成内容本身包含代码围栏、YAML、Markdown 表格,JSON 转义会让模型频繁出错;块协议对流式输出和局部失败也更宽容。配套的 parseFileBlocks(ingest.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.”
由 updateWikiIndexDeterministically(ingest.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/60K、LONG_SOURCE_DIGEST_MAX = 15K、LONG_SOURCE_MAX_SINGLE_PASS_BUDGET = 300K(ingest.ts:47-53)。超长源先分块摘要再汇总,避免单次上下文溢出。
(5) 并发与崩溃恢复
withProjectLock项目级互斥ingest-commit-coordinator.ts串行提交,防止并发 LLM 调用写冲突- 队列持久化至
.llm-wiki/file-change-queue.json,应用重启后恢复 - 失败任务自动重试最多 3 次
(6) 结构化数据保真
Prompt 中反复强调(ingest.ts:2178、ingest.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_query(search.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 | query → embedding → LanceDB ANN 取 topK×3(≥30)个 chunk |
实现见 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(一页一行)→ v2wiki_chunks_v2(一 chunk 一行)(vectorstore.rs:62-66),保留 legacy 表的行数检测与清理路径,支持平滑迁移 - Arrow schema:
chunk_id: Utf8/page_id: Utf8/embedding: FixedSizeList<Float32, dim>/chunk_text/heading_path - 嵌入文本富化(
embedding.ts:299 enrichChunkForEmbedding):实际编码的是页标题 + 标题面包屑 + chunk 正文。注释说明理由:一个 300 字的孤立片段,只有显式带上"它属于哪几层小节"才具备可检索性 - 增量优化:每写入 20 页触发一次
optimize(INCREMENTAL_OPTIMIZE_PAGE_THRESHOLD = 20) - 重建索引采用 prepare-all-then-swap(
embedding.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 在分块前剥离,避免元数据污染向量
- 小块合并(<
minChars200)避免向量库被 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.search、wiki.read_page、source.search、graph.search、web.search、anytxt.search |
| 研究 | deep_research.run |
| 写入 | wiki.write_page(默认 create-only,覆盖需显式 allowOverwrite)、workspace.write_file、workspace.append_file |
| 技能 | skills.load、skill.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_budget(runtime.rs:2798) |
Fast 4 / Standard·LocalFirst 8 / Deep 12;带 skills 时 8 / 16 / 20 |
| 检索预算 | agent_loop_retrieval_budget(runtime.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() |
按模型窗口裁剪观察记录 |
三种检索模式(AgentRetrievalMode,types.rs:20)
-
Standard —— 单遍 / planner 驱动,保持既有行为
-
Smart —— 有界证据闭环:每轮只针对"未闭合的证据缺口"发一次精化检索,优先跟进已发现的页面而非扩大查询范围
-
Faithful(只读原文) —— 实现不是靠 prompt 说"请只用原文",而是在上下文装配前物理移除
overview.md与schema.md(runtime.rs:2783-2796),注释明说:“asking the model to ignore either would be a soft guarantee rather than source-only context isolation.”
安全模型
- 能力白名单
PermissionPolicy(permissions.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 字符)。
知识上下文附着
AgentKnowledgeContext(types.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 | entity → concept 1.2 | entity 0.8 | source 1.0 | synthesis 1.0 | query 0.8 |
设计上最有意思的一点:权重最高的不是显式链接,而是"来源重叠"(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 | maxCtx(默认 204800) |
一个漂亮的设计闭环: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 | interface ProviderConfig { |
支持三种 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_tokens → max_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_content,content 返回空 |
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。
一个值得注意的安全设计:会话级项目 pin(project-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 | npm install |
.env.test.local由src/test-helpers/load-test-env.ts加载,文件缺失时该 setup 为 no-op,所以 mock 测试可以无条件运行。
5.2 首次配置(5 步)
| 步骤 | 操作 | 产生什么 |
|---|---|---|
| ① 创建项目 | 欢迎页 → 新建 → 选场景模板 | 从 templates.ts:640 的 5 套模板生成 purpose.md + schema.md + wiki/ 目录骨架 + raw/sources、raw/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 | wiki/sources/<源文件名>.md ← 源摘要页(必定生成,有兜底机制) |
② 查询(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 | <项目>/.llm-wiki/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 | ① 设置 → API + MCP → 开启 API,生成 Token(可选是否允许本机免鉴权 / LAN 访问) |
或直接用官方 agent skill 一行接入:
1 | 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 | curl -H "Authorization: Bearer $TOKEN" \ |
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 值得迁移到其他系统的具体手法
- 把模型不可靠的环节移出关键路径
index / log 由代码确定性维护,模型只负责它擅长的内容生成。判断标准:这个操作出错的代价是"局部瑕疵"还是"整体损坏"?后者一律代码化。 - 结构约束优于文本约束
审核项 OPTIONS 被枚举死;Faithful 模式靠物理删除上下文而非 prompt 请求。凡是能用类型 / 枚举 / 数据结构表达的约束,都不要写进 prompt。 - 块协议 > JSON
当模型输出本身包含大量代码围栏、YAML、表格时,自定义分隔块的容错性远高于 JSON。 - RRF 是异构召回融合的最小可用解
不需要归一化分数尺度,只用名次,1/(K + rank),K=60 是文献推荐值。多路召回系统的首选融合方案。 - 多路召回要留固定配额
图谱路占 15–30% 结果窗口且随向量覆盖率自适应,否则结构信号永远被语义信号挤掉。弱信号需要制度性保护。 - chunk 嵌入必须带结构面包屑
title + heading path + text是低成本高收益的召回改进 —— 短 chunk 的语义歧义主要来自缺失的层级上下文。 - 降级链要显式设计并可观测
MinerU→pdfium、向量→关键词、批量嵌入→逐条、planner 失败→单次搜索、oversize→文本折半。每一级降级都要留下用户可见的原因(getLastEmbeddingError()就是为此存在)。 - 异步人机协作
把 human-in-the-loop 从阻塞式改成队列式,是让知识库能规模化的关键。原方法论建议全程参与,本实现改成事后处理的 Review 队列 —— 这是从"个人玩具"到"可持续系统"的分水岭。 - 同一内核多入口
Agent 内核下沉到 Rust,UI / HTTP API / MCP 共享。避免"三套实现三种行为"是长期维护成本最大的一笔节省。 - 端点兼容性知识要沉淀成代码注释
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-7、llm-task-routing.ts:20-22、search.rs与search.ts) - 并发原语自建且带注释:
createAsyncLimiter的许可转移逻辑、parallelForEach的 worker 模型、withProjectLock的锁粒度,都有解释为什么不用现成库 - 错误信息面向用户:
"Endpoint rejected input even at N chars. Lower Settings -> Embedding -> Max Chunk Chars."—— 错误信息直接给出操作路径
八、局限与风险
以下为客观评估,非贬低:
- 关键词打分是启发式加权,不是 BM25
无 IDF、无长度归一,统计 token 种类数而非词频,长文档天然占优。相关注释还写着 “BM25”,属陈旧信息。改进空间明确。 - README 与代码存在漂移
- 四信号模型被描述为检索阶段 2,实际只服务图谱可视化
- "2 跳衰减遍历"在当前 Rust 检索路径中是一跳
- 引用的召回率 58.2% → 71.4% 无仓库内可复现基准支撑
- 摄入成本随源数线性增长
每个源至少 2 次 LLM 调用(长文档 map-reduce 更多,加上图片 VLM 描述)。SHA256 缓存只能防重复摄入,防不了首次成本。大批量导入的 token 开销可观。 - 输出协议解析仍是脆弱环节
靠严格 prompt + 多重兜底(buildFallbackSourceSummary、文件名重写、frontmatter 日期补盖、语言一致性检查)维持。小模型下失败率会明显上升 —— 这也是为什么代码里有大量"兜底生成源摘要页"的逻辑。 - 图谱质量依赖模型写链接的纪律
wikilink 由 LLM 生成,链接密度与准确性直接决定图扩展召回和社区检测质量,无独立校验机制(仅 lint 事后发现 broken-link / orphan)。 - 单机单用户
LanceDB 嵌入式无多写者支持,项目锁是进程内互斥,不支持团队协作 —— 尽管原方法论把 business/team 列为典型用例。 - overview.md 仍由模型重写
index / log 已代码化,但 overview 每次摄入后由 LLM 重新生成,在大 wiki 上仍有内容丢失风险。 - 版本漂移提醒
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_inner(search.rs:168)和build_knowledge_context_index(tools.rs:2085)也各自重复一遍同样的全量扫描。 - 问题:单次查询复杂度 O(N 文件 × 文件大小)。500 页 × 平均 8 KB ≈ 每次查询读 4 MB 并做多次全文小写化。Agent 一轮对话可能触发 3–6 次检索,成本线性叠加。
- 建议:
- 建内存索引缓存:
{path → (mtime, size, title, tokens, wikilinks, images)},用 mtime+size 做失效判断。项目已有.llm-wiki/file-snapshot.json(md5+size+mtime)这套基础设施可复用。 - 进一步可引入 tantivy(Rust 原生全文索引),同时解决 P1-4 的 BM25 问题。
- 图谱邻接表单独缓存,随
dataVersion失效(前端已有 dataVersion 信号机制)。
- 建内存索引缓存:
- 预期收益:中大型 wiki 的查询延迟从"随规模线性增长"变为近似常数;Agent 多轮检索的体感改善最明显。
- 风险:需处理外部编辑(Obsidian 写入)的缓存失效 —— 已有
notify文件监听可复用。
P0-2 向量库 page_id 使用文件名 stem,跨目录同名页冲突
-
现状:
page_id= 文件名去扩展名(search.rs:369-379)。当wiki/entities/transformer.md与wiki/concepts/transformer.md同时存在时,两者共用同一个向量 page_id,后写入者覆盖前者。代码已经发现了这个问题,但只打了一行eprintln!:duplicate wiki page stem '{stem}': '{previous}' and '{}' share one vector page_id -
问题:静默的检索正确性损失。中文项目尤其容易撞名(“概述”“方法”“总结”)。用户完全无感知。
-
建议:
page_id改为项目相对路径规范化后的值(如wiki/entities/transformer),validate_page_id放宽允许/。- 提供一次性迁移:检测 legacy id 格式 → 触发重建(已有
vector_legacy_row_count/vector_drop_legacy的先例可参照)。 - 过渡期至少把该警告透出到 UI(设置 → Embedding 状态区),而不是只进 stderr。
-
预期收益:消除一类静默错误。
-
风险:需要全量重建索引;建议做成"检测到冲突时提示用户重建"而非强制。
P0-3 嵌入默认串行(batchSize=1、concurrency=1)
- 现状:
EmbeddingConfig.batchSize注释写明 “Defaults to 1”,concurrency在createAsyncLimiter(cfg.concurrency)中?? 1(embedding.ts:143-165)。即默认情况下,全量索引是一次一个 chunk 串行请求。 - 问题:一个 300 页的 wiki 可能有 3000+ chunk,串行 + 每请求 100–300 ms ≈ 5–15 分钟。而代码里 batch 与并发的能力已经写好了(
fetchBatchEmbeddings、supportsOpenAiCompatibleBatch、批量失败自动逐条回退),只是默认没开。 - 建议:默认
batchSize = 8~16、concurrency = 4(与图片描述的concurrency: 4默认值保持一致);Google:embedContent与 Doubao 多模态端点已有排除判断,会自动走单条路径。 - 预期收益:首次索引与全量重建耗时数倍缩短,几乎零改动成本。
- 风险:小型本地端点(llama.cpp 默认 512 token 上下文)可能过载 —— 已有 oversize 自动折半重试与批量失败逐条回退兜底;保守做法是按端点类型分档默认值。
P1 — 明显收益,可排期
P1-4 关键词打分升级为 BM25
- 现状:
score_file(search.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 四信号关联度模型未接入检索(文档与代码漂移)
- 现状:
calculateRelevance(graph-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 文件后
break并eprintln,但响应体中没有任何截断标志(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-7、llm-task-routing.ts:20-22)。 - 建议:抽取 golden 测试向量(一组输入 + 期望输出 JSON),两侧测试各自加载同一份 fixture 断言。CI 同时跑
npm run test:mocks与cargo test,任一侧改动导致偏离即失败。 - 预期收益:把"靠人记得同步"变成"CI 强制"。
P1-10 Prompt 缓存只用在 Anthropic
- 现状:只有 Anthropic 系统块打了
cache_control: ephemeral(llm-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 | 第一轮(低风险高收益):P0-3 调默认值 → P1-7 截断标志 → P1-6 消除文档漂移 |
其中 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管理能力。
- 各类工程加强:文件查出,上下文配置,跨平台国际化等
体验截图和分析

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

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

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

可灵活配置工具使用的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 剪藏扩展 |




