Open in
智能体步骤与交接
将智能体拆分为多个步骤,每个步骤都有各自的指令、工具和已收集的值,并可在通话过程中在智能体之间交接通话。
单个提示词会一次性将整个任务交给模型。步骤会一次处理通话的一个阶段:每个步骤都有自己的指令、自己的工具、可选的逐字朗读语句,以及在继续之前必须收集的值。转交可让您组织中的另一名智能体在通话过程中接手,并保留完整的通话记录,因此来电者无需重复说明。
每个智能体最初都是单个提示词。在您添加步骤或转交之前,它不会发生任何变化。
使用步骤还是单个提示词?
| 在以下情况下使用步骤 | 在以下情况下保留单个提示词 |
|---|---|
| 通话包含必须按顺序进行的阶段:先验证,再预约。 | 对话是开放式的:常见问题、前台接待、一般支持。 |
| 智能体必须先获取某个值才能继续。 | 无需按固定顺序收集任何内容。 |
| 某条规则绝不能变通,例如“未满 18 岁的来电者转给监护人”。 | 模型的判断足以处理每个分支。 |
| 必须在通话中的固定节点执行查询。 | 来电者提出请求时,随时都可以调用工具。 |
| 通话的一部分属于另一个智能体,且其拥有自己的提示词和语音。 | 一个角色处理所有事项。 |
步骤并不是脚本化的电话树。在一个步骤中,模型会像使用单个提示词时一样开展对话。步骤决定它当前处理的内容、可使用的工具,以及离开该步骤前需要获取的信息。
分步骤智能体的构建方式
当智能体包含步骤时,其 prompt 保存共享指令:适用于每个步骤的角色设定和规则。每个步骤会在此基础上添加自己的指令。每一轮中,模型都会看到共享指令、当前步骤的名称和指令,以及目前已收集的值。
在构建器中,将智能体从单个提示词切换为步骤。您会看到每个步骤对应一张卡片,其中包含名称、指令、朗读语句、工具、已收集的值和转换、转交选择器,以及步骤的只读流程图。切换回单个提示词时,系统会提供一个合并后的提示词,其中整合了共享指令和每个步骤。
通过 API,steps 和 handoffs 是
智能体上的字段。与所有智能体字段一样,PATCH 会将它们暂存到草稿中;POST /v1/agents/{agent_id}/deploy 会将它们发布上线。发送
"steps": null 会将智能体切换回单个提示词。测试通话和通话使用草稿。
步骤字段
| 字段 | 含义 |
|---|---|
id | 必填。小写字母、数字和下划线,以字母开头,最多 40 个字符。在智能体内必须唯一。 |
name | 必填显示名称,长度为 1 至 80 个字符。 |
instructions | 必填,最多 20,000 个字符。与 prompt 一样,支持 {{var}} 通话变量。 |
say | 可选。在进入步骤时逐字朗读的语句,最多 1,000 个字符。支持 {{var}}。 |
tools | null 会让该步骤使用附加到智能体的所有工具。列表会按名称将该步骤限制为仅使用其中的工具。end_call 和 transfer_call 等内置操作在每个步骤中都遵循智能体的设置。 |
collect | 该步骤收集的值,最多 20 个。每个值都有 name、type、description 和 required(默认为 true)。 |
on_enter_tool | 可选工具名称,在进入步骤时自动调用。请参阅 Webhook 步骤。 |
transitions | 步骤接下来可前往的位置的有序列表。请参阅转换。 |
global_when | 可选。使该步骤可从任意步骤到达。请参阅全局步骤。 |
voice | 此步骤的可选语音 ID。智能体使用自定义语音配置时会忽略该字段。 |
流程图本身为 {"start_step": "<step id>", "steps": [...]}。通话从 start_step 开始。
转移
每个转移都包含 to(一个步骤 ID),以及以下其中一项:
when:自然语言条件,由模型决定。智能体会获得一个内置的go_to_step工具,其中列出可跳转的步骤,每个when都是选择该步骤的原因。condition:针对已收集值的规则,由代码决定。模型无法跳过它,也无法通过对话绕过它。"always": true:无需检查,直接继续。代码会优先决定:一旦进入某个步骤,或收集到其必填值后,第一个匹配的condition或always转移就会生效,此时模型尚未能选择when。因此,请将always放在各项条件之后作为后备方案,或单独使用它将一个步骤直接串联到下一个步骤。包含always的步骤绝不会等待when选择。
condition 的格式为 {"value": <collected name or dotted path>, "op": <op>, "target": <literal>}:
op | 当值满足以下条件时为真 |
|---|---|
eq, ne | 等于或不等于 target |
lt, lte, gt, gte | 小于、不大于、大于或不小于 target |
in, not_in | 是或不是 target 列表中的某个值 |
exists, missing | 已收集或尚未收集(无需 target) |
eq、ne、in 和 not_in 会在去除空格后以不区分大小写的方式比较文本。数字按数值比较,日期按 YYYY-MM-DD 字符串比较。未曾收集的值会使除 missing 以外的所有操作符均为假。
将 when 和 global_when 写成条件,例如“来电者想预约”,而不要写成要说出的内容。智能体会将其视为内部路由备注,并被要求绝不朗读或提及正在切换步骤。
当模型调用 go_to_step 时:
- 步骤中的每个
required值都必须存在。若缺少某项,模型会获知缺少哪一项,并向来电者询问。 - 值会被存储。
- 会按顺序检查
condition和always转移。第一个匹配项生效。 - 否则,智能体会移动到模型选择的步骤。
- 若没有任何匹配项,且模型未指定步骤,智能体将停留在当前位置。
跳转到全局步骤是例外:来电者更改了话题,因此即使当前步骤仍缺少必填值,也会继续跳转,并且会在检查该步骤条件之前进行。
每次进入步骤时,也会检查 condition 和 always 转移,因此一连串由代码决定的步骤会一次性执行。链条会在以下情况停止:
- 到达本次运行中已访问过的步骤;
- 经过 8 次跳转后;
- 到达仍有必填值待收集的步骤,除非该步骤具有
on_enter_tool(webhook 步骤始终根据其结果进行路由)。
只会说出链条停止所在步骤的 say 行。
没有转移的步骤是终止步骤。智能体会在此完成流程,并使用其常规内置操作结束或转接通话。
收集值
在 collect 中列出某个步骤所需的内容。模型在调用 go_to_step 时传入这些值,它们会在通话剩余期间保持可用。
type | 可存储内容 |
|---|---|
string | 任意文本 |
number | 数字 |
boolean | true 或 false |
date | 格式为 YYYY-MM-DD 的日期 |
收集的值属于通话内容。它们会与转录文本一同显示在通话中,并遵循相同的保留、删除和脱敏规则。当智能体的通话录音关闭时,收集的值既不会被存储,也不会发送到 webhook;步骤时间线仍会保留。请参阅在通话中查看的位置。
说话内容
当智能体进入步骤时,say 中的内容会严格按原文说出。随后,模型会在同一回复中继续处理新步骤,并且知道这句话刚刚已经说过,因此不会重复。将 say 用于问候、必需的告知事项和固定措辞,其余回复交由模型处理。
起始步骤的 say 是智能体在任何同意声明之后、首次发言时的开场白。当来电者先开口时,它会作为智能体首次对真人作出的回复开头,绝不会对 IVR 或语音信箱问候语说出。
当通话通过 __previous__ 或交回返回某个步骤时,该步骤的 say 不会再次说出。请按实际应有的发音方式编写:将语音可能误读的内容拼写完整。说话内容支持 {{var}} 通话变量,但不支持已收集的值。
Webhook 步骤
将 on_enter_tool 设置为智能体的某个工具,步骤一进入便会立即运行,无需等待模型。其参数为名称与工具参数名称匹配的已收集值。结果会存储为名为 <tool>_result 的已收集值,并截断为 4 KB;系统会立即检查该步骤的 condition 和 always 转换,因此查询可以根据自身结果路由通话。
存储的值包含两个字段:
| 字段 | 内容 |
|---|---|
status | 您的端点返回的 HTTP 状态码。 |
response | 响应正文。 |
使用 lookup_account_result.response.<field> 根据正文中的字段路由,使用 lookup_account_result.status 根据状态码路由。
查询最多运行 8 秒,或者使用工具自身的超时时间(如果更短)。如果耗时超过 3 秒,智能体会说一句简短的话,避免来电者陷入静默。如果您的端点返回 400 或更高的状态码,或者未能及时响应,智能体会告知来电者目前无法完成该检查,并继续处理其能够完成的事项。
全局步骤
带有 global_when 的步骤可在满足该描述时从任何步骤进入。与 when 一样,由模型决定是否进入。将其用于来电者可能随时提出的话题:账单问题、请求人工服务、投诉。
全局步骤上带有 "to": "__previous__" 的转换会将通话返回到此前处于活动状态的步骤,让来电者从中断处继续。该步骤会恢复:其 on_enter_tool 不会再次运行,say 不会重复,智能体会继续执行该步骤,而不会重新开始。
交接
handoffs 列出您所在组织中可以接管通话的其他智能体:
[{"agent_id": 413, "when": "The caller asks about billing or a refund"}]智能体会获得内置的 hand_off 工具,其中包含这些智能体和对应原因。与转场一样,每个 when 都是内部路由说明,绝不会被读出来。
进行交接时,智能体会用自己的声音说一句简短的话,告知来电者将被转接到哪里,例如“当然,我会将您转接到我们的账单团队”。随后,目标智能体的提示词、步骤、工具和声音将接管,通话会在同一份记录中继续。新的智能体会简短地介绍自己并回应来电者的最后一个请求;不会宣布发生了转接。此后,目标智能体的知识库即可使用。
通话首次到达某个智能体时,会从该智能体的 start_step 开始:该步骤的 on_enter_tool 会运行,并朗读其 say。当通话交接回此前已访问过的智能体时,该智能体会从其之前所在的步骤继续,不会运行 on_enter_tool、重复其 say 或再次介绍自己。通话中收集的值由所有智能体共享。
- 目标必须是同一组织中的智能体,并且智能体不能将通话交接给自身。
- 所有通话级别的设置都保留在接听通话的智能体中:产品、语言、由谁先说话、语音信箱、同意声明及所有其他通话设置。
- 目标智能体可以继续交接,也可以交接回来。一通电话最多可到达 8 个智能体。
- 测试通话会运行您测试的智能体草稿版本。其交接到的智能体会运行各自已部署的版本。
- 如果两个智能体拥有同名但定义不同的工具,通话中只会保留一个,并且始终以接听通话的智能体为准。保存时,构建器会发出警告。
- 智能体可以在没有步骤的情况下设置交接。其完整提示词会按原样使用。
对于具有步骤或交接的智能体,go_to_step 和 hand_off 是保留名称,因此您自己的工具不能使用这些名称。
如何在通话中查看
在 通话记录 中打开该通话。记录旁边的步骤时间线会显示通话进入过的每个步骤及其原因,并同时列出收集的值。
通话对象 和通话完成 webhook(telephony.complete / web.complete)会以 steps_trace 和 collected_values 提供相同的数据。没有步骤的通话中,这两项均不存在。
"steps_trace": [
{"t_ms": 0, "agent": "a412", "step": "greet", "via": "start"},
{"t_ms": 51230, "agent": "a412", "step": "verify", "via": "model"},
{"t_ms": 98410, "agent": "a412", "step": "minor", "via": "condition"}
],
"collected_values": {"full_name": "Dana Reyes", "dob": "2010-04-02", "age": 15}agent 是 a 后跟智能体 ID。via 说明通话是如何转移的:
via | 含义 |
|---|---|
start | 通话从此步骤开始。 |
model | 模型通过 go_to_step、when 或 global_when 选择了该步骤。 |
condition | 匹配了 condition 或 always 转场。 |
on_enter | 根据 webhook 步骤的结果匹配了转场。 |
handoff | 另一个智能体接管了通话。 |
步骤的运行位置
步骤可原生运行于 Spark、Bolt 和所有 Storm 产品,适用于呼入、呼出和营销活动通话、测试通话,以及网页和小组件通话。
在其他所有位置,智能体仍可正常工作,只是没有这种结构:它会按顺序运行一个合并了共享指令和所有步骤的扁平化提示词。这包括:
限制
| 限制 | 值 |
|---|---|
| 每个智能体的步骤数 | 100 |
| 每个步骤收集的值数量 | 20 |
| 每个智能体的交接目标数 | 8 |
| 单次通话中可触达的智能体数 | 8 |
steps JSON 的大小 | 200 KB |
步骤 instructions | 20,000 个字符 |
say 行 | 1,000 个字符 |
存储的 on_enter_tool 结果 | 4 KB |
违反规则的保存会返回 400,并附带错误字段的路径,例如
steps.steps[2].transitions[0].to。tools 和
on_enter_tool 中的工具名称必须是已附加到该智能体的工具。
暂不支持按步骤设置语言、跳过模型的脚本化步骤,以及 say 行内的收集值。
导入的智能体
从 Retell、Bland、Vapi 或 ElevenLabs 导入 会将多提示词智能体、流程、路径和工作流导入为步骤,并将小队和智能体转接导入为已导入智能体之间的交接。导入前,审核页面会同时显示两者。
示例:牙科接诊
按顺序包含三个步骤:问候、收集患者信息、预约。
{
"prompt": "You are Ava, the front desk assistant at Acme Dental. Be warm and brief. Ask one question at a time.",
"steps": {
"start_step": "greet",
"steps": [
{
"id": "greet",
"name": "Greeting",
"instructions": "Greet the caller and ask how you can help.",
"say": "Thanks for calling Acme Dental, this is Ava. How can I help you today?",
"tools": null,
"collect": [],
"transitions": [
{"to": "details", "when": "The caller wants to book an appointment"}
]
},
{
"id": "details",
"name": "Patient details",
"instructions": "Ask for the caller's full name, date of birth and the reason for the visit. Read the date of birth back and ask the caller to confirm it.",
"tools": null,
"collect": [
{"name": "full_name", "type": "string", "description": "Caller's full name", "required": true},
{"name": "dob", "type": "date", "description": "Date of birth", "required": true},
{"name": "reason", "type": "string", "description": "Reason for the visit", "required": true},
{"name": "new_patient", "type": "boolean", "description": "First visit to the practice", "required": false}
],
"transitions": [
{"to": "book", "when": "The caller has confirmed their details"}
]
},
{
"id": "book",
"name": "Book",
"instructions": "Offer the two earliest open slots from book_slot and book the one the caller picks. Confirm the date and time, then say goodbye and end the call.",
"tools": ["book_slot"],
"collect": [],
"transitions": []
}
]
}
}使用 PATCH /v1/agents/{agent_id} 暂存,通过 通话 试用,然后部署。
在收集姓名、出生日期和就诊原因之前,details 步骤无法交接至预约;new_patient 为可选项。
示例:年龄限制分支
模型可以与任何人交谈,但由代码决定谁可以预约。收集年龄,然后在考虑模型自身选择之前,依据年龄进行路由。
{
"id": "verify",
"name": "Verify age",
"instructions": "Ask for the caller's age.",
"tools": null,
"collect": [
{"name": "age", "type": "number", "description": "Caller's age in years", "required": true}
],
"transitions": [
{"to": "guardian", "condition": {"value": "age", "op": "lt", "target": 18}},
{"to": "book", "when": "The caller has given their age"}
]
}无论模型如何决定,说自己十五岁的来电者都会进入 guardian。
其他所有人都会进入 book。两者都是图中其他位置的常规步骤。
示例:账户查询 webhook 步骤
来电者提供账户号码,进入下一步骤时会运行查询,其结果会决定路由。为简洁起见,省略了 not_found、collections 和 support 步骤。
[
{
"id": "identify",
"name": "Identify",
"instructions": "Ask for the caller's account number and read it back digit by digit.",
"tools": null,
"collect": [
{"name": "account_number", "type": "string", "description": "Account number", "required": true}
],
"transitions": [
{"to": "lookup", "when": "The caller has confirmed their account number"}
]
},
{
"id": "lookup",
"name": "Look up account",
"instructions": "Tell the caller what you found on their account and ask how you can help.",
"tools": ["lookup_account"],
"collect": [],
"on_enter_tool": "lookup_account",
"transitions": [
{"to": "not_found", "condition": {"value": "lookup_account_result.response.found", "op": "eq", "target": false}},
{"to": "collections", "condition": {"value": "lookup_account_result.response.account_status", "op": "in", "target": ["past_due", "suspended"]}},
{"to": "support", "when": "The caller needs help with an active account"}
]
}
]lookup_account 会接收 account_number,因为其参数使用了该名称。如果响应正文包含 "found": false,通话将转至 not_found。如果其 account_status 为 past_due 或 suspended,则转至 collections。有效账户会停留在 lookup,模型将在此提供帮助,然后转至 support。如需将查询失败发送至独立步骤,请先添加状态条件:错误响应会存储其状态码,因此 {"value": "lookup_account_result.status", "op": "gte", "target": 400} 可以捕获该情况;超时或无响应会存储 "error",因此 {"value": "lookup_account_result.status", "op": "eq", "target": "error"} 可以捕获该情况。
示例:前台转接至账单部门
两个智能体。前台(智能体 412)接听所有通话,并将账单相关问题转交给账单智能体(智能体 413),后者拥有自己的提示词、工具和语音。
在前台中:
{
"handoffs": [
{"agent_id": 413, "when": "The caller asks about a bill, a payment or a refund"}
]
}在账单智能体中,以便其可将来电者转回:
{
"handoffs": [
{"agent_id": 412, "when": "The billing question is resolved and the caller needs something else"}
]
}前台会说一句简短的话,例如“我帮您转接至账单团队”,账单智能体会进行自我介绍,并从前台结束的位置继续对话。账单部门转回后,前台会从原位置继续,无需再次向来电者问候。通话的产品、语言、同意声明及其他通话设置会保留在前台,因为前台接听了通话。
如果账单部门只需要自己的指令,而不需要独立的智能体,请改用前台中的全局步骤。问题得到解答后,通话会返回来电者之前所在的步骤:
{
"id": "billing",
"name": "Billing questions",
"instructions": "Answer questions about bills and payments. Offer to send a payment link by email.",
"tools": null,
"collect": [],
"transitions": [
{"to": "__previous__", "when": "The billing question is answered"}
],
"global_when": "The caller asks about a bill or payment at any point"
}