搭一个简单的智能体

比如说我们想要搭一个与用户能够交流处理退款的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_processorcoupon_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_toolsms_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]

关键点说明

  1. @tool 装饰器
    LangChain 会自动将函数的名称、文档字符串(docstring)和参数类型转换为工具的名称、描述和输入 schema。大型语言模型正是通过读取这些信息来决定何时及如何调用工具。

  2. 输入参数

    • order_api_tool 接收 order_id: str,返回结构化字典。

    • sms_sender_tool 接收 phone_number: strmessage: str,返回状态字符串。
      智能体(LLM)会根据用户对话内容自动提取这些参数并调用工具。

  3. 错误处理
    工具内部返回包含 error 信息的字典或失败描述,而不是直接抛出异常。这样智能体可以“看到”错误信息并尝试修正(例如向用户询问正确的订单号),流程不会因异常中断。

  4. 如何集成到原有的智能体配置
    直接将这两把工具放入 AGENT_CFGtools 列表即可:

    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` 字段非空,版本号格式正确。
- 报告任何解析失败的文件名。

## 诊断输出格式
诊断结束后,生成如下结构化报告:

关键点说明

    1. 输出应该是这种形式:
    系统诊断报告 - {时间戳}  
    ================================  
    [✓] 订单 API:正常 (响应时间: 120ms)  
    [✗] 短信网关:异常 - 连接超时 (错误: timeout after 5s)  
    [✓] 知识库文件:3/3 文件完整  
    [✗] 子智能体 'order-checker':无法连接 (错误: 'agent not found')  
    [✓] 检查点存储:读写正常  
    [!] 技能文件:1 个警告 (coupon_manager SKILL.md 缺少 version 字段
    
  • 2.虽然 order_api_toolsms_sender_tool代码是我们自己编写的,但它们本质上是对外部远程服务的封装:

  • order_api_tool 内部通常会调用一个运行在远端服务器上的订单微服务 REST/gRPC 接口;

  • sms_sender_tool 则依赖第三方短信网关(如 Twilio、阿里云短信)或公司内部的短信中台。

所以 “外部 API 连通性测试” 测的并不是我们写的工具函数本身(那些在单元测试 test_order_tools.py 中已经覆盖),而是工具背后真实依赖的远端服务是否可达、响应是否正常。工具代码只是桥梁,在系统诊断中,必须越过桥梁去确认对岸的“外部”服务是健康的。因此把它们归为外部 API 连通性测试是准确且必要的。

五.测试系统

1. test_order_tools.py —— 工具单元测试

目的
验证 order_api_toolsms_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.mdSKILL.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
    )

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