Qwen-Agent构建RAG
Qwen-Agent是一个开发框架。充分利用基于通义千问模型(Qwen)的指令遵循、工具使用、规划、记忆能力。
Qwen-Agent支持的模型形式:
DashScope服务提供的Qwen模型服务
支持通过OpenAI API方式接入开源的Qwen模型服务
智能体复杂度描述
Qwen-Agent构建的智能体包含三个复杂度级别,每一层都建立在前一层的基础上
Level-1:检索
处理100万字上下文的一种朴素方法是简单采用增强检索生成(RAG)。 RAG将上下文分割成较短的块,每块不超过512个字,然后仅保留最相关的块在8k字的上下文中。 挑战在于如何精准定位最相关的块。经过多次尝试,我们提出了一种基于关键词的解决方案:
-
步骤1:指导聊天模型将用户查询中的指令信息与非指令信息分开。
- 例如,将用户查询"回答时请用2000字详尽阐述,我的问题是,自行车是什么时候发明的?请用英文回复。"转化为{"信息": ["自行车是什么时候发明的"], "指令": ["回答时用2000字", "尽量详尽", "用英文回复"]}。
-
步骤2:要求聊天模型从查询的信息部分推导出多语言关键词。
- 例如,短语"自行车是什么时候发明的"会转换为{"关键词_英文": ["bicycles", "invented", "when"], "关键词_中文": ["自行车", "发明", "时间"]}。
-
步骤3:运用BM25这一传统的基于关键词的检索方法,找出与提取关键词最相关的块。
Level-2:分块检索
level-1 RAG方法很快速,但常在相关块与用户查询关键词重叠程度不足时失效,导致这些相关的块未被检索到、没有提供给模型。尽管理论上向量检索可以缓解这一问题,但实际上效果有限。 为了解决这个局限,我们采用了一种暴力策略来减少错过相关上下文的几率:
-
步骤1:对于每个512字块,让聊天模型评估其与用户查询的相关性,** 如果认为不相关则输出"无", 如果相关则输出相关句子。这些块会被并行处理以避免长时间等待。
-
步骤2:然后,取那些非"无"的输出(即相关句子),用它们作为搜索查询词,通过BM25检索出最相关的块** (总的检索结果长度控制在8k上下文限制内)。
-
步骤3:最后,基于检索到的上下文生成最终答案,** 这一步骤的实现方式与通常的RAG相同。
Level-3:逐步推理
在基于文档的问题回答中,一个典型的挑战是多跳推理。
-
例如,考虑回答问题:“与第五交响曲创作于同一世纪的交通工具是什么?
-
模型首先需要确定子问题的答案,“第五交响曲是在哪个世纪创作的?”即19世纪。
-
然后,它才可以意识到包含“自行车于19世纪发明”的信息块实际上与原始问题相关的。
-
工具调用智能体或ReAct智能体是经典的解决方案,它们内置了问题分解和逐步推理的能力。因此,我们将前述级别二的智能体(Lv2-智能体)封装为一个工具,由工具调用智能体(Lv3-智能体)调用。工具调用智能体进行多跳推理的流程如下
1. 向Lv3-智能体提出一个问题。
2. while (Lv3-智能体无法根据其记忆回答问题) {
Lv3-智能体提出一个新的子问题待解答。
Lv3-智能体向Lv2-智能体提问这个子问题。
将Lv2-智能体的回应添加到Lv3-智能体的记忆中。
}
3. Lv3-智能体提供原始问题的最终答案。
例如,Lv3-智能体最初向Lv2-智能体提出子问题:“贝多芬的第五交响曲是在哪个世纪创作的?
- 收到“19世纪”的回复后,Lv3-智能体提出新的子问题:“19世纪期间发明了什么交通工具?
- 通过整合Lv2-智能体的所有反馈,Lv3-智能体便能够回答原始问题:“与第五交响曲创作于同一世纪的交通工具是什么?”
代码实现
-
安装
# 一来安装 #安装稳定版本:完整安装,包含qwen-agent所有功能 pip install -U "qwen-agent[rag,code_interpreter,gui,mcp]" # 使用 `pip install -U qwen-agent` 来安装最小依赖。 # 可使用双括号指定如下的可选依赖: # [gui] 用于提供基于 Gradio 的 GUI 支持; # [rag] 用于支持 RAG; # [code_interpreter] 用于提供代码解释器相关支持; # [mcp] 用于支持 MCP。 -
logging准备(捕获输出内容供模型后续使用)
# -*- coding: utf-8 -*- import logging import io import os # 自定义的日志处理器 class LogCapture: def __init__(self): self.log_capture_string = io.StringIO() self.log_handler = logging.StreamHandler(self.log_capture_string) self.log_handler.setLevel(logging.INFO) self.log_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') self.log_handler.setFormatter(self.log_formatter) # 获取 qwen_agent 的日志记录器 self.logger = logging.getLogger('qwen_agent_logger') self.logger.setLevel(logging.INFO) self.logger.addHandler(self.log_handler) # 可以捕获根日志记录器的输出 self.root_logger = logging.getLogger() self.root_logger.setLevel(logging.INFO) self.root_logger.addHandler(self.log_handler) def get_log(self): return self.log_capture_string.getvalue() def clear_log(self): self.log_capture_string.truncate(0) self.log_capture_string.seek(0) # 初始化日志捕获器 log_capture = LogCapture() -
配置LLM
llm_cfg = { # 使用 DashScope 提供的模型服务: 'model': 'qwen-max', 'model_server': 'dashscope', 'api_key': os.getenv("DASHSCOPE_API_KEY"), 'generate_cfg': { 'top_p': 0.8 } } -
创建智能体
system_instruction = '' tools = [] files = ['./浦发上海浦东发展银行西安分行个金客户经理考核办法.pdf'] # 给智能体一个 PDF 文件阅读。 # 清除之前的日志 log_capture.clear_log() # 创建Assistant智能体 bot = Assistant(llm=llm_cfg, system_message=system_instruction, function_list=tools, files=files) -
作为聊天机器人运行智能体
messages = [] # 这里储存聊天历史。 query = "客户经理被客户投诉一次,扣多少分?" # 将用户请求添加到聊天历史。 messages.append({'role': 'user', 'content': query}) response = [] current_index = 0 # 运行智能体 for response in bot.run(messages=messages): # 在第一次响应时,分析日志以查找召回的文档内容 if current_index == 0: # 获取日志内容 log_content = log_capture.get_log() print("\n===== 从日志中提取的检索信息 =====") # 查找与检索相关的日志行 retrieval_logs = [line for line in log_content.split('\n') if any(keyword in line.lower() for keyword in ['retriev', 'search', 'chunk', 'document', 'ref', 'token'])] # 打印检索相关的日志 for log_line in retrieval_logs: print(log_line) # 尝试从日志中提取文档内容 # 通常在日志中会有类似 "retrieved document: ..." 或 "content: ..." 的行 content_logs = [line for line in log_content.split('\n') if any(keyword in line.lower() for keyword in ['content', 'text', 'document', 'chunk'])] print("\n===== 可能包含文档内容的日志 =====") for log_line in content_logs: print(log_line) print("===========================\n") current_response = response[0]['content'][current_index:] current_index = len(response[0]['content']) print(current_response, end='') # 将机器人的回应添加到聊天历史。 messages.extend(response) # 运行结束后,分析完整的日志 print("\n\n===== 运行结束后的完整日志分析 =====") log_content = log_capture.get_log() # 尝试从日志中提取更多信息 print("\n1. 关键词提取:") keyword_logs = [line for line in log_content.split('\n') if 'keywords' in line.lower()] for log_line in keyword_logs: print(log_line) print("\n2. 文档处理:") doc_logs = [line for line in log_content.split('\n') if 'doc' in line.lower() or 'chunk' in line.lower()] for log_line in doc_logs: print(log_line) print("\n3. 检索相关:") retrieval_logs = [line for line in log_content.split('\n') if 'retriev' in line.lower() or 'search' in line.lower() or 'ref' in line.lower()] for log_line in retrieval_logs: print(log_line) print("\n4. 可能包含文档内容的日志:") content_logs = [line for line in log_content.split('\n') if 'content:' in line.lower() or 'text:' in line.lower()] for log_line in content_logs: print(log_line) print("===========================\n") -
多文档RAG助手处理实现
import os def get_file_list(folder_path): # 初始化文件列表 file_list = [] # 遍历文件夹 for root, dirs, files in os.walk(folder_path): for file in files: # 获取文件的完整路径 file_path = os.path.join(root, file) # 将文件路径添加到列表中 file_list.append(file_path) return file_list # 获取指定知识库文件列表 file_list = get_file_list('./docs')from qwen_agent.agents import Assistant from qwen_agent.agents import Assistant # 配置使用的 LLM llm_cfg = { # 使用 DashScope 提供的模型服务: 'model': 'qwen-max', 'model_server': 'dashscope', 'api_key': os.getenv('DASHSCOPE_API_KEY'), 'generate_cfg': { 'top_p': 0.8 } } # 创建一个智能体。 system_instruction = '你是一位保险专家,根据你的经验来精准的回答用户提出的问题' tools = [] bot = Assistant(llm=llm_cfg, system_message=system_instruction, function_list=tools, files=file_list) # 作为聊天机器人运行智能体。 messages = [] # 这里储存聊天历史。 while True: query = input('用户请求: ') if query == '-1': break # 将用户请求添加到聊天历史。 messages.append({'role': 'user', 'content': query}) response = [] current_index = 0 for response in bot.run(messages=messages): # 流式输出 current_response = response[0]['content'][current_index:] current_index = len(response[0]['content']) print(current_response, end='') # 将机器人的回应添加到聊天历史。 messages.extend(response)

浙公网安备 33010602011771号