可易

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

可易团队阅读约 13 分钟

说明上线后应按每次经核验解决的完整成本验收,而不是只看 Token,并给出护栏、评判器与调优顺序。

目录

监控、评测与护栏

优化后的工作流进入生产之后,保持一个反复运行的评测循环。目标不是孤立地最小化 Token。目标是以每次成功结果的最低总成本,正确、安全、快速地解决客户问题。

测量任务效率,而不只是 Token 效率

Token 数量是有用的诊断,但它们不告诉你客户的问题是否被解决。一个更便宜的模型如果需要反复尝试、不必要的工具调用或人工纠正,那么按已解决问题计算的成本,可能高于一个第一次就正确完成任务的更强模型。

OpenAI 的指南建议测量达到可接受结果的完整成本,包括“模型和工具用量、尝试次数、完成率、延迟和人工复核”。对客户支持而言,这个被接受的结果可以是一个已解决的工单。见如何管理智能体时代的 AI 投入和AI 时代的记分卡。

一个有用的运营公式是:

每次经核验解决的混合成本 = 模型、工具、基础设施、重试、人工复核、升级和返工成本之和 / 经核验已解决的客户问题数

分子必须包含不成功尝试上的花费,而不只是最终通过的轨迹。把自主解决和人工协助解决分开跟踪,这样智能体成本表面上的下降就不会掩盖工作被转移给支持团队。

例如,一个每张工单花费 0.02 美元且解决 50% 工单的工作流,每次成功解决花费 0.04 美元。一个每张工单花费 0.03 美元且解决 90% 的工作流,每次成功解决大约花费 0.033 美元。第二个工作流每次尝试更贵,但每次成功结果更便宜。这些数字是说明性的,不含人工支持成本。

优化之前先定义成功

成功的回复使用正确的账户、订单和政策事实,并给出准确的下一步。必要工具必须以有效参数成功,承诺的动作必须已完成或明确处于待处理。政策和授权检查决定哪些案例可以自动解决,哪些需要升级。

政策要求的升级可以是一次成功的处理结果,但它不是自主解决。同样,开立工单或索取照片并不能证明客户的根本问题已经解决。计算首次接触解决率和自动化率时,把这些结果分开。

跟踪完整的支持工作流

对自主案例和人工协助案例分别监控经核验的解决,包括重复联系和重新打开的工单。把这些结果与政策及升级准确率、每次经核验解决的总成本,以及面向客户的 p50/p95 延迟放在一起。

用模型调用、工具失败、重试、Token 用量和路由决策来解释这些结果的变化。按意图、风险、语言、地区、客户层级和模型路由分段,以免平均值掩盖回归。

检查完整执行轨迹,而不只是最终答案。OpenAI 的智能体评测指南描述了捕获模型调用、工具调用、护栏和交接的轨迹,从而能够识别被润色过的回复所掩盖的不必要循环、错误动作和路由失败。

在相同的代表性工单分布上比较工作流变体,包括困难和对政策敏感的案例。在比较成本或延迟之前,把政策合规、动作正确性、安全和升级准确率当作硬门。用生产失败刷新数据集,并在提示、模型、工具、路由或政策变化时重跑评测。见评测最佳实践。

看起来高效、却会造成下游风险的护栏失败模式包括:工具超时、空的或过大的工具载荷、重复的工具循环、不安全的账户访问动作、跳过必要核验,以及在资格或完成得到确认之前承诺退款。

演示局限: 这个笔记本直接建模 Token、估算成本、工具用量、延迟、动作准确率、政策合规和升级行为。真正的首次接触解决、重新打开的工单、重试历史、已完成的下游结果和人工处理成本,需要生产支持系统和轨迹数据。不要仅从空跑模拟推断这些指标。

from simulation import deterministic_guardrail_check

