ThunderPhone 2.0 正式上线。全程自助,低至2美分/分钟。查看发布公告

Using the dashboard

智能体步骤与交接

将智能体拆分为多个步骤,每个步骤都有各自的指令、工具和已收集的值,并可在通话过程中在智能体之间交接通话。

单个提示词会一次性将整个任务交给模型。步骤会一次处理通话的一个阶段:每个步骤都有自己的指令、自己的工具、可选的逐字朗读语句,以及在继续之前必须收集的值。转交可让您组织中的另一名智能体在通话过程中接手,并保留完整的通话记录,因此来电者无需重复说明。

每个智能体最初都是单个提示词。在您添加步骤或转交之前,它不会发生任何变化。

使用步骤还是单个提示词?

在以下情况下使用步骤在以下情况下保留单个提示词
通话包含必须按顺序进行的阶段:先验证,再预约。对话是开放式的:常见问题、前台接待、一般支持。
智能体必须先获取某个值才能继续。无需按固定顺序收集任何内容。
某条规则绝不能变通,例如“未满 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}}。
toolsnull 会让该步骤使用附加到智能体的所有工具。列表会按名称将该步骤限制为仅使用其中的工具。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 时:

  1. 步骤中的每个 required 值都必须存在。若缺少某项,模型会获知缺少哪一项,并向来电者询问。
  2. 值会被存储。
  3. 会按顺序检查 condition 和 always 转移。第一个匹配项生效。
  4. 否则,智能体会移动到模型选择的步骤。
  5. 若没有任何匹配项,且模型未指定步骤,智能体将停留在当前位置。

跳转到全局步骤是例外:来电者更改了话题,因此即使当前步骤仍缺少必填值,也会继续跳转,并且会在检查该步骤条件之前进行。

每次进入步骤时,也会检查 condition 和 always 转移,因此一连串由代码决定的步骤会一次性执行。链条会在以下情况停止:

  • 到达本次运行中已访问过的步骤;
  • 经过 8 次跳转后;
  • 到达仍有必填值待收集的步骤,除非该步骤具有 on_enter_tool(webhook 步骤始终根据其结果进行路由)。

只会说出链条停止所在步骤的 say 行。

没有转移的步骤是终止步骤。智能体会在此完成流程,并使用其常规内置操作结束或转接通话。

收集值

在 collect 中列出某个步骤所需的内容。模型在调用 go_to_step 时传入这些值,它们会在通话剩余期间保持可用。

type可存储内容
string任意文本
number数字
booleantrue 或 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 产品,适用于呼入、呼出和营销活动通话、测试通话,以及网页和小组件通话。

在其他所有位置,智能体仍可正常工作,只是没有这种结构:它会按顺序运行一个合并了共享指令和所有步骤的扁平化提示词。这包括:

  • 实验性 Bolt 产品;
  • Realtime 会话,包括已保存智能体的会话;
  • 由动态通话配置 webhook 返回的内联配置。返回 agent_id 的 webhook 会使用该智能体的步骤。

限制

限制值
每个智能体的步骤数100
每个步骤收集的值数量20
每个智能体的交接目标数8
单次通话中可触达的智能体数8
steps JSON 的大小200 KB
步骤 instructions20,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"
}

后续步骤