基于BGE-M3与Qwen2.5构建乌克兰语Agentic RAG系统实战
1. 项目概述当RAG走向“能动性”为乌克兰语内容赋能最近在折腾大语言模型应用落地的朋友对RAG检索增强生成这个概念肯定不陌生。简单说就是让模型在回答问题时不是凭空“编造”而是先去一个庞大的知识库比如你的文档、数据库里检索相关信息再基于这些“证据”来生成答案。这极大地缓解了模型“一本正经胡说八道”的幻觉问题。但传统的RAG流程往往是“一次性”的用户提问 - 检索 - 生成答案然后就结束了。整个过程模型是被动的缺乏对复杂、多轮问题的深度思考和规划能力。这就引出了“Agentic RAG”能动性RAG这个概念。它不是一个具体的工具而是一种设计范式。你可以把它理解为给RAG系统装上了一个“大脑”和“双手”。这个系统不再只是被动响应而是能像一个智能代理Agent一样主动规划任务、拆解复杂问题、进行多轮甚至多工具的交互。比如面对一个需要综合多份文档、进行对比分析才能回答的问题Agentic RAG会自己制定计划先检索A文档的关键部分再根据初步结果去检索B文档的相关段落发现信息矛盾时可能还会去检索第三份权威资料进行验证最后综合所有信息生成一个逻辑严谨、证据链完整的回答。而我这次动手实践的目标是将这套先进的Agentic RAG范式应用到一个相对小众但极具价值的领域乌克兰语内容处理。选择乌克兰语一方面是出于对多语言AI应用公平性的关注另一方面也是因为其作为一门拥有复杂语法和丰富形态变化的语言对检索和生成都是不小的挑战。市面上成熟的RAG方案大多围绕英语、中文优化直接套用到乌克兰语上效果往往大打折扣。这个项目的核心就是构建一个能理解、检索并智能处理乌克兰语文档的“能动”系统。我选用了两个在各自领域表现出色的开源模型作为基石BGE-M3作为多语言嵌入模型负责文档的向量化与检索Qwen2.5-3B-Instruct作为轻量级但能力不俗的生成模型扮演“智能体大脑”的角色。接下来我会详细拆解从设计思路到代码实现的每一个环节分享如何让这两个模型协同工作打造一个真正能为乌克兰语内容服务的Agentic RAG系统。2. 核心架构与组件选型解析构建一个Agentic RAG系统远不是把检索和生成模型简单拼在一起。它需要一套清晰的架构让各个组件各司其职又能高效协同。我的设计核心是“分工明确循环迭代”。整个系统可以看作一个由“规划器”、“执行器”检索工具和“生成器”组成的智能工作流。2.1 为什么是Agentic与传统RAG的本质区别传统RAG可以看作一个“查询-响应”系统。它的流程是线性的、静态的。用户输入问题系统将其转化为向量去向量数据库做一次相似性搜索返回Top-K个相关片段然后一股脑塞给大模型说“给这是相关资料请生成答案。” 这种方式存在几个明显短板检索僵化一次检索定终身。如果第一次检索没找到关键信息或者检索到的信息相互矛盾系统没有自我修正的机会。缺乏推理模型只是信息的“总结者”而非“思考者”。它无法判断是否需要进一步搜索也无法将复杂问题拆分成多个子问题逐步解决。上下文局限当问题涉及多个方面或需要多步骤推理时一次性提供的杂乱上下文很容易让模型迷失重点。Agentic RAG则引入了“智能体”的思维模式。它将整个问答过程视为一个可规划、可执行、可评估的任务。系统会先对用户问题进行意图理解和任务规划例如“这是一个需要对比分析的问题我需要先找到A产品的特性再找到B产品的特性最后进行比较”。然后它会自主调用检索工具可能进行多轮、带有反馈的检索。每次检索后它都会评估当前收集到的信息是否足够、是否准确如果不够就生成新的、更精准的查询词再次检索。这个过程可以循环多次直到它认为信息完备再启动最终答案的生成。简单类比传统RAG像是一个记忆力好但不会思考的图书管理员你问什么他就在固定区域找几本书给你。而Agentic RAG像是一个资深的研究助理他会先理解你研究课题的深度然后制定查阅计划去档案室、数据库、期刊库等多处查找、对比、验证资料最后给你整理出一份详实的报告。2.2 核心组件深度选型BGE-M3与Qwen2.5-3B-Instruct选型直接决定了系统的能力上限和落地成本。我经过大量测试和对比为本次乌克兰语项目锁定了以下两个核心模型。嵌入模型BGE-M3检索的基石在于如何将文本无论是问题还是文档转化为数学向量嵌入。这个向量的质量直接决定了检索的准确性。对于多语言场景尤其是乌克兰语模型必须在多种语言上都有均衡且强大的表现。为何选择BGE-M3BGE-M3是北京智源研究院开源的“重磅炸弹”它最突出的特点是“全能”。它支持超过100种语言并且在 Massive Text Embedding Benchmark (MTEB) 排行榜上综合表现非常靠前。它不仅支持最常用的稠密向量检索dense retrieval还同时支持稀疏向量检索lexical retrieval 类似传统关键词匹配和多向量检索ColBERT-like这意味着它可以通过多种方式理解文本相似性。对于乌克兰语我测试了其与LaBSE、multilingual-e5等模型的对比BGE-M3在语义相似度任务上表现更稳定对语言形态变化如词尾变化的理解更好。实操注意点BGE-M3模型较大约2.2B参数需要一定的GPU内存例如FP16精度下需要约4.5GB。在部署时可以使用FlagEmbedding库轻松加载。一个关键技巧是对于非英语查询官方建议在查询文本前添加指令“Represent this sentence for searching relevant passages: ”这能显著提升跨语言检索效果。对于乌克兰语文档我们则不需要添加此指令。生成模型/智能体大脑Qwen2.5-3B-Instruct在Agentic RAG中生成模型扮演着“大脑”的角色它要理解用户意图、规划任务、决定何时检索、如何提炼检索结果、并生成最终答案。因此我们需要一个既足够“聪明”能进行复杂规划又足够“轻量”便于快速响应和部署的模型。为何选择Qwen2.5-3B-Instruct通义千问开源的Qwen2.5系列在轻量级模型中表现堪称惊艳。Qwen2.5-3B-Instruct虽然只有30亿参数但其指令跟随、逻辑推理和工具调用能力在同类模型中脱颖而出。它完全支持Agent所需的复杂指令理解和多轮对话规划。相比于更大的7B、14B模型3B版本在消费级显卡甚至高端CPU上就能流畅运行推理速度极快非常适合作为实时交互的智能体核心。它的多语言能力也足够覆盖乌克兰语的理解与生成。实操注意点使用该模型作为Agent关键在于系统提示词System Prompt的设计。我们需要在提示词中明确赋予它“规划者”和“执行者”的角色定义好它可以使用的工具这里主要是检索工具并规定它输出格式例如以特定JSON格式表示下一步动作。我们可以使用transformers库或vLLM进行部署和推理。对于3B模型在RTX 306012GB上即可进行FP16精度的流畅推理。2.3 系统工作流设计整个系统的工作流程是一个动态循环如下图所示概念描述初始化用户输入一个乌克兰语问题。规划阶段Qwen2.5-3B-Instruct作为Agent接收问题结合系统指令分析问题复杂度。如果问题简单直接它可能决定直接跳转到最终生成如果问题复杂它会生成一个任务规划例如“首先需要检索关于‘乌克兰能源政策’的概述性文档其次需要检索最近一年关于‘可再生能源投资’的具体新闻或报告。”执行与检索阶段Agent根据规划生成一个或多个精准的检索查询词可能是乌克兰语也可能是模型内部翻译成的英语但BGE-M3能处理。系统调用BGE-M3模型将这些查询词转化为向量在预先构建好的乌克兰语文档向量库中进行相似性搜索返回最相关的文本片段。评估与迭代阶段Agent收到检索结果后对其进行评估。它会判断信息是否足够是否回答了当前子问题是否存在矛盾如果不够它会基于已有信息生成一个更聚焦、更具体的后续查询词例如“在刚才找到的能源政策文档中没有提到太阳能的具体补贴金额请专门检索‘乌克兰 太阳能 补贴 2024’。”然后回到第3步。这个过程可能循环2-4次。生成阶段当Agent判定信息已收集充分或达到最大迭代次数时它将所有相关的检索片段作为上下文综合生成一个连贯、准确、基于证据的乌克兰语最终答案返回给用户。这个循环的核心是“思考-行动-观察”的智能体范式使得系统具备了处理复杂、开放式问题的能力。3. 乌克兰语知识库构建与检索优化实战Agentic RAG的“智能”发挥建立在高质量、易检索的知识库之上。对于乌克兰语这一步的挑战更大需要针对语言特点做专门处理。3.1 文档预处理与分块策略原始文档PDF、网页、DOCX等不能直接用于检索。我们需要将其转化为纯文本并切割成大小合适的“块”。文本提取使用pypdfPDF、beautifulsoup4HTML、python-docxDOCX等库进行文本提取。对于乌克兰语网页特别注意编码问题通常使用UTF-8。分块Chunking这是关键步骤。分块太大会包含无关信息稀释检索精度分块太小会割裂语义让模型难以理解。策略选择对于一般性文档我采用“递归字符分割”结合“句子重叠”的策略。使用langchain的RecursiveCharacterTextSplitter是一个不错的选择。分隔符可以设置为[\n\n, \n, 。, , , , ]优先按段落分割。乌克兰语特调乌克兰语句子结束符与英语类似但要注意其特有的标点。块大小chunk_size设置为512-1024个字符约150-250个单词重叠chunk_overlap设置为100-150个字符。重叠部分能确保关键信息不会因为恰好被切在块边缘而丢失。元数据附加为每个文本块附加元数据非常重要例如来源文件名、原始页码、章节标题等。这在最终生成答案时可以为模型提供引用来源信息也便于人工核查。3.2 使用BGE-M3生成高质量向量分块后的文本需要转化为向量存入数据库。加载模型from FlagEmbedding import BGEM3FlagModel model BGEM3FlagModel(BAAI/bge-m3, use_fp16True) # 使用FP16加速节省显存批量编码一次性对大量文本进行编码效率更高。注意BGE-M3的encode方法会返回稠密向量、稀疏向量等多重表示。我们主要使用稠密向量dense_vecs进行检索。# 假设 chunks 是文本块列表 embeddings model.encode(chunks, batch_size32, # 根据GPU内存调整 max_length8192, # 模型最大长度 return_denseTrue, return_sparseFalse, return_colbert_vecsFalse) dense_embeddings embeddings[dense_vecs]注意BGE-M3模型本身支持长文本但通常我们会将输入限制在512或1024个token以内这与我们分块的大小相匹配。对于超过模型长度的文本它内部会进行截断或池化操作。3.3 向量数据库的选择与部署我们需要一个数据库来存储这些向量并支持高效的相似性搜索。选型Chroma DB。我选择Chroma是因为它轻量、易用、完全开源并且与Python生态集成极好。它支持内存和持久化模式对于中小规模知识库百万级向量以下完全够用。部署与插入import chromadb from chromadb.config import Settings # 创建或连接持久化数据库 client chromadb.PersistentClient(path./ukrainian_rag_db) collection client.get_or_create_collection(nameukrainian_docs) # 准备数据ID、文本、向量、元数据 ids [fchunk_{i} for i in range(len(chunks))] documents chunks metadatas [...] # 对应的元数据列表 # 批量插入 collection.add( idsids, documentsdocuments, metadatasmetadatas, embeddingsdense_embeddings.tolist() # 注意转为list )检索查询在Agentic循环中当需要检索时# 假设 query 是Agent生成的搜索词 # 对查询词进行编码添加检索指令以优化效果 query_for_retrieval fRepresent this sentence for searching relevant passages: {query} query_embedding model.encode([query_for_retrieval], return_denseTrue)[dense_vecs][0] # 在Chroma中搜索 results collection.query( query_embeddings[query_embedding.tolist()], n_results5 # 每次检索返回5个最相关片段 ) retrieved_docs results[documents][0] retrieved_metadatas results[metadatas][0]4. 智能体Agent的构建与提示工程这是Agentic RAG的灵魂所在。我们需要让Qwen2.5-3B-Instruct模型学会规划、使用工具检索、评估和生成。4.1 系统提示词设计系统提示词定义了Agent的角色、能力和行为规范。一个强大的提示词是成功的关键。你是一个专业的乌克兰语信息分析助手。你的核心能力是使用检索工具来查找乌克兰语文档库中的信息以回答用户的问题。 ## 能力 1. **规划**分析用户问题的复杂性。如果问题简单可以直接回答如果复杂将其分解为多个子问题。 2. **检索**你可以使用search_documents工具。根据你的规划生成最精准、关键的搜索查询词来查找相关信息。你可以进行多轮检索。 3. **评估**每次收到检索结果后评估信息是否足够、相关、准确。思考是否已回答了当前子问题是否需要从不同角度或更具体地搜索 4. **综合与生成**当你认为信息足够时综合所有检索到的相关信息生成一个清晰、准确、完整的乌克兰语答案。答案必须基于检索到的证据可以引用来源如提到“根据XX文档”。 ## 行动格式 你必须严格按照以下JSON格式输出你的“思考过程”和“下一步行动” { thought: 你的详细思考过程分析问题规划步骤评估当前信息。用乌克兰语或英语思考均可。, action: next_step, // 可以是 search, final_answer action_input: { // 只有当 action 为 search 时才需要 query: 你的搜索查询词使用最可能找到答案的关键词 } } ## 流程控制 - 从分析用户问题开始。 - 如果决定搜索输出上述JSON工具会被自动调用并将结果返回给你。 - 你收到搜索结果后继续输出JSON格式的思考和新行动。 - 当你决定给出最终答案时将 action 设为 final_answer并在 thought 字段后直接开始用乌克兰语书写完整答案。 现在开始处理用户的问题。这个提示词明确了角色、步骤、输出格式和循环逻辑将大模型“框定”在我们希望的工作流中。4.2 工具调用与循环控制实现我们需要编写代码来解析模型的输出调用工具并将结果反馈给模型形成循环。import json from transformers import AutoTokenizer, AutoModelForCausalLM import torch # 加载Qwen2.5-3B-Instruct模型 model_name Qwen/Qwen2.5-3B-Instruct tokenizer AutoTokenizer.from_pretrained(model_name) model AutoModelForCausalLM.from_pretrained(model_name, torch_dtypetorch.float16, device_mapauto) def chat_with_agent(user_query, system_prompt, max_turns5): 与Agent进行多轮交互 messages [ {role: system, content: system_prompt}, {role: user, content: user_query} ] for turn in range(max_turns): # 1. 获取模型响应 inputs tokenizer.apply_chat_template(messages, tokenizeTrue, add_generation_promptTrue, return_tensorspt).to(model.device) with torch.no_grad(): outputs model.generate(inputs, max_new_tokens512, do_sampleTrue, temperature0.7) response_text tokenizer.decode(outputs[0][len(inputs[0]):], skip_special_tokensTrue) # 2. 尝试解析JSON格式的“行动指令” try: # 找到可能的JSON块 lines response_text.strip().split(\n) json_str None for line in lines: if line.strip().startswith({): json_str line.strip() break if json_str: action_data json.loads(json_str) else: # 如果没有检测到JSON可能模型直接给出了最终答案 print(fTurn {turn1}: 模型可能直接生成了最终答案或格式错误。) print(响应:, response_text[:200]) return response_text except json.JSONDecodeError: print(fTurn {turn1}: 解析JSON失败响应内容, response_text[:200]) return Agent响应格式错误。 # 3. 根据行动类型处理 if action_data.get(action) final_answer: # 提取最终答案通常在thought字段之后 final_answer response_text.split(thought:)[1].split(final_answer:)[-1] if final_answer: in response_text else action_data.get(thought, ) print(fTurn {turn1}: 生成最终答案。) return final_answer elif action_data.get(action) search: search_query action_data.get(action_input, {}).get(query) if not search_query: print(搜索查询词为空。) break print(fTurn {turn1}: 执行搜索查询词: {search_query}) # 4. 调用检索工具这里接入上一节的检索函数 search_results retrieve_documents(search_query, top_k5) # 假设retrieve_documents是封装好的检索函数 # 将检索结果格式化为文本准备喂给模型 retrieved_context \n---\n.join([f[片段{i1}]: {doc} for i, doc in enumerate(search_results)]) # 5. 将检索结果作为“助手”的观察添加到对话历史 messages.append({role: assistant, content: response_text}) # 记录模型刚才的“行动”输出 messages.append({role: user, content: f这是检索到的信息\n{retrieved_context}\n\n请根据这些信息继续分析。}) else: print(f未知行动: {action_data.get(action)}) break return 达到最大交互轮数未生成最终答案。 # 使用示例 system_prompt ... # 填入上面的系统提示词 user_question 乌克兰在可再生能源领域特别是风能和太阳能方面最新的国家战略和激励政策是什么 final_answer chat_with_agent(user_question, system_prompt) print(\n 最终答案 \n) print(final_answer)这段代码实现了核心的Agent循环。模型输出JSON指令代码解析并执行检索再将结果反馈直到模型决定给出最终答案。4.3 多轮检索与自我修正策略在循环中Agent的“智能”体现在它如何利用前一轮的检索结果来优化下一轮的查询。这通常通过系统提示词和模型自身的推理能力来实现。在我们的提示词中要求模型进行“评估”。在实践中模型可能会细化查询第一轮搜索“乌克兰可再生能源政策”返回的是概述。模型发现缺少具体数据第二轮搜索“乌克兰 2024 太阳能 装机容量 目标”。纠正方向第一轮搜索“乌克兰风能补贴”返回信息过时。模型在思考中判断“可能需要查找2023年或2024年的最新法案”从而发起新的搜索。综合验证从不同文档中检索到看似矛盾的信息如两个不同的补贴比例模型可能会发起第三次搜索查询“乌克兰 可再生能源 补贴 官方最新文件”来寻找权威来源进行核实。这种自我修正能力是Agentic RAG超越传统单次检索的核心价值。5. 系统集成、评估与性能调优将各个模块组装成一个稳定、高效的服务并科学评估其效果是项目落地的最后一步。5.1 端到端服务搭建我们可以使用FastAPI搭建一个简单的Web服务提供API接口。from fastapi import FastAPI, HTTPException from pydantic import BaseModel import uvicorn app FastAPI(titleUkrainian Agentic RAG API) class QueryRequest(BaseModel): question: str max_turns: int 5 class QueryResponse(BaseModel): answer: str sources: list[str] # 可以返回引用的文档来源ID或片段 turn_count: int app.post(/ask, response_modelQueryResponse) async def ask_question(request: QueryRequest): 接收用户问题启动Agentic RAG流程返回答案。 try: # 这里调用前面实现的 chat_with_agent 函数 final_answer chat_with_agent( user_queryrequest.question, system_promptSYSTEM_PROMPT, # 全局定义的系统提示词 max_turnsrequest.max_turns ) # 在实际应用中需要在检索和生成过程中记录来源 # 这里简化处理假设从全局变量或函数返回值中获取 retrieved_source_ids get_last_retrieval_sources() # 伪函数获取本次对话检索到的源ID return QueryResponse( answerfinal_answer, sourcesretrieved_source_ids, turn_count... # 记录实际交互轮数 ) except Exception as e: raise HTTPException(status_code500, detailstr(e)) if __name__ __main__: uvicorn.run(app, host0.0.0.0, port8000)这样前端或其它服务就可以通过发送POST请求到/ask端点来获取智能答案。5.2 效果评估方法论如何判断这个系统比传统RAG好需要定量和定性结合。检索相关性评估人工标注一批乌克兰语问题并标注标准答案及相关文档片段。计算系统的检索召回率RecallK即在前K个检索结果中有多少比例包含了正确答案所需的片段。对比单次检索和Agentic多轮检索的召回率。答案准确性评估基于事实的准确性将生成的答案与标注的标准答案对比检查关键事实如日期、数字、名称、政策要点是否一致。可以使用LLM-as-a-judge让一个更强的模型如GPT-4来评判辅助进行。幻觉率统计答案中无法从提供上下文中找到依据的陈述比例。复杂问题处理能力设计需要多步推理、综合多来源信息的复杂问题。定性评估Agentic RAG是否能通过多轮检索成功拆解问题并找到所有必要信息而传统RAG是否只能回答问题的某个片面或直接失败。人工评估邀请懂乌克兰语的专家或用户从答案的有用性、完整性、连贯性、基于证据的程度等多个维度进行打分例如1-5分。5.3 性能瓶颈分析与调优在实际运行中可能会遇到性能问题。检索延迟BGE-M3编码和向量数据库查询是主要耗时点。优化对知识库向量进行索引优化如Chroma的HNSW索引。对于查询可以启用缓存对相同或相似的查询直接返回缓存结果。考虑使用更快的向量数据库如Weaviate或Pinecone云服务但它们会引入复杂性和成本。生成延迟Qwen2.5-3B-Instruct的生成速度。优化使用vLLM或TGI(Text Generation Inference) 部署模型它们具有高效的连续批处理和PagedAttention技术能极大提升吞吐量。对于3B模型在合适硬件上达到每秒生成数十个token是可行的。循环轮次控制防止Agent陷入无限循环或进行不必要的检索。优化在系统提示词中强调效率。在代码中设置最大轮次如5轮。可以引入一个简单的“信心分数”评估当连续两轮检索结果高度相似或新增信息很少时自动触发最终生成。上下文长度管理多轮检索的上下文会越来越长。优化不是将所有历史检索片段都堆给模型。可以采用“滑动窗口”或“摘要”策略。例如只保留最近2-3轮的检索片段或者让模型在每一轮后对已收集的信息做一个简要总结然后将总结作为下一轮的历史上下文而不是原始文本。构建一个面向乌克兰语的Agentic RAG系统是一次将前沿AI范式与具体语言需求结合的深度实践。从选择BGE-M3和Qwen2.5-3B-Instruct这对“多语言检索尖兵”和“轻量智能体大脑”组合到设计能自我规划、检索、评估的智能循环再到针对乌克兰语进行细致的预处理和调优每一步都需要权衡技术选型、资源开销和最终效果。这个过程让我深刻体会到Agentic RAG不是简单的功能叠加而是通过赋予系统“主动性”和“思考能力”从根本上提升了复杂信息获取任务的可靠性和深度。对于资源相对稀缺的非英语语言这种能够主动、精准挖掘知识的智能系统其价值尤为显著。未来可以考虑引入更多工具如计算器、网页搜索API让这个乌克兰语智能助理的能力边界进一步扩展。