语音代理开发:将提示视为状态机

本文介绍如何通过将提示视为有限状态机(FSM)解决实时语音API的提示遵循问题。

语音代理开发:将提示视为状态机

实时语音LLM对于自然的对话非常棒,但存在权衡:如果坚持严格的确定性(精确脚本),就会失去流畅性;如果放任不管,就可能产生不可预测或不合规的回复。

实用的中间路径是将你的对话提示和流程设计为显式的FSM风格规范:简短的NLG模板 + 类型实体 + 验证器 + 小的、显式的状态转换 + 边界情况处理程序。这将LLM变成语言层,而一个简单的策略(FSM)则作为可靠的控制平面。

本文面向产品经理、工程师和设计师,他们正在构建语音代理(电话机器人、通话中的助手、演示流程),希望以一种实用且非技术的方式让实时LLM语音代理遵循所需的脚本,同时不会破坏自然性。

快速、通俗易懂的背景知识:

  • 实时语音LLM(流式音频输入→流式音频输出)让你可以与用户进行低延迟的双向口语对话。这使得交互感觉真实——而且错误会立即被听到。
  • LLM是概率性的:它们生成合理的语言,但不能保证精确的句子。模型设置或环境的微小变化可能会改变输出。
  • FSM很简单:可以想象成一个简短步骤的地图(状态)。每个状态说明了基于前一个回复(条件)代理应该说什么(动作)、它需要从用户那里收集什么,以及哪些用户回复会将其带入下一步。结合简短的模板和验证器,这是可读、可测试和可预测的。

问题的三句话总结:

  1. 有些步骤必须逐字执行,出于法律/合规原因或完成业务操作。
  2. LLM有时会偏离精确措辞或在中断后选择错误分支。
  3. 团队花费太多时间手动迭代提示以使其“稳定”——这很慢且脆弱。

1、提示即FSM

创建一个简短、人类可读的“对话规范”,包含:

a) 状态(简短的命名步骤)

每个状态指定:

  • 代理说什么(简短模板,1-2句话)。
  • 它需要捕捉什么(例如,会议时间、同意与否)。
  • 哪些用户意图会带你进入哪个下一个状态(例如,用户给出时间 → 确认;用户说不感兴趣 → 结束)。

b) 实体(类型槽位)

你收集的信息片段(日期时间、语言、布尔值)。定义友好的口语格式和标准机器格式(例如,“星期四,6月5日下午3点IST” vs ISO日期时间)。

c) 验证器和规范化器

简单规则说明“这看起来像有效的会议时间”或“这太模糊了,再问一次”。

d) NLG模板

代理说话的简短、标记模板。将模板标记为严格用于合规关键文本,或在允许改述时标记为软。

e) 边界情况处理程序

快速行为用于语言切换、中断、模糊回答或退出。

f) 接受标准

成功对话的简单检查清单(例如,“会议时间已捕获并确认”)。


# example_fsm_spec.yaml  
domain: "appointment_booking_voice_agent"  
meta:  
  description: "适用于实时音频中简短预订对话的初学者友好型FSM规范。"  
  base_language: "en-US"  
  supported_languages: ["en-US", "hi-IN", "mr-IN", "te-IN", "kn-IN"]  
  voice_profiles: ["neutral_male", "neutral_female"]  

entities:  
  - name: customer_name  
    type: string  
    required: false  
    spoken_format: "{{ customer_name }}"  
  - name: language_preference  
    type: string  
    required: true  
    value: "en-US"  
    allowed_values: ["en-US", "hi-IN", "mr-IN", "te-IN", "kn-IN"]  
  - name: meeting_time  
    type: datetime  
    required: false  
    canonical_format: "ISO-8601"  
    spoken_format: "Thursday, June 5 at 3 PM IST"  
  - name: consent_call  
    type: boolean  
    required: true  
    default: true  

validators:  
  - name: time_parser  
    description: "检测明确的时间并标记模糊的时间。"  
    pattern: "(?i)(tomorrow|today|\\b\\d{1,2}(:\\d{2})?\\s?(am|pm)?\\b)"  
    rule: "如果明确时间 -> 规范化为ISO;如果模糊 -> 标记为'vague_time'。"  

