语音Agent工具参数怎样设计才省交互
作者结合九个客户语音Agent的配置,讨论工具命名、参数数量、输入校验和返回内容的设计。实时电话场景中,参数需要从对话获取,接口设计因此直接影响追问次数与等待体验。
AI 深度解读
给语音 Agent 接预约接口,最容易忽略的成本,是每个参数都可能增加一次追问、一段等待,或一次模型猜测。作者回看自己基于 Retell AI、n8n 和 GoHighLevel 搭建的 9 份客户 Agent 导出配置,得出的结论是:工具应该只接收对话能提供的信息,其余交给后端。
这篇值得程序员读,不只是因为它讲语音预约。它把工具调用里几件容易混在一起的事拆开了:模型负责理解什么,代码负责计算什么,以及接口失败后,用户究竟会听到什么。
参数越齐全,模型就越容易办好事吗
Retell 的 custom function 包含四部分:名称、描述、参数的 JSON Schema 和 URL。通话中,模型根据对话决定调用哪个工具并填写参数,Retell 向对应 URL 发起 POST,返回内容进入模型上下文,模型再继续说话。
可以把它理解成一次远程过程调用(RPC),只是调用方变成了边听电话边提取信息的 LLM。
作者的 9 份配置中,有 5 份通过 get_slots_from_ghl 和 create_appointment_using_ghl 完成预约。预约工具有 8 个参数,其中 5 个必填。这段定义展示了模型承担了哪些工作:
{
"required": ["email", "first_name", "phone", "start_time", "end_time"],
"properties": {
"start_time": { "type": "string", "description": "Appointment start time in ISO 8601 format (e.g. 2026-04-15T14:00:00-07:00)" },
"end_time": { "type": "string", "description": "Appointment end time in ISO 8601 format" },
"phone": { "type": "string", "description": "Contact's phone in E.164 format (e.g. +17025551234)" },
"calendarId": { "type": "string", "const": "REPLACE_WITH_CALENDAR_ID" },
"locationId": { "type": "string", "const": "REPLACE_WITH_LOCATION_ID" },
"title": { "type": "string", "const": "Free Legal Consultation" },
"first_name": { "type": "string" },
"email": { "type": "string" }
}
}
要看的是 calendarId、locationId、end_time 和格式要求。模型不仅要理解「周四两点左右」,还要生成带 UTC 偏移的 ISO 8601 时间、计算结束时间,把口述号码整理成 E.164 格式。查询时段的工具还要求 IANA 时区,并特别告诫模型不要使用 PST、EST 这样的缩写。
这些任务并非都需要语言模型。日历归属是配置,结束时间是计算,电话号码格式化是解析。把它们暴露成工具参数,就把确定性的工作塞进了生成过程。
另外 4 份配置采用 checkCalendar、bookCalendar 和 getWeekAvailability。其中,屋顶维修业务的预约定义缩减成了这样:
{
"required": ["name", "reason", "requestedTime"],
"properties": {
"name": { "type": "string", "description": "Caller's full name" },
"reason": { "type": "string", "description": "Type of roofing service the caller needs (e.g. roof repair, full replacement, storm damage inspection, gutter installation)" },
"requestedTime": { "type": "string", "description": "Confirmed inspection time in format 2026-04-15T09:00:00 (Eastern time, no offset suffix)" }
}
}
这里要看 requestedTime:模型仍需整理日期时间,但只提交企业所在 Eastern 时区的本地时间,不附偏移。后端负责日历、预约时长和当天的 UTC 偏移。getWeekAvailability 更进一步,完全不接收参数。
能由服务端确定的信息,就别让模型从对话里生成。
租户、日历、时长和时区,可以根据 Agent 或被拨打的号码在后端解析。作者所谓「每个必填参数都是一个问题」,更适合作为设计检查:这个值究竟来自用户表达,还是服务端本来就知道?
描述要交代调用条件,返回值要能直接说出口
作者对比了两种查询工具描述。
第一种只写「检查 GHL 日历中的免费咨询时段」。模型知道它是干什么的,却未必知道何时使用。
第二种则明确要求:在来电者说出具体日期和时间后,检查该时段;requestedTime 使用 2026-04-15T09:00:00,按 Eastern 时间填写,不带偏移。预约工具还补了一条前置条件:用户确认具体时间后才能调用。
差别在于,描述从功能标签变成了操作说明。它应回答三个问题:什么时候调用、传什么、调用前必须已经确认什么。全局提示词也要一起检查,否则工具描述要求先确认,全局规则却催促立即预约,模型只能在冲突中选择。
返回值同样影响后续行为。作者的 n8n 测试工作流会返回自然语言,甚至包含指令:每天最多读出 2 个可用时段,或要求只向来电者提供两个选项。
这个观察很实用,但实现不能停在提示词上。
如果最多展示两个选项,后端就应该只返回两个。与其把几十个 ISO 时间戳交给模型,不如由后端整理成「周二上午 10 点和下午 2 点」。结果越接近最终口播,模型临场处理的步骤越少。
失败也要有可说出口的结果,例如「暂时无法连接日历」并附下一步处理方式。原文没有给出统一的错误响应结构,但明确反对把堆栈信息直接交给模型处理。
只有工具返回了预约确认,Agent 才能告诉用户预约成功。
这条规则应该贯穿描述、后端结果和对话行为。用户听到肯定答复后会据此行动,不能让模型拿一句流畅的安慰代替真实状态。
电话里最难忍受的故障,可能是一直没有声音
作者发现,全部 9 份导出配置中的自定义工具,都沿用了两个设置:speak_during_execution=false,以及 timeout_ms=120000。
也就是工具执行时不说话,超时上限为 120 秒。后台任务可以等两分钟,电话另一端的人很难一直等下去。
作者现在建议,对真实 I/O 开启 speak_during_execution,先告诉用户「我帮您查一下」。同时缩短 Retell 侧超时,并在工作流内部设置更短的超时,让后端有机会先返回一句可理解的失败说明。原文没有给出应该缩短到多少毫秒,不能替它补一个通用答案。
作者还建议每个工具只做一次查询,并缓存通话期间不会变化的服务区域、营业时间等数据。
验证方式则分三层:直接用周末日期、畸形时间等参数调用接口;用脚本化通话检查应该调用和不应该调用工具的场景;主动破坏后端,听 Agent 如何回应。
其中「不应该调用」尤其值得保留。接口测试只能证明请求进来后如何处理,无法证明模型是否在用户还没确认时就发起了预约。
我的判断
我赞同作者对职责的划分:固定配置、格式解析和时间计算,应尽量交给可验证的代码。它能减少模型的决策负担,也方便定位错误发生在哪一层。
但这仍是一份基于个人项目配置的经验总结。9 份导出文件、5 份旧形态和 4 份新形态,不能证明参数减少后成功率提升了多少。作者没有提供预约准确率、通话时长或失败率的对照数据,超时调整也属于正在推进的改进。
单一时区还有明确前提。屋顶维修业务只在一个业务时区内工作,提交本地时间比较自然;如果业务跨地区,或者涉及夏令时切换时的重复、缺失时刻,后端仍需明确处理歧义。原文没有展开这些情况。
此外,const 不宜笼统理解成「提示」。它在 JSON Schema 中表达固定值约束,但实际系统是否强制校验,原文没说清。即便校验有效,服务端自行解析日历归属,通常也更容易管理。
工具描述值得认真写,前置条件却不能只靠文字守住。与常规 API 一样,后端仍应校验可预约时段、必要信息和提交条件。描述帮助模型选对动作,代码负责拒绝不成立的请求。
能拿来做什么
- 给现有工具的参数逐个标注来源:用户表达、已有上下文或服务端配置。若日历 ID、固定时长仍由模型填写,可以优先移到 Node 后端或工作流配置中。
- 把工具描述改成明确的调用说明,写清触发条件、参数格式和确认要求,再补上「用户尚未确认时间」这类不应触发预约的测试。
- 为查询结果和失败结果准备口播文本,由后端限制选项数量、处理时间显示,并确保预约成功回复依赖真实确认。
- 检查一次超时链路,让工作流内部先超时、先返回可理解的结果,再测试断网、日历不可用时用户实际听到了什么。