1个主控调度+3个Agent:基于Qwen的多智能体编排实战解析

发布时间:2026/8/31 10:06:38
1个主控调度+3个Agent:基于Qwen的多智能体编排实战解析 之前调研多智能体编排方案时发现很多资料只讲单 Agent 的提示词工程真正能把 1 个主控调度器 多个 Agent 串起来并且换成国内可用的大模型接口的教程却很少。这篇文章会完整拆解一套轻量级多智能体系统1 个主控调度器统一管理任务3 个 Agent 分别承担检索、编码、报告生成底层统一使用 Qwen 通义千问的 OpenAI 兼容接口。你可以直接照着搭建也能从代码中理解多智能体系统为什么一定要有主控调度层以及 Agent 之间如何共享上下文、如何避免死循环。1. 背景与核心概念1.1 什么是 Hermes AgentHermes Agent 可以理解为一套“智能体编排框架”或“多智能体运行环境”。它通常负责管理多个职责不同的 Agent让它们在一个共享任务上下文里协同完成复杂任务。不同发布渠道的 Hermes Agent 项目可能形态不同有的是桌面客户端有的是 Python 服务但核心逻辑基本一致注册 Agent、配置模型、主控调度、汇总结果。有些开发者第一次接触这个概念时会想直接用一个大模型不就行了吗为什么要引入“多智能体”这需要从任务复杂度来看。单次问答可以直接交给大模型但真实业务往往包含多个环节。比如“写一个 Python 脚本并整理使用说明”如果只调用一次 Qwen模型可能直接给出代码但缺少检索、校验、格式整理这些环节如果用多个 Agent 分开处理每个 Agent 专注一个子任务主控调度器再把结果串联起来生成质量会稳定很多。本文不绑定某个特定客户端版本而是把核心的多智能体主控调度原理拆出来用 Python 实现一个可运行的最小系统再结合 Qwen 通义千问完成真实调用。这样无论你后续使用什么客户端、什么框架都能迁移理解。1.2 为什么需要主控调度多智能体不是简单把多个模型堆在一起。如果把多个 Agent 直接并联每个 Agent 各说各话最终用户会得到一堆互相冲突的答案如果让 Agent 之间任意互相调用又没有人负责流程控制任务可能陷入循环。主控调度器承担了四件事第一任务分解。用户输入一个大任务后主控调度器需要判断这个任务应该拆成几个步骤先做什么、后做什么。第二Agent 选择。不同 Agent 有不同的提示词和职责边界主控调度器要根据任务类型决定调用哪个 Agent。第三结果拼接。每个 Agent 的输出不能是孤立的主控调度器要把前一个 Agent 的结果拼到上下文里传给下一个 Agent保证信息连续。第四循环控制。主控调度器需要设置最大轮数或终止条件避免 Agent 反复生成相似内容也避免调度链路陷入死循环。所以主控调度器更像是一个“项目负责人”而不是简单的中转站。它决定流程、传递上下文、兜底异常。1.3 多智能体的四种典型交互模式多智能体系统常见的交互模式可以分成四类主从模式Master-Slave主控调度器统一分配任务多个 Agent 各自执行后回报结果。适合业务流程明确、步骤固定的场景。合作模式Collaborative多个 Agent 地位对等各自负责一个子任务最后汇总结果。适合并行处理、任务相互独立的情况。辩论模式Debate多个 Agent 针对同一问题分别给出意见主控或仲裁者综合判断。适合方案评审、风险分析。流水线模式Pipeline每个 Agent 只处理上一环节的输出形成串行流水线。适合代码生成、报告生成等强前后依赖的任务。本文实现的系统采用“主从 流水线”的混合形态。主控调度器统一下发任务三个 Agent 按 search → code → report 的顺序串行协作每个 Agent 都能看到前一个 Agent 的输出。这种方式既容易理解也方便扩展成真实项目。2. 系统架构1 个主控调度 3 个 Agent2.1 整体架构整个系统可以用一个简单的表格表示角色名称职责主控调度Orchestrator接收任务、拆分流程、调用 Agent、拼接上下文Agent 1SearchAgent检索计划、提取关键词、整理素材Agent 2CodeAgent根据素材生成 Python 代码和解释Agent 3ReportAgent将代码和说明整理成结构化中文报告任务流转是用户任务输入主控调度器主控调度器按计划依次调用 SearchAgent、CodeAgent、ReportAgent每个 Agent 的输入都包含原始任务和前面 Agent 的输出上下文最终把三个结果汇总返回。这里要注意Agent 与主控调度器之间并不是网络服务的关系而是内存中的对象调用关系。每个 Agent 都持有同一个 LLM 客户端也就是 Qwen 的 OpenAI 兼容客户端所以生产者是同一个大模型但提示词上下文不同输出侧重点也不同。2.2 三个 Agent 的角色划分先看 SearchAgent。它的提示词要求它只做信息检索规划输出检索关键词、需要查阅的资料范围不需要写代码也不需要做最终报告。这样设计的目的是把“探索”和“实现”分离避免模型在一个 Agent 里既做调研又写代码导致输出结构混乱。再看 CodeAgent。它的提示词要求它根据 SearchAgent 给出的素材编写可运行代码同时补充运行说明。这个 Agent 只关注代码质量和可运行性不做资料扩展。最后是 ReportAgent。它的提示词要求它把 CodeAgent 输整理为结构化中文报告包含背景、代码、运行方式、注意事项。这样最终输出是一份可以直接交给项目成员查看的文档而不是一段零散对话。三个 Agent 的职责边界非常清晰检索不写代码代码不写报告报告不重新调研。这种边界设计正是多智能体优于单 Agent 大 prompt 的原因。2.3 一次任务调度的完整链路当用户输入“用 Python 编写一个计算斐波那契数列的程序并给出运行说明”时主控调度器会执行以下步骤第一步接收原始任务初始化一个空字符串 context。第二步调用 SearchAgent得到的输出追加到 context。第三步调用 CodeAgentCodeAgent 的输入是“原始任务 SearchAgent 输出”输出结果继续追加到 context。第四步调用 ReportAgent输入是“原始任务 前面所有输出”最终输出报告。第五步主控调度器把三个 Agent 的结果以字典形式返回。如果某一轮 Agent 调用失败主控调度器可以根据异常类型决定重试或者跳过。如果超过最大调度轮数主控调度器会强制结束防止死循环。这段逻辑看似简单却是多智能体系统稳定运行的关键。3. 环境准备与依赖安装3.1 运行环境与版本本文示例环境以 macOS 和 Linux 为主Windows 同样支持。Python 版本建议使用 3.9 或以上因为代码中使用了类型注解和 dataclass 特性Python 3.8 部分兼容但强烈建议用 3.10。版本不需要完全照搬如果你的项目已经使用其他版本只要满足基础要求即可。后续代码中会用到 openai 包作为 OpenAI 兼容客户端建议版本不低于 1.30.0因为旧版本的 OpenAI 包对 base_url 和 chat.completions 的支持不够稳定。3.2 获取 Qwen 的 API Key要调用 Qwen 通义千问需要先开通阿里云百炼服务或对应的 DashScope 服务然后在控制台创建 API Key。创建完成后推荐使用环境变量保存export QWEN_API_KEY你的 API Key不建议把 Key 直接写在代码里否则代码一旦上传到 Git 仓库就会泄露。在后面的 Python 代码中我们会从环境变量读取 API Key并允许通过.env文件覆盖。如果你使用的是 Hermes Agent 桌面客户端或命令行版本通常也需要在设置页面或配置文件中填入 API Key。不同客户端入口不一样但原则相同不要写死在业务代码中。3.3 安装 Python 依赖创建项目后在项目根目录添加requirements.txtopenai1.30.0 pymilvus2.4.0 python-dotenv1.0.0执行安装命令pip install -r requirements.txt其中 openai 包用于调用兼容接口pymilvus 是可选的只有在扩展知识库时才需要python-dotenv 用于加载.env文件。如果你不需要知识库功能可以先不安装 pymilvus。安装完成后可以通过一个小命令验证环境是否正常python -c from openai import OpenAI; print(OpenAI SDK OK)如果输出正常说明依赖安装成功。4. 实战落地主控调度器与三个 Agent 的代码实现4.1 项目目录结构为了让代码清晰我们采用下面的项目结构hermes-multiagent/ ├── config/ │ ├── __init__.py │ └── settings.py ├── core/ │ ├── __init__.py │ ├── llm_client.py │ └── orchestrator.py ├── agents/ │ ├── __init__.py │ ├── base_agent.py │ ├── search_agent.py │ ├── code_agent.py │ └── report_agent.py ├── main.py ├── .env.example └── requirements.txtconfig 目录放配置管理代码core 目录放 LLM 客户端和主控调度器agents 目录放三个 Agent 和抽象基类。main.py 是入口文件。这种分层设计适合后续扩展如果你想增加第四个 Agent只需要在 agents 目录新增一个类再在 main.py 中注册即可。4.2 配置模块先创建config/settings.py用于统一读取环境变量# 文件路径config/settings.py import os from dataclasses import dataclass dataclass class Settings: qwen_api_key: str os.getenv(QWEN_API_KEY, ) qwen_base_url: str os.getenv( QWEN_BASE_URL, https://dashscope.aliyuncs.com/compatible-mode/v1 ) qwen_model: str os.getenv(QWEN_MODEL, qwen-plus) max_retry: int int(os.getenv(QWEN_MAX_RETRY, 3))这段代码的作用是从环境变量读取 Qwen 的 API Key、Base URL、模型名称和最大重试次数。其中 Base URL 使用了 DashScope 的 OpenAI 兼容地址这样我们后续可以直接用 openai 包调用 Qwen。如果你使用的是 Qwen 本地部署版本例如通过 vLLM 或 Ollama 启动的 OpenAI 兼容服务只需要修改QWEN_BASE_URL和QWEN_MODEL两个环境变量即可核心代码不需要改动。这也是使用 OpenAI 兼容协议的好处。4.3 基于 Qwen 的 LLM 客户端接下来创建core/llm_client.py# 文件路径core/llm_client.py from openai import OpenAI class QwenClient: def __init__(self, api_key: str, base_url: str, model: str): self.model model self.client OpenAI(api_keyapi_key, base_urlbase_url) def chat(self, system_prompt: str, user_prompt: str, temperature: float 0.3) - str: resp self.client.chat.completions.create( modelself.model, messages[ {role: system, content: system_prompt}, {role: user, content: user_prompt} ], temperaturetemperature ) return resp.choices[0].message.contentQwenClient 非常简单只有一个 chat 方法。它接收 system_prompt 和 user_prompt返回模型生成的文本。之所以要封装一层是因为后续如果要从 Qwen 切换到其他模型只需要替换这一个类。temperature 参数控制随机性。Agent 类任务建议使用 0.3 左右既保证稳定性又不至于输出完全重复。如果是创意生成任务可以调到 0.7 以上。4.4 基础 Agent 抽象类所有 Agent 都应该有统一的接口所以先写一个抽象基类。创建agents/base_agent.py# 文件路径agents/base_agent.py from abc import ABC, abstractmethod class BaseAgent(ABC): def __init__(self, name: str, llm_client): self.name name self.llm_client llm_client property def system_prompt(self) - str: return 你是一个通用智能体。 abstractmethod def handle(self, task: str) - str: pass def chat(self, task: str) - str: return self.llm_client.chat(self.system_prompt, task)这里的 BaseAgent 做了两件事一是定义统一的handle接口让主控调度器可以多态调用任意 Agent二是提供了默认的chat方法子类只需要重写system_prompt和handle即可。为什么要有抽象类因为主控调度器不应该关心具体 Agent 的内部逻辑它只需要知道每个 Agent 都有一个handle(task: str) - str方法。这种面向接口的写法也让测试变得更加方便你可以轻松替换任何一个 Agent 的实现。4.5 三个具体 Agent创建agents/search_agent.py# 文件路径agents/search_agent.py from agents.base_agent import BaseAgent class SearchAgent(BaseAgent): property def system_prompt(self) - str: return ( 你是一个信息检索专家。你的任务是根据用户需求生成检索计划 包括检索关键词、需要查阅的资料类型、可能的信息来源维度。 不要编写代码不要写最终报告只输出信息检索层面的结构化内容。 ) def handle(self, task: str) - str: return self.chat(task)创建agents/code_agent.py# 文件路径agents/code_agent.py from agents.base_agent import BaseAgent class CodeAgent(BaseAgent): property def system_prompt(self) - str: return ( 你是一个资深 Python 开发工程师。请根据已有素材编写可直接运行的 Python 代码 并说明运行环境、依赖、执行步骤。代码必须考虑异常输入给出必要注释。 不要做过多背景调研重点放在代码实现和可运行性上。 ) def handle(self, task: str) - str: return self.chat(task)创建agents/report_agent.py# 文件路径agents/report_agent.py from agents.base_agent import BaseAgent class ReportAgent(BaseAgent): property def system_prompt(self) - str: return ( 你是一个技术文档工程师。请把前面的检索结果和代码内容整理成结构化中文报告 报告包含需求背景、技术方案、完整代码、运行说明、注意事项。 语言要精炼结构要清晰不要编造不存在的功能。 ) def handle(self, task: str) - str: return self.chat(task)三个 Agent 的差异完全体现在 system_prompt 上。搜索 Agent 聚焦检索规划代码 Agent 聚焦代码实现报告 Agent 聚焦最终输出。这种设计让同一个 Qwen 模型在不同的提示词约束下表现出不同角色。4.6 主控调度器现在写最核心的主控调度器。创建core/orchestrator.py# 文件路径core/orchestrator.py from typing import Dict, List class Orchestrator: def __init__(self, agents: Dict[str, object], max_round: int 3): self.agents agents self.max_round max_round def dispatch(self, task: str, plan: List[str]) - Dict[str, str]: context results {} round_count 0 for agent_name in plan: round_count 1 if round_count self.max_round: print(达到最大调度轮数提前结束。) break agent self.agents.get(agent_name) if not agent: continue agent_task self._compose_task(task, context) output agent.handle(agent_task) results[agent_name] output context f\n[{agent_name} 的输出]\n{output}\n return results def _compose_task(self, original_task: str, context: str) - str: if not context: return original_task return ( f原始任务{original_task}\n\n f前面 Agent 已经生成的内容\n{context}\n\n 请基于这些内容继续处理不要重复已经生成的结论。 )主控调度器的核心是 dispatch 方法。它按 plan 列表逐个调用 Agent并把每个 Agent 的输出追加到 context 中。由于 context 会不断变长主控调度器需要在真实项目中限制 context 长度否则会把上下文撑爆。_ compose_task 方法也很关键。如果 context 为空直接返回原始任务如果已经有多轮输出就拼接原始任务和已有上下文并加一句“不要重复已经生成的结论”这能有效降低重复输出概率。4.7 主入口与运行效果创建main.py# 文件路径main.py import sys from agents.code_agent import CodeAgent from agents.report_agent import ReportAgent from agents.search_agent import SearchAgent from config.settings import Settings from core.llm_client import QwenClient from core.orchestrator import Orchestrator def main(): settings Settings() llm QwenClient( api_keysettings.qwen_api_key, base_urlsettings.qwen_base_url, modelsettings.qwen_model ) agents { search: SearchAgent(search, llm), code: CodeAgent(code, llm), report: ReportAgent(report, llm), } orchestrator Orchestrator(agents, max_round3) task sys.argv[1] if len(sys.argv) 1 else \ 用 Python 编写一个计算斐波那契数列的程序并给出运行说明。 plan [search, code, report] result orchestrator.dispatch(task, plan) print(\n 多智能体执行结果 ) for agent_name, output in result.items(): print(f\n--- {agent_name} ---) print(output) if __name__ __main__: main()运行项目export QWEN_API_KEY你的 API Key python main.py 用 Python 编写一个计算斐波那契数列的程序并给出运行说明。预期输出会分成三段。第一段是 SearchAgent 生成的检索计划包括关键词、资料类型第二段是 CodeAgent 生成的 Python 代码和运行说明第三段是 ReportAgent 整理的结构化报告。三段内容有承接关系CodeAgent 不会重复 SearchAgent 的检索规划ReportAgent 则会把前两段内容汇总成文档。运行完成后你会看到主控调度器把三个 Agent 的输出按顺序打印出来。这就是一个最小可用的 Hermes 风格多智能体系统。5. 扩展给系统接入 Qwen Embedding 与 Milvus 知识库5.1 为什么需要外部知识库上面的系统虽然可以运行但所有知识都来自 Qwen 模型自身。真实项目中我们往往希望 Agent 能查询企业内部文档、历史项目记录或专用知识库而不是完全依赖模型记忆。这时候需要引入两个组件Embedding 模型和向量数据库。Embedding 模型把文本转换成向量向量数据库负责存储和检索。Qwen 提供了 embedding 模型Milvus 是常用的开源向量数据库。两者结合可以给 Hermes Agent 外挂一个知识库让 Agent 在回答前先检索相关文档。这个思路和 LangChain、LangChain4j 的 RAG 方案类似。如果你所在团队以 Java 为主也可以使用 langchain4j 完成类似流程底层逻辑是一样的先向量化再检索最后把检索结果作为上下文交给大模型。5.2 向量化入库以 Python 为例演示如何使用 Qwen Embedding 生成向量并存入 Milvusfrom openai import OpenAI from pymilvus import MilvusClient client OpenAI( api_key你的 API Key, base_urlhttps://dashscope.aliyuncs.com/compatible-mode/v1 ) resp client.embeddings.create( modeltext-embedding-v3, input[Hermes Agent 主控调度器的作用是什么] ) embeddings [item.embedding for item in resp.data] dim len(embeddings[0]) milvus_client MilvusClient(urihttp://localhost:19530) if not milvus_client.has_collection(hermes_kb): milvus_client.create_collection( collection_namehermes_kb, dimensiondim ) milvus_client.insert( hermes_kb, [ { id: 1, vector: embeddings[0], text: 主控调度器负责任务拆分、Agent 选择和上下文汇总。 } ] )这里有几个关键点。首先Embedding 模型用的是text-embedding-v3具体模型名称以你的 DashScope 账号可用模型为准。其次创建 Milvus collection 时维度必须和 Embedding 模型返回的向量维度一致所以上面的代码先算出了dim再创建集合。最后插入 Milvus 的字段中可以附带原始文本方便后续把检索结果映射成可读内容。实际生产环境中入库流程通常是一次性完成的离线脚本。你可以把文档切分成小块批量生成向量并写入 Milvus后续只需要维护增量更新。5.3 在调度链路中检索知识在 Agent 被调用之前主控调度器可以先做一次知识检索。具体做法是把原始任务转成向量在 Milvus 中查询最相近的几条记录然后把检索到的文本拼进 Agent 的 user_prompt。修改思路如下def retrieve_knowledge(task: str, milvus_client, embedding_client) - str: resp embedding_client.embeddings.create( modeltext-embedding-v3, input[task] ) query_vector resp.data[0].embedding search_res milvus_client.search( collection_namehermes_kb, data[query_vector], limit3, output_fields[text] ) docs [] for hits in search_res: for hit in hits: docs.append(hit[entity][text]) return \n.join(docs)检索到相关知识后主控调度器可以把这段内容追加到 context 中让后续 Agent 在回答时参考。这样既不会影响现有调度逻辑又能让 Agent 获得外部业务知识。不过要特别提醒知识库内容可能存在过期或错误Agent 在回答时必须说明信息来源不能把检索结果当成绝对真理。这也是工程实践中容易被忽略的问题。6. 常见问题与排查思路6.1 安装过程中提示需要登录不少用户在安装 Hermes Agent 客户端时遇到过“需要登录网站”的提示。这里有两种可能一种是客户端需要登录账号才能从远端获取初始配置另一种是首次安装需要获取用户授权令牌。解决思路很简单按提示完成一次登录一般是为了生成 token。这个 token 会保存在本地配置文件中后续启动不需要重复登录。如果你希望完全离线使用可以手动申请 API Key然后把 Key 填入配置不依赖账号登录。6.2 API Key 修改与鉴权失败调用 Qwen 时报 401 错误时常见原因有三个API Key 不正确、Base URL 不正确、模型名称不存在。排查顺序建议先检查代码读取的 API Key 是否来自环境变量再确认 Base URL 是否包含/compatible-mode/v1后缀最后确认模型名是否在百炼控制台可见。如果你用的是 Hermes Agent 客户端一般情况下在设置页面的“模型配置”或“API Key”入口修改即可。修改后必须重启相关服务。6.3 Qwen 输出死循环或重复内容多智能体系统最容易出现的问题就是“输出死循环”。所谓死循环是指 Agent 反复输出相似内容或者主控调度器反复调用同一个 Agent形成闭环。根本原因通常是缺少最大轮数限制或者提示词没有明确“不要重复”。解法是给主控调度器增加 max_round同时在 context 拼接时明确提示“基于已有内容继续不要重复”。如果模型仍然重复可以在调用层增加去重逻辑比较两个相邻输出的相似度。超过阈值就停止当前 Agent 的执行把已有结果交给下一环节。这个策略能显著提升稳定性。6.4 Mac 与 Kali 等环境安装差异Mac 用户安装依赖时需要注意 Python 版本和 openai 版本。如果你使用的是 Apple Silicon建议使用 Python 3.10并避免使用过旧的 torch 或 numpy 版本。Kali 等 Linux 发行版的问题通常出现在系统自带 Python 与 pip 版本冲突上。建议先清理系统 Python 干扰或者使用虚拟环境。无论如何不要在命令行使用 sudo pip install 向系统 Python 安装依赖这会污染系统环境。推荐使用 venvpython3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt这样能隔离所有依赖避免环境冲突。7. 最佳实践与工程建议7.1 主控调度器要有“兜底”主控调度器是系统的负责人但它不是神。Agent 可能返回空内容模型可能超时网络可能抖动。因此调度器必须有兜底逻辑超过最大重试次数就放弃当前 Agent或者返回一个降级结果而不是让整个流程挂死。建议在每次调用 Agent 时使用 try-except 包裹记录错误日志并判断是否重试。如果连续失败可以跳过该 Agent把失败信息记录到结果中供后续人工查看。7.2 合理设计任务上下文上下文越长token 成本和模型推理耗时越高。在多智能体系统中每个 Agent 都看到完整历史会让上下文快速增长。建议在真实项目中做摘要压缩每完成一个 Agent让主控调度器调用一次轻量模型对历史输出做摘要而不是原样拼接。另一个方法是只传递上一层 Agent 的输出而不是全量历史。在流水线模式中很多场景只需要上一环节的结果并不需要最早的任务描述。主控调度器应该支持配置“可见范围”避免上下文无限膨胀。7.3 日志、限流与费用控制多智能体系统会成倍放大 API 调用次数。一次任务如果调用三个 Agent就是三次大模型请求如果每个 Agent 内部还要做多次重试费用会快速上升。生产环境建议记录每次调用的模型、token 数、耗时和错误码按天统计费用。同时要做好并发限流避免多个用户同时触发调度任务时把 API 额度打满。使用 Qwen 时尤其要注意不同模型的限流阈值和价格差异qwen-plus 和 qwen-max 的成本不同选择要谨慎。7.4 生产环境的安全边界多智能体系统一旦接入了外部工具或知识库就必须考虑权限边界。Agent 只能访问它被授权的数据不能因为提示词被绕过就读取敏感文件。如果某个 Agent 负责执行动态生成的代码应该把执行环境放到沙箱容器中。另外API Key 必须使用环境变量或密钥管理服务保存严禁写入日志、提交到 Git。知识库中的文档也要分级管理不要让低权限的 Agent 检索到高权限内容。安全原则是宁可功能少一些也不能放开越权风险。到这里一套完整的 Hermes Agent 风格多智能体系统已经搭建完成。你可以把这段代码作为雏形逐步扩展自己的 Agent 列表、接入真实知识库、增加可视化界面。多智能体系统的核心不是某一个模型有多强而是主控调度器能不能把多个角色的工作有条理地串起来。