guardrail_rows = []
for _, row in traces.iterrows():
    ticket = next(t for t in EVAL_SET if t["ticket_id"] == row["ticket_id"])
    failures = deterministic_guardrail_check(ticket, row.to_dict())
    guardrail_rows.append(
        {
            "variant_label": row["variant_label"],
            "ticket_id": row["ticket_id"],
            "failures": ", ".join(failures),
            "passed": not failures,
        }
    )

guardrails = pd.DataFrame(guardrail_rows)
guardrail_summary = guardrails.groupby("variant_label", sort=False).agg(pass_rate=("passed", "mean"), failures=("passed", lambda s: (~s).sum())).reset_index()

display(guardrail_summary.style.format({"pass_rate": "{:.0%}"}))
display(guardrails[~guardrails["passed"]].head(20))

variant\_labelpass\_ratefailures
0Bad baseline0%10
1Round 1: controls100%0
2Round 2: routing100%0
3Round 3: caching100%0
4Round 4: split workflow100%0
variant\_labelticket\_idfailurespassed
0Bad baselineT-001too\_many\_unnecessary\_tools, missing\_required\_r...False
1Bad baselineT-002too\_many\_unnecessary\_tools, policy\_or\_action\_m...False
2Bad baselineT-003missing\_required\_response\_content, policy\_or\_a...False
3Bad baselineT-004customer\_answer\_too\_longFalse
4Bad baselineT-005missing\_required\_response\_content, policy\_or\_a...False
5Bad baselineT-006missing\_required\_response\_content, policy\_or\_a...False
6Bad baselineT-007too\_many\_unnecessary\_tools, missing\_required\_r...False
7Bad baselineT-008missing\_required\_response\_content, policy\_or\_a...False
8Bad baselineT-009missing\_required\_response\_content, policy\_or\_a...False
9Bad baselineT-010missing\_required\_response\_content, policy\_or\_a...False

可选:评判客户答案的完整性与依据

更便宜的工作流是否保留了准确、有用的答案?这个评判器只检查一个问题:给定客户工单、相关政策和记录下来的工具结果,答案是否正确说明了结果和下一步,且没有无依据的声称?

评判辅助函数 返回 passed 和简短的 reason。它接受等价措辞:“您的退货符合我们的 30 天政策”不必包含夹具中的精确短语 “within 30 days”。但是,当记录的工具结果只确认开立了复核工单时,“您的退款正在处理中”应当失败。工具结果在工具运行时捕获,而不是从期望动作重建。

运行设置单元格之前,设置 RUN_LLM_JUDGE=true 和 OPENAI_API_KEY。下面的代码用固定的 gpt-5.4-mini 评判器和评分标准,为每一轮优化的同一批已记录答案打分。评判器看不到变体名称、智能体模型、成本或期望动作标签。这些是对合成智能体轨迹的真实评判调用,因此结果评估的是预制答案,而不是模型表现。对于实时答案,在选择加入之后调用 live_judge_response(live_ticket, live_result["response_text"], live_result["tool_results"], client=judge_client)。

表格把评判通过率放在确定性通过率旁边。both_pass_rate 要求两项检查都通过;评判通过永远不能覆盖确定性失败。评判通过率和合并通过率只覆盖成功打分的轨迹,因此比较变体之前要检查覆盖率和错误。跳过、拒绝、格式错误或不完整的评分仍然不可用。评测成本与智能体成本和客户延迟分开报告;known_judge_cost_usd 使用返回的用量,judge_cost_unavailable 标记没有成本数据的尝试。

在把这些评分用作发布门之前,自己标注一个小样本,包括一次有效改写、一个缺失的下一步,以及一次无依据的退款承诺。检查一致性,并在评分标准不一致时修订它。见评测最佳实践。

from evaluation import evaluate_answer_traces, summarize_answer_evals

