用类型化记录搭建 Python 通话质检
教程先用 JSON Schema 定义情绪、合规和升级原因等字段,再将版本化模板绑定到 AI 助手。通话分析结果经过验证后进入质检评分卡,完整示例收录于 Telnyx 代码仓库。
完整中文译文
自由文本通话摘要适合人读,却很难直接拿来统计:客户情绪如何、哪些披露缺失、为什么要求升级处理,每次都要再解析一遍。作者用 Python 和 Telnyx 搭了一条质检流水线,先定义数据结构,再让 AI 分析通话,最后只汇总通过校验的结果。
这篇值得看的不是摘要提示词,而是如何把 AI 输出接进业务系统。它把配置核验、异步结果获取和无效数据隔离串了起来。不过,结构符合约定,并不代表判断符合事实。
先规定字段,才能稳定地做统计
同样是客户要求找主管,模型可能写成「要求经理介入」「请求升级」「想找负责人」。人能看懂,看板却无法直接按同一个值分组。
作者先定义 JSON Schema,也就是字段、类型和取值范围的约定,再创建三个带版本的分析模板:
sentiment-v1返回积极程度、挫败感、整体情绪和关键情感。compliance-v1返回身份核验结果,以及限定范围内的合规标记。escalation-v1返回受控的升级原因,以及客服是否处理得当。
下面的 Schema 解决升级原因无法统一归类的问题:
ESCALATION_SCHEMA = {
"type": "object",
"properties": {
"escalation_reason": {
"type": "string",
"enum": [
"caller_request",
"compliance_violation",
"poor_call_quality",
"system_error",
"other",
],
},
"agent_performed_well": {"type": "boolean"},
},
"required": ["escalation_reason", "agent_performed_well"],
"additionalProperties": False,
}
要看最后两行:两个字段都必须存在,也不允许额外字段。enum 则让升级原因只能从五个值中选择。
这里也暴露了一个缺口。模板指令要求判断是否发生升级,但 Schema 没有单独的「是否升级」字段,枚举也没有明确的「未升级」。没有发生升级的通话该怎么表示,原文没说清。直接把它归进 other,可能混淆「未升级」和「原因未知」。
创建模板时,create_templates.py 向 POST /ai/conversations/insights 发送请求,样例通过 json.dumps(ESCALATION_SCHEMA) 把 json_schema 转为 JSON 字符串,使用 timeout=30。API 也接受 Schema 对象,这是请求表示方式的差异。
请求成功以后,还要确认配置真的生效
三个模板被放进 qa-scorecard-v1 分析组,再通过 Assistant 的 insight_settings.insight_group_id 绑定。后续通话便可以统一使用这组分析约定,避免逐个维护模板绑定。
作者没有停在「接口返回成功」。
create_group.py 查找或创建分组、分配模板,然后重新读取成员列表。只要缺少任何预期模板,脚本就退出。wire_assistant.py 通过 POST /ai/assistants/{assistant_id} 更新配置,同样使用 timeout=30,随后发起 GET,核对返回的 insight_group_id。
这类写后读取检查,验证的是实际保存的配置。对依赖远端状态的自动化脚本,它比只检查请求状态更有用。
运行前需要克隆 team-telnyx/telnyx-code-examples 仓库,进入 telnyx-code-examples/insights-pipeline,用 python -m venv .venv 创建环境,执行 source .venv/bin/activate、pip install -r requirements.txt 和 cp .env.example .env。
环境配置包含 TELNYX_API_KEY、使用 E.164 格式的 ASSISTANT_PHONE_NUMBER 与 DESTINATION_NUMBER,以及可选的 ASSISTANT_ID、PUBLIC_WEBHOOK_BASE_URL。未设置 ASSISTANT_ID 时,脚本会用平台默认的模型和语音创建 Assistant;原文没有指定具体型号。
下面这组命令完成模板配置、Assistant 绑定、测试呼叫和按日期统计:
python create_templates.py
python create_group.py
python wire_assistant.py
python place_call.py
python scorecard.py --date YYYY-MM-DD
需要特别检查 place_call.py 执行前的 DEMO_MODE:默认 true 只打印计划事件载荷,不拨打真实号码,也不写入虚假的 conversation ID。准备真实测试时才设为 false。API 密钥应保存在环境变量或密钥管理器中。
分析没完成,和分析结果不合法,是两回事
通话结束后,分析结果未必立即可用。样例提供两条发现完成结果的路径:call.conversation.insights_generated webhook 用作实时完成信号,GET /ai/conversations/{conversation_id}/conversations-insights 用作轮询查询的事实来源。即使 webhook 接收器没启动,轮询也能完成流程。
可选接收器通过 python webhook_receiver.py 启动。它要求提供 TELNYX_PUBLIC_KEY,使用 Ed25519 验证签名;签名数据缺失或无效时返回 400,没有跳过验证的降级路径。
拿到结果以后,scorecard.py 先把模板 ID 映射回名称,再解析并校验必填字段和枚举值。只有已经完成、且校验通过的记录才参与统计。完成但不合法的结果会连同 conversation ID 打印出来,并被排除。
尚未就绪是流程状态,结构无效才是数据问题。
可以执行 python scorecard.py --strict,注入一条缺少 positivity_score 的模拟情绪记录,检查无效数据是否被挡住。也可以用 python scorecard.py --conversation-id <CONVERSATION_ID> 绕过本地通话记录,单独查询一段通话。
原文明示的汇总包括情绪分布和合规标记数量,但汇总列表并不完整,也没有给出完整的轮询状态规则,不能据此补出更多实现细节。
我的判断
我赞同把数据约定放在分析之前。相比「先生成摘要,再用另一个步骤抽字段」,这种方式让下游看板获得更明确的输入边界,也减少了自由措辞带来的归类麻烦。
但它解决的是输出结构的可用性。模型可能选择了合法的合规标记,却误判了通话;也可能正确输出布尔值,却没有可靠依据。原文没有提供人工标注对照、准确率、成本或耗时数据,因此不能把这个教程当成质检效果已经验证的方案。
分组回读和无效记录隔离都值得借鉴,但生产环境还需要保存原始结果、模板版本与处理状态。作者也明确建议用持久化存储替换本地 conversations.json,把无效记录送入运营复核队列。
版本名称同样只是起点。v1 能标识约定,却不会自动解决新旧字段如何兼容、历史统计能否比较的问题。尤其是升级与合规判断,应该先检查业务分类是否完整,再要求模型严格遵守。
能拿来做什么
- 给通话质检看板定义输入结构时,先列出需要统计和筛选的字段,再设计枚举;同时明确「未发生」「未知」和「其他」分别怎么表达。
- 用 Node 接收分析 webhook 时,可以沿用签名验证、轮询补查、结果校验的流程,并把「等待分析」和「结果无效」显示为不同状态。
- 把
python scorecard.py --strict纳入样例验收,确认缺字段的记录不会进入总数;再用人工核对的通话检查分类是否正确。 - 上线前替换
conversations.json,把模板版本与结果一起保存,并为被排除的数据安排复核入口,避免看板只剩下一个无法解释的数字。