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

资讯详情

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

用RAG打造私有知识库:LLM Wiki实战指南

用RAG打造私有知识库:LLM Wiki实战指南 要说为什么做 llm_wiki其实是因为我自己的资料库已经乱到忍无可忍了。Markdown 文件散落在各个目录印象笔记里的内容多年没整理浏览器书签收藏了上百篇“以后再看”的文章真到用的时候一个都搜不出来。传统 wiki 的检索是关键词匹配你记得原文里有个什么词才能搜到但很多时候你只记得“那篇文章大概讲过模型量化的事情好像是关于显存优化的”这种模糊需求传统搜索毫无办法。后来我把大语言模型LLM和 wiki 体系结合起来给自己的知识库配了一个会用自然语言对话的 AI 助手——这就是 llm_wiki 项目的由来。简单说llm_wiki 干的事情就是你像聊天一样问它“我为啥量化后推理变慢了”它会在你自己的知识库里找到真正相关的内容然后整理成一段有依据的回答并且告诉你结论是从哪些文档里来的。这套东西既能跑在本地也能用云端大模型 API适合 AI 工程学习者、知识管理重度用户或者想给团队搭一个私有知识问答系统的人。这篇内容我会把从技术选型、原理拆解到完整代码实现和常见坑位全部摊开讲照着做你也能拥有一个属于自己的 LLM 知识库。1. 项目解读与整体架构设计1.1 llm_wiki 到底是什么从字面上拆llm_wiki 就是“大语言模型 Wiki”的组合体。但这里有个需要澄清的点它并不是让你做一个像维基百科那样的在线百科网站而是“把你的私有知识库变成能聊天、能问答、能溯源的系统”。Wiki 在这里代表的是一种结构化、可长期维护的知识沉淀方式LLM 则充当那个“理解你问题、读懂你文档、组织答案”的智能层。我见过不少团队做过类似的内部知识库最常见的失败模式是搞了个很重的平台录入内容要靠专人维护最后大家还是习惯去问聊天软件。llm_wiki 的定位正好相反——底层知识依然是你熟悉的 Markdown 文件文件就是 wiki 页面你完全可以用任何编辑器维护内容上层用 RAGRetrieval-Augmented Generation检索增强生成技术把大模型接进来。维护成本和体验收益解耦内容该怎么样还怎么样只是查询方式彻底变了。整个项目跑起来之后你得到的不只是一个“搜索引擎”而是一个真正读过你所有笔记的助手。你问它“上次那个线上事故最后怎么解决的”它不只是给你甩一堆链接而是把事故原因、处理过程、最终结论糅合成一段人话然后告诉你这些结论出自哪些笔记。这个体验传统 wiki 给不了。1.2 核心需求池与技术选型思路在动手写代码之前我先把需求列了一个清单所有技术选型都是围绕这些需求来的数据必须是本地优先文件格式用 Markdown方便 Git 版本管理也方便自己随时改检索要能处理“语义相似”而不是单纯关键词匹配这决定了必须引入 Embedding 向量化生成答案要支持本地推理和云端 API 两种模式方便在不同机器上切换项目本身要轻不能一上来就引入一堆中间件个人电脑也能跑得动查询结果必须带引用来源不然 AI 一本正经地胡说八道会害死人这几条需求落下来技术栈基本就清晰了。Embedding 模型我用的是 BGE-M3 系列里面对中文支持很好向量存储早期试过 Chroma后来数据量上来之后换成了 Milvus推理侧本地用 Ollama 跑 Qwen 系列模型云端留了 OpenAI 兼容接口。整个编排代码没上 LangChain 这类框架直接用 Python 手写代码量不大反而更容易排查问题。我强烈建议读者第一版也用手写方式。LangChain 抽象层级太多出了问题你根本分不清是 Embedding 的问题、向量库的问题还是 Chain 内部的问题。手写 200 行代码每个环节你都知道在干什么。1.3 方案对比为什么没选传统搜索引擎或现成知识库做一个知识库问答系统其实有几条路。第一条是直接用 Elasticsearch 的关键词搜索配合 ik 分词器做中文支持。这条路实现简单但问题很明显——搜索“怎么优化显存”搜不到你笔记里“降低显存占用”的内容因为词汇对不上。这也是传统 wiki 最大的痛点。第二条是直接用大模型做长文本对话把所有文档一股脑塞进上下文窗口。这条路对 token 消耗太严重而且 GPT-4 级别的上下文窗口也就几十万字资料稍微多点就放不下更别提很多开源本地模型窗口更小。更关键的是大模型本身会“编造”内容没有了检索过程的约束回答的准确性完全不可控。第三条就是 RAG也就是现在的 llm_wiki 方案。先通过语义检索召回最相关的文档片段再把片段作为参考材料拼到提示词里让模型总结回答。这样模型只需在给定的片段里理解归纳不需要凭空生成专业知识答案基本都有依据。而且向量检索快十万篇文档也能在几百毫秒内完成召回。权衡之后RAG 是唯一能在“资料量、回答质量、资源消耗、可追溯性”四个维度上同时兼顾的方案。2. 核心技术链路从文档到问答的完整原理2.1 RAG 为什么能解决“大模型不懂我的文档”很多人第一次接触大模型知识库都会有个疑问为什么不直接拿我所有的笔记去微调一个模型答案是没必要也不划算。微调的本质是改变模型的行为模式和知识边界成本高、周期长而且每次文档更新都要重新训练。我们大部分私有知识是事实型信息不需要通过调整模型权重来记住只需要在“提问时”把相关内容临时提供给它即可——这就像考试开卷你只需要知道去书的哪一页找答案就行。RAG 的全流程可以拆成两条线。离线索引线把 wiki 里的 Markdown 文档切分成若干片段每个片段用 Embedding 模型转成向量再把向量和原文一起存入向量数据库。在线问答线提问进来之后同样用 Embedding 模型把问题转成向量去向量库里做相似度检索取回最相关的 Top-K 片段最后把这些片段和用户问题一起组织成 Prompt 交给大模型让它基于这些片段生成最后答案。两条线的核心都是 Embedding——这种技术能把文本映射成一个几百几千维的浮点数组语义相近的文本在向量空间里距离也越近。比如“如何降低显存占用”和“显存优化方案”这两句话字面完全不同但向量距离很近所以语义检索就能把它们关联起来。2.2 文档加载与切片最容易被低估的环节整个 RAG 链路里我认为切片策略对最终问答质量的影响能占到 60% 以上但很多教程都一带而过。切片太大了比如直接把整个 wiki 页面作为一片向量化之后语义被稀释检索时精度会下降切片太小了比如一句话一片又容易丢失上下文信息模型看了一段孤立的话根本不知道在说什么。我最后采用的方案是“层级切片”优先按 Markdown 的标题层级切一级标题下的内容是一个大块大块里再按段落和字符数联合切分。具体参数是主块最大 800 字符重叠 100 字符。重叠这个操作很重要它可以避免一句话被从中间截断导致语义不完整。另外我记录了每个块对应的标题路径这样在后面生成引用来源时可以直接显示出“来自哪篇文章的哪个章节”。这里要补充一个我踩过的坑代码块和表格如果被拦腰切断检索回来就是一堆垃圾。因此切片器要聪明一点尽量让代码块和表格整体保留在同一个块里宁可块长一些也不要切断。这个逻辑在实现时其实就是判断当前字符是不是在代码围栏内情况允许就延迟切分点。2.3 向量化与相似度计算数学在背后做了什么Embedding 模型会把文本转换成一个固定维度的向量。我的选择是 BGE-M3输出 1024 维向量支持中文效果也不错。向量化之后检索的核心操作就是计算查询向量和库中所有向量的相似度常用的是余弦相似度两个向量夹角越接近 0相似度越高也就是语义越接近。实际实现时当然不会真的遍历所有向量做全量计算那样十万篇文档每秒只能查几次。向量数据库会建立 ANN近似最近邻索引常见的有 HNSW、IVF 等。HNSW 是一种多层图结构查询时先在图的上层粗粒度找方向再往下层精确定位速度比暴力计算快几个数量级。代价是索引构建需要时间和内存但对个人知识库这种量级来说完全不是问题。我自己实测的数据是把 3000 多个文本块写入 Milvus构建 HNSW 索引耗时不到 3 秒查询耗时一般在 30 毫秒到 80 毫秒之间。这个量级的延迟对问答体验来说完全感知不到。不过如果你的数据量到了百万级那就要认真调 HNSW 的 efConstruction 和 M 参数了这些后续在图索引那节再细讲。2.4 提示词组织与答案溯源让模型学会“只说自己知道的”检索拿到了相关片段最后一步是生成。这一步的关键是提示词设计。我的系统提示词严格规定你只能依据检索到的资料片段回答问题如果资料中没有相关信息就明确说“知识库中没有相关内容”禁止编造。同时要求回答以“根据知识库中的资料”作为开头并且每次回答末尾按引用的文档标题列出参考来源。这里有一个细节多个片段拼接进 Prompt 时需要告诉模型哪段来自哪个文档。我通常在每段前面加一个类似[来源: filename.md#section]的元信息行。这样模型在回答时更容易把内容和来源对应起来生成出来的引用也更准确。如果你只是把一堆文本拼接起来模型可能会乱配来源——它分不清这段话到底是哪篇文章里的。上下文窗口也要提前估算。如果每篇笔记切出来 800 字符的块Top-K 设置 6 那就大约是 4800 字符的参考内容加上问题本身和系统提示词总输入大概在 1500 token 左右。本地 7B 模型的窗口普遍有 8K 到 32K完全能覆盖云端模型更不用说。但要注意参考内容越多模型的回答质量不一定越好——太多不相关内容反而会干扰判断。所以 Top-K 一般在 4 到 8 之间比较合适。3. 手把手实现 llm_wiki完整实操流程3.1 环境准备与依赖安装我默认大家用的是 Linux 或 macOS 环境Windows 的话建议先装 WSL2因为有些 Python 的向量计算库在原生 Windows 上编译会遇到各种问题。首先是创建虚拟环境Python 版本要求 3.10 以上mkdir llm_wiki cd llm_wiki python3 -m venv .venv source .venv/bin/activate接下来安装核心依赖。我先把所以依赖列出来说明一下各自作用pip install pymilvus2.4.9 pip install sentence-transformers3.0.1 pip install openai1.40.0 pip install markdown3.7 pip install python-frontmatter1.1.0 pip install gradio4.44.1 pip install tiktoken0.7.0pymilvus 是向量数据库客户端sentence-transformers 用来加载 Embedding 模型openai 库负责调大模型接口markdown 和 python-frontmatter 用于解析 wiki 文档gradio 用来快速做 Web 界面tiktoken 则是为了估算 token 数量防止超限。另外如果你打算用 Ollama 跑本地模型记得先去官网装好 Ollama 运行时再拉一个推理模型比如ollama pull qwen2.5:7b。3.2 准备种子数据Wiki 目录结构与 Markdown 规范llm_wiki 的知识来源是某个固定目录下的 Markdown 文件。我建议目录结构设计成下面这样wiki_data/ ├── 01-大模型基础/ │ ├── transformer结构.md │ └── 注意力机制.md ├── 02-工程实践/ │ ├── 显存优化指南.md │ ├── 推理加速方案.md └── 03-团队规范/ └── 代码评审流程.md目录按数字编号是为了排序稳定。每个 Markdown 文件头部还可以加 YAML front-matter用来标注作者、标签、创建时间等元信息后面构建索引时可以直接读取。比如工程实践那篇文章开头可能是--- title: 显存优化指南 tags: [推理, 显存, 性能调优] created: 2025-01-10 --- # 显存优化指南 ## 背景 在 7B 模型推理过程中显存占用过高会导致 OOM...这种规范不会影响普通阅读习惯但能让索引阶段获得更多结构化信息。如果你的 wiki 里已经有海量文件没有 front-matter也不用补解析器里做容错就行缺失元数据不影响主流程。3.3 构建索引从 Markdown 文件到向量库索引构建是整个项目的地基我把核心代码拆成几个函数。首先是文档加载与解析import os from pathlib import Path import frontmatter from markdown import markdown from bs4 import BeautifulSoup def load_markdown_files(root_dir: str) - list[dict]: docs [] for path in Path(root_dir).rglob(*.md): with open(path, encodingutf-8) as f: post frontmatter.load(f) # 转 HTML 后去标签方便做文本清理同时保留原始 Markdown 文本用于展示 html markdown(post.content, extensions[fenced_code, tables]) soup BeautifulSoup(html, html.parser) text soup.get_text(separator\n) docs.append({ title: post.get(title, path.stem), path: str(path), content: text, raw: post.content, tags: post.get(tags, []), }) return docs这个函数的目的是把目录下所有 Markdown 文件统一解析成字典。用 frontmatter 库读取头部元数据用 markdown 库把正文转成 HTML 再去标签是为了让后续切片时拿到干净的文本。注意我这里同时保留了原始 Markdownraw字段后面检索显示时我们可以选择展示原文或者纯文本。切片部分我用了递归按标题切分的策略import re def split_by_headings(text: str, max_chunk_size: int 800, overlap: int 100): lines text.split(\n) chunks [] current_chunk {heading: , content: []} current_chars 0 for line in lines: heading_match re.match(r^(#{1,4})\s(.)$, line) if heading_match: if current_chunk[content] and current_chars 0: chunks.append({ heading: current_chunk[heading], content: \n.join(current_chunk[content]), char_count: current_chars, }) current_chunk { heading: heading_match.group(2), content: [line], } current_chars len(line) else: current_chunk[content].append(line) current_chars len(line) 1 # 超过阈值时检查当前块是否在代码块外安全则切分 if current_chars max_chunk_size and not inside_code_block(current_chunk[content]): chunks.append({ heading: current_chunk[heading], content: \n.join(current_chunk[content]), char_count: current_chars, }) current_chunk {heading: current_chunk[heading], content: []} current_chars 0 if current_chunk[content]: chunks.append({ heading: current_chunk[heading], content: \n.join(current_chunk[content]), char_count: current_chars, }) # 对超长块做二次硬切分 final_chunks [] for chunk in chunks: if chunk[char_count] max_chunk_size * 1.5: final_chunks.extend(hard_split(chunk, max_chunk_size, overlap)) else: final_chunks.append(chunk) return final_chunks这里有个 inside_code_block 函数要简单说明实际上就是遍历已有行数统计代码围栏 的奇偶数量如果奇数说明目前在代码块内延迟切分。这个细节直接决定代码类碎片能不能被完整切出值得多写几行。最后的 hard_split 是兜底逻辑负责处理一段没有标题的长文直接按字符数硬切并带上重叠。向量化和写入向量库的代码相对简单from sentence_transformers import SentenceTransformer from pymilvus import MilvusClient, DataType model SentenceTransformer(BAAI/bge-m3) client MilvusClient(llm_wiki_milvus.db) if not client.has_collection(wiki_chunks): client.create_collection( collection_namewiki_chunks, dimension1024, primary_field_nameid, vector_field_namevector, metric_typeCOSINE, ) docs load_markdown_files(wiki_data) records [] for idx, doc in enumerate(docs): chunks split_by_headings(doc[content]) for cidx, chunk in enumerate(chunks): chunk_text f{doc[title]}\n{chunk[heading]}\n{chunk[content]} vector model.encode(chunk_text).tolist() records.append({ id: len(records), title: doc[title], path: doc[path], heading: chunk[heading], content: chunk[content], vector: vector, }) client.insert(collection_namewiki_chunks, datarecords) print(fInserted {len(records)} chunks)这里一个细节值得注意我把文档标题、章节标题、正文内容拼接在一起喂给 Embedding 模型而不是只编码正文。因为标题往往是内容的强语义概括带着标题向量化可以让相似的主题聚得更近搜索“显存优化”时更容易命中那篇文档。这是一个不花一分钱就能提升检索精度的技巧。3.4 检索问答查询接口与 Prompt 设计索引建好了接下来做查询接口。第一步是把用户的问题向量化去向量库查相似块然后把命中的块组装成 Promptfrom openai import OpenAI def search_wiki(query: str, top_k: int 6) - list[dict]: q_vector model.encode(query).tolist() results client.search( collection_namewiki_chunks, data[q_vector], limittop_k, output_fields[title, heading, content, path], ) hits [] for hit in results[0]: entity hit[entity] hits.append({ title: entity[title], heading: entity[heading], content: entity[content], path: entity[path], score: hit[distance], }) return hits def build_prompt(query: str, hits: list[dict]) - str: context for i, hit in enumerate(hits): context f[来源{i1}: {hit[title]} - {hit[heading]}]\n{hit[content]}\n\n return f你是一个基于个人知识库的问答助手。请根据下面提供的参考内容回答用户的问题。 要求 1. 只能依据参考内容回答禁止使用外部知识或编造内容。 2. 如果参考内容中没有相关信息请明确回答“知识库中未找到相关内容”。 3. 回答时在句子末尾或段落后的括号中注明来源编号例如[来源1]。 4. 使用简洁、专业的中文回答。 参考内容 {context} 用户问题{query} 我为了兼容不同的大模型后端OpenAI 客户端的 base_url 和 api_key 是从环境变量里读的。既然要用 Ollama 本地接口就设置export OPENAI_BASE_URLhttp://localhost:11434/v1 export OPENAI_API_KEYollama然后在代码里client OpenAI()就能直接调用本地模型。这套做法非常实用因为代码完全不用改换云端模型的时候只需要改环境变量和模型名。完整问答函数长这样def ask_llm_wiki(query: str, top_k: int 6) - tuple[str, list[dict]]: hits search_wiki(query, top_ktop_k) if not hits: return 知识库中未找到相关内容。, [] prompt build_prompt(query, hits) response client.chat.completions.create( modelqwen2.5:7b, messages[{role: user, content: prompt}], temperature0.3, max_tokens1024, ) answer response.choices[0].message.content return answer, hits温度设置成 0.3 是有讲究的。问答场景需要确定性输出温度太高模型容易自由发挥编造内容温度太低回答会比较死板。0.3 是我反复测试后觉得比较合适的点既能正常组织语言也不会天马行空。另外 max_tokens 给到 1024个人知识库的问题一般不需要更长的答案太长反而显得啰嗦。3.5 快速做一个可交互的 Web 界面命令行体验终究差一点所以我用 Gradio 套了一个极简界面让整个东西看起来像个正经产品import gradio as gr def chat_ui(message, history): answer, hits ask_llm_wiki(message) refs \n.join([f- [{h[title]}]({h[path]}) for h in hits]) return f{answer}\n\n**参考来源**\n{refs} gr.ChatInterface( fnchat_ui, titlellm_wiki - 个人知识库问答, description基于 RAG 的个人 Wiki 智能问答系统, ).launch(server_name0.0.0.0, server_port7860)启动之后浏览器打开http://localhost:7860就能像用 ChatGPT 一样跟自己的知识库聊天了。这里我把参考来源以 Markdown 链接的形式展示在答案下方用户点过去就能直接看到 wiki 原文这比单纯给一个编号更有用。如果团队内部使用可以把 server_name 改成内网 IP让同事也能访问甚至加一层简单的认证。3.6 效果测试一次完整的问答之旅为了验证系统的真实效果我放了一个测试问题进去“如何降低推理显存占用”。检索阶段返回了三个高相关的片段分别是显存优化指南里的“背景”章节、工程实践中“量化方案”章节以及“批处理策略”章节。模型给出的回答是根据知识库中的资料降低推理显存占用可以从以下几个方面入手第一使用量化技术将模型权重从 FP16 压缩到 INT8 或 INT4显著减少显存占用第二合理设置 batch size避免过大的批处理导致显存峰值过高第三采用梯度检查点或激活值重计算技术减少中间激活的存储消耗[来源2][来源3]。这个回答把三个文档的内容自然地融合在了一起并且正确标注了来源。这让我确认 RAG 链路整体是通的。你要知道最怕的结果是模型只回答某一片段内容或者自己编造这时候你就要检查检索召回、提示词、模型温度这几个环节了。4. 踩坑实录与优化经验4.1 常见问题速查表这个项目从零跑到稳定我前前后后踩了不少坑。把最有代表性的一些问题和排查思路整理在下面方便大家对照排查现象可能原因解决办法搜索总是召回无关内容切片过大语义稀释或者 Embedding 模型没有拼接标题调小 max_chunk_size编码时带上标题与章节信息答案经常说“知识库中未找到相关内容”Top-K 取值太小或者切片过细导致信息被拆散增大 Top-K检查切片是否把关键内容切断模型回答里引用标记是乱写的提示词中来源编号与内容位置对应关系不明显在每段前明确加[来源N: 标题-章节]并强调只能引用给定编号本地模型回答特别慢没有 GPU 或者模型太大换小模型如 qwen2.5:3b或者改用云端 API代码块内容出现乱码切片时把代码块切断了实现 inside_code_block 检测延迟切分向量库数据更新困难没有增量更新机制每次重新全量构建数据量小时最省事或按 mtime 增量构建Gradio 界面加载很慢初始化时加载了 Embedding 模型到内存模型全局只加载一次避免每次请求重复加载4.2 检索质量调优的心得调优检索质量有一个基本思路先查“错在哪一层”。如果问题明明在知识库里但没召回那是切片或向量化的问题如果召回了但回答得不好那是提示词或生成参数的问题。我在实践中发现两个非常有效的调优手段。第一个是“查询改写”。用户的问题通常很口语化比如“为啥我模型跑起来那么慢”直接拿去向量检索效果不如把它改写成“模型推理速度慢的原因和优化”。可以用一个轻量模型先改写问题再检索实测命中率能有明显提升。第二个是“重排序”。向量检索召回 Top 50再用一个 Cross-Encoder 模型逐一计算每个候选与问题的相关性打分取最高的 Top 6。Cross-Encoder 比 Bi-Encoder 精确得多只是速度慢所以适合做精排。BGE 官方就提供了对应的 reranker 模型可以直接套用。4.3 关于模型和成本的选择建议很多人问我到底是本地模型好还是云端 API 好。我的回答是分阶段。首次跑通项目、调试代码的时候用云端 API 最省心不用折腾环境速度又快等系统稳定了再根据你的隐私要求决定是否迁移到本地模型。如果知识库里包含客户信息、个人隐私等敏感内容那就直接上本地模型比如 Qwen2.5-7B 或者 Llama-3-8B配一张 24G 显存的显卡足够用。如果只是个人笔记用云端 API 成本也不高正常问答频率一个月几十块钱够了关键是每次只传相关片段而不是整个文档省很多 token。不过用本地模型要注意7B 模型的理解能力和总结能力跟 GPT-4 级别的还是有差距尤其在长文本归纳和多步推理上。如果你发现答案质量不满意一个高效的做法是在不改变架构的前提下换更大参数的模型比如 14B 或 32B 版本而不是去改代码。4.4 为什么我最后放弃了 LangChain说实话我最初是用 LangChain 写第一版的代码确实短——加载器、切片器、向量库、对话链都是现成组件二三十行就拼完了。但真正用起来就发现问题一是版本更新太频繁每个小版本都可能改 API网上搜到的代码很可能跑不了二是抽象层级导致问题排查困难比如检索结果不理想你很难确定是哪一步出了问题要层层 breakpoint三是过度封装导致自定义逻辑难做比如我要加“代码块不切断”的切片逻辑在 LangChain 里反而要绕很多弯。手写的好处是每一步都在掌控之中出了问题能直接定位。等到以后真的要上生产、需要大规模调度的时候再引入框架也不迟。对于 llm_wiki 这种中轻量项目手写百来行代码是性价比最高的选择。5. llm_wiki 的扩展方向从个人笔记到团队知识中台项目跑通之后很多人会想能不能让它更进一步。这里我分享几个我认为有价值的扩展方向。第一个方向是增加标注与反馈机制。在问答界面加点赞/点踩按钮记录用户的反馈数据后续就可以分析哪些问题总是回答不好针对性优化切片策略或补全文档。别小看这个功能它能让一个 demo 变成可持续改进的系统。第二个方向是引入多模态支持。现在的版本只处理 Markdown 文本但很多知识其实藏在 PDF、图片里。如果 wiki 里经常放架构图截图可以考虑接入视觉语言模型或者在知识库层面先把图片上的文字提取出来再做向量化。第三个方向是自动化索引流水线。用 Git 钩子或者定时任务扫描 wiki 目录检测到 Markdown 文件变更后自动增量更新向量库。这一步做完llm_wiki 就能像一个真正的“活”知识库永远跟你的最新笔记保持同步。我自己做过的最大一次扩展是把个人版改成了团队版加了文档权限控制和简单的 tag 过滤。因为检索时可以根据当前用户的可视范围过滤文档集合这对于存储多个客户项目的资料非常有价值。具体实现也不复杂就是在向量检索之前加一层过滤表达式Milvus 原生支持带表达式的 search。回到最初的体验我现在的日常工作流已经离不开这个系统了。写方案前先问一下自己的 wiki开会前快速查一下之前的项目总结那份从 2023 年攒到现在的几百篇技术笔记终于不再是数据孤岛。如果你也有一大堆精心整理但根本搜不出来的文档我建议你花一个周末把 llm_wiki 搭起来。它不需要昂贵的服务器核心代码也就几百行但它会改变你管理知识的习惯。
返回列表