AI-Agents-实战-二-
AI Agents 实战(二)
译者:飞龙
你将有两种选择:
直接从 from_model_id 方法加载你的模型:
from langchain_huggingface import HuggingFacePipeline
llm = HuggingFacePipeline.from_model_id(
model_id="microsoft/Phi-3-mini-4k-instruct",
task="text-generation",
pipeline_kwargs={
"max_tokens": 100, "top_k": 50,
"temperature": 0.1,
},
)
llm.invoke("Your query here")
快速提示: 使用 AI 代码解释器和快速复制(Quick Copy)功能增强你的编码体验。在下一代 Packt Reader 中打开本书。点击复制(Copy)按钮 (1) 将代码快速复制到你的编码环境中,或点击解释(Explain)按钮 (2) 让 AI 助手向你解释一段代码。
function calculate(a, b) {
return {sum: a + b};
};
购买本书免费赠下一代 Packt Reader。扫描二维码访问 packpub.com/unlock,然后使用搜索栏通过名称查找此书。双检显示的版本以确保获取正确版本。
使用 Hugging Face 端点(你可以创建一个免费账户来访问它——在此处了解更多关于 HF 账户的信息: https://huggingface.co/pricing ):
from langchain_huggingface import HuggingFaceEndpoint
llm = HuggingFaceEndpoint(
repo_id="meta-llama/Meta-Llama-3-8B-Instruct",
task="text-generation", max_new_tokens=100,
do_sample=False
)
llm.invoke("Hugging Face is")
例如,如果你想在 LangChain 中利用 HF 的聊天模型,请使用以下代码:
from langchain_huggingface import (
HuggingFaceEndpoint,
ChatHuggingFace
)
llm = HuggingFaceEndpoint(
repo_id="microsoft/Phi-3-mini-4k-instruct",
task="text-generation", max_new_tokens=512,
do_sample=False, repetition_penalty=1.03,
)
chat = ChatHuggingFace(llm=llm, verbose=True)
准备本地资源
确保项目目录中存在以下文件和文件夹:
-
piadiniera.db:包含产品和供应商的 SQLite 数据库 -
docs/:包含 PDF 文件的文件夹(例如:食品安全证书、所有者历史)
运行购物车 API
在 localhost:3000 上启动一个 JSON 服务器以启用购物车管理工具:
json-server --watch cart-data.json --port 3000
熟悉笔记本
打开仓库中提供的 Jupyter 笔记本并开始与 AI 代理交互以完成以下操作:
-
查询产品详情
-
将商品添加到购物车
-
搜索餐厅文档
-
使用 LangSmith 集成评估代理响应
启动移动应用程序
启动 Streamlit 前端以与代理交互(应用将运行在你的本地主机8080):
streamlit run app.py
LangChain 生态系统介绍
LangChain 于 2022 年 10 月推出,是一个开源框架,旨在简化由大语言模型(LLM)驱动的应用程序开发。最初,它提供了将 LLM 连接到外部数据源和实用程序的工具,有助于创建更具动态性和上下文感知能力的应用程序。
随着时间的推移,LangChain 从最初的框架演变为一个全面的生态系统。这一转变源于 AI 应用复杂性的增加,以及对管理整个 LLM 驱动解决方案更健壮工具的需求。现在,LangChain 涵盖了一套组件和集成,旨在支持开发者从原型设计到部署的全周期。
根据官方文档,整个 LangChain 生态系统允许你构建、运行和部署 AI 驱动的解决方案。让我们根据上图详细说明这三个步骤。
构建——架构基础
生态系统的基础是两个开源库:LangChain 和 LangGraph。
这两个组件代表了 AI 应用逻辑的核心构建块:
-
LangChain提供了链(chains)、代理(agents)、工具和内存等模块化组件,帮助开发者编排模型行为。它关注可组合性和集成,便于工作流的开发和扩展。 -
LangGraph虽然较新,但它引入了基于状态机的强大范式——这种计算模型定义了一系列状态及其转换,并通过事件或条件进行控制。它允许使用基于图的抽象构建多代理系统和复杂的控制流(例如:循环、分支、条件推理)。这对于需要持久状态和复杂规划的应用特别有价值。
注意
在本章中,我们将利用
LangChain作为库,而在下一章处理多代理系统时,我们将使用LangGraph。
这些工具共同构 LangChain 生态系统的构建时逻辑和控制平面。它们是开源的,确保了灵活性、透明度和社区驱动的创新。
运行——运营层
一旦构建了代理,下一步就是部署。这就是 LangGraph Platform 发挥作用的地方。LangGraph Platform 提供了一个托管环境来实现以下操作:
-
托管并部署
LangGraph代理或工作流 -
流式交互
-
集成人工回路(human-in-the-loop)流
-
处理实时并发和状态管理
该平台弥补了实验与生产之间的鸿沟,抽象了在云环境中运行 LLM 代理通常涉及的设施开销。
与平台配套的是集成层(Integrations layer,同样开源的),它允许开发者将其代理连接到以下内容:
-
APIs 和 webhooks(后者是允许外部系统在 LangChain 执行过程中发生特定事件时接收实时通知或数据的一种)
-
向量数据库和数据存储器
-
外部工具,如计算器、检索系统或第三方系统
这些集成为代理提供了实用的能力,使其能够根据外部数据和系统执行有意义的动作,我们将在下一段中介绍其中一些。
管理 — 可观测性与迭代
管理通常是决定现实世界 AI 应用成败的关键。在 LangChain 生态系统中,这由 LangSmith 处理。
LangSmith 是一个商业平台,旨在解决这些核心需求:
-
调试代理行为
-
监控使用情况和性能
-
评估输出质量
-
管理提示词(prompts)和数据集
-
用于监督改进的标注和反馈循环
与传统的日志或 APM 工具不同,LangSmith 是专为 LLM 原生应用构建的,它不仅能洞察“发生了什么”,还能理解“为什么发生”——从提示词结构到工具调用以及中间推理步骤。
通过将 LangSmith 集成到你的代理开发周期中,你可以确保能够做到以下:
-
追踪失败或意外输出
-
评估提示词版本或工具逻辑的有效性
-
在生产环境中不断完善和优化应用程序
考虑到上述组件,LangChain 生态系统不再仅仅是一个开发友好的框架——它是一个面向 AI 代理整个生命周期(以及更广泛的 AI 驱动解决方案)的结构化平台。
在下一节中,我们将深入探讨可以在“构建”级别利用的某些预构建组件,并准备用它们来初始化我们的 AI 代理。
预构建组件概述
LangChain 提供了一套全面的预构建组件,促进了复杂的语言模型应用的开发。这些组件被分为四个主要类别:检索增强生成 (RAG)、存储与索引,以及提取:
-
RAG: 如前面的章节所示,RAG 使语言模型能够在生成响应之前,动态地从外部源(通常是文档存储器或向量数据库)中检索相关信息,从而超越其静态知识。LangChain 为整个 RAG 生命周期提供了原生支持:
-
文档摄取与分片: 加载材料(如 PDF、Notion 页面、HTML 或 markdown)并使用灵活的
TextSplitters将其拆分为具有语义意义的分片。这些分片更便于嵌入和检索。 -
嵌入生成: 每个分片都使用嵌入模型(例如 OpenAI、Cohere、H Face)嵌入到高维向量。
-
存储在向量数据库中: 这些向量存储在可插拔的向量存储器中(如
FAISS、Chroma、Weaviate和Pinecone),允许基于相似性的高效检索。 -
检索器接口: LangChain 同时支持基础检索器和高级检索策略,如
MultiQueryRetriever、ParentDocumentRetriever和ContextualCompressionRetriever,每种策略都是针对在精度、召回率和相关性有不同权衡的用例而设计的。
-
RAG 检索对于文档问答、法律助手、客户支持机器人和研究副驾驶等应用至关重要——任何需要对外部数据进行实时对齐的场景。
-
存储与索引: 支持流水线的是一套专门用于存储和索引的工具。这些组件处理数据的后端准备,确保在检索期间可以高效地访问:
-
文档加载器: LangChain 包含了针对 50 多种数据源的文档加载器,如本地文件、APIs、网页、Notion 数据库、Google Docs 等。这些加载器将内容标准化为 LangChain 的文档格式。
-
文本拆分器: 它们根据字符数、句子或递归启发式对文档进行分割,同时保留内容的逻辑完整性(例如将段落保留或将标题与内容连接)。
-
向量存储器: LangChain 通过通用接口与许多向量数据库集成。开发者可以在不更改检索逻辑的情况下,切换
FAISS、Chroma、Qdrant或 Azure Cognitive Search。 -
元数据与过滤: 嵌入元数据(如来源、日期或主题)与文档分片一起存储,允许过滤检索或混合检索(结合关键词搜索和向量搜索)。
-
这一层是任何依赖大规模结构化或半结构化知识访问的系统的基础。
-
提取: LangChain 还提供了信息提取能力,涉及从非结构化文本中识别并结构化特定信息。这对于需要从文档、聊天或响应中挖掘数据并以结构化形式(例如 JSON 或数据库记录)提供的领域非常有用:
- 用例包括自动填写表单、将会议记录总结到 CRM 字段中、将法律文本转换为条款映射,或从事件描述中提取时间线。
-
代理 (Agents): LangChain 中的代理框架引入了更深层次的自主性和灵活性。与静态链不同,代理可以对任务进行推理,选择使用哪种工具,并根据中间步骤的结果进行调整:
-
工具抽象: 工具是封装了元数据(名称、描述、输入模式)的 Python 函数,代理可以在相关时调用它们。LangChain 与 OpenAI 的函数/工具调用能力无缝集成。
-
工具包 (Toolkits): 为常用平台(如 SQL 数据库、文件系统、Python REPL 或浏览器自动化)提供预包工具集。工具包极大缩短了生产时间。
-
代理执行器 (Agent executors): 这些组件编排代理的推理逻辑、跟踪内存、调用工具并管理中间输出。选项包括
React-OpenAIToolsAgent和ConversationalAgent,取决于所需的结构和灵活性水平。 -
多工具推理: 代理通过链式工具调用、解释结果和规划下一步来执行多步任务——适用于复杂工作流,例如“寻找供应商、检查库存、生成报价”。
-
LangChain 代理可以像动态助手一样,跨系统编排逻辑,无论是客户支持、销售自动化还是研究任务。
即使并非详尽(会随时间不断更新),上述列表让我们了解 LangChain 预构建组件背后的“模型”。事实上,这四类组件代表了 LangChain 不仅仅构建 LLM 封装器,而是构建可组合 AI 系统的承诺——而且正如我们反复提到的,模块化和可组合性是构建代理系统的关键!
通过抽象常见挑战(例如如何摄取文档,以及检索知识、提取的数据或跨工具推理),LangChain 让开发者能够有信心地构建智能、可扩展且生产级的 AI 解决方案。
现在我们拥有了构建第一个 AI 代理所需的所有工具,让我们编码吧!
用例 — 电子商务 AI 代理
AI 代理正在改变企业与客户互动的方式——提供个性化、实时的协助,这种协助既具有对话性又具有深度功能。在食品和零售行业,这种转变为通过智能自动化增强客户体验、简化运营以及建立品牌忠诚度开启了新的可能性。在本节中,我们将探索该行业中的一个具体案例。
场景描述
我们将为一个真实场景构建一个 AI 智能体:一家名为 Mammachepiada 的现代家族经营的意大利餐厅。这家比饼店(piaderia)的名字源于表达“Mamma che piada”,大致翻译为“哇,好棒的比饼啊!”——它最近通过推出在线商店扩大了业务。
Mammachepiada 的核心特色是新鲜制作的 piadina 比饼,里面填充了高质量的意大利食材:生意大利火腿(prosciutto crudo)、stracchino 奶酪、烤蔬菜、日干番茄等。除了这些即食餐点外,商店还出售单独食材——例如手工奶酪、腌制蔬菜和橄榄油——供想要在家里重现这种体验的顾客。
该业务以电子商务商店的形式运营,客户可以进行以下操作:
-
订购新鲜制作的 piadina 和餐点进行本地送货(特定半径内)
-
购买全国发货的食材和产品
本章的目标是创建一个数字助手——一个 AI 智能体——它可以帮助客户自然地与商店进行交互。用户可能想要执行以下任何操作:
-
检查菜单上的可用物品
-
询问产品成分
-
询问库存可用物品
-
获取 piadina 搭配建议
-
创建自定义购物车并下单
该智能体将准备好协助完成这些任务。
结果将是一个亲切、高效的界面,将 Mammachepiada 的魅力带入数字世界——桥接传统与技术,连接语言与行动。
我们将这个 AI 智能体命名为 AskMamma。
在接下来的章节中,我们将逐步介绍如何一步创建这个 AI 智能体界面,整合检索能力、外部工具和逻辑,以便以一种让客户感到自然的方式处理现实世界中的交互。
AskMamma 的构建模块
在第 1 章中,我们概述了 AI 智能体的主要组成部分:
现在让我们详细介绍这些组件,以设计 AskMamma:
让我们探索图 6.3 显示的每个组件:
-
UI: 智能体将存在于餐厅移动应用程序的上下文中。在本节中,我们将逐一关注核心组件,而在下一节中,我们将看到使用 Streamlit UI 显示的最终结果。
-
LLM: 我们将使用 AzureOpenAI GPT-4o,因为它提供了顶尖的推理能力,同时对延迟进行了优化——这使其成为实时餐厅交互的理想选择。
注意
选择 LLM 意味着要平衡以下因素:
- 质量与成本:更大的模型更智能,但价格更贵。
- 速度与力量:更小的模型响应更快,但可能会忽略细节。
- 上下文需求:更长的对话或大型文档?使用具有更大上下文窗口的模型。
- 多模态:如果你的智能体需要查看图像,请选择 GPT-4o。
选择适合应用需求的模型,而不仅仅是最大的那个。
-
系统消息: 我们正在指示智能体成为意大利餐厅的智能 AI 助手。
-
编排: 我们将利用
LangChain进行编排,利用LangSmith进行可观测性和可追溯性。 -
记忆: 我们的智能体将赋予短期记忆,以跟踪正在进行的对话。
-
知识库: 我们的智能体将了解餐厅老板多年获得的所有相关证书和奖励。
-
工具: 除了提到的 RAG 工具外,我们还有两个额外的工具:
-
数据库工具: 该工具将检索库存物品、其价格、供应商、过敏原和其他相关信息。
-
添加购物车工具: 这是一个 API 工具,将与餐厅应用程序的后端进行通信。事实上,用户的购物车由数据库驱动,该工具将根据用户的查询将项目添加到数据库中。
-
请注意,在这一场景中,我们使用了三种不同类型的工具:作为检索器的非结构化知识库和结构化数据库,以及 API 集成。
注意
我们将在实现中将知识库视为一个工具,利用第 5 章探索的智能体 RAG(agentic RAG)概念。
让我们看看实践中如何这样做。
开发智能体
让我们看看如何逐步构建 AskMamma 智能体:
- 导入库并初始化模型: 在这里,我们将初始化所需的两个模型:作为智能体大脑的 LLM(AzureOpenAI GPT-4o)以及我们用于对非结构化 PDF 进行向量化索引的嵌入模型(AzureOpenAI text-embedding-3-large)。
import os
from dotenv import load_dotenv
from langchain_openai import (
AzureChatOpenAI
)
import requests
from langchain.agents import (
AgentExecutor, create_openai_tools_agent
)
from langchain_core.prompts import (
ChatPromptTemplate, MessagesPlaceholder
)
from langchain.tools import BaseTool, StructuredTool
## 加载环境变量
load_dotenv()
## 访问环境变量
openai_version = os.getenv("AZURE_OPENAI_VERSION")
azure_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT")
openai_key = os.getenv("AZURE_OPENAI_API_KEY")
azure_deployment = os.getenv("AZURE_OPENAI_CHAT_DEPLOYMENT")
## 初始化 Azure OpenAI 模型
model = AzureChatOpenAI(
azure_deployment=azure_deployment,
api_endpoint=azure_endpoint,
api_key=openai_key,
api_version=openai_version,
)
from langchain_openai import AzureOpenAIEmbeddings
embeddings = AzureOpenAIEmbeddings(
azure_deployment="text-embedding-3-large",
api_key=openai_key
)
- 初始化 SQL 工具: 在这里我们将利用 LangChain 的内置组件。这是一个可以与 SQL 数据库工作的工具集,包括但不限于推理架构、运行查询和检查查询正确性。
要初始化工具,你需要一个 LLM 和数据库实例。对于后者,我们将使用具有以下架构的 SQL 实例:
| suppliers | | products |
| --- | --- | --- |
| Supplier ID PK | <--> Supplier ID FK |
| Supplier Name | | Product ID PK |
| Contact Email | | Category |
| Country | | Price |
| | | Stock |
| | | Allergens |
| | | Description |
| | | Calories |
| | | Vegetarian |
关系:一个供应商 -> 多个产品
图 6.4:餐厅数据库的结构
你可以在书籍的 GitHub 仓库中找到数据库。
现在让我们初始化工具包并获取第一个工具:
from langchain_community.utilities.sql_database import SQLDatabase
db = SQLDatabase.from_uri("sqlite:///piaderia.db")
智能代理与工具集成示例
from langchain_community.agent_toolkits.sql.toolkit import (
SQLToolkit
)
sql_toolkit = SQLToolkit(db=db, llm=model)
输出如下:
[QuerySQLDatabaseTool(description=此工具的输入是详细且正确的 SQL 查询,输出是数据库结果。如果查询错误,将返回错误消息。如果返回错误,请重写查询、检查查询并再次尝试。如果遇到 Unknown column 'xxxx' in 'field list' 的问题,请使用 db_schema 查询正确的表字段。=<langchain_community.utilities.sql_database.SQLDatabase object at 0x00000250e33160f0>]
如所示,此工具带有名称(QuerySQLDatabaseTool)和对其能力的描述,以便智能体知道如何调用它。
初始化 add_to_cart 工具
在这里我们需要编写一个正确的 HTTP 操作将项目添加到餐厅应用程序的后端中。为此,我们首先需要准备一个托管会话上下文中用户项目的数据库。在这种情况下,我初始化了一个空的 db.json 对象如下:
{
"cart": []
}
在运行智能体之前,我们需要确保此数据库是开启的(为了演示,我们将在本地运行)。
注意事项
| 注意事项 |
| :--- |
| 此 DB 与 SQL 工具部分提到的 SQL DB 不同!让我们清楚地区分这两者:
SQL DB:这是餐厅的商店记录系统,所有者可以在此跟踪库存、价格、供应商等。db.json:这是在会话上下文中支持用户购物车的 DB。它是应用程序数据库。 |
现在我们已经有了购物车 DB,就可以定义我们的工具了:
## 定义一个向购物车添加物品的工具
@tool
def add_to_cart(item_name: str, item_price: float) -> str:
"""将商品添加到购物车中"""
url = 'http://localhost:3000/cart' # 确保这与 JSON Server 的端点匹配
cart_item = {
'name': item_name,
'price': item_price
}
response = requests.post(url, json=cart_item)
if response.status_code == 201:
return f"{item_name} 已成功添加到购物车。"
else:
return f"添加商品到购物车失败: {response.status_code} - {response.text}"
如你所见,我们使用 @tool 装饰器定义了工具,这是 LangChain 的典型用法。我们添加了名称(add_to_cart)和描述(格式为 """docstring""")。我们还指定了正确调用此工具所需的两个参数:物品名称和相对价格;这意味着代理(agent)将在调用工具之前尽力获取这两个元素。最后,正确的“动作”(action)是针对实时购物车 DB 端点的 POST 方法。
初始化 RAG 工具
在这里,我们希望让我们的代理植根于某些关于餐厅食品合规证书和多年来获得的奖项的上下文。以下是从 PDF 中提取的几个段落:
| 披萨店卫生与食品安全合规报告 | 第 1 页:起源与家族传统 |
| :--- | :--- |
| 第 2 页:HACCP 认证 | 危害分析关键控制点(HACCP)是我们食品安全运营的基础。我们建立了完整的 HACCP 系统,用于识别和监控食品准备和供应过程所有的关键控制点。我们父母的根源可以追溯到意大利艾米利亚-罗马罗马尼亚地区罗马米尼附近的一个小村庄。制作面条不仅仅是食物——它更是一个文化符号。这一传统的创始人 Luca 和 Sofia 在一个家庭农场里长大。他们的祖父母拥有一座家庭农场,传统的配方在那里自豪地代代传承。 |
| 食品处理的每个阶段都面临风险,包括储存、准备、烹饪和供应。我们的员工接受过训练,能够识别此类危害,例如温度和过敏原问题。 | 从小起,Luca 和 Sofia 就沉浸在揉面团的节奏、清晨的烘焙以及迷迭草厨房和新鲜扁饼的香气中。他们的童年既是乐场也是教室。他们不仅学会了制作食物,还学会了如何尊重食物。 |
| 我们的 HACCP 计划由外部认证机构进行审查和审计,确保我们符合最新的科学和法律安全标准。我们保留了所有监控活动、纠正措施和验证结果的记录,可根据要求提供。 | 带着与世界分享这份遗产的梦想,他们着手建立一家餐厅,在尊重其传统的同时,欢迎来自世界各地的美食爱好者。 |
图 6.5:我们将作为知识库使用的食品证书快照
快速提示: 想要查看此图像的高分辨率版本吗?请在下一代 Packt Reader 中打开此书,或在 PDF/ePub 版本中查看。购买此书将免费赠送下一代 Packt Reader。扫描二维码 或访问 packtub.com/unlock,然后使用搜索框通过名称查找此书。双检显示的版本以确保你获取的是正确的版本。
为了创建我们的工具,我们首先需要对 PDF 进行适当的向量化并索引到向量 DB 中。为此,我们将使用 LangChain 中一些预构建的组件进行文档处理,并使用 FAISS 向量数据库作为存储。
定义
Facebook AI Similarity Search (FAISS) 是由 Meta AI 开发的一个开源向量数据库和库,用于密集向量的高效相似性搜索和聚类。它实现了高维向量的快速检索,是语义搜索、推荐系统和基于 LLM 的检索等应用的理想选择。
让我们从初始化 vector_store 对象开始。
import faiss
from langchain_community.docstore.in_memory import InMemoryDocStore
from langchain_community.vectorstores import FAISS
# 使用 L2(欧式)距离创建一个 FAISS 索引,其维度
# 与嵌入函数的输出相同(例如,简单嵌入
# #vector 的长度)
index = faiss.IndexFlatL2(len(embeddings.query("hello world")))
vector_store = FAISS(
embedding_function=embeddings,
index=index,
docstore=InMemoryDocStore(),
index_to_docstore_id={},
)
然后我们可以利用 LangChain 的库对文档进行分块和处理:
from langchain_text_splitters import CharacterTextSplitter
from langchain.document_loaders import PyPDFDirectoryLoader
file_path = ("documents")
loader = PyPDFDirectoryLoader(file_path)
documents = loader.load()
text_splitter = CharacterTextSplitter(
chunk_size=1000, chunk_overlap=0
)
docs = text_splitter.split_documents(documents)
注意,在这种情况下,我们利用了一个文本分割器库——CharacterTextSplitter,它根据字符数分割文本。然而,LangChain 提供了各种类型的分割器,在将大型文档馈入向量存储之前,将其拆分成更小的、易于管理的块。一些最流行的包括:
-
RecursiveCharacterTextSplitter(推荐):尝试在更有意义的边界上进行分割(例如:段落 -> 句子 -> 单词 -> 字符)。当分块太大时,递归地回退到单元。 -
TokenTextSplitter:基于 token 计数分割(对于具有严格限制的 LLM,如 OpenAI 的 GPT 模型有用)。用于将输入与模型上下文窗口对齐。 -
NLKTextSplitter/SpacyTextSplitter:感知的分割器,使用 NLP 库的句子分割。适用于需要更多语法保留的情况。
一旦我们将文档拆分为可管理的块并传递给 vector_store.add_documents(),每个块都会使用嵌入模型转换成高维向量表示。
vector_store.add_documents(documents=docs)
这些向量被索引在向量存储中,允许后续进行相似性搜索。这是一个示例:
-
分块 1:"Mozzarella 是一种意大利奶酪..." 向量:[0.21, -0.45, ...]
-
分块 2:"Prosciutto(火腿肉)与甜瓜非常配对..." 向量:[0.67, 0.10, ...]
-
分块 3:"我们的 piadine 每天新鲜制作..." 向量:[0.33, -0.88, ...]
这些嵌入与元数据(例如,源文档、文件位置)一起存储在向量数据库中。当用户提问时,他们的查询会被嵌入并与存储的向量进行比较,以检索相关的块。
此过程允许系统根据语义含义“检索”知识,而不仅仅是关键词。
现在我们拥有了创建 RAG 工具的所有原料:
retriever = vector_store.as_retriever()
from langchain.tools.retriever import create_retriever_tool
rag_tool = create_retriever_tool(
retriever,
"document_search",
""
搜索并返回关于餐厅证书和所有者历史的信息。
""
)
如你所见,LangChain 中的 vector_store 对象带有一个方法 (.as_retriever),可以自动将其转换为检索器对象。通过这样做,我们可以使用 create_retriever_tool 函数创建我们的工具,正如我们在许多示例中学到的,该函数需要一个工具名称和一个对工具能力的描述。
初始化系统消息
在创建代理之前我们需要的最后一个原料是系统消息(system message)。在 LangChain 中,我们可以利用 ChatTemplate 类,该类旨在为对话语言模型构建提示。
它具有以下功能:
-
基于角色的消息:定义特定角色的消息以设置对话的和流。
-
动态变量:在消息中包含占位符,可以在运行时使用用户输入或其他动态数据进行填充。
-
上下文管理:使用
MessagesPlaceholder作为占位符维护对话历史和中间步骤,使 AI 能够生成连贯且符合上下文的响应。
对于我们的 "AskMamma" 助手,我们将具有以下结构:
# 定义提示模板
prompt = ChatPromptTemplate.from_messages(
("system", """你是一个 Piadineria 餐厅的 AI 助手。
你通过友好、交互式的问题帮助客户探索菜单并选择最佳的 piadine 饼或意大利特产。
当用户询问产品详情(配料、过敏原、素食选项、价格等)时,你可以查询产品数据库。
当用户准备好下单时,询问他们是否想将选定的项目添加到购物车。
如果他们确认,使用你的工具将项目添加到购物车。
当使用工具时,仅响应最终结果。
例如:
Human: 将 Classic Piadina 加入购物车,价格为 5.50
AI: 'Classic Piadina'已成功添加到购物车。"""),
MessagesPlaceholder("chat_history", optional=True),
("human", "{input}"),
MessagesPlaceholder("agent_scratchpad")
)
让我们将其拆分为三个组成部分:
-
系统消息 (System message):这是指令性消息,设定了 AI 的整体行为和边界。
("system", """You are an AI assistant for a Piadineria Restaurant. ...") -
MessagesPlaceholder("chat_history"):这是一个动态占位符,会将完整的对话历史(如果可用)插入提示词中,以便 AI 了解之前说过的内容的上下文。注意它被标记为optional=True,因此对话可以新开始,也可以继续进行的会话。 -
带有输入占位符的人类消息:这是插入当前用户消息的地方。
{input}占位符将在将在运行时替换为用户刚刚输入的内容(例如,“你们有无麸质的选项吗?”)。
("human", "{input}")
MessagesPlaceholder("agent_scratchpad"):在工具增强型工作流期间,这是注入中间代理推理或工具使用痕迹的地方。
注意
这种结构化格式非常适用于以下情况的代理系统:
- 你需要持久内存(
chat_history)
- 代理可以调用工具(例如添加到购物车、查询产品)
- 你希望预先给出清晰的行为指令(通过系统消息)
它确保 AI 行为的一致性,适应上下文,并像真实的助手一样执行操作——而不会产生幻觉或超出允许范围做出响应。
初始化代理
我们现在拥有了初始化代理的所有原料。为此,我们将利用 LangChain 中提供的 create_openai_agent 构建函数,它设计用于构建一个利用 OpenAI 工具调用能力的 AI 代理。
定义
OpenAI 的工具调用能力(以前称为函数调用)使语言模型能够以结构化和动态的方式与外部工具或函数交互。在 AI 代理的领域中,工具调用成为更大编排拼图的一部分,正如我们多次提到的,我们在 LLM 之上额外添加了一层智能,使代理能够决定是否使用工具、选择调用哪种工具、将工具链联起来以及执行后续操作等。因此,虽然工具调用的机制仍然存在,但开发者正逐渐远离编写原始的工具调用逻辑,转向能够为你处理这些推理的代理接口。
让我们初始化它:
## 设置工具包
toolkit = [rag_toolkit, add_to_cart, *sql_toolkit.get_tools()]
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
message_history = ChatMessageHistory()
# 构建 OpenAI Tools 代理
agent = create_openai_tools_agent(model, toolkit, prompt)
# 通过传入代理和工具创建代理执行器
agent_executor = AgentExecutor(agent=agent, tools=toolkit, verbose=True)
agent_with_chat_history = RunnableWithMessageHistory(
agent_executor,
# 这是必要的,因为在大多数现实场景中,需要一个 session id
# 在这里并没有真正使用它,因为我们使用的是简单的内存 ChatMessageHistory
lambda session_id: message_history,
input_messages_key="input",
history_messages_key="chat_history",
)
现在是时候测试它了。在运行代理之前,我们需要部署 db.json,以便代理在需要时可以使用 add_to_cart 工具执行 POST 请求。要运行你的数据库,请在终端中执行以下命令:
npm install -g json-server
cd path_to_your_db
json-server --watch db.json
第 6 章
现在你的 db.json 运行在本地的 3000 端口(默认端口):
图 6.6:作为本地运行的应用数据库
太好了,现在我们可以运行我们的代理了。为了模拟对话,我将使用一个 while 循环:
交互循环
while True:
user_input = input("在此输入你的问题(或输入 'exit' 退出): ")
if user_input.lower() == 'exit':
break
result = agent_with_chat_history.invoke(
{"input": user_input},
# 这是必要的,因为在大多数现实场景中,需要一个 session id
# 在这里并没有真正使用它,因为我们使用的是简单的内存 ChatMessageHistory
config={}
)
运行代码后,我发送了以下输入(注意我发送了一些问题,以假设之前的上下文被保留,从而测试短期记忆):
-
你好
-
你们有哪些食品认证?
-
你们有里里塔奶士吗?
-
是的,把它加入购物车
-
那个物品的供应商是谁?
让我们看看结果。现在我保持了代理的详细模式(verbose),这意味着我们可以看到中间步骤和工具输出。随后,输出在某些中间步骤中截断了,但你可以在 GitHub 仓库中查看完整版本。
使用 LangChain 构建你的第一个 AI 代理
为了让以下内容更易读,我将突出显示用户输入、AI 输出和推理步骤。在其之间,我会添加我的注释,以突出一些值得记住的特定代理模式:
User: Hello
代理理解这是一个需要特定工具的简单交互。
AI: Hi there! Welcome to Piadineria Restaurant. How can I assist you today? Are you looking to explore our menu or need help with something specific?
User: Which food certificates do you have?
代理理解它需要使用特定的查询调用特定的 RAG 工具来回答这个问题。
> Entering new AgentExecutor chain...
Invoking: `document_search` with `{'query': 'food certificates'}`
Piadineria Hygiene & Food Safety Compliance Report
Page 2: HACCP Certification
Hazard Analysis [...]
> Finished chain.
AI: 我们有多种食品安全认证和合规措施:
-
HACCP 认证:我们有一套完整的危害分析和关键控制点 (HACCP) 系统,用于识别和监控产品准备过程中的关键点。该系统由外部认证机构每季度审核一次。
-
当地食品培训与 PIC:piadineria 的每个食品处理人员都根据当地法规完成了食品安全培训。我们在每个地点都配备了负责人 (PIC) 来监督卫生标准并执行最佳实践。你想了解更多关于我们的菜单或其他信息吗?
User: Do you have ricotta cheese in stock?
代理理解它需要调用 SQL 工具。首先,它需要通过 sql_db_list_tables 和 sql_db_schema 工具理解架构,然后将使用 sql_db_query 运行实际查询。
> Entering new AgentExecutor chain...
Invoking: `sql_db_list_tables` with `{}`
responded: Let me check the availability of ricotta cheese for you. I'll need a moment to look it up.
products, suppliers
Invoking: `sql_db_schema` with `{'table_names': 'products'}`
[]
Invoking: `sql_db_query` with `{'query': 'SELECT * FROM products WHERE "Product Name" = \'Ricotta Cheese\'}`
[]
> Finished chain.
AI: 是的,我们有里里塔奶士!详情如下:
产品名称: Ricotta Cheese
类别: Cheese
价格: 7.75
库存: 15 单位
过敏原: 乳制品
描述: 奶油般的里里塔奶士,适合甜点或意面。
卡路里: 120
素食: 是
你想把它加入购物车吗,还是需要其他帮助?
User: Yes, add it to cart
代理理解它需要调用 add_to_cart 工具,并且它能够从之前的对话中检索所需的两个参数——名称和价格。
> Entering new AgentExecutor chain...
Invoking: `add_to_cart` with `{'item_name': 'Ricotta Cheese', 'item_price': 7.75}`
“里科塔奶酪”已成功添加到购物车。里科塔奶酪已成功添加到您的购物车。您还有其他想添加的物品或需要其他帮助吗?
链执行完毕。
一旦执行了此工具,如果你重新运行 db.json 服务器,你将看到它已相应更新:
AI: 里科塔奶酪已成功添加到您的购物车。您还有其他想添加的物品或需要其他帮助吗?
User: 那个物品的供应商是谁?
代理能够再次调用 SQL 工具,这次它将执行连接(join)操作,以查找两个表并提取里科塔奶酪的供应商。此外,我们没有指定物品,而是依赖于代理的上下文感知能力。
> Entering new AgentExecutor chain...
Invoking: `sql_db_list_tables` with `{}`
products, suppliers
Invoking: `sql_db_schema` with `{'table_names': 'products, 'suppliers'}`
[]]
Invoking: `sql_db_query` with `{'query': 'SELECT suppliers."Supplier Name" FROM suppliers JOIN products ON suppliers."Supplier ID" = products."Supplier ID" WHERE products."Product Name" = \'Ricotta Cheese\'}}`
[]]
> Finished chain.
AI: 我们里科塔奶酪的供应商是 Dolce Italia。
您还有其他想知道或添加到订单中的内容吗?
User: 谢谢!
AI: 不客气!如果您有任何其他问题或需要进一步帮助,请随时询问。祝您用餐愉快!
如您所示,代理能够执行以下操作:
-
根据用户的请求调用正确的工具,这归功于每个工具都配备了自然语言描述
-
在用户会话的上下文中保持记忆
-
以对话的方式与用户交互
仅需三个工具,我们就能通过强大的代理来实现良好的用户体验。
然而,我们还缺少拼图中的重要的一块:我们如何监控代理的行为?事实上,在现实场景中,可观测性、可追溯性以及评估和持续改进是在设计端到端架构时需要考虑的关键元素。
在下一节中,我们将探索如何用几行代码来实现这些。
可观测性、可追溯性和评估
可观测性、可追溯性和评估是实现这些目标的关键组件。我们提供了对 AI 代理内部工作机制的洞察,便于调试并实现持续改进。
可观测性是指根据系统的外部输出理解其内部状态。在 AI 代理中,这涉及监控输入、输出、中间过程以及与外部工具或 API 的交互。可追溯性通过提供代理决策过程的详细记录来补充可观测性,包括所采取的动作序列及其背后的原理。它们允许开发者精确定位问题、理解代理行为并确保系统按预期运行。
评估涉及根据指标或基准评估 AI 代理的性能。这可以包括准确性、相关性、连贯性或其他特定领域的标准。定期评估有助于识别改进领域、验证更新并确保代理符合所需标准。它在维护用户信任和满意度方面也起着至关重要的作用。
注意
根据任务和背景,可以使用多种方法来评估 AI 代理。这些包括:
- 人机回评估(Human-in-the-loop),由人类评审员评估代理响应的质量和帮助性。
- LLM 裁判(LLM-as-a-judge),由受信任的模型(例如 GPT-4)对响应的相关性或准确性评分。
- 知识库(KB)验证,适用于检索增强系统,其中检索文档的正确性至关重要。
- 自动指标,例如用于摘要或问答任务的 BLEU、ROUGE 或精确匹配。
随着 AI 代理变得越来越复杂——能够进行推理、使用工具并维护多轮记忆——对健壮的可观测性和评估的需求变得至关重要。与传统的软件系统不同(在传统系统中错误可以追溯到特定的代码行或逻辑分支),AI 代理在概率性和动态性的环境中运行。它们的决策不仅取决于输入数据,还取决于提示词结构、检索到的上下文、工具交互以及之前的对话轮次。这使得调试和性能评估本质上变得更加困难。为了解决这个问题,LangSmith 等工具应运而生,提供了一种结构化的方法来实时监控、跟踪和评估代理行为。
LangSmith 是 LangChain 生态系统的一部分,旨在增强 AI 应用中的可观测性、可追溯性和评估。它提供了记录和可视化代理交互、分析性能指标以及进行系统性评估的工具。
LangSmith 的核心功能包括:
-
执行跟踪:可视化展示代理的每一步决策,包括输入、输出和工具使用
-
性能洞察:监控延迟、Token 数量和错误模式等关键统计数据以优化您的系统
-
灵活评估:使用内置或自定义逻辑运行结构化评估,以评估响应质量
-
提示词版本控制:跟踪、比较和迭代提示词,以随时间推移优化代理行为
-
协作友好:与团队共享运行结果、反馈和评估,以支持共同开发
LangSmith 与各种 AI 框架无缝集成(不限于 LangChain!),为监控和改进 AI 代理提供了统一的接口。
让我们在实践中看看它。
第一步是在以下链接的 LangSmith 设置页面:https://smith.langchain.com/settings 上创建一个 API 密钥。
图 6.8:LangSmith 管理界面
注意,如果你还没有账户,将被提示注册(免费的)。
然后,安装 LangSmith 依赖:
pip install -U langsmith
使用你的变量设置环境。在我们案例中,由于我们在 Jupyter Notebook 中演示代理,我将使用 os 模块直接初始化变量:
from langsmith import Client
client = Client(
api_key=os.getenv("LANGSMITH_API_KEY"),
api_url=os.getenv("LANGSMITH_ENDPOINT"),
)
现在我们有了初始化 LangChainTracer 对象的所有原料,LangChainTracer 是一个回调处理器,用于捕捉应用程序的详细执行跟踪(如链、工具和语言模型交互),并将这些信息发送到 LangSmith。
from langchain.callbacks.tracers import LangChainTracer
tr = LangChainTracer(client=client, project_name="askmama")
如您所示,初始化 LangChainTracer 需要 client 和可选的 project_name 参数——这将是存储和存档日志的文件夹。该跟踪器钩入 LangChain 的执行流,捕获链、工具调用和语言模型调用的开始和结束事件。
跟踪器是我们需要为现有代理添加的唯一额外元素。我们可以在调用代理时将其作为 config 参数传递:
agent_with_chat_history.invoke(
{"input": user_input},
# 这是因为在大多数现实场景中,需要会话 id
# 在这里我们没有真正使用它,因为我们使用的是简单的内存 ChatMessageHistory
config={"configurable": {"session_id": "foo"},
"callbacks": [tracer]}
)
此配置确保代理执行的每一步都记录在 LangSmith 中,允许您可视化操作序列、检查输入输出并识别任何错误或性能瓶颈。
例如,一旦我们运行代理,我们将在 LangSmith 仪表板上看到一个新初始的项目:
图 6.9:LangSmith 的追踪项目
我们确实可以看到许多有趣的统计数据,如错误率、总成本、运行次数等。让我们从 Askmamma 项目概览中获取更多细节:
图 6.10:LangSmith 的项目概览
在这里,我们可以看到三个主要信息来源:
- 运行 (Runs):一次运行是与智能体的单次交互。例如,如果用户输入“Hi”,这就是一次运行。
在每次运行下,我们都可以探索关于智能体活动的许多细节以回答问题。例如,当我们问“你有哪些食品证书?”时,智能体执行了几个步骤,我们可以在 LangSmith 中看到所有的步骤:
图 6.11:LangSmith 的运行概览
我们还可以检查那些失败的任务:
图 6.12:LangSmith 运行步骤概览
在这种情况下,原因是调用 add_to_cart 工具时,由于数据库离线,智能体无法继续执行。
线程 (Threads):线程可以定义为一个会话。线程由唯一标识标记,你可以将它们初始化为名称。例如,在我们的场景中,我们初始化了一个名为 <foo> 的线程:
事实上,你可以在 LangSmith 中看到这个线程:
线程可以帮助你组织运行,并对智能体后台发生的情况有一个更高层次的视角:
使用 LangChain 构建你的第一个 AI 代理
个人 > 追踪项目 > askmamma <foo>
线程 <foo>
| 追踪计数 | 总 Token | 延迟 |
| :--- | :--- | :--- |
| 10 | 8,559 / $0.02532 | 2.33s 5.2% |
-
轮 1
-
轮 2
-
轮 3
-
轮 4
-
轮 5
-
轮 6
-
轮 7
-
轮 8
- 监控 (Monitor):在这里,你可以看到一些实用的图表来对智能体进行 360 度视图,包括追踪计数、LLM 调用次数、成功率等:
askmamma
在评估方面,AI 代理以及更广泛的由 LLM 驱动的应用不能依赖传统的机器学习评估器,例如准确率、假阳性率、ROC 曲线等。事实上,AI 输出在设计上是对话性的,这意味着我们需要新的工具和技术来评估我们 AI 驱动解决方案的性能。
通常,为了评估 LLM 或智能体流水线的质量,你有三个主要组件需要处理:
-
数据集 (Dataset):这是测试用例的集合——输入以及可选的预期输出——你的模型或智能体将基于这些进行评估。将其视为你的地面真值或基准集。
-
目标函数 (Target function):这是你想要测试的实际内容——在我们案例中是智能体的输出。当你运行评估时,LangSmith 将每个数据集输入传递给此目标函数并捕获输出——就像自动化测试执行一样。输出将与预期输出比较或取决于你选择的评估器进行独立评估。
-
评估器 (Evaluator):评估器用于判断目标函数输出的质量。它们可以是基于 LLM 的评估器,如 BLEU 或 ROUGE 等字符串指标,或者传统的指标,如准确率(在我们使用 LLM 进行分类或预测等任务时)。
定义
BLEU(双语言评估对理解)和 ROUGE(面向摘要评估的召回率定向理解)是自然语言处理 (NLP) 中最广泛使用的两种自动评估器,特别是对于文本生成、翻译和摘要任务。
BLEU 是一种基于精确率的指标,通过衡量生成的文本与一个或多个参考文本之间的 n-gram 重叠次数来评估生成文本的质量,通常用于机器翻译。
ROUGE 是一种基于召回率的指标,通过比较参考文本的内容有多少比例出现在输出中来评估文本生成,通常用于摘要任务。
让我们看一个使用我们的 AskMamma 代理的例子。在场景下,我们将利用 LangSmith SDK,你可以从 UI Playground 运行示例评估:
- 初始化数据集:在这里,我们将初始化一个带有输入和输出的数据集。你可以在 LangSmith 中看到这个数据集:
使用 LangSmith 和 Streamlit 评估与部署 AI 代理
初始化 LangSmith 客户端
首先,我们需要初始化 LangSmith 客户端,用于创建数据集、记录实验和管理评估。
from langsmith import Client
client = Client(
api_key=os.getenv("LANGSMITH_API_KEY")
)
dataset = client.create_dataset(
name="askmamma",
description="A dataset for testing askmamma agent"
)
创建示例
接下来,我们为数据集添加示例,每个示例包含一个输入问题和一个标准答案。
examples = [
{
"inputs": {"question": "你有哪些食品证书?"},
"outputs": {"answer": "我有食品安全许可证、HACCP 和有机食品认证。"}
}
]
client.create_examples(dataset_id=dataset.id, examples=examples)
然后,你可以从 UI Playground 运行示例评估。
定义评估器
评估器将判断目标函数输出的质量。对于 AI 驱动的评估,我们将 LLM 指向其他 LLM 的“法官”。让我们初始化 LLM 作为法官。在这种情况下,我们将使用正确性指标。
from openai import AzureOpenAI
openai_client = AzureOpenAI(
endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
api_key=os.getenv("AZURE_OPENAI_API_KEY"),
api_version=os.getenv("AZURE_OPENAI_API_VERSION")
)
from langsmith import wrappers
eval_instructions = "你是一个专门负责学生答案的专家教授。"
def correctness(
inputs: dict, outputs: dict, reference_outputs: dict
) -> bool:
user_content = f"""你正在评估以下问题:\n{inputs['question']}\n真实答案:\n{reference_outputs['answer']}\n你正在评估以下预测答案:\n{outputs['response']}\n用 1-5 分回答,1 分最低,5 分最高。\n你的回答仅包含分数。"""
response = openai_client.chat.completions.create(
model="gpt-4",
temperature=0,
messages=[
{"role": "system", "content": eval_instructions},
{"role": "user", "content": user_content},
]
).choices[0].message.content
return response.strip()
初始化目标函数
最后是评估的逻辑,我们希望你的智能体输出结构与我们定义的评估器相匹配。
def target(inputs: dict) -> dict:
response = agent_with_chat_history.invoke(
第 6 章
151
{"input": inputs['question']},
# 之所以这样做是因为在大多数真实场景中,
# 需要一个会 id
# 在这里并没有真正使用它,因为我们使用的是简单的
# 内存 ChatMessageHistory
config={"configurable": {"session_id": "foo"},
"callbacks": [tracer]},
return {"response": response['output']}
就是这些了!让我们运行评估器。
experiment_results = client.evaluate(
target,
data="QA Askmamma",
evaluators[
correctness,
# 可以在此处添加多个评估器
],
experiment_prefix="first-eval-in-langsmith",
max_concurrency=2
)
你可以从 UI 中检查结果。
图 6.17:LangSmith 的评估结果
如你所见,我们的实际输出返回的正确性得分为 5。对于每个示例,你可以检查相关的指标,如实际输出、延迟、令牌等。
此外,在 AI 代理的语境下,我们还需要考虑工具管理:
-
工具调用(
tool calling)是否正常? -
代理是否能够调用多个工具来解决复杂问题?
-
代理是否足够智能,在不需要时不去调用工具?
换句话说,你需要评估代理的轨迹。让我们来看一个如何做的示例(注意以下代码是来自 LangSmith 官方文档的适配版本,你可以在此处找到: https://docs.smith.langchain.com/evaluation/how_to_guides )。
初始化新数据集
在这种情况下,我们需要按照如下方式添加预期的中间步骤。
import uuid
questions = [
("Do you have ricotta cheese in stock",
{
"reference": "Yes we do have ricotta cheese in stock.",
"expected_steps": ["sql_db_list_tables",
"sql_db_schema", "sql_db_query_checker",
"sql_db_query"]
},
),
("hi",
{
"reference": "Hello, how can I assist you?",
"expected_steps": [], # 期望直接回答
},
),
("Can you add ricotta cheese to the cart with price 5.50?",
{
"reference": "The item 'ricotta cheese' has been added to the cart.",
"expected_steps": ["add_to_cart"]
}
),
("Do you have food safety certificate?",
{
"reference": "Yes, we have a food safety certificate",
"expected_steps": ["document_search"]
},
),
]
uid = uuid.uuid4()
dataset_name = f"Agent Tool Eval Example {uid}"
ds = client.create_dataset(
dataset_name,
description="Example agent evals dataset using search and
calendar checks."
)
client.create_examples(
inputs=[{"input": q[0]} for q in questions],
outputs=[q[1] for q in questions],
dataset_id=ds.id,
)
初始化评估器
在这里,我们将评估中间步骤的正确性,确保调用了正确的工具。
from typing import Optional
from langchain.schemas import Example, Run
def intermediate_step_correctness(
run: Run, example: Optional[Example] = None
) -> dict:
if run.outputs is None:
raise ValueError("Run outputs cannot be None")
intermediate_steps = run.outputs.get("intermediate_steps") or []
trajectory = [action.tool for action, _ in intermediate_steps]
# 这是我们上传到数据集的内容
expected_trajectory = example.outputs["expected_steps"]
## 使用 LangChain 构建你的第一个 AI 代理
score = int(trajectory == expected_trajectory)
return {"key": "Intermediate steps correctness", "score": score}
注意
为了获取代理的中间步骤访问,我们需要确保 return_intermediate_steps 参数等于 True。
agent_executor = AgentExecutor(agent=agent,
tools=toolkit,
verbose=True, return_intermediate_steps=True)
我们还需要确保用正确的 schema 初始化我们的评估器,以便它能够解析我们在测试数据集中拥有的多个输出。
def prepare_data(run: Run, example: Example) -> dict:
return {
"input": example.inputs["input"],
"prediction": run.outputs["output"],
"reference": example.outputs["reference"],
}
根据参考答案判断 QA 答复是否“正确”。
qa_evaluator = LangChainStringEvaluator(
"qa", prepare_data=prepare_data, config={"llm": model}
)
定义目标函数
在这里,我们只是在创建一个函数,该函数将使用适当的配置参数调用我们的代理。
def agent(inputs: dict):
return agent_with_chat_history.invoke(
inputs, config={"configurable": {"session_id": "foo"}})
)
现在我们拥有了运行评估的所有材料。
chain_results = evaluate(
agent,
data=dataset_name,
evaluators=[intermediate_step_correctness, qa_evaluator],
experiment_prefix="Agent Eval Example",
max_concurrency=1,
)
让我们检查 LangSmith UI 中的结果
Agent Eval Example-440d2zfa
| 输入 | 参考输出 | 输出 | 正确性 | 中间 | 延迟 | 状态 | 令牌 |
|---|---|---|---|---|---|---|---|
| Do you have safety... | Exp... | We have a food safety certificate. | us, our restaurant has formi... | 1 correct | 1.00 | 3.3s | SUCCESS | 631 |
| Do you ricotta cheese... | Exp... | We do ricotta cheese in stock. | Yes, we Have Ricotta Cheese | 1 correct | 0.0 | 2.8s | SUCCESS | 181 |
| hi | | Hello, can i assist you? | Hello agent Can assist you today?... | 1 correct | 0.0 | 0.64s | SUCCESS | 462 |
| Can you add ricotta cheese... | Exp... | The item 'ricotta cheese' has been add... | 1 correct | 1.00 | 1.42s | SUCCESS | 1010 |
图 6.18:评估结果仪表盘
太棒了!你可以看到,我们的代理失败了。让我们了解为什么一个界面被评估为错误。
图 6.19:评估结果仪表盘
参考输出
-
参考:We have a food safety certificate
-
预期步骤:
-
sql_db_list_tables -
...
-
-
输出:We do ricotta cheese in stock.
-
预期步骤:
add_to_cart
在移动应用中注入 AI 代理
AI 代理可以通过多种方式消费:作为交互式应用程序、由业务流程触发、或嵌入现有应用程序中。在我们的场景中,我们将探索最后一种方案。
假设我们有一个餐厅的移动应用程序,并希望注入由 AI 代理驱动的对话式界面。新 UI 的外观将如下:
图 6.20:移动应用的 UI
第 6 章
157
为了开发 UI,我们将利用 Streamlit,这是一个开源 Python 库,可以轻松构建和共享交互式应用,特别是数据科学和机器学习工作流。你不需要花费时间进行前端开发,只需专注于 Python 代码,Streamlit 会自动处理 UI。
在其功能中,Streamlit 拥有社区支持的组件和模板,可以轻松与 LangChain 流线集成(例如创建聊天界面或文本生成演示)。这种协同效应让我们利用 Streamlit 的交互性和易用界面以及 LangChain 强大的语言处理能力快速启动 LLM 应用。
内存管理
我们首先初始化一个兼容 Streamlit 的消息历史对象作为对话历史的后端存储。在这种情况下,我们将利用 LangChain 内存类内置的 ConversationBufferMemory,它将所有消息保留在缓冲区中(即运行列表)。
from langchain_community.chat_message_histories import (
ChatMessageHistory
)
from langchain_core.runnables.history import (
RunnableWithMessageHistory
)
from langchain_community.chat_message_histories import (
StreamlitChatMessageHistory
)
msgs = StreamlitChatMessageHistory()
memory = ConversationBufferMemory(
chat_memory=msgs, return_messages=True,
memory_key="chat_history", output_key="output"
)
通过这种方法,我们将向 agent_executor 传递一个兼容 Streamlit 的内存对象。
agent_executor = AgentExecutor(
agent=agent, tools=toolkit, memory=memory,
return_intermediate_steps=True,
handle_parsing_errors=True, verbose=True
)
初始化 Streamlit 状态
在 Streamlit 中,状态是指跨越用户交互保留变量的能力,例如即使在重新运行后,也能记住用户输入过或点击过的内容。
if 'chat_history' not in st.session_state:
st.session_state['chat_history'] = []
if "messages" not in st.session_state:
st.session_state.messages = []
前面的代码确保了当用户打开应用时,内存中有一个地方可以同时存储对话历史(chat_history)和屏幕上显示的消息。其中一个用于追踪说过过的内容,另一个用于显示的内容。如果没有这些,应用将将不知道如何保存或检索之前的消息。
渲染基于持久化的消息
在这个代码块中,我们想要显示用户与 AI 之间的完整历史,从内存中提取它们,以便之前发生的所有内容都能显示。
avatars = {"human": "user", "ai": "assistant"}
for idx, msg in enumerate(st.session_state.messages):
with st.chat_message(avatars[msg["type"]]):
# 如果保存了任何中间步骤,则渲染
for step in st.session_state.steps.get(str(idx), []):
if step["tool"] is None:
continue
with st.status(f"**{step['tool']}**: {step['input']}", state="complete"):
st.write(step[0])
st.write(step[1])
渲染当前状态
这显示仅存储在当前状态中的消息——通常来自当前的运行。
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
渲染 AI 的聊天界面
这段代码在你的 Streamlit 应用底部设置了聊天界面,邀请用户提问——例如“菜单里有什么?”。当用户发送消息时,它会立即作为用户气泡显示在聊天中。然后,AI(agent_executor)会接管:它处理提示词,可能在此过程中使用工具,并在助手气泡中回复答案。
prompt = st.chat_input("What is in the menu?")
if prompt:
st.chat_message("user").write(prompt)
with st.chat_message("assistant"):
st_cb = StreamlitCallbackHandler(st.container(), expand_new_thoughts=False)
response = agent_executor.invoke({"input": prompt}, {"callbacks": [st_cb]})
st.write(response["output"])
st.session_state.steps[str(len(st.session_state.messages)) - 1] = response["intermediate_steps"]
这里很棒的一点是,AI 的推理步骤——例如在后台使用的工具——被 StreamlitCallbackHandler 捕获,并存储在st.session_state中。这让你能够详细可视化这些步骤,让用户一窥 AI 是如何得出答案的。
一旦你的代码准备就绪并保存在app.py文件中,你可以通过streamlit run app.py运行它并在本地查看结果。最终结果看起来如下:
使用 LangChain 构建你的第一个 AI Agent
图 6.21:AskMamma 对话界面的 UI
如你所见,我们能够与 AskMamma 交互并代表我们执行某些操作,例如向购物车中添加一个项目。
总结
在本章中,我们探索了创建功能性 AI Agent 所需的基础概念和实际步骤。从理解 LangChain 的基础到实现高级功能,你对 AI 开发领域获取了宝贵的见解。
通过利用 LangChain 强大的工具和框架,你可以创建能够执行复杂任务并做出明智决策的智能代理。在本章中获得的知识和技能将为 AI 领域的进一步探索和创新奠定坚实的基础。
在下一章中,我们将开始看到多个代理在更复杂的场景下协同工作,进入多代理应用领域。
参考文献
-
Semantic kernel 插件:https://learn.microsoft.com/en-us/semantic-kernel/concepts/plugins?platform=programming-language-csharp
-
LangChain 工具:https://python.langchain.com/docs/how_to/tool_calling/
-
LangChain 生态系统:https://www.langchain.com/
-
LangSmith:https://docs.smith.langchain.com/
-
LangSmith 中间步骤评估:https://docs.smith.langchain.com/evaluation/how_to_guides
-
LangChain 教程:https://github.com/langchain-ai/langsmith-cookbook/blob/main/testing-examples/agent_steps/evaluating_agents.ipynb
订阅获取免费电子书
新框架、演进中的架构、研究发布、生产环境分析——AI_Distilled 将噪音过滤掉,为亲手操作 LLM 和 GenAI 系统的工程师和研究人员提供一份每周简报。现在订阅即可获得免费电子书,以及帮助你保持专注并掌握最新信息的每周见解。
请访问 https://packt.link/TRO5B 订阅或扫描下方的二维码。
第 7 章
165
现在,在多代理系统中,同样的日历功能可以由专门的“日历代理”(Calendar Agent)来处理。该代理将超越基础的数据检索。它可以执行以下操作:
-
理解模糊的查询,例如“我下一次下午什么时候有空?”
-
检测并解决调度冲突(例如,两次会议重叠)
-
与其他代理协商变更——例如,提议一个所有参与者都方便的新会议时间
这种从简单功能到智能代理的转变,说明了多代理系统如何为基础任务带来更丰富的推理能力、灵活性和自主性。
我们为什么要这样做呢?我们不能依靠简单的工具来完成吗?简单的答案是——考虑这个简单的例子——可能是肯定的,我们可以依靠简单的工具。然而,在某些情况下,你可能希望有一个非常专门的代理而不是工具,这样你就可以为其提供清晰的系统消息(system message)并进一步扩展它的能力。此外,这个日历代理本身可能用于不同的流程和应用程序,使其成为你组织中可重复的组件。
让我们考虑另一个例子——这次稍微复杂一些。假设我们想启用一个 AI 应用,帮助你在多个商店进行在线购物。这是一个你可能想要执行的典型查询:“从 XYW 购买新款鞋,欧码 39 码。”
图 7.1:分层多代理系统示例
快速提示: 需要查看此图像的高分辨率版本吗?请在下一代 Packt Reader 中打开本书,或在 PDF/ePub 版本中查看。
下一代 Packt Reader 购买本书时免费赠送。扫描二维码或访问 packtub.com/unlock,使用搜索框通过名称找到它。请双检显示的版本,以确保你获得的是正确的版本。
在单代理设置中,该代理将按顺序搜索鞋子、管理调度、填写地址详情并处理支付。但在模块化的多代理设计中,此任务被拆分为专门的组件:
-
规划代理(Planner Agent)解释指令并协调后续执行。
-
Web 代理(Web Agent)浏览电子商务平台,使用低级 Web 控制代理(例如,Selenium 驱动的机器人)与界面交互——点击、滚动和视觉化搜索。
-
调度代理(Scheduling Agent)检查日历以查看送货可用性,调用日历代理来获取或创建事件。
-
下单代理(Order Placement Agent)通过与以下项协调来完成购买:
-
支付代理(Payment Agent),负责安全处理交易
-
地址代理(Address Agent),验证并格式化收货地址
-
每个代理独立运行,但为更大的任务做出贡献。规划代理充当指挥的角色,协调一个由智能表演者组成的团队:Web 代理、调度、下单代理、Web 控制器、日历代理、地址代理和支付代理。
这种方法非常符合我们在之前的章节中涵盖的模块化和抽象两个核心概念,提供了若干优势:
-
可扩展性(Scalability): 多代理系统在设计上具有自然扩展性。由于每个代理都封装了特定的功能或责任,代理可以独立地部署在不同的服务器、容器、甚至地理区域中。如果某个代理成为瓶颈(例如,流量高峰期间的网络爬虫代理),可以对其进行水平扩展——复制并负载均衡——而不影响系统的其他部分。这种分布式特性使得构建随用户需求而增长的 AI 应用变得更加容易。
-
可维护性(Maintainability): 模块化促进了可维护性。由于每个代理都是自包含的,并通过协议或接口通信,因此可以对其进行更新、重构或替换,而无需重写整个系统。例如,将摘要代理更换为更先进的模型(甚至是不同的模型提供商)不会影响处理检索、过滤或用户交互的代理逻辑。这种清晰的关注点分离(separation of concerns)使得长期演进和实验变得更容易更安全。
-
专业化(Specialization): 每个代理都可以根据其工作进行微调,或使用最佳工具进行构建。某些代理可能使用代码生成 LLM(如 GPT-4),而其他代理可能基于检索增强架构或基于规则的逻辑。同样(使用 Python、JavaScript 等)或框架,只要它们遵循共享协议。这种灵活性允许团队优化性能和成本,为系统的每个组件选择合适的方法。
最后,为了构建和理解多代理系统,我们必须从两个主要视角转向微服务世界:基础设计以及一种使多代理智能能够实际运行的基础设施模式。
定义
微服务是一种软件架构模式,将应用程序拆分为较小的、独立的服务,每个服务负责单一功能,并通过 HTTP 或消息队列等轻量级协议进行通信。这种模块化方法实现了可扩展性、灵活性和更易于维护,构成了现代分布式系统的基础——并且日益地,构成了多 AI 系统的基础。
从设计角度来看,微服务建立在模块化原则之上——每个代理都有单一职责。换句话说,多代理设计就是微服务设计。你可以将一个代理更换为更智能的代理而不干扰系统的其他部分。
从基础设施角度来看,微服务为部署此类分布式系统提供了骨干。每个代理可以是:
-
容器化的(例如 Docker)并作为 API 部署
-
托管在云原生环境中(例如 Kubernetes),并进行独立扩展
-
通过服务网格或消息代理连接,实现安全的通信
-
在多语言环境中开发(例如,一个代理运行 Python,另一个运行 JavaScript)
图 7.2:多代理系统的微服务后端
让我们重新回到 7.1 中的例子。规划代理将任务(“买鞋,39 码”)拆分为子任务,由模块化代理——Web 代理、调度代理、下单代理来处理,每个代理都有自己的辅助项(例如支付代理和地址代理)。这些代理可以作为:
-
部署在 Kubernetes 集群中的独立微服务
-
按需启动的无服务器函数
-
通过事件驱动架构协调的具有持久内存和状态的后台
没有微服务基础设施,构建、部署和管理此类分布式代理系统将变得脆弱且低效。模块化设计与可扩展托管使得这一切成为可能。
同时,我们还需要定义代理之间相互通信的方式,这引入了多代理设计工作流的主题。
为你的多代理系统理解并设计不同的工作流
当我们从单代理系统转向多代理架构时,代理交互的方式变得至关重要。不同的工作流(换句话说,通信模式)塑造了代理如何协作、共享信息和决策。选择正确的工作流取决于任务的性质、每个代理的角色以及预期结果。
让我们探索五种核心多代理工作流(见图 7.3):
图 7.3:不同类型的代理工作流
以下是五个核心多智能体工作流的详细介绍:
网络(Network)
所有智能体都是全连接图中的节点,每个智能体都可以与任何其他智能体直接通信。这允许高度交互、动态的协作。
让我们考虑以下示例。在一家快速发展的初创公司中,AI 团队协作对新的移动应用程序发布进行构思、验证和规划。每个智能体都有明确的领域,但它们以动态、迭代的方式工作,就像真实的产品团队一样:
-
Agent A—市场研究智能体:该智能体持续监控消费者趋势、商店数据和竞争对手活动。它识别出对 AI 驱动的健康应用的兴趣日益增长,并分享一份强调未满足用户需求的报告。
-
Agent B—产品设计智能体:该智能体利用来自市场研究智能体的见解,草拟健康应用的初步概念,包括用户流和功能原型。它参考了 UX 风格指南并适配之前的成功模式。它将这些分享给团队以获取反馈。
-
Agent C—监管合规智能体:一旦提出新的功能想法,该智能体就会审查其潜在的法律或隐私问题——例如健康数据的处理——并建议修改以保持应用程序符合 GDPR 和 HIPAA。
-
Agent D—项目经理智能体:该智能体跟踪进度和任务之间的依赖关系。它提议时间表并标记风险,例如可能需要额外合规审批的设计功能。当决策等待或任务阻塞他人时,它还会提醒团队。
图 7.4:网络工作流
第 7 章
171
这种设置(如图 7.4 所示)模仿了跨功能敏捷团队,在持续协调和快速反馈中蓬勃发展。
反思(Reflection)
一种自我评估循环,智能体对其自身的输出进行反思或批判(或让另一个智能体执行),允许通过反馈进行迭代改进。例如,考虑一个想要使用写作助手生成科学论文草稿的大学小组。主智能体生成内容,而“评审员”智能体评估清晰度、逻辑和引用准确性。它会标记薄弱的论点或缺失的参考文献。
第一个智能体随后相应修改文本并重新提交评审。此循环重复,直到输出达到出版质量。
图 7.5:反思工作流
反思工作流对于质量控制以及精炼与生成同样重要的任务非常有效。
顺序(Sequential)
智能体被组织成线性流水线。每个智能体执行特定任务并将其输出传递给链中的下一个智能体。例如,考虑一个媒体机构如何使用由专业智能体组成的顺序链来自动完成创建和发布突发新闻文章的过程:
-
Agent A—新闻聚合器:监控来自新闻线路、社交媒体和新闻稿的推送,以识别新兴故事。它选择一个突发新闻事件并提取相关事实。
-
Agent B—事实检查员:根据政府数据库、之前的新闻报道和知识图谱等可信源对收集的信息进行验证。它在故事进展之前标记任何差异并确保准确性。
-
Agent C—写作智能体:获取经过验证的数据并撰写一篇引人入胜的新闻文章,遵循新闻语调和出版标准。
-
Agent D—SEO 和社交优化智能体:精炼文章标题、头部元描述、标签并调整格式,以实现在搜索引擎和社交平台上获得最大的触达率。
-
Agent E—发布智能体:在网页、移动端和通讯邮件平台上调度并发布文章。
图 7.6:顺序工作流
通过这种工作流,你将对智能体有更多的控制,因为你正在给它们一个清晰的执行顺序。
层级(Hierarchical)
经理监督并委托任务给从属智能体,并汇总它们的输出。通信通常从上到下和从下向上流动。例如,考虑一家部署了层级 AI 系统的 SaaS 公司。当客户提交“为什么我这个月被收了两次费?”时,经理智能体解析请求并委托子任务:
-
账单智能体检查交易历史。
-
技术智能体检查系统日志是否存在重复收费。
-
FAQ 智能体检查这是否为已知问题。
一旦每个从属智能体做出响应,经理汇总并交付一个连贯、统一的答案。这种设置反映了现实世界的管理,促进了模块化和专门化处理。
第 7 章
173
图 7.7:层级工作流
如果执行顺序不是预先规定的(与顺序模式不同),但你仍然需要在顶层保留某种监控循环(与网络模式不同),这种模式就特别有用。
混合(Hybrid)
结合了多种模式(例如,带有反思循环的层级主体,或带有嵌入网络的顺序步骤),允许灵活、分层的协作。让我们重新回到顺序架构中的媒体出版流水线,并在其中一个步骤中添加层级结构:
-
Agent A—新闻聚合器:监控来自新闻线路、社交媒体和新闻稿的推送,以识别新兴故事。它选择一个突发新闻事件并提取相关事实。
-
Agent B—事实检查员:根据政府数据库、之前的新闻报道和知识图谱等可信源对收集的信息进行验证。它在故事进展之前标记任何差异并确保准确性。
-
Agent C—写作智能体:获取经过验证的数据并撰写一篇引人入胜的新闻文章,遵循新闻语调和出版标准。
-
Agent D—SEO 和社交优化智能体:精炼文章标题、头部元描述、标签并调整格式,以实现在搜索引擎和社交平台上获得最大的触达率。
-
Agent E—发布智能体:在网页、移动端和通讯邮件平台上调度并发布文章。
图 7.8:混合模式
执行它们,无缝地协调多个功能或插件。此外,与主要关注基于文本的对话历史的传统代理系统不同,TaskWeaver 将聊天历史与代码执行历史以及内存数据状态相结合,使其成为处理表格和数据库等结构化高维数据的理想选择。
TaskWeaver 的核心特性
- 复杂任务规划与反思性执行:
TaskWeaver实现了智能任务分解、进度跟踪和反思性执行,允许代理根据执行反馈动态调整策略。
TaskWeaver
-
丰富的数据处理和有状态执行: 该框架支持
DataFrames等丰富的 Python 数据结构,跨任务维护计算状态,并在执行前通过验证生成的代码来确保一致的用户体验。 -
可定制、可扩展且安全: 开发者可以轻松使用领域特定的插件扩展
TaskWeaver,封装自定义算法,并通过会话隔离和安全代码执行来安全地管理多智能体工作流。 -
开发者友好且透明:
TaskWeaver提供了简单的安装体验,拥有开箱即用的插件、便于调试的详细日志,以及开放、透明的架构,帮助用户理解并控制执行过程。
鉴于其代码驱动的方法和对结构化数据的强大支持,TaskWeaver 特别适用于数据密集型应用、分析工作流、科学研究、金融建模以及任何对精度、可复性和执行透明度要求关键的环境。
OpenAI Agents SDK
OpenAI Agents SDK 是一个轻量级但强大的框架,旨在简化构建和编排多智能体工作流。它由 OpenAI 开发,提供了一种模块化方法来连接代理、为其分配工具、应用护栏,并在它们之间无缝传输控制权。
它是提供者无关(provider-agnostic),这意味着它可以与 OpenAI 的 API 工作,同时也兼容 100 多种不同的 LLM,对于不同的 AI 后端极具灵活性。
Agents SDK 的主要特性包括:
-
用于代理协作的交接(Hand-offs):
OpenAI Agents引入了“交接”——一种根据任务上下文在代理之间传递控制权的整洁原生机制,使多智能体协作变得直观且自然。 -
内置护栏: 安全是核心关注点;你可以为输入验证、输出检查和自定义约束定义护栏,提高工作流的可靠性和伦理行为。
-
原生追踪和调试工具: SDK 包含内置的追踪支持,允许你视觉化代理交互、监控执行路径并轻松调试多智能体工作流,这对于透明度和优化至关重要。
-
简单快速的 API: 代理易于定义(名称、指令和工具),并可以用极少的样板代码组合成复杂的工作流。它是为快速原型设计设计的,且不牺牲深度。
-
多提供者支持: 虽然它与 OpenAI 的模型紧密集成,但并没有被锁定——你可以轻松地连接到其他模型提供者,支持多样化的部署需求。
总的来说,OpenAI Agent SDK 赋予开发者构建智能多智能体系统的能力,这些系统能够推理、委托任务并自主与工具交互。
LangGraph
LangGraph 是 LangChain 生态系统的扩展,旨在通过基于图的架构编排多智能体系统。
| 定义 |
| :--- |
| 在数学中,图是由节点(也称为顶点)通过边连接形成的结构,用于对对象对之间的关系或连接进行建模。图是网络理论、计算机科学和组合数学等领域的基础。 |
在 LangGraph 的语文中,图通过以下元素来建模 AI 工作流:
-
节点(Nodes): 在
LangGraph中,每个节点代表工作流中的特定任务或操作。节点可以是 LLM 代理、工具或自定义函数,根据输入执行文档检索、数据处理、文本生成或决策等操作。 -
边(Edges): 边定义了控制权如何在节点之间移动,规定了执行的顺序。它们还可以包含条件逻辑,允许工作流根据上一个节点的状态或输出进行动态路由。
-
状态(State): 状态是在执行过程中跨越节点传输的共享数据结构。它携带了必要的上下文——如消息、中间结果或相关数据——节点利用这些信息来执行任务并协调行动。
-
条件逻辑(Conditional logic):
LangGraph通过条件边实现了动态决策。这些边评估当前状态以确定下一步,支持工作流中的分支、循环和实时调整。 -
工作流编译(Workflow compilation): 一旦定义定义,
LangGraph就会将节点、边和条件逻辑编译成可执行的图。该编译后的工作流负责管理执行顺序、状态转换和节点之间的协调,确保任务以高效且可靠的方式得以执行。
例如,在图 7.9 中,你可以看到一个 LangGraph 工作流,其中我们有一个第一个条件边判断是否需要代理;然后根据输出,它决定是必须重写查询还是可以直接作为最终结果显示给用户。
图 7.9:LangGraph 工作流示例
第7章
通过结合这些元素,LangGraph 允许开发者构建模块化、可复用且动态的工作流,能够利用 LLM 和外部工具驱动复杂的多智能体交互和复杂的决策。
在 AutoGen、TaskWeaver、Agents SDK 或 LangGraph 之间没有绝对的“最佳”编排器。每个工具的设计都有不同的哲学和优势,正确的选择在很大程度上取决于你的特定用例、技术偏好以及你旨在构建的工作流的复杂度。有些人倾向于轻量级的快速原型设计;另一些则倾向于多代理之间的有状态交互。
在下一节中,我们将利用 LangGraph 进行实操用例,探索如何一步步构建多智能体系统。
使用 LangGraph 构建你的第一个智能体应用
是时候动手了!在这一节中,我们将使用 LangGraph 构建一个智能体应用。请注意,我不会在这里包含所有代码,而是包含理解构建块的相关部分。你可以在书籍的 GitHub 仓库找到完整代码: https://github.com/PackPublishing/AI-Agents-in-Practice 。
让我们介绍一下我们要用应用程序解决的挑战。
管理投资组合是一项复杂的任务,需要不断关注市场趋势、资产表现和风险。许多个人投资者和财务顾问难以手动分析大量数据以做出决策。该应用程序旨在通过使用一组智能代理分析用户的组合、提取相关的市场洞察并提供操作建议来缓解这一挑战。
目标是交付一份自动生成的综合报告,帮助用户优化投资回报或降低风险。为了演示方便,我们假设组合以 JSON 文件表示,结构如下:
{
"symbol": "AAPL",
"sector": "Technology",
"quantity": 13,
"purchase_price": 1202.57,
"total_invested": 15633.41,
"purchase_date": "2022-03-12"
}
小贴士:使用 AI 代码解释器和快速复制功能增强你的编码体验。在下一代 Packt Reader 中打开此书。点击复制按钮 (1) 快速复制代码到你的开发环境中,或点击解释按钮 (2) 让 AI 助手向你解释一段代码。
function calculate(a, b) {
return {sum: a + b};
}
购买此书将免费获得下一代 Packt Reader。扫描二维码或访问 packpub.com/unlock,然后通过名称搜索此书。请仔细检查显示的版本以确保获取的是正确版本。
让我们检查图 7.10 所示的每个组件:
-
search_agent: 专门用于检索相关网站以回答用户问题,并提供了以下工具:
-
tavily_tool: LangChain 中可用的一个预构建工具,专门设计用于从 Tavily API 检索搜索结果
定义:
Tavily 是一个由 AI 驱动的搜索 API,旨在通过提供实时、高质量的网页搜索结果来增强检索增强生成 (RAG) 和基于智能体的应用。它允许开发者通过简单、快速且具有成本效益的 API 调用,将外部知识集成到 LLM 工作流中。Tavily 通常用于通过将响应置于最新的网络内容中来提高 AI 生成响应的准确性和相关性。
让我们初始化这个智能体,从工具的初始化开始:
# 从 .env 文件加载环境变量
load_dotenv()
## 初始化 Azure OpenAI 模型
llm = AzureOpenAI(
openai_api_version=openai_api_version,
azure_deployment=azure_chat_deployment,
)
tavily_tool = TavilySearchResults(max_results=5,
tavily_api_key=tavily_api_key)
现在,我们可以创建智能体将其初始化为 LangGraph 图中的节点:
search_agent = create_react_agent(llm, tools=[tavily_tool])
def search_node(state: State) -> Command[Literal["supervisor"]]:
result = search_agent.invoke(state)
return Command(update={
多智能体应用
"messages": [
HumanMessage(
content=result["messages"][-1].content,
name="search"
)
},
# 我们希望我们的工作人员在完成后“始终”向主管“汇报”
goto="supervisor",
-
read_portfolio_agent: 专门用于从提供的路径读取投资组合,并提供了以下工具:
- read_portfolio: 硬编码函数,用于读取保存为 JSON 文件的投资组合
让我们初始化工具和智能体:
@tool
def read_sample_portfolio(
json_path: str = "sample_portfolio.json"
"""
读取 sample_portfolio.json 文件并将其内容作为
字符串返回。
每个条目包括股票代码、行业、数量、购买
价格和购买日期。
"""
if not os.path.exists(json_path):
return f"未找到文件: {json_path}"
with open(json_path, "r") as f:
portfolio = json.load(f)
if not isinstance(portfolio, list):
return "意外的投资组合格式。"
response = "Sample Portfolio:\n"
for stock in portfolio:
response += (
第 7 章
183
f"{stock['symbol']} {stock['sector']}: "
f"{stock['quantity']}shares @${stock['purchase_price']}"
f"(Bought on {stock['purchase_date']})\n"
return response
read_portfolio_agent = create_react_agent(
llm, tools=[read_sample_portfolio]
)
def read_portfolio_node(
state: State
) -> Command[Literal["supervisor"]]:
result = read_portfolio_agent.invoke(state)
return Command(
update={
"messages": [
HumanMessage(content=result["messages"][-1].content, name="read_portfolio")
],
},
# 我们等待我们的工作人员“始终”向主管“汇报”
goto="supervisor"
)
-
doc_writer: 专门用于编写结构化大纲的详细报告,并提供了以下工具:
- write_document: 硬编码函数,将智能体的输出结果写入预定义的目录中
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import Dict, Optional
from typing_extensions import TypedDict
定义一个真实的目录路径
REAL_DIRECTORY = Path(r"your_path")
#TEMP_DIRECTORY = TemporaryDirectory()
WORKING_DIRECTORY = Path(REAL_DIRECTORY)
@tool
def write_document(
content: Annotated[
str, "写入文档的文本内容."],
file_name: Annotated[str, "保存文档的文件路径."],
) -> Annotated[str, "保存的文档路径."]:
"""
创建并保存一个文本文档。
"""
with (WORKING_DIRECTORY / file_name).open("w") as file:
file.write(content)
return f"文档已保存至 {file_name}"
"""
你是一个报告生成专家。给定来自其他智能体的输入,你将生成关于如何优化提供的投资组合的详细报告。
报告将包含以下大纲:
-
市场格局介绍
-
投资组合概览
-
投资策略
-
性能分析
-
建议
-
结论
-
参考文献
一旦生成报告,请使用你的 write_document 工具将其保存。
doc_writer_agent = create_react_agent(
llm,
tools=[write_document],
prompt=report_prompt,
)
def doc_writing_node(state: State):
result = doc_writer_agent.invoke(state)
return Command(
update={
"messages": [
HumanMessage(content=result["messages"][-1].content, name="doc_writer")
],
},
# 我们等待我们的工作人员“始终”向主管“汇报”
goto="supervisor"
)
188
多代理应用
'doc_writer': {'messages': [HumanMessage(content='2025年第四季度投资组合优化的报告已成功生成。您可以在文件 **Portfolio_Optimization_Q4_2025.txt** 中找到它。如果您需要进一步的帮助或任何修改,请随时告诉我!', additional_kwargs={}, response_metadata={}, name='doc_writer', id='8f9e6a9-fafc-4d6c-bbbc-3951465a3cbe')])}
---
{'supervisor': {'next': '__end__'}}
你将在指定的文件夹(通常为 outputs)中找到你的文档。
图 7.12:本地文件夹中创建的 .txt 文件示例
以下是输出内容:
图 7.12:代理生成的最终报告示例
如你所见,输出结果遵循了我们在报告生成代理中作为系统消息所指定的大纲。
总结
在本章中,我们超越了单一工具增强代理的世界,探索了多代理系统(multi-agent systems)这一令人兴奋的领域。我们看到代理如何像人类团队一样协作,每个代理都专注于自己的任务,但共同工作解决任何单一代理无法独立的复杂问题。
我们还确定了,设计多代理系统既是一项架构挑战,也是一项 AI 挑战,它需要对模块化、通信、协调和可靠性进行深思熟虑。通过与微服务架构进行类比,我们意识到代理可以(并且应该)是模块化的、独立的,并经过智能编排以形成可扩展的且具韧性的系统。
我们引入了如 AutoGen、TaskWeaver 和 LangGraph 等多代理编排器,并对通过后者进行了一次实操演示,构建了我们的第一个多代理应用。
本章也标志着本书第二部分的结束。在下一章中,我们将转换思路,探索一个关键主题——构建负责任的 AI 系统。随着我们创建出日益自治的代理和多代理生态系统,设计安全性、透明度、安全性和伦理对齐变得变得至关重要。
参考文献
-
LangGraph: https://www.langchain.com/langgraph
-
使用 LangGraph 构建多代理系统: https://langchain-ai.github.io/langgraph/concepts/multi_agent/
-
LangGraph 代理概念: https://langchain-ai.github.io/langgraph/concepts/agentic_concepts/
-
OpenAI Agents SDK: https://github.com/openai/openai-agents-python
-
AutoGen: https://github.com/microsoft/autogen
-
TaskWeaver:
https://github.com/microsoft/TaskWeaver
现在解锁本书专属福利
扫描此二维码或访问 packtpub.com/unlock,然后通过书名搜索此书
注意:在开始之前准备好您的购买发票。
第 3 部分
通往开放、代理化生态系统的之路
本部分介绍了企业级 AI 代理的新兴格局,特别关注了使大规模代理部署成为可能的设施、协议和负责任的设计实践。
我们首先从探索下一代开放协议开始,这些协议旨在标准化和扩展多代理协作——例如 MCP、A2A 和 NLWeb——它们注将在实现跨平台和跨代理的互操作性方面发挥基础性作用。
我们将深入探讨企业在部署自治代理时如何确保负责任的 AI 实践。主题包括评估、安全过滤器、防护护栏,以及在高风险场景下“人类回路”(human-in-the-loop)系统的重要性。你还将学习优化成本和维持规模性能的策略。
最后,本部分反思了代理系统更广泛的演进——从原型到生产——并对该领域的未来方向以及短期内对智能软件的预期提供了前瞻性视角。
本部分包含以下章节:
-
第 8 章,编排智能:下一代代理协议蓝图
-
第 9 章,在现实世界 AI 中应对伦理挑战
编排智能:下一代代理协议蓝图
在人工智能(AI)发展的不断演变的格局中,协议正迅速成为连接模型、工具和外部系统的纽带。在过去的几个月里,我们看到围绕新协议的活动激增,这些旨在增强智能代理之间的互操作性和协作。其中最著名的包括 Anthropic 的模型上下文协议 (MCP)、Google 的 Agent2Agent (A2A) 以及 Virtuals 的代理商业协议 (ACP)。
初看之下,这些可能只是已经拥挤的 AI 生态系统中的又一波框架。毕竟,LangChain、Semantic Kernel 和 AutoGen 等框架长期以来一直承诺重用的、模块化的 AI 组件,如插件、工具、提示词和代理。但协议运行在不同的抽象层级。编排器帮助构建和控制智能工作流,而协议定义了这些组件如何跨系统进行通信,提供了一致性和结构。
在本章中,我们将涵盖以下主题:
-
什么是协议?
-
理解模型上下文协议
-
Agent2Agent
-
代理商业协议
技术要求
本章的所有代码和必要的依赖项列在 requirements.txt 中。要设置环境,只需克隆仓库并按照说明操作。或者,你可以从零开始并遵循官方 SDK:https://github.com/modelcontextprotocol/python-sdk。
什么是协议?
协议只是定义两个系统如何通信的规则集。最熟悉的例子是 HTTP——浏览器用于与网站通信的协议。当你访问 URL 时,浏览器发送请求,服务器返回页面或数据。
第 8 章
假设你正在访问乞力马扎罗的维基百科页面。你的浏览器发送以下内容:
GET /wiki/Mount_Kilimanjaro HTTP/1.1
Host: en.wikipedia.org
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp/*;q=0.8
Accept-Language: en-US,en;q=0.5
Connection: keep-alive
服务器返回 HTML 页面,由你的浏览器渲染:
图 8.1:浏览器渲染网页
现在,想象一下如果你不是显示整个页面,而是只需要数据。这就是表述状态转移(REST)API 的用用场。REST API 允许应用程序通过 HTTP 以机器可读的格式交换数据。客户端可能会发送以下内容:
GET /api/mountains/kilimanjaro
它们可能会接收到如下:
{
"name": "Mount Kilimanjaro",
"elevation": 5895,
"location": "Tanzania"
}
第8章
197
REST Client
服务器
图 8.2:REST API 请求示例
这种同样的原理——在标准协议上进行结构化通信——是 MCP 等新型 AI 原生协议的核心。
理解模型上下文协议 (MCP)
MCP 是一项基础协议,旨在标准化大语言模型(LLMs)与外部工具、数据源和工作流的交互方式。类似于 Web 的 HTTP,MCP 为 AI 代理提供了一个统一一致的接口,用于查询和调用模型之外的能力。
在 MCP 之前,AI 工具领域是碎片化的:
-
碎片化的编排器:如
LangChain、AutoGen、Semantic Kernel等框架拥有自己的工具注册表和代理逻辑,但缺乏工具调用的共享标准。 -
自定义集成:每个系统都使用定制代码来集成工具,使得重用和互操作性变得困难。
-
提供者依赖:编排器与提供者的 API 密耦合,而 API 可能会发生不可预测的更改。
-
缺乏通用协议:代理和模型缺乏一种跨主机和平台的通用方法来发现、描述或调用工具。
MCP 通过引入客户端服务器架构并标准化工具和资源接口解决了这些局限性。
从架构角度来看,MCP 由以下组件组成:
-
MCP 主机 (host):运行 LLM 并促进通信的应用或环境。示例包括
Claude Desktop、GitHub Copilot和Cursor。 -
MCP 服务器 (server):以结构化、可发现的格式暴露工具、资源或提示(prompts)的外部服务。
-
MCP 客户端 (client):主机内部的组件,连接到一个或多个 MCP 服务器。它处理协议消息和路由。
图 8.3:MCP 组件
与硬编码工具逻辑的传统客户端不同,MCP 允许 LLM 根据用户意图选择调用哪个服务器(基于 JSON-RPC 的调用)。
定义
JSON-RPC 2.0 是一种轻量级、无状态的协议,用于通过远程过程调用(RPC)进行系统间的结构化通信。它使用 JSON 编码请求和响应,允许一个系统调用另一个系统执行的函数,并以一致的格式接收结果或错误消息。
一个典型的 JSON-RPC 请求如下:
{
"jsonrpc": "2.0",
"method": "get_stock_price",
"params": { "ticker": "AAPL" },
"id": 1
}
一个有效的响应如下:
{
"jsonrpc": "2.0",
"result": 189.23,
"id": 1
}
MCP 使用 JSON-RPC 2.0 来结构化请求和响应。它支持多种传输方式(包括 HTTP 和 STDIO),MCP 服务器可以封装传统的 REST API,将标准化的 MCP 请求转换为自定义后端逻辑。
MCP 将能力分为三种服务器类型:
- 工具 (Tools) 是 AI 模型可以调用的可执行函数,用于执行特定操作,例如获取股票价格、转换格式和查询 API。这些工具使用 JSON Schema 描述,使其能够被模型发现并安全调用。
例如,你可以看到上述工具的 JSON 定义:
"name": "get_stock_price",
"description": "Fetch the latest stock price",
"inputSchema": {
"type": "object",
"properties": {
"ticker": { "type": "string" }
}
}
}
此 Schema 允许 LLM 理解输入是什么以及工具执行的操作。工具定义促进了跨不同主机的一致性和重用性。
- 资源 (Resources):资源代表 AI 模型可能需要检索和使用的结构化数据对象,如文档、JSON 数据集或数据库输出。例如,一个受任合同分析的符合 MCP 标准的 AI 代理可能会通过其 URI 从文档管理系统中检索法律文件,提取相关条款,并将其映射到存储在 JSON 数据集中的合规检查清单中。同样,在客户服务自动化流中,AI 可以从数据库输出中获取客户交互日志,处理数据以检测情感或未解决的问题,并生成摘要。这些资源使用统一资源标识符 (URI) 进行引用,并包含 MIME 类型和描述等元数据,允许模型高效地解释内容的格式和结构。
在这里你可以看到与 URI 引用相关的典型 Schema 示例:
{
"uri": "resource://finance/market_data",
"name": "Market Data",
"description": "Recent market summaries",
"mimeType": "application/json"
}
资源允许以最小的解析或硬编码逻辑访问静态或动态数据,提供了一种检索相关信息的声明式方法。
- 提示 (Prompts):MCP 中的提示是可重用的提示模板,可以引导模型完成特定的工作流或任务。这些对于实现多步推理或与用户上下文的精细化交互特别有用。
例如,考虑这样一个场景:AI 助手被要求为财务分析师总结季度报告。使用 MCP 中的可重用提示,助手首先提取收入、运营支出净利润等关键指标。然后将其与之前的季度进行比较,并突出异常或趋势。
这种多步交互完全由提示模板引导,确保了输出结构的一致性以及符合分析师过去偏好的定制摘要。这里是一个示例(为了演示目的,提示已截断):
{
"name": "summarize_report",
"description": "Summarize a financial report [...]",
"arguments": [
{ "name": "report_uri", "required": true}
]
}
提示在工具执行之上添加了一个语义层,实现了更丰富的交互和直接嵌入协议接口的上下文引导。
让我们逐步构建并暴露一个简单的符合 MCP 的工具。这将展示从 Python 到在支持的 MCP 主机(如 Claude Desktop)内部进行实时交互的全周期。
首先,我们定义一个简单的 Python 函数,使用 yfinance 包获取收盘价:
def get_stock_price(ticker: str) -> float:
stock = yf.Ticker(ticker)
return stock.history(period="1d")["Close"].iloc[-1]
该函数的功能完全如其名——接受股票代码作为输入并返回最近的收盘价。
我们使用 FastMCP 工具将此函数暴露为工具:
mcp = FastMCP("Demo")
@mcp.tool()
def get_stock_price(ticker: str) -> float:
"""Fetch the latest stock price"""
stock = yf.Ticker(ticker)
return stock.history(period="1d")["Close"].iloc[-1]
FastMCP 注册该工具并创建一个通过 STDIO 使用 MCP 的服务器接口。
现在,你可以通过你喜欢的主机使用你的服务器。在示例中,我们将连接到 Claude Desktop。按照以下步骤操作:
- 运行命令:
uv mcp install server.py
- 这将更新
claude_desktop_config.json:
{
"mcpServers": {
"Demo": {
"command": "uv",
"args": [
"run",
"--with",
"mcp[cli]",
"mcp",
"run",
"C:/path/to/server.py"
]
}
}
}
- 重启
Claude Desktop。现在你应该可以在服务器面板看到你的自定义工具:
- 在 Demo 服务器下,你可以看到可用的 MCP 资源类型。在我们案例中,我们将看到
get_stock_price工具:
- 设置完成后,你可以通过使用自然语言查询来测试集成,例如:“今天微软的收价是多少是多少”
正如你所看到的,驱动 Claude Desktop 的 LLM——在我们案例中是 Claude 3.7 Sonnet(你可以在聊天栏右下角的列表中选择模型)理解为了回答你的问题需要调用 get_stock_price 工具。
编排智能:下一代代理协议蓝图
从身份验证的角度来看,MCP 通过强制客户端与模型之间的身份验证和安全通信来处理身份验证和安全。它使用 OAuth 2.0 或类似的基于令牌的机制对用户和服务进行身份验证,确保只有经过授权的实体才能发起或访问模型交互。安全性方面,MCP 支持端到端加密、审计和访问控制策略,这有助于保护传输中和存储状态下的敏感数据。此外,它还可以与身份提供者集成,并实施基于角色的访问控制 (RBAC),以符合组织标准。
最后,MCP 通过为上下文定义标准化的机制(例如回退策略)和错误处理来确保系统行为的健壮性。它采用了定义良好的 JSON-RPC 错误代码——例如解析错误 (32700)、无效请求 (32608)、“未找到方法”(32601)、无效参数 (32602) 和内部错误 (32603)—实现可以扩展到 32000 以上的自定义代码。MCP 错误响应通过请求-响应循环、传输层告警和专用协议处理器进行通信,从而实现结构化的错误检测和恢复。
对于容错性,MCP 支持智能回退:客户端可以配置错误阈值,当超过阈值或服务下降时,将触发自动切换到备份模型或服务器版本——理想情况下不会丢失对话上下文——从而确保高可用性。
这些机制共同为运行在动态或分布式环境中的 AI 系统提供了一个韧性的、自愈的工作流。
MCP 正在为更具互操作性的 AI 未来奠定基础。它为工具生态系统带来了结构,实现了可复组件,并允许模型以可发现、安全且标准化的方式与工具交互。
在了解 MCP 如何帮助 AI 代理连接工具和数据之后,我们将下一步探索 AI 代理如何相互连接——这就是 Google 的 A2A 协议发挥用场的地方,它促进了代理之间的直接通信。
Agent2Agent (代理代理)
A2A 由 Google 开发,是一种促进自主代理之间无缝通信与协调的协议,无论它们的底层平台或供应商如何。
虽然 A2A 专注于代理与代理交互,但它补充了 MCP 的代理与工具之间的通信。在实践中,代理可能会使用 MCP 访问外部工具或数据源,并使用 A2A 向其他代理委托任务或共享信息。这种分层方法使得构建复杂的、互操作的 AI 生态系统成为可能。
第8 章
205
A2A 的前提是,代理应该能够向另一个代理请求协助或共享信息,即使它们是由不同的构建的,或者运行在不同的平台上。这对于规模化 AI 解决方案至关重要:与其让一个巨大的 AI 尝试做所有事情,不如让由较小 AI 组成的网络各司其擅长的工作,并通过交流来完成复杂任务。
A2A 的核心是在代理之间建立一种通用的面向任务的对话。它不是自由形式聊天;它将交互结构化为任务、结果以及可选的后续问题。关键原则包括:
- 代理可发现性:为了进行通信,代理需要知道如何找到对方以及每个代理拥有什么能力。A2A 引入了“代理卡片”(agent card)的概念——一种元数据描述,代理可以发布身份、支持的任务、输入/输出格式和端点 URL。这通常是一个机器可读的 JSON 文件。例如,让我们考虑以下天气代理的卡片:
{
"name": "WeatherBot",
"description": "Provides accurate weather forecasts and historical data",
"url": "https://weatherbot.example.com/a2a",
"version": "1.0.0",
"capabilities": {
"streaming": true,
"pushNotifications": true,
"stateTransitionHistory": true
},
"authentication": {
"schemes": ["bearer", "apiKey"],
},
"defaultInputModes": ["text/plain"],
"defaultOutputModes": ["application/json"],
"skills": [
{
"id": "forecast",
"name": "Weather Forecast",
"description": "提供指定位置和日期的天气预报。",
"tags": ["weather", "forecast"],
"examples": ["明天米兰的天气预报是什么?"]
},
{
"id": "historical",
"name": "Historical Weather Data",
"description": "检索历史天气数据进行分析。",
"tags": ["weather", "historical"],
"examples": ["上周罗马的温度是多少度?"]
}
]
}
让我们拆解每个组件:
-
name: 代理的人类可读名称 -
description: 代理用途和功能的简要摘要 -
url: 代理接受 A2A 协议请求的端点 -
version: 代理或其遵循的 A2A 协议的版本 -
capabilities: 指示代理对特定功能的支持:-
streaming: 支持通过服务器发送事件 (SSE) 进行实时数据流 -
pushNotifications: 可以向客户端发送异步更新 -
stateTransitionHistory: 维护任务状态变更的历史
-
-
authentication: 指定代理支持的身份验证方法,如负载令牌 (bearer tokens) 或 API 密钥 -
defaultInputModes: 代理期望的输入数据默认内容类型(例如纯文本) -
defaultOutputModes: 代理在响应中产生的默认内容类型(例如 JSON) -
skills: 代理提供的特定功能列表:-
id: 技能的唯一标识符 -
name: 技能的人类可读名称 -
description: 关于该技能作用的详情 -
tags: 与技能关联的关键词,便于发现 -
examples: 技能可以处理的示例查询或任务
-
这种结构化格式允许其他代理有效地发现并与天气代理交互,促进了多代理系统中的无缝协作。
-
结构化请求(任务): A2A 以任务请求的形式使通信正式化。一个代理(请求者)向另一个代理(提供者)发送 JSON 格式的任务,其中包含了请求的内容和任何必要参数。代理 A 可能会向代理 B 发送一个“将以下文本翻译成西班牙语”的任务并附带文本。这些任务具有定义的模式,因此代理 B 知道去哪里查找指令和数据。
-
异步交互: 如果任务需要时间或多个步骤,代理可能不会立即做出响应。A2A 通过允许代理响应中间状态更新(例如“任务已接受”或“已完成 50%”)并在完成后发送完成消息来支持异步处理。它可以使用 HTTP 长轮询或 SSE 进行流更新。这很重要,因为两个代理的运行速度可能不同,或者其中一个可能必须执行长时间的工作(例如搜索大型数据库)——协议确保了请求代理不会处于信息真空。
-
多轮澄清: 通常情况下,代理的请求可能是模糊或不完整的。A2A 允许请求者提出澄清性问题。这在任务范围内创建了一个微对话。例如,代理 B 可能会回复:“我可以翻译文本,但你没有指定是哪种西班牙语。应该是中立的还是地区特有的?”代理 A 随后可以回答,然后任务继续。
-
标准数据格式: 交换的内容(如任务和结果的有效负载)采用标准格式(结构化数据使用 JSON,需要时使用二进制文件的引用)。如果代理需要向另一个代理发送大文件,可能会提供 URL 或句柄,而不是在 JSON 中提供原始字节,具体取决于实现。A2A 刻意构建在网络标准之上(HTTP 用于传输、JSON 用于数据、OAuth 用于授权)之上,使其能够自然地融入现有网络中。
编排智能:下一代代理协议蓝图
想象一个多代理工作流场景。用户提出请求:“为我计划一次周末音乐节旅行,包括机票、交通和酒店。预算控制在 500 欧元以内。”一个单体式代理可能难以应对所有方面,但一组代理可以高效地处理这些任务:
-
旅游代理了解机票和酒店
-
活动代理了解演唱会和节庆
-
预算代理可以进行数字计算以保持在预算之内
-
交通代理通过实时数据了解可用的公共交通工具
让我们来看下方的插图:
图 8.7:多个代理通过 A2A(代理对代理)进行通信的示例
第 8 章
快速提示: 想要查看此图像的高分辨率版本吗?请在下一代 Packt Reader 中打开本书,或在 PDF/ePub 版本中查看。
下一代 Packt Reader 随书免费赠送。扫描二维码或访问 packtpub.com/unlock,使用搜索栏通过名称查找本书。请仔细检查显示的版本以确保获取的是正确的版本。
交互从旅游代理开始,它是初始协调。用户的请求包含多个组件:寻找合适的音乐活动、识别交通方案、预订住宿以及控制预算。旅游代理并不依赖单一的单体系统,而是将请求的不同部分委托给更擅长处理这些部分的代理。
它首先联系活动代理,任务是在特定日期在米兰附近寻找一个节庆。一旦确定了相关活动,活动代理会独立地联系预算代理,评估 120 欧元的票价在用户 500 欧元的预算内是否可行。这种绕过原始旅游代理的横向通信是 A2A 的特征——代理可以在彼此之间发起任务,无需中央编排。
同时,旅游代理咨询了交通代理,探索米兰与活动地点之间的旅行方案,并指定日期和出发地。它还直接与预算代理对接,提供预期成本的早期明细,并请求对旅行和住宿进行建议分配。
每个代理都作为一个专家系统运行,专注于特定领域——活动、交通和预算——它们通过交换结构化的任务消息进行流式协作。结果是一个协调一致的、感知上下文的响应,对终端用户来说这种感觉是完整的,尽管它是通过去中心化的、异步的代理协作产生的。
提示
在这种场景下,你可能会想:为什么不单独使用像 LangGraph 之类的框架呢?
此类框架擅长在单个应用程序内编排多个代理——只要它们属于同一个代码库或运行环境,它就可以协调旅游、活动和预算代理。
然而,当你需要代理跨跨系统具有自主性、可发现性和互操作性时,A2A 就派上用场了。通过 A2A,每个代理可以位于不同的云端,由不同的团队构建,或使用不同的框架——但仍然可以通过共享协议进行协作。
将其类比为微服务:AI 框架管理紧耦合的系统,而 A2A 则允许实现松耦合的分布式代理生态系统。
总结来说,A2A 是允许这些代理协作的胶水。如果没有它,你可能需要一个代理拥有所有领域的知识,或者为彼此的领域使用专有 API。有了 A2A,任何理解该协议的代理都可以从任何其他代理处寻求帮助,按需形成一个临时的专家团队。这很容易看出它与软件架构中微服务的相似之处;每个代理都像一个具有清晰 API(即其任务)的微服务,而 A2A 就是交换语言。
Google 设计 A2A 时强调了开放性和厂商中立性。它并不绑定于 Google 的内部技术;他们发布了规范和参考实现,并一直与合作伙伴一起推动采用。事实上,微软宣布其代理(在 Microsoft 365 Copilot 生态系统中)兼容 A2A,以便与 Google 的代理进行协作,这是对跨平台支持的显著展示。这种跨厂商的代理通信对于“代理网络(agentic web)”至关重要,因为用户不可避免会使用来自不同来源的代理。A2A 意味着你的个人 AI 助手可以直接与(例如)银行的 AI 代理通信以协商贷款报价,而不仅仅是通过僵化的 API 传递消息。
总结来说,A2A 填补了代理编排的空白,标准化了自主 AI 服务对话和协作的方式。
第 8 章
211
注意
MCP 和 A2A 是互补的。MCP 标准化了代理访问工具和外部数据的方式(例如,获取机票或酒店)。A2A 允许代理之间进行对话,跨系统共享任务、结果和决策。将 MCP 视为代理到工具的桥梁,将 A2A 视为代理到代理的握手。
在 MCP 和 A2A 的基础上,我们有了代理使用工具和相互通信的协议。拼图的下一步是让代理参与交易和协议,这就是 ACP 出现的地方。
代理商业协议
随着 AI 代理变得越来越自主并开始代表企业或个人采取,一个新问题随之产生:代理能够可靠地相互签订合同或交易吗?ACP 由一家名为 Virtuals 的公司开发,是一项巨大的尝试,旨在赋予代理以结构化、最小信任的方式进行经济交换和协作协议的能力。ACP 本质上是为 AI 代理构建一个商业层,允许它们买卖服务、为对方的工作补偿,并确保所有方履行其义务。
考虑一个未来的场景,你有一个管理个人任务的 AI 代理。你可能需要它雇佣另一个代理(例如,一个自由职业平面设计师代理)为你的项目创建一个 Logo。它们如何形式化这种安排?你的代理如何支付给设计师代理,它又如何确保交付的 Logo 符合要求?今天,人类可能会使用一个自由职业平台,该平台托管款项并在批准后释放款项。ACP 将这种机制针对 AI 对 AI 的交易进行了泛化。

浙公网安备 33010602011771号