可易

优化客服智能体的成本与质量(上)

可易团队阅读约 23 分钟

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

目录

用例与智能体设置

我们虚构的电商助手处理订单状态、损坏配送、退款资格、重复扣款和账户访问。常规查询值得评估更小的模型;对政策敏感的案例则用来检验优化后的工作流是否仍能正确升级。

五个模拟工具分别代表订单系统(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\_idintentriskdifficultyexpected\_toolsexpected\_actionmust\_escalate
0T-001order\_statuslowsimple\_lookup\[lookup\_order\]provide\_status\_etaFalse
1T-002damaged\_deliverymediumroutine\_policy\[lookup\_order, lookup\_policy\]request\_photo\_then\_offer\_replacementFalse
2T-003refund\_eligibilitymediumroutine\_policy\[lookup\_order, lookup\_policy, create\_refund\_case\]open\_refund\_caseFalse
3T-004billing\_issuemediumsensitive\_policy\[lookup\_order, lookup\_policy, escalate\_to\_human\]escalate\_billing\_reviewTrue
4T-005account\_accesshighaccount\_security\[lookup\_customer, lookup\_policy, escalate\_to\_h...escalate\_account\_securityTrue
5T-006refund\_disputehighoutside\_policy\_window\[lookup\_order, lookup\_policy, escalate\_to\_human\]escalate\_refund\_reviewTrue
6T-007delivered\_not\_receivedmediumroutine\_policy\[lookup\_order, lookup\_policy\]start\_delivery\_trace\_stepsFalse
7T-008high\_value\_damagehighhigh\_value\_policy\[lookup\_order, lookup\_policy, escalate\_to\_human\]escalate\_high\_value\_damageTrue
8T-009refund\_eligibilitylowroutine\_policy\[lookup\_order, lookup\_policy, create\_refund\_case\]open\_refund\_caseFalse
9T-010account\_accesshighaccount\_security\[lookup\_customer, lookup\_policy, escalate\_to\_h...escalate\_account\_securityTrue

支持数据与工具

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")

inputcached\_inputoutput
model
------------
gpt-5.42.500.25015.00
gpt-5.4-mini0.750.0754.50
gpt-5.4-nano0.200.0201.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()

variantvariant\_labelticket\_idintentriskdifficultymodelrouting\_tokenstool\_callsexpected\_toolstoolsactionexpected\_actioninput\_tokenslatency\_input\_tokenscacheable\_prefix\_tokenscached\_tokensoutput\_tokensvisible\_output\_tokensreasoning\_tokens...total\_tokenslatency\_ssync\_cost\_usdbackground\_tokensbackground\_cost\_usdcost\_usdescalatedcustomer\_responsemissing\_required\_toolsextra\_tool\_callsunnecessary\_toolsescalation\_correctaction\_correctpolicy\_compliantconciseresponse\_completemissing\_required\_phrasesforbidden\_claims\_absentforbidden\_claims\_foundquality\_score
000\_bad\_baselineBad baselineT-001order\_statuslowsimple\_lookupgpt-5.405lookup\_orderlookup\_customer, lookup\_order, lookup\_policy, ...provide\_status\_etaprovide\_status\_eta123011230100612167445...129134.880.03993300.00.039933FalseI reviewed your message for ticket T-001 and c...4create\_refund\_case, escalate\_to\_human, lookup\_...TrueTrueFalseFalseFalsein transit, tomorrowTrue0.55
100\_bad\_baselineBad baselineT-002damaged\_deliverymediumroutine\_policygpt-5.405lookup\_order, lookup\_policylookup\_customer, lookup\_order, lookup\_policy, ...open\_replacement\_without\_photorequest\_photo\_then\_offer\_replacement122731227300644174470...129174.880.04034300.00.040343FalseI reviewed your message for ticket T-002 and c...3create\_refund\_case, escalate\_to\_human, lookup\_...TrueFalseFalseFalseTrueTrue0.65
200\_bad\_baselineBad baselineT-003refund\_eligibilitymediumroutine\_policygpt-5.405lookup\_order, lookup\_policy, create\_refund\_caselookup\_customer, lookup\_order, lookup\_policy, ...escalate\_refund\_reviewopen\_refund\_case121781217800640170470...128184.870.04004500.00.040045TrueI reviewed your message for ticket T-003 and c...2escalate\_to\_human, lookup\_customerFalseFalseFalseFalseFalserefund case, within 30 daysTrue0.22
300\_bad\_baselineBad baselineT-004billing\_issuemediumsensitive\_policygpt-5.405lookup\_order, lookup\_policy, escalate\_to\_humanlookup\_customer, lookup\_order, lookup\_policy, ...escalate\_billing\_reviewescalate\_billing\_review121661216600664169495...128304.870.04037500.00.040375TrueI reviewed your message for ticket T-004 and c...2create\_refund\_case, lookup\_customerTrueTrueTrueFalseTrueTrue0.85
400\_bad\_baselineBad baselineT-005account\_accesshighaccount\_securitygpt-5.405lookup\_customer, lookup\_policy, escalate\_to\_humanlookup\_customer, lookup\_order, lookup\_policy, ...escalate\_account\_securityescalate\_account\_security7415741500697172525...81123.970.02899300.00.028993TrueI reviewed your message for ticket T-005 and c...2create\_refund\_case, lookup\_orderTrueTrueFalseFalseFalseaccount security, verificationTrue0.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\_labelmean\_qualitypolicy\_complianceaction\_accuracyescalation\_accuracymean\_tool\_callsmean\_extra\_tool\_callsmean\_sync\_tokensmean\_total\_tokensmean\_cached\_tokensp50\_latency\_scost\_per\_ticket\_usdcost\_reduction\_vs\_baselinemonthly\_cost\_at\_100k\_tickets
0Bad baseline0.5110%60%70%5.02.411,93511,93504.88$0.038130%$3,813
1Round 1: controls0.98100%100%100%2.60.01,3791,37902.32$0.0051287%$512
2Round 2: routing0.98100%100%100%2.60.01,4851,48501.87$0.0030292%$302
3Round 3: caching0.98100%100%100%2.60.02,6842,6841,7791.85$0.0024494%$244
4Round 4: split workflow0.98100%100%100%2.60.02,4122,9741,7791.41$0.0020495%$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\_labelmean\_qualitypolicy\_compliancemean\_tool\_callsmean\_extra\_tool\_callsmean\_sync\_tokensmean\_total\_tokensmean\_cached\_tokensp50\_latency\_scost\_per\_ticket\_usdmean\_sync\_tokens\_delta\_vs\_previousmean\_total\_tokens\_delta\_vs\_previousp50\_latency\_s\_delta\_vs\_previouscost\_per\_ticket\_usd\_delta\_vs\_previousquality\_delta\_vs\_previous
0Bad baseline0.5110%5.02.411,93511,93504.88$0.03813+nan+nan+nan$+nan+nan
1Round 1: controls0.98100%2.60.01,3791,37902.32$0.00512-10,556-10,556-2.56$-0.03301+0.48
2Round 2: routing0.98100%2.60.01,4851,48501.87$0.00302+106+106-0.45$-0.00210+0.00
3Round 3: caching0.98100%2.60.02,6842,6841,7791.85$0.00244+1,199+1,199-0.02$-0.00058+0.00
4Round 4: split workflow0.98100%2.60.02,4122,9741,7791.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.

configticket\_idmodeltool\_callslatency\_sestimated\_cost\_usdinput\_tokenscached\_tokensoutput\_tokensreasoning\_tokenstotal\_tokens
001\_prompt\_tool\_context\_controlsT-003gpt-5.438.0569090.00808517880241842029

优化第 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\_labelticket\_idintenttoolsactionextra\_tool\_callspolicy\_compliantconcisevisible\_output\_tokenstotal\_tokenslatency\_scost\_usdquality\_score
0Bad baselineT-001order\_statuslookup\_customer, lookup\_order, lookup\_policy, create\_refund\_case, escalate\_to\_humanprovide\_status\_eta4FalseFalse167129134.88$0.039930.55
1Bad baselineT-002damaged\_deliverylookup\_customer, lookup\_order, lookup\_policy, create\_refund\_case, escalate\_to\_humanopen\_replacement\_without\_photo3FalseFalse174129174.88$0.040340.65
2Bad baselineT-003refund\_eligibilitylookup\_customer, lookup\_order, lookup\_policy, create\_refund\_case, escalate\_to\_humanescalate\_refund\_review2FalseFalse170128184.87$0.040040.22
3Bad baselineT-004billing\_issuelookup\_customer, lookup\_order, lookup\_policy, create\_refund\_case, escalate\_to\_humanescalate\_billing\_review2TrueFalse169128304.87$0.040380.85
4Bad baselineT-005account\_accesslookup\_customer, lookup\_order, lookup\_policy, create\_refund\_case, escalate\_to\_humanescalate\_account\_security2FalseFalse17281123.97$0.028990.60
5Bad baselineT-006refund\_disputelookup\_customer, lookup\_order, lookup\_policy, create\_refund\_case, escalate\_to\_humanescalate\_refund\_review2FalseFalse168128694.88$0.040770.60
6Bad baselineT-007delivered\_not\_receivedlookup\_customer, lookup\_order, lookup\_policy, create\_refund\_case, escalate\_to\_humanstart\_delivery\_trace\_steps3FalseFalse172128624.87$0.040180.57
7Bad baselineT-008high\_value\_damagelookup\_customer, lookup\_order, lookup\_policy, create\_refund\_case, escalate\_to\_humanpromise\_refund\_high\_value\_damage2FalseFalse174129974.91$0.041360.22
8Bad baselineT-009refund\_eligibilitylookup\_customer, lookup\_order, lookup\_policy, create\_refund\_case, escalate\_to\_humanescalate\_refund\_review2FalseFalse169129214.88$0.040290.22
9Bad baselineT-010account\_accesslookup\_customer, lookup\_order, lookup\_policy, create\_refund\_case, escalate\_to\_humanescalate\_account\_security2FalseFalse17281153.97$0.029000.60
10Round 1: controlsT-001order\_statuslookup\_orderprovide\_status\_eta0TrueTrue4212522.00$0.004180.98
11Round 1: controlsT-002damaged\_deliverylookup\_order, lookup\_policyrequest\_photo\_then\_offer\_replacement0TrueTrue4513542.17$0.004820.98
12Round 1: controlsT-003refund\_eligibilitylookup\_order, lookup\_policy, create\_refund\_caseopen\_refund\_case0TrueTrue5113932.32$0.005120.98
13Round 1: controlsT-004billing\_issuelookup\_order, lookup\_policy, escalate\_to\_humanescalate\_billing\_review0TrueTrue3513832.31$0.005120.98
14Round 1: controlsT-005account\_accesslookup\_customer, lookup\_policy, escalate\_to\_humanescalate\_account\_security0TrueTrue3913782.32$0.005430.99
15Round 1: controlsT-006refund\_disputelookup\_order, lookup\_policy, escalate\_to\_humanescalate\_refund\_review0TrueTrue3614182.32$0.005450.99
16Round 1: controlsT-007delivered\_not\_receivedlookup\_order, lookup\_policystart\_delivery\_trace\_steps0TrueTrue4113902.17$0.004860.98
17Round 1: controlsT-008high\_value\_damagelookup\_order, lookup\_policy, escalate\_to\_humanescalate\_high\_value\_damage0TrueTrue3814452.33$0.005680.99
18Round 1: controlsT-009refund\_eligibilitylookup\_order, lookup\_policy, create\_refund\_caseopen\_refund\_case0TrueTrue5113972.32$0.005130.98
19Round 1: controlsT-010account\_accesslookup\_customer, lookup\_policy, escalate\_to\_humanescalate\_account\_security0TrueTrue3913822.32$0.005440.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\_labelticket\_idintentriskmodelrouting\_tokenstotal\_tokenssync\_cost\_usdquality\_scorepolicy\_compliant
10Round 1: controlsT-001order\_statuslowgpt-5.401252$0.004180.98True
11Round 1: controlsT-002damaged\_deliverymediumgpt-5.401354$0.004820.98True
12Round 1: controlsT-003refund\_eligibilitymediumgpt-5.401393$0.005120.98True
13Round 1: controlsT-004billing\_issuemediumgpt-5.401383$0.005120.98True
14Round 1: controlsT-005account\_accesshighgpt-5.401378$0.005430.99True
15Round 1: controlsT-006refund\_disputehighgpt-5.401418$0.005450.99True
16Round 1: controlsT-007delivered\_not\_receivedmediumgpt-5.401390$0.004860.98True
17Round 1: controlsT-008high\_value\_damagehighgpt-5.401445$0.005680.99True
18Round 1: controlsT-009refund\_eligibilitylowgpt-5.401397$0.005130.98True
19Round 1: controlsT-010account\_accesshighgpt-5.401382$0.005440.99True
20Round 2: routingT-001order\_statuslowgpt-5.4-mini2081353$0.001230.98True
21Round 2: routingT-002damaged\_deliverymediumgpt-5.4-mini2081451$0.001410.98True
22Round 2: routingT-003refund\_eligibilitymediumgpt-5.4-mini2101490$0.001490.98True
23Round 2: routingT-004billing\_issuemediumgpt-5.4-mini2071474$0.001470.98True
24Round 2: routingT-005account\_accesshighgpt-5.42061490$0.005360.99True
25Round 2: routingT-006refund\_disputehighgpt-5.42121536$0.005370.99True
26Round 2: routingT-007delivered\_not\_receivedmediumgpt-5.4-mini2131492$0.001420.98True
27Round 2: routingT-008high\_value\_damagehighgpt-5.42181570$0.005620.99True
28Round 2: routingT-009refund\_eligibilitylowgpt-5.4-mini2141498$0.001490.98True
29Round 2: routingT-010account\_accesshighgpt-5.42091497$0.005370.99True

PRIMARY_SOURCE: https://developers.openai.com/cookbook/examples/agent_optimization/optimizing_agents_for_cost_and_quality

  • #客服智能体
  • #成本优化
  • #工具约束
  • #模型路由
  • #质量评测