大模型 Agent 的强大之处,在于它不仅仅能“说”和“思考”,还能“做”。这种“做”的能力,就是通过工具(Tools)来实现的。工具是 Agent 与外部世界交互的接口,它把 Agent 的智能决策转化为实际行动,比如查询数据库、发送邮件、调用第三方服务等。本章将详细介绍如何定义、封装、描述以及安全地管理这些工具。
5.1 工具的定义与封装:如何将内部 API 和外部服务封装为 Agent 可调用的工具
大模型 Agent 的强大之处,在于它不仅仅能“说”和“思考”,还能**“做”。这种“做”的能力,就是通过工具(Tools)**来实现的。工具是 Agent 与外部世界交互的接口,它把 Agent 的智能决策转化为实际行动,比如查询数据库、发送邮件、调用第三方服务等。
要让 Agent 能够使用这些工具,我们首先需要对现有系统中的各种功能进行定义和封装。这就像给 Agent 准备一个工具箱,每个工具都有明确的名称、功能和使用说明。
5.1.1 什么是工具(Tool)?
在 Agent 的语境中,一个“工具”通常是一个特定功能的封装,它接收一些输入(参数),执行一个操作,然后返回一个结果。
- 内部 API:企业内部开发的应用程序接口,例如电商后台的订单管理系统 API、库存查询 API、用户管理 API 等。
- 外部服务:第三方提供的服务 API,例如物流公司的物流查询 API、短信验证码服务 API、支付网关 API、天气预报 API 等。
Agent 通过调用这些工具,能够打破大模型自身的知识和能力边界,获取实时信息,执行实际操作。
5.1.2 工具的封装原则
为了让 LLM 能够理解和调用工具,以及方便后续的管理和扩展,我们在封装工具时需要遵循以下原则:
- 原子性与单一职责:每个工具应只负责一个明确且独立的任务。例如,查询订单状态是一个工具,修改订单地址是另一个工具。避免一个工具承担过多功能,否则会增加 LLM 理解和使用的难度。
- 清晰的输入与输出:工具的参数(输入)和返回值(输出)必须清晰、明确、有结构。LLM 需要根据这些信息来判断如何传递参数和解析结果。通常建议使用结构化数据格式,如 JSON。
- 标准化接口:无论内部 API 还是外部服务,都应该被封装成统一的接口形式,这样 Agent 框架才能以一致的方式调用它们。
- 幂等性(Idempotence):对于涉及写操作的工具(如修改订单、发起退货),尽量设计成幂等的。这意味着多次调用同一个工具,并且参数相同,其结果应该保持一致,不会产生额外的副作用。这对于 Agent 的容错和重试机制非常重要。
- 错误处理与返回:工具在执行失败时,应返回清晰的错误信息和错误码,以便 Agent 的反思机制能够识别问题并进行修正。
5.1.3 如何封装工具?(以 Python 和 LangChain 为例)
在实际开发中,我们可以使用编程语言(如 Python)将现有 API 或服务封装成 Agent 框架可识别的“工具类”或“函数”。
以电商客服 Agent 为例,我们将封装以下三个常见工具:
- 订单状态查询 (query_order_status)
- 库存查询 (check_product_stock)
- 退换货申请 (submit_return_request)
1. 订单状态查询 (query_order_status)
假设我们有一个内部订单管理系统的 API GET /api/orders/{order_id}
。
Python
# Python 示例:封装订单状态查询工具
from typing import Dict, Any
def _call_order_api(order_id: str) -> Dict[str, Any]:
"""
模拟调用内部订单管理系统API,查询订单状态。
实际应用中,这里会发起HTTP请求到真实的API。
"""
# 模拟数据
mock_orders = {
"ORDER_12345": {"status": "已发货", "logistics_info": "顺丰快递,单号SF123456789", "estimated_delivery": "2025-06-28"},
"ORDER_67890": {"status": "待付款", "logistics_info": "N/A", "estimated_delivery": "N/A"},
"ORDER_CLOSED": {"status": "已完成", "logistics_info": "已签收", "estimated_delivery": "2025-06-20"}
}
if order_id in mock_orders:
return mock_orders[order_id]
else:
return {"error": "订单号不存在或格式错误", "code": "ORDER_NOT_FOUND"}
# 将其封装为 LangChain 可用的 Tool
from langchain_core.tools import tool
@tool
def query_order_status(order_id: str) -> str:
"""
查询指定订单号的当前状态、物流信息和预计送达时间。
参数:
- order_id (str): 用户的订单编号,例如 "ORDER_12345"。
返回:
- str: 包含订单状态、物流信息和预计送达时间的JSON字符串。
例如: '{"status": "已发货", "logistics_info": "...", "estimated_delivery": "..."}'
或错误信息: '{"error": "...", "code": "..."}'
"""
print(f"Agent: 正在调用 query_order_status,订单号: {order_id}")
result = _call_order_api(order_id)
return str(result) # 返回JSON字符串,LLM能更好地解析
# 测试封装的工具 (在Agent外部模拟调用)
# print(query_order_status("ORDER_12345"))
# print(query_order_status("NON_EXISTENT_ORDER"))
2. 库存查询 (check_product_stock)
假设我们有一个内部库存管理系统的 API GET /api/products/{product_sku_id}/stock
。
Python
# Python 示例:封装库存查询工具
def _call_stock_api(product_name: str, product_sku_id: str = None) -> Dict[str, Any]:
"""
模拟调用内部库存管理系统API,查询商品库存。
实际应用中,这里会发起HTTP请求。
"""
# 模拟数据
mock_stock = {
"红色T恤-M": {"stock": 50, "status": "有货"},
"黑色T恤-L": {"stock": 0, "status": "无货"},
"牛仔裤-30": {"stock": 15, "status": "有货"}
}
if product_sku_id:
key = product_sku_id # 假设 product_sku_id 是唯一标识
elif product_name:
# 实际中可能需要通过 product_name 先查询 SKU,这里简化处理
if "红色T恤" in product_name and "M" in product_name:
key = "红色T恤-M"
elif "黑色T恤" in product_name and "L" in product_name:
key = "黑色T恤-L"
elif "牛仔裤" in product_name and "30" in product_name:
key = "牛仔裤-30"
else:
return {"error": "未找到匹配的商品", "code": "PRODUCT_NOT_FOUND"}
else:
return {"error": "缺少商品名称或SKU ID", "code": "MISSING_PARAMS"}
return mock_stock.get(key, {"error": "未找到匹配的商品或SKU", "code": "PRODUCT_NOT_FOUND"})
@tool
def check_product_stock(product_name: str, product_sku_id: str = None) -> str:
"""
查询指定商品名称或SKU的当前库存数量。
参数:
- product_name (str): 商品的通用名称,例如 "红色T恤"。
- product_sku_id (str, optional): 商品的唯一SKU编号。如果提供,优先使用SKU查询。
返回:
- str: 包含库存数量和状态的JSON字符串。
例如: '{"stock": 50, "status": "有货"}' 或 '{"error": "...", "code": "..."}'
"""
print(f"Agent: 正在调用 check_product_stock,商品名: {product_name}, SKU: {product_sku_id}")
result = _call_stock_api(product_name, product_sku_id)
return str(result)
# 测试封装的工具
# print(check_product_stock(product_name="红色T恤", product_sku_id="红色T恤-M"))
# print(check_product_stock(product_name="黑色T恤", product_sku_id="黑色T恤-L"))
3. 退换货申请 (submit_return_request)
假设我们有一个内部退换货管理系统的 API POST /api/returns
。这是一个写操作,需要特别注意幂等性和错误处理。
Python
# Python 示例:封装退换货申请工具
def _call_return_api(order_id: str, product_sku_id: str, quantity: int, reason: str) -> Dict[str, Any]:
"""
模拟调用内部退换货系统API,提交退货申请。
实际应用中,这里会发起HTTP请求。
"""
# 模拟简单的退货逻辑
if not order_id or not product_sku_id or not quantity or not reason:
return {"error": "缺少必需的退货参数", "code": "MISSING_PARAMS"}
# 模拟订单或商品不存在的情况
if order_id == "INVALID_ORDER" or product_sku_id == "INVALID_SKU":
return {"error": "订单或商品信息无效,无法提交退货", "code": "INVALID_INFO"}
# 模拟退货成功
return_id = f"RMA_{order_id}_{product_sku_id}"
return {"status": "退货申请已提交", "return_id": return_id, "message": "您的退货申请已成功提交,请等待审核。"}
@tool
def submit_return_request(order_id: str, product_sku_id: str, quantity: int, reason: str) -> str:
"""
提交用户的退货申请。此操作会创建一条新的退货记录。
参数:
- order_id (str): 关联的订单编号。
- product_sku_id (str): 需要退货的商品SKU编号。
- quantity (int): 退货数量。
- reason (str): 退货原因,例如"质量问题", "尺码不符", "不喜欢"等。
返回:
- str: 包含退货申请状态和ID的JSON字符串。
例如: '{"status": "退货申请已提交", "return_id": "RMA_...", "message": "..."}'
或错误信息: '{"error": "...", "code": "..."}'
"""
print(f"Agent: 正在调用 submit_return_request,订单号: {order_id}, SKU: {product_sku_id}, 数量: {quantity}, 原因: {reason}")
result = _call_return_api(order_id, product_sku_id, quantity, reason)
return str(result)
# 测试封装的工具
# print(submit_return_request("ORDER_12345", "红色T恤-M", 1, "尺码不符"))
# print(submit_return_request("INVALID_ORDER", "SOME_SKU", 1, "测试"))
5.1.4 总结
本节我们学习了 Agent 工具的定义和封装原则,并通过 Python 和 LangChain 的 @tool
装饰器,将电商客服场景中的订单查询、库存查询和退换货申请功能封装成了 Agent 可调用的工具。
关键在于:
- 功能原子化:每个工具专注于一个独立任务。
- 参数清晰化:工具的输入和输出必须明确,便于 LLM 理解和程序解析。
- 统一封装:使用 Agent 框架提供的机制(如 LangChain 的
tool
装饰器)进行标准化封装。
这些封装好的工具,将作为 Agent 的“外部能力”,在后续章节中,我们将看到如何通过清晰的工具描述,让 LLM 知道何时、以及如何正确地使用它们。
5.2 工具描述与 Agent 调用:编写清晰准确的工具描述,引导 Agent 正确理解和使用工具
仅仅封装好工具还不够,Agent (也就是底层的 LLM)并不知道这些工具的存在,更不知道它们能做什么、如何使用。这就需要我们提供清晰、准确的工具描述。这些描述是 Agent “理解”工具的唯一方式,也是它进行**规划(Planning)和决策(Decision-making)**的基础。
5.2.1 为什么工具描述至关重要?
- 理解工具功能:LLM 依靠工具描述来理解每个工具的用途。一个好的描述能让 LLM 准确判断在何种场景下应该使用哪个工具。
- 生成正确参数:描述中包含的参数信息(名称、类型、用途、是否必需)是 LLM 正确生成
Action Input
的关键。如果描述模糊,LLM 可能会生成错误的参数,导致工具调用失败。 - 规划与决策:在 Agent 的 ReAct 循环中,LLM 的
Thought
(思考)过程会结合用户意图和工具描述来制定行动计划。清晰的描述有助于 LLM 形成更合理的规划。 - 减少幻觉与错误:模糊的描述容易让 LLM 产生“幻觉”,调用不存在的工具,或以错误的方式使用工具。
5.2.2 工具描述的编写原则
编写高质量的工具描述是一门艺术,也是提示工程的关键组成部分。
- 明确而简洁:用精炼的语言概括工具的功能,避免冗余信息。
- 包含所有必要信息:
- 工具名称:必须与代码中封装的函数名一致。
- 工具功能:一句话概括其核心用途。
- 参数列表:
- 参数名称:清晰的变量名。
- 参数类型:如
str
,int
,bool
等。 - 参数描述:详细说明每个参数的含义、期望的输入内容和约束(例如,
order_id
必须是字符串,表示订单编号)。 - 是否必需:明确指出参数是必需 (
required
) 还是可选 (optional
)。
- 返回结果预期:简要说明工具成功执行后会返回什么类型的信息,以及可能的错误情况。
- 使用自然语言,但结构化:描述应该像人类语言一样易读,但同时也要有明确的结构(例如,使用列表或清晰的段落来区分功能、参数、返回值等),便于 LLM 解析。
- 提供示例(可选但推荐):在描述中包含简单的输入/输出示例,可以进一步帮助 LLM 理解工具的行为模式。
- 避免歧义:确保工具描述不会与其他工具混淆,每个工具的功能边界清晰。
5.2.3 将工具描述集成到 Agent Prompt 中
在 4.1 Agent 核心 Prompt 设计中,我们已经看到了 Agent 主 Prompt 中包含了一个 可用工具列表 (Tools)
。这里我们将填充更详细的工具描述。
我们以 5.1 节中封装的三个工具为例,展示其在 Prompt 中的描述:
# ... (Agent 系统 Prompt 和指令格式,见 4.5 节示例) ...
**可用工具列表 (Tools):**
- `query_order_status(order_id: str)`:
功能: 用于查询指定订单号的当前状态、物流信息和预计送达时间。例如,用户询问“我的订单ABC123到哪里了?”时,应使用此工具。
参数:
- `order_id` (str, 必需): 用户的订单编号,通常是一串数字或字母数字组合。例如 "ORDER_12345" 或 "20240501123456"。
返回: 包含订单状态、物流信息和预计送达时间的JSON字符串。例如: '{"status": "已发货", "logistics_info": "顺丰快递,单号SF123456789,最新动态: 包裹已抵达[城市]转运中心", "estimated_delivery": "2025-06-28"}'。
错误返回: 如果订单号不存在或格式错误,返回 '{"error": "订单号不存在或格式错误", "code": "ORDER_NOT_FOUND"}'。
- `check_product_stock(product_name: str, product_sku_id: str = None)`:
功能: 用于查询指定商品名称或商品SKU的当前库存数量和状态。
参数:
- `product_name` (str, 必需): 商品的通用名称,例如 "红色T恤"。这是主要识别商品的字段。
- `product_sku_id` (str, 可选): 商品的唯一SKU编号。如果用户提供了明确的SKU,应优先使用此参数进行精确查询。
返回: 包含库存数量和状态的JSON字符串。例如: '{"stock": 50, "status": "有货"}'。
错误返回: 如果未找到匹配的商品或SKU,返回 '{"error": "未找到匹配的商品或SKU", "code": "PRODUCT_NOT_FOUND"}'。
- `submit_return_request(order_id: str, product_sku_id: str, quantity: int, reason: str)`:
功能: 用于提交用户的退货申请。此操作会创建一条新的退货记录,用户询问“我想退我买的手机”时可使用。
参数:
- `order_id` (str, 必需): 关联的订单编号,例如 "ORDER_67890"。
- `product_sku_id` (str, 必需): 需要退货的商品SKU编号。
- `quantity` (int, 必需): 退货数量,必须是正整数。
- `reason` (str, 必需): 退货原因,应尽可能具体。例如"质量问题", "尺码不符", "不喜欢", "发错货"。
返回: 包含退货申请状态和ID的JSON字符串。例如: '{"status": "退货申请已提交", "return_id": "RMA_123", "message": "您的退货申请已成功提交,请等待审核。"}'。
错误返回: 如果缺少必需参数或订单/商品信息无效,返回 '{"error": "...", "code": "..."}'。
# ... (对话历史和用户输入,见 4.5 节示例) ...
5.2.4 Agent 调用工具的工作流程
当用户输入一个请求后,Agent 框架和 LLM 会按照以下步骤协作,完成工具调用:
- 用户输入:用户提出请求,例如:“我昨天买的衬衫能退吗?”
- LLM 接收 Prompt:Agent 框架将完整的 Prompt(包含系统设定、指令、工具列表、对话历史和用户当前输入)发送给 LLM。
- LLM 推理(Thought):LLM 阅读 Prompt,结合其自身的通用知识和对工具的描述,在 Thought 字段中进行推理。
- 思考过程:用户想退货。退货需要订单号、商品SKU、数量和原因。我应该使用
submit_return_request
工具。但是,用户没有提供订单号和具体的商品信息,我需要先获取这些信息。
- 思考过程:用户想退货。退货需要订单号、商品SKU、数量和原因。我应该使用
- LLM 决定 Action:如果 LLM 判断需要调用工具来完成任务,它会根据推理结果,选择一个合适的 Action(工具名称)和 Action Input(参数)。
- Action:
submit_return_request
- Action Input:
{"order_id": null, "product_sku_id": null, "quantity": null, "reason": "用户想退货,原因未知,需进一步询问"}
(此时可能因参数缺失而不会立即调用,而是会追问) - 或者,如果LLM判断需要先获取信息,Action可能是内部的“追问”动作,或者直接生成回复:
- Thought: 用户想退货,但缺少订单号和商品信息。我需要先问用户是哪个订单和退什么商品。
- Action:
Final Answer
(或一个内部的ask_for_details
工具) - Action Input: “您好!请问是哪一个订单呢?以及您想退哪件商品和退货原因?”
- Action:
- Agent 框架执行 Action:
- 如果 LLM 输出的是一个工具调用
Action
,Agent 框架会解析Action Input
中的参数,然后调用实际封装的 Python 函数(例如query_order_status(order_id="ABC123")
)。 - 如果 LLM 输出的是
Final Answer
,框架则直接将答案返回给用户。
- 如果 LLM 输出的是一个工具调用
- 工具返回 Observation:被调用的工具执行完毕后,会将结果(或错误信息)返回给 Agent 框架。
- LLM 接收 Observation 进行下一轮 Thought:Agent 框架将工具返回的
Observation
添加到上下文,再次发送给 LLM,开启下一轮的思考-行动循环。LLM 会根据这次的Observation
调整其Thought
和Action
,直到任务完成或判断无法继续。
5.2.5 总结
工具描述是连接 Agent 智能与实际执行能力的关键桥梁。通过清晰、详细且结构化的描述,我们能有效引导 LLM 准确理解每个工具的功能、参数和预期结果,从而在复杂的对话和任务中,正确地选择和使用工具。这不仅提升了 Agent 的任务完成率,也大大降低了错误率,是构建强大 Agent 不可或缺的一环。
5.3 工具调用的安全性与权限管理:如何确保 Agent 安全、合规地调用外部工具
将 Agent 与外部工具集成,赋予了它强大的执行能力,但同时也带来了潜在的安全风险和合规挑战。想象一下,如果 Agent 随意调用支付接口,或者未经授权访问敏感数据,后果将不堪设想。因此,建立一套严密的安全性和权限管理机制是 Agent 部署和运营中不可或缺的一环。
5.3.1 核心安全风险
在 Agent 调用外部工具时,主要面临以下安全风险:
- 数据泄露 (Data Leakage):
- Agent 自身泄露:Agent 在与用户交互或调用工具时,可能在 LLM 输出、日志或记忆中无意中泄露敏感信息。
- 工具接口泄露:如果工具接口设计不当或认证不足,可能被恶意利用,导致数据泄露。
- LLM 幻觉泄露:LLM 有时会“幻觉”出不存在的信息,如果这些信息恰好与敏感数据格式相似,可能被误认为是真实数据。
- 越权操作 (Unauthorized Operations):
- Agent 误用权限:LLM 错误理解用户意图,调用了超出用户或 Agent 权限范围的工具,例如,未经验证就执行高风险操作(如退款、修改核心信息)。
- 被攻击者利用:恶意用户通过“Prompt Injection”等方式,诱导 Agent 调用敏感工具或执行有害操作。
- 服务滥用与拒绝服务 (Service Abuse / DoS):
- Agent 陷入循环,无限次调用某个工具,导致后端服务过载。
- 恶意用户诱导 Agent 对目标服务进行大量无效调用,造成拒绝服务攻击。
- 恶意注入 (Prompt Injection):
- 这是 Agent 特有的风险,攻击者通过在用户输入中注入恶意指令,尝试覆盖或修改 Agent 的系统 Prompt,诱导其执行非预期行为,例如绕过安全限制,获取敏感信息,或执行未经授权的操作。
5.3.2 安全机制设计
为了应对上述风险,我们需要在 Agent 架构的各个层面构建多重安全防线。
- 输入校验与Prompt净化 (Input Validation & Prompt Sanitization):
- 用户输入校验:在将用户输入传递给 LLM 之前,对输入进行基本的安全校验,例如过滤掉常见的恶意脚本字符、SQL 注入关键字等。
- Prompt 净化:在将用户输入拼接进 Agent 的总 Prompt 之前,进行适当的转义或编码,防止 Prompt Injection 攻击。尽管 LLM 在一定程度上能够抵御简单的注入,但复杂的攻击仍可能成功。
- 敏感词过滤:在用户输入和 Agent 输出中,对敏感词进行过滤或替换,防止敏感信息直接暴露。
- 细粒度权限控制 (Fine-grained Access Control):
- 工具级权限:为每个工具定义明确的权限,例如,“订单查询”工具是只读权限,“退换货申请”工具是写权限。
- 用户-Agent-工具映射:将用户的身份与 Agent 可调用的工具集进行绑定。例如,普通用户只能查询自己的订单,而客服管理员可以查询所有订单。Agent 在调用工具前,需要先检查当前用户是否具备执行此操作的权限。
- 最小权限原则:赋予 Agent 及其调用的工具最小必要的权限,避免过度授权。
- 身份认证与授权 (Authentication & Authorization):
- 工具 API 认证: Agent 调用后端服务 API 时,必须通过标准的认证机制(如 OAuth2、API Key、JWT 等)进行身份验证。这些认证凭证应安全存储,并通过加密通道传输。
- 基于角色的访问控制 (RBAC):后端工具服务应实施 RBAC,确保只有具备相应角色的 Agent 或后端服务才能访问特定资源。
- 安全沙箱与隔离 (Sandboxing & Isolation):
- 如果 Agent 需要执行一些可能不安全或不可信的代码(例如,自定义插件、用户提供的脚本),应将其放在沙箱环境中运行,与其他核心系统隔离,以限制潜在的破坏范围。
- 不同 Agent 实例或不同用户会话之间的数据和状态也应严格隔离。
- 输出审查与脱敏 (Output Review & Sanitization):
- LLM 输出审查:在 Agent 的最终回复发送给用户之前,应对 LLM 生成的内容进行二次审查。这可以通过规则匹配、另一个小模型进行审核或人工审核来实现。
- 敏感信息脱敏:强制对 Agent 输出中的手机号、身份证号、银行卡号等敏感信息进行脱敏处理,例如
138****1234
。这可以通过正则表达式或专门的脱敏服务实现。
- 监控、审计与告警 (Monitoring, Auditing & Alerting):
- 全面日志记录:记录 Agent 的每一次对话、每一个
Thought
、每一次工具调用及其返回结果。这对于追踪问题、审计安全事件至关重要。 - 异常行为检测:对工具的调用频率、参数异常、错误率进行实时监控。例如,如果某个工具在短时间内被高频调用或连续返回错误,可能预示着攻击或 Agent 陷入循环。
- 告警机制:当检测到可疑或异常行为时,及时触发告警通知运维或安全团队。
- 全面日志记录:记录 Agent 的每一次对话、每一个
5.3.3 应对 Prompt Injection 的策略
Prompt Injection 是 Agent 特有的高级风险,需要特别关注:
- 指令与数据分离:在构建 Prompt 时,将系统指令(Agent 的核心规则、角色设定)与用户输入严格区分开。例如,将系统指令放在一个独立的、难以被用户输入覆盖的模板部分。
- 输入消毒与转义:对用户输入中的特殊字符进行转义,或限制其长度和结构。
- LLM 增强:选择对 Prompt Injection 有更好抵抗力的 LLM 模型。一些最新的模型会内置对抗注入的机制。
- 双重验证 (Human-in-the-Loop):对于高风险操作(如退款、修改密码),在 Agent 决定执行前,引入人工审核或要求用户进行二次确认(如短信验证码)。
- "守卫 Prompt" (Guard Prompt):在 Agent 真正执行业务逻辑之前,加入一个专门的“守卫 Prompt”来检测用户输入是否包含恶意指令,或是否尝试让 Agent 偏离其设定职责。如果检测到,则拒绝执行或转接人工。
5.3.4 案例实践中的安全性考量
对于电商客服 Agent:
- 订单查询:用户只能查询自己关联的订单。Agent 在调用
query_order_status
前,需要确保从会话上下文或用户身份验证中获取到正确的用户 ID,并在后端 API 调用时传递,让后端系统校验权限。 - 退换货申请:
- 这是写操作,必须有严格的用户身份验证(例如,通过短信验证码确认用户身份)。Agent 在调用
submit_return_request
前,必须先完成身份验证环节。 - 退货原因、数量等参数需经过 LLM 解析,但最终提交前,后端 API 仍需对这些参数进行严格的业务逻辑校验(如,是否在退货期内,数量是否合理等)。
- 这是写操作,必须有严格的用户身份验证(例如,通过短信验证码确认用户身份)。Agent 在调用
- 商品推荐:这是一个只读操作,风险相对较低,但仍需注意避免推荐不合规或违禁商品。
- 转人工客服:这是一个安全的兜底机制,当 Agent 无法处理或认为存在风险时,主动转人工,将风险转移给专业的人工处理。
总结:Agent 的工具调用能力是其核心价值所在,但安全性是构建信任和确保业务稳定运行的生命线。从前端的用户输入到后端的 API 调用,再到 Agent 自身的思考和输出,每一个环节都需要融入严密的安全性考量,并通过细粒度的权限管理、多重验证、严格的监控和反思机制,确保 Agent 在提供便利服务的同时,始终处于安全可控的范围之内。
5.4 案例实践:为电商客服 Agent 集成订单查询 API、退换货 API 和商品推荐 API
在前几节中,我们理解了工具的定义与封装原则,以及如何编写清晰的工具描述。现在,我们将把这些理论知识应用到具体的电商客服 Agent 场景中,集成几个核心 API,并展示它们如何在 Agent 的 Prompt 中被描述和启用。
我们的目标是让电商客服 Agent 能够:
- 查询订单状态和物流信息。
- 协助用户发起退换货申请。
- 根据用户需求推荐商品。
我们将结合 4.5 节的基础 Agent Prompt 示例,来详细展示这些工具的集成。
5.4.1 工具的定义与封装(回顾与集成)
在实际项目中,这些工具会作为独立的模块或函数,由后端服务提供。这里我们只展示其在 Python 中的封装骨架,并假设它们背后连接着真实的业务 API。
Python
# tools.py - 存放所有 Agent 可调用的工具函数
import json
from typing import Dict, Any, List
from langchain_core.tools import tool
# 模拟后端 API 调用,实际应通过 HTTP 请求调用真实服务
def _call_backend_api(api_name: str, params: Dict[str, Any]) -> Dict[str, Any]:
"""模拟后端服务调用"""
print(f"[后端模拟] 调用API: {api_name}, 参数: {params}")
if api_name == "order_service":
order_id = params.get("order_id")
# 模拟订单数据
mock_orders = {
"ORDER_12345": {"status": "已发货", "logistics_info": "顺丰快递,单号SF123456789,最新动态: 包裹已抵达上海转运中心", "estimated_delivery": "2025-06-28"},
"ORDER_67890": {"status": "待付款", "logistics_info": "N/A", "estimated_delivery": "N/A"},
"ORDER_CLOSED": {"status": "已完成", "logistics_info": "已签收", "estimated_delivery": "2025-06-20"}
}
return mock_orders.get(order_id, {"error": "订单号不存在或格式错误", "code": "ORDER_NOT_FOUND"})
elif api_name == "return_service":
order_id = params.get("order_id")
product_sku_id = params.get("product_sku_id")
quantity = params.get("quantity")
reason = params.get("reason")
if not all([order_id, product_sku_id, quantity, reason]):
return {"error": "退货申请参数不完整", "code": "MISSING_PARAMS"}
if order_id == "INVALID_ORDER" or product_sku_id == "INVALID_SKU":
return {"error": "订单或商品信息无效,无法提交退货", "code": "INVALID_INFO"}
return_id = f"RMA_{order_id}_{product_sku_id}_{hash(reason) % 1000}"
return {"status": "退货申请已提交", "return_id": return_id, "message": "您的退货申请已成功提交,请等待审核。"}
elif api_name == "product_recommendation_service":
user_id = params.get("user_id")
category = params.get("category")
query = params.get("query")
# 模拟推荐逻辑
if category == "衬衫":
return {"recommendations": [{"name": "商务休闲衬衫", "price": "189.00", "url": "http://example.com/shirt_biz"}, {"name": "时尚格子衬衫", "price": "129.00", "url": "http://example.com/shirt_grid"}], "message": "根据您的偏好,推荐以下衬衫。"}
elif query == "跑鞋":
return {"recommendations": [{"name": "Nike AirMax", "price": "799.00", "url": "http://example.com/nike_run"}, {"name": "Adidas UltraBoost", "price": "899.00", "url": "http://example.com/adidas_run"}], "message": "这里是一些热门跑鞋。"}
return {"recommendations": [], "message": "暂无推荐。"}
return {"error": "未知API", "code": "UNKNOWN_API"}
@tool
def query_order_status(order_id: str) -> str:
"""
查询指定订单号的当前状态、物流信息和预计送达时间。
在用户询问“我的订单号XXX到哪里了?”时使用。
参数: order_id (str): 用户的订单编号,例如 "ORDER_12345" 或 "20240501123456"。
返回: JSON字符串,包含状态、物流和预计送达时间,或错误信息。
"""
result = _call_backend_api("order_service", {"order_id": order_id})
return json.dumps(result, ensure_ascii=False)
@tool
def submit_return_request(order_id: str, product_sku_id: str, quantity: int, reason: str) -> str:
"""
提交用户的退货申请。此操作会创建一条新的退货记录。
在用户询问“我想退我买的手机”时可使用。
参数:
- order_id (str): 关联的订单编号。
- product_sku_id (str): 需要退货的商品SKU编号。
- quantity (int): 退货数量,必须是正整数。
- reason (str): 退货原因,应尽可能具体。例如"质量问题", "尺码不符", "不喜欢", "发错货"。
返回: JSON字符串,包含退货申请状态和ID,或错误信息。
"""
result = _call_backend_api("return_service", {
"order_id": order_id,
"product_sku_id": product_sku_id,
"quantity": quantity,
"reason": reason
})
return json.dumps(result, ensure_ascii=False)
@tool
def recommend_products(user_id: str = None, category: str = None, query: str = None) -> str:
"""
根据用户偏好、历史行为或搜索关键词推荐相关商品。
如果用户说“给我推荐几件衬衫”或“我需要一双跑鞋”,可以使用此工具。
参数:
- user_id (str, optional): 用户的唯一标识符,用于个性化推荐。
- category (str, optional): 用户感兴趣的商品品类,例如"衬衫", "手机", "图书"。
- query (str, optional): 用户输入的商品关键词,例如"夏季连衣裙", "降噪耳机"。
至少需要提供 category 或 query 中的一个。
返回: JSON字符串,包含推荐商品列表和消息。例如: '{"recommendations": [{"name": "...", "price": "...", "url": "..."}, ...], "message": "..."}'
"""
result = _call_backend_api("product_recommendation_service", {
"user_id": user_id,
"category": category,
"query": query
})
return json.dumps(result, ensure_ascii=False)
# 实际应用中,你可能还会有一个工具列表的变量,例如:
# ALL_AVAILABLE_TOOLS = [query_order_status, submit_return_request, recommend_products]
5.4.2 工具描述与 Agent Prompt 集成
现在,我们将这些工具的详细描述,集成到 Agent 的核心 Prompt 中。这个 Prompt 将被传递给 LLM,指导它何时以及如何使用这些工具。
你是一个专业的、友好的电商客服Agent,名为“电商智能助手”。
你的职责是高效准确地解答用户关于商品、订单、物流、退换货及促销活动的咨询。
你的核心目标是提升用户满意度,同时尽可能通过自助服务解决问题。
请始终保持积极、礼貌、耐心和专业的态度。使用简洁明了的中文进行交流,避免使用过于口语化或不专业的词汇。
严禁泄露用户个人敏感信息,如身份证号、银行卡号、完整手机号等。
不得提供超出你知识范围的、不准确的或带有误导性的信息。
你是一个能够使用外部工具来完成任务的Agent。请根据用户输入和你的能力,按照以下步骤进行思考和行动:
**思考过程 (Thought/Action/Observation Loop):**
你将按照以下思维链进行多步推理和任务分解:
1. **Thought**: 你当前的思考过程。分析用户意图,确定接下来需要做什么。如果需要多步才能完成任务,请在此处列出你的子任务分解。这里是你进行复杂逻辑推理、分解问题的地方。
2. **Action**: 你决定调用的工具名称(必须是下面“可用工具列表”中列出的工具之一)。如果任务已经完成并且准备好回复用户,使用 'Final Answer' 工具。
3. **Action Input**: 调用该工具所需的参数,以 JSON 格式表示,例如:`{"param1": "value1", "param2": "value2"}`。
4. **Observation**: 工具执行后返回的结果。
(重复 Thought/Action/Action Input/Observation 循环,直到任务完成)
5. **Final Answer**: 当你认为任务已完成并准备好回复用户时,以这个标签开始,并给出最终答案。你的最终答案应该清晰、准确,直接解决用户的问题,语言亲切、专业。回复结束后,可以主动询问用户是否还有其他需要帮助的地方。
**可用工具列表 (Tools):**
- `query_order_status(order_id: str)`:
功能: 用于查询指定订单号的当前状态、物流信息和预计送达时间。例如,用户询问“我的订单ABC123到哪里了?”时,应使用此工具。
参数:
- `order_id` (str, 必需): 用户的订单编号,通常是一串数字或字母数字组合。例如 "ORDER_12345" 或 "20240501123456"。
返回: JSON字符串,包含状态、物流和预计送达时间。例如: '{"status": "已发货", "logistics_info": "顺丰快递,单号SF123456789,最新动态: 包裹已抵达[城市]转运中心", "estimated_delivery": "2025-06-28"}'。
错误返回: 如果订单号不存在或格式错误,返回 '{"error": "订单号不存在或格式错误", "code": "ORDER_NOT_FOUND"}'。
- `submit_return_request(order_id: str, product_sku_id: str, quantity: int, reason: str)`:
功能: 用于提交用户的退货申请。此操作会创建一条新的退货记录。用户询问“我想退我买的手机”时可使用。
参数:
- `order_id` (str, 必需): 关联的订单编号,例如 "ORDER_67890"。
- `product_sku_id` (str, 必需): 需要退货的商品SKU编号。
- `quantity` (int, 必需): 退货数量,必须是正整数。
- `reason` (str, 必需): 退货原因,应尽可能具体。例如"质量问题", "尺码不符", "不喜欢", "发错货"。
返回: JSON字符串,包含退货申请状态和ID。例如: '{"status": "退货申请已提交", "return_id": "RMA_...", "message": "您的退货申请已成功提交,请等待审核。"}'。
错误返回: 如果缺少必需参数或订单/商品信息无效,返回 '{"error": "...", "code": "..."}'。
- `recommend_products(user_id: str = None, category: str = None, query: str = None)`:
功能: 根据用户偏好、历史行为或搜索关键词推荐相关商品。如果用户说“给我推荐几件衬衫”或“我需要一双跑鞋”,可以使用此工具。
参数:
- `user_id` (str, optional): 用户的唯一标识符,用于个性化推荐。如果可用,请务必提供。
- `category` (str, optional): 用户感兴趣的商品品类,例如"衬衫", "手机", "图书"。
- `query` (str, optional): 用户输入的商品关键词,例如"夏季连衣裙", "降噪耳机"。
至少需要提供 category 或 query 中的一个,如果两者都有,优先使用 query。
返回: JSON字符串,包含推荐商品列表和推荐消息。例如: '{"recommendations": [{"name": "商务休闲衬衫", "price": "189.00", "url": "http://example.com/shirt_biz"}, {"name": "时尚格子衬衫", "price": "129.00", "url": "http://example.com/shirt_grid"}], "message": "根据您的偏好,推荐以下衬衫。"}'。
错误返回: 如果推荐服务异常或无推荐结果,返回 '{"recommendations": [], "message": "暂无推荐。"}'。
**当前对话历史 (Conversation History):**
[这里将动态插入用户和Agent之间的多轮对话历史,例如:
Human: 你好!
AI: 您好!我是电商智能助手,请问有什么可以帮您的?
Human: ...
]
**用户输入 (User Input):**
{用户的当前输入}
**Agent 回复 (Agent Response):**
5.4.3 初始行为示例与测试
有了上述 Prompt,我们的电商客服 Agent 就能开始工作,并展现出基于工具调用的智能行为:
示例 1:用户查询订单物流
- 用户输入:
我的订单号 ORDER_12345 到哪里了?
- Agent (LLM 的 Thought):
Thought: 用户想查询订单物流,我需要使用 'query_order_status' 工具。
Action: query_order_status
Action Input: {"order_id": "ORDER_12345"}
- Agent (框架执行工具):
query_order_status("ORDER_12345")
返回{"status": "已发货", "logistics_info": "顺丰快递,单号SF123456789,最新动态: 包裹已抵达上海转运中心", "estimated_delivery": "2025-06-28"}
- Agent (LLM 接收 Observation 后再次 Thought):
Observation: {"status": "已发货", "logistics_info": "顺丰快递,单号SF123456789,最新动态: 包裹已抵达上海转运中心", "estimated_delivery": "2025-06-28"}
Thought: 我已经获取到订单物流信息,现在可以给用户提供最终答案了。
Final Answer: 您的订单 ORDER_12345 已于2025年6月25日发货,目前包裹已抵达上海转运中心,预计2025年6月28日送达。您还有其他问题吗?
示例 2:用户请求退货(需要补全信息)
- 用户输入:
我想退货。
- Agent (LLM 的 Thought):
Thought: 用户想退货,这需要使用 'submit_return_request' 工具。但是,退货需要订单号、商品SKU、数量和具体原因。用户没有提供这些信息,我需要先询问。
Action: Final Answer
(LLM 会判断现在无法直接调用工具,需要向用户索取更多信息)Action Input: N/A
Final Answer: 您好!请问您要退的是哪一个订单中的哪件商品呢?以及退货数量和具体的退货原因是什么?提供这些信息我才能帮您处理哦。
示例 3:用户请求商品推荐
- 用户输入:
给我推荐一些衬衫吧。
- Agent (LLM 的 Thought):
Thought: 用户想看衬衫推荐,我需要使用 'recommend_products' 工具,并指定品类为“衬衫”。
Action: recommend_products
Action Input: {"category": "衬衫"}
- Agent (框架执行工具):
recommend_products(category="衬衫")
返回{"recommendations": [{"name": "商务休闲衬衫", "price": "189.00", "url": "http://example.com/shirt_biz"}, {"name": "时尚格子衬衫", "price": "129.00", "url": "http://example.com/shirt_grid"}], "message": "根据您的偏好,推荐以下衬衫。"}
- Agent (LLM 接收 Observation 后再次 Thought):
Observation: {"recommendations": [...], "message": "根据您的偏好,推荐以下衬衫。"}
Thought: 我已经获取到商品推荐列表,现在可以给用户提供最终答案了。
Final Answer: 好的,为您推荐几款衬衫:1. 商务休闲衬衫,价格189元。2. 时尚格子衬衫,价格129元。您可以点击链接查看详情。您对这些推荐满意吗?或者有其他品类想了解?
5.4.4 总结
通过上述案例实践,我们展示了如何将后端业务能力封装成 Agent 可理解和调用的工具,并通过精心设计的 Prompt (特别是工具描述部分) 来引导 Agent 进行正确的规划和执行。这是一个将 LLM 从单纯的“聊天机器人”提升为能够“解决实际问题”的 Agent 的核心过程。在实际部署中,这些工具会与真实的服务接口对接,并结合安全性、权限管理和持续优化机制,构建一个强大的企业级智能 Agent。