answer_evals = evaluate_answer_traces(
    EVAL_SET, traces.to_dict("records"), client=judge_client
)
answer_eval_summary = summarize_answer_evals(answer_evals)
display(answer_eval_summary.drop(columns="variant").style.format(
    {
        "deterministic_pass_rate": "{:.0%}",
        "judge_coverage": "{:.0%}",
        "judge_pass_rate": "{:.0%}",
        "both_pass_rate": "{:.0%}",
        "known_judge_cost_usd": "${:.5f}",
    },
    na_rep="Not available",
))
if RUN_LLM_JUDGE:
    # Inspect failures, errors, and disagreements with the literal phrase checks.
    needs_review = answer_evals[
        answer_evals["judge_status"].eq("error") | answer_evals["passed"].eq(False)
        | answer_evals["passed"].ne(answer_evals["deterministic_passed"])
    ]
    display(needs_review[["variant_label", "ticket_id", "deterministic_passed", "passed", "reason"]])
else:
    print("Judge not run. Set RUN_LLM_JUDGE=true to grade these saved answers.")

variant\_labelticketsdeterministic\_pass\_ratejudge\_gradedjudge\_coveragejudge\_errorsjudge\_pass\_rateboth\_pass\_rateknown\_judge\_cost\_usdjudge\_cost\_unavailable
0Bad baseline100%10100%00%0%$0.056370
1Round 1: controls10100%10100%080%80%$0.012520
2Round 2: routing10100%10100%070%70%$0.013500
3Round 3: caching10100%10100%090%90%$0.012790
4Round 4: split workflow10100%10100%080%80%$0.012850
variant\_labelticket\_iddeterministic\_passedpassedreason
0Bad baselineT-001FalseFalseThe answer does not give the customer the actu...
1Bad baselineT-002FalseFalseThe reply does not follow the policy: it shoul...
2Bad baselineT-003FalseFalseThe answer is not grounded in the evidence: th...
3Bad baselineT-004FalseFalseThe reply does not clearly tell the customer t...
4Bad baselineT-005FalseFalseIt does not give the customer the needed accou...
5Bad baselineT-006FalseFalseIt does not clearly tell the customer that O-1...
6Bad baselineT-007FalseFalseThe reply does not give the customer-facing ne...
7Bad baselineT-008FalseFalseThe answer is not grounded in the evidence: it...
8Bad baselineT-009FalseFalseThe answer is vague and overly internal. It do...
9Bad baselineT-010FalseFalseIt follows the policy direction to escalate, b...
12Round 1: controlsT-003TrueFalseThe case opening is supported, but the answer ...
19Round 1: controlsT-010TrueFalseIt correctly says identity must be verified be...
22Round 2: routingT-003TrueFalseThe answer overstates the outcome: it only sho...
24Round 2: routingT-005TrueFalseThe reply correctly says account details can’t...
29Round 2: routingT-010TrueFalseThe reply gives the right general guidance, bu...
39Round 3: cachingT-010TrueFalseThe reply gives the right general guidance, bu...
44Round 4: split workflowT-005TrueFalseThe reply is grounded on the identity check an...
49Round 4: split workflowT-010TrueFalseThe answer gives the right general guidance, b...

工单前后走查

这些示例比较低效基线与最终优化路径。

walkthrough_tickets = ["T-002", "T-004", "T-008"]
walkthrough = traces[
    traces["ticket_id"].isin(walkthrough_tickets)
    & traces["variant"].isin(["00_bad_baseline", "04_split_workflow"])
].copy()
walkthrough["response_preview"] = walkthrough["customer_response"].str.replace("\n", " ").str.slice(0, 220)

display(
    walkthrough[
        [
            "ticket_id",
            "variant_label",
            "intent",
            "risk",
            "model",
            "tools",
            "action",
            "policy_compliant",
            "sync_tokens",
            "total_tokens",
            "latency_s",
            "cost_usd",
            "quality_score",
            "response_preview",
        ]
    ].style.format({"cost_usd": "${:.5f}", "quality_score": "{:.2f}", "latency_s": "{:.2f}"})
)

