优化客服智能体的成本与质量(上)
说明如何给电商客服智能体建立可测量的降本循环:先固定评测集和质量门,再收紧提示、工具与输出,并把常规工单路由到更小模型。

目录
用例与智能体设置
我们虚构的电商助手处理订单状态、损坏配送、退款资格、重复扣款和账户访问。常规查询值得评估更小的模型;对政策敏感的案例则用来检验优化后的工作流是否仍能正确升级。
五个模拟工具分别代表订单系统(lookup_order)、客户记录(lookup_customer)、政策来源(lookup_policy)、退款或换货工单(create_refund_case)以及人工支持(escalate_to_human)。它们是本地 Python 函数,因此即使是实时模型示例也不会更改真实客户账户。
基线对每一步都暴露全部工具、返回过大的载荷,并使用完整模型。它还会在回复之前做内部质量保证(QA)、分析打标和路由审计。后续各轮保持业务任务不变,同时减少不必要的工作,并把后续处理移出面向客户的路径。
环境准备
使用 Python 3.10 或更高版本。克隆 Cookbook 仓库,或下载整个示例文件夹,然后以 examples/agent_optimization 为工作目录启动笔记本,以便本地导入能够解析。
在笔记本环境中安装依赖:
pip install --upgrade openai pandas matplotlib jinja2 ipykernel
同一批依赖也列在 requirements.txt 中。jinja2 用于带样式的 pandas 表格。配套文件包含模拟数据、工具和提示、模拟与检查、实时 API 辅助函数、离线答案评测和场景评分。
笔记本默认不调用 API。若要选择加入实时智能体示例,请在启动内核之前设置这些变量:
export OPENAI_API_KEY=...
export RUN_LIVE_API_CALLS=true
可选的答案评判器有自己的开关 RUN_LLM_JUDGE=true,并且同样需要 OPENAI_API_KEY。它用 50 次付费评判请求为 50 条已有模拟轨迹(10 张工单 × 5 个变体)打分;它不运行实时智能体。两个开关都不设置时,可以完全离线运行。
从上到下运行单元格。Batch 示例会在 outputs/ 下写一个本地文件;其提交代码只展示供检查,并不执行。
import json
import math
import os
os.environ.setdefault("MPLCONFIGDIR", "/tmp/matplotlib")
import matplotlib.pyplot as plt
import pandas as pd
from IPython.display import display
from openai import OpenAI
RUN_LIVE_API_CALLS = os.environ.get("RUN_LIVE_API_CALLS", "false").lower() == "true"
RUN_LLM_JUDGE = os.environ.get("RUN_LLM_JUDGE", "false").lower() == "true"
if (RUN_LIVE_API_CALLS or RUN_LLM_JUDGE) and not os.environ.get("OPENAI_API_KEY"):
raise RuntimeError("Set OPENAI_API_KEY before enabling the live agent or judge.")
client = OpenAI() if RUN_LIVE_API_CALLS else None
judge_client = OpenAI() if RUN_LLM_JUDGE else None
pd.set_option("display.max_columns", 40)
pd.set_option("display.width", 140)
print("RUN_LIVE_API_CALLS =", RUN_LIVE_API_CALLS)
print("RUN_LLM_JUDGE =", RUN_LLM_JUDGE)
RUN_LIVE_API_CALLS = True
RUN_LLM_JUDGE = True
成功标准与约束
只有当智能体仍然使用正确事实、遵守政策、采取必要行动并正确升级时,才接受节省。简洁回复必须给客户下一步,且不暴露内部数据。在这些质量检查通过之后,再比较 p50/p95 延迟和总成本。
评测集故意做得很小。在生产环境中,使用分层样本覆盖主要意图、风险级别、语言、地区、客户层级和边界情况。保留一个留出集,并在比较节省之前,用质量把每一轮优化挡在门外。
模拟契约
默认路径使用模拟数据和建模指标。它演示测量循环;其中的数字不是生产基准。
测试工具(harness)测量序列化文本长度,并对照夹具比较工具、动作、升级和回复短语检查。Token 数量由这些长度估算。推理 Token、延迟、缓存命中和汇总质量分遵循说明性公式;成本把已核验的价格表应用到估算用量上。
路由和优化后的动作来自夹具标签,因此这次模拟并不测量模型选择它们的能力。回复检查使用不区分大小写的字面短语,这可能拒绝有效的改写,也不能确立事实正确性。做部署决策时,用真实用量、计时、工具结果、路由决策,以及校准过的评判器或人工评测替换这些轨迹。
优化旋钮
| 旋钮 | 低效基线 | 优化模式 | 主要指标 |
|---|---|---|---|
| 提示与输出 | 宽泛的“要全面”指令和长答案 | 具体任务规则、简洁回复契约、text.verbosity="low"、封顶输出 | 输出 Token、简洁度、质量 |
| 推理力度 | 每张工单都用高推理 | 常规工作用低力度,只对高风险决策提高 | 推理 Token、延迟 |
| 工具表面 | 每个请求都暴露全部工具 | 稳定的完整工具列表,再加上按任务设置的 tool_choice.allowed_tools | 工具调用、可缓存性 |
| 工具模式 | 冗长描述和宽泛的载荷预期 | 只含决策关键参数的小模式 | 输入 Token |
| 工具载荷 | 原始客户关系管理(CRM)、承运商、审计和附录大块 | 下一步决策所需的精简字段 | 工具输出 Token |
| 模型路由 | 所有步骤用一个大模型 | 分诊/标签用 nano,常规解决用 mini,高风险案例用完整模型 | 成本、延迟、升级准确率 |
| 提示缓存 | 易变的工单数据混进前缀 | 稳定的指令、工具、政策框架和模式在前;工单数据在后 | 缓存的输入 Token、成本 |
| 工作流拆分 | 质量保证、分析、摘要和审计都在客户路径上 | 客户解决走同步;质量保证/标签/报告通过后台、flex 或 Batch 异步进行 | p50 延迟、同步成本 |
| 护栏与评测 | 非正式抽查 | 确定性检查,再加上实时轨迹的评判器模式 | 回归率、安全通过率 |
样本评测集
这个小样本评测集为笔记本提供具体工单、期望工具、期望动作、升级标签和禁止声称,以便为每一轮优化打分。
from support import EVAL_SET
pd.DataFrame(EVAL_SET)[
[
"ticket_id",
"intent",
"risk",
"difficulty",
"expected_tools",
"expected_action",
"must_escalate",
]
]
| ticket\_id | intent | risk | difficulty | expected\_tools | expected\_action | must\_escalate | |
|---|---|---|---|---|---|---|---|
| 0 | T-001 | order\_status | low | simple\_lookup | \[lookup\_order\] | provide\_status\_eta | False |
| 1 | T-002 | damaged\_delivery | medium | routine\_policy | \[lookup\_order, lookup\_policy\] | request\_photo\_then\_offer\_replacement | False |
| 2 | T-003 | refund\_eligibility | medium | routine\_policy | \[lookup\_order, lookup\_policy, create\_refund\_case\] | open\_refund\_case | False |
| 3 | T-004 | billing\_issue | medium | sensitive\_policy | \[lookup\_order, lookup\_policy, escalate\_to\_human\] | escalate\_billing\_review | True |
| 4 | T-005 | account\_access | high | account\_security | \[lookup\_customer, lookup\_policy, escalate\_to\_h... | escalate\_account\_security | True |
| 5 | T-006 | refund\_dispute | high | outside\_policy\_window | \[lookup\_order, lookup\_policy, escalate\_to\_human\] | escalate\_refund\_review | True |
| 6 | T-007 | delivered\_not\_received | medium | routine\_policy | \[lookup\_order, lookup\_policy\] | start\_delivery\_trace\_steps | False |
| 7 | T-008 | high\_value\_damage | high | high\_value\_policy | \[lookup\_order, lookup\_policy, escalate\_to\_human\] | escalate\_high\_value\_damage | True |
| 8 | T-009 | refund\_eligibility | low | routine\_policy | \[lookup\_order, lookup\_policy, create\_refund\_case\] | open\_refund\_case | False |
| 9 | T-010 | account\_access | high | account\_security | \[lookup\_customer, lookup\_policy, escalate\_to\_h... | escalate\_account\_security | True |
支持数据与工具
support.py 中的五个本地工具函数代表内部系统。基线返回过大的载荷,用来展示工具输出如何主导输入 Token;后续各轮只返回决策和回复所需的字段。
下面的单元格展示一条精简订单记录。模拟动作工具返回合成结果,不会创建真实工单或升级。
from support import lookup_order
# Inspect the decision-critical fields returned by the slim payload.
print(json.dumps(lookup_order("O-1001", payload="slim"), indent=2))
{
"found": true,
"order_id": "O-1001",
"status": "in_transit",
"carrier": "UPS",
"eta": "tomorrow",
"delivered_days_ago": null,
"payment_status": "paid_once",
"item_value": 18.0,
"events": [
"regional_delay"
]
}
from support import SLIM_TOOLS, VERBOSE_TOOLS, allowed_tool_choice
print("Verbose tool schema tokens:", math.ceil(len(json.dumps(VERBOSE_TOOLS)) / 4))
print("Slim tool schema tokens:", math.ceil(len(json.dumps(SLIM_TOOLS)) / 4))
Verbose tool schema tokens: 550
Slim tool schema tokens: 397
基线架构
糟糕的基线在一条同步路径上做得太多。
flowchart LR
A["客户消息"] --> B["最强模型上的一个通用智能体"]
B --> C["客户查询"]
B --> D["订单查询"]
B --> E["政策查询"]
B --> F["退款或升级工具"]
B --> G["客户回复"]
B --> H["质量保证摘要"]
B --> I["分析打标"]
B --> J["路由审计"]宽泛指令、高推理力度和不受限的工具使每个请求都很贵。大模式和冗长载荷膨胀输入,而长答案和同步质量保证则在客户收到回复之前增加工作。
from support import CONTROLLED_PROMPT
print(CONTROLLED_PROMPT)
Role: E-commerce support assistant.
Goal: Resolve routine support tickets with the fewest necessary tool calls while preserving policy correctness.
Tool rules:
- Use only tools required for the current decision.
- Order status: order lookup only.
- Damaged delivery or refund: order lookup plus the relevant policy.
- Billing duplicate charge: order lookup plus billing policy, then escalate.
- Account access with unverified identity: customer lookup plus account policy, then escalate.
Response rules:
- Give the customer the outcome and next step.
- Do not expose internal reasoning, raw tool data, audit notes, or policy text.
- Keep the customer-facing answer under 120 words unless escalation legally requires more detail.
指标辅助函数
实时辅助函数读取 input_tokens、output_tokens、total_tokens、input_tokens_details.cached_tokens 和 output_tokens_details.reasoning_tokens。输出 Token 用量已经包含推理 Token;计算成本时不要再加一次。
下表是标准费率下每百万文本 Token 的美元价格,于 2026 年 9 月 14 日对照 GPT-5.4、mini 和 nano 页面核验。它覆盖这里使用的短 GPT-5.4 请求。估算器不覆盖长上下文溢价、优先定价或 GPT-5.6 的缓存写入费用;更改这些设置之前请更新估算器。当前费率见定价页。
from simulation import MODEL_PRICES_USD_PER_1M
pd.DataFrame(MODEL_PRICES_USD_PER_1M).T.rename_axis("model")
| input | cached\_input | output | |
|---|---|---|---|
| model | |||
| --- | --- | --- | --- |
| gpt-5.4 | 2.50 | 0.250 | 15.00 |
| gpt-5.4-mini | 0.75 | 0.075 | 4.50 |
| gpt-5.4-nano | 0.20 | 0.020 | 1.25 |
空跑模拟
模拟辅助函数 把每个变体应用到同一批工单上,根据提示和载荷估算用量,并记录客户回复及其质量检查。缺少必要短语和出现禁止声称会降低质量,并使演示政策检查失败,即使动作和升级标签匹配也是如此。
优化变体假设应用路由器正确,并且期望动作已知。缓存使用简化的热缓存假设和 1,024 Token 的资格阈值,并不是对实际缓存行为的测量。重复的操作手册把演示做得足够大,以便走这条分支;生产提示应包含有用的共享上下文,缓存资格取决于请求设置。
最后一个变体把后台工作移出同步延迟,同时仍然计算其 Token 和 Batch 成本。在依赖平均值之前,先检查单条轨迹。
from simulation import CACHE_FRIENDLY_PROMPT, VARIANT_ORDER, simulate_trace
traces = pd.DataFrame(
simulate_trace(ticket, variant)
for variant in VARIANT_ORDER
for ticket in EVAL_SET
)
traces.drop(columns="tool_results").head()
| variant | variant\_label | ticket\_id | intent | risk | difficulty | model | routing\_tokens | tool\_calls | expected\_tools | tools | action | expected\_action | input\_tokens | latency\_input\_tokens | cacheable\_prefix\_tokens | cached\_tokens | output\_tokens | visible\_output\_tokens | reasoning\_tokens | ... | total\_tokens | latency\_s | sync\_cost\_usd | background\_tokens | background\_cost\_usd | cost\_usd | escalated | customer\_response | missing\_required\_tools | extra\_tool\_calls | unnecessary\_tools | escalation\_correct | action\_correct | policy\_compliant | concise | response\_complete | missing\_required\_phrases | forbidden\_claims\_absent | forbidden\_claims\_found | quality\_score | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 00\_bad\_baseline | Bad baseline | T-001 | order\_status | low | simple\_lookup | gpt-5.4 | 0 | 5 | lookup\_order | lookup\_customer, lookup\_order, lookup\_policy, ... | provide\_status\_eta | provide\_status\_eta | 12301 | 12301 | 0 | 0 | 612 | 167 | 445 | ... | 12913 | 4.88 | 0.039933 | 0 | 0.0 | 0.039933 | False | I reviewed your message for ticket T-001 and c... | 4 | create\_refund\_case, escalate\_to\_human, lookup\_... | True | True | False | False | False | in transit, tomorrow | True | 0.55 | ||
| 1 | 00\_bad\_baseline | Bad baseline | T-002 | damaged\_delivery | medium | routine\_policy | gpt-5.4 | 0 | 5 | lookup\_order, lookup\_policy | lookup\_customer, lookup\_order, lookup\_policy, ... | open\_replacement\_without\_photo | request\_photo\_then\_offer\_replacement | 12273 | 12273 | 0 | 0 | 644 | 174 | 470 | ... | 12917 | 4.88 | 0.040343 | 0 | 0.0 | 0.040343 | False | I reviewed your message for ticket T-002 and c... | 3 | create\_refund\_case, escalate\_to\_human, lookup\_... | True | False | False | False | True | True | 0.65 | |||
| 2 | 00\_bad\_baseline | Bad baseline | T-003 | refund\_eligibility | medium | routine\_policy | gpt-5.4 | 0 | 5 | lookup\_order, lookup\_policy, create\_refund\_case | lookup\_customer, lookup\_order, lookup\_policy, ... | escalate\_refund\_review | open\_refund\_case | 12178 | 12178 | 0 | 0 | 640 | 170 | 470 | ... | 12818 | 4.87 | 0.040045 | 0 | 0.0 | 0.040045 | True | I reviewed your message for ticket T-003 and c... | 2 | escalate\_to\_human, lookup\_customer | False | False | False | False | False | refund case, within 30 days | True | 0.22 | ||
| 3 | 00\_bad\_baseline | Bad baseline | T-004 | billing\_issue | medium | sensitive\_policy | gpt-5.4 | 0 | 5 | lookup\_order, lookup\_policy, escalate\_to\_human | lookup\_customer, lookup\_order, lookup\_policy, ... | escalate\_billing\_review | escalate\_billing\_review | 12166 | 12166 | 0 | 0 | 664 | 169 | 495 | ... | 12830 | 4.87 | 0.040375 | 0 | 0.0 | 0.040375 | True | I reviewed your message for ticket T-004 and c... | 2 | create\_refund\_case, lookup\_customer | True | True | True | False | True | True | 0.85 | |||
| 4 | 00\_bad\_baseline | Bad baseline | T-005 | account\_access | high | account\_security | gpt-5.4 | 0 | 5 | lookup\_customer, lookup\_policy, escalate\_to\_human | lookup\_customer, lookup\_order, lookup\_policy, ... | escalate\_account\_security | escalate\_account\_security | 7415 | 7415 | 0 | 0 | 697 | 172 | 525 | ... | 8112 | 3.97 | 0.028993 | 0 | 0.0 | 0.028993 | True | I reviewed your message for ticket T-005 and c... | 2 | create\_refund\_case, lookup\_order | True | True | False | False | False | account security, verification | True | 0.60 |
5 rows × 41 columns
summary = (
traces.groupby(["variant", "variant_label"], sort=False)
.agg(
tickets=("ticket_id", "count"),
mean_quality=("quality_score", "mean"),
policy_compliance=("policy_compliant", "mean"),
action_accuracy=("action_correct", "mean"),
escalation_accuracy=("escalation_correct", "mean"),
concise_rate=("concise", "mean"),
mean_tool_calls=("tool_calls", "mean"),
mean_extra_tool_calls=("extra_tool_calls", "mean"),
mean_input_tokens=("input_tokens", "mean"),
mean_cached_tokens=("cached_tokens", "mean"),
mean_output_tokens=("output_tokens", "mean"),
mean_reasoning_tokens=("reasoning_tokens", "mean"),
mean_sync_tokens=("sync_tokens", "mean"),
mean_total_tokens=("total_tokens", "mean"),
p50_latency_s=("latency_s", "median"),
p95_latency_s=("latency_s", lambda s: s.quantile(0.95)),
sync_cost_per_ticket_usd=("sync_cost_usd", "mean"),
background_cost_per_ticket_usd=("background_cost_usd", "mean"),
cost_per_ticket_usd=("cost_usd", "mean"),
)
.reset_index()
)
baseline_cost = summary.loc[summary["variant"] == "00_bad_baseline", "cost_per_ticket_usd"].iloc[0]
baseline_tokens = summary.loc[summary["variant"] == "00_bad_baseline", "mean_total_tokens"].iloc[0]
baseline_latency = summary.loc[summary["variant"] == "00_bad_baseline", "p50_latency_s"].iloc[0]
summary["cost_reduction_vs_baseline"] = 1 - summary["cost_per_ticket_usd"] / baseline_cost
summary["token_reduction_vs_baseline"] = 1 - summary["mean_total_tokens"] / baseline_tokens
summary["latency_reduction_vs_baseline"] = 1 - summary["p50_latency_s"] / baseline_latency
summary["monthly_cost_at_100k_tickets"] = summary["cost_per_ticket_usd"] * 100_000
summary_view = summary[
[
"variant_label",
"mean_quality",
"policy_compliance",
"action_accuracy",
"escalation_accuracy",
"mean_tool_calls",
"mean_extra_tool_calls",
"mean_sync_tokens",
"mean_total_tokens",
"mean_cached_tokens",
"p50_latency_s",
"cost_per_ticket_usd",
"cost_reduction_vs_baseline",
"monthly_cost_at_100k_tickets",
]
]
display(
summary_view.style.format(
{
"mean_quality": "{:.2f}",
"policy_compliance": "{:.0%}",
"action_accuracy": "{:.0%}",
"escalation_accuracy": "{:.0%}",
"mean_tool_calls": "{:.1f}",
"mean_extra_tool_calls": "{:.1f}",
"mean_sync_tokens": "{:,.0f}",
"mean_total_tokens": "{:,.0f}",
"mean_cached_tokens": "{:,.0f}",
"p50_latency_s": "{:.2f}",
"cost_per_ticket_usd": "${:.5f}",
"cost_reduction_vs_baseline": "{:.0%}",
"monthly_cost_at_100k_tickets": "${:,.0f}",
}
)
)
| variant\_label | mean\_quality | policy\_compliance | action\_accuracy | escalation\_accuracy | mean\_tool\_calls | mean\_extra\_tool\_calls | mean\_sync\_tokens | mean\_total\_tokens | mean\_cached\_tokens | p50\_latency\_s | cost\_per\_ticket\_usd | cost\_reduction\_vs\_baseline | monthly\_cost\_at\_100k\_tickets | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Bad baseline | 0.51 | 10% | 60% | 70% | 5.0 | 2.4 | 11,935 | 11,935 | 0 | 4.88 | $0.03813 | 0% | $3,813 |
| 1 | Round 1: controls | 0.98 | 100% | 100% | 100% | 2.6 | 0.0 | 1,379 | 1,379 | 0 | 2.32 | $0.00512 | 87% | $512 |
| 2 | Round 2: routing | 0.98 | 100% | 100% | 100% | 2.6 | 0.0 | 1,485 | 1,485 | 0 | 1.87 | $0.00302 | 92% | $302 |
| 3 | Round 3: caching | 0.98 | 100% | 100% | 100% | 2.6 | 0.0 | 2,684 | 2,684 | 1,779 | 1.85 | $0.00244 | 94% | $244 |
| 4 | Round 4: split workflow | 0.98 | 100% | 100% | 100% | 2.6 | 0.0 | 2,412 | 2,974 | 1,779 | 1.41 | $0.00204 | 95% | $204 |
逐轮影响
每一行把一轮与上一轮比较。这比单一的前后数字更容易理解各个优化旋钮。
round_impact = summary[
[
"variant_label",
"mean_quality",
"policy_compliance",
"mean_tool_calls",
"mean_extra_tool_calls",
"mean_sync_tokens",
"mean_total_tokens",
"mean_cached_tokens",
"p50_latency_s",
"cost_per_ticket_usd",
]
].copy()
for col in ["mean_sync_tokens", "mean_total_tokens", "p50_latency_s", "cost_per_ticket_usd"]:
round_impact[f"{col}_delta_vs_previous"] = round_impact[col].diff()
round_impact["quality_delta_vs_previous"] = round_impact["mean_quality"].diff()
display(
round_impact.style.format(
{
"mean_quality": "{:.2f}",
"policy_compliance": "{:.0%}",
"mean_tool_calls": "{:.1f}",
"mean_extra_tool_calls": "{:.1f}",
"mean_sync_tokens": "{:,.0f}",
"mean_total_tokens": "{:,.0f}",
"mean_cached_tokens": "{:,.0f}",
"p50_latency_s": "{:.2f}",
"cost_per_ticket_usd": "${:.5f}",
"mean_sync_tokens_delta_vs_previous": "{:+,.0f}",
"mean_total_tokens_delta_vs_previous": "{:+,.0f}",
"p50_latency_s_delta_vs_previous": "{:+.2f}",
"cost_per_ticket_usd_delta_vs_previous": "${:+.5f}",
"quality_delta_vs_previous": "{:+.2f}",
}
)
)
| variant\_label | mean\_quality | policy\_compliance | mean\_tool\_calls | mean\_extra\_tool\_calls | mean\_sync\_tokens | mean\_total\_tokens | mean\_cached\_tokens | p50\_latency\_s | cost\_per\_ticket\_usd | mean\_sync\_tokens\_delta\_vs\_previous | mean\_total\_tokens\_delta\_vs\_previous | p50\_latency\_s\_delta\_vs\_previous | cost\_per\_ticket\_usd\_delta\_vs\_previous | quality\_delta\_vs\_previous | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Bad baseline | 0.51 | 10% | 5.0 | 2.4 | 11,935 | 11,935 | 0 | 4.88 | $0.03813 | +nan | +nan | +nan | $+nan | +nan |
| 1 | Round 1: controls | 0.98 | 100% | 2.6 | 0.0 | 1,379 | 1,379 | 0 | 2.32 | $0.00512 | -10,556 | -10,556 | -2.56 | $-0.03301 | +0.48 |
| 2 | Round 2: routing | 0.98 | 100% | 2.6 | 0.0 | 1,485 | 1,485 | 0 | 1.87 | $0.00302 | +106 | +106 | -0.45 | $-0.00210 | +0.00 |
| 3 | Round 3: caching | 0.98 | 100% | 2.6 | 0.0 | 2,684 | 2,684 | 1,779 | 1.85 | $0.00244 | +1,199 | +1,199 | -0.02 | $-0.00058 | +0.00 |
| 4 | Round 4: split workflow | 0.98 | 100% | 2.6 | 0.0 | 2,412 | 2,974 | 1,779 | 1.41 | $0.00204 | -272 | +290 | -0.44 | $-0.00040 | +0.00 |
plot_df = summary.copy()
labels = plot_df["variant_label"].str.replace("Round ", "R", regex=False)
fig, axes = plt.subplots(1, 3, figsize=(16, 4))
axes[0].bar(labels, plot_df["mean_sync_tokens"], color="#4C78A8")
axes[0].set_title("Mean synchronous tokens")
axes[0].set_ylabel("sync tokens per ticket")
axes[0].tick_params(axis="x", rotation=30)
axes[1].bar(labels, plot_df["cost_per_ticket_usd"], color="#59A14F")
axes[1].set_title("Estimated cost")
axes[1].set_ylabel("USD per ticket")
axes[1].tick_params(axis="x", rotation=30)
axes[2].plot(labels, plot_df["mean_quality"], marker="o", color="#E15759")
axes[2].set_ylim(0, 1.0)
axes[2].set_title("Quality score")
axes[2].set_ylabel("score")
axes[2].tick_params(axis="x", rotation=30)
plt.tight_layout()
plt.show()
可选:实时 Responses API 工具循环
live_api.py 中的实现在追加函数结果之前转发 response.output,从而保留推理项和工具调用项。后续请求保留已配置的工具选择,因此可以先做订单和政策查询,再调用退款。经过 max_tool_rounds 批之后,最后一次请求使用 tool_choice="none",以便在不再执行工具的情况下得到答案。
示例使用一张退款工单,并从这个同一工单推导允许的工具。这个路由器仍然使用夹具标签;用于实时流量时,应换成经过评测的应用逻辑。不完整的回复或意外的最终工具调用会抛出错误,而不会被报告为已完成的答案。
对于分开的对话轮次,previous_response_id 可以携带状态。当指令应当作用于下一次请求时,再次提供指令。
from live_api import (
background_followup_request,
live_config_for_ticket,
run_live_support_ticket,
)
# Keep the allowed tools tied to the ticket being evaluated.
live_ticket = EVAL_SET[2] # Order/policy lookup, then open a refund case.
live_config = live_config_for_ticket(live_ticket, "01_prompt_tool_context_controls")
if RUN_LIVE_API_CALLS:
live_result = run_live_support_ticket(live_ticket, live_config, client=client)
print(live_result["response_text"])
display(pd.DataFrame([{k: v for k, v in live_result.items() if k not in {"response_text", "tool_results"}}]))
else:
print("Dry-run mode. Set OPENAI_API_KEY and RUN_LIVE_API_CALLS=true to run a live Responses API ticket.")
Your refund request is eligible, and I’ve opened a return/refund case for order O-1003.
Next step: please use the return instructions from your order page or confirmation email to send the item back. Once the return is received and processed, your refund will be issued.
| config | ticket\_id | model | tool\_calls | latency\_s | estimated\_cost\_usd | input\_tokens | cached\_tokens | output\_tokens | reasoning\_tokens | total\_tokens | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 01\_prompt\_tool\_context\_controls | T-003 | gpt-5.4 | 3 | 8.056909 | 0.008085 | 1788 | 0 | 241 | 84 | 2029 |
优化第 1 轮:提示、工具与上下文控制
从具体的回复规则和工具规则开始。下面的请求把低详细度、低推理力度、输出上限和允许的工具子集组合在一起。输出上限同时包含可见 Token 和推理 Token,因此调整它时要检查不完整回复。
辅助函数还限制工具轮次,并返回精简载荷。对长对话,要谨慎评估压缩或截断:去掉更早的上下文可能丢掉下一步决策所需的事实。
这个演示使用已知的工单元数据来限制工具。生产路由器需要单独评测,以及低置信度路由的回退。如果使用提示优化,要针对一个已观察到的具体失败,并重跑同一套评测。
round1_request_example = {
"model": "gpt-5.4",
"instructions": CONTROLLED_PROMPT,
"tools": SLIM_TOOLS,
"tool_choice": allowed_tool_choice(["lookup_order", "lookup_policy"], mode="auto"),
"reasoning": {"effort": "low"},
"text": {"verbosity": "low"},
"max_output_tokens": 350,
"parallel_tool_calls": True,
"truncation": "auto",
"context_management": [{"type": "compaction", "compact_threshold": 20_000}],
"input": [
{
"role": "user",
"content": "My blender arrived cracked. Order O-1002. Can you replace it?",
}
],
}
print(json.dumps(round1_request_example, indent=2)[:2400] + "\n...")
{
"model": "gpt-5.4",
"instructions": "Role: E-commerce support assistant.\n\nGoal: Resolve routine support tickets with the fewest necessary tool calls while preserving policy correctness.\n\nTool rules:\n- Use only tools required for the current decision.\n- Order status: order lookup only.\n- Damaged delivery or refund: order lookup plus the relevant policy.\n- Billing duplicate charge: order lookup plus billing policy, then escalate.\n- Account access with unverified identity: customer lookup plus account policy, then escalate.\n\nResponse rules:\n- Give the customer the outcome and next step.\n- Do not expose internal reasoning, raw tool data, audit notes, or policy text.\n- Keep the customer-facing answer under 120 words unless escalation legally requires more detail.",
"tools": [
{
"type": "function",
"name": "lookup_customer",
"description": "Fetch minimal customer verification and support tier fields.",
"parameters": {
"type": "object",
"properties": {
"customer_id": {
"type": "string"
}
},
"required": [
"customer_id"
],
"additionalProperties": false
},
"strict": true
},
{
"type": "function",
"name": "lookup_order",
"description": "Fetch order status, delivery age, payment status, and item value.",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string"
}
},
"required": [
"order_id"
],
"additionalProperties": false
},
"strict": true
},
{
"type": "function",
"name": "lookup_policy",
"description": "Fetch the policy needed for the current support decision.",
"parameters": {
"type": "object",
"properties": {
"topic": {
"type": "string",
"enum": [
"shipping",
"damaged_delivery",
"refunds",
"billing",
"account_access"
]
}
},
"required": [
"topic"
],
"additionalProperties": false
},
"strict": true
},
{
"type": "function",
"name": "create_refund_case",
"description": "Open a refund or replacement case only after polic
...
round1_detail = traces[traces["variant"].isin(["00_bad_baseline", "01_prompt_tool_context_controls"])]
display(
round1_detail[
[
"variant_label",
"ticket_id",
"intent",
"tools",
"action",
"extra_tool_calls",
"policy_compliant",
"concise",
"visible_output_tokens",
"total_tokens",
"latency_s",
"cost_usd",
"quality_score",
]
].style.format({"cost_usd": "${:.5f}", "quality_score": "{:.2f}", "latency_s": "{:.2f}"})
)
| variant\_label | ticket\_id | intent | tools | action | extra\_tool\_calls | policy\_compliant | concise | visible\_output\_tokens | total\_tokens | latency\_s | cost\_usd | quality\_score | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Bad baseline | T-001 | order\_status | lookup\_customer, lookup\_order, lookup\_policy, create\_refund\_case, escalate\_to\_human | provide\_status\_eta | 4 | False | False | 167 | 12913 | 4.88 | $0.03993 | 0.55 |
| 1 | Bad baseline | T-002 | damaged\_delivery | lookup\_customer, lookup\_order, lookup\_policy, create\_refund\_case, escalate\_to\_human | open\_replacement\_without\_photo | 3 | False | False | 174 | 12917 | 4.88 | $0.04034 | 0.65 |
| 2 | Bad baseline | T-003 | refund\_eligibility | lookup\_customer, lookup\_order, lookup\_policy, create\_refund\_case, escalate\_to\_human | escalate\_refund\_review | 2 | False | False | 170 | 12818 | 4.87 | $0.04004 | 0.22 |
| 3 | Bad baseline | T-004 | billing\_issue | lookup\_customer, lookup\_order, lookup\_policy, create\_refund\_case, escalate\_to\_human | escalate\_billing\_review | 2 | True | False | 169 | 12830 | 4.87 | $0.04038 | 0.85 |
| 4 | Bad baseline | T-005 | account\_access | lookup\_customer, lookup\_order, lookup\_policy, create\_refund\_case, escalate\_to\_human | escalate\_account\_security | 2 | False | False | 172 | 8112 | 3.97 | $0.02899 | 0.60 |
| 5 | Bad baseline | T-006 | refund\_dispute | lookup\_customer, lookup\_order, lookup\_policy, create\_refund\_case, escalate\_to\_human | escalate\_refund\_review | 2 | False | False | 168 | 12869 | 4.88 | $0.04077 | 0.60 |
| 6 | Bad baseline | T-007 | delivered\_not\_received | lookup\_customer, lookup\_order, lookup\_policy, create\_refund\_case, escalate\_to\_human | start\_delivery\_trace\_steps | 3 | False | False | 172 | 12862 | 4.87 | $0.04018 | 0.57 |
| 7 | Bad baseline | T-008 | high\_value\_damage | lookup\_customer, lookup\_order, lookup\_policy, create\_refund\_case, escalate\_to\_human | promise\_refund\_high\_value\_damage | 2 | False | False | 174 | 12997 | 4.91 | $0.04136 | 0.22 |
| 8 | Bad baseline | T-009 | refund\_eligibility | lookup\_customer, lookup\_order, lookup\_policy, create\_refund\_case, escalate\_to\_human | escalate\_refund\_review | 2 | False | False | 169 | 12921 | 4.88 | $0.04029 | 0.22 |
| 9 | Bad baseline | T-010 | account\_access | lookup\_customer, lookup\_order, lookup\_policy, create\_refund\_case, escalate\_to\_human | escalate\_account\_security | 2 | False | False | 172 | 8115 | 3.97 | $0.02900 | 0.60 |
| 10 | Round 1: controls | T-001 | order\_status | lookup\_order | provide\_status\_eta | 0 | True | True | 42 | 1252 | 2.00 | $0.00418 | 0.98 |
| 11 | Round 1: controls | T-002 | damaged\_delivery | lookup\_order, lookup\_policy | request\_photo\_then\_offer\_replacement | 0 | True | True | 45 | 1354 | 2.17 | $0.00482 | 0.98 |
| 12 | Round 1: controls | T-003 | refund\_eligibility | lookup\_order, lookup\_policy, create\_refund\_case | open\_refund\_case | 0 | True | True | 51 | 1393 | 2.32 | $0.00512 | 0.98 |
| 13 | Round 1: controls | T-004 | billing\_issue | lookup\_order, lookup\_policy, escalate\_to\_human | escalate\_billing\_review | 0 | True | True | 35 | 1383 | 2.31 | $0.00512 | 0.98 |
| 14 | Round 1: controls | T-005 | account\_access | lookup\_customer, lookup\_policy, escalate\_to\_human | escalate\_account\_security | 0 | True | True | 39 | 1378 | 2.32 | $0.00543 | 0.99 |
| 15 | Round 1: controls | T-006 | refund\_dispute | lookup\_order, lookup\_policy, escalate\_to\_human | escalate\_refund\_review | 0 | True | True | 36 | 1418 | 2.32 | $0.00545 | 0.99 |
| 16 | Round 1: controls | T-007 | delivered\_not\_received | lookup\_order, lookup\_policy | start\_delivery\_trace\_steps | 0 | True | True | 41 | 1390 | 2.17 | $0.00486 | 0.98 |
| 17 | Round 1: controls | T-008 | high\_value\_damage | lookup\_order, lookup\_policy, escalate\_to\_human | escalate\_high\_value\_damage | 0 | True | True | 38 | 1445 | 2.33 | $0.00568 | 0.99 |
| 18 | Round 1: controls | T-009 | refund\_eligibility | lookup\_order, lookup\_policy, create\_refund\_case | open\_refund\_case | 0 | True | True | 51 | 1397 | 2.32 | $0.00513 | 0.98 |
| 19 | Round 1: controls | T-010 | account\_access | lookup\_customer, lookup\_policy, escalate\_to\_human | escalate\_account\_security | 0 | True | True | 39 | 1382 | 2.32 | $0.00544 | 0.99 |
优化第 2 轮:模型选择
按步骤匹配模型大小,而不是选一个全局模型。为每个工作负载建立 GPT-5.4 基线,并用相同的已标注工单、提示、工具、结构化输出模式和质量标准来评估它。
- 意图分类、抽取和低风险路由: 用
gpt-5.4-nano做工单分类、实体抽取和简单标签。比较意图准确率、高风险假阴性、结构化输出可靠性、延迟,以及每张正确分类工单的成本。(GPT-5.4 nano) - 常规支持与订单工作流: 用
gpt-5.4-mini处理订单状态、损坏配送、直接的退款资格检查,以及其他需要解释政策或使用工具的可重复支持任务。评估解决正确性、工具调用准确率、政策合规、p50/p95 延迟,以及每张成功解决工单的成本。(GPT-5.4 mini) - 复杂或高风险案例: 用
gpt-5.4处理账户访问问题、重复扣款升级、退款争议和其他高后果交互。保留确定性的授权与退款检查、明确的升级规则,以及需要时的人工复核。测量解决质量、政策遵守、延迟和端到端成本。(GPT-5.4)
GPT-5.6 系列提供与这些相同层级对应的更新模型。GPT-5.6 Luna(gpt-5.6-luna)对应 nano 层,用于分类和高流量任务。GPT-5.6 Terra(gpt-5.6-terra)对应 mini 层,用于常规支持工作流。GPT-5.6 Sol(gpt-5.6-sol)对应完整模型层,用于复杂或高风险案例。每一个都可以用相同工单和质量标准,对照其对应的 GPT-5.4 基线评估。
每次比较都从现有的推理力度设置开始,并再评估低一级的力度。如果更新的模型用更少的重试、不必要的工具调用或升级来解决工单,它在任务层面可以更经济。只有选定模型明确支持时,才考虑微调。(GPT-5.6 迁移指南)
from live_api import TRIAGE_SCHEMA
print(json.dumps(TRIAGE_SCHEMA, indent=2))
# Optional: from live_api import live_triage_example
# live_triage_example(EVAL_SET[0]["message"], client=client)
{
"type": "json_schema",
"name": "support_triage",
"strict": true,
"schema": {
"type": "object",
"properties": {
"intent": {
"type": "string",
"enum": [
"order_status",
"damaged_delivery",
"refund_eligibility",
"billing_issue",
"account_access",
"refund_dispute",
"delivered_not_received",
"high_value_damage"
]
},
"risk": {
"type": "string",
"enum": [
"low",
"medium",
"high"
]
},
"needs_human": {
"type": "boolean"
},
"order_id": {
"type": [
"string",
"null"
]
}
},
"required": [
"intent",
"risk",
"needs_human",
"order_id"
],
"additionalProperties": false
}
}
model_routing_view = traces[traces["variant"].isin(["01_prompt_tool_context_controls", "02_model_routing"])]
display(
model_routing_view[
[
"variant_label",
"ticket_id",
"intent",
"risk",
"model",
"routing_tokens",
"total_tokens",
"sync_cost_usd",
"quality_score",
"policy_compliant",
]
].style.format({"sync_cost_usd": "${:.5f}", "quality_score": "{:.2f}"})
)
| variant\_label | ticket\_id | intent | risk | model | routing\_tokens | total\_tokens | sync\_cost\_usd | quality\_score | policy\_compliant | |
|---|---|---|---|---|---|---|---|---|---|---|
| 10 | Round 1: controls | T-001 | order\_status | low | gpt-5.4 | 0 | 1252 | $0.00418 | 0.98 | True |
| 11 | Round 1: controls | T-002 | damaged\_delivery | medium | gpt-5.4 | 0 | 1354 | $0.00482 | 0.98 | True |
| 12 | Round 1: controls | T-003 | refund\_eligibility | medium | gpt-5.4 | 0 | 1393 | $0.00512 | 0.98 | True |
| 13 | Round 1: controls | T-004 | billing\_issue | medium | gpt-5.4 | 0 | 1383 | $0.00512 | 0.98 | True |
| 14 | Round 1: controls | T-005 | account\_access | high | gpt-5.4 | 0 | 1378 | $0.00543 | 0.99 | True |
| 15 | Round 1: controls | T-006 | refund\_dispute | high | gpt-5.4 | 0 | 1418 | $0.00545 | 0.99 | True |
| 16 | Round 1: controls | T-007 | delivered\_not\_received | medium | gpt-5.4 | 0 | 1390 | $0.00486 | 0.98 | True |
| 17 | Round 1: controls | T-008 | high\_value\_damage | high | gpt-5.4 | 0 | 1445 | $0.00568 | 0.99 | True |
| 18 | Round 1: controls | T-009 | refund\_eligibility | low | gpt-5.4 | 0 | 1397 | $0.00513 | 0.98 | True |
| 19 | Round 1: controls | T-010 | account\_access | high | gpt-5.4 | 0 | 1382 | $0.00544 | 0.99 | True |
| 20 | Round 2: routing | T-001 | order\_status | low | gpt-5.4-mini | 208 | 1353 | $0.00123 | 0.98 | True |
| 21 | Round 2: routing | T-002 | damaged\_delivery | medium | gpt-5.4-mini | 208 | 1451 | $0.00141 | 0.98 | True |
| 22 | Round 2: routing | T-003 | refund\_eligibility | medium | gpt-5.4-mini | 210 | 1490 | $0.00149 | 0.98 | True |
| 23 | Round 2: routing | T-004 | billing\_issue | medium | gpt-5.4-mini | 207 | 1474 | $0.00147 | 0.98 | True |
| 24 | Round 2: routing | T-005 | account\_access | high | gpt-5.4 | 206 | 1490 | $0.00536 | 0.99 | True |
| 25 | Round 2: routing | T-006 | refund\_dispute | high | gpt-5.4 | 212 | 1536 | $0.00537 | 0.99 | True |
| 26 | Round 2: routing | T-007 | delivered\_not\_received | medium | gpt-5.4-mini | 213 | 1492 | $0.00142 | 0.98 | True |
| 27 | Round 2: routing | T-008 | high\_value\_damage | high | gpt-5.4 | 218 | 1570 | $0.00562 | 0.99 | True |
| 28 | Round 2: routing | T-009 | refund\_eligibility | low | gpt-5.4-mini | 214 | 1498 | $0.00149 | 0.98 | True |
| 29 | Round 2: routing | T-010 | account\_access | high | gpt-5.4 | 209 | 1497 | $0.00537 | 0.99 | True |
PRIMARY_SOURCE: https://developers.openai.com/cookbook/examples/agent_optimization/optimizing_agents_for_cost_and_quality
- #客服智能体
- #成本优化
- #工具约束
- #模型路由
- #质量评测


