短信缺乏撤回机制,AI Agent 的错误(错号码、模板变量破损、草稿泄露)会立即变成客户事件;论证在 API 调用前加人工审批关卡的必要性。
一个能够给客户发短信的 AI Agent,也可能在凌晨两点把短信发给错误的客户,而且里面还带着失效的合并字段。只要在任何 SMS 发出前加入人工审批门禁,就能在发送前发现问题,而不是事后补救。
Email 还有一段可以在对方阅读前删除的缓冲期,SMS 没有。它通常会在几秒内抵达手机并显示为通知——没有草稿箱,没有“撤回消息”,也没有垃圾邮件过滤器悄悄吞掉这个错误。如果你的 Agent 会起草预约提醒、物流更新或重新唤回用户的短信,那么一次糟糕的运行——从过期记录中取出了错误的电话号码、模板变量失效并被渲染成 {{first_name}}、原本用于内部 QA 的消息发给了真实客户——都会立刻演变成直接影响客户的事故。
Twilio、Vonage 以及类似的服务提供商,会毫不犹豫地发送你交给它们的任何字符串——它们不知道内容是否合理。这项检查必须发生在 API 调用之前,而且必须由人来完成,不能只是再写一个 prompt,让模型“自行复查”。
假设你为一家牙科诊所运行一个预约调度 Agent。它会读取明天的预约列表,为每位患者起草一条提醒短信,过去会直接调用 Twilio API。现在,它会先把每条草稿推送到 Impri,只有在前台接待人员批准后才发送:
import os
import time
import requests
IMPRI_KEY = os.environ["IMPRI_API_KEY"]
BASE = "https://api.impri.dev"
HEADERS = {"Authorization": f"Bearer {IMPRI_KEY}", "Content-Type": "application/json"}
def push_sms_for_approval(to_number, body):
resp = requests.post(f"{BASE}/v1/actions", headers=HEADERS, json={
"kind": "sms.send",
"title": f"Reminder SMS to {to_number}",
"preview": {"format": "plain", "body": body},
"expires_in": 3600, # stale reminder isn't worth sending after an hour
"editable": ["preview.body"],
"idempotent": False, # resending would double-text the patient
})
resp.raise_for_status()
return resp.json()["id"]
def wait_for_decision(action_id, poll_every=5):
while True:
resp = requests.get(f"{BASE}/v1/actions/{action_id}", headers=HEADERS)
data = resp.json()
if data["status"] != "pending":
return data
time.sleep(poll_every)
action_id = push_sms_for_approval("+15551234567", "Hi Sam, reminder: your cleaning is tomorrow at 2pm.")
decision = wait_for_decision(action_id)
if decision["status"] == "approved":
final_body = decision["decision"]["final_preview"]["body"]
# send via Twilio only after approval
twilio_client.messages.create(to="+15551234567", from_=CLINIC_NUMBER, body=final_body)
requests.post(f"{BASE}/v1/actions/{action_id}/result", headers=HEADERS,
json={"status": "executed"})
Twilio 调用只存在于 if approved 分支中,在代码库的其他任何地方都不存在——这才让它成为真正的审批门禁,而不是流于形式。如果 Agent、程序 Bug,或来自某个上游数据源的 prompt 注入指令,试图绕过审批直接调用 twilio_client.messages.create(...),它仍然需要 API key 和封装层,而这两者在该分支之外都无法访问。
设置 editable: ["preview.body"] 意味着收件箱中的审批卡片不只是提供批准或拒绝按钮——前台接待人员还可以在发送前修正错别字、调整语气或更正姓名,Impri 会通过 decision.final_preview.body 返回编辑后的文本。始终发送这个字段,绝不要发送原始的 preview.body;如果人工修改过消息,就不应该再把原始版本发出去。
这一点对于 SMS 来说比大多数渠道都更重要,因为你没有第二次澄清的机会。一封措辞别扭的 Email 还可以再发一封跟进邮件解释。但如果短信因为某个被自动纠正的单词,把“你的预约已确认”写成了“你的预约已取消”,困惑的患者就会直接打电话过来。
如果你的 Agent 在一次运行中处理全天的预约列表,它可能会在短时间内集中推送 40~50 个 action。POST /v1/actions 对每个 key 的限制是每分钟 60 次请求,足以轻松覆盖一家诊所的日常业务量——但如果你要把它用于规模更大的机构,例如连锁药店或拥有多个营业点的诊所,就应该错开推送时间,或者为每个营业点使用单独的 key,而不是用同一个 key 持续发起请求直至超过限制。
Impri 会保存起草好的消息、通知审核人员并等待审批决定——它不会读取消息内容,不知道怎样的措辞才符合你们诊所的语气,也不会代替你与 Twilio 通信。发送 SMS 仍然由你的 Agent 负责;Impri 负责确保在发送前确实有人批准。至于另外两种集成方式——使用 MCP tool call 代替原始 HTTP,以及通过配置 executor,确保在未经批准时确实无法访问凭证——请参阅主集成指南。
如果你希望审核人员直接从 Slack 批准,而不是使用 Web 收件箱,请参阅 Slack 审批。第一次使用 Impri?请从快速入门开始。
对于进一步的操作,你可以考虑屏蔽此人和/或举报滥用行为。