本文面向对使用Google的 A2A和Anthropic的 MCP构建AI代理感兴趣的程序员.
本文将探讨各种架构组件,包括人类, 副驾驶, AI 代理, AI 模型, MCP 主机/客户端/服务器. 由于数据会持续在这些组件之间传递,我将首先讨论如何思考输入到组件中的数据类型以及从组件中输出的数据类型. 这种思考方式在与其他构建 AI 代理的工程师交流时对我非常有帮助.
随后,我将逐一介绍各组件,阐述其在整体架构中的主要功能,以及何时如何与其他组件协同工作. 读完本文后,我希望你能理解每个组件的用途,并掌握实现这些功能的最佳方式. 这正是我当初学习这些内容时,希望能够阅读到的文章.
人类, AI 模型与软件集成
人类和 AI 模型均接受不精确的输入,如自然语言文本, 图像/视频和音频,并试图"理解"或"解读"这些输入. 由于不精确的输入存在解释空间,不同的人类和 AI 模型可能会产生不同或不精确的输出:
Imprecise Data ⇨ Human/AI ⇨ Imprecise Data
另一方面,软件接受精确输入,如标量值(布尔值, 整数, 浮点数), 格式良好的字符串(URL, 日期/时间, UUID, JSON, XML, CSV 等)以及由这些组成的结构/数组. 由于精确输入不受解释影响,不同软件对同一输入的解释完全一致. 例如,用一种编程语言编写的服务可以创建并输出一个 JSON 对象给用另一种编程语言编写的客户端,而服务和客户端都以完全相同的方式理解该 JSON 对象. 此外,软件会产生精确的输出:
Precise Data ⇨ Software ⇨ Precise Data
人类和 AI 模型通常会产生不精确的输出. 但它们可以尝试产生精确的输出:
csharp
Imprecise Data with Precise Data ask ⇨ Human/AI ⇨ attempted Precise Data
例如,人类希望将脑海中不精确的算法想法转换为某种编程语言的源代码(精确输出). 同样, AI 模型可以通过不精确的自然语言提示生成精确的数据输出. 以下是一个 AI 提示的示例:
vbnet
Given the following structure:
structure Person {
string FirstName
string LastName
string CompanyName
string EmploymentDate // in YYYY-MM-DD format
}
Produce a JSON object (and nothing else) for
"On June 1st of 2015, John Smith, joined Microsoft as a full-time employee."
当我向 AI 模型发送这个不精确的输入提示时,我得到了以下精确输出:
json
{
"FirstName": "John",
"LastName": "Smith",
"CompanyName": "Microsoft",
"EmploymentDate": "2015-06-01"
}
上述 JSON 对象可作为精确输入传递给软件进行可靠处理.
当要求 AI 模型输出精确数据时,如果提示中包含的名称(如 FirstName, LastName 等)具有描述性, AI 模型就能更好地理解其含义,从而大大提高成功率.
当然,我们必须认识到人类和AI模型都可能出错,这就是为什么我强调它们只是"尝试"输出精确数据. 但这并非绝对保证,如果软件尝试处理不精确的输入,很可能导致失败,而我们显然希望避免这种情况:
scss
Imprecise Data ⇨ Software ⇨ 💣(FAILURE)
以下是本次讨论的关键要点:
- 不精确数据对人类和AI模型而言是有效输入,但对软件而言并非如此.
- 若人类或AI模型输出不精确数据,该输出旨在供人类或其他AI模型使用.
- 若人类或AI模型输出精确数据,该输出旨在供人类, AI模型或软件使用. 然而,尝试输出精确数据可能失败,此时传递不精确数据可能导致不可预测的结果.
使用A2A的AI代理
AI代理代表人类或其他AI代理追求目标并完成任务. 每个AI代理专精于某一特定技能集,因此可能需要依赖其他专精的AI代理来完成任务的某些部分. 下图展示了人类使用旅行预订代理的情景,该代理可能进一步调用其他AI代理来完成整个旅行的预订.

