Qwen-Agent智能体框架实战:从工具调用到多智能体协作
1. 项目概述从大语言模型到智能体框架的跃迁最近在折腾大语言模型应用落地的朋友们估计都绕不开一个词——“智能体”。当大家还在为如何让一个LLM大语言模型稳定输出JSON格式而头疼时业界已经悄然进入了下一个阶段如何让LLM不仅能回答问题还能自主调用工具、处理复杂任务、甚至与其他智能体协作。这正是“Qwen-Agent”这个项目试图回答的核心问题。它不是一个孤立的模型而是一个由阿里云通义千问团队开源的、用于构建基于大语言模型的智能体应用框架。简单来说Qwen-Agent让你能像搭积木一样将强大的Qwen系列大语言模型如Qwen2.5、Qwen2.5-Coder等与各种工具代码解释器、浏览器、文件读写API等和记忆模块组合起来构建出能理解用户意图、规划执行步骤、并最终完成任务的“智能体”。无论是开发一个能自动分析数据并生成报告的分析助手还是一个能联网搜索最新资讯并整理摘要的信息助手Qwen-Agent都提供了一套标准化的“脚手架”和“零部件”。我花了近两周时间深度体验和拆解了这个框架从环境搭建到自定义工具开发再到部署一个可交互的Web Demo。整个过程下来我的感受是它极大地降低了智能体应用开发的门槛尤其是对于已经熟悉Qwen系列模型的开发者。框架设计上有很多贴合实际工程需求的巧思比如对长上下文128K的原生优化、对多模态图像、文档输入的支持以及清晰的工具调用协议。当然作为一个快速发展的开源项目它在文档的细致度和一些“开箱即用”的体验上还有打磨空间但这并不妨碍它成为一个极具潜力的起点。2. 核心架构与设计哲学拆解要理解Qwen-Agent不能只看它提供了哪些类和方法更要理解其背后的设计思路。这决定了你用起来是顺手还是别扭。2.1 智能体范式的选择ReAct与Plan-and-Execute的融合当前主流的智能体范式主要有两种ReAct和Plan-and-Execute。Qwen-Agent在底层设计上更倾向于后者但同时也吸收了前者的优点。ReAct强调“思考-行动-观察”的循环。智能体每执行一步都会生成一个“思考”Reasoning然后决定采取哪个“行动”Action执行后得到“观察”Observation再进入下一轮循环。这种方式交互性强逻辑透明适合调试但任务规划是隐式的、逐步展开的。Plan-and-Execute智能体先根据目标制定一个完整的、分步骤的计划Plan然后按顺序执行Execute每一步。这种方式结构清晰效率可能更高尤其适合步骤明确的任务但对模型的规划能力要求高。Qwen-Agent的Agent类在初始化时会加载一个function_calling的LLM即支持函数调用的模型。当用户提出请求时框架的核心工作流是1LLM根据用户请求和可用工具列表理解意图并规划需要调用哪些工具或直接回答2框架执行工具调用3将工具执行结果返回给LLM4LLM整合信息决定下一步是继续调用工具还是生成最终答案给用户。这个过程更像是一个“规划-执行-再规划”的混合模式既有一次性规划多个工具调用的潜力如果模型能力足够也保留了单步交互的灵活性。注意这里的“规划”并不总是显式地输出一个计划文本。对于Qwen2.5-72B-Instruct这类强模型它可能在心里隐状态就规划好了几步然后连续输出多个工具调用请求。框架需要能妥善处理这种“流式”的工具调用。2.2 核心组件模块化与可插拔Qwen-Agent的代码结构清晰地体现了模块化思想主要包含以下几大块智能体核心位于qwen_agent/agent目录下。最核心的是Agent基类定义了智能体的基本生命周期初始化、运行、重置。Assistant和Router是两个重要的具体实现。Assistant最常用的单智能体封装了与LLM的对话、工具调用逻辑。你大部分的自定义智能体都会继承或组合它。Router路由智能体可以根据用户问题类型将请求分发给不同的子智能体Specialist处理实现智能体间的协作。工具集位于qwen_agent/tools目录。这是框架的“武器库”。工具被设计成可插拔的每个工具都是一个独立的类需要实现call方法。框架内置了丰富工具代码解释器在沙箱中执行Python代码处理数据计算、图表生成等这是实现“数据分析智能体”的核心。关键词搜索与网页抓取如amap_weather天气、web_extractor网页内容提取、image_gen文生图等。文档处理支持读取PDF、Word、Excel、PPT、TXT等文件并能进行摘要、问答。基础工具如storage键值存储用于记忆、code_interpreter的变种等。LLM服务层位于qwen_agent/llm目录。它抽象了与不同大模型服务的交互。目前主要支持DashScope阿里云灵积模型服务的Python SDK是调用Qwen系列模型最直接的方式。OpenAI兼容OpenAI API格式的模型服务这意味着你可以方便地接入其他兼容API的模型如一些本地部署的模型服务增强了框架的灵活性。记忆与状态管理智能体不是“一锤子买卖”需要有记忆。Qwen-Agent通过messages列表来维护对话历史同时也提供了storage工具来实现更结构化的长期记忆如用户偏好。Agent基类中的run方法其核心就是维护和更新这个messages列表。多模态支持框架在设计之初就考虑了多模态。LLM基类的chat方法可以接受包含图像、文档等文件的messages。这意味着你可以直接让智能体“看”一张图表并进行分析或者读取一个PDF文件后回答相关问题。2.3 为什么选择Qwen-Agent优势与场景分析与LangChain、LlamaIndex等更通用的智能体框架相比Qwen-Agent有其鲜明的特点与Qwen模型深度集成如果你主要使用通义千问系列模型那么Qwen-Agent在工具调用格式、长上下文处理、多模态理解上会有更好的兼容性和性能表现。它就像是为Qwen模型量身定制的“驾驶舱”。轻量级与聚焦相较于LangChain庞大的生态和复杂的概念Qwen-Agent更轻量概念更集中主要围绕Agent、Tool、LLM学习曲线相对平缓适合快速原型开发。内置强大工具其内置的代码解释器、文档处理工具完成度很高特别是代码解释器提供了文件上传/下载、安全沙箱等完整功能省去了大量集成工作。对中文和国内生态友好工具集中包含了如高德地图天气等国内服务示例和文档也以中文为主对国内开发者更友好。它最适合哪些场景快速构建基于Qwen模型的智能体应用你想做一个能联网搜索、写代码、分析数据的AI助手Qwen-Agent提供了最短的路径。研究智能体行为与评估其相对简洁的架构便于你修改和实验不同的规划、工具调用策略。企业内网知识库问答增强结合其文档处理工具和RAG技术可以构建能理解内部文档的智能客服或分析助手。当然如果你的项目需要集成大量第三方非标准API或者对框架的扩展性和社区生态有极高要求可能还需要评估LangChain等更成熟的方案。但对于大多数从模型应用走向智能体的团队来说Qwen-Agent是一个绝佳的起点。3. 从零开始环境搭建与第一个智能体理论说了这么多我们动手搭一个环境并创建第一个能调用工具的智能体。这是检验一个框架是否“友好”的第一步。3.1 基础环境配置与依赖安装首先你需要一个Python环境建议3.9。然后通过pip安装Qwen-Agent。这里有个小坑项目依赖的某些库如readability-lxml用于网页提取可能需要额外的系统依赖。# 1. 克隆仓库为了获取示例和最新代码 git clone https://github.com/QwenLM/Qwen-Agent.git cd Qwen-Agent # 2. 创建并激活虚拟环境推荐 python -m venv venv source venv/bin/activate # Linux/Mac # venv\Scripts\activate # Windows # 3. 安装核心包 # 方式一安装基础库最精简 pip install qwen-agent # 方式二安装全部依赖推荐包含所有工具和Web UI pip install -e .[all] # 注意在Windows上安装[all]时可能会遇到一些包如unstructured用于文档解析的编译错误。 # 如果遇到可以尝试先安装Microsoft C Build Tools或者暂时跳过全部依赖按需安装。 # pip install qwen-agent # pip install playwright beautifulsoup4 # 按需添加网页抓取工具 # playwright install # 安装浏览器驱动安装完成后最关键的一步是配置模型API密钥。Qwen-Agent默认使用阿里云DashScope服务。你需要去阿里云官网开通DashScope并获取一个API Key。设置环境变量是最方便的方式# Linux/Mac export DASHSCOPE_API_KEYyour-dashscope-api-key-here # Windows (PowerShell) $env:DASHSCOPE_API_KEYyour-dashscope-api-key-here或者在代码中直接设置import os os.environ[DASHSCOPE_API_KEY] your-dashscope-api-key-here3.2 创建你的第一个工具调用智能体我们来创建一个最简单的智能体让它能够查询天气。这里会用到内置的amap_weather工具它需要高德地图的API Key。你需要去高德开放平台申请一个Web服务的Key。import os from qwen_agent.agent import Assistant from qwen_agent.tools import AmapWeather # 1. 设置API密钥实际开发中建议用环境变量或配置文件 os.environ[DASHSCOPE_API_KEY] your-dashscope-api-key os.environ[AMAP_TOKEN] your-amap-api-key # 高德地图Key # 2. 定义工具列表 tools [AmapWeather()] # 3. 初始化智能体 # 这里使用 qwen-max 模型你也可以换成 qwen-plus 或本地模型 agent Assistant( llm{model: qwen-max, model_server: dashscope}, # 使用DashScope上的qwen-max模型 function_listtools, system_message你是一个有帮助的助手可以查询天气。 ) # 4. 运行智能体 messages [{role: user, content: 北京今天天气怎么样}] response agent.run(messagesmessages) # 5. 打印结果 for rsp in response: print(rsp) # 打印流式输出的每一部分 # 最终 response 会是一个包含完整对话历史的列表 print(response[-1][content])执行这段代码你会看到智能体首先“思考”需要调用天气工具然后框架执行工具调用向高德API发送请求最后模型将返回的天气信息组织成自然语言回复给你。实操心得第一次运行很可能失败。常见问题API Key错误确保DASHSCOPE_API_KEY和AMAP_TOKEN都已正确设置且有效。DashScope的Key需要开通相应模型如qwen-max的权限。网络问题确保你的网络能正常访问阿里云服务。工具初始化失败有些工具如WebExtractor依赖playwright需要额外安装和初始化playwright install。模型服务参数model_server默认为dashscope如果你使用兼容OpenAI API的自部署模型需要设置为对应的base_url如http://localhost:8000/v1并将model参数改为你的模型名。3.3 深入理解消息格式与运行流程上面代码中的messages变量是整个框架交互的核心。它必须是一个字典列表每个字典包含role和content。role可以是user、assistant、tool。当agent.run()被调用时内部发生了以下关键步骤历史消息整合将传入的messages与智能体内部维护的历史记录合并。LLM调用与规划将整合后的消息、系统提示词、可用工具描述自动生成一起发送给LLM。LLM返回的响应可能包含纯文本回答。一个或多个function_call请求格式为{name: tool_name, arguments: ...}。工具调用与结果封装如果LLM返回了function_call框架会找到对应的工具实例用arguments调用其call方法。然后将工具执行结果封装成一个role为tool的消息content是执行结果或错误信息并包含一个name字段标识工具。循环或终止将工具执行结果作为新消息追加到历史中然后重复步骤2-3直到LLM返回一个不包含function_call的纯文本消息作为最终答复。流式输出agent.run()返回的是一个生成器它会yield出每一步的中间结果如LLM的思考、工具调用开始、工具返回结果、最终回答这对于构建实时交互的UI非常有用。理解这个流程对于调试智能体行为至关重要。你可以通过打印每一步的messages来观察智能体是如何“思考”和“行动”的。4. 核心工具深度解析与自定义开发内置工具虽好但真正的威力在于能够自定义工具将智能体接入你自己的业务系统。我们来深入看看工具的工作原理并动手创建一个。4.1 内置工具详解以代码解释器为例代码解释器是Qwen-Agent中最强大的工具之一。它允许模型在安全的沙箱环境中执行Python代码处理用户上传的文件如CSV、Excel进行计算、绘图并将结果文件返回给用户。from qwen_agent.tools import CodeInterpreter # 初始化代码解释器工具 ci_tool CodeInterpreter() # 模拟一个工具调用 # 假设LLM生成了这样一个function_call tool_call { name: code_interpreter, arguments: {code: import pandas as pd\\ndf pd.read_csv(\user_uploaded_file.csv\)\\nprint(df.describe()), lang: python} } # 框架会这样调用工具 result ci_tool.call(tool_call[arguments]) print(result) # 输出可能包含代码执行的标准输出、错误信息、以及生成的新文件列表。关键特性与配置沙箱安全默认在独立子进程或容器中运行代码限制了网络访问和文件系统权限防止恶意代码。文件管理工具能自动管理一个“工作区”。用户上传的文件会被放在工作区内代码可以读写工作区内的文件生成的新文件也会被记录并可供用户下载。会话持久同一个智能体会话中的多次代码调用其工作区是保持的这意味着后面的代码可以访问前面代码生成的文件。多语言支持理论上支持任何命令行工具通过lang参数指定。注意事项资源限制长时间运行或内存消耗大的代码可能会被终止。生产环境需要仔细配置超时时间和资源上限。依赖管理沙箱环境中的Python包是有限的。Qwen-Agent的代码解释器预装了一些常用数据科学库如pandas, numpy, matplotlib但如果需要其他包需要在工具初始化时指定或者让模型在代码中尝试安装pip install但这可能有安全风险。文件路径代码中引用文件必须使用相对路径或工具提供的特定路径不能使用绝对路径。4.2 自定义工具开发从零创建一个“查询数据库”工具假设我们有一个内部数据库存放着产品信息。我们想创建一个工具让智能体能够查询产品库存。步骤一定义工具类所有自定义工具必须继承BaseTool类并实现call方法。call方法接收一个字符串参数即LLM传来的arguments返回一个字符串结果。import json import sqlite3 # 这里用SQLite示例实际可能是MySQL、PostgreSQL等 from qwen_agent.tools.base import BaseTool class ProductInventoryTool(BaseTool): name query_product_inventory # 工具的唯一标识LLM通过这个名称来调用 description 查询产品的库存信息。输入应为JSON格式包含product_id字段。 # 描述很重要LLM靠它理解工具功能 def __init__(self, db_pathproducts.db): super().__init__() self.db_path db_path # 初始化数据库连接等这里简单化每次调用时连接 def call(self, params: str): :param params: LLM传来的参数字符串期望是JSON。 :return: 查询结果的字符串表示。 try: # 1. 解析参数 args json.loads(params) product_id args.get(product_id) if not product_id: return 错误参数中缺少 product_id。 # 2. 执行查询 conn sqlite3.connect(self.db_path) cursor conn.cursor() cursor.execute(SELECT name, inventory FROM products WHERE id ?, (product_id,)) row cursor.fetchone() conn.close() # 3. 格式化结果 if row: product_name, inventory row result f产品 {product_name} (ID: {product_id}) 的当前库存为 {inventory} 件。 else: result f未找到ID为 {product_id} 的产品。 return result except json.JSONDecodeError: return 错误参数不是有效的JSON格式。请确保输入是包含product_id的JSON对象。 except Exception as e: return f查询过程中发生错误{str(e)}步骤二将工具集成到智能体中from qwen_agent.agent import Assistant # 创建自定义工具实例 my_tool ProductInventoryTool(db_pathmy_products.db) # 创建智能体传入自定义工具 agent Assistant( llm{model: qwen-max, model_server: dashscope}, function_list[my_tool], # 可以和其他内置工具一起放入列表 system_message你是一个库存管理助手可以查询产品库存。 ) # 使用 messages [{role: user, content: 帮我查一下产品ID为P1001的库存还有多少}] for rsp in agent.run(messagesmessages): # 观察执行过程LLM会识别用户意图调用我们的 query_product_inventory 工具 if content in rsp: print(rsp[content])步骤三优化工具描述为了让LLM更好地理解和使用你的工具description字段至关重要。你应该清晰地描述工具是做什么的。输入参数的格式和每个字段的含义。输出的大致格式。更完善的description示例description 查询指定产品的当前库存数量。 输入必须是一个JSON对象包含以下字段 - product_id: (字符串必需) 产品的唯一标识符例如 P1001。 输出是一个描述库存情况的字符串。 框架会自动将这些描述转换成模型能理解的函数调用定义。清晰的描述能极大提高工具调用的准确率。4.3 工具调用协议与LLM的适配Qwen-Agent使用的工具调用协议与OpenAI的Function Calling高度兼容。这意味着当你使用一个支持Function Calling的模型时如Qwen2.5-Instruct系列、GPT-4等模型能很好地理解如何调用工具。在底层框架在每次调用LLM时会将所有工具的name和description以及参数schema如果定义了的话作为“函数定义”的一部分传给模型。模型根据对话历史和这些定义决定是否需要调用函数工具以及生成什么样的参数。一个重要的细节参数格式。我们的call方法接收一个字符串params。但LLM生成的arguments可能是一个JSON字符串如{product_id: P1001}也可能是一个看起来像JSON的对象字符串。在call方法内部我们需要做健壮的解析。上面的示例使用了json.loads这是最规范的方式。确保你的工具描述中明确要求输入是JSON能引导LLM生成正确的格式。5. 构建复杂智能体多智能体协作与记忆管理单个智能体能力有限复杂的任务可能需要多个智能体分工合作。同时让智能体记住之前的对话是实现连贯交互的关键。5.1 实现一个路由智能体Router智能体可以将问题分配给不同的专家智能体处理。例如我们构建一个系统包含一个“天气专家”和一个“库存专家”。from qwen_agent.agent import Router, Assistant from qwen_agent.tools import AmapWeather from .product_inventory_tool import ProductInventoryTool # 假设我们之前定义的工具在这个模块 # 1. 创建专家智能体 weather_agent Assistant( llm{model: qwen-plus, model_server: dashscope}, function_list[AmapWeather()], name天气专家, description专门回答与天气、气候相关的问题。 ) inventory_agent Assistant( llm{model: qwen-plus, model_server: dashscope}, function_list[ProductInventoryTool()], name库存专家, description专门处理产品库存查询和管理相关的问题。 ) # 2. 创建路由智能体 router Router( llm{model: qwen-max, model_server: dashscope}, # 路由模型可以用能力更强的 agents[weather_agent, inventory_agent], # system_message 可以指导路由逻辑例如 system_message你是一个总调度员。根据用户问题决定将其分配给哪个专家处理。如果问题同时涉及多个领域请协调多个专家共同回答。 ) # 3. 使用路由智能体 messages [{role: user, content: 北京天气如何另外产品P1001库存够吗}] final_response list(router.run(messagesmessages))[-1][content] print(final_response)在这个例子中Router会根据用户问题先判断属于哪个领域然后将问题和必要的上下文转发给对应的Assistant。Assistant处理完后结果会返回给RouterRouter可能会整合多个专家的回答再返回给用户。路由策略的定制默认的路由逻辑由Router的LLM决定。你可以通过更精细的system_message来引导或者继承Router类重写其决策逻辑实现基于规则如关键词匹配的路由。5.2 短期记忆与长期记忆的实现短期记忆由Agent基类自动维护的messages列表就是短期记忆。它记录了当前会话中的所有对话轮次、工具调用和结果。这是智能体理解当前对话上下文的基础。长期记忆Qwen-Agent提供了Storage工具qwen_agent.tools.storage它是一个简单的键值存储可以用于保存跨会话的信息。例如记住用户的偏好。from qwen_agent.tools import Storage # 初始化存储工具 storage Storage() # 在智能体中使用 agent Assistant( llm{model: qwen-max, model_server: dashscope}, function_list[storage], # 将存储工具也作为功能提供给LLM system_message你可以使用存储工具记住用户的信息。 ) # 假设用户说“记住我最喜欢的颜色是蓝色。” # LLM可能会调用 storage.call({set: {key: user_favorite_color, value: blue}}) # 之后用户问“我喜欢什么颜色” # LLM可能会调用 storage.call({get: {key: user_favorite_color}})更复杂的记忆系统对于需要向量检索的记忆如记住大量过去的对话片段Qwen-Agent没有直接提供内置方案。但你可以轻松集成创建一个自定义工具这个工具内部封装了一个向量数据库如Chroma、Milvus的客户端。当用户提问时LLM可以调用这个“记忆检索”工具传入问题工具返回相关的历史片段LLM再基于这些片段生成回答。这就实现了类似“记忆库”的功能。5.3 处理多轮对话与状态保持智能体的run方法默认会维护一个内部状态包括messages历史。如果你是在一个Web服务器或聊天机器人中使用你需要为每个用户/会话维护一个独立的Agent实例或者至少维护其messages历史。class ChatSession: def __init__(self, session_id): self.session_id session_id self.agent Assistant(llm..., function_list...) self.history [] # 或者直接使用 agent.messages def chat(self, user_input): # 将用户输入追加到历史 self.history.append({role: user, content: user_input}) # 运行智能体传入完整历史 responses [] for rsp in self.agent.run(messagesself.history): responses.append(rsp) # 如果是流式可以在这里实时推送部分结果给前端 if content in rsp and rsp[content]: # yield rsp[content] # 用于流式响应 pass # 更新历史记录agent.run 返回的最后一个元素通常包含更新后的完整消息列表 if responses: self.history responses[-1] # 注意这里需要根据实际情况调整run的返回值需要处理 # 返回最终答案 final_content responses[-1][content] if responses else return final_content实操心得管理会话状态时要注意messages列表的长度。Qwen模型支持超长上下文如128K但过长的历史仍然会增加计算成本和延迟。一种常见的策略是“摘要式记忆”当对话轮次过多时让LLM对之前的对话历史生成一个简短的摘要然后用这个摘要替换掉大部分旧消息只保留最近几轮完整对话。这需要在run方法前后加入自定义逻辑。6. 部署与实战打造一个交互式Web应用框架再好最终要能交付给用户使用。Qwen-Agent贴心地提供了一个基于Gradio的Web UI示例我们可以基于它快速搭建一个演示或产品原型。6.1 基于Gradio快速搭建Web UI项目根目录下的examples文件夹里有现成的Web UI示例web_demo.py。我们可以以此为基础进行定制。# 假设我们创建一个 custom_web_demo.py import gradio as gr from qwen_agent.agent import Assistant from qwen_agent.tools import CodeInterpreter, AmapWeather from my_tools import ProductInventoryTool # 导入自定义工具 # 1. 初始化智能体这里用更复杂的配置 tools [ AmapWeather(), CodeInterpreter(), ProductInventoryTool() ] agent Assistant( llm{model: qwen-max, model_server: dashscope}, function_listtools, system_message你是一个全能助手可以查询天气、分析数据、查询库存。请根据用户问题选择合适的工具。, files[] # 可以初始化上传文件列表 ) # 2. 定义Gradio处理函数 def respond(message, history, files): :param message: 用户当前输入 :param history: 对话历史格式为Gradio期望的列表 [[user_msg1, bot_msg1], ...] :param files: 用户上传的文件路径列表 :return: 更新后的历史 # 将Gradio历史格式转换为Agent需要的messages格式 messages [] for h in history: messages.append({role: user, content: h[0]}) messages.append({role: assistant, content: h[1]}) messages.append({role: user, content: message}) # 如果有文件需要将文件信息也加入到消息中Qwen-Agent支持多模态消息 if files: # 注意这里需要根据框架要求构造文件消息可能需要使用 llm.chat 接口的特定格式 # 简化处理将文件路径作为文本内容的一部分或使用框架提供的文件处理工具 # 更标准的做法是使用 agent._call_llm 或直接处理文件上传 pass # 运行智能体 response_text for rsp in agent.run(messagesmessages): if content in rsp and rsp[content]: response_text rsp[content] # 如果是流式可以在这里yield部分更新实现打字机效果 # 这里简化等所有内容生成完再返回 # 更新Gradio历史 history.append([message, response_text]) return history, # 返回更新后的历史和清空输入框 # 3. 构建Gradio界面 with gr.Blocks(title我的Qwen智能体助手) as demo: gr.Markdown(# 我的Qwen智能体助手) chatbot gr.Chatbot(label对话历史, height500) msg gr.Textbox(label输入你的问题, placeholder例如北京天气怎么样, lines2) file gr.File(label上传文件可选, file_countmultiple) submit_btn gr.Button(发送) # 绑定事件 submit_btn.click(fnrespond, inputs[msg, chatbot, file], outputs[chatbot, msg]) msg.submit(fnrespond, inputs[msg, chatbot, file], outputs[chatbot, msg]) # 支持回车发送 # 4. 启动 if __name__ __main__: demo.launch(server_name0.0.0.0, server_port7860, shareFalse) # shareTrue可生成临时公网链接运行这个脚本访问http://localhost:7860就能看到一个简单的聊天界面。你可以输入文本智能体会调用相应的工具来回答。6.2 处理文件上传与多模态输入上面的示例简化了文件处理。Qwen-Agent的LLM层支持多模态消息。一个完整的消息可以包含文本和文件列表。在Web Demo中我们需要将用户上传的文件进行处理。更接近官方示例的做法是利用框架内置的multimodal能力。你需要检查qwen_agent.llm是否支持多模态模型如qwen-vl-max并将文件路径或base64编码的图像数据构造到messages中。# 假设处理上传的图片 from qwen_agent.llm import get_chat_model llm get_chat_model(modelqwen-vl-max, model_serverdashscope) def build_messages_with_files(text_input, file_paths): messages [{role: user, content: []}] # 添加文本部分 if text_input: messages[0][content].append({text: text_input}) # 添加文件部分 for fp in file_paths: if fp.lower().endswith((.png, .jpg, .jpeg, .gif, .bmp)): # 图片文件可以读取为base64或直接传路径取决于模型服务要求 # 这里以传本地路径为例DashScope的某些模型支持 messages[0][content].append({image: fp}) elif fp.lower().endswith((.pdf, .docx, .txt)): # 文档文件Qwen-Agent有专门的文档处理工具也可以尝试直接让多模态模型读取 # 更常见的做法是先用文档工具提取文本再将文本放入content messages[0][content].append({document: fp}) return messages # 然后在respond函数中使用 llm.chat(build_messages_with_files(...)) 来获取响应关键点文件上传后需要先保存到服务器临时目录然后将文件路径传递给智能体。对于文档通常先用DocParser工具如果内置提取纯文本再将文本交给LLM。对于图像可以直接传递给支持视觉的模型。6.3 性能优化与生产部署考量当从Demo走向生产环境时需要考虑以下几点模型服务部署云端API使用DashScope最简单但需考虑网络延迟、成本和对数据的控制力。本地部署使用Qwen2.5的开源模型通过vLLM、TGI等高性能推理框架部署提供兼容OpenAI的API。将model_server指向你的本地服务地址。这能获得更好的数据隐私和可控性。智能体实例管理无状态与池化在Web服务中不要为每个请求都创建一个新的Agent实例初始化LLM和工具开销大。可以考虑使用连接池或单例模式管理智能体但要注意线程安全和会话隔离。每个用户的对话历史messages需要单独存储如Redis、数据库。工具调用的超时与重试工具调用尤其是网络请求、代码执行可能失败或超时。在生产代码中需要对tool.call()进行异常捕获、超时设置和重试机制。流式输出优化agent.run()是流式生成器要充分利用这一点实现“打字机”效果提升用户体验。在Gradio中可以使用gr.Chatbot的stream模式或者使用yield逐步返回内容。日志与监控记录所有用户交互、工具调用和模型响应用于分析智能体行为、发现错误和优化提示词。特别是工具调用的输入输出是调试的宝贵资料。7. 避坑指南与常见问题排查在实际使用中你一定会遇到各种问题。以下是我踩过的一些坑和解决方案。7.1 工具调用失败原因与调试方法问题现象LLM生成了工具调用请求但执行失败或返回错误。排查步骤检查工具参数首先打印出LLM生成的arguments字符串。它是否符合工具期望的JSON格式字段名和类型是否正确# 在自定义工具的call方法开头添加日志 def call(self, params: str): print(f[Tool {self.name}] Received params: {params}) # 关键日志 try: args json.loads(params) ...检查工具初始化工具所需的API密钥、依赖库、外部服务是否都就绪例如AmapWeather需要高德TokenWebExtractor需要playwright浏览器驱动。检查网络与权限如果工具需要访问外部API或网络资源确保运行环境有网络权限且没有防火墙阻挡。模拟调用脱离智能体框架直接用预期的参数手动调用工具的call方法看是否能成功。这能快速定位是工具本身的问题还是LLM生成参数的问题。优化工具描述如果LLM总是生成错误的参数很可能是description描述不够清晰。用更结构化、更示例化的语言重写描述。7.2 模型不调用工具或错误调用工具问题现象用户的问题明明应该调用工具但模型直接给出了文本回答或者调用了错误的工具。解决方案强化系统提示词在system_message中明确指令。例如“你是一个必须使用工具来回答问题的助手。对于天气查询你必须调用amap_weather工具对于数据计算你必须调用code_interpreter工具。不要试图自己猜测答案。”提供少量示例在对话历史中提供一两个“用户提问-助手调用工具”的示例Few-shot Learning引导模型学习正确的行为。检查工具列表确保function_list中包含了正确的工具实例。模型能力尝试更换更强或更新的模型。Qwen2.5-72B-Instruct在工具调用上的表现通常远好于较小的7B模型。7.3 处理长上下文与性能瓶颈问题随着对话轮次增多messages历史越来越长每次调用LLM的token数暴涨导致响应变慢、成本增加。策略历史摘要定期如每10轮对话后让LLM对之前的对话历史生成一个简短的摘要。然后用这个摘要替换掉旧的历史消息只保留最近2-3轮完整对话。这需要自定义Agent的run方法逻辑。选择性记忆只将与当前任务高度相关的历史片段保留在上下文中。可以结合向量检索在每次调用LLM前从历史库中检索最相关的几条记录加入上下文。使用支持长上下文的模型Qwen2.5系列支持128K上下文这已经能覆盖很长的对话。但即使如此过长的上下文仍会影响推理速度。流式处理优化对于代码解释器这类可能产生大量输出如图表、文件的工具考虑让工具将大输出保存为文件然后只返回文件链接或摘要给LLM避免污染主要对话上下文。7.4 安全性与风险控制智能体开放了代码执行、网络访问等能力安全至关重要。代码解释器沙箱确保CodeInterpreter运行在隔离的容器或严格限制权限的进程中。限制可执行的语言、可导入的模块、运行时间和内存使用。禁止访问敏感目录和网络除非必要。工具权限最小化每个自定义工具只赋予完成其功能所需的最小权限。例如一个查询数据库的工具不应该有删除表的权限。对用户输入进行严格的验证和清洗防止SQL注入、命令注入等攻击。内容过滤在LLM调用前后对用户输入和模型输出进行内容安全过滤防止生成有害或不当内容。阿里云DashScope API本身提供了一定的安全过滤但自己部署模型时需要额外注意。用户认证与授权在Web应用中确保只有授权用户才能访问智能体并且不同用户的工具权限和数据访问范围可能不同。7.5 依赖与版本兼容性问题Qwen-Agent依赖较多且更新较快。锁定版本在生产环境中使用requirements.txt或poetry严格锁定所有依赖的版本避免因自动升级导致的不兼容。关注更新日志升级前仔细阅读GitHub仓库的Release Notes了解破坏性变更。虚拟环境隔离为每个项目创建独立的Python虚拟环境避免全局包冲突。最后遇到问题时第一站应该是项目的GitHub Issues页面很多常见问题已经有讨论和解决方案。积极参与社区分享你的使用经验和自定义工具是推动项目和你自己共同成长的好方法。智能体的世界刚刚打开Qwen-Agent提供了一个坚实而灵活的起点剩下的就看你的想象力了。