ticket\_idvariant\_labelintentriskmodeltoolsactionpolicy\_compliantsync\_tokenstotal\_tokenslatency\_scost\_usdquality\_scoreresponse\_preview
1T-002Bad baselinedamaged\_deliverymediumgpt-5.4lookup\_customer, lookup\_order, lookup\_policy, create\_refund\_case, escalate\_to\_humanopen\_replacement\_without\_photoFalse12917129174.88$0.040340.65I reviewed your message for ticket T-002 and checked the customer profile, order system, policy library, refund workflow, escalation queue, carrier events, billing signals, and internal audit notes. Based on the availabl
3T-004Bad baselinebilling\_issuemediumgpt-5.4lookup\_customer, lookup\_order, lookup\_policy, create\_refund\_case, escalate\_to\_humanescalate\_billing\_reviewTrue12830128304.87$0.040380.85I reviewed your message for ticket T-004 and checked the customer profile, order system, policy library, refund workflow, escalation queue, carrier events, billing signals, and internal audit notes. Based on the availabl
7T-008Bad baselinehigh\_value\_damagehighgpt-5.4lookup\_customer, lookup\_order, lookup\_policy, create\_refund\_case, escalate\_to\_humanpromise\_refund\_high\_value\_damageFalse12997129974.91$0.041360.22I reviewed your message for ticket T-008 and checked the customer profile, order system, policy library, refund workflow, escalation queue, carrier events, billing signals, and internal audit notes. Based on the availabl
41T-002Round 4: split workflowdamaged\_deliverymediumgpt-5.4-minilookup\_order, lookup\_policyrequest\_photo\_then\_offer\_replacementTrue238129401.26$0.000970.98I can help with a replacement. Please reply with a photo of the cracked blender and packaging, and we can start the replacement or refund process under the damaged-delivery policy.
43T-004Round 4: split workflowbilling\_issuemediumgpt-5.4-minilookup\_order, lookup\_policy, escalate\_to\_humanescalate\_billing\_reviewTrue240129421.41$0.001030.98I found a duplicate-charge signal and sent this to billing review. The billing team will verify the charge before any refund is confirmed.
47T-008Round 4: split workflowhigh\_value\_damagehighgpt-5.4lookup\_order, lookup\_policy, escalate\_to\_humanescalate\_high\_value\_damageTrue249230632.03$0.003810.99I am sorry the item arrived damaged. Because this is a high-value item, I escalated it for human review. Please attach photos of the item and packaging.
walkthrough_delta = (
    walkthrough.pivot(index="ticket_id", columns="variant", values=["sync_tokens", "total_tokens", "latency_s", "cost_usd", "quality_score"])
    .copy()
)

walkthrough_delta[("delta", "sync_tokens_saved")] = walkthrough_delta[("sync_tokens", "00_bad_baseline")] - walkthrough_delta[("sync_tokens", "04_split_workflow")]
walkthrough_delta[("delta", "total_tokens_saved")] = walkthrough_delta[("total_tokens", "00_bad_baseline")] - walkthrough_delta[("total_tokens", "04_split_workflow")]
walkthrough_delta[("delta", "latency_saved_s")] = walkthrough_delta[("latency_s", "00_bad_baseline")] - walkthrough_delta[("latency_s", "04_split_workflow")]
walkthrough_delta[("delta", "cost_saved_usd")] = walkthrough_delta[("cost_usd", "00_bad_baseline")] - walkthrough_delta[("cost_usd", "04_split_workflow")]
walkthrough_delta[("delta", "quality_change")] = walkthrough_delta[("quality_score", "04_split_workflow")] - walkthrough_delta[("quality_score", "00_bad_baseline")]