AI 代理接受输入请求,并基于其拥有的领域特定知识对请求进行推理. 这将产生一个执行计划,然后利用其可用的各种工具执行操作. 在本节中,我们将探讨由 Google A2A 定义的 AI 代理. 下图提供了一个可供参考的视觉示意图.

从消费者的角度来看, AI 代理 以HTTP服务的形式实现,该服务暴露的操作接受不精确的输入并返回不精确的输出. 此外, AI 代理具有名称, 描述以及一组技能(每项技能都有自己的名称和描述),用于描述该 AI 代理能够执行的任务类型. 所有这些属性都是不精确的字符串值,因此人类或AI模型(而非软件)可能会选择特定的AI代理并使用它来执行任务. 以下是一个AI代理属性的示例:
sql
Name: GeoSpatial Route Planner Agent
Description: Provides advanced route planning, traffic analysis,
and custom map generation services. This agent can calculate
optimal routes, estimate travel times considering real-time
traffic, and create personalized maps with points of interest.
Skill #1:
Name: Traffic-Aware Route Optimizer
Description: Calculates the optimal driving route between two or more
locations, taking into account real-time traffic conditions,
road closures, and user preferences (e.g., avoid tolls,
prefer highways).
Skill #2:
Name: Personalized Map Generator
Description: Creates custom map images or interactive map views based on
user-defined points of interest, routes, and style
preferences. Can overlay data layers.
由于我专注于概念,因此忽略了一些细节. 如需更完整的AI代理示例,请参阅Google 的示例代理卡片.
客户端AI代理会根据服务器AI代理的公开描述和技能,与服务器AI代理发起一个任务. 发起新任务会在客户端和服务器AI代理之间建立一个可能长期持续的对话. 任务的推进发生在客户端AI代理向对话中添加用户消息(由AI模型处理)或服务器AI代理添加代理消息(AI模型的响应)时.
需要注意的是,AI 模型是无状态的,因此 AI 代理必须尽可能保留对话历史并将其发送给 AI 模型以推进对话. 每个 AI 模型都会记录其 上下文窗口,指示其支持的对话最大大小. 为了管理对话历史大小,AI 代理经常采用删除旧消息或尝试删除与对话"相关性较低"的消息等技术.
每条消息包含1个或多个部分(文本, 文件URI/数据或JSON数据). 任务的最终代理消息包含由服务器AI代理生成的输出产物(文档, 图像, 结构化数据). 该产物也包含1个或多个部分.
以下是一个任务对话消息的示例(仅关注核心概念). 完整示例及更多细节请参阅:规范 --- 代理间协议(A2A).
1. 客户端发起与AI代理的任务对话:
vbnet
User Message:
Parts:
Text: I'd like to book a flight
2. AI 代理响应并提示需要输入:
vbnet
State: input-required
Agent Message:
Parts:
Text: Sure, I can help with that! Where would you like to fly to,
and from where? Also, what are your preferred travel dates?
3. 客户端将所需输入消息添加到对话中:
vbnet
User Message:
Parts:
Text: I want to fly from New York (JFK) to London (LHR) around
October 10th, returning October 17th.
4. AI 代理完成任务处理:
vbnet
State: completed
Agent Message:
Parts:
Text: Okay, I've found a flight for you. Details are in the artifact.
Artifacts:
Parts:
Data: {
"from": "JFK",
"to": "LHR",
"departure": "2024--10--10T18:00:00Z",
"arrival": "2024--10--11T06:00:00Z",
"returnDeparture": "..."
}
内部地,AI 代理有一个主执行循环,称为 编排器(Orchestrator) . 编排器的职责是推动任务对话直至完成. 有多种工具可协助开发者构建编排器,例如LangChain, Semantic Kernel和AutoGen.
处理新消息时,编排器通常需要通过添加AI代理专属的知识 来增强消息的提示,从而提升输出质量. 这种技术被称为检索增强生成(RAG). 具体而言,编排器会为用户消息的提示生成一个嵌入向量,然后对数据文件, PDF 等进行相似性搜索,以找到与用户提示内容相似的源内容. 编排器将此内容嵌入到提示中.
此时,编排器将整个对话发送给 AI 模型. AI 模型输出以下内容之一:
- 一个不精确的结果,该结果将附加到任务的对话中.
- 一组精确的工具名称,用于调用. 工具名称可以指代另一个专用AI代理,也可以指代某些软件功能(通常由MCP服务器实现,将在下面讨论). 调用工具是编排器的职责,而调用软件功能正是使AI代理能够执行操作并具备代理性的关键. 例如,软件功能可以添加日历条目, 创建数据库记录, 发送电子邮件等. 每个工具的字符串结果将附加到任务的对话中. 本文后面的序列图将可视化此工作流.
编排器随后循环继续对话,直到任务完成.
通过 MCP 主机, 客户端和服务器访问 AI 代理
本节将探讨由Anthropic的 MCP定义的MCP主机 , MCP客户端 及MCP服务器组件. 该协议的最大优势在于,它使任何遵循该协议的 AI 代理能够利用任何MCP服务器实现的专用功能来完成任务. 当 AI 代理执行此操作时,它也成为了我们所说的 MCP 主机.
要使该功能正常工作,必须首先将1个或多个MCP服务器注册到AI代理中; 具体操作方式因AI代理而异,但未来可能也会在此领域形成统一标准. 运行时,MCP主机为每个MCP服务器实例化1个MCP客户端. 部分MCP服务器以可执行进程形式实现,与AI代理运行在同一台本地PC上,并通过标准输入/输出进行通信. 其他MCP服务器则以HTTP服务的形式实现; MCP客户端通过向服务URL发送HTTP请求与之远程通信.
每个MCP服务器向AI代理及其编排器暴露工具 (函数), 资源 和提示词.
MCP服务器工具
在实例化 MCP 客户端/服务器后,AI 代理会向每个服务器请求其可用工具列表. 每个工具包含名称, 描述以及指定调用该工具所需精确 JSON 输入的模式. 以下是一个示例(来自 这里):
css
Tool #1:
Name: calculate_sum
Description: Add two numbers together
InputSchema:
{
type: "object",
properties: {
a: { type: "number" },
b: { type: "number" }
},
required: ["a", "b"]
}
Tool #2:
Name: execute_command
Description: Run a shell command
InputSchema:
{
type: "object",
properties: {
command: { type: "string" },
args: { type: "array", items: { type: "string" } }
}
}
AI 代理的编排器随后将上述所有工具信息添加到最新对话用户消息中. 对话随后发送至 AI 模型,该模型可能输出编排器应调用的工具的精确名称. AI 模型基于工具的文本描述选择最佳工具进行调用. AI模型还会输出一个精确的JSON对象,其值与输入模式(InputSchema)匹配; 这些值由AI模型根据属性名称初始化.
AI模型可能选择让编排器调用多个工具. 例如,对于"巴黎的天气和当前时间是什么?"这样的提示,需要调用一个获取巴黎天气的工具和另一个获取巴黎当前时间的工具. 编排器可以并行执行这两个调用以提升性能.
调用工具后,其字符串输出会被放入新消息并附加到对话中.
以下是 展示上述工作流的示意图:

使用工具时需注意以下几点:
- 由于AI模型存在错误,它可能无法选择最佳工具,并可能生成损坏或不正确的JSON对象. 编排器无法检测到此类问题,因此强烈建议AI代理在调用工具前提示人类用户请求授权. 提示操作还允许人类用户在将JSON对象发送给任意工具前,检查其中是否包含敏感数据.
- 编排器向AI模型提供的工具越多,选中最佳工具的可能性越低. 编排器可能采用描述关键词匹配, 语义相似性或预定义映射等技术,从大量潜在工具中筛选出相关工具.
- 即使AI模型选中了最佳工具并准备了完美的JSON对象,工具的代码仍可能执行任意操作,这可能导致严重后果(如删除数据). 这也是许多编排器在调用工具前会向人类寻求授权的原因.
MCP 服务器资源
AI 代理可能支持读取资源/文件并将其数据包含在传递给 AI 模型的对话用户消息(提示)中,以便模型对数据进行推理.
在实例化 MCP 服务器后,AI 代理会向每个 MCP 服务器请求其可用资源列表. 每个资源必须包含名称和 URI,可选地包含 MIME 类型和描述. 以下是一个示例:
yaml
Resource #1:
Name: Application Logs
URI: file:///logs/app.log
Mime Type: text/plain
Description: The application's log file.
Resource #2:
Name: Annual Report
URI: https://contoso.com/annual-report.pdf
Mime Type: application/pdf
Description: Contoso's Annual Report.
Resource #3:
Name: Temperature
URI: http://temperature/{deviceName}{?units}
Mime Type: text/plain
Description: Device temperature.
Resource #4:
Name: Stock Info
URI: http://stock/{ticker}
Mime Type: application/json
Description: Stock details.
上述资源 #3 和 #4 是使用资源模板 URI 的示例. 这些 URI 包含遵循 RFC 6570 语法的变量. 要从 URI 模板资源中读取数据,必须有人填充这些变量; 这可以是人类, 了解模板变量并知道如何设置它们的 AI 代理,或 AI 模型.
选择要读取的资源 URI 取决于 AI 代理的具体实现. 如果 AI 代理提供 UI ,则可允许人类从可用资源列表中选择. 或者,AI 代理可生成包含部分资源属性(描述, URI 等)的不精确提示消息,并要求 AI 模型精确输出资源 URL; 以下是一个此类提示的示例:
perl
Get the office-23 thermostat's temperature in Celsius given these URLs:
The application's log file.: file:///logs/app.log
Contoso's Annual Report: https://contoso.com/annual-report.pdf
Device temperature.: http://temperature/{deviceName}{?units}
Stock details.: http://stock-info/{ticker}
Return just the URL
当我将此不精确的输入提示发送给AI模型时,我得到了以下包含填充变量的精确输出:
ini
http://temperature/office-23?units=celsius
一旦选定资源URI,AI代理会请求拥有该URI的MCP服务器读取其数据. 此数据通常会被嵌入到下一条消息提示中,该提示会被追加到对话历史记录并发送给AI模型进行处理.
MCP服务器提示
AI 代理 可能支持从MCP服务器获取提示建议的功能. 通常,这些提示建议是由人类通过斜杠命令, 快速操作, 上下文菜单项, 命令面板条目等选取的. 当然,这需要一个具备应用程序或网站等 UI 的AI代理; 请参阅本文后面的"UI AI代理"部分.
在实例化MCP服务器后,UI AI代理会向每个MCP服务器请求其有用提示词列表. 每个提示词必须包含名称, 描述及一组参数. 以下是一个示例(来自这里):
yaml
Prompt #1:
Name: analyze-code
Description: Analyze code for potential improvements
Argument #1:
Name: language
Description: Programming Language
Required: True
Prompt #2:
Name: analyze-project
Description: Analyze project logs and code
Argument #1:
Name: time-frame
Description: Time period to analyze logs
Required: True
Argument #2
Name: fileUri
Description: URI of code file to review
Required: True
如上所述,通常由人类选择提示,但编排器可通过描述关键词匹配, 语义相似性或预定义映射来选择提示. 一旦选择提示,参数值需由人类或AI模型提供.
随后,编排器将名称/参数发送至MCP服务器,该服务器返回包含参数值的1条或多条消息至编排器. 这些消息还可能包含MCP服务器的部分资源数据. 例如,当MCP服务器被要求处理analyze-project提示词(参数为time-frame=1h和fileUri= file:///path/to/code.py)时,可能返回以下消息:
yaml
User Message (AI Model prompt instruction):
Text: Analyze these system logs and the code file for any issues:
User Message (appended to AI Model prompt from resource URI):
URI: logs://recent?timeframe=1h
Mime Type: text/plain
Text: [2024--03--14 15:32:11] ERROR: Connection timeout in network.py:127
[2024--03--14 15:32:15] WARN: Retrying connection (attempt 2/3)
[2024--03--14 15:32:20] ERROR: Max retries exceeded
User Message (appended to AI Model prompt from another resource URI):
URI: file:///path/to/code.py
Mime Type: text/x-python
Text: def connect_to_service(timeout=30):
retries = 3
for attempt in range(retries):
try:
return establish_connection(timeout)
except TimeoutError:
if attempt == retries - 1:
raise
time. Sleep(5)
def establish_connection(timeout):
# Connection implementation
pass
编排器将这 3 条用户消息附加到对话中,并将更新后的对话传递给其 AI 模型进行处理. AI 模型的输出由编排器按常规方式处理: 调用工具或将代理消息附加到对话中. 编排器随后循环继续对话,直至任务完成.
MCP 服务器采样
MCP 服务器可能希望在其内部工作中使用 AI 模型. MCP 协议通过一个名为 采样(sampling) 的功能支持此需求. 通过采样,MCP 服务器向 AI 代理发送一个 sampling/createMessage 消息,要求其使用其中一个 AI 模型来完成工作. 此方法的优势在于,MCP 服务器无需自行配置, 支付或管理 API 密钥/密文以访问其自有 AI 模型. 尽管这听起来对希望具备此功能的 MCP 服务器颇具吸引力,但实际上,目前支持采样的流行 MCP 客户端寥寥无几,如 示例客户端 --- MCP 所示.
MCP 服务器根目录
AI 代理可能希望将 MCP 服务器聚焦于潜在资源的子集. 典型的示例是 IDE(如 VS Code)将当前项目代码文件的根目录发送给所有 MCP 服务器,以便它们仅关注项目源代码文件(资源),而非用户电脑上的所有文件. AI 代理向其 MCP 服务器发送根目录(一组 URI). 这些 URI 通常是文件系统路径(file:),但也可能是其他类型. 以下是一个 MCP 服务器可能接收的根目录示例(来自 这里):
json
{
"roots": [
{
"uri": "file:///home/user/projects/frontend",
"name": "Frontend Repository"
},
{
"uri": "https://api.example.com/v1",
"name": "API Endpoint"
}
]
}
在AI代理的生命周期内,它可能会向其MCP服务器发送新的URI根列表. 例如,当IDE用户打开不同项目时. 与采样类似,目前支持根的流行MCP主机非常少,如示例客户端 --- MCP所示.
UI AI代理
在上述讨论中,我将 AI 代理定位为远程 HTTP 服务. 然而,客户端应用程序或网站也可以作为自己的 AI 代理,拥有自己的编排器, AI 模型, 知识(RAG), AI 代理列表以及 MCP 客户端/服务器. 但这些 AI 代理具有 UI,而非响应 HTTP 请求. 微软将任何带有"AI 上的 UI"的应用程序或网站称为 Copilot. 当然,人类与 UI AI 代理(Copilot)进行交互. 以下是之前展示的图形的更新版本,其中包含 UI AI 代理.

既然我们已经讨论过AI代理,关于UI AI代理就没什么好说的了,除了它拥有UI. UI AI代理通常具有类似聊天的界面,人类输入用户消息,编排器执行其任务,生成的代理消息在UI中显示.
然而,与普通AI代理不同,UI AI代理可以访问应用程序或网站中可用的任何上下文, 资源, 工具或提示. 实际上,UI AI代理本身就是一个MCP服务器. 事实上,UI AI代理可以作为内部MCP服务器实现其自身功能,而非本地或远程MCP服务器. 这简化了实现 UI AI 代理的代码,除非代理需要更丰富或更集成的资源/工具访问权限. 当然,UI AI 代理还可以利用其他远程 AI 代理以及任何其他本地安装或远程可访问的 MCP 服务器.
总结一下
综上所述, AI 代理的架构整合了由不同个人和组织开发的各类组件. 其真正的优势, 力量与灵活性,源于这些组件采用行业标准协议,例如谷歌的 A2A 和Anthropic的 MCP . 本文对这些组件及其相互作用进行了全面概述,强调了不精确和精确数据处理的重要性,以及编排器在管理对话和任务中的作用.
AI 技术的持续发展有望带来更加先进和强大的AI代理. 多AI模型的集成, 先进工具和资源的运用,以及自主处理复杂任务的能力,只是未来令人期待的发展方向中的一小部分.
好吧, 今天的内容就分享到这里啦!
一家之言, 欢迎拍砖!
Happy coding! Stay GOLDEN!