intents:  
  - id: greet  
    examples: ["Hi", "Hello", "Namaste"]  
  - id: ask_info  
    examples: ["What is this about?", "Explain", "Tell me more"]  
  - id: provide_time  
    examples: ["Tomorrow 3pm", "Thursday at 3 PM", "5 PM"]  
  - id: vague_time  
    examples: ["Tomorrow", "Sometime next week", "Later"]  
  - id: not_interested  
    examples: ["Not interested", "No thanks", "Stop"]  
  - id: language_switch_request  
    examples: ["Can we speak in Hindi?", "Telugu please", "Marathi bolu?"]  
  - id: interruption  
    examples: ["Wait", "Hold on", "Stop"]  

nlg:  
  opening:  
    description: "允许说话 + 目的"  
    strictness: soft  
    base_template: "Hi{{#if customer_name}} {{ customer_name }}{{/if}}, quick call to see if now is a good time for a 2-minute info session. Is now okay?"  
  info_outline:  
    description: "简短价值 + 询问可用性"  
    strictness: soft  
    base_template: "We help streamline scheduling and support. Would a short follow-up help? What time works for you?"  
  time_request:  
    description: "当模糊时重新请求具体时间"  
    strictness: strict  
    base_template: "Could you tell me a specific date and time for a quick follow-up (for example: Thursday at 3 PM)?"  
  confirm_time:  
    description: "确认捕获的时间并结束"  
    strictness: strict  
    base_template: "Great — locking in {{ meeting_time }}. I’ll send a reminder. Thanks!"  
  language_ack:  
    description: "承认并切换语言"  
    strictness: soft  
    base_template: "Sure — I will speak in {{ language }} from now on."  

policy:  
  initial: S0_opening  
  states:  
    S0_opening:  
      say: nlg.opening  
      capture: []  
      on:  
        ask_info: S1_info  
        not_interested:  
          say: nlg.not_interested_close  
          end: closed  
        language_switch_request:  
          say: nlg.language_ack  
          action: update_entity(language_preference)  
          then: S0_opening  
    S1_info:  
      say: nlg.info_outline  
      capture: []  
      on:  
        provide_time: S2_confirm  
        vague_time:  
          say: nlg.time_request  
          then: S1_info  
        not_interested:  
          say: nlg.not_interested_close  
          end: closed  
    S2_confirm:  
      say: nlg.confirm_time  
      capture: [meeting_time]  
      end: booked  

edge_cases:  
  - id: follow_up_missing_time  
    trigger_intent: provide_time  
    behavior: "如果解析的时间是模糊的,再次请求nlg.time_request。"  
  - id: language_switch  
    trigger_intent: language_switch_request  
    behavior: "更新language_preference并重新用翻译后的最后代理提示说话;从相同状态继续。"  
  - id: user_interrupt  
    trigger_intent: interruption  
    behavior: "短暂承认(1个token)并重新评估意图;在澄清之前不要继续状态转换。"  

scenarios:  
  - id: happy_path_booking  
    steps:  
      - { bot: nlg.opening, expect: ask_info }  
      - { bot: nlg.info_outline, expect: provide_time }  
      - { bot: nlg.confirm_time }  
  - id: language_switch_example  
    steps:  
      - { bot: nlg.opening, context: { language_preference: "en-US" } }  
      - { user: "Hindi mein baat kar sakte hain?" }  
      - { edge_case: language_switch -> update language_preference: "hi-IN" }  
      - { bot: nlg.language_ack (translated) + re-issue nlg.opening translated }  

acceptance_criteria:  
  - "打开状态说明目的并请求权限。"  
  - "以标准形式捕获会议时间并在口语形式中确认。"  
  - "立即尊重不感兴趣并礼貌结束。"  
  - "如果用户给出模糊答案,则提示具体时间。"  
  - "在请求时立即更新language_preference并将其用于后续输出。"

2、结束语

这里描述的方法在你需要确定性时效果最好——例如,合规性、预订或对话中精确措辞和可预测流程重要的情况。

如果你的目标是让模型自由思考并更自然地回应,那么这种FSM风格的提示不是你想要的。在这种情况下,要利用LLM的概率特性,允许更高的变化性,并专注于优先考虑参与度而非严格遵守的对话设计。


原文链接:The Secret to Reliable Voice Agents: Treat Prompts Like State Machines

汇智网翻译整理,转载请标明出处