多智能体系统的上下文工程——使用 MCP 构建多智能体系统

254 阅读37分钟

在上一章中,我们已经建立了上下文工程的基础技能:把一个模糊的 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)

在写任何一行代码之前,我们都需要一份清晰的架构规划。本节就是那份“蓝图”。
我们会把整体目标拆解为核心组件,定义系统中每个部分的角色,并画出一张通信流程图,展示整个智能体团队如何“运转起来”。

首先,我们来定义本设计中的两个核心概念:MASMCP

  • MAS
    我们将设计一个系统,它可以运行多个彼此独立的智能体,每个智能体专精于某一项任务,比如:研究、写作、数据分析。
    通过为每个智能体提供清晰的上下文,我们可以确保它在自己的职责范围内发挥最大能力。
  • MCP
    为了让智能体之间可以协同,它们需要一种共享语言。MCP 就提供了这种“消息传递规则”:
    它定义了智能体之间如何彼此传递任务与信息,确保每条消息都是结构化、可靠且可被完全理解的。

我们的 MAS 将由三个不同组件组成。每个组件承担特定角色,三者协同形成一个从高层用户目标到最终成品的完整工作流:

image.png

图 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 笔记本中按“代码块”依次推进:

  1. 定义通信协议
  2. 构建各个专业智能体
  3. 构建 Orchestrator 并让它管理这些智能体
  4. 最后运行整个系统,观察这支“智能体团队”的实际表现

下面先初始化 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 的调用。

image.png

图 2.2:多智能体函数的架构

这张图展示了一个使用两个 AI 智能体来创作博客文章的工作流。信息从最初的“想法”一路流动到“完整内容”,经过若干个步骤:

  1. 流程从一个“研究主题”开始,这是拉起整个工作流的初始 prompt。
    携带这个主题的消息会被发送给第一个智能体 —— Researcher agent(研究员智能体) ,它会从一个模拟的内部数据库中收集相关事实和信息。
  2. 当 Researcher 获得原始数据之后,它会调用 LLM API(一个外部语言模型服务)来处理这些信息,并生成一份关键发现的摘要
    这份摘要,是流程从“信息收集阶段”进入“写作阶段”的桥梁。
  3. 这份摘要随后会传给第二个智能体 —— Writer agent(写作智能体) 。它的角色是把这些研究要点转化为一篇完整的文章。
    Writer 同样会与 LLM API 通信,但它处理的是更加复杂和创意化的“写作任务”。
  4. 系统的最终输出是一篇博客文章,这清晰地展示了:
    专门化的智能体如何合作,把一个高层目标变成一个完整成品。

现在我们就来实现这些会相互通信的智能体。

创建辅助函数

在真正编写智能体之前,我们需要一种可靠且统一的方式,让它们与 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_promptresearch_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_promptresearch_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 团队的项目经理:
它的工作是接收一个高层目标,把它拆分成一系列任务,并把对应的任务分配给合适的智能体。同时,它还要管理信息流动:从一个智能体那里接收输出,再把它作为输入传给下一个智能体,从而形成一个首尾闭环的流程:

image.png

图 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)

image.png

图 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 库,用来在重试之间暂停程序执行。
这个 importMAS_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,以确保消息结构完整且可预测

  1. 先检查消息是否是字典;
  2. 再确认所有必要字段(protocol_versionsendercontentmetadata)都存在。

这样就可以阻止“格式有问题的上下文”进入工作流,避免一些诡异的错误。

有了这个校验器,Orchestrator 可以信任自己分发的消息,智能体也可以专注于执行各自的任务。
再加上 call_llm_robust 提供的 API 调用弹性,我们就为系统打好了可靠性 + 容错的基础。

接下来,我们再增加一层保护:
给专业智能体增加专门控制与质量检验

为智能体增加专门化控制与校验

Adding agent specialization controls and validation

目前这支智能体团队虽然能工作,但仍有一个明显弱点:
系统完全信任 Writer 的输出
即便上下文设计得很好,LLM 仍然有可能误解事实、或者直接产生“幻觉(hallucination)”。
要提高系统的可靠性,我们需要引入一个质量控制步骤,在 Writer 的成果被接受为“最终产出”之前,先做一次检查。

第一步,是在 MAS_MCP_control.ipynb 中更新现有的 researcher_agentwriter_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
  • 输出非常简单:

    • 如果草稿中的所有陈述都能在摘要中得到支持,则返回 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_summarydraft_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 智能体就是这种思路的例子:
每个智能体都围绕一个明确的单一职责被设计出来,并且把这件事做到很好,如下图所示:

image.png

图 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 来让智能体之间通过标准结构进行通信。
随后,我们设计了整个系统的架构,利用语义蓝图来定义智能体团队的角色与工作流。

接着,我们一步一步搭建系统:

  1. 从一个用于创建标准 MCP 消息的函数开始;

  2. 构建每个智能体,并为它们设置独特技能和专用 system prompt;

  3. 构建 Orchestrator 来管理上下文流转:

    • 它从 Researcher 那里接收输出,再把它传递给 Writer;

最终,我们得到了一个可运行的 MAS
它能接收用户目标,自主完成研究与写作的全过程。

最后,我们通过为架构增加错误处理和校验逻辑,让这个原型更加健壮。

到现在为止,你已经从零设计、实现并稳固了一套 MAS
你可以使用这些高级工具,去构建下一代 AI 解决方案。

下一章,我们将把这段旅程提升到新层次:
通过引入 RAG(检索增强生成) ,让 MAS 能够访问外部数据。