尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

用RAG搭建本地知识助手,打通Wiki与代码割裂

用RAG搭建本地知识助手,打通Wiki与代码割裂 你有没有遇到过这种场景项目 Wiki 里明明写着“用户登录已迁移到 OAuth 2.0 流程”可你翻代码的时候发现实际实现早就换成了 JWT 换 token又或者你在写量化策略的时候明明记得 Wiki 上有一篇 K 线预处理的踩坑记录却因为目录太乱找不到入口只能硬着头皮把代码重写一遍。Wiki 和代码各说各话不是文档团队偷懒而是知识载体本身就有代差。后来我搭了一个本地“知识助手”把 Wiki、代码和零散笔记全部丢进去用 RAG 的方式让它们互相引用、彼此解释效果比我预想的好不少。这篇就把我的思路、搭建过程和踩过的坑完整写出来给同样被文档和代码割裂折磨的人做个参考。1. 为什么 Wiki 和代码总在“各说各话”1.1 知识散落的三种典型姿势我先说说我最常遇到的三种割裂场景你看看熟不熟悉。第一种是“文档滞后型”。代码迭代快接口改了参数函数换了位置但 Wiki 里的描述还停在三个月前。写过技术文档的人都知道文档更新永远赶不上代码演进尤其是团队没有专职文档工程师的时候文档靠“谁有空谁维护”时效性基本靠自觉。我在一个量化策略项目里就吃过这个亏Wiki 上写着“行情订阅使用 WebSocket 协议”结果代码早就切到了 gRPC 流式推送我从头查了两天才发现是文档没跟上。第二种是“索引错位型”。Wiki 和代码分别存放在不同的系统里Wiki 里写的是业务概念比如“订单状态机”而代码里对应的是OrderStateMachine、OrderEvent、transition()这一堆符号。当我不知道某个业务概念对应哪几个代码文件时想在 IDE 里全局搜索也无从下手。反过来从代码找业务文档也一样难你看到一段诡异的逻辑想找设计说明但代码注释只有一两行Wiki 又不知道入口在哪里。第三种是“上下文割裂型”。我经常在写代码时有一个疑问比如“这个服务重试机制是怎么定义的”我得先开浏览器查 Wiki再开 IDE 翻代码再回 Wiki 找配置说明来回切换思路不知道断了多少次。哪怕文档和代码都存在人也很难同时“阅读”两种介质并建立联系。这种认知负担很隐性但它每天都在吃掉大量时间。说白了Wiki 和代码各自是完整的体系但“人”需要同时在两者之间来回跳转时就出现了一条很深的鸿沟。知识助手要解决的不是“再写一份更好的文档”而是把这两套体系在检索层面打通让人用一个入口就能同时拿到代码层面的证据和文档层面的解释。1.2 知识助手到底在解决什么问题把问题拆开看知识助手其实要兑现三件事。第一件是统一的入口。不管我问的是“登录接口怎么调”还是“为什么这里的重试会卡死”知识助手都应该给出一个答案并且这个答案基于本地的 Wiki 文档和代码内容而不是模型凭空编造的泛化回答。这就天然要求一个带检索的架构否则直接套大模型很容易出幻觉。第二件是双向溯源。答案里如果说“订单取消之后会推送OrderCancelEvent”我希望能直接指向 Wiki 的某个章节、代码的某个文件让我可以点过去验证。知识助手不能是一个封闭的黑盒它的定位应该是“带证据的回答”证据链完整才有真正的参考价值。第三件是状态同步能力。我用 Git 管理代码也希望知识库能跟着代码版本走至少能让索引定期重建让 Wiki 和代码的引用关系可追踪。这一点听起来简单但做起来牵扯到切块粒度、索引更新策略和文档规范是后面会重点讲的坑。我做这个项目的时候最核心的原则就一句话知识助手不是用来代替人看文档的而是用来减少人找文档和代码之间对应关系的时间。把检索、溯源、同步这三件事做好它的价值就已经到位了。2. 选型思考为什么是「RAG 本地知识库」2.1 RAG 的运行链路RAG 是 Retrieval-Augmented Generation 的缩写直译过来就是“检索增强生成”。它的核心思路很简单大模型天生不知道自己训练完之后出现了什么新内容也不会知道你本地代码的所有细节但如果我先从知识库里检索出相关的片段再把片段和问题一起塞给大模型它就能“看着资料”回答问题而不是凭记忆瞎编。我落地时跑通的链路是这么几段采集文件、清洗解析、切块、向量化、存储、检索、拼接提示词、生成回答。整体并不复杂但每一段都有讲究。采集和清洗解决的是“哪些内容能进知识库”。我本地有一个knowledge目录里面是 Obsidian 管理的 Markdown 文档、项目源码的说明文档、少量接口规范文本。采集脚本会按扩展名遍历目录过滤掉.git、node_modules、__pycache__这些中间产物只保留.md、.py、.js、.txt这几类文本文件。切块解决的是“按什么粒度理解内容”。原始文档和代码文件可能几千行直接整篇向量化的话检索时语义太模糊而且大模型上下文也塞不下。我把文档按标题层级切开把代码按函数和类切开每块限制在几百 token 左右块与块之间保留少量重叠避免切断语义。向量化解决的是“怎么让计算机理解语义相似”。我会把每一块文本通过嵌入模型转成一个高维向量。到这一步之后知识库就变成了一个向量集合查询时把用户问题也转成向量然后通过相似度计算把最相关的几块文本捞出来。最后一段是生成。把检索到的文本块拼进提示词交给本地部署的大模型生成最终回答同时要求在回答中给出对应来源。这样一轮下来答案就不是凭空产生的而是有了本地知识库作为支撑。2.2 为什么不做微调为什么坚持本地化有人可能会问为什么不直接拿私有代码微调一个大模型我理解这个想法的冲动但实际做下来微调在知识库场景里性价比很低原因有三点。第一是更新成本。代码和文档一周变好几次每变一次就微调一次模型不管算力还是时间都扛不住。RAG 方案里知识库更新就是重新跑一遍索引几分钟的事情谁都能操作。第二是可追溯性。微调后的模型是一个“记忆混合物”你问它一个具体代码逻辑它可能记得也可能不记得就算回答对了也没有办法给出“我这是根据哪个文件得出来的结论”。但 RAG 的检索结果可以打印出来每个答案都能追到文档片段和代码文件这种可验证性对开发类场景极其重要。第三是冷启动成本。微调一套 7B 模型光是准备数据集就要很久更别提训练环境。而 RAG 只需要一台能跑嵌入模型的机器再加一个能聊天的生成模型就足够起步了。那为什么又必须强调“本地”最现实的原因是私有代码不能随便往外发。很多项目的代码库、接口文档、内部 Wiki 都有保密要求走外部服务意味着核心资产离开内网。放在本地部署数据和索引全部留在自己的机器上访问路径可控、访问记录可审计这才能让人放心用。另外本地化还有一个很实际的好处没有额外费用不按 token 计费。我自己迭代知识库内容的时候一天可能跑几十次索引重建和上百条查询如果用外部 API 的话成本很敏感本地部署随便折腾不心疼。生成的模型用本地量化模型嵌入模型用开源向量模型整条链路完全开源这也是我坚持本地化的原因。3. 从零搭建一套本地知识助手3.1 第一步把知识库底座收拾齐整我开始动手前先干了一件事把散乱的文档统一迁到 Obsidian 管理的 Markdown 知识库里。为什么选 Obsidian主要是三方面考虑。一是它默认就是纯文本 Markdown 文件批量处理没有任何格式障碍二是支持双链语法[[...]]我可以在 Wiki 里把相关页面串起来三是它配合 Git 可以做版本管理知识库的变更历史一目了然。知识库目录结构我建议先按“项目/主题”分两层不用一开始就设计得很复杂。比如我本地长这样knowledge/ ├── quant-trading/ │ ├── kline-preprocessing.md │ ├── strategy-signal.md │ └── risk-control.md ├── web-service/ │ ├── auth-flow.md │ ├── api-contract.md └── notes/ ├── rag-experience.md └── embedding-models.md这里有个细节值得注意文档文件名尽量用英文短横线连接内容里加 frontmatter 元信息包括tags、updated、related-code。后面构建索引的时候这些元信息会变成检索结果里的“上下文标签”方便判断一段内容属于哪个主题、最后更新时间是什么时候、关联哪个代码模块。还有一个容易被忽略的点如果你团队用的是在线 Wiki 系统比如飞书文档或者 Confluence导出成 Markdown 之后往往带有大量层级结构、表格和注释清洗时特别容易粘连正文。我的建议是导出后先跑一遍“正文归一化”脚本把[toc]之类的模板标记删掉统一缩进和列表格式否则切块之后会出现大量“空壳块”检索质量会很难看。知识库整理完之后再决定哪些代码目录要纳入范围。我不建议一上来就把整个代码仓库丢进去因为源码文件太多切块之后向量库会非常臃肿检索时还会被大量无关文件干扰。我建议只纳入“源码里和接口、模块边界、业务流程设计相关的部分”比如服务入口、领域模型、核心工具函数主要测试文件和编译产物排除掉。3.2 第二步采集、切块与向量化这个环节是整个知识助手的技术核心也是最容易出现隐性坑的地方。采集脚本本身不复杂我用 Python 遍历知识库路径按扩展名过滤文件读取的时候统一指定encodingutf-8避免中文乱码。对于少量编码不标准的文件用errorsignore兜底宁可丢弃几个字符也不能让整个脚本挂在乱码上。切块策略值得多说两句这是检索效果好坏的分水岭。文档我按 Markdown 标题层级来切一般以##和###为切分点这样切出来的每一块天然就是一个主题段落有明确标题语义集中。代码则按函数和类来切用简单的正则匹配def、class行作为切分边界。至于块的大小我踩过几轮后定下来一个经验区间Markdown 文档每块控制在 500 token 左右代码块控制在 200-300 token。token 数有个粗估方法英文大概一个词算一个 token中文一个字算一到两个 token500 token 大约对应几百字的中文段落。块太大了向量表示会被无关内容冲淡块太小又会切断函数内部的完整逻辑导致上下文不连贯。块与块之间我设置了 10%-15% 的重叠也就是 50-80 token。这个重叠很重要因为很多关键技术信息恰好出现在段落交界处如果两边都不保留检索时就会漏掉。别小看这个细节我刚开始没加 overlap结果“如何初始化连接池”这类问题总是检索不到答案因为答案正好横跨两个块。向量化模型的选择直接影响匹配精度。我做过几组对比中文场景下效果排序大概是BAAI/bge-m3moka-ai/m3e-base 通用英文 embedding 模型。bge-m3 对中文的长文本语义理解明显更好维度也足够表达复杂关系。如果只是做英文技术文档sentence-transformers/all-MiniLM-L6-v2就够用性能开销也小。向量库我用的是 Chroma原因是部署简单数据持久化到本地一个文件夹重启不丢查询接口也足够支撑个人使用。如果数据量小也可以用 FAISS但 FAISS 没有内置的持久化和元数据过滤起步阶段我更推荐 Chroma。我把核心的索引构建脚本贴在这里细节我已经实际跑过可以直接参考。注意根据自己的目录结构微调。import os from pathlib import Path from langchain.text_splitter import ( MarkdownHeaderTextSplitter, RecursiveCharacterTextSplitter, ) from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import Chroma KNOWLEDGE_DIR Path(./knowledge) DB_DIR ./chroma_db MODEL_NAME BAAI/bge-m3 def collect_files(root: Path): 收集 Markdown、Python、JS、TXT 文件过滤掉中间产物。 candidates [] for ext in (*.md, *.py, *.js, *.txt): candidates.extend(root.rglob(ext)) return [ p for p in candidates if .git not in str(p) and node_modules not in str(p) and __pycache__ not in str(p) ] def split_markdown(text: str): splitter MarkdownHeaderTextSplitter( headers_to_split_on[(##, h2), (###, h3)] ) return splitter.split_text(text) def split_code(text: str): splitter RecursiveCharacterTextSplitter( chunk_size300, chunk_overlap50, separators[\ndef , \nclass , \n\n, \n, , ], ) return splitter.split_text(text) def build_index(): docs [] for path in collect_files(KNOWLEDGE_DIR): text path.read_text(encodingutf-8, errorsignore) if path.suffix .md: chunks split_markdown(text) # MarkdownHeaderTextSplitter 返回 Document 对象带 heading 元数据 for chunk in chunks: chunk.metadata[source] str(path) docs.append(chunk) else: chunks split_code(text) for chunk in chunks: docs.append({source: str(path), text: chunk}) print(f{path.name}: {len(chunks)} chunks) if not docs: print(没有采集到任何文件请检查目录路径。) return embeddings HuggingFaceEmbeddings(model_nameMODEL_NAME) # 代码类的 chunk 结构不一样简单处理时统一取 text texts [d.page_content if hasattr(d, page_content) else d[text] for d in docs] metadata [d.metadata if hasattr(d, metadata) else d for d in docs] db Chroma.from_texts( textstexts, embeddingembeddings, metadatasmetadata, persist_directoryDB_DIR, ) db.persist() print(f索引构建完成共 {len(texts)} 个文本块。) if __name__ __main__: build_index()这段脚本我建议放在知识库根目录的外面避免索引脚本本身也被当成知识文件。运行时如果是第一次下载bge-m3模型需要联网拉取模型权重之后会在本地缓存后续构建索引不会再消耗网络带宽。这一步完成后知识库向量索引就生成了查询阶段可以直接复用。3.3 第三步接入模型跑通问答闭环索引建好之后下一步就是把检索和生成串起来。生成模型我用的是 Ollama 本地跑的qwen2.5:14b14B 的参数规模在普通开发机上能跑起来效果也比 7B 明显好一截。如果机器只有 16G 内存可以先从 7B 量化版本起步体验链路再逐步升级。Ollama 的安装很简单装完之后拉取模型就能用了。注意第一次拉模型会花费一些时间因为需要把完整的模型文件下载到本地之后所有推理都在本地完成不依赖外部服务也不会产生按 token 计费的问题。我平常用下面这个命令拉取模型# 拉取默认的 qwen2.5 14B 版本 ollama pull qwen2.5:14b # 查看本机已有的模型 ollama list查询端的核心逻辑也不复杂先用相同的 embedding 模型把用户问题转成向量在 Chroma 里做相似度检索取出最相关的 5-8 个知识块然后把这些知识块和问题拼成一个提示词扔给 Ollama 生成回答。这里有个特别重要的细节检索出的片段不能全部塞进去因为生成模型的上下文窗口有限而且无关片段越多模型越容易跑偏。我一般先取 top 20 个候选块再用一个重排器粗略过滤一遍最终保留 5 个最相关的。重排我用的是bge-reranker-base它对中文支持不错而且重排的计算量比向量检索大所以只处理候选集数量不大可以接受。下面是我一直在用的查询脚本核心部分纯 Python 实现依赖requests和刚才建好的 Chroma 索引。import requests from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import Chroma DB_DIR ./chroma_db EMBED_MODEL BAAI/bge-m3 OLLAMA_URL http://localhost:11434/api/chat OLLAMA_MODEL qwen2.5:14b def retrieve(question: str, k8): embeddings HuggingFaceEmbeddings(model_nameEMBED_MODEL) db Chroma(persist_directoryDB_DIR, embedding_functionembeddings) results db.similarity_search_with_relevance_scores(question, kk) return results def ask(question: str): results retrieve(question) context_blocks [] for doc, score in results: if score 0.3: continue source doc.metadata.get(source, 未知来源) context_blocks.append(f[来源: {source}]\n{doc.page_content}) context \n\n.join(context_blocks) prompt ( 你是一个本地知识助手。回答问题时必须优先基于下面检索到的资料。 如果资料中没有答案请直接说明知识库中未找到不要编造。\n\n f检索资料:\n{context}\n\n f问题: {question}\n\n 请给出基于资料的回答并在回答后列出引用的来源。 ) resp requests.post( OLLAMA_URL, json{ model: OLLAMA_MODEL, messages: [{role: user, content: prompt}], stream: False, }, timeout120, ) return resp.json()[message][content]阈值0.3是我根据向量模型实际输出调的不同模型下分数含义不同建议先打印几组分数观察分布再定。如果检索出来的片段分数普遍很低说明知识库里确实没有相关内容这时候与其强行生成不如明确提示“未找到”。整个链路跑通后我会在终端里用一个简单脚本接收问题直接跑ask(登录接口的鉴权流程怎么实现)就能看到回答和来源。我实际用下来的体验是对于“某个模块的职责是什么”“这段代码为什么会这么写”这类问题回答质量已经能到“能直接用来定位问题”的程度。4. 实操中的常见问题与排查实录4.1 检索结果不准该怎么调这是我最常被问到的问题也是我自己踩得最深的一个坑。检索不准时第一步不是换模型而是先搞清楚问题出在召回端还是生成端。我的排查方法是先不看生成结果单独打印检索出来的文本块看看这些块和问题是不是真的相关。如果检索出来的块本身就不相关那就是召回的问题如果检索到的块相关但生成答案跑偏那就是生成提示词的问题。这两种问题的解决路径完全不同混在一起调试只会浪费时间。召回端的问题优先检查三件事。第一是切块粒度块太大会稀释语义块太小会丢失逻辑上下文这是绝大多数“检索不准”的根源回到 3.2 节把 chunk size 调一遍往往比换模型更有效。第二是 embedding 模型对专业词汇的适应度比如代码里的英文标识符混着中文描述时通用的中文 embedding 模型可能识别不好这时候可以试试在文本块里保留原始代码标识符或者在bge-m3和m3e-base之间切换对比。第三是 top_k 大小k 太小会漏掉正确答案k 太大会混入噪声我个人常用检索 20 个、重排后取 5 个。如果问题出在生成端更常见的原因是提示词里没有明确约束“必须基于检索内容回答”。模型在无约束情况下会自作聪明地动用内部知识反而把正确答案冲淡了。我现在的提示词固定加了一句“如果资料中没有答案请直接说明知识库中未找到”效果立竿见影幻觉明显减少。4.2 中文内容被截断或乱码中文是知识库场景里绕不开的坎尤其是原本来自飞书文档、Confluence 导出的内容。乱码通常发生在文件读取阶段也就是没有统一指定utf-8编码。但还有一个更隐蔽的问题切分器对中文的分句能力不足。英文有空格作为天然分词边界RecursiveCharacterTextSplitter能利用空格切出比较合理的块。但中文没有空格如果分隔符列表里不加入中文句读切出来的块可能是“一句话被从中间劈开”既破坏语义又影响向量表达。我的解决办法是在分隔符列表里加上中文标点separators[ \n## , \n### , \ndef , \nclass , \n\n, 。, , , \n, , ]这么改完之后中文段落的切分边界按照句子级别断开语义完整性会好很多。另外还有一个细节某些 Wiki 导出文本里带有全角空格、不间断空格\u00a0这类字符肉眼看不见但会影响后续向量化效果建议在清洗阶段统一替换成普通空格。4.3 本地模型生成太慢怎么办本地模型的速度和硬件强相关尤其是生成环节14B 模型在没有独立显卡的机器上可能每秒钟只能产出几个 token体验比较差。我的做法是分清场景检索和重排永远用最轻量的模型只有最终生成才用大模型而且如果只是想让搜索更高效很多情况下连生成都不需要直接看检索结果就够了。如果确实需要生成但速度太慢有三个方向可以优化。第一是换更小参数的量化版本比如从 14B 换成 7B 或 4B 量化版回答质量会略降但速度收益很大。第二是裁剪上下文限制检索片段的数量减少输入 token生成速度会明显提升因为输入阶段的计算量也被算进去了。第三是提前预判如果某些问题是高频的、可枚举的比如“某接口的请求参数是什么”我宁可直接做一个结构化查询而不是每次都走大模型生成这样响应速度能压到毫秒级。内存方面也要留意14B 量化模型加载后大约占 8-10G 内存加上向量索引和 embedding 模型整机 16G 内存会显得紧张。如果发现系统卡顿优先把供养度低的模型替换成更小版本或者把 Chroma 索引放到更快的磁盘上。4.4 Wiki 与代码的版本同步问题这个问题的源头不在技术而在工作流。知识助手能索引的是“过去某个时刻的快照”如果代码改了但 Wiki 没更新索引再准也是引用旧知识。这也解释了为什么我前面强调用 Git 管理知识库把文档和代码放进同一个版本体系后至少可以做到“谁知道改了什么、谁该为同步负责”。我的做法是给每篇 Wiki 文档的 frontmatter 强制加上updated字段并在代码合并请求的模板里增加一项“是否涉及文档更新”如果涉及开发者就必须把相关 Wiki 页面一起改掉。这一步相当于把“文档同步”从自觉行为变成流程约束比任何技术手段都可靠。索引重建的频率建议和代码提交频率挂钩。我本地用脚本监听 Git 提交事件每次提交后自动对变更的目录重新跑一遍索引构建只更新增量部分几秒钟就完成。如果团队里不想跑脚本也可以定一个“每天凌晨全量重建”的 cron 任务代价是几分钟的构建时间换来知识库的即时性。这里的关键是别让索引更新变成一个“想起来才做”的事否则知识助手很快又会退化成又一份没人看的旧文档。5. 从「能用」到「好用」的几条进阶经验5.1 知识卡片化让索引更可靠一开始我的知识库是整篇整篇的长文档检索效果只能说勉强可用。后来我把核心知识点拆成“知识卡片”每张卡片解决一个问题卡片头部带元信息卡片内容用标准结构组织效果立刻不一样了。知识卡片的范式大概长这样--- title: K线预处理标准流程 tags: [quant, kline, preprocessing] updated: 2024-06-15 related-code: data_processor.py, indicator_engine.py --- # K线预处理标准流程 ## 问题背景 原始行情数据包含停牌、除权、复权导致的跳变直接用于策略回测会产生偏差。 ## 处理流程 1. 按合约代码分组 2. 检查复权因子优先使用前复权 3. 过滤成交量异常为零的K线 4. 写入预处理结果缓存 ## 关键代码索引 - 复权逻辑见 data_processor.py 中的 adjust_factor() 函数 - 过滤器见 indicator_engine.py 中的 filter_zero_volume()这种结构对 RAG 特别友好。原因很简单切块时每一张卡片都是一个完整的语义单元检索出来就是“自解释”的一段内容related-code字段让知识助手在后台建立“文档-代码”关联索引回答时可以顺便指明去看哪个文件的哪个函数。写卡片很麻烦但我发现日常维护成本没有想象中高。我一般是在写代码的时候顺手写卡片一个功能模块写完对应卡片也出来了比事后补文档轻松得多。而且卡片粒度下文档更新范围也变小了不会出现“改一句话要重读三页”的心理负担。5.2 给代码建一份引用档案知识助手可以自动建立代码的引用档案让文档和代码的对应关系可视化、可跳转。工作原理不复杂在文档的related-code里写入相对路径索引脚本解析后形成一张“文档 → 源码文件”的映射表。查询时如果检索到文档块就顺带把对应的源码路径展示出来如果检索到代码块就反向把代码所在模块对应的 Wiki 链接展示出来。我在本地还把这份映射导出成一个 HTML 页面点一下文档名就能跳转到源码文件位置点一下代码文件就能看到相关文档。从 Obsidian 侧则启用一个链接插件在 Markdown 预览视图里渲染出可点击的代码引用。效果就是看 Wiki 时能看到代码位置看代码时能想起相关 Wiki知识助手在中间充当索引中枢。这个能力听起来有点像 IDE 的“查找引用”但它的价值不只是跳转而是把“业务概念”和“实现符号”这两套命名体系联系起来。比如业务文档里写“熔断降级”而代码里是CircuitBreaker检索时只要能命中其中一个就能顺手把另一个引出来这才是真正意义上的知识融合。5.3 混合检索与反馈闭环向量检索擅长语义相近的问题但代码场景里有一个天然优势是关键词精确匹配搜def train()这种精确符号时关键词检索往往比向量检索更直接。所以进阶阶段我引入了混合检索把 BM25 精确匹配的分数和向量相似度分数做加权融合显著提升了含代码符号的查询命中率。混合检索不需要复杂实现我直接用rank_bm25库对同一批候选文本块跑一遍关键词打分然后按“0.7 向量分 0.3 关键词分”加权排序。调权重时有几条经验如果团队大量用中文描述问题向量分权重要高如果经常搜英文函数名、类名、错误信息关键词分权重要高。反馈闭环是一个可以让知识助手越用越准的机制。我在查询脚本里加了一个记录函数每次查询后把问题和最终采用的文档块存到本地 sqlite 数据库。每周末我会跑一个小脚本统计哪些文档块被频繁引用哪些文档块从没被检索到后者我会检查是不是内容过期了。这样做还有一个额外收获可以观察团队真正问得最多的问题集中在哪个模块反向驱动文档补全。说到底知识助手不是我写完索引和提示词就撒手不管的东西它需要持续喂养和调优。但正因为有了这一步Wiki 和代码才真正从“两套系统”变成了“一个可以对话的知识体”这也是我搭完这套东西之后最有成就感的地方。
返回列表