display(
    walkthrough_delta[["delta"]].style.format(
        {
            ("delta", "sync_tokens_saved"): "{:,.0f}",
            ("delta", "total_tokens_saved"): "{:,.0f}",
            ("delta", "latency_saved_s"): "{:.2f}",
            ("delta", "cost_saved_usd"): "${:.5f}",
            ("delta", "quality_change"): "{:+.2f}",
        }
    )
)

delta
variantsync\_tokens\_saved
------
ticket\_id
------
T-00210,536
T-00410,429
T-00810,505

前后摘要

最后一行同时包含同步客户路径成本和建模的异步后续成本。mean_sync_tokens 是面向客户的路径;mean_total_tokens 在工作流拆分之后还包括后台质量保证/打标工作。

最强的结果不是来自单一技巧。它来自按安全顺序使用这些杠杆:

建立基线 -> 提示/输出控制 -> 工具控制 -> 基本上下文卫生 -> 模型路由 -> 缓存 -> 感知缓存的上下文调优 -> 拆分工作流 -> 处理层级

关键工程习惯是按步骤优化,而不是全局优化。常规分类器、高风险退款争议、面向客户的回复和离线质量保证打标器,不应当使用相同的模型、上下文、工具、延迟目标或服务层级。

before_after = summary[summary["variant"].isin(["00_bad_baseline", "04_split_workflow"])].copy()
display(
    before_after[
        [
            "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",
            "sync_cost_per_ticket_usd",
            "background_cost_per_ticket_usd",
            "cost_per_ticket_usd",
            "monthly_cost_at_100k_tickets",
        ]
    ].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}",
            "sync_cost_per_ticket_usd": "${:.5f}",
            "background_cost_per_ticket_usd": "${:.5f}",
            "cost_per_ticket_usd": "${:.5f}",
            "monthly_cost_at_100k_tickets": "${:,.0f}",
        }
    )
)

variant\_labelmean\_qualitypolicy\_complianceaction\_accuracyescalation\_accuracymean\_tool\_callsmean\_extra\_tool\_callsmean\_sync\_tokensmean\_total\_tokensmean\_cached\_tokensp50\_latency\_ssync\_cost\_per\_ticket\_usdbackground\_cost\_per\_ticket\_usdcost\_per\_ticket\_usdmonthly\_cost\_at\_100k\_tickets
0Bad baseline0.5110%60%70%5.02.411,93511,93504.88$0.03813$0.00000$0.03813$3,813
4Round 4: split workflow0.98100%100%100%2.60.02,4122,9741,7791.41$0.00194$0.00010$0.00204$204

建议的调优顺序

  1. 基线
  2. 提示/输出控制
  3. 工具控制
  4. 上下文卫生
  5. 模型路由
  6. 提示缓存
  7. 感知缓存的上下文
  8. 拆分工作流
  9. 处理层级

结论

支持智能体的成本优化,最好是一连串经过测量的小改动,而不是一次模型替换或提示重写。先建立基线,暴露 Token、工具调用、延迟、质量失败和花费去了哪里。然后收紧提示和输出控制,限制工具使用,减小工具载荷,修剪上下文,把简单工作路由到更小的模型,让稳定前缀对缓存友好,并把非面向客户的工作移出同步路径。

主要原则是把能力花在保护质量的地方。一个常规的订单状态问题、一个结构化分诊步骤、一个政策很重的退款争议,以及一个离线质量保证打标器,不应当使用相同的模型、上下文、工具或延迟层级。优化后的系统应当因为更有纪律而更便宜,而不是因为盲目去掉防护。

上线变更之前,用有代表性的评测和轨迹指标验证它们。跟踪质量分、政策合规、动作准确率、升级准确率、工具调用次数、Token 用量、缓存 Token 量、p50 和 p95 延迟、同步成本、异步后续成本和每张工单的总成本。只有在降低成本的同时守住支持质量线,一个配置才更好。

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

  • #质量评测
  • #护栏
  • #成本优化
  • #客服智能体
  • #上线验收