在上一章中,我们已经建立了上下文工程的基础技能:把一个模糊的 prompt,转化为一个结构化的“语义蓝图(semantic blueprint)”。
现在,我们要把这项技能扩展到更大的尺度上 —— 构建一个由多个专门化 AI 智能体组成的系统,这些智能体通过“上下文”来协同解决复杂问题。
单一的 LLM 非常聪明,但它只是一个通才,不是真正的“专家”。
对于任何复杂的、多步骤任务,只靠一个 LLM 来完成,往往效率低下,而且经常失败。
因此,我们要设计一个多智能体系统(Multi-Agent System,MAS) ,在这个系统中,我们所工程化的“上下文”,将不再局限于一条消息的内容,而是扩展到整个系统的设计层面。这包括:
- 需要哪些智能体
- 每个智能体的具体角色和能力
- 它们之间通过什么结构化方式通信,才能不丢失信息
这才是上下文工程的真正范围。
此外,为了让我们的多智能体系统足够可靠,我们会引入 Model Context Protocol(MCP) —— 一种共享语言,保证我们工程化的上下文能在智能体之间原汁原味地传递。
在接下来的动手部分,我们将从零开始构建一个完整的 MAS + MCP 系统。你会学会:
- 如何设计一个负责整体流程的 Orchestrator(编排器)
- 如何设计两个专业智能体:Researcher(研究员) 和 Writer(写作智能体) 来执行任务
- 如何把错误处理、消息校验和自动化质量控制,直接内嵌进工作流中,使系统具备从失败中恢复、并保持事实准确性的能力
当你完成这一章时,你将拥有一个完整可用的系统:
它可以接受一个高层目标,并能自主管理一个复杂的多步骤过程。
简单来说,本章会涵盖以下主题:
- 构建一个由上下文驱动的 MAS 工作流
- 使用 MCP 构建 MAS 来管理上下文
- 错误处理与校验
- AI 架构的演进
- 构建智能体系统的工具
现在,让我们从系统架构开始。
使用 MCP 设计 MAS 工作流(Architecting the MAS workflow with MCP)
在写任何一行代码之前,我们都需要一份清晰的架构规划。本节就是那份“蓝图”。
我们会把整体目标拆解为核心组件,定义系统中每个部分的角色,并画出一张通信流程图,展示整个智能体团队如何“运转起来”。
首先,我们来定义本设计中的两个核心概念:MAS 和 MCP:
- MAS:
我们将设计一个系统,它可以运行多个彼此独立的智能体,每个智能体专精于某一项任务,比如:研究、写作、数据分析。
通过为每个智能体提供清晰的上下文,我们可以确保它在自己的职责范围内发挥最大能力。 - MCP:
为了让智能体之间可以协同,它们需要一种共享语言。MCP 就提供了这种“消息传递规则”:
它定义了智能体之间如何彼此传递任务与信息,确保每条消息都是结构化、可靠且可被完全理解的。
我们的 MAS 将由三个不同组件组成。每个组件承担特定角色,三者协同形成一个从高层用户目标到最终成品的完整工作流:
图 2.1:MAS 工作流的架构蓝图(示意)
流程图展示了整个系统的工作路径。
我们把这个“认知流水线”中各个组件的角色拆解如下:
Orchestrator(编排器 / 项目经理)
Orchestrator 就是整个系统的大脑。
它自己不执行专业任务,而是负责管理整个工作流:
- 接收用户的高层目标
- 将目标拆解为多个逻辑步骤
- 把每一步分派给合适的智能体
- 接收某个智能体的输出,并把它作为上下文,传递给下一个智能体
换句话说,Orchestrator 把我们上一章用在“一次对话内部”的 context chaining(上下文链式) ,推到了系统层面,作用在整个 MAS 上。
Researcher 智能体(信息专家)
这是我们的第一个“专业角色”智能体。
它的职责是:
- 接收一个具体主题
- 找到相关信息
- 把信息整理成结构化的摘要
在本项目中,它会接收 Orchestrator 下发的研究任务,并以清晰的要点列表的形式返回结果。
Writer 智能体(内容创作者)
这是第二个专业智能体。
它擅长的是表达和创作:
- 接收 Researcher 提供的结构化摘要
- 把这些要点转化为一篇内容完整、风格自然、适合人类阅读的成品
- 注意语气、风格、叙事连贯性等方面的打磨
在这个系统中,信息并不是以“随手写的文本片段”流动的。
每一次智能体之间的交互,都被打包为一个结构化的 MCP 消息。
这可以保证:任务和结果总是携带完整的上下文,并且以一致、可预测、可靠的格式传递。
MCP 就是把一堆“单个智能体”粘合成“一个整体系统”的连接组织。
有了这份蓝图,我们就可以开始用 MCP 来构建我们的 MAS 了。
说明:
在本书中,我们探索的是一个前沿方向:将 MCP 原本用于“智能体 ↔ 工具”交互的原则,进一步扩展到“智能体 ↔ 智能体”的通信上。
虽然 MCP 最初是为 agent-tool 交互设计的,但一些最新探索(例如 Microsoft 的 Can You Build Agent2Agent Communication on MCP? Yes! )已经展示了 MCP 演进后如何支持新兴的多智能体协调模式。
文章链接:developer.microsoft.com/blog/can-yo…
使用 MCP 构建 MAS(Building an MAS with MCP)
现在我们有了架构蓝图,是时候开始写代码了。
在本节中,我们会一步一步实现 MAS + MCP 系统:
- 先实现系统的核心功能
- 然后在后面的 Error handling and validation(错误处理与校验) 小节中再补充鲁棒性
请打开本章仓库中的 MAS_MCP.ipynb,跟着一起操作。
最初的 OpenAI 安装步骤和第 1 章中的 SRL.ipynb 是一样的。
我们会在 Colab 笔记本中按“代码块”依次推进:
- 定义通信协议
- 构建各个专业智能体
- 构建 Orchestrator 并让它管理这些智能体
- 最后运行整个系统,观察这支“智能体团队”的实际表现
下面先初始化 OpenAI 客户端。
初始化客户端(Initializing the client)
我们首先初始化 OpenAI 客户端,它将作为我们访问 LLM 的“网关”。
同时导入 json 库,用于把结构化消息以更易读的格式打印出来:
#@title 1. Initializing the Client
# -------------------------------------------------------------------------
# We'll need the `openai` library to communicate with the LLM.
# Note: This notebook assumes you have already run a setup cell in your Colab
# environment to load your API key from Colab Secrets into an environment
# variable, as you specified.
# -------------------------------------------------------------------------
import json
# --- Initialize the OpenAI Client ---
# The client will automatically read the OPENAI_API_KEY from your environment.
client = OpenAI()
print("OpenAI client initialized.")
输出会显示一条确认信息:
OpenAI client initialized.
定义协议(Defining the protocol)
正如我们之前所说,智能体要协作,就必须有一种共享语言。
在这里,这种语言就是 MCP。
本节我们会实现一个简化版,用于展示整个过程。
虽然这里用 Python 字典就能很好地说明问题,但理解 MCP 官方定义的规则,仍然是有价值的。
消息格式(Message format)
每条 MCP 消息的结构都被严格定义,以确保一致性:
- 所有消息遵循 JSON-RPC 2.0 格式,表现为干净的 JSON 对象
- 消息必须以 UTF-8 编码,以保证通用兼容性
- 每条消息必须在单行内完成,不能包含换行符,以便快速、可靠地解析
确定消息格式后,MCP 还会定义传输层。
传输层(Transport layers)
传输层定义了消息在智能体之间“如何传递”。主要有两种方法:
- STDIO(标准输入/输出) :
当智能体在同一台机器上运行(例如在 Colab 中),就可以通过标准输入 / 标准输出直接通信。这是最简单、最直接的方式。 - HTTP:
当智能体运行在不同服务器上时,它们通过标准 HTTP 请求来传输消息。
协议管理(Protocol management)
MCP 同时还包含一些关于兼容性和安全性的规则:
- 版本控制(Versioning) :
对于 HTTP 传输,需要一个版本头(version header),确保客户端和服务端使用的是同一套协议规则。 - 安全性(Security) :
包含验证连接的规则,以防常见网络攻击,并确保你真的在和预期的服务端通信。
在本书的动手项目中,我们聚焦于 MCP 的“精神”——结构化的通信。
一个简单的 Python 字典对我们来说已经足够,能作为正式 JSON-RPC 对象的“代替品”,展示消息是如何被构造与传递的,而不用陷入协议细节。
有了这些准备,我们就可以创建一个辅助函数 create_mcp_message。
这个函数就是我们系统中“消息”的模板。
通过统一结构,我们确保信息在智能体之间传递时不会丢失或被误读。代码如下:
#@title 2. Defining the Protocol: The MCP Standard
# -------------------------------------------------------------------------
# Before we build our agents, we must define the language they will speak.
# MCP provides a simple, structured way to pass context. For this example,
# our MCP message will be a Python dictionary with key fields.
# -------------------------------------------------------------------------
def create_mcp_message(sender, content, metadata=None):
"""Creates a standardized MCP message."""
return {
"protocol_version": "1.0",
"sender": sender,
"content": content,
"metadata": metadata or {}
}
print("--- Example MCP Message (Our Simplified Version) ---")
example_mcp = create_mcp_message(
sender="Orchestrator",
content="Research the benefits of the Mediterranean diet.",
metadata={"task_id": "T-123", "priority": "high"}
)
print(json.dumps(example_mcp, indent=2))
输出展示了我们简化版 MCP 消息的 JSON 结构:
--- Example MCP Message (Our Simplified Version) ---
{
"protocol_version": "1.0",
"sender": "Orchestrator",
"content": "Research the benefits of the Mediterranean diet.",
"metadata": {
"task_id": "T-123",
"priority": "high"
}
}
create_mcp_message 函数接收三个输入:
sender:消息的发送者content:任务或信息的内容metadata:可选的元数据
它总是返回同一种结构的 Python 字典。
这种结构的一致性,就是系统可靠性的基础 —— 消息可以在智能体之间流动,而不会丢失、被误读或被误解。
协议定义完毕后,我们就可以开始构建专业智能体了。
构建智能体(Building the agents)
本节包含的代码,会把图 2.1 中的工作流示意图真正“变成活的智能体”。
我们会把每个智能体都定义成一个 Python 函数。每个智能体函数都接收一个结构化的 MCP 消息作为输入,并返回另一个 MCP 消息作为输出。
智能体的具体角色由它的 system prompt(系统提示词) 决定——也就是告诉 LLM“你是谁”“你应该怎么表现”。
为了让整个系统的通信保持一致,我们还会创建一个统一的辅助函数 call_llm,用来集中管理所有对 OpenAI API 的调用。
图 2.2:多智能体函数的架构
这张图展示了一个使用两个 AI 智能体来创作博客文章的工作流。信息从最初的“想法”一路流动到“完整内容”,经过若干个步骤:
- 流程从一个“研究主题”开始,这是拉起整个工作流的初始 prompt。
携带这个主题的消息会被发送给第一个智能体 —— Researcher agent(研究员智能体) ,它会从一个模拟的内部数据库中收集相关事实和信息。 - 当 Researcher 获得原始数据之后,它会调用 LLM API(一个外部语言模型服务)来处理这些信息,并生成一份关键发现的摘要。
这份摘要,是流程从“信息收集阶段”进入“写作阶段”的桥梁。 - 这份摘要随后会传给第二个智能体 —— Writer agent(写作智能体) 。它的角色是把这些研究要点转化为一篇完整的文章。
Writer 同样会与 LLM API 通信,但它处理的是更加复杂和创意化的“写作任务”。 - 系统的最终输出是一篇博客文章,这清晰地展示了:
专门化的智能体如何合作,把一个高层目标变成一个完整成品。
现在我们就来实现这些会相互通信的智能体。
创建辅助函数
在真正编写智能体之前,我们需要一种可靠且统一的方式,让它们与 LLM 通信。
我们的辅助函数 call_llm 就负责处理所有对 OpenAI 的 API 调用。
它接收一个 system_prompt 和一个 user_content 作为输入,并返回模型的文本响应:
def call_llm(system_prompt, user_content):
"""A helper function to call the OpenAI API using the new client syntax."""
try:
# Using the updated client.chat.completions.create method
response = client.chat.completions.create(
model="gpt-5",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_content}
]
)
return response.choices[0].message.content…
这个函数有两个输入参数:
system_prompt:一个字符串,用来告诉模型应该以什么身份、什么风格来回答user_content:一个字符串,包含我们真正要发送的信息或问题
在函数内部,我们调用 OpenAI 客户端来创建一次对话补全请求。
messages 被作为字典列表传入,其中:
role="system":用于设定智能体的行为框架role="user":用于传递具体输入内容
函数最终返回模型响应中的文本内容。
同时,try/except 用于做基本的错误处理,避免在 API 调用失败时整个程序直接崩溃。
有了这个辅助函数,我们就可以开始构建第一个专业智能体:Researcher agent。
定义 Researcher 智能体
我们的第一个智能体是 Researcher(研究员) 。
它的工作是:接收一个主题 → 查找相关信息 → 为后续步骤生成摘要。
首先,我们定义这个函数。researcher_agent 函数接收 mcp_input 作为参数,它遵循我们之前定义的标准 MCP 消息格式。当智能体被激活时,它会打印一条日志,方便我们追踪何时开始工作:
def researcher_agent(mcp_input):
"""
This agent takes a research topic, finds information, and returns a summary.
"""
print("\n[Researcher Agent Activated]")
接着,我们创建一个 simulated_database,也就是一个简单的字典,用来模拟真实数据源。
对于当前示例来说,这已经足够 —— 在后续章节中,我们会把它替换成向量数据库,以支持 RAG:
simulated_database = {
"mediterranean diet": "The Mediterranean diet is rich in fruits, vegetables, whole grains, olive oil, and fish. Studies show it is associated with a lower risk of heart disease, improved brain health, and a longer lifespan. Key components include monounsaturated fats and antioxidants."
}
然后,我们从输入消息中提取研究主题。
智能体从 MCP 消息的 content 字段中读取内容,并在模拟数据库中查找对应条目。如果找不到,就返回一个默认消息:
research_topic = mcp_input['content']
research_result = simulated_database.get(
research_topic.lower(),
"No information found on this topic."
)
接下来定义 system_prompt。
别看 prompt 简单,在 MAS 中它其实是一条经过精心设计的“行为说明书”。
在这里,这个 prompt 告诉 LLM:你是一个研究分析师,要把给定信息压缩为 3–4 个简洁的要点:
system_prompt = (
"You are a research analyst. Your task is to synthesize the "
"provided information into 3-4 concise bullet points. "
"Focus on the key findings."
)
然后,智能体调用我们的辅助函数 call_llm,传入 system_prompt 和 research_result。
LLM 生成的摘要会保存在 summary 中,我们再打印一条日志做确认:
summary = call_llm(system_prompt, research_result)
print(f"Research summary created for: '{research_topic}'")
最后,智能体使用 create_mcp_message 将摘要打包成一条新的 MCP 消息。
这样可以确保输出格式与协议保持一致,同时附上内容与元数据:
return create_mcp_message(
sender="ResearcherAgent",
content=summary,
metadata={"source": "Simulated Internal DB"}
)
带有内容和元数据的这条消息,就是 Researcher 智能体的最终输出。
接下来,我们要构建 Writer 智能体,它是系统中的“内容创作者”,会把这个摘要转成一篇博客文章。
定义 Writer 智能体
系统中的第二个智能体是 Writer。
它接收来自 Researcher agent 的“研究摘要”,并把这些要点转化为一篇简短的博客文章。
同样,我们先定义函数。
writer_agent 也接收一个 MCP 消息作为输入。激活日志方便我们跟踪它何时开始工作:
def writer_agent(mcp_input):
"""
This agent takes research findings and writes a short blog post.
"""
print("\n[Writer Agent Activated]")
然后,从消息中提取研究摘要 —— 这正是之前 Researcher agent 生成的内容:
research_summary = mcp_input['content']
接下来,我们为 Writer 定义 system_prompt。
与专注于“事实综合”的 Researcher 不同,Writer 的职责是写作,它被指示采用一种适合健康与养生博客的有吸引力、信息丰富且鼓励性的语气。
Prompt 还规定了文章长度(约 150 词)并要求提供一个吸睛标题:
system_prompt = (
"You are a skilled content writer for a health and wellness blog. "
"Your tone is engaging, informative, and encouraging. "
"Your task is to take the following research points and write a "
"short, appealing blog post (approx. 150 words) with a catchy title."
)
智能体再次调用 call_llm,传入 system_prompt 和 research_summary,返回的文本就是我们的博客草稿。
我们也打印一条日志确认草稿已生成:
blog_post = call_llm(system_prompt, research_summary)
print("Blog post drafted.")
最后,我们用 create_mcp_message 将博客文章包装成新的 MCP 消息。
输出同样包含内容与元数据,这里元数据记录的是草稿的词数:
return create_mcp_message(
sender="WriterAgent",
content=blog_post,
metadata={"word_count": len(blog_post.split())}
)
至此,Writer 智能体就定义完了。
它从 Researcher 那里接收结构化输入,应用自己的 system prompt,并输出一篇完整的博客文章,而且同样以统一的 MCP 消息格式返回。
随着 Researcher 和 Writer 两个智能体的就位,我们就已经定义好了 MAS 的核心:
每个智能体各司其职,又通过结构化消息协同工作,能够把一份原始信息一路推进到一个完成的内容成品。
构建 Orchestrator(编排器)
现在我们已经有了专门化的智能体,但还缺少一个“总指挥”来管理它们,这个角色就是 Orchestrator(编排器) 。可以把它想象成我们这支 AI 团队的项目经理:
它的工作是接收一个高层目标,把它拆分成一系列任务,并把对应的任务分配给合适的智能体。同时,它还要管理信息流动:从一个智能体那里接收输出,再把它作为输入传给下一个智能体,从而形成一个首尾闭环的流程:
图 2.3:智能体之间的 Orchestrator(编排器)
在上图展示的工作流中,流程从一个初始目标(initial goal)被发送到 Orchestrator 开始。
Orchestrator 作为一个中心枢纽,先把任务发给 Researcher agent(研究员智能体) 。
当研究完成后,Researcher 会把结果返回给 Orchestrator。
Orchestrator 再对这些信息做处理,把新的任务分配给 Writer agent(写作智能体) 。
Writer 完成写作后,会把最终内容返回给 Orchestrator。
最后,Orchestrator 对所有结果进行汇总,生成最终输出(final output) ,整个流程至此完成。
下面的代码定义了我们的 Orchestrator。这个函数从头到尾管理整个多智能体工作流。它的职责是:依次调用 Researcher、Writer,最后把结果组装成完整的产物。我们一步一步来看:
def orchestrator(initial_goal):
"""
Manages the multi-agent workflow to achieve a high-level goal.
"""
print("=" * 50)
print(f"[Orchestrator] Goal Received: '{initial_goal}'")
print("=" * 50)
我们首先定义 orchestrator 函数。它接收一个参数 initial_goal,代表我们希望系统完成的高层任务。函数启动时会打印一条确认信息,表示“目标已接收”。
第一步是把研究任务委派出去。这里我们将研究主题硬编码为 Mediterranean Diet(地中海饮食),但在真实场景中,这个主题可以根据用户的高层目标动态生成:
# --- Step 1: Orchestrator plans and calls the Researcher Agent ---
print("\n[Orchestrator] Task 1: Research. Delegating to Researcher Agent.")
research_topic = "Mediterranean Diet"
接着,我们把研究主题封装进一个 MCP 消息中,以保持通信格式的一致性:
mcp_to_researcher = create_mcp_message(
sender="Orchestrator",
content=research_topic
)
现在 Orchestrator 调用 Researcher 智能体,把这个 MCP 消息作为输入传递过去,并接收返回结果,然后打印这份摘要:
mcp_from_researcher = researcher_agent(mcp_to_researcher)
print("\n[Orchestrator] Research complete. Received summary:")
print("-" * 20)
print(mcp_from_researcher['content'])
print("-" * 20)
此时,Orchestrator 已经拿到了 Researcher 的响应,存放在 mcp_from_researcher 中,它可以接着把这份内容传给 Writer 智能体。
Orchestrator 会先构造发给 Writer 的 MCP 消息 mcp_to_writer,然后调用 Writer,并打印日志表示写作阶段完成:
# --- Step 2: Orchestrator calls the Writer Agent ---
print("\n[Orchestrator] Task 2: Write Content. Delegating to Writer Agent.")
mcp_to_writer = create_mcp_message(
sender="Orchestrator",
content=mcp_from_researcher['content']
)
mcp_from_writer = writer_agent(mcp_to_writer)
print("\n[Orchestrator] Writing complete.")
最后,Orchestrator 从 mcp_from_writer 中取出 Writer 的响应,并展示最终结果——也就是完整的博客文章:
# --- Step 3: Orchestrator presents the final result ---
final_output = mcp_from_writer['content']
print("\n" + "="*50)
print("[Orchestrator] Workflow Complete. Final Output:")
print("="*50)
print(final_output)
到这里,我们的 orchestrator 函数就构建完成了。接下来就可以运行整个系统,观察整个工作流是如何“串起来”的。
运行系统(Running the system)
此时,我们已经具备所有组成部分:Researcher 智能体、Writer 智能体,以及 Orchestrator。
最后一步就是运行系统,看看它们如何协同工作。
下面这段代码定义了一个用户的高层目标,把它传给 Orchestrator,并触发整个工作流。
只需要一次函数调用,Orchestrator 就会:
- 委派任务给 Researcher
- 收集摘要
- 把摘要交给 Writer
- 并最终组装出完整的博客文章:
#@title 5. Run the System
# ------------------------------------------------------------------------
# Let's give our Orchestrator a high-level goal and watch the agent team work.
# ------------------------------------------------------------------------
user_goal = "Create a blog post about the benefits of the Mediterranean diet."
orchestrator(user_goal)
图 2.4:一个由上下文驱动的 MAS 的信息流示意图
图 2.4 以可视化方式展示了智能体之间的对话过程:
Orchestrator 先接收用户目标并将其分配给 Researcher,后者生成要点摘要;
摘要返回给 Orchestrator,再被传递给 Writer;
Writer 草拟博客文章,并将其发回 Orchestrator;
最终,Orchestrator 汇总并展示完整的博客文章,演示了多智能体如何把一次简单请求变成一个成品输出。
现在,让我们看看 MAS 的真实运行输出。下面是控制台日志的拆解,展示了每个智能体是如何一步步贡献力量的。
系统启动于我们向 Orchestrator 提交目标时。Orchestrator 确认收到目标,并准备将其拆解为第一个任务:
==================================================
[Orchestrator] Goal Received: 'Create a blog post about the benefits of the Mediterranean diet.'
==================================================
Orchestrator 将任务委派给 Researcher
接下来,Orchestrator 知道自己首先需要信息,于是声明一个研究任务,并激活 Researcher 智能体。Researcher 完成工作后,会提示已生成摘要:
[Orchestrator] Task 1: Research. Delegating to Researcher Agent.
[Researcher Agent Activated]
Research summary created for: 'Mediterranean Diet'
当 Researcher 完成任务后,Orchestrator 确认已接收摘要,并展示 Researcher 提取出的关键要点。这些要点就是后续博客创作所依赖的核心信息:
[Orchestrator] Research complete. Received summary:
--------------------
- Emphasizes fruits, vegetables, whole grains, olive oil, and fish as core foods.
- Associated with lower risk of heart disease, improved brain health, and increased longevity.
- Benefits are linked to high intake of monounsaturated fats (especially from olive oil) and antioxidants.
--------------------
有了这份摘要之后,Orchestrator 继续进入下一步:
它宣布要把内容写作任务交给 Writer 智能体。Writer 随即开始撰写博客草稿:
[Orchestrator] Task 2: Write Content. Delegating to Writer Agent.
[Writer Agent Activated]
Blog post drafted.
Writer 智能体完成草稿
Writer 完成博客撰写后,Orchestrator 确认写作任务已完成:
[Orchestrator] Writing complete.
最后,Orchestrator 宣告整个工作流完成,并展示最终博客文章 —— 这是所有智能体协同工作的成果:
==================================================
[Orchestrator] Workflow Complete. Final Output:
==================================================
Mediterranean Magic: A Delicious Way to Boost Heart, Brain, and Longevity
The Mediterranean-style plate celebrates colorful fruits and vegetables, hearty whole grains, silky olive oil, and plenty of fish. Research links this pattern with a lower risk of heart disease, sharper brain health, and a longer life. The secret sauce? Monounsaturated fats—especially from extra-virgin olive oil—help improve cholesterol balance and keep blood vessels supple, while antioxidant-rich plants and seafood fight oxidative stress and inflammation….
这次运行演示了我们 MAS 的完整生命周期:
在目前这个阶段,系统已经按预期工作。但现实环境中,一次网络抖动、一条格式错误的消息,或者一次出乎意料的 LLM 响应,都可能中断整个流程。
要让工作流更加可靠、向生产级系统迈进一步,我们需要用错误处理、校验和防护机制来强化它。下一节,我们就会转向这一主题。
错误处理与校验
到目前为止,我们构建的系统是一个可用的原型:在一切顺利时,它可以正常工作。
但在真实世界里,事情很少完全照计划进行:
API 可能会失败、消息可能格式错误、智能体的输出也可能出现意料之外的结果。
在认为系统“足够稳健”之前,我们必须先让它对这些潜在故障更有防御能力。
在本节中,我们会把这份简单脚本升级为一个更健壮的系统:
- 为 API 调用增加错误处理
- 创建一个校验器,用于保证 MCP 消息格式正确
- 教会 Orchestrator:即使某个智能体失败或产生有问题的输出,也能尽量维持工作流继续运行
这一节我们在 MAS_MCP_control.ipynb 笔记本中操作,该文件中包含完整实现。
我们将重点围绕两个核心工程原则展开:弹性(resilience) 和 可靠性(reliability) :
- Resilience(弹性) :
加固与 LLM 的连接,使临时问题(例如 API 超时、限流)不会直接导致系统崩溃。 - Reliability(可靠性) :
确保消息的完整性,让智能体之间始终交互可预测、有效的 MCP 消息。
我们先从提升“弹性”开始。
最初的 call_llm 函数刻意保持得非常简单:发出一次 API 请求,拿到结果就返回。
在生产环境中,这种方式过于脆弱——如果 OpenAI API 出问题,或者请求因为网络不稳定失败,整个工作流都可能崩掉。
为了解决这个问题,我们需要构建一个具备重试能力的、更健壮的版本。
为 LLM 构建健壮组件(Building robust components for the LLM)
加强系统的第一步,就是用一个更有弹性的函数 call_llm_robust 替换原来的 call_llm。
新的版本在请求失败时不会立刻放弃,而是会进行多次重试,让系统可以从临时问题中自动恢复。
在定义这个函数之前,我们需要引入 time 库,用来在重试之间暂停程序执行。
这个 import 在 MAS_MCP_control.ipynb 的初始化部分已经包含:
import time
有了这些准备之后,就是新的函数:
#@title 3.Building Robust Components
--- Hardening the call_llm Function ---
def call_llm_robust(system_prompt, user_content, retries=3, delay=5):
"""A more robust helper function to call the OpenAI API with retries."""
for i in range(retries):
try:
response = client.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_content}
]
)
return response.choices[0].message.content
except Exception as e:
print(f"API call failed on attempt {i+1}/{retries}. Error: {e}")
if i < retries - 1:
print(f"Retrying in {delay} seconds...")
time.sleep(delay)
else:
print("All retries failed.")
return None
在这个升级版的 call_llm_robust 中,我们把 API 调用写在 for 循环里的 try/except 块中:
- 一旦出现错误,系统会打印出错信息,
- 然后等待几秒(通过
time.sleep)再重试, - 多次尝试之后仍失败,则返回
None。
这种重试机制让系统对临时性网络问题更加有弹性。
如果所有尝试都失败,函数返回 None,这会向 Orchestrator 发出一个信号:
需要由它来决定下一步如何应对这次失败。
有了对 API 调用的弹性增强,现在我们转向“可靠性”:
通过引入一个函数来校验每一条 MCP 消息的结构。
校验 MCP 消息(Validating MCP messages)
为了让智能体之间可靠通信,它们必须能够信任自己收到的消息。
如果一条格式错误的消息溜了进去,整个工作流都有可能中断。
为防止这种情况,我们引入一个 MCP 校验器(validator) ——
在消息交给智能体之前,先检查它是否符合协议要求:
--- The MCP Validator ---
def validate_mcp_message(message):
"""A simple validator to check the structure of an MCP message."""
required_keys = ["protocol_version", "sender", "content", "metadata"]
if not isinstance(message, dict):
print(f"MCP Validation Failed: Message is not a dictionary.")
return False
for key in required_keys:
if key not in message:
print(f"MCP Validation Failed: Missing key '{key}'")
return False
print(f"MCP message from {message['sender']} validated successfully.")
return True
这个函数是一个非常关键的“防护栏(guardrail)”。
Orchestrator 会在把任何消息传下去之前调用 validate_mcp_message,以确保消息结构完整且可预测:
- 先检查消息是否是字典;
- 再确认所有必要字段(
protocol_version、sender、content、metadata)都存在。
这样就可以阻止“格式有问题的上下文”进入工作流,避免一些诡异的错误。
有了这个校验器,Orchestrator 可以信任自己分发的消息,智能体也可以专注于执行各自的任务。
再加上 call_llm_robust 提供的 API 调用弹性,我们就为系统打好了可靠性 + 容错的基础。
接下来,我们再增加一层保护:
给专业智能体增加专门控制与质量检验。
为智能体增加专门化控制与校验
Adding agent specialization controls and validation
目前这支智能体团队虽然能工作,但仍有一个明显弱点:
系统完全信任 Writer 的输出。
即便上下文设计得很好,LLM 仍然有可能误解事实、或者直接产生“幻觉(hallucination)”。
要提高系统的可靠性,我们需要引入一个质量控制步骤,在 Writer 的成果被接受为“最终产出”之前,先做一次检查。
第一步,是在 MAS_MCP_control.ipynb 中更新现有的 researcher_agent 和 writer_agent,
把原来的 call_llm 替换成新的 call_llm_robust:
#@title 4.Building the Agents: The Specialists
--- Agent 1: The Researcher ---
def researcher_agent(mcp_input):
... (code omitted for brevity) ...
system_prompt = "You are a research analyst. Synthesize the provided information into 3-4 concise bullet points."
# Now using the robust caller
summary = call_llm_robust(system_prompt, research_result)
... (code omitted for brevity) ...
--- Agent 2: The Writer ---
def writer_agent(mcp_input):
... (code omitted for brevity) ...
system_prompt = "You are a content writer. Take the following research points and write a short, appealing blog post (approx. 150 words) with a catchy title."
# Now using the robust caller
blog_post = call_llm_robust(system_prompt, research_summary)
... (code omitted for brevity) ...
这样一来,Researcher 和 Writer 在遇到临时 API 故障时不会立刻“翻车”,而是会自动重试。
它们变得更有“韧性”了,但我们仍缺少一环:
如何确认 Writer 的草稿,确实与 Researcher 的发现保持事实一致?
下一步,我们要把这支团队从两个专家扩展到三个专家——引入一个新的智能体:validator agent(验证者) 。
验证者智能体的唯一职责,就是充当“事实核查员”:
它比较 Writer 的草稿和 Researcher 的摘要,判断是否事实一致,从而为系统提供一层非常关键的质量控制。
# --- Agent 3: The Validator ---
def validator_agent(mcp_input):
"""This agent fact-checks a draft against a source summary."""
print("\n[Validator Agent Activated]")
# Extracting the two required pieces of information
source_summary = mcp_input['content']['summary']
draft_post = mcp_input['content']['draft']
system_prompt = """
You are a meticulous fact-checker. Determine if the 'DRAFT' is factually consistent with the 'SOURCE SUMMARY'.
- If all claims in the DRAFT are supported by the SOURCE, respond with only the word "pass".
- If the DRAFT contains any information not in the SOURCE, respond with "fail" and a one-sentence explanation.
"""
validation_context = f"SOURCE SUMMARY:\n{source_summary}\n\nDRAFT:\n{draft_post}"
validation_result = call_llm_robust(system_prompt, validation_context)
print(f"Validation complete. Result: {validation_result}")
return create_mcp_message(
sender="ValidatorAgent",
content=validation_result
)
Validator 智能体有自己明确的“语义蓝图”:
-
输入包括两部分:
- 来自 Researcher 的摘要
source_summary - 来自 Writer 的草稿
draft_post
- 来自 Researcher 的摘要
-
输出非常简单:
- 如果草稿中的所有陈述都能在摘要中得到支持,则返回
pass - 如果草稿中出现了摘要没有涉及的信息,则返回
fail,并附带一句解释
- 如果草稿中的所有陈述都能在摘要中得到支持,则返回
这样 Orchestrator 就多了一重保护:
它不再需要盲目信任 Writer,而是可以在接受草稿为最终结果之前,先做一次事实校验。
接下来,我们会把这个 Validator 整合进 Orchestrator 的工作流中。
带验证循环的最终 Orchestrator
The final Orchestrator with a validation loop
最初那个简单版的 Orchestrator 只是一个线性任务调度器。
我们现在用一个更智能的 final_orchestrator 替换它:
新版本包含了一个验证 + 修订循环(validation & revision loop) 。
现在的工作流不再是简单的顺序执行:
当 writer_agent 生成草稿后,Orchestrator 会把它交给 validator_agent。
如果验证失败,Orchestrator 会把 Validator 的反馈一并传回给 Writer,请求进行修订。
这一机制构成了一个强大的自我纠错系统,类似于真实世界中的“编辑流程”。
下面我们来看 final_orchestrator 中的关键变化(在笔记本第 5 部分)。
第 1 步和最初的 Orchestrator 类似,但关键新增点是:
在收到 Researcher 的输出后,立刻调用 validate_mcp_message。
如果 Researcher 返回了一个格式有问题的消息,或者内容为空(例如 call_llm_robust 返回了 None),Orchestrator 会立即终止工作流:
#@title 5.The Final Orchestrator with Validation Loop
def final_orchestrator(initial_goal):
... (Initialization code omitted) ...
# Step 1 Research and Immediate Validation
# --- Step 1: Research ---
print("\n[Orchestrator] Task 1: Research. Delegating to Researcher Agent.")
# ... (Call to researcher_agent) ...
mcp_from_researcher = researcher_agent(mcp_to_researcher)
# New: Validate the message structure immediately
if not validate_mcp_message(mcp_from_researcher) or not mcp_from_researcher['content']:
print("Workflow failed due to invalid or empty message from Researcher.")
return
research_summary = mcp_from_researcher['content']
print("\n[Orchestrator] Research complete.")
第 2、3 步则被合并成一个迭代循环:
- 如果验证通过(
pass),草稿被接受,循环结束; - 如果验证不通过(
fail),Orchestrator 把 Writer 的草稿和 Validator 的反馈一起返回给 Writer,请求修订。
同时,为了防止出现无限循环,我们给修订次数设定了一个上限 max_revisions:
# --- Step 2 & 3: Iterative Writing and Validation Loop ---
final_output = "Could not produce a validated article."
max_revisions = 2
for i in range(max_revisions):
print(f"\n[Orchestrator] Writing Attempt {i+1}/{max_revisions}")
# Prepare context for the writer
writer_context = research_summary
if i > 0:
# If this is a revision, add the validator's feedback
writer_context += f"\n\nPlease revise the previous draft based on this feedback: {validation_result}"
# Call the Writer Agent and Validate
mcp_to_writer = create_mcp_message(sender="Orchestrator", content=writer_context)
mcp_from_writer = writer_agent(mcp_to_writer)
if not validate_mcp_message(mcp_from_writer) or not mcp_from_writer['content']:
print("Aborting revision loop due to invalid message from Writer.")
break
draft_post = mcp_from_writer['content']
Writer 生成草稿后,Orchestrator 立即进入新的验证步骤与决策点。
我们将 research_summary 与 draft_post 一并打包为一条 MCP 消息,传给 validator_agent:
# --- Validation Step ---
print("\n[Orchestrator] Draft received. Delegating to Validator Agent.")
# Prepare context for the Validator (needs both summary and draft)
validation_content = {"summary": research_summary, "draft": draft_post}
mcp_to_validator = create_mcp_message(sender="Orchestrator", content=validation_content)
mcp_from_validator = validator_agent(mcp_to_validator)
# Validate Validator output
if not validate_mcp_message(mcp_from_validator) or not mcp_from_validator['content']:
print("Aborting revision loop due to invalid message from Validator.")
break
validation_result = mcp_from_validator['content']
# Decision Point
if "pass" in validation_result.lower():
print("\n[Orchestrator] Validation PASSED. Finalizing content.")
final_output = draft_post
break
else:
print(f"\n[Orchestrator] Validation FAILED. Feedback: {validation_result}")
if i < max_revisions - 1:
print("Requesting revision.")
else:
print("Max revisions reached. Workflow failed.")
现在的工作流中已经加入了 Validator 智能体:
-
Writer 完成草稿后,Orchestrator 把草稿交给 Validator;
-
根据 Validator 的结果进行决策:
- 如果验证失败:Orchestrator 启动反馈循环,把 Validator 的意见一并传回给 Writer,请求修订;
- 如果验证通过:Orchestrator 接受草稿作为最终结果,结束循环。
在这一设计下,Orchestrator 不再只是一个“任务分发器”,而更像是一个总编辑(editor-in-chief) :
它同时关注事实一致性和错误容忍能力。
有了这个验证循环,整个工作流具备了自我纠错能力,能更稳定地产出可靠的结果。
接下来,我们就可以实现这一整套最终版本的、健壮的系统了。
运行最终的健壮系统
Running the final robust system
下面这个最后的代码块会运行完整升级后的系统。
它不再调用最初的简易 Orchestrator,而是执行 final_orchestrator。
通过这个入口,我们可以看到整个“健壮工作流”的运转,包括新的校验与修订步骤:
#@title 6.Run the Final, Robust System
user_goal = "Create a blog post about the benefits of the Mediterranean diet."
final_orchestrator(user_goal)
输出中会显示更详细的日志信息。
我们可以看到:在每个智能体完成任务之后,都会进行 MCP 消息校验;
同时还能看到完整的“写作 + 校验”循环是如何运作的:
==================================================
[Orchestrator] Goal Received: 'Create a blog post about the benefits of the Mediterranean diet.'
==================================================
[Orchestrator] Task 1: Research. Delegating to Researcher Agent.
[Researcher Agent Activated]
Research summary created for: 'Mediterranean Diet'
MCP message from ResearcherAgent validated successfully.
[Orchestrator] Research complete.
…
What's more? Numerous studies indicate that adherence to the Mediterranean diet is your ticket to increased longevity. So, here's your secret recipe for a long, robust life. Dive into the Mediterranean diet today, relish the taste of good health, and cheers to a vibrant life!
有了这一系列升级,我们的 MAS 现在可靠性大大提升,能够做到:
- 通过重试逻辑优雅地处理 API 错误
- 校验每一条 MCP 消息的结构,确保一致性
- 应用自动化的质量控制流程
很清楚的一点是:AI 系统正变得越来越强大、越来越复杂。
为了给本章收个尾,我们来简要看看当前正在发生的AI 架构演进。
AI 架构的演进
我们构建 AI 的方式正在发生变化。
这些模型本身非常强大、知识面很广,但它们并不是某个单一领域的“专家”。
当任务不断变化时,单一模型往往很难始终保持专注和准确。
因此,我们从“使用一个通用模型”,转向“构建一个由专门化智能体组成的团队”。
我们通过上下文工程来定义每个智能体的角色与技能。
本章的 Researcher 和 Writer 智能体就是这种思路的例子:
每个智能体都围绕一个明确的单一职责被设计出来,并且把这件事做到很好,如下图所示:
图 2.5:AI 架构从“单模型”到“系统”的演进
一支“专家团队”要解决复杂问题,就必须有一个良好的协同结构。
MAS(多智能体系统)提供了这种结构,用来管理这些智能体;
MCP 帮助智能体之间进行可靠的通信;
上下文工程则是把这一切粘合在一起的“胶水”:
它一开始用于提升单个 LLM 的回答质量,如今进一步被扩展,用来定义智能体本身及它们之间的交互方式。
随着 AI 在真实世界中的大规模落地,这些系统也会不断扩展与规模化。
构建智能体系统的工具
到目前为止,我们是从零开始搭建了一套 MAS。
这种“手工搭建”的方式,让我们对 MAS 和 MCP 背后的核心理念有了扎实的理解:编排、结构化通信、弹性以及校验。
在很多项目中,客户或公司管理层可能要求避免使用外部平台或框架,
甚至连可下载的开源项目都不允许用,有些场景还会要求只能使用本地 LLM。
比如航天、国防、银行、金融等领域,这往往是硬性前置条件。
但在另一些场景下,使用框架不仅被允许,甚至是被鼓励、被要求的。
那么,作为一名上下文工程师,正确的路径是什么呢?
答案是:先从零开始理解上下文工程(就像我们现在做的这样)。
有了这一层理解之后:
- 你既可以从头实现一个完全定制的解决方案,
- 也可以在使用现成框架时,比大多数工程师更有优势。
你既是架构师,也是问题解决者:
当“现成框架”不再满足需求时,你能想办法让系统继续运转下去。
可以进一步探索以下资源:
- MCP 规范(The MCP specification)
官方 MCP 规范是必读文档,详细说明了智能体间如何通信的正式规则。
我们在本章实现的是一个简化版本,正式站点上则定义了 JSON RPC 传输层、安全性等标准:
👉modelcontextprotocol.io - MAS 框架(MAS frameworks)
有不少开源框架可以大大简化 MAS 的构建工作。
它们处理了很多“硬骨头”,例如:编排、通信、智能体管理等。
这些工具背后的思想和我们刚刚手工搭建的系统非常相似。
研究这些框架,是了解这些概念如何在生产环境中使用的好方式。 - autogen
autogen是微软推出的一个框架,用于构建“多个智能体彼此对话、共同解决问题”的应用。
autogen 中的智能体非常灵活:既可以调用 LLM,也可以结合人类输入和其它工具来完成工作。
GitHub 地址:github.com/microsoft/autogen - CrewAI
CrewAI 专注于构建“基于角色的智能体团队”。
它提供了一套结构化方式,让多个智能体在复杂任务上协同工作。
CrewAI 非常强调“协作”和“专门化”,其设计理念与我们实现的 Orchestrator 模式非常接近。
更多信息:www.crewai.com/ - LangGraph
LangGraph 是 LangChain 工具家族的一部分,它支持把智能体工作流构建成“图(graph)”。
这是构建可迭代、可自我纠错系统的一种非常强大的方式。
LangGraph 可以让你对信息流和决策路径进行非常细粒度的控制。
文档地址:python.langchain.com/docs/langgraph
到此为止,我们已经从零实现了一个 MAS + MCP 的完整用例,
并通过实战方式理解了最新一代“上下文工程系统”的核心思想。
总结(Summary)
在本章中,我们迈出了超越“单智能体交互”的一步。
我们构建了一支专门化智能体团队,每个智能体都执行一个明确的职能。
我们使用了 MCP 来让智能体之间通过标准结构进行通信。
随后,我们设计了整个系统的架构,利用语义蓝图来定义智能体团队的角色与工作流。
接着,我们一步一步搭建系统:
-
从一个用于创建标准 MCP 消息的函数开始;
-
构建每个智能体,并为它们设置独特技能和专用 system prompt;
-
构建 Orchestrator 来管理上下文流转:
- 它从 Researcher 那里接收输出,再把它传递给 Writer;
最终,我们得到了一个可运行的 MAS:
它能接收用户目标,自主完成研究与写作的全过程。
最后,我们通过为架构增加错误处理和校验逻辑,让这个原型更加健壮。
到现在为止,你已经从零设计、实现并稳固了一套 MAS。
你可以使用这些高级工具,去构建下一代 AI 解决方案。
下一章,我们将把这段旅程提升到新层次:
通过引入 RAG(检索增强生成) ,让 MAS 能够访问外部数据。