用 Python 从零搭一个多智能体系统:从 Mock 闭环到真实 LLM 接入
- 原文:Building a Multi-Agent System in Python | Towards Data Science
- 原文链接:https://towardsdatascience.com/building-a-multi-agent-system-in-python/
- 作者:Mahnoor Javed
- 发布时间:2026-06-07
- 整理类型:实战教程
- 适用读者:会基础 Python,想理解“多 Agent 系统”如何落到代码里的开发者
0. 这篇教程解决什么问题
原文用一个旅游规划系统讲解多智能体系统:把“制定旅行计划”拆成多个专家角色,再让它们按顺序协作。
本文在保留原文思路的基础上,做成一个更适合实战落地的版本:
- 先写一个完全离线可运行的 Mock 版本,不用 API Key,先验证流程。
- 再抽象出统一的
Agent类和顺序工作流。 - 最后给出接入 OpenAI/OpenRouter 兼容接口的改造方式。
- 补上原文没有强调的安全边界:API Key、结构化输出、超时、失败处理、幻觉风险。
核心原则:不要一开始就接真实模型。先让多 Agent 流程在本地闭环跑通,再替换模型后端。
1. 多智能体系统到底是什么
多智能体系统(Multi-Agent System, MAS)不是神秘框架。最小可用版本可以理解为:
把一个复杂任务拆成多个角色,每个角色只负责一段清晰职责,然后把它们串起来完成最终目标。
以旅行规划为例,如果只用一个 Agent,它要同时负责:
- 查目的地信息
- 安排行程
- 估算预算
- 输出最终计划
这会让 prompt 变长、职责混乱,也不方便排查错误。
拆成多个 Agent 后,可以变成:
ResearchAgent:只做目的地研究。ActivityAgent:只做每日活动安排。BudgetAgent:只做费用估算。FinalAgent:只做最终整合。
最简单的协作方式是顺序链:
用户需求 → ResearchAgent → ActivityAgent → BudgetAgent → FinalAgent → 最终行程这不是最复杂的多 Agent 架构,但它是最容易跑通、最容易调试、最适合初学者的起点。
2. 项目结构
新建目录:
mkdir python-multi-agent-demo
cd python-multi-agent-demo
mkdir src最终结构:
python-multi-agent-demo/
├── src/
│ ├── mock_agents.py
│ ├── workflow.py
│ └── openai_backend.py
└── .env.example先不要安装任何第三方依赖。第一版只用 Python 标准库。
3. 第一版:离线 Mock 多 Agent 闭环
先写一个不调用真实模型的版本,验证“角色拆分 + 顺序传递”是否成立。
创建 src/mock_agents.py:
from dataclasses import dataclass
@dataclass
class AgentResult:
agent_name: str
output: str
@dataclass
class MockAgent:
name: str
role: str
def run(self, task: str) -> AgentResult:
if self.name == "Research Agent":
output = (
"目的地研究:伊斯坦布尔适合亲子游。推荐圣索菲亚大教堂、"
"托普卡帕宫、伊斯坦布尔水族馆、KidZania 和博斯普鲁斯海峡游船。"
)
elif self.name == "Activity Planner Agent":
output = (
"活动安排:第 1 天游览历史城区;第 2 天安排水族馆和 KidZania;"
"第 3 天安排公园、博物馆和集市。"
)
elif self.name == "Budget Agent":
output = (
"预算估算:4 人 3 天游约 3100-3800 美元,包含机票、住宿、餐饮、"
"交通、门票和杂费。"
)
else:
output = f"最终整合:\n{task}\n请根据以上信息生成一份简洁行程。"
return AgentResult(agent_name=self.name, output=output)再创建 src/workflow.py:
from dataclasses import dataclass
from mock_agents import MockAgent
@dataclass
class TravelRequest:
origin: str
destination: str
days: int
travelers: int
budget: str
interests: str
def to_prompt(self) -> str:
return (
f"出发地:{self.origin}\n"
f"目的地:{self.destination}\n"
f"天数:{self.days}\n"
f"人数:{self.travelers}\n"
f"预算:{self.budget}\n"
f"偏好:{self.interests}"
)
def build_agents() -> list[MockAgent]:
return [
MockAgent("Research Agent", "研究目的地、景点和旅行提示"),
MockAgent("Activity Planner Agent", "根据研究结果安排每日活动"),
MockAgent("Budget Agent", "估算旅行预算并给出省钱建议"),
MockAgent("Final Travel Assistant", "整合所有结果,输出最终行程"),
]
def run_workflow(request: TravelRequest) -> str:
current_context = request.to_prompt()
history: list[str] = []
for agent in build_agents():
result = agent.run(current_context)
history.append(f"## {result.agent_name}\n{result.output}")
current_context = "\n\n".join(history)
return current_context
if __name__ == "__main__":
request = TravelRequest(
origin="Islamabad",
destination="Istanbul",
days=3,
travelers=4,
budget="$4000",
interests="kid friendly",
)
print(run_workflow(request))运行:
python3 src/workflow.py预期输出形态:
## Research Agent
目的地研究:...
## Activity Planner Agent
活动安排:...
## Budget Agent
预算估算:...
## Final Travel Assistant
最终整合:...如果这一步跑不通,不要急着接 LLM。先修本地流程。
4. 为什么要先做 Mock 版本
Mock 版本能验证三件事:
- 工作流是否合理:每个 Agent 的输入输出是否接得上。
- 职责边界是否清晰:有没有某个 Agent 什么都想做。
- 最终产物是否可组合:前面 Agent 的结果能不能支撑最后总结。
这一步也是生产系统常见做法:先验证控制流,再替换真实外部依赖。
如果直接接模型,错误来源会混在一起:
- 是 prompt 写错?
- 是模型输出不稳定?
- 是 API Key 错?
- 是网络超时?
- 是工作流设计不合理?
Mock 版本可以先排除大部分流程问题。
5. 第二版:把 Agent 后端抽象出来
真实系统里,Agent 不应该只能绑定一个模型。我们可以把“生成文本”的能力抽象为 backend。
创建一个更通用的结构:
from dataclasses import dataclass
from typing import Protocol
class AgentBackend(Protocol):
def generate(self, system_prompt: str, user_prompt: str) -> str:
pass
@dataclass
class Agent:
name: str
role: str
backend: AgentBackend
def run(self, task: str) -> str:
return self.backend.generate(
system_prompt=self.role,
user_prompt=task,
)这个结构的好处是:
- Mock backend 可以本地测试。
- OpenAI/OpenRouter backend 可以真实调用模型。
- 以后也能替换成本地 LLM、Gemini、Claude 或其他服务。
6. 第三版:接入 OpenAI/OpenRouter 兼容接口
如果要接真实模型,先准备 .env.example:
OPENAI_BASE_URL=https://openrouter.ai/api/v1
OPENAI_API_KEY=replace-with-your-key
OPENAI_MODEL=gpt-4.1-mini注意:
- 不要提交真实
.env。 - API Key 只能放环境变量或本地配置文件。
- 不要写进代码。
创建 src/openai_backend.py:
import os
from dataclasses import dataclass
from openai import OpenAI
@dataclass
class OpenAIBackend:
base_url: str
api_key: str
model: str
max_tokens: int = 1200
@classmethod
def from_env(cls) -> "OpenAIBackend":
base_url = os.environ.get("OPENAI_BASE_URL", "https://openrouter.ai/api/v1")
api_key = os.environ["OPENAI_API_KEY"]
model = os.environ.get("OPENAI_MODEL", "gpt-4.1-mini")
return cls(base_url=base_url, api_key=api_key, model=model)
def generate(self, system_prompt: str, user_prompt: str) -> str:
client = OpenAI(base_url=self.base_url, api_key=self.api_key)
response = client.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
],
max_tokens=self.max_tokens,
)
content = response.choices[0].message.content
if not content:
return ""
return content安装依赖:
python3 -m venv .venv
. .venv/bin/activate
pip install openai运行前设置环境变量:
export OPENAI_BASE_URL="https://openrouter.ai/api/v1"
export OPENAI_API_KEY="你的 API Key"
export OPENAI_MODEL="gpt-4.1-mini"7. 真实 LLM 版本的工作流写法
将工作流改成注入 backend:
from dataclasses import dataclass
from openai_backend import OpenAIBackend
@dataclass
class Agent:
name: str
role: str
backend: OpenAIBackend
def run(self, task: str) -> str:
print(f"{self.name} is working...")
return self.backend.generate(self.role, task)
def build_agents(backend: OpenAIBackend) -> list[Agent]:
return [
Agent(
"Research Agent",
"You are an expert travel researcher. Find attractions, hidden gems, local experiences, and travel tips.",
backend,
),
Agent(
"Activity Planner Agent",
"You are a travel activity planner. Create a day-wise plan from the research notes.",
backend,
),
Agent(
"Budget Agent",
"You estimate travel costs. Be concise and clearly mark estimates as estimates.",
backend,
),
Agent(
"Final Travel Assistant",
"You combine research, activities, and budget into a concise final itinerary.",
backend,
),
]关键点:
Research Agent不应该输出最终行程。Budget Agent不应该重新规划景点。Final Travel Assistant不应该凭空改预算事实。
每个 Agent 的 prompt 越窄,输出越容易检查。
8. 给每一步加结构化输出
原文直接让模型输出自然语言。教学可以,但真实项目最好让每个 Agent 输出结构化内容。
例如 Research Agent 可以要求输出:
{
"attractions": ["Hagia Sophia", "Topkapi Palace"],
"local_experiences": ["Bosphorus cruise"],
"family_friendly_notes": ["KidZania Istanbul is suitable for children"],
"risks": ["Opening hours and prices need real-time verification"]
}Budget Agent 可以要求输出:
{
"currency": "USD",
"estimated_total_min": 3100,
"estimated_total_max": 3800,
"assumptions": ["Flights are estimated, not real-time quotes"],
"needs_verification": ["visa fees", "hotel prices", "flight prices"]
}结构化输出的好处:
- Final Agent 更容易整合。
- Validator 更容易检查。
- 前端或 API 更容易消费。
- 后续能落库、追踪、审计。
9. 增加 Validator Agent:让系统更像真实产品
原文的系统少了一个关键角色:校验者。
旅游规划这类任务有很高的幻觉风险,尤其是:
- 签证要求
- 航班价格
- 酒店价格
- 门票价格
- 开放时间
- 地理路线是否合理
可以增加一个 Validator Agent:
Final Travel Assistant → Validator Agent → Final AnswerValidator 的职责不是重写全部内容,而是检查:
- 有没有未标注的估算。
- 有没有明显过期或无法验证的信息。
- 有没有预算加总不一致。
- 有没有一天内安排过满。
- 哪些内容必须提醒用户实时确认。
Validator Prompt 示例:
You are a strict travel-plan validator.
Check the itinerary for unsupported claims, outdated travel data, budget inconsistencies, and overpacked schedules.
Do not rewrite the itinerary.
Return:
1. blocking_issues
2. warnings
3. facts_that_need_real_time_verification
4. recommended_user_disclaimer10. 失败处理:不要让 Agent 静默失败
多 Agent 系统最怕的问题是:前面某一步失败了,后面还一本正经继续生成。
建议每个 Agent 返回统一结构:
{
"ok": true,
"agent": "Budget Agent",
"output": "...",
"warnings": [],
"error": null
}失败时:
{
"ok": false,
"agent": "Budget Agent",
"output": null,
"warnings": [],
"error": "model timeout"
}工作流规则:
- Research 失败:停止,不进入后续步骤。
- Activity 失败:停止,要求重试或降级输出。
- Budget 失败:可以输出行程,但必须标注“预算未生成”。
- Final 失败:返回前三步原始结果,避免空白响应。
11. 最小验收标准
一个可交付的多 Agent demo,至少应满足:
- 能在 Mock 模式下无网络运行。
- 每个 Agent 职责单一。
- 每一步输出能被下一步消费。
- API Key 不写死在代码里。
- 真实模型失败时有错误提示。
- 对实时信息明确标注“需要验证”。
- 最终输出能区分事实、估算和建议。
12. 常见坑
坑 1:把顺序链误认为复杂多 Agent
原文示例本质是线性流水线。它适合入门,但不等于高级 MAS。
如果你需要真正复杂的协作,可能还需要:
- Planner
- Executor
- Reviewer
- Tool Agent
- Memory
- State Store
- Retry Policy
- Task Queue
坑 2:让模型凭记忆查实时信息
旅游预算、签证、航班、酒店都不应该只靠模型内部知识。
真实系统应该接:
- 航班 API
- 酒店 API
- 签证官网或可信数据源
- 地图/路线 API
- 汇率 API
坑 3:每个 Agent 都写得太宽
如果每个 Agent 都能“自由发挥”,多 Agent 会变成多份重复答案。
好的 Agent prompt 应该窄:
只做预算,不改行程。
只做校验,不重写正文。
只做目的地研究,不生成最终计划。坑 4:没有最终校验
没有 Validator 的多 Agent 系统,很容易把多个 Agent 的幻觉叠加起来。
多 Agent 不一定降低幻觉;如果没有校验,它可能只是让幻觉更有层次。
13. 推荐的实战升级路径
按这个顺序迭代:
- Mock 顺序链:验证流程。
- 接真实 LLM:验证角色 prompt。
- 改结构化输出:降低整合难度。
- 增加 Validator:减少明显错误。
- 接实时工具/API:减少事实幻觉。
- 加状态和日志:支持复盘与调试。
- 再考虑 LangGraph、CrewAI、AutoGen 等框架。
不要反过来。一开始就上框架,通常会把问题藏得更深。
14. 总结
这篇原文的价值不在于它给出了一个生产级多 Agent 系统,而在于它用最小代码解释了多 Agent 的基本思想:
把复杂任务拆成多个角色,让每个角色只处理自己擅长的一段,再把结果组合起来。
真正落地时,需要补上四件事:
- Mock-first 的可测试流程。
- 结构化输出。
- 错误处理和超时策略。
- 实时工具与 Validator。
如果你只是学习多 Agent,原文的顺序链足够入门。
如果你要做真实产品,请记住:
多 Agent 的核心不是“Agent 数量多”,而是职责边界清晰、数据流可验证、失败路径可控。