优化客服智能体的成本与质量(下)
说明上线后应按每次经核验解决的完整成本验收,而不是只看 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\_label | pass\_rate | failures | |
|---|---|---|---|
| 0 | Bad baseline | 0% | 10 |
| 1 | Round 1: controls | 100% | 0 |
| 2 | Round 2: routing | 100% | 0 |
| 3 | Round 3: caching | 100% | 0 |
| 4 | Round 4: split workflow | 100% | 0 |
| variant\_label | ticket\_id | failures | passed | |
|---|---|---|---|---|
| 0 | Bad baseline | T-001 | too\_many\_unnecessary\_tools, missing\_required\_r... | False |
| 1 | Bad baseline | T-002 | too\_many\_unnecessary\_tools, policy\_or\_action\_m... | False |
| 2 | Bad baseline | T-003 | missing\_required\_response\_content, policy\_or\_a... | False |
| 3 | Bad baseline | T-004 | customer\_answer\_too\_long | False |
| 4 | Bad baseline | T-005 | missing\_required\_response\_content, policy\_or\_a... | False |
| 5 | Bad baseline | T-006 | missing\_required\_response\_content, policy\_or\_a... | False |
| 6 | Bad baseline | T-007 | too\_many\_unnecessary\_tools, missing\_required\_r... | False |
| 7 | Bad baseline | T-008 | missing\_required\_response\_content, policy\_or\_a... | False |
| 8 | Bad baseline | T-009 | missing\_required\_response\_content, policy\_or\_a... | False |
| 9 | Bad baseline | T-010 | missing\_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\_label | tickets | deterministic\_pass\_rate | judge\_graded | judge\_coverage | judge\_errors | judge\_pass\_rate | both\_pass\_rate | known\_judge\_cost\_usd | judge\_cost\_unavailable | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Bad baseline | 10 | 0% | 10 | 100% | 0 | 0% | 0% | $0.05637 | 0 |
| 1 | Round 1: controls | 10 | 100% | 10 | 100% | 0 | 80% | 80% | $0.01252 | 0 |
| 2 | Round 2: routing | 10 | 100% | 10 | 100% | 0 | 70% | 70% | $0.01350 | 0 |
| 3 | Round 3: caching | 10 | 100% | 10 | 100% | 0 | 90% | 90% | $0.01279 | 0 |
| 4 | Round 4: split workflow | 10 | 100% | 10 | 100% | 0 | 80% | 80% | $0.01285 | 0 |
| variant\_label | ticket\_id | deterministic\_passed | passed | reason | |
|---|---|---|---|---|---|
| 0 | Bad baseline | T-001 | False | False | The answer does not give the customer the actu... |
| 1 | Bad baseline | T-002 | False | False | The reply does not follow the policy: it shoul... |
| 2 | Bad baseline | T-003 | False | False | The answer is not grounded in the evidence: th... |
| 3 | Bad baseline | T-004 | False | False | The reply does not clearly tell the customer t... |
| 4 | Bad baseline | T-005 | False | False | It does not give the customer the needed accou... |
| 5 | Bad baseline | T-006 | False | False | It does not clearly tell the customer that O-1... |
| 6 | Bad baseline | T-007 | False | False | The reply does not give the customer-facing ne... |
| 7 | Bad baseline | T-008 | False | False | The answer is not grounded in the evidence: it... |
| 8 | Bad baseline | T-009 | False | False | The answer is vague and overly internal. It do... |
| 9 | Bad baseline | T-010 | False | False | It follows the policy direction to escalate, b... |
| 12 | Round 1: controls | T-003 | True | False | The case opening is supported, but the answer ... |
| 19 | Round 1: controls | T-010 | True | False | It correctly says identity must be verified be... |
| 22 | Round 2: routing | T-003 | True | False | The answer overstates the outcome: it only sho... |
| 24 | Round 2: routing | T-005 | True | False | The reply correctly says account details can’t... |
| 29 | Round 2: routing | T-010 | True | False | The reply gives the right general guidance, bu... |
| 39 | Round 3: caching | T-010 | True | False | The reply gives the right general guidance, bu... |
| 44 | Round 4: split workflow | T-005 | True | False | The reply is grounded on the identity check an... |
| 49 | Round 4: split workflow | T-010 | True | False | The 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\_id | variant\_label | intent | risk | model | tools | action | policy\_compliant | sync\_tokens | total\_tokens | latency\_s | cost\_usd | quality\_score | response\_preview | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | T-002 | Bad baseline | damaged\_delivery | medium | gpt-5.4 | lookup\_customer, lookup\_order, lookup\_policy, create\_refund\_case, escalate\_to\_human | open\_replacement\_without\_photo | False | 12917 | 12917 | 4.88 | $0.04034 | 0.65 | I 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 |
| 3 | T-004 | Bad baseline | billing\_issue | medium | gpt-5.4 | lookup\_customer, lookup\_order, lookup\_policy, create\_refund\_case, escalate\_to\_human | escalate\_billing\_review | True | 12830 | 12830 | 4.87 | $0.04038 | 0.85 | I 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 |
| 7 | T-008 | Bad baseline | high\_value\_damage | high | gpt-5.4 | lookup\_customer, lookup\_order, lookup\_policy, create\_refund\_case, escalate\_to\_human | promise\_refund\_high\_value\_damage | False | 12997 | 12997 | 4.91 | $0.04136 | 0.22 | I 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 |
| 41 | T-002 | Round 4: split workflow | damaged\_delivery | medium | gpt-5.4-mini | lookup\_order, lookup\_policy | request\_photo\_then\_offer\_replacement | True | 2381 | 2940 | 1.26 | $0.00097 | 0.98 | I 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. |
| 43 | T-004 | Round 4: split workflow | billing\_issue | medium | gpt-5.4-mini | lookup\_order, lookup\_policy, escalate\_to\_human | escalate\_billing\_review | True | 2401 | 2942 | 1.41 | $0.00103 | 0.98 | I found a duplicate-charge signal and sent this to billing review. The billing team will verify the charge before any refund is confirmed. |
| 47 | T-008 | Round 4: split workflow | high\_value\_damage | high | gpt-5.4 | lookup\_order, lookup\_policy, escalate\_to\_human | escalate\_high\_value\_damage | True | 2492 | 3063 | 2.03 | $0.00381 | 0.99 | I 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 | |
|---|---|
| variant | sync\_tokens\_saved |
| --- | --- |
| ticket\_id | |
| --- | --- |
| T-002 | 10,536 |
| T-004 | 10,429 |
| T-008 | 10,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\_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 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Bad baseline | 0.51 | 10% | 60% | 70% | 5.0 | 2.4 | 11,935 | 11,935 | 0 | 4.88 | $0.03813 | $0.00000 | $0.03813 | $3,813 |
| 4 | Round 4: split workflow | 0.98 | 100% | 100% | 100% | 2.6 | 0.0 | 2,412 | 2,974 | 1,779 | 1.41 | $0.00194 | $0.00010 | $0.00204 | $204 |
建议的调优顺序
- 基线
- 提示/输出控制
- 工具控制
- 上下文卫生
- 模型路由
- 提示缓存
- 感知缓存的上下文
- 拆分工作流
- 处理层级
结论
支持智能体的成本优化,最好是一连串经过测量的小改动,而不是一次模型替换或提示重写。先建立基线,暴露 Token、工具调用、延迟、质量失败和花费去了哪里。然后收紧提示和输出控制,限制工具使用,减小工具载荷,修剪上下文,把简单工作路由到更小的模型,让稳定前缀对缓存友好,并把非面向客户的工作移出同步路径。
主要原则是把能力花在保护质量的地方。一个常规的订单状态问题、一个结构化分诊步骤、一个政策很重的退款争议,以及一个离线质量保证打标器,不应当使用相同的模型、上下文、工具或延迟层级。优化后的系统应当因为更有纪律而更便宜,而不是因为盲目去掉防护。
上线变更之前,用有代表性的评测和轨迹指标验证它们。跟踪质量分、政策合规、动作准确率、升级准确率、工具调用次数、Token 用量、缓存 Token 量、p50 和 p95 延迟、同步成本、异步后续成本和每张工单的总成本。只有在降低成本的同时守住支持质量线,一个配置才更好。
PRIMARY_SOURCE: https://developers.openai.com/cookbook/examples/agent_optimization/optimizing_agents_for_cost_and_quality
- #质量评测
- #护栏
- #成本优化
- #客服智能体
- #上线验收


