[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"consumer-news-detail-920":3,"consumer-news-interaction-920":41,"consumer-news-related-920":44},{"detail":4,"item":36},{"card":5,"schemaVersion":23,"fields":24,"content":30},{"id":6,"kind":7,"targetType":8,"targetId":9,"subtype":7,"typeLabel":10,"title":11,"subtitle":12,"summary":13,"coverUrl":14,"badgeText":15,"href":16,"sourceName":12,"meta":17,"metrics":20,"tags":21,"resolved":22},"NEWS_ARTICLE:920","news","NEWS_ARTICLE",920,"资讯","AI编程实战：博客建站+RAG知识库","博客园","大家好，我是Java烘焙师。最近利用业余时间，完成了博客建站+RAG知识库的搭建，分享一下过程中遇到的选型问题、实现步骤。 搭建博客站点和RAG知识库的初衷，是因为日积月累写了几十篇技术文章，希望有一个独立的站点，并且能用自然语言问答、查找知识点。 下面是用到的技术栈： 静态页面构建：docmd 网","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F1247698\u002F202608\u002F1247698-20260829115308559-547026660.png","","\u002Fnews\u002F920",[18,19],"2026","人工智能",{},[19],true,"consumer-content-detail-v1",{"sourceName":12,"authorName":25,"categoryName":19,"summary":13,"description":13,"publishTime":26,"updateTime":27,"sourceUrl":28,"language":29},"Java烘焙师","2026-08-30T08:28","2026-09-02T20:37:56","https:\u002F\u002Fwww.cnblogs.com\u002Ftoplist\u002Fp\u002F22645282","中文",{"format":31,"policy":32,"normalized":22,"html":33,"text":34,"wordCount":35,"hasBody":22},"HTML","NEWS_CONTENT_V1","\u003Cp>大家好，我是Java烘焙师。最近利用业余时间，完成了博客建站+RAG知识库的搭建，分享一下过程中遇到的选型问题、实现步骤。\u003Cbr>\n  搭建博客站点和RAG知识库的初衷，是因为日积月累写了几十篇技术文章，希望有一个独立的站点，并且能用自然语言问答、查找知识点。\u003Cbr>\n  下面是用到的技术栈：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>静态页面构建：docmd\u003C\u002Fli>\n \u003Cli>网页托管：github pages\u003C\u002Fli>\n \u003Cli>RAG知识库：llamaIndex、coding plan包含的云端embedding向量模型、chroma本地向量库\u003C\u002Fli>\n \u003Cli>LLM：coding plan包含的云端大语言模型\u003C\u002Fli>\n \u003Cli>知识库问答web页：gradio\u003C\u002Fli>\n\u003C\u002Ful>\n效果\n\u003Cp>博客github pages地址：\u003Ca href=\"https:\u002F\u002Ftopcoding.github.io\u002Farch-notes\u002F\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">https:\u002F\u002Ftopcoding.github.io\u002Farch-notes\u002F\u003C\u002Fa>\u003Cbr>\n  包含了所有的技术文章，后续除了在各大博客平台更新，也会维护github pages（时效性低一些，有空才会操作）。\u003C\u002Fp>\n\u003Cp>本地RAG知识库web页\u003Cbr>\u003Cimg src=\"https:\u002F\u002Fi1.wp.com\u002Fimg2024.cnblogs.com\u002Fblog\u002F1247698\u002F202608\u002F1247698-20260829115308559-547026660.png?w=720&amp;quality=65&amp;strip=all\" alt=\"rag知识库\">\u003C\u002Fp>\n博客建站\n\u003Ch2>选型\u003C\u002Fh2>\n\u003Cp>生成博客的工具有很多，比如：docmd、jekyll、hugo、hexo、MkDocs等。\u003Cbr>\n  最终选择了docmd，是因为想低成本构建，不用额外了解各种前端框架、或者安装额外的工具链，在零配置、或少量配置的情况下，快速构建出静态页面。\u003Cbr>\n  docmd让人眼前一亮的功能有：导航栏、全文搜索、mermaid文本绘图支持、站点地图、自动生成适合LLM阅读的文档、多语言支持等，能开箱即用。\u003Cbr>\n  至于其它方案，多少都有点门槛：jekyll虽然是gitHub pages原生支持，但它基于ruby工具链，安装搭建比较麻烦；hugo编译速度快，但主题用的是Go模板语法，想自定义样式就得学一套模板写法；hexo更偏前端工程化，选主题、改组件、配构建，多少都得懂点前端框架。\u003C\u002Fp>\n\u003Ch2>实现细节\u003C\u002Fh2>\n\u003Col>\n \u003Cli>全局安装docmd\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cpre>\u003Ccode>npm install -g @docmd\u002Fcore\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Col>\n \u003Cli>启动本地开发服务器，并修改配置文件（可选）\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cpre>\u003Ccode># 这一步可以零配置，快速预览效果\ndocmd dev\n\n# 长期项目，建议初始化配置文件、并做修改\ndocmd init\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Col>\n \u003Cli>构建静态页面\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cpre>\u003Ccode>docmd build\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>其它注意事项：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>本地目录、文件名，改为短线分隔的英文翻译，因为会出现在导航url中，更通用些\u003C\u002Fli>\n \u003Cli>批量下载markdown文档里的图片，并替换为本地相对路径。因为我是先在博客平台上发布，再转成本地markdown文件，所以需要这一步。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cpre>\u003Ccode>npx @wll8\u002Fmd-img -i . -o output --imgdir .\u002Fassets\u002Fimages\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Col>\n \u003Cli>上传到github，通过github actions自动构建和部署\u003Cbr>\n   如果本地构建输出了静态页面（site目录），就会有两份图片文件，如果直接上传github会占用git仓库空间。所以仅上传必要文件（排除掉site目录），依靠github actions来构建和部署站点。\u003C\u002Fli>\n\u003C\u002Fol>\nRAG知识库\n\u003Ch2>原理\u003C\u002Fh2>\n\u003Cp>RAG是检索增强生成（Retrieval Augmented Generation）：预先把私有知识（这里是博客文章）切块、向量化存入向量库；提问时先用问题去向量库检索最相关的片段，再把检索结果、问题一起拼进prompt，交给大语言模型生成回答。之所以在大语言模型前，先过一道向量检索，是为了缩小查询范围，并且避免大模型产生幻觉、胡言乱语。\u003Cbr>\n  这里的向量化，是把文本映射到一个多维数字向量，比如[1.12, 0.98, 3.76, ...]。两个文本的语义越相近，则向量距离越近。\u003Cbr>\n  经过一番调研，发现有两个方向，一是低代码平台，二是用开源框架搭建。\u003C\u002Fp>\n\u003Ch2>低代码平台\u003C\u002Fh2>\n\u003Cp>用低代码平台的好处是可以几乎不写代码、快速搭建demo原型。\u003Cbr>\n  最终选择了dify，是目前较为流行的AI工作流平台，模板和生态丰富，可以在页面上拖拖拽拽，控制数据流向、节点操作。\u003C\u002Fp>\n\u003Ch3>dify实现细节\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cp>创建“知识库”模板应用\u003Cbr>\n    “知识库”模板里已经预设了“用户输入” -&gt; “知识检索” -&gt; “大语言模型” -&gt; “输出”的流程，只需要按提示修改其中的节点。\u003Cbr>\u003Cimg src=\"https:\u002F\u002Fi1.wp.com\u002Fimg2024.cnblogs.com\u002Fblog\u002F1247698\u002F202608\u002F1247698-20260829170600265-1781828715.png?w=720&amp;quality=65&amp;strip=all\" alt=\"\">\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\u003Cp>在知识库页面导入docmd生成的llms-full.txt文件，在“知识检索”节点选择该知识库。\u003Cbr>\u003Cimg src=\"https:\u002F\u002Fi1.wp.com\u002Fimg2024.cnblogs.com\u002Fblog\u002F1247698\u002F202608\u002F1247698-20260829171232292-24520251.png?w=720&amp;quality=65&amp;strip=all\" alt=\"\">\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\u003Cp>在“大语言模型”节点选择模型、上下文\u003Cbr>\n    部分模型有免费试用额度，上下文选择第2步经过向量查询的“知识检索”结果。\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\u003Cp>调试运行\u003Cbr>\n    输入一个问句，会先从知识库检索相关内容，再一起作为prompt给到大语言模型，最终得到靠谱的回答。\u003Cbr>\u003Cimg src=\"https:\u002F\u002Fi1.wp.com\u002Fimg2024.cnblogs.com\u002Fblog\u002F1247698\u002F202608\u002F1247698-20260829170216233-1524499907.png?w=720&amp;quality=65&amp;strip=all\" alt=\"\">\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\u003Cp>发布上线\u003Cbr>\n    可以选择“嵌入到网站中”，这样就能在已有网站里出现一个问答对话框了。\u003C\u002Fp>\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>开源框架\u003C\u002Fh2>\n\u003Cp>用开源框架的好处是更加灵活、自主可控。llamaIndex用来做知识库，是专用工具。\u003Cbr>\n  之所以不用LangChain、LangGraph、AutoGen这类agent开发框架，是因为它们面向的是多步工具调用、自主规划的复杂场景，做知识库检索问答太重了，属于杀鸡用牛刀了，而llamaIndex开箱就带文档解析、向量库对接、检索器这些现成能力。\u003C\u002Fp>\n\u003Ch3>llamaIndex实现细节\u003C\u002Fh3>\n\u003Cp>把markdown文档向量化存入本地chroma，然后用自然语言提问，得到\u003Cstrong>带来源引用\u003C\u002Fstrong>的回答。\u003C\u002Fp>\n\u003Cp>整体分\u003Cstrong>离线构建索引\u003C\u002Fstrong>（一次性）和\u003Cstrong>在线问答\u003C\u002Fstrong>（每次提问）两条线，共享本地Chroma向量库与云端向量模型、大语言模型。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>离线建索引（一次性）\u003C\u002Fstrong>\u003Cbr>\n  切分成多少个文档chunk，就会调多少次云端embedding模型，第一次构建会比较耗时。\u003C\u002Fp>\n\u003Col>\n \u003Cli>设置云端API key、模型名、endpoint\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cpre>\u003Ccode>def _make_embedding(model: str, api_key: str, api_base: str):\n    \"\"\"构造兼容 OpenAI SDK 的某coding plan的embeddin模型，兼容 LlamaIndex BaseEmbedding。\n    \"\"\"\n    import time\n    import openai\n    from llama_index.core.embeddings import BaseEmbedding\n    from openai import OpenAI\n\n    class _Impl(BaseEmbedding):\n        _client: Any = PrivateAttr(default=None)\n\n        def __init__(self, model_name: str, api_key: str, api_base: str, **kwargs):\n            super().__init__(model_name=model_name, **kwargs)\n            self._client = OpenAI(api_key=api_key, base_url=api_base)\n\n        def _create(self, input_data):\n            return self._client.embeddings.create(model=self.model_name, input=input_data)\n\n    return _Impl(model_name=model, api_key=api_key, api_base=api_base)\n\n# 设置Settings全局变量，指定embedding模型的API key、模型名、endpoint\nSettings.embed_model = _make_embedding(\n    model=EMBED_MODEL,\n    api_key=API_KEY,\n    api_base=BASE_URL,\n)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Col>\n \u003Cli>加载文档\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cpre>\u003Ccode>    # SimpleDirectoryReader递归读取博客目录下的几十篇markdown文档\n    reader = SimpleDirectoryReader(\n        input_dir=BLOG_DATA_DIR,\n        required_exts=[\".md\"],\n        recursive=True,\n        filename_as_id=True\n    )\n    documents = reader.load_data(show_progress=True)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Col>\n \u003Cli>文档切分\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cpre>\u003Ccode>    # 文档切分：经MarkdownNodeParser按标题层级切成几百个chunk\n    parser = MarkdownNodeParser()\n    nodes = parser.get_nodes_from_documents(documents)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Col>\n \u003Cli>向量化、向量结果保存至本地\u003Cbr>\n   这一步会调云端的embedding模型API，不过从代码看不出来调用过程，是因为llamaIndex封装好了，会读取全局Settings变量，没有显式调用过程。\u003Cbr>\n   向量结果存储至本地向量库chroma，作为后续查询知识库的索引，避免每次重建。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cpre>\u003Ccode>    # 向量化并写入chroma（本地持久化）\n    db = chromadb.PersistentClient(path=CHROMA_PATH)\n    if rebuild:\n        try:\n            db.delete_collection(COLLECTION_NAME)\n            logger.info(\"已清空旧索引\")\n        except Exception:\n            logger.error(\"清空索引失败\")\n\n    collection = db.get_or_create_collection(COLLECTION_NAME)\n    vector_store = ChromaVectorStore(chroma_collection=collection)\n\n    # 向量化，调用云端embedding模型API，逐个向量化（仅首次构建索引时会调云端）\n    storage_context = StorageContext.from_defaults(vector_store=vector_store)\n    VectorStoreIndex(nodes, storage_context=storage_context, show_progress=True)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>在线问答（每次提问）\u003C\u002Fstrong>\u003Cbr>\n  每次问答，会调1次云端embedding模型做query向量化、调1次本地chroma向量库检索top-k相近文档chunk、调1次云端LLM模型做最终回答。\u003C\u002Fp>\n\u003Col>\n \u003Cli>加载本地向量库索引\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cpre>\u003Ccode>    db = chromadb.PersistentClient(path=CHROMA_PATH)\n    try:\n        collection = db.get_collection(COLLECTION_NAME)\n    except Exception:\n        print(\"未找到向量库，请先构建索引\")\n        sys.exit(1)\n    if collection.count() == 0:\n        print(\"向量库为空，请先构建索引\")\n        sys.exit(1)\n    vector_store = ChromaVectorStore(chroma_collection=collection)\n    return VectorStoreIndex.from_vector_store(vector_store)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Col>\n \u003Cli>query向量化，调用云端embedding模型获取query的向量结果，再查找本地向量库里匹配的内容；拼上文件名、标题名，得到检索结果\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cpre>\u003Ccode>def format_source(meta: dict) -&gt; str:\n    \"\"\"从节点的metadata组装来源信息：标题路径（文件名）。\"\"\"\n    file_name = meta.get(\"file_name\", \"未知文件\")\n    # MarkdownNodeParser 把各级标题存为 header_path（形如 \"\u002FH1\u002FH2\u002F\"）\n    header_str = meta.get(\"header_path\", \"\").strip(\"\u002F\").replace(\"\u002F\", \" \u002F \")\n    return f\"{header_str}（{file_name}）\" if header_str else file_name\n\ndef answer(index, question: str):\n    \"\"\"检索本地向量库里top-k匹配的内容，并生成答案。\"\"\"\n    retriever = index.as_retriever(similarity_top_k=TOP_K)\n    nodes = retriever.retrieve(question)\n\n    if not nodes:\n        return (\"知识库中未找到相关内容。\", \"\")\n\n    # 拼接带编号的context，LLM根据此标注 [序号]，与下方来源列表编号一致\n    context_parts = []\n    for i, node in enumerate(nodes, start=1):\n        source = format_source(node.node.metadata)\n        context_parts.append(f\"【{i}】来源：{source}\\n{node.node.text}\")\n    context = \"\\n\\n\".join(context_parts)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Col>\n \u003Cli>生成最终回答：知识库检索结果，拼上query，一起作为大语言模型的prompt提示词，调用云端的LLM模型\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cpre>\u003Ccode>    # 拼接 prompt\n    prompt = (\n        \"你是一个博客知识库助手。请仅根据下方「参考资料」回答用户问题。\\n\\n\"\n        \"要求：\\n\"\n        \"1. 只使用参考资料中的信息，不要编造。\\n\"\n        \"2. 如果参考资料中没有相关内容，直接回答「知识库中未找到相关内容」。\\n\"\n        \"3. 引用信息时在句末标注 [序号]，序号对应下方资料编号，例如 [1]、[2]。\\n\\n\"\n        \"参考资料：\\n\"\n        + context\n        + \"\\n\\n用户问题：\"\n        + question\n        + \"\\n\\n回答：\"\n    )\n    answer_text = complete_answer(prompt).strip()\n\n    sources_lines = []\n    for i, node in enumerate(nodes, start=1):\n        score = node.score if node.score is not None else 0.0\n        sources_lines.append(f\"  [{i}] {format_source(node.node.metadata)}  (相似度 {score:.3f})\")\n    sources_text = \"\\n\".join(sources_lines)\n    return (answer_text, sources_text)\n\ndef complete_answer(prompt: str) -&gt; str:\n    \"\"\"调用云端LLM，生成流式回答（OpenAI兼容chat接口）。\"\"\"\n    import time\n\n    t0 = time.time()\n    resp = _get_client().chat.completions.create(\n        model=LLM_MODEL,\n        messages=[{\"role\": \"user\", \"content\": prompt}],\n        temperature=TEMPERATURE,\n    )\n    text = resp.choices[0].message.content or \"\"\n    logger.info(f\"LLM 返回: {len(text)}字, 耗时 {time.time()-t0:.2f}s\")\n    return text\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>流程图如下：\u003C\u002Fp>\n\u003Cdiv>\n flowchart TB MD[(\"博客markdown文档目录\")] subgraph OFF[\"离线建索引（一次性）\"] direction TB R[\"SimpleDirectoryReader&lt;br\u002F&gt;递归读取 .md 文件\"] P[\"MarkdownNodeParser&lt;br\u002F&gt;按标题切分chunk\"] E1[\"向量化每个chunk&lt;br\u002F&gt;（调多次云端embedding模型）\"] R --&gt; P --&gt; E1 end ARK[\"云端API（OpenAI兼容）&lt;br\u002F&gt;embedding模型&lt;br\u002F&gt;+ LLM模型\"] CH[(\"chroma本地向量库&lt;br\u002F&gt;2048维\")] subgraph ON[\"在线问答（每次提问）\"] direction TB UI[\"query提问&lt;br\u002F&gt;web页 \u002F CLI\"] RV[\"query向量化&lt;br\u002F&gt;（调1次云端embedding模型）\"] RT[\"向量检索知识库文档片段&lt;br\u002F&gt;（检索本地chroma向量库）\"] PG[\"prompt拼接&lt;br\u002F&gt;向量检索结果 + query\"] GEN[\"流式生成答案&lt;br\u002F&gt;（调1次云端LLM模型）\"] SRC[\"返回答案、知识库文档来源\"] UI --&gt; RV RV --&gt; RT --&gt; PG --&gt; GEN --&gt; SRC --&gt; UI end MD --&gt; R E1 -- 写入向量与原文 --&gt; CH RT -- top-k近邻查询 --&gt; CH E1 -. embedding .-&gt; ARK RV -. embedding .-&gt; ARK GEN -. LLM 流式 .-&gt; ARK classDef store fill:#fff3e0,stroke:#e65100,color:#bf360c classDef cloud fill:#f3e5f5,stroke:#6a1b9a,color:#4a148c class CH,MD store class ARK cloud style OFF fill:#e8f5e9,stroke:#2e7d32 style ON fill:#e3f2fd,stroke:#1565c0\n\u003C\u002Fdiv>\n更进一步\n\u003Cp>以上就是完整的 博客建站 + RAG知识库 的流程了，欢迎一起探讨。\u003Cbr>\n  如果想做得更深入，还可以考虑搭建本地embedding模型、LLM模型，这样就完全自主可控，不会有泄露敏感信息的风险了。\u003C\u002Fp>","大家好，我是Java烘焙师。最近利用业余时间，完成了博客建站+RAG知识库的搭建，分享一下过程中遇到的选型问题、实现步骤。 搭建博客站点和RAG知识库的初衷，是因为日积月累写了几十篇技术文章，希望有一个独立的站点，并且能用自然语言问答、查找知识点。 下面是用到的技术栈： 静态页面构建：docmd 网页托管：github pages RAG知识库：llamaIndex、coding plan包含的云端embedding向量模型、chroma本地向量库 LLM：coding plan包含的云端大语言模型 知识库问答web页：gradio 效果 博客github pages地址：https:\u002F\u002Ftopcoding.github.io\u002Farch-notes\u002F 包含了所有的技术文章，后续除了在各大博客平台更新，也会维护github pages（时效性低一些，有空才会操作）。 本地RAG知识库web页 博客建站 选型 生成博客的工具有很多，比如：docmd、jekyll、hugo、hexo、MkDocs等。 最终选择了docmd，是因为想低成本构建，不用额外了解各种前端框架、或者安装额外的工具链，在零配置、或少量配置的情况下，快速构建出静态页面。 docmd让人眼前一亮的功能有：导航栏、全文搜索、mermaid文本绘图支持、站点地图、自动生成适合LLM阅读的文档、多语言支持等，能开箱即用。 至于其它方案，多少都有点门槛：jekyll虽然是gitHub pages原生支持，但它基于ruby工具链，安装搭建比较麻烦；hugo编译速度快，但主题用的是Go模板语法，想自定义样式就得学一套模板写法；hexo更偏前端工程化，选主题、改组件、配构建，多少都得懂点前端框架。 实现细节 全局安装docmd npm install -g @docmd\u002Fcore 启动本地开发服务器，并修改配置文件（可选） # 这一步可以零配置，快速预览效果 docmd dev # 长期项目，建议初始化配置文件、并做修改 docmd init 构建静态页面 docmd build 其它注意事项： 本地目录、文件名，改为短线分隔的英文翻译，因为会出现在导航url中，更通用些 批量下载markdown文档里的图片，并替换为本地相对路径。因为我是先在博客平台上发布，再转成本地markdown文件，所以需要这一步。 npx @wll8\u002Fmd-img -i . -o output --imgdir .\u002Fassets\u002Fimages 上传到github，通过github actions自动构建和部署 如果本地构建输出了静态页面（site目录），就会有两份图片文件，如果直接上传github会占用git仓库空间。所以仅上传必要文件（排除掉site目录），依靠github actions来构建和部署站点。 RAG知识库 原理 RAG是检索增强生成（Retrieval Augmented Generation）：预先把私有知识（这里是博客文章）切块、向量化存入向量库；提问时先用问题去向量库检索最相关的片段，再把检索结果、问题一起拼进prompt，交给大语言模型生成回答。之所以在大语言模型前，先过一道向量检索，是为了缩小查询范围，并且避免大模型产生幻觉、胡言乱语。 这里的向量化，是把文本映射到一个多维数字向量，比如[1.12, 0.98, 3.76, ...]。两个文本的语义越相近，则向量距离越近。 经过一番调研，发现有两个方向，一是低代码平台，二是用开源框架搭建。 低代码平台 用低代码平台的好处是可以几乎不写代码、快速搭建demo原型。 最终选择了dify，是目前较为流行的AI工作流平台，模板和生态丰富，可以在页面上拖拖拽拽，控制数据流向、节点操作。 dify实现细节 创建“知识库”模板应用 “知识库”模板里已经预设了“用户输入” -> “知识检索” -> “大语言模型” -> “输出”的流程，只需要按提示修改其中的节点。 在知识库页面导入docmd生成的llms-full.txt文件，在“知识检索”节点选择该知识库。 在“大语言模型”节点选择模型、上下文 部分模型有免费试用额度，上下文选择第2步经过向量查询的“知识检索”结果。 调试运行 输入一个问句，会先从知识库检索相关内容，再一起作为prompt给到大语言模型，最终得到靠谱的回答。 发布上线 可以选择“嵌入到网站中”，这样就能在已有网站里出现一个问答对话框了。 开源框架 用开源框架的好处是更加灵活、自主可控。llamaIndex用来做知识库，是专用工具。 之所以不用LangChain、LangGraph、AutoGen这类agent开发框架，是因为它们面向的是多步工具调用、自主规划的复杂场景，做知识库检索问答太重了，属于杀鸡用牛刀了，而llamaIndex开箱就带文档解析、向量库对接、检索器这些现成能力。 llamaIndex实现细节 把markdown文档向量化存入本地chroma，然后用自然语言提问，得到带来源引用的回答。 整体分离线构建索引（一次性）和在线问答（每次提问）两条线，共享本地Chroma向量库与云端向量模型、大语言模型。 离线建索引（一次性） 切分成多少个文档chunk，就会调多少次云端embedding模型，第一次构建会比较耗时。 设置云端API key、模型名、endpoint def _make_embedding(model: str, api_key: str, api_base: str): \"\"\"构造兼容 OpenAI SDK 的某coding plan的embeddin模型，兼容 LlamaIndex BaseEmbedding。 \"\"\" import time import openai from llama_index.core.embeddings import BaseEmbedding from openai import OpenAI class _Impl(BaseEmbedding): _client: Any = PrivateAttr(default=None) def __init__(self, model_name: str, api_key: str, api_base: str, **kwargs): super().__init__(model_name=model_name, **kwargs) self._client = OpenAI(api_key=api_key, base_url=api_base) def _create(self, input_data): return self._client.embeddings.create(model=self.model_name, input=input_data) return _Impl(model_name=model, api_key=api_key, api_base=api_base) # 设置Settings全局变量，指定embedding模型的API key、模型名、endpoint Settings.embed_model = _make_embedding( model=EMBED_MODEL, api_key=API_KEY, api_base=BASE_URL, ) 加载文档 # SimpleDirectoryReader递归读取博客目录下的几十篇markdown文档 reader = SimpleDirectoryReader( input_dir=BLOG_DATA_DIR, required_exts=[\".md\"], recursive=True, filename_as_id=True ) documents = reader.load_data(show_progress=True) 文档切分 # 文档切分：经MarkdownNodeParser按标题层级切成几百个chunk parser = MarkdownNodeParser() nodes = parser.get_nodes_from_documents(documents) 向量化、向量结果保存至本地 这一步会调云端的embedding模型API，不过从代码看不出来调用过程，是因为llamaIndex封装好了，会读取全局Settings变量，没有显式调用过程。 向量结果存储至本地向量库chroma，作为后续查询知识库的索引，避免每次重建。 # 向量化并写入chroma（本地持久化） db = chromadb.PersistentClient(path=CHROMA_PATH) if rebuild: try: db.delete_collection(COLLECTION_NAME) logger.info(\"已清空旧索引\") except Exception: logger.error(\"清空索引失败\") collection = db.get_or_create_collection(COLLECTION_NAME) vector_store = ChromaVectorStore(chroma_collection=collection) # 向量化，调用云端embedding模型API，逐个向量化（仅首次构建索引时会调云端） storage_context = StorageContext.from_defaults(vector_store=vector_store) VectorStoreIndex(nodes, storage_context=storage_context, show_progress=True) 在线问答（每次提问） 每次问答，会调1次云端embedding模型做query向量化、调1次本地chroma向量库检索top-k相近文档chunk、调1次云端LLM模型做最终回答。 加载本地向量库索引 db = chromadb.PersistentClient(path=CHROMA_PATH) try: collection = db.get_collection(COLLECTION_NAME) except Exception: print(\"未找到向量库，请先构建索引\") sys.exit(1) if collection.count() == 0: print(\"向量库为空，请先构建索引\") sys.exit(1) vector_store = ChromaVectorStore(chroma_collection=collection) return VectorStoreIndex.from_vector_store(vector_store) query向量化，调用云端embedding模型获取query的向量结果，再查找本地向量库里匹配的内容；拼上文件名、标题名，得到检索结果 def format_source(meta: dict) -> str: \"\"\"从节点的metadata组装来源信息：标题路径（文件名）。\"\"\" file_name = meta.get(\"file_name\", \"未知文件\") # MarkdownNodeParser 把各级标题存为 header_path（形如 \"\u002FH1\u002FH2\u002F\"） header_str = meta.get(\"header_path\", \"\").strip(\"\u002F\").replace(\"\u002F\", \" \u002F \") return f\"{header_str}（{file_name}）\" if header_str else file_name def answer(index, question: str): \"\"\"检索本地向量库里top-k匹配的内容，并生成答案。\"\"\" retriever = index.as_retriever(similarity_top_k=TOP_K) nodes = retriever.retrieve(question) if not nodes: return (\"知识库中未找到相关内容。\", \"\") # 拼接带编号的context，LLM根据此标注 [序号]，与下方来源列表编号一致 context_parts = [] for i, node in enumerate(nodes, start=1): source = format_source(node.node.metadata) context_parts.append(f\"【{i}】来源：{source}\\n{node.node.text}\") context = \"\\n\\n\".join(context_parts) 生成最终回答：知识库检索结果，拼上query，一起作为大语言模型的prompt提示词，调用云端的LLM模型 # 拼接 prompt prompt = ( \"你是一个博客知识库助手。请仅根据下方「参考资料」回答用户问题。\\n\\n\" \"要求：\\n\" \"1. 只使用参考资料中的信息，不要编造。\\n\" \"2. 如果参考资料中没有相关内容，直接回答「知识库中未找到相关内容」。\\n\" \"3. 引用信息时在句末标注 [序号]，序号对应下方资料编号，例如 [1]、[2]。\\n\\n\" \"参考资料：\\n\" + context + \"\\n\\n用户问题：\" + question + \"\\n\\n回答：\" ) answer_text = complete_answer(prompt).strip() sources_lines = [] for i, node in enumerate(nodes, start=1): score = node.score if node.score is not None else 0.0 sources_lines.append(f\" [{i}] {format_source(node.node.metadata)} (相似度 {score:.3f})\") sources_text = \"\\n\".join(sources_lines) return (answer_text, sources_text) def complete_answer(prompt: str) -> str: \"\"\"调用云端LLM，生成流式回答（OpenAI兼容chat接口）。\"\"\" import time t0 = time.time() resp = _get_client().chat.completions.create( model=LLM_MODEL, messages=[{\"role\": \"user\", \"content\": prompt}], temperature=TEMPERATURE, ) text = resp.choices[0].message.content or \"\" logger.info(f\"LLM 返回: {len(text)}字, 耗时 {time.time()-t0:.2f}s\") return text 流程图如下： flowchart TB MD[(\"博客markdown文档目录\")] subgraph OFF[\"离线建索引（一次性）\"] direction TB R[\"SimpleDirectoryReader\u003Cbr\u002F>递归读取 .md 文件\"] P[\"MarkdownNodeParser\u003Cbr\u002F>按标题切分chunk\"] E1[\"向量化每个chunk\u003Cbr\u002F>（调多次云端embedding模型）\"] R --> P --> E1 end ARK[\"云端API（OpenAI兼容）\u003Cbr\u002F>embedding模型\u003Cbr\u002F>+ LLM模型\"] CH[(\"chroma本地向量库\u003Cbr\u002F>2048维\")] subgraph ON[\"在线问答（每次提问）\"] direction TB UI[\"query提问\u003Cbr\u002F>web页 \u002F CLI\"] RV[\"query向量化\u003Cbr\u002F>（调1次云端embedding模型）\"] RT[\"向量检索知识库文档片段\u003Cbr\u002F>（检索本地chroma向量库）\"] PG[\"prompt拼接\u003Cbr\u002F>向量检索结果 + query\"] GEN[\"流式生成答案\u003Cbr\u002F>（调1次云端LLM模型）\"] SRC[\"返回答案、知识库文档来源\"] UI --> RV RV --> RT --> PG --> GEN --> SRC --> UI end MD --> R E1 -- 写入向量与原文 --> CH RT -- top-k近邻查询 --> CH E1 -. embedding .-> ARK RV -. embedding .-> ARK GEN -. LLM 流式 .-> ARK classDef store fill:#fff3e0,stroke:#e65100,color:#bf360c classDef cloud fill:#f3e5f5,stroke:#6a1b9a,color:#4a148c class CH,MD store class ARK cloud style OFF fill:#e8f5e9,stroke:#2e7d32 style ON fill:#e3f2fd,stroke:#1565c0 更进一步 以上就是完整的 博客建站 + RAG知识库 的流程了，欢迎一起探讨。 如果想做得更深入，还可以考虑搭建本地embedding模型、LLM模型，这样就完全自主可控，不会有泄露敏感信息的风险了。",6815,{"id":6,"kind":7,"title":11,"summary":13,"image":14,"href":16,"meta":37,"badge":10,"author":12,"stats":-1,"accent":38,"coverRatio":39,"tags":40},"2026 · 人工智能","#2563eb","16 \u002F 10",[19],{"targetType":8,"targetId":9,"likedByMe":42,"likeCount":43,"commentCount":43,"contentLikeCount":43,"contentCommentCount":43,"sourceLikeCount":43,"sourceCommentCount":43},false,0,[45,52,59,66,72,78,85,92],{"id":46,"kind":7,"title":47,"summary":48,"image":49,"href":50,"meta":37,"badge":10,"author":12,"stats":-1,"accent":38,"coverRatio":39,"tags":51},"NEWS_ARTICLE:827","MHS 三部曲（下）：谁允许 AI 行动？——权力、合规与中国厂商的答卷","我们总在等待一个像 ChatGPT 那样的机器人时刻。但具身智能真正的拐点，也许先发生在更不起眼的地方：一台陌生设备，第一次能把自己的能力、状态和边界完整告诉 AI；一个 Agent，第一次能把试出来的经验固化成可验证、可复用的机器技能。","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F510\u002F202608\u002F510-20260830153745229-1536981088.jpg","\u002Fnews\u002F827",[19],{"id":53,"kind":7,"title":54,"summary":55,"image":56,"href":57,"meta":37,"badge":10,"author":12,"stats":-1,"accent":38,"coverRatio":39,"tags":58},"NEWS_ARTICLE:835","体验向量数据库 qdrant","作者:张富春(ahfuzhang)，转载时请注明作者和引用链接，谢谢！ cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 背景 为了早点搞懂公司的百万行 C# 的祖传代码，我想在缺乏文档、缺乏帮手的情况下，先建立一个 企业知识库 来快速帮我理清头绪。 一开始，我是打算以 weav","https:\u002F\u002Fimg2022.cnblogs.com\u002Fblog\u002F1457949\u002F202202\u002F1457949-20220216153819145-1193738712.png","\u002Fnews\u002F835",[19],{"id":60,"kind":7,"title":61,"summary":62,"image":63,"href":64,"meta":37,"badge":10,"author":12,"stats":-1,"accent":38,"coverRatio":39,"tags":65},"NEWS_ARTICLE:853","为什么团队用了 AI 之后，测试质量反而下降了！","一、AI 提效之后，往往先崩的是质量 前两天刷到一篇同行的复盘。 「AI 提效半年后，我们的测试团队规模反而更大了」 用AI 提效半年，人数不降，反而变更多了？ 这位老哥是认真做过团队实践的，观察写得很扎实。提测质量肉眼可见地下滑，需求做漏了，单位时间里测试捞出来的 bug 反而变多，最后测试团队加","https:\u002F\u002Fimage.kjdaohang.com\u002Fimg\u002F20260831223910845.png","\u002Fnews\u002F853",[19],{"id":67,"kind":7,"title":68,"summary":69,"image":15,"href":70,"meta":37,"badge":10,"author":12,"stats":-1,"accent":38,"coverRatio":39,"tags":71},"NEWS_ARTICLE:856","AI生产力：从效率到工作流重构","当初学专业的笔记还在吗？相信大部分人都没保留，而能力在不断积累和更新，关注和思考的角度在变化，在AI蒸馏我们的同时。也可以用AI的能力，去蒸馏一下魔幻的职场。","\u002Fnews\u002F856",[19],{"id":73,"kind":7,"title":74,"summary":75,"image":15,"href":76,"meta":37,"badge":10,"author":12,"stats":-1,"accent":38,"coverRatio":39,"tags":77},"NEWS_ARTICLE:861","个体看衰AI，企业加速转型","信息化数字化智能化，十年前的说法在逐步兑现。最后回到一个灵魂拷问：当业务运营更加高效。组织间的协作更加流畅，企业究竟想去拓宽市场，还是手起刀落降低成本？","\u002Fnews\u002F861",[19],{"id":79,"kind":7,"title":80,"summary":81,"image":82,"href":83,"meta":37,"badge":10,"author":12,"stats":-1,"accent":38,"coverRatio":39,"tags":84},"NEWS_ARTICLE:864","阿里开源：skill-up，一款Agent Skill 评测工具！","2026 年走到现在，Agent Skill 已经成了 AI 领域的标配。 把个人经验沉淀成 SKILL.md，把团队最佳实践封装成可复用的技能包，这些都已经是很常规的操作了。最近在社区上看到有篇文章问了一个特别扎心的问题。 「一个 Skill 的『好坏』，到底由谁定义？评判它的标准又是什么？」 这","https:\u002F\u002Fimage.kjdaohang.com\u002Fimg\u002F20260831105634380.png","\u002Fnews\u002F864",[19],{"id":86,"kind":7,"title":87,"summary":88,"image":89,"href":90,"meta":37,"badge":10,"author":12,"stats":-1,"accent":38,"coverRatio":39,"tags":91},"NEWS_ARTICLE:868","DeepSeekHarness-MCP-Manager 一款DSH超好用的MCP管理跟Skills管理插件","链接：https:\u002F\u002Fgithub.com\u002Fxxxyz\u002FDeepSeekHarness-MCP-Manager 设置 → MCP 管理&#160;管理项目级与全局&#160;cordis.patch.yml&#160;中的&#160;@deepseek-ai\u002Fdsh-mcp-client&#160;","https:\u002F\u002Fcamo.githubusercontent.com\u002F5d48b15e4e45c30746d4410bd5dd51552b20ed0294e6703d220931a8377c3ff8\u002F68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f40787878797a2f6473682d6d63702d6d616e616765723f6c6f676f3d6e706d26636f6c6f723d636233383337","\u002Fnews\u002F868",[19],{"id":93,"kind":7,"title":94,"summary":95,"image":96,"href":97,"meta":37,"badge":10,"author":12,"stats":-1,"accent":38,"coverRatio":39,"tags":98},"NEWS_ARTICLE:871","\"AgentOPSD: Recursive Self-Distillation for Agentic Reinforcement Learning\" 论文笔记","清华、浙大与美团 LongCat 团队合作的 AgentOPSD，目前挂在 Arxiv 26.08 上，做的是长程多轮 Agentic RL 里的回合级信用分配。可验证奖励的 RL 只在整条轨迹结束时给一个稀疏的 0\u002F1 信号，GRPO 这类方法把轨迹级优势均匀广播到每个 token，无法把成败真正","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F3614753\u002F202609\u002F3614753-20260901141154042-339903878.png","\u002Fnews\u002F871",[19]]