通过电话验证
当代理需要确认存储的组织信息是否仍然真实,并且通过拨打该组织公开的电话号码来确认是可行的方法时,请使用此技能。
verify-by-phone 是一个验证工作流技能。它针对每条记录仅发起一次公开的、事先说明身份的出站 CALL-E 通话,从转录中提取答案并附上逐字支持文本片段,然后使用透明的匹配权重算术将答案与存储记录进行比对,最终返回校准后的置信度(calibrated confidence)或明确的弃权(explicit abstention)。其设计目标是创建一个永远不会将不确定的电话回答转换为自信的数据库写入的代理。
何时使用
将此技能用于:
- 验证提供商目录列表:该诊所是否真实、可联系、接收新患者、接受特定保险计划
- 检查存储的商业记录(营业时间、服务、可用性)是否仍然与实际情况一致
- 刷新任何以电话为真相来源且记录可能过时的数据集
- 在工作流中必须如实记录“未知”结果,而非猜测的场景
- 一次处理一条记录,且拥有经人工授权的接收人列表
何时不使用
请勿将此技能用于:
- 进行未说明身份或虚假借口的通话;每次通话都会在开始时立即声明其是自动助手及其通话目的
- 拨打移动电话或个人号码;验证目标应为公开的组织电话线路
- 进行营销、销售、潜在客户开发或任何推广外呼
- 未经每条记录的人工授权就批量拨号列表
- 直接从通话结果覆盖存储记录;输出的是带有证据的裁决,写入决定由操作员负责
- 猜测受访者未提供的答案;如果通话未能获得可用答案,结果将是弃权,这才是正确的输出
同意与披露规则
这些规则是核心要求,并非套话:
- 通话开始时,必须在其他任何内容之前,同时声明 AI 身份和通话目的,并告知通话可能被录音。
- 如果受访者反对与自动来电者交谈,通话将致谢并立即结束。结果将把拒绝记录为无法验证的状态。
- 等待时间有上限。如果受访者要求来电者等待,它会短暂等待,然后返回报告,而不是无限期等待。
- 代理绝不会捏造其未被给予的信息,无论是在通话中还是结果中。具体来说:诊所经常会询问来电者姓名、出生日期、保险会员号或卡号,或者就诊原因。代理会明确说明它没有这些信息,因为这是目录验证通话而非预约请求,然后重复它拨打来询问的问题。占位即为捏造。
- 语音邮件不算答案。当接通语音邮件或自动应答机时,通话会立即结束而不留言:目录事实无法从录音中确定,也没人应该在自己的电话线上收到一条机器人留言。
- 通话是信息验证,绝非推广。假设每次通话都受到多方同意录音要求的约束。
验证工作流
- 首先,使用
scripts/calibrate.py在有标签的场景数据上校准弃权阈值,这样“自信”就有了可衡量的含义:在默认级别下,真实答案在至少 90% 的保留数据测试中落入预测集。它会打印出步骤 6 需要的qhat。此步骤无需凭证且不拨打电话,因此可以事先一次性完成。 - 收集要验证的记录:组织名称、E.164 格式的公开电话号码,以及需要检查的声称(例如,接收新患者,接受指定保险计划)。
- 确认操作员授权此次特定通话拨打此特定号码。
- 使用
scripts/place_verify_call.py构建通话任务。该脚本默认执行空运行,打印出确切的任务和接收人,不实际拨号;仅在空运行结果正确后才传递--live参数。 - 使用
scripts/poll_result.py轮询最终结果,该脚本将完整的结果载荷保存到本地文件。 - 使用
scripts/extract_answer.py --qhat(使用步骤 1 中的值)提取答案。每个提取的字段都包含其支持的逐字转录文本片段和字符偏移量。模棱两可的回答(“我觉得是”)会以其降低后的信任分数保留其倾向。非回应性的对话轮次(错号、拒绝、“稍后再打”)绝不算作答案。仅当校准后的预测集是单一值且该值不为“unknown”时,才会提供答案;否则结果为弃权。省略--qhat会导致对所有内容弃权,并将输出标记为uncalibrated,因为没有校准保证的阈值毫无意义。 - 使用
scripts/reconcile_record.py与存储记录进行对账:每个匹配的字段增加记录的证据量,每个不匹配的字段减少证据量,最终裁决为已验证(verified)、已矛盾(contradicted)或无法验证(unverifiable)。
维护说明
此技能中的脚本是 Attest 后端(源仓库中的 backend/app)参考实现的自包含副本。它们特意保持较小规模,以便技能安装时无需依赖该仓库。
这种副本关系是强制性的,而非承诺性的。源仓库中的 tests/test_skill_parity.py 测试会在相同的转录文本上运行此技能的提取器和后端的提取器,如果它们在答案、引用文本片段的字符偏移量或关键词语索引本上存在分歧,测试将失败。此说明的早期版本曾要求人工手动重新同步,但副本仍然发生了漂移:当后端学会了信任对话轮次中的最后一个线索并将“没问题”解读为同意时,此脚本仍然信任第一个线索,因此一句简单的“不,我们不是”在此处被弃权,而在后端则被回答。
快速开始
pip install calle-ai
# 1. 首先校准门控。打印出步骤 5 需要的 qhat。
python3 scripts/calibrate.py --data references/sample-scenarios.jsonl --alpha 0.1
# 2. 空运行:打印任务和掩码后的接收人,不拨号。
python3 scripts/place_verify_call.py \
--org "Example Counseling Center" \
--phone "+15550101234" \
--claim-accepting-new-patients yes \
--claim-plan "Example Health PPO"
# 3. 实际拨打电话(需要 CALLE_API_KEY)。
python3 scripts/place_verify_call.py ... --live
# 4. 等待最终结果载荷。
python3 scripts/poll_result.py --call-id call_abc123 --out result.json
# 5. 提取基于文本片段的答案,并通过校准阈值进行门控。
python3 scripts/extract_answer.py --payload result.json --qhat 0.750
# 6. 与存储记录进行对账。
python3 scripts/reconcile_record.py --payload result.json \
--claim-accepting-new-patients yes --claim-plan-accepted yes
此技能中的所有示例号码均为保留的虚构号码。空运行路径和捆绑的场景数据意味着除步骤 3 外,所有操作均无需凭证和真实通话。
输出示例
extract_answer.py 为每个声称输出一个 JSON 对象:
{
"claim": "accepting_new_patients",
"answer": "yes",
"trust_score": 0.9,
"hedged": false,
"span": {"turn": 6, "text": "Yep.", "char_start": 0, "char_end": 3},
"abstain": false,
"gate": "conformal(qhat=0.750)"
}
弃权结果会保持相同的结构,但 "abstain": true,原因可能是校准后的预测集中包含多个标签,或者提取器根本未找到任何答案("answer": "unknown")。gate 字段标明了做出决策的阈值,当未提供 --qhat 时显示为 uncalibrated,这是唯一一种无论说了什么,每个声称都会弃权的情况。reconcile_record.py 会添加匹配权重算术和最终裁决。没有任何字段会出现在没有支持文本片段或明确弃权的情况下。
副作用与取消
- 副作用:每次
--live调用会发起一个出站电话呼叫,拨打操作员提供的号码。没有递归操作;此技能中没有调度器。 - 成本:每次实际运行会产生一次 CALL-E 通话费用。空运行是免费的。
- 取消:CALL-E 不提供通话取消功能,因此停止的时机是在
--live之前。空运行默认值正是为此目的而存在。 - 数据:结果载荷写入操作员命名的本地文件。电话号码在控制台输出中被掩码处理。此技能中的任何内容都不会将结果传输到 CALL-E API 本身之外的任何地方。
参考
references/api-notes.md:此技能依赖的经验观察到的 CALL-E 载荷结构和 API 行为,包括通过测试而非文档发现的事实。references/verification-protocol.md:完整的披露脚本、出站验证通话的法律立场,以及为何弃权是核心设计决策。references/sample-scenarios.jsonl:标记的虚构场景数据,供scripts/calibrate.py使用,以便校准过程无需凭证和通话即可运行。