AI Engineering(2)
搭一个简单的智能体
比如说我们想要搭一个与用户能够交流处理退款的agent
在搭一个agent之前我们要搞清楚一个agent有啥
1.LLM:这是agent的大脑,我们要想好我们需要哪家的大模型,拿到大模型我们要不要根据预训练的模型微调,使之适合我们的业务
2.tools:这是agent的工具箱,很多时候我们要根据业务流程自己用python和langchain写代码造工具
3.memory:这是agent的记忆模块,一般分为长期记忆和短期记忆
4.subagent:这是agent的手下,子智能体
5.identity:这是agent的角色与使命
6.workflow:这是agent的工作流
7.tone:这是agent的语气和沟通原则
8.limitations:这是agent的工作规范和限制
9.health:这是agent的健康状态检查和稳定系统
很像一个实习生是吧,确实现在的agent充当的是实习生的作用,辅助我们的工作流
上面是一个agent需要具有的东西,那agent开发中我们会在agent开发项目文件中实现这些东西的配置。
一般的VSCode项目目录长成这样:
my-refund-agent/ # 📁 项目根目录
├── AGENTS.md # [必读] 全局项目规范 (如代码风格、团队SOP总则)
├── main.py # [主入口] 智能体启动与编排逻辑
│
├── .deepagents/ # 🧠 LangChain专用, 含记忆、项目指令等
│ ├── AGENTS.md # [自动加载] 项目指令的优先存放位置
│ ├── memory/ # 🗃️ 长期记忆 (由智能体自动读写)
│ │ └── learned_conventions.md
│ ├── agents/ # 🤖 子智能体定义 (Markdown + YAML)
│ │ ├── order-checker/ # 一个叫“order-checker”的子智能体
│ │ │ └── AGENTS.md # 子智能体的身份、分工与SOP (用YAML元数据描述)
│ │ └── compensator/ # 另一个叫“compensator”的子智能体
│ │ └── AGENTS.md
│ └── skills/ # 📦 项目专属技能 (可被任务按需触发)
│ ├── refund_processor/ # 你的退款处理技能
│ │ └── SKILL.md # |-- [核心] 退款SOP就在这里 (用Markdown编写)
│ └── coupon_manager/ # 其他技能
│ └── SKILL.md
│
├── .agents/ # 🌐 跨工具通用, 用于存放可在不同AI CLI间共享的公共技能
│ └── skills/
│ └── system_diagnostics/ # 不依赖特定框架的通用技能
│ └── SKILL.md
│
├── config/ # ⚙️ 结构化配置文件 (YAML, JSON格式)
│ ├── refund_policies.yaml # 退款规则配置 (如退货期限、退款比例)
│ ├── tools_config.yaml # 工具配置 (如短信服务、内部API的凭据和端点)
│ └── langgraph.json # LangGraph 服务器部署配置
│
├── tools/ # 🔧 自定义工具实现
│ ├── __init__.py
│ ├── order_api.py # 对接内部订单系统API
│ ├── payment_gateway.py # 封装支付网关
│ └── sms_sender.py # 触发短信验证码发送
│
├── tests/ # 🧪 测试
│ ├── test_refund_agent.py
│ └── test_order_tools.py
│
├── .env # 环境变量 (API keys, 请勿提交到Git)
├── .gitignore
├── requirements.txt # Python依赖包
└── README.md
下面我们一一介绍这些文件:
一.记忆系统
1.项目根目录下的AGENT.MD
作用:全局级别的智能体行为准则、公司退款政策摘要和通用处理标准。所有智能体实例(包括子智能体)都能访问此文件,是最高级别的知识指引
markdown
# 公司退款处理总则
## 角色与使命
你是 Acme 商城的退款处理专家,目标是为客户提供**高效、公正且富有同理心**的退款体验。你必须严格遵守公司政策,同时灵活应对边缘情况。
## 退款政策核心要点
1. **退款时效**:商品签收后 30 天内可申请退款。
2. **商品状态**:必须保持原包装完整,未经使用或激活。消耗品(如食品、软件授权)一经开封概不退换。
3. **退款方式**:按原支付路径退回,处理时间 3-5 个工作日。
4. **特殊订单**:定制商品、闪购商品、超过 5000 元的高价值订单需主管审批。
5. **运费处理**:因我方过失(发错货、严重破损)我方承担双向运费;客户原因退款仅退还商品金额,客户承担退货运费。
## 退款流程关键步骤
- **Step 1 验证订单**:通过订单编号确认订单存在且状态可退款。
- **Step 2 核实原因**:礼貌询问退款原因,区分是我方责任还是客户个人原因。
- **Step 3 条件检查**:核对商品状态、时效、是否属于可退款品类。
- **Step 4 执行通知**:创建退款单并通过短信通知客户。
- **Step 5 升级规则**:遇到金额 ≥ 5000 元、争议订单、系统无法自动处理的场景,必须升级到人工主管。
## 语气与沟通原则
- 保持专业、温和且耐心。
- 对客户表示理解和共情,尤其是因我方问题导致的投诉。
- 信息透明:清晰说明退款金额、到账时间和后续步骤。
- 若无法满足退款要求,委婉解释原因并提供替代方案(如换货、店铺积分)。
## 数据隐私
禁止泄露任何客户个人身份信息(PII)。讨论订单时只使用订单号和脱敏后的联系方式。
2.深目录下的基础配置:.deepagents/AGENTS.md
作用:deepagents 框架特定的主配置文件。它定义了智能体如何使用工具、记忆、技能和子智能体的行为约束,可作为对根目录 AGENTS.md 的补充和细化。
# Deep Agent 行为配置
## 记忆使用策略
- **自动检索**:在每次对话开始时,自动从 `.deepagents/memory/` 加载相关上下文。优先读取 `refund_policy_details.md` 以获取详细策略。
- **动态更新**:当客户表达新的偏好或退款处理完毕后,将关键结论摘要写入 `.deepagents/memory/user_notes.md`(需经用户确认)。
## 技能激活规则
- 当退款金额 ≥ 5000 元时,自动激活 `high_value_refund` 技能(位于 `.deepagents/skills/high_value_refund/skill.md`)。
- 当客户提及“物流问题”“破损”等关键词时,激活 `logistics_issue_handler` 技能。
- 技能激活后,其指令将临时覆盖标准流程,技能执行完毕后恢复。
## 工具调用协议
- `order_api_tool`:务必在对话早期调用以获取权威订单状态。如果返回错误,向客户确认订单号并重试一次。
- `sms_sender_tool`:仅在退款创建成功或需要客户提供额外凭证时调用。**严禁发送营销内容**。
- 所有工具调用前需先向客户说明即将进行的操作(如“我帮您查一下订单状态”)。
## 子智能体委派
- `order-checker` 子智能体专门负责核实订单真实性和完整性。当怀疑订单造假、订单状态异常或需要深度查询物流轨迹时,将任务委派给它。
- 委派后等待子智能体结果,整合后回复客户,不得将子智能体的原始内部输出直接转给客户。
## 故障处理
- 若工具连续失败 2 次,停止自动重试,向客户说明系统暂时故障,并建议等待或转接人工。
- 遇到政策灰色地带,优先从宽处理但备注详情,避免反复拉锯。
你会发现相较于根目录下的AGENT.MD这更细,这需要依靠业务人员的经验
3. .deepagents/memory/ :目录下的持久化知识文件
作用:存放结构化且可更新的持久事实,如详细的退款政策矩阵、常见问题决策树、客户历史记录等。智能体在每次推理时会搜索这些文件,并且在每次执行完任务后会自己更新这些文件
我们可以在这跟目录下创建两个文件(可根据需要扩展):
文件 refund_policy_details.md
markdown
# 退款政策详细矩阵
## 按品类退款规则
| 品类 | 退货窗口 | 开封后退款 | 特殊条件 |
|------------------|----------|------------|--------------------------|
| 电子产品 | 30 天 | 若激活则拒 | 需提供序列号相片 |
| 服装鞋帽 | 30 天 | 允许 | 吊牌完好,未穿着外出 |
| 食品与饮料 | 7 天 | 开封即拒 | 包装破损可酌情 |
| 虚拟商品/软件 | 0 天 | 不可退 | 除非授权未使用 |
| 定制/个人化商品 | 0 天 | 不可退 | 属非标产品 |
## 退款金额计算
- 使用优惠券的订单:仅退还实际支付金额,优惠券不退。
- 组合套餐中的部分退款:按单品原价比例折算,剩余商品恢复原价。
- 发生返现/积分抵扣的订单:优先退还实际支付部分,积分按有效期返还。
## 物流责任判定
- 快递公司提供签收底单 → 责任在客户。
- 物流轨迹停滞超过 7 天 → 我方协查,确认丢件后先行退款并追偿。
- 商品外包装严重破损 → 客户拒收,物流反馈后立即退款。
## 退货物流支持
- 合作物流:顺丰、京东快递,系统可自动下发上门取件码。
- 若客户所在地无合作物流,指导自行寄回,运费凭票据报销。
文件 user_notes.md(初始为空模板)
# 用户备注与交互历史摘要
请在下方按客户编号或订单号记录重要交互,方便后续会话快速加载。
<!-- 示例记录格式:
### ORD-1001 (客户手机尾号 1234)
- 2026-05-08: 客户因耳机音质问题申请退款,已通过退款申请,金额 299.99 元,待寄回。
- 客户偏好短信通知,不喜电话打扰。
-->
二. 技能系统
以下是为 refund_processor 和 coupon_manager 两个技能编写的 SKILL.md 文件,采用 YAML 头部 + Markdown 正文 的混合格式,与 deepagents 框架兼容。智能体运行时可根据触发条件按需激活对应技能,动态加载其中的流程指令
1. 退款处理技能 refund_processor/SKILL.md
markdown
# 技能元数据
name: refund_processor
description: 处理标准退款请求的完整 SOP,覆盖验证订单、审核退款资格、计算金额、执行退款和通知客户。
triggers:
- keyword: ["退款", "退货", "refund", "return", "申请退款"]
- condition: "退款金额 >= 5000" # 高金额自动激活,同时触发升级规则
- context: "用户明确表达退款意图"
priority: high
version: 1.0.0
---
# 退款处理标准操作流程 (SOP)
## 前置检查
在进入本流程前,确保已经通过 `order_api_tool` 获取到订单实时信息,并将结果保存在工作记忆中。
若订单不存在或信息错误,中断本流程,礼貌要求客户核实订单号。
## 流程步骤
### 步骤 1:确认退款资格
- 检查 `order.status`:
- `shipped` / `delivered` → 继续。
- `pending` → 询问客户是否确认取消订单,若同意则走取消流程,不涉及退货。
- `cancelled` / `refunded` → 告知客户订单已失效/已退款,结束。
- 检查 `order.is_refundable`,若为 `false`,向客户解释原因(参考全局政策),提供替代方案(换货、积分补偿),**不得强行退款**。
### 步骤 2:收集退款原因
- 使用开放式问题询问退款原因,例如:“为了更好地为您处理,请问是商品质量问题、与描述不符,还是个人原因呢?”
- 将原因记录到上下文中,后续可用于更新 `user_notes.md` 及上报。
- **特别关注关键词**:破损、发错、质量问题 → 标记为“商家责任”;其他默认“客户原因”。
### 步骤 3:计算退款金额
根据原因和责任方,调用退款金额计算逻辑:
- 商家责任:
- 全额退还 `total_amount`;
- 客户已付运费 → 一并退还;
- 提供免费退货物流(调用 `logistics_issue_handler` 技能,若存在)。
- 客户原因:
- 仅退还商品金额 `total_amount`;
- 原运费不退;退货运费由客户承担。
- **优惠券/积分处理**:若订单使用了优惠券或积分,激活 `coupon_manager` 技能进行精确计算,避免退款金额错误。
### 步骤 4:发送退款确认与通知
- 调用 `sms_sender_tool` 发送退款确认短信,模板:
> “【Acme商城】您的订单 {order_id} 退款已受理,金额 {refund_amount}元 将退回原支付账户,预计3-5个工作日到账。退货运单号 {return_label},请按指引寄回。如有疑问请联系客服。”
- 仅在退款单**真正创建**后发送,避免误通知。
- 如果用户选择不接收短信,则跳过。
### 步骤 5:生成退货运单(如需要退货)
- 商家责任或客户需要退货时,生成退货运单号(此处可调用物流工具,若可用)。
- 将运单号包含在通知中。
### 步骤 6:升级条件检查
在以下任一条满足时,暂停自动处理,转入人工主管:
- `total_amount >= 5000`;
- 客户坚持全额退款但政策不支持,且协商失败;
- 订单标记为可疑(如高风险地址);
- 工具调用连续失败 2 次。
升级时,向客户说明:“您的情况需要专员复核,我会将您的诉求第一时间转接,请您稍候。”
### 步骤 7:记录与归档
- 在 `user_notes.md` 中追加本次处理摘要,格式:
[日期时间] 订单 {order_id}:{退款原因};责任方:{商家/客户};退款金额:{金额};状态:已完成/已升级
- 确保敏感信息脱敏(手机号中间四位隐藏)。
## 异常处理
- 订单状态与客户描述严重不符 → 重新核实,必要时委派 `order-checker` 子智能体。
- 退款计算出现歧义 → 优先从宽,但备注详情,避免客户二次投诉。
2. 优惠券管理技能 coupon_manager/SKILL.md
markdown
name: coupon_manager
description: 处理退款时涉及的优惠券、满减、积分等优惠资产的退回、作废或补发规则,确保退款金额准确并符合营销政策。
triggers:
- condition: "订单使用了优惠券、积分或参与促销活动"
- keyword: ["优惠券", "折扣", "积分", "满减", "券"]
- dependency: "refund_processor 技能激活时自动检查触发"
priority: medium
version: 1.0.0
---
# 优惠券与积分退款处理规则
本技能在退款流程中被激活,用于精确计算退款金额并决定优惠资产的处理方式。
## 核心原则
- **优惠券不可退还为现金**:用户支付的优惠券金额不参与退款。
- **退回的优惠按规则恢复**:视活动规则,部分优惠券可在退款后恢复至用户账户,但不可折现。
- **积分规则**:使用积分抵扣的金额,优先退还实际支付部分(现金/银行卡),积分原路退回至账户,有效期不延长。
- **组合套餐拆分退款**:按各单品原价比例拆分优惠,避免客户套取差价。
## 退款金额计算公式
- 退款实退金额 = 原订单实付金额 - 已使用不可恢复优惠 - 平摊运费
- 详细拆解:
1. 计算订单总优惠额度(满减券 + 折扣券 + 积分抵扣)。
2. 若是部分退款(如退其中一件商品),按单品原价占比分配优惠额度。
3. 将分摊到该商品上的优惠金额扣除后得到实际应退金额。
4. 如果用户使用多张券,按券规则决定哪些可恢复、哪些已消耗。
## 优惠券处理决策树
1. **全场满减券**(如 200-30):
- 全额退款 → 券退回用户账户,有效期不变。
- 部分退款后剩余商品金额不再满足满减门槛 → 券不予恢复,视为已消耗。
2. **单品折扣券**(如 8 折):
- 退款后,该券即作废,不恢复。
3. **积分抵扣**:
- 全额退款 → 积分全部退回,有效期不变。
- 部分退款 → 按退款商品所占积分抵扣比例退回相应积分。
4. **平台补贴/红包**:
- 退款后红包自动失效,不补发,需向客户解释。
## 与退款流程交互
- 当 `refund_processor` 计算退款金额时,调用本技能的逻辑(或由主智能体直接应用本规则)。
- 返回结果应包含:
- `adjusted_refund_amount`:最终应退金额
- `coupon_actions`:券处理动作列表(如“退回券 ID 12345”)
- `points_actions`:积分调整列表(如“退回 500 积分”)
- 如果遇到规则未覆盖的复杂促销(如闪购秒杀),标记升级,备注:“涉及特殊营销,需人工核对”。
## 示例场景
- **场景 A**:用户购买手机壳 59.50 元,使用 10 元无门槛券,实付 49.50 元。全额退款 → 退 49.50 元,10 元券退回。
- **场景 B**:订单 2 件衣服,使用 200-30 券。只退其中 1 件,导致剩余商品总价 120 元不满足 200 门槛 → 30 元券作废,退单品金额扣除已分摊优惠后为 120*(1-30/200)=102 元。
- **场景 C**:混合支付(现金+积分),全额退 → 现金全额退回,积分原路返回。
务必确保客户对退款金额明细知情,可在通知短信中简要说明:“含优惠券退回 10 元券”,提升客户体验。
三. 工具系统
我们用langchain写两个tool:order_api_tool和sms_sender_tool
python
import os
import httpx
from typing import Optional
from langchain_core.tools import tool
# ------------------------------------------------------------
# 1. 订单查询工具
# ------------------------------------------------------------
@tool
def order_api_tool(order_id: str) -> dict:
"""
根据订单号查询订单详细信息,包括订单状态、金额、商品列表、退款资格等。
参数:
order_id: 要查询的订单唯一标识符(字符串格式,如 "ORD-12345")
返回:
包含订单详细信息的字典,至少包含以下字段:
- order_id: 订单号
- status: 订单状态 (pending/shipped/delivered/cancelled/refunded)
- total_amount: 订单总金额(浮点数)
- items: 商品列表
- is_refundable: 是否可退款(布尔值)
"""
# ---- 实际应用中替换为真实的 API 调用 ----
# 这里模拟调用内部订单服务(REST API 或 gRPC)
# 可使用 httpx 或 requests 库:
#
# async with httpx.AsyncClient() as client:
# resp = await client.get(
# f"{ORDER_SERVICE_URL}/orders/{order_id}",
# headers={"Authorization": f"Bearer {os.getenv('API_KEY')}"}
# )
# resp.raise_for_status()
# return resp.json()
# ---- 模拟数据,便于测试 ----
mock_db = {
"ORD-1001": {
"order_id": "ORD-1001",
"status": "delivered",
"total_amount": 299.99,
"items": [{"name": "无线耳机", "price": 299.99, "quantity": 1}],
"is_refundable": True
},
"ORD-1002": {
"order_id": "ORD-1002",
"status": "refunded",
"total_amount": 159.50,
"items": [{"name": "手机壳", "price": 59.50, "quantity": 2}],
"is_refundable": False
}
}
if order_id in mock_db:
return mock_db[order_id]
else:
# 工具执行出错时,可返回错误信息供智能体判断
return {"error": f"订单 {order_id} 不存在,请核实订单号。"}
# ------------------------------------------------------------
# 2. 短信发送工具
# ------------------------------------------------------------
@tool
def sms_sender_tool(phone_number: str, message: str) -> str:
"""
向指定手机号发送短信通知,通常用于退款成功、流程更新等场景。
参数:
phone_number: 接收短信的手机号,需包含国家代码,如 "+8613800138000"
message: 要发送的短信文本内容,长度不超过 500 字符
返回:
发送结果的描述字符串,成功时返回 "发送成功",失败时返回错误信息
"""
# ---- 接入真实短信服务 (如 Twilio, 阿里云短信等) ----
# 示例:使用 Twilio
# from twilio.rest import Client
# client = Client(os.getenv("TWILIO_ACCOUNT_SID"), os.getenv("TWILIO_AUTH_TOKEN"))
# msg = client.messages.create(body=message, from_="+1234567890", to=phone_number)
# return f"发送成功,SID: {msg.sid}"
# 或者使用异步 httpx 调用内部短信网关:
# async with httpx.AsyncClient() as client:
# resp = await client.post(
# f"{SMS_GATEWAY_URL}/send",
# json={"to": phone_number, "text": message},
# headers={"Authorization": f"Bearer {os.getenv('SMS_API_KEY')}"}
# )
# resp.raise_for_status()
# return resp.json()["message"]
# ---- 模拟发送(打印即可,方便调试) ----
# 在实际生产环境中,请去掉模拟代码,启用真实的 API 调用。
print(f"[模拟短信] 发送至 {phone_number}: {message}")
# 简单校验手机号格式
if not phone_number.startswith("+"):
return "发送失败:手机号格式错误,需要包含国家代码(例如 +86...)。"
return "发送成功"
# ------------------------------------------------------------
# 工具列表(可直接用于 AGENT_CFG['tools'])
# ------------------------------------------------------------
tools = [order_api_tool, sms_sender_tool]
关键点说明
-
@tool装饰器
LangChain 会自动将函数的名称、文档字符串(docstring)和参数类型转换为工具的名称、描述和输入 schema。大型语言模型正是通过读取这些信息来决定何时及如何调用工具。 -
输入参数
-
order_api_tool接收order_id: str,返回结构化字典。 -
sms_sender_tool接收phone_number: str和message: str,返回状态字符串。
智能体(LLM)会根据用户对话内容自动提取这些参数并调用工具。
-
-
错误处理
工具内部返回包含error信息的字典或失败描述,而不是直接抛出异常。这样智能体可以“看到”错误信息并尝试修正(例如向用户询问正确的订单号),流程不会因异常中断。 -
如何集成到原有的智能体配置
直接将这两把工具放入AGENT_CFG的tools列表即可:python from tools import order_api_tool, sms_sender_tool AGENT_CFG = { "tools": [order_api_tool, sms_sender_tool], # ... 其余记忆、技能等配置 }
四.健康与稳定系统
健康与稳定系统.agents/skills/system_diagnostics/SKILL.md 的内容。这是一个与框架无关、可跨不同 AI CLI 共享的通用技能,用于对智能体自身的运行环境及外部依赖进行健康检查,确保工具和服务可用,并在出现问题时给出清晰的诊断信息。
markdown
# 跨工具通用技能元数据
name: system_diagnostics
description: >
对智能体运行环境及其依赖的外部服务(订单 API、短信网关、数据库等)进行快速健康检查,
定位常见故障并提供修复建议。本技能与具体框架无关,可在任何支持 Markdown 技能的 AI 平台中使用。
triggers:
- keyword: ["系统诊断", "健康检查", "自检", "tools check", "服务状态"]
- condition: "主智能体或子智能体连续两次调用同一工具失败"
- on_demand: "用户或运维人员直接请求诊断"
priority: high
version: 1.0.0
framework_agnostic: true
# 系统诊断与健康检查技能
## 目标
确保智能体依赖的所有关键组件(工具 API、记忆存储加载、子智能体通信等)均正常工作。在客户退款流程中出现异常时,快速判断是系统故障还是输入错误,并提供可操作的恢复步骤。
## 诊断范围
1. **外部 API 连通性**:订单服务、短信网关
2. **本地知识库完整性**:`memory/` 目录下的政策文件是否存在且可解析
3. **子智能体可达性**:子智能体是否能够正常启动并响应
4. **检查点存储器状态**:SQLite/Redis 等状态存储是否可读写
5. **技能文件语法校验**:检查所有 `SKILL.md` 文件 YAML 头部是否规范
## 诊断流程
### 步骤 1:外部 API 连通性测试
- 调用 `order_api_tool` 的健康检查端点(如果工具提供),或使用一个已知的测试订单号(如 `health-check-0000`)尝试请求。
- 调用 `sms_sender_tool` 的测试接口(如发送至内部测试号码或检查网关响应)。
- 记录每次调用的响应时间和状态码。
- **判定标准**:返回状态码 200 且在 3 秒内响应视为健康;超时或非 2xx 视为异常。
### 步骤 2:知识库完整性检查
- 遍历 `memory` 配置中指定的所有文件/目录,确保文件存在且非空。
- 尝试加载并解析文件内容,检测 Markdown 格式错误或缺失必要字段。
- 对于 `.deepagents/memory/user_notes.md`,检查是否可用写入权限(若智能体需要更新)。
- 输出缺失或损坏的文件列表。
### 步骤 3:子智能体通信测试
- 向 `order-checker` 子智能体发送一个简单的探测任务(如:验证订单号 `diag-0000` 是否存在),设置超时 5 秒。
- 如果子智能体无响应或返回错误,记录失败原因(网络分区、配置错误、子智能体未启动等)。
### 步骤 4:检查点存储器验证
- 尝试向检查点存储 (SQLite 或 Redis) 写入一个键值对 `diag_timestamp: <当前时间>`,然后立即读取并比较。
- 若写入或读取失败,返回存储后端异常,可能原因:磁盘满、权限不足、数据库文件损坏。
### 步骤 5:技能文件语法检查
- 扫描 `.deepagents/skills/` 和 `.agents/skills/` 下所有 `SKILL.md` 文件。
- 提取 YAML 头部,验证 `name` 和 `description` 字段非空,版本号格式正确。
- 报告任何解析失败的文件名。
## 诊断输出格式
诊断结束后,生成如下结构化报告:
关键点说明
-
- 输出应该是这种形式:
系统诊断报告 - {时间戳} ================================ [✓] 订单 API:正常 (响应时间: 120ms) [✗] 短信网关:异常 - 连接超时 (错误: timeout after 5s) [✓] 知识库文件:3/3 文件完整 [✗] 子智能体 'order-checker':无法连接 (错误: 'agent not found') [✓] 检查点存储:读写正常 [!] 技能文件:1 个警告 (coupon_manager SKILL.md 缺少 version 字段 -
2.虽然
order_api_tool和sms_sender_tool的代码是我们自己编写的,但它们本质上是对外部远程服务的封装: -
order_api_tool内部通常会调用一个运行在远端服务器上的订单微服务 REST/gRPC 接口; -
sms_sender_tool则依赖第三方短信网关(如 Twilio、阿里云短信)或公司内部的短信中台。
所以 “外部 API 连通性测试” 测的并不是我们写的工具函数本身(那些在单元测试 test_order_tools.py 中已经覆盖),而是工具背后真实依赖的远端服务是否可达、响应是否正常。工具代码只是桥梁,在系统诊断中,必须越过桥梁去确认对岸的“外部”服务是健康的。因此把它们归为外部 API 连通性测试是准确且必要的。
五.测试系统
1. test_order_tools.py —— 工具单元测试
目的
验证 order_api_tool 和 sms_sender_tool 两个独立工具函数在正常情况、异常情况、边界值时的输出是否符合预期,确保工具本身的质量。
常用框架:pytest + unittest.mock(模拟外部 API 调用)
应包含的测试场景
| 测试对象 | 场景 | 预期 |
|---|---|---|
order_api_tool |
查询存在的订单 | 返回完整的订单字典,包含必要字段 |
| 查询不存在的订单 | 返回包含 error 的字典,不抛出异常 |
|
| 空字符串订单号 | 返回错误信息 | |
sms_sender_tool |
格式正确的手机号 + 正常内容 | 返回 "发送成功" |
| 手机号缺少国家代码 | 返回包含“格式错误”的失败信息 | |
| 消息体超过限制 | 根据实现返回失败(如截断或报错) | |
| 模拟短信网关异常 | 返回失败信息,不中断流程 |
示例代码 (test_order_tools.py)
python
import pytest
from unittest.mock import patch
from tools import order_api_tool, sms_sender_tool
# 测试 order_api_tool
class TestOrderApiTool:
def test_existing_order(self):
result = order_api_tool("ORD-1001")
assert result["order_id"] == "ORD-1001"
assert "status" in result
assert "total_amount" in result
assert isinstance(result["is_refundable"], bool)
def test_nonexistent_order(self):
result = order_api_tool("NO-SUCH-ORDER")
assert "error" in result
assert "不复存在" in result["error"] # 中文错误提示验证
def test_empty_order_id(self):
result = order_api_tool("")
assert "error" in result
# 模拟真实 API 调用(避免依赖外部服务)
@patch("httpx.Client.get") # 假设用了 httpx
def test_real_api_success(self, mock_get):
mock_get.return_value.json.return_value = {
"order_id": "ORD-9999",
"status": "delivered",
"total_amount": 100,
"items": [],
"is_refundable": False
}
mock_get.return_value.raise_for_status = lambda: None
result = order_api_tool("ORD-9999")
assert result["status"] == "delivered"
@patch("httpx.Client.get")
def test_real_api_timeout(self, mock_get):
mock_get.side_effect = Exception("连接超时")
result = order_api_tool("ORD-0000")
assert "error" in result # 优雅降级
# 测试 sms_sender_tool
class TestSmsSenderTool:
def test_valid_phone_and_message(self):
result = sms_sender_tool("+8613800138000", "您的退款已受理")
assert result == "发送成功"
def test_phone_missing_country_code(self):
result = sms_sender_tool("13800138000", "退款通知")
assert "格式错误" in result
def test_empty_message(self):
result = sms_sender_tool("+8613800138000", "")
# 假设空消息也允许发送(或应防止),根据实现决定预期
assert result == "发送成功" # 或 assert "失败" in result
@patch("tools.httpx.Client.post") # 模拟短信网关异常
def test_gateway_failure(self, mock_post):
mock_post.side_effect = Exception("短信服务暂不可用")
result = sms_sender_tool("+8613800138000", "测试")
assert "失败" in result or "error" in result.lower()
2. test_refund_agent.py —— 智能体集成与行为测试
目的
验证完整智能体在各种对话流程中的表现是否符合公司在 AGENTS.md 和 SKILL.md 中定义的业务规则。这是典型的端到端测试,确保工具、记忆、技能、子智能体协同工作正确。
常用框架:pytest + LangChain 的 AgentExecutor 或者直接调用 agent.invoke(),配合 unittest.mock 模拟工具外部依赖。有时还会使用 场景化断言(检查 LLM 回复是否包含某些关键词)。
应包含的测试场景
| 测试场景 | 模拟工具行为 | 预期智能体行为 |
|---|---|---|
| 正常退款流程 | order_api_tool 返回可退款订单 |
智能体查询订单 → 确认退款资格 → 请求用户确认 → 调用 sms_sender_tool 发送成功通知 |
| 订单不可退款(已退款) | order_api_tool 返回 is_refundable=False |
智能体礼貌告知用户订单已退款,不尝试退款,不发送短信 |
| 高金额订单(≥5000元) | order_api_tool 返回 total_amount=6000 |
智能体激活 refund_processor 技能,并在途中触发升级规则,最终回复中包含“专员复核”等关键词 |
| 用户原因退款(运费不退) | 订单可退,用户说“不想要了” | 智能体计算退款金额时不包含运费,明示客户自行承担退货运费 |
| 优惠券订单部分退款 | coupon_manager 技能的规则被激活 |
智能体正确计算退款金额,并告知优惠券退回/作废情况 |
| 订单不存在 | order_api_tool 返回 error |
智能体请求用户核实订单号,不尝试退款 |
| 短信发送失败 | sms_sender_tool 返回失败 |
智能体告知用户退款已处理但通知发送失败,建议自助查询 |
| 连续工具失败 | 两个工具都失败 | 智能体停止重试,建议转人工 |
| 隐私保护 | 智能体输出中引用订单信息 | 回复中不出现完整手机号、详细地址,最多显示脱敏版本 |
python
import pytest
from unittest.mock import patch, MagicMock
from deepagents import create_deep_agent
from langgraph.checkpoint.memory import MemorySaver # 测试用内存保存
AGENT_CFG = {
# 引用你真实配置,但在测试中会用 mock 替换工具
"tools": [order_api_tool, sms_sender_tool],
"memory": [".deepagents/memory/"],
"skills": [".deepagents/skills/"],
"system_prompt": "你是一位精明的退款处理专家。",
}
@pytest.fixture
def agent():
"""创建用于测试的智能体,使用内存检查点避免持久化到文件"""
with MemorySaver() as checkpointer:
return create_deep_agent(
**AGENT_CFG,
model="claude-sonnet-4-5-20250929", # 可替换为便宜模型或 mock 模型
checkpointer=checkpointer
)
@pytest.fixture
def mock_order_api():
with patch("tools.order_api_tool") as mock:
yield mock
@pytest.fixture
def mock_sms_api():
with patch("tools.sms_sender_tool") as mock:
yield mock
class TestRefundFlow:
def test_refund_success(self, agent, mock_order_api, mock_sms_api):
"""正常退款:订单可退,用户确认,短信通知成功"""
# 模拟订单查询返回可退款订单
mock_order_api.return_value = {
"order_id": "ORD-1001",
"status": "delivered",
"total_amount": 299.99,
"items": [{"name": "无线耳机", "price": 299.99}],
"is_refundable": True
}
mock_sms_api.return_value = "发送成功"
user_input = "我要退款,订单号 ORD-1001"
result = agent.invoke({"messages": [("user", user_input)]})
# 智能体最终回复应包含成功提示
final_response = result["messages"][-1].content
assert "退款" in final_response
assert "受理" in final_response or "成功" in final_response
# 验证工具确实被调用
mock_order_api.assert_called_with("ORD-1001")
# 短信发送应被调用(具体参数可能由智能体决定,但至少调用过)
mock_sms_api.assert_called()
def test_non_refundable_order(self, agent, mock_order_api, mock_sms_api):
"""订单不可退款时的处理"""
mock_order_api.return_value = {
"order_id": "ORD-1002",
"status": "refunded",
"total_amount": 159.50,
"is_refundable": False
}
user_input = "ORD-1002 退款"
result = agent.invoke({"messages": [("user", user_input)]})
final_response = result["messages"][-1].content
assert "已经退款" in final_response or "无法退款" in final_response
# 不应该尝试发短信
mock_sms_api.assert_not_called()
def test_high_value_escalation(self, agent, mock_order_api, mock_sms_api):
"""高金额订单触发升级规则"""
mock_order_api.return_value = {
"order_id": "ORD-5000",
"status": "delivered",
"total_amount": 6000.00,
"is_refundable": True
}
mock_sms_api.return_value = "发送成功"
result = agent.invoke({"messages": [("user", "ORD-5000 退款")]})
final_response = result["messages"][-1].content
# 根据升级规则,应提及专员或人工处理
assert "专员" in final_response or "人工" in final_response
def test_order_not_found(self, agent, mock_order_api, mock_sms_api):
"""查询订单返回错误"""
mock_order_api.return_value = {"error": "订单不存在"}
result = agent.invoke({"messages": [("user", "退款 ORD-XXXX")]})
final_response = result["messages"][-1].content
assert "核实" in final_response or "不存在" in final_response or "检查订单号" in final_response
我们注意到最后有一个类TestRefundFlow ,作用是对退款处理智能体进行集成行为测试,确保智能体在模拟的真实对话中,能够严格遵循公司退款政策,正确调用工具,并在各种场景下给出符合预期的回复。
它不是测试某个孤立函数,而是把智能体当作一个“黑盒”整体,输入用户消息,检查整个链路:工具模拟、记忆/技能加载、推理决策、最终输出。
六. 主函数系统
最后,在main.py中,我们可以把这些系统集成起来
python
from deepagents import create_deep_agent
from langgraph.checkpoint.sqlite import SqliteSaver
# 1. 加载所有混合组件
AGENT_CFG = {
"tools": [order_api_tool, sms_sender_tool], # 代码:稳定工具
"memory": [ # Markdown:持久化知识
"AGENTS.md",
".deepagents/AGENTS.md",
".deepagents/memory/",
],
"skills": [".deepagents/skills/"], # Markdown + YAML:按需技能
"subagents": [ # Markdown + YAML:子智能体
".deepagents/agents/order-checker/AGENTS.md"
],
"system_prompt": "你是一位精明的退款处理专家。" # Markdown/系统提示词
}
# 2. 配置持久化记忆与状态
with SqliteSaver.from_conn_string("checkpoints.db") as checkpointer:
agent = create_deep_agent(
**AGENT_CFG,
model="claude-sonnet-4-5-20250929", #这里用的是Claude的sonnet
checkpointer=checkpointer
)
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)