
说实话这个问题的答案在我电脑里躺了很久。我一直被一件事折磨项目 Wiki 写了三十多页代码仓里躺了几千个文件可每次想查点东西Wiki 是一套说法代码是另一套写法两个东西各说各话谁也接不上谁。后来我搭了一个本地「知识助手」把 Wiki 和代码放进同一个知识库用语义检索加本地大模型来问答总算把这条裂缝补上了。这篇文章就是把我的搭建过程、踩坑经验还有各种参数选择的心得一次性讲清楚。这套方案适合谁适合团队里维护文档的技术负责人也适合一个人维护好几个仓库的独立开发者更适合那些想把自己的 Obsidian 笔记、飞书 Wiki、代码注释整合成第二大脑的知识管理爱好者。只要你受够了在文档和代码之间反复横跳这篇内容就能给你一套立刻能落地的做法。1. Wiki 和代码为什么总是各说各话1.1 三个典型割裂场景第一个场景是文档界面和代码实现对不上。业务改了功能下线了架构调整了但 Wiki 大概率还停留在三个月前的截图。我见过最离谱的文档里写着系统包含 A、B、C 三个模块实际代码里 A 已经被重构掉了B 和 C 合并成了 D新人照着文档去对代码第一反应是怀疑自己 clone 错了分支。第二个场景是新人入职根本无从下手。Wiki 写的是结果描述登录模块支持 OAuth2.0、支持验证码、支持自动续期。可新人真正想知道的是这段逻辑在哪个仓库、哪个目录、哪个文件、哪个函数里方法之间怎么调用数据怎么流转。这些关键信息Wiki 里几乎永远找不到。于是新人只能靠 IDE 全局搜索加肉眼硬啃一啃就是两三天。第三个场景是老手写代码但不写文档。代码里全是auth_service、TokenManager、SessionGuard这种名字变量命名倒是规范可背后的业务决策没人记录。等这个人一离职知识直接断层。后来的人看到代码只能推断它在做什么却永远不知道它为什么这么做。这三个场景的共同点文档是叙述性的代码是执行性的两者的组织方式和检索方式天然不一样。文档按主题组织适合通篇阅读代码按模块组织适合精确定位。Wiki 靠关键词搜索结果遇到登录流程这种业务描述和auth_service这种技术符号之间根本没有关联自然各说各话。1.2 问题的根源语境断裂拆开来看Wiki 和代码割裂的根源不只是更新不及时这么简单。更深一层是语境断裂Wiki 里的每个概念在代码里都有一串对应的符号代码里的每个设计在 Wiki 里都有一篇对应的说明。可这两者之间没有任何桥梁。Wiki 的登录模块是一段自然语言代码里的login()是一段函数实现它们之间缺少一层映射。人肉去维护这种映射短期可行项目一大人就崩了。另一个根源是信息更新节奏完全不在一个频率。代码每次 commit 都在变文档可能一个季度才动一次。代码是活的文档是按快门拍出来的照片照片怎么可能跟得上活物你不可能要求每个开发都在改代码的同一秒去改 Wiki这不现实。所以必须有一种机制能自动把代码和文档的变化汇集到一起让知识随代码同步更新。1.3 知识助手到底解决什么知识助手解决的不是写文档这件事而是检索和理解这件事。它做的事情本质上是把 Wiki 文档、代码仓库、技术笔记全部切碎、向量化塞进同一个向量数据库你提问的时候它先做语义检索把最相关的文档片段和代码片段捞出来再交给本地大模型组织成带出处、可验证的回答。为什么语义检索能打通割裂因为传统关键词搜索是字面匹配你搜登录流程它只找包含这几个字的页面而语义检索是意图匹配它能把你的问题映射到auth_service相关的代码片段和文档段落上。同一个问题关键词搜索可能一无所获语义检索却能直接从代码仓库里捞出一段函数签名。这就是知识助手区别于传统 Wiki 搜索的核心价值。2. 知识助手的设计思路与方案选型2.1 整体架构采集、切分、向量化、检索、生成整套系统的架构我用一句话概括先囤书再拆书然后配一个随叫随到的图书管理员。囤书是把 Wiki、代码、笔记全部采集进来拆书是把长文档按语义切成小块每一小块做向量化存进数据库图书管理员就是 RAG检索增强生成管道它收到问题后先到书库里翻出最相关的几页再把这些内容连同问题一起交给大模型由大模型组织回答。RAG 这个词听起来玄乎其实就是先检索后生成。我不需要让大模型记住我全部的代码这不现实也浪费算力我只需要它每次回答前临时去知识库里翻相关资料然后基于这些资料作答。这样一来模型挂掉也不怕知识库是独立的知识库更新了回答自然跟着更新不需要重新训练模型。具体到我用的架构是五段式数据采集层负责对接 Obsidian、飞书 Wiki、Git 仓库预处理层负责格式清洗、敏感信息过滤、Markdown 结构解析切分层负责把文档和代码切成检索友好的片段向量层负责生成 embedding 和相似度检索生成层负责把检索结果组装成 Prompt交给本地大模型生成最终答案。每一层都要独立设计哪一层出了问题都能单独替换。2.2 关键选型为什么优先考虑本地方案关于本地还是在线我自己的判断很明确优先本地数据不出内网是底线。团队代码和 Wiki 里往往有内部架构、业务策略、未公开功能的信息这些内容扔给外部 API 服务做向量化或者问答等于把底裤露给人家。就算公司允许你也要考虑成本——文档量一大按 token 计费的在线方案每个月账单看得人心慌。本地方案的好处不止是隐私。首先是可控模型参数、切分参数、检索逻辑全在自己手里想调就调。其次是可离线出差、断网、内网隔离环境知识助手照常可用。第三是可复用同一个知识库可以接不同的模型今天用 7B 模型明天换了 14B 模型向量库不用动只替换生成层就行。当然本地方案有代价需要一台内存够大的机器CPU 推理慢GPU 不是每个人都有。我的态度是本地跑小模型本地向量检索组合起来的效果对绝大多数查代码、找文档、捋逻辑的场景完全够用。真要追求顶级的代码理解能力可以在本地 RAG 管道上再挂一个更大的模型做后审但那是后话了。2.3 工具选型参考表我踩了一圈工具把最终落在实处的方案整理成表格每个环节都写了我选它的理由环节我用的方案选型理由知识库Obsidian / 飞书 Wiki 导出 Markdown文档已有重点是统一转成 Markdown 格式方便后续切分代码源本地 Git 仓库、码云 / GitHub 镜像直接 clone 到本地按目录结构扫描保留文件路径和语言信息文档切分Markdown 标题结构切分 递归字符切分按标题切能保住章节语义按字符切兜底长段落代码切分按函数/类级别的语法感知切分避免代码块被腰斩保证每个片段都是一个完整语义单元向量库Chroma本地单机足够零配置API 简单持久化方便嵌入模型BGE-M3中文效果好支持 1024 维向量本地运行即可生成模型Ollama Qwen2.5 7B中文代码场景表现稳16G 内存机器能跑量化后体积可控对话前端自写 CLI Open WebUI自己写的脚本用于调试WebUI 用于日常问答共用同一套 API这表里的每一项都可以替换。你如果团队已经上了 Confluence就把 Confluence 导出成 HTML 再转 Markdown如果机器有独显生成模型换 14B 效果会明显上一个台阶。核心思路是数据源多样化管道标准化。3. 落地实操从零搭一个本地知识助手3.1 第一步盘点数据源与导出动手之前先别急着写代码先回答一个问题你有哪些知识资产我的建议是画一张清单把 Wiki 页面、代码仓库、接口文档、FAQ、个人笔记全部列出来每个数据源标清楚格式和大概体量。我当时的清单大概是飞书 Wiki 导出 Markdown 约 80 篇代码仓库 2 个共 3000 多个文件Obsidian 笔记零散几百条。导出这一步有几个坑要注意。飞书 Wiki 可以按目录导出为 Markdown 或者 Word导出之后要检查图片路径和代码块格式很多导出工具会把代码块变成普通文本缩进全没了。Git 仓库只需要 clone 到本地固定目录但要注意排除.git目录、node_modules、dist这类无关文件否则向量库里全是依赖包代码检索时噪声极大。导出完成后建议做一次目录规范化。我的处理方式是建一个sources/目录下面分docs/和code/两个子目录docs/放所有 Wiki 和笔记code/按仓库名分目录。这样做的好处是后续给每条知识生成 metadata 时可以从路径上直接读出类型、模块、仓库名检索时可以直接按 metadata 过滤。3.2 第二步文档切分与向量化切分是整套系统里最影响效果的一环比模型选型还关键。为什么不能把整篇文档当成一条向量存进去因为向量检索返回的是一个片段如果你每个片段是一整篇三千字的文章把整篇文章塞进大模型的上下文既浪费 token又会引入大量无关内容回答精度反而下降。反过来说如果切得太碎比如每个句子一条向量检索出来的内容就缺上下文模型看到一句话根本不知道在讲什么。我的经验是按 Markdown 标题结构切保持章节完整。用 Markdown 的#、##、###做分界每一章作为基本片段如果某章太长再用递归字符切分按窗口继续切分窗口设 500重叠设 80。重叠的目的是让相邻片段之间保持上下文衔接避免一个问题正好落在切口上导致内容缺失。代码的切分思路和文档不一样。代码不能按字符切必须按语法边界切。我试过用行数硬切效果极差一个函数被切成两半前半部分有签名没逻辑后半部分有逻辑没签名模型根本无法判断这段代码在干什么。正确做法是按函数、类、方法作为切分单元从 AST 里解析出每个函数/类的起止行号按行号切片把函数签名、注释、函数体一起保留在同一条向量里。切分完成后就是向量化。嵌入模型我选了 BGE-M3中文和代码混合场景下表现比很多通用模型好。每条数据存入向量库时除了文本本身我还会写入 metadata来源文件、章节标题、文件路径、语言类型、函数名、标签。这些 metadata 在后续检索过滤时能发挥巨大作用比如只搜某个模块的代码或者只搜 Wiki 文档。from langchain_text_splitters import MarkdownHeaderTextSplitter, RecursiveCharacterTextSplitter headers_to_split_on [ (#, 章节), (##, 子章节), ] markdown_splitter MarkdownHeaderTextSplitter(headers_to_split_onheaders_to_split_on) docs markdown_splitter.split_text(markdown_text) recursive_splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap80, separators[\n\n, \n, 。, , ] ) final_docs recursive_splitter.split_documents(docs)3.3 第三步把代码变成可检索的上下文很多人搭知识助手只处理文档代码文件直接当作纯文本切了入库效果往往一般。原因很简单代码是结构化的纯文本切分破坏了它的结构。我后来摸索出一个效果明显的做法代码元数据 模块摘要双管齐下。具体做法是对每个代码文件除了存函数级片段之外再生成一份模块摘要。摘要内容包括这个文件是干什么的、涉及哪些核心类、对外暴露了哪些接口、关键依赖是什么。这份摘要可以人工写也可以用本地模型批量生成。摘要随文件的函数片段一起入库用户问登录流程怎么走的时检索系统先命中模块摘要再顺着摘要里的文件路径定位到具体函数回答就精确多了。一定要在每条代码向量里带上文件路径和函数签名这不仅是溯源需要更是回答可验证的关键。模型如果回答登录逻辑位于auth/service.py中的login()使用者就能直接跳过去看代码。事实上给出位置这件事比给出结论在代码场景里更有价值。代码仓库还有个加分动作把 commit message 和关键 PR 描述也作为语料入库。commit message 里经常写着修正 token 过期判断、调整重试策略这些可以回答为什么这里要这样改。我试过一次之后把所有历史 commit 都导入了效果出奇好尤其适合回答那些当时为什么这么设计的问题。3.4 第四步检索与生成串联切分和向量化完成之后知识库就建好了。接下来要做的就是写一条 RAG 管道拿到用户问题转成向量去向量库做相似度检索取回 top_k 片段拼装 Prompt调用本地模型生成回答。检索参数我在实战里常用的配置是top_k6。太少比如只取 2 条容易漏掉关键信息太多比如取 20 条又会塞入大量无关片段模型容易跑偏。如果你的文档之间关联度很高可以适当加大top_k但 Prompt 的长度会涨回答速度会慢。调参的时候记得看两件事第一检索回来的片段跟问题有没有直接关系第二追加到 Prompt 里的总字符数有没有超过窗口限制。Prompt 设计是要单独花功夫的。我的 Prompt 核心约束有三条只依据提供的上下文回答不许编造回答必须引用来源文件的路径上下文不足以回答时直接说明不知道。这三条写不写回答质量差距天壤之别。不写的模型经常自由发挥张嘴就编一个接口名你照着去找代码根本不存在。def ask(question): docs vectorstore.similarity_search(question, k6) context \n\n---\n\n.join([ f来源: {d.metadata.get(source, 未知)}\n{d.page_content} for d in docs ]) prompt f你是一名熟悉本项目代码与文档的技术助手。 请严格依据下面的上下文回答问题。 如果上下文内容不足请直接说知识库中没有找到相关信息。 回答时引用相关文件的路径。 上下文 {context} 问题{question} 回答 response ollama.chat(modelqwen2.5:7b, messages[{role: user, content: prompt}]) return response[message][content]这个脚本虽然简陋但已经足够跑通一条完整链路。我建议你先跑通它再去考虑 Web 界面、历史记录、多用户这些功能。管道通了后面都是锦上添花。3.5 第五步接入本地模型与对话界面生成模型我用 Ollama 跑 Qwen2.5 7B 的量化版16G 内存的笔记本跑 CPU 推理确实慢一个问题要等十几秒但日常用可以接受。如果你的机器有 24G 以上内存或者一块 GPU直接上 14B回复质量和代码理解能力会明显提升。这一步注意量化版本体积小适合本地部署但精度略有损失代码细节敏感的场景建议用更高精度。对话界面我留了两个入口。日常调试用命令行脚本问题输入进去直接打印回答方便快速验证检索效果同事也要用的时候就上 Open WebUI配置好 Ollama 的 API 地址再把向量检索结果通过 Prompt 方式接进去浏览器里就能问答了。前端本质上只是入口核心逻辑都在 RAG 那一层换什么界面都不影响。如果你完全不想写代码也可以用 AnythingLLM 这种一体化工具直接把文档和代码目录拖进去自动完成切分和向量化再连上 Ollama 就能用。这种工具的好处是零门槛缺点是检索和切分的参数可控性差出了问题不好排查。我的建议是先用一体化工具验证思路到底有没有用确定要长期用了再迁移到自建管道。4. 常见问题与排查技巧实录4.1 检索结果不相关 / 答非所问这是我被问得最多的一个问题为什么我的知识助手回答得驴唇不对马嘴按照我的排查顺序第一件事不是调模型而是直接看检索回来的原文片段。输出一下similarity_search的结果看看向量库捞回来的到底是些什么内容。如果捞回来的内容本身就不相关那问题一定出在切分或向量化环节。切分粒度过大一个片段混进了多个主题检索命中一半扯出一半切分粒度过小片段之间失去上下文检索只捞到一句没头没尾的话。这两种情况都把chunk_size往中间调我常用的文档切分是 400 到 800 之间。如果嵌入模型对中文理解差可以对比几个本地模型的效果再定如果检索时混入太多代码或文档噪声可以按 metadata 过滤比如限制只检索某种类型的数据源。4.2 代码片段经常答非所问怎么办代码场景最常见的翻车现场是模型回答的头头是道引用的函数名却是编的根本不存在。这类问题从根上说有两个来源一是切分截断了代码结构二是模型本身代码理解能力不足。先解决切分问题确认每个片段是不是完整的函数或类再确认 metadata 里有没有文件路径和函数签名没有的话照样容易幻觉最后再考虑换更大的模型。还有一个很容易被忽略的点代码注释和代码本身被切到了不同的片段。如果切分策略按字符硬切函数上面的注释属于上一个片段函数体属于下一个片段模型只看到函数体当然不知道这个函数是干什么的。解决办法是用语法感知切分让注释、签名、函数体永远在同一条向量里。我在实操中发现这个改动对回答质量的影响比换一个更大的模型还明显。4.3 知识库更新后回答仍是旧的代码每天都在变知识库向量如果不跟着变回答就是过时的。我遇到过的坑主要有两个一个是向量库里旧版本的向量没删除新版本插入后检索时新旧混杂回答里新老接口各说一句另一个是文档改了但嵌入向量没重新生成干脆返回的是缓存命中。解决方案其实很简单给每个数据源打一个doc_id内容变化后计算 hashhash 变了就把旧的向量删掉再插入新的。代码仓库更新频率高我的习惯是每次 pull 之后只对发生变化的文件重新做切分和向量化。如果数据量不大比如几万条以内每月做一次全量重建更省心直接清空向量库重新生成成本也就几分钟的事。4.4 本地模型回答幻觉所谓幻觉就是模型一本正经地胡说八道。代码场景里幻觉的杀伤力比文档场景大得多因为代码是要拿去跑的。一个不存在的函数名模型能把它说得极其自然连参数类型都替你编好。要压制幻觉单靠调 Prompt 是不够的从管道上就要下功夫。第一temperature调到最低比如 0.1 或 0减少模型自由发挥的空间。第二Prompt 里强调只依据上下文回答并强制要求引用来源路径模型在必须给出处时会更谨慎。第三top_k别太小太小了关键上下文缺失模型为了凑答案只能编也别太大太大噪声多。第四对高价值的代码问答建议增加一道人工抽检流程特别是回答里包含 API 调用时先验证再使用。4.5 参数速查表把常用的参数整理成速查表方便配置时直接对照参数推荐值说明文档 chunk_size400 ~ 800按章节长短调整中文场景建议 500文档 chunk_overlap50 ~ 100保留上下文衔接别超过 chunk 的 20%代码切分单元函数 / 类用语法树解析按起止行号切片检索 top_k6 ~ 10先试 6上下文不够再加大temperature0 ~ 0.1代码问答必须低温度向量库Chroma单机够用无需额外服务本地模型参数量7B 起步有 GPU 直接上 14B4.6 几个让效果翻倍的独门小技巧第一个技巧把术语表放进知识库。给 Wiki 里出现的高频技术名词做一份解释表比如令牌网关灰度各是什么在这个项目里指什么入库后检索命中率提升明显。原因是很多问题里的用词和文档里的用词不一致术语表就像一座桥天然把语义拉近了。第二个技巧commit message 和 PR 描述一定要收进语料。这部分内容以前是知识孤岛没人会去翻但恰恰记录了最真实的决策过程。有一次我让知识助手回答为什么网关层要做两次重试它直接引用了两年前的 commit当时线上偶发超时加了重试后来发现幂等性问题又用了独特 ID 去重。这种历史上下文翻代码翻一天都翻不出来。第三个技巧不要追求第一次就搭得完美。先接两条数据源跑通流程再逐渐把其他仓库、文档加进来。知识助手的效果依赖数据质量和迭代次数你用得越多越知道该往库里加什么、Prompt 该怎么调。我现在的知识库已经跑了大半年每周都有新语料加进来效果比刚搭建时好了不止一个档次。我自己现在处理一个陌生模块流程是这样的先打开知识助手问一句这个模块的核心链路是什么拿到它引用的文件路径再用 IDE 跳过去看真实代码两分钟就能定位。放在以前先翻 Wiki 再看代码再搜 commit半小时起步还经常看不全。设置好之后你会跟我一样再也回不去了。