尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

AI编程实战:博客建站+RAG知识库

AI编程实战:博客建站+RAG知识库 大家好我是Java烘焙师。最近利用业余时间完成了博客建站RAG知识库的搭建分享一下过程中遇到的选型问题、实现步骤。搭建博客站点和RAG知识库的初衷是因为日积月累写了几十篇技术文章希望有一个独立的站点并且能用自然语言问答、查找知识点。下面是用到的技术栈静态页面构建docmd网页托管github pagesRAG知识库llamaIndex、coding plan包含的云端embedding向量模型、chroma本地向量库LLMcoding plan包含的云端大语言模型知识库问答web页gradio效果博客github pages地址https://topcoding.github.io/arch-notes/包含了所有的技术文章后续除了在各大博客平台更新也会维护github pages时效性低一些有空才会操作。本地RAG知识库web页博客建站选型生成博客的工具有很多比如docmd、jekyll、hugo、hexo、MkDocs等。最终选择了docmd是因为想低成本构建不用额外了解各种前端框架、或者安装额外的工具链在零配置、或少量配置的情况下快速构建出静态页面。docmd让人眼前一亮的功能有导航栏、全文搜索、mermaid文本绘图支持、站点地图、自动生成适合LLM阅读的文档、多语言支持等能开箱即用。至于其它方案多少都有点门槛jekyll虽然是gitHub pages原生支持但它基于ruby工具链安装搭建比较麻烦hugo编译速度快但主题用的是Go模板语法想自定义样式就得学一套模板写法hexo更偏前端工程化选主题、改组件、配构建多少都得懂点前端框架。实现细节全局安装docmdnpminstall-gdocmd/core启动本地开发服务器并修改配置文件可选# 这一步可以零配置快速预览效果docmd dev# 长期项目建议初始化配置文件、并做修改docmd init构建静态页面docmd build其它注意事项本地目录、文件名改为短线分隔的英文翻译因为会出现在导航url中更通用些批量下载markdown文档里的图片并替换为本地相对路径。因为我是先在博客平台上发布再转成本地markdown文件所以需要这一步。npx wll8/md-img-i.-ooutput--imgdir./assets/images上传到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、模型名、endpointdef_make_embedding(model:str,api_key:str,api_base:str):构造兼容 OpenAI SDK 的某coding plan的embeddin模型兼容 LlamaIndex BaseEmbedding。 importtimeimportopenaifromllama_index.core.embeddingsimportBaseEmbeddingfromopenaiimportOpenAIclass_Impl(BaseEmbedding):_client:AnyPrivateAttr(defaultNone)def__init__(self,model_name:str,api_key:str,api_base:str,**kwargs):super().__init__(model_namemodel_name,**kwargs)self._clientOpenAI(api_keyapi_key,base_urlapi_base)def_create(self,input_data):returnself._client.embeddings.create(modelself.model_name,inputinput_data)return_Impl(model_namemodel,api_keyapi_key,api_baseapi_base)# 设置Settings全局变量指定embedding模型的API key、模型名、endpointSettings.embed_model_make_embedding(modelEMBED_MODEL,api_keyAPI_KEY,api_baseBASE_URL,)加载文档# SimpleDirectoryReader递归读取博客目录下的几十篇markdown文档readerSimpleDirectoryReader(input_dirBLOG_DATA_DIR,required_exts[.md],recursiveTrue,filename_as_idTrue)documentsreader.load_data(show_progressTrue)文档切分# 文档切分经MarkdownNodeParser按标题层级切成几百个chunkparserMarkdownNodeParser()nodesparser.get_nodes_from_documents(documents)向量化、向量结果保存至本地这一步会调云端的embedding模型API不过从代码看不出来调用过程是因为llamaIndex封装好了会读取全局Settings变量没有显式调用过程。向量结果存储至本地向量库chroma作为后续查询知识库的索引避免每次重建。# 向量化并写入chroma本地持久化dbchromadb.PersistentClient(pathCHROMA_PATH)ifrebuild:try:db.delete_collection(COLLECTION_NAME)logger.info(已清空旧索引)exceptException:logger.error(清空索引失败)collectiondb.get_or_create_collection(COLLECTION_NAME)vector_storeChromaVectorStore(chroma_collectioncollection)# 向量化调用云端embedding模型API逐个向量化仅首次构建索引时会调云端storage_contextStorageContext.from_defaults(vector_storevector_store)VectorStoreIndex(nodes,storage_contextstorage_context,show_progressTrue)在线问答每次提问每次问答会调1次云端embedding模型做query向量化、调1次本地chroma向量库检索top-k相近文档chunk、调1次云端LLM模型做最终回答。加载本地向量库索引dbchromadb.PersistentClient(pathCHROMA_PATH)try:collectiondb.get_collection(COLLECTION_NAME)exceptException:print(未找到向量库请先构建索引)sys.exit(1)ifcollection.count()0:print(向量库为空请先构建索引)sys.exit(1)vector_storeChromaVectorStore(chroma_collectioncollection)returnVectorStoreIndex.from_vector_store(vector_store)query向量化调用云端embedding模型获取query的向量结果再查找本地向量库里匹配的内容拼上文件名、标题名得到检索结果defformat_source(meta:dict)-str:从节点的metadata组装来源信息标题路径文件名。file_namemeta.get(file_name,未知文件)# MarkdownNodeParser 把各级标题存为 header_path形如 /H1/H2/header_strmeta.get(header_path,).strip(/).replace(/, / )returnf{header_str}{file_name}ifheader_strelsefile_namedefanswer(index,question:str):检索本地向量库里top-k匹配的内容并生成答案。retrieverindex.as_retriever(similarity_top_kTOP_K)nodesretriever.retrieve(question)ifnotnodes:return(知识库中未找到相关内容。,)# 拼接带编号的contextLLM根据此标注 [序号]与下方来源列表编号一致context_parts[]fori,nodeinenumerate(nodes,start1):sourceformat_source(node.node.metadata)context_parts.append(f【{i}】来源{source}\n{node.node.text})context\n\n.join(context_parts)生成最终回答知识库检索结果拼上query一起作为大语言模型的prompt提示词调用云端的LLM模型# 拼接 promptprompt(你是一个博客知识库助手。请仅根据下方「参考资料」回答用户问题。\n\n要求\n1. 只使用参考资料中的信息不要编造。\n2. 如果参考资料中没有相关内容直接回答「知识库中未找到相关内容」。\n3. 引用信息时在句末标注 [序号]序号对应下方资料编号例如 [1]、[2]。\n\n参考资料\ncontext\n\n用户问题question\n\n回答)answer_textcomplete_answer(prompt).strip()sources_lines[]fori,nodeinenumerate(nodes,start1):scorenode.scoreifnode.scoreisnotNoneelse0.0sources_lines.append(f [{i}]{format_source(node.node.metadata)}(相似度{score:.3f}))sources_text\n.join(sources_lines)return(answer_text,sources_text)defcomplete_answer(prompt:str)-str:调用云端LLM生成流式回答OpenAI兼容chat接口。importtime t0time.time()resp_get_client().chat.completions.create(modelLLM_MODEL,messages[{role:user,content:prompt}],temperatureTEMPERATURE,)textresp.choices[0].message.contentorlogger.info(fLLM 返回:{len(text)}字, 耗时{time.time()-t0:.2f}s)returntext流程图如下在线问答每次提问离线建索引一次性写入向量与原文top-k近邻查询embeddingembeddingLLM 流式博客markdown文档目录SimpleDirectoryReader递归读取 .md 文件MarkdownNodeParser按标题切分chunk向量化每个chunk调多次云端embedding模型云端APIOpenAI兼容embedding模型 LLM模型chroma本地向量库2048维query提问web页 / CLIquery向量化调1次云端embedding模型向量检索知识库文档片段检索本地chroma向量库prompt拼接向量检索结果 query流式生成答案调1次云端LLM模型返回答案、知识库文档来源更进一步以上就是完整的 博客建站 RAG知识库 的流程了欢迎一起探讨。如果想做得更深入还可以考虑搭建本地embedding模型、LLM模型这样就完全自主可控不会有泄露敏感信息的风险了。
返回列表