Loading

Qwen-Agent构建RAG

Qwen-Agent是一个开发框架。充分利用基于通义千问模型(Qwen)的指令遵循、工具使用、规划、记忆能力。

Qwen-Agent支持的模型形式:

  • DashScope服务提供的Qwen模型服务

  • 支持通过OpenAI API方式接入开源的Qwen模型服务

Github:https://github.com/QwenLM/Qwen-Agent

智能体复杂度描述

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-智能体便能够回答原始问题:“与第五交响曲创作于同一世纪的交通工具是什么?”

代码实现

  1. 安装

    # 一来安装
    #安装稳定版本:完整安装,包含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。
    
  2. 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()
    
  3. 配置LLM

    llm_cfg = {
        # 使用 DashScope 提供的模型服务:
        'model': 'qwen-max',
        'model_server': 'dashscope',
        'api_key': os.getenv("DASHSCOPE_API_KEY"),
        'generate_cfg': {
            'top_p': 0.8
        }
    }
    
  4. 创建智能体

    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)
    
    
  5. 作为聊天机器人运行智能体

    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")
    
  6. 多文档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)
    
posted @ 2026-05-08 14:51  木子七  阅读(73)  评论(0)    收藏  举报