引言

我们最近发布了新的开源 Agents SDK,旨在帮助您使用轻量级、易于使用的包和最少的抽象来构建代理式人工智能应用程序。

本指南演示了如何将 Agents SDK 与 Stripe 的 API 结合使用来处理争议管理,这是许多企业面临的常见运营挑战。具体来说,我们关注两个实际场景:

  1. 公司失误: 公司明显犯了错误的情况,例如未能履行订单,此时接受争议是适当的操作。

  2. 客户争议(最终销售): 客户明知故犯地对交易提出争议,尽管他们收到了正确的商品并理解购买是最终销售,需要进一步调查以收集支持证据。

为了解决这些场景,我们将引入三个不同的代理:

  • 分类代理: 根据订单的履行状态确定是接受还是升级争议。

  • 接受代理: 通过自动接受争议并提供简洁的理由来处理明确的案例。

  • 调查代理: 通过分析通信记录和订单信息来收集关键证据,对争议进行彻底调查。

在本指南中,我们将逐步指导您,演示自定义代理工作流如何自动化争议管理并支持您的业务运营。

先决条件

在运行此指南之前,您必须设置好以下帐户并完成一些设置操作。这些先决条件对于与本项目中使用的 API 进行交互至关重要。

1. OpenAI 帐户

  • 目的: 您需要一个 OpenAI 帐户才能访问语言模型并使用本指南中介绍的 Agents SDK。

  • 操作: 如果您还没有 OpenAI 帐户,请注册一个 OpenAI 帐户。拥有帐户后,请访问OpenAI API 密钥页面创建 API 密钥。

2. Stripe 帐户

  • 目的: 需要 Stripe 帐户来模拟支付处理、管理争议以及在我们演示工作流中与 Stripe API 进行交互。

  • 操作: 通过访问Stripe 注册页面创建一个免费的 Stripe 帐户。

  • 查找您的 API 密钥: 登录您的 Stripe 信息中心,然后导航到“开发者”>“API 密钥”。

  • 使用测试模式: 在所有开发和测试中使用您的测试密钥

3. 创建一个包含您的 OpenAI API 和 Stripe API 密钥的 .env 文件

OPENAI_API_KEY=
STRIPE_SECRET_KEY=

环境设置

首先,我们将安装必要的依赖项,然后导入库并编写一些稍后将使用的实用函数。

%pip install python-dotenv --quiet
%pip install openai-agents --quiet
%pip install stripe --quiet
%pip install typing_extensions --quiet
import os
import logging
import json
from dotenv import load_dotenv
from agents import Agent, Runner, function_tool  # 仅导入您需要的
import stripe
from typing_extensions import TypedDict, Any
# 从 .env 文件加载环境变量
load_dotenv()

# 配置日志记录
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# 从环境变量设置 Stripe API 密钥
stripe.api_key = os.getenv("STRIPE_SECRET_KEY")

定义函数工具

本节定义了几个支持争议处理工作流的辅助函数工具。

  • get_orderget_phone_logsget_emails 通过根据提供的标识符返回订单详细信息和电子邮件/电话记录来模拟外部数据查找。
  • retrieve_payment_intent 与 Stripe API 交互以获取支付意图详细信息。
  • close_dispute 使用提供的争议 ID 自动关闭 Stripe 争议,确保争议得到正确解决和记录。
@function_tool
def get_phone_logs(phone_number: str) -> list:
    """
    返回给定电话号码的电话通话记录列表。
    每条记录可能包括通话时间戳、时长、备注以及关联的 order_id(如果适用)。
    """
    phone_logs = [
        {
            "phone_number": "+15551234567",
            "timestamp": "2023-03-14 15:24:00",
            "duration_minutes": 5,
            "notes": "Asked about status of order #1121",
            "order_id": 1121
        },
        {
            "phone_number": "+15551234567",
            "timestamp": "2023-02-28 10:10:00",
            "duration_minutes": 7,
            "notes": "Requested refund for order #1121, I told him we were unable to refund the order because it was final sale",
            "order_id": 1121
        },
        {
            "phone_number": "+15559876543",
            "timestamp": "2023-01-05 09:00:00",
            "duration_minutes": 2,
            "notes": "General inquiry; no specific order mentioned",
            "order_id": None
        },
    ]
    return [
        log for log in phone_logs if log["phone_number"] == phone_number
    ]


@function_tool
def get_order(order_id: int) -> str:
    """
    通过 ID 从预定义的订单列表中检索订单。
    返回相应的订单对象或“未找到订单”。
    """
    orders = [
        {
            "order_id": 1234,
            "fulfillment_details": "not_shipped"
        },
        {
            "order_id": 9101,
            "fulfillment_details": "shipped",
            "tracking_info": {
                "carrier": "FedEx",
                "tracking_number": "123456789012"
            },
            "delivery_status": "out for delivery"
        },
        {
            "order_id": 1121,
            "fulfillment_details": "delivered",
            "customer_id": "cus_PZ1234567890",
            "customer_phone": "+15551234567",
            "order_date": "2023-01-01",
            "customer_email": "customer1@example.com",
            "tracking_info": {
                "carrier": "UPS",
                "tracking_number": "1Z999AA10123456784",
                "delivery_status": "delivered"
            },
            "shipping_address": {
                "zip": "10001"
            },
            "tos_acceptance": {
                "date": "2023-01-01",
                "ip": "192.168.1.1"
            }
        }
    ]
    for order in orders:
        if order["order_id"] == order_id:
            return order
    return "No order found"


@function_tool
def get_emails(email: str) -> list:
    """
    返回给定电子邮件地址的电子邮件记录列表。
    """
    emails = [
        {
            "email": "customer1@example.com",
            "subject": "Order #1121",
            "body": "Hey, I know you don't accept refunds but the sneakers don't fit and I'd like a refund"
        },
        {
            "email": "customer2@example.com",
            "subject": "Inquiry about product availability",
            "body": "Hello, I wanted to check if the new model of the smartphone is available in stock."
        },
        {
            "email": "customer3@example.com",
            "subject": "Feedback on recent purchase",
            "body": "Hi, I recently purchased a laptop from your store and I am very satisfied with the product. Keep up the good work!"
        }
    ]
    return [email_data for email_data in emails if email_data["email"] == email]


@function_tool
async def retrieve_payment_intent(payment_intent_id: str) -> dict:
    """
    按 ID 检索 Stripe 支付意图。
    成功时返回支付意图对象,失败时返回空字典。
    """
    try:
        return stripe.PaymentIntent.retrieve(payment_intent_id)
    except stripe.error.StripeError as e:
        logger.error(f"检索支付意图时发生 Stripe 错误:{e}")
        return {}

@function_tool
async def close_dispute(dispute_id: str) -> dict:
    """
    按 ID 关闭 Stripe 争议。
    成功时返回争议对象,失败时返回空字典。
    """
    try:
        return stripe.Dispute.close(dispute_id)
    except stripe.error.StripeError as e:
        logger.error(f"关闭争议时发生 Stripe 错误:{e}")
        return {}

定义代理

  • 争议接收代理(investigator_agent)负责通过收集所有相关证据并提供报告来调查争议。
  • 接受争议代理(accept_dispute_agent)通过自动关闭争议并提供简要解释来处理被确定为有效的争议。
  • 分类代理(triage_agent)充当决策者,从支付意图的元数据中提取订单 ID,检索详细的订单信息,然后决定是升级争议给调查员还是将其传递给接受争议代理。
  • 总之,这些代理构成了一个模块化的工作流,通过将特定任务委派给专门的代理来自动化和简化争议解决过程。
investigator_agent = Agent(
    name="Dispute Intake Agent",
    instructions=(
        "作为争议调查员,请在您的最终输出中汇总以下详细信息:\n\n"
        "争议详情:\n"
        "- 争议 ID\n"
        "- 金额\n"
        "- 争议原因\n"
        "- 卡品牌\n\n"
        "支付和订单详情:\n"
        "- 订单的履行状态\n"
        "- 承运商和追踪号\n"
        "- TOS 接受确认\n\n"
        "电子邮件和电话记录:\n"
        "- 任何相关的电子邮件线索(包括完整的正文文本)\n"
        "- 任何相关的电话记录\n"
    ),
    model="o3-mini",
    tools=[get_emails, get_phone_logs]
)


accept_dispute_agent = Agent(
    name="Accept Dispute Agent",
    instructions=(
        "您是负责接受争议的代理。请执行以下操作:\n"
        "1. 使用提供的争议 ID 关闭争议。\n"
        "2. 提供接受争议的原因的简短解释。\n"
        "3. 引用从数据库检索到的任何相关订单详细信息(例如,未发货的订单等)。\n\n"
        "然后,请按此确切格式生成您的最终输出:\n\n"
        "争议详情:\n"
        "- 争议 ID\n"
        "- 金额\n"
        "- 争议原因\n\n"
        "订单详情:\n"
        "- 订单的履行状态\n\n"
        "关闭争议的原因\n"
    ),
    model="gpt-4o",
    tools=[close_dispute]
)

triage_agent = Agent(
    name="Triage Agent",
    instructions=(
        "请执行以下操作:\n"
        "1. 从支付意图的元数据中查找订单 ID。\n"
        "2. 检索有关订单的详细信息(例如,发货状态)。\n"
        "3. 如果订单已发货,请将此争议升级给调查代理。\n"
        "4. 如果订单未发货,请接受争议。\n"
    ),
    model="gpt-4o",
    tools=[retrieve_payment_intent, get_order],
    handoffs=[accept_dispute_agent, investigator_agent],
)

检索争议并启动代理工作流

此函数使用提供的 payment_intent_id 从 Stripe 检索争议详细信息,并通过将检索到的争议信息传递给指定的 triage_agent 来启动争议处理工作流。

async def process_dispute(payment_intent_id, triage_agent):
    """检索给定 PaymentIntent 的争议并处理数据。"""
    disputes_list = stripe.Dispute.list(payment_intent=payment_intent_id)
    if not disputes_list.data:
        logger.warning("未找到 PaymentIntent 的争议数据:%s", payment_intent_id)
        return None

    dispute_data = disputes_list.data[0]

    relevant_data = {
        "dispute_id": dispute_data.get("id"),
        "amount": dispute_data.get("amount"),
        "due_by": dispute_data.get("evidence_details", {}).get("due_by"),
        "payment_intent": dispute_data.get("payment_intent"),
        "reason": dispute_data.get("reason"),
        "status": dispute_data.get("status"),
        "card_brand": dispute_data.get("payment_method_details", {}).get("card", {}).get("brand")
    }

    event_str = json.dumps(relevant_data)
    # 将争议数据传递给分类代理
    result = await Runner.run(triage_agent, input=event_str)
    logger.info("工作流结果:%s", result.final_output)

    return relevant_data, result.final_output

场景 1:公司失误(未收到产品)

此场景代表公司明显犯了错误的情况,例如未能履行或发货订单。在这种情况下,接受争议可能比对其提出异议更合适。

payment = stripe.PaymentIntent.create(
  amount=2000,
  currency="usd",
  payment_method = "pm_card_createDisputeProductNotReceived",
  confirm=True,
  metadata={"order_id": "1234"},
  off_session=True,
  automatic_payment_methods={"enabled": True},
)
relevant_data, triage_result = await process_dispute(payment.id, triage_agent)

场景 2:客户争议(最终销售)

此场景描述了客户故意对交易提出争议的情况,尽管他们收到了正确的产品并且完全知道购买已明确标记为“最终销售”(无退款或退货)。此类争议通常需要进一步调查以收集证据,以便有效地对争议提出异议。

payment = stripe.PaymentIntent.create(
  amount=2000,
  currency="usd",
  payment_method = "pm_card_createDispute",
  confirm=True,
  metadata={"order_id": "1121"},
  off_session=True,
  automatic_payment_methods={"enabled": True},
)
relevant_data, triage_result = await process_dispute(payment.id, triage_agent)

结论

在本 Jupyter Notebook 中,我们探讨了 OpenAI Agents SDK 的功能,演示了如何使用简单、以 Python 为中心的方法来高效地创建基于代理的 AI 应用程序。具体来说,我们展示了以下 SDK 功能:

  • 代理循环:管理工具调用,将结果传达给 LLM,并循环直到完成。
  • 交接:实现多个专用代理之间的协调和任务委派。
  • 函数工具:将 Python 函数转换为具有自动模式生成和验证的工具。

此外,该 SDK 还提供内置的跟踪功能,可通过 OpenAI 信息中心访问。跟踪功能可帮助您在开发和生产阶段可视化、调试和监控您的代理工作流。它还与 OpenAI 的评估、微调和蒸馏工具无缝集成。

虽然在本 Notebook 中没有直接介绍,但强烈建议在生产应用程序中实施保护栏,以验证输入并主动检测错误。

总而言之,本 Notebook 为进一步探索奠定了清晰的基础,强调了 OpenAI Agents SDK 如何促进直观有效的代理驱动工作流。