跳到正文

现有业务说明文档 ​

本文档列出 src/business/ 下当前实现并注册的所有业务,包含:业务名(用于数据库路由)、 分类、代码位置、目的与关键能力。注册清单的唯一权威来源是 src/business/index.ts 的 allBusinesses(= coreBusinesses + demo/index.ts 的 demoBusinesses); src/runtime/business-registry.ts 只是消费它构建 code → handler 反查表。

业务一览:

1. 核心生产业务(位于 src/business/ 根目录) ​

业务名代码位置用途关键能力
callout-flow-businesscallout-flow-business/通用外呼流程解释器:按 callout-server 流程脚本逐节点执行流程图 JSON + NLU decision/extract + transfer 转人工 + 结果回写
llm-chat-businessllm-chat-business/真实大模型语音对话(OpenAI Chat Completions / GPUStack)callHear 持续 ASR + OpenAI /v1/chat/completions 流式 + TTS 播报
kb-audio-chat-businesskb-audio-chat-business/知识库录音对话(客户话术→知识库→录音播放)+ 实时盯屏/人工介入callHear 持续 ASR + 知识库 HTTP 查询 + 本地 TTS 播放 + 逐轮进度上报 + 会话注册表介入
kb-llm-chat-businesskb-llm-chat-business/知识库大模型语音对话知识库策略检索 + LLM 总结 + TTS 播报
verification-code-businessverification-code-business/验证码识别:持续 ASR + 自动按键应答 + 结果上报callHear + postHangupDrainMs 尾部补收 + 自动 DTMF + HTTP 上报
browser-softphone-assist-businessbrowser-softphone-assist-business.ts浏览器软电话辅助坐席:坐席网页呼入建会并外呼客户SIP Header 会话恢复 + 会议转写推流 + 前端命令循环
agent-conference-guestagent-conference-guest-business.ts坐席入会(AI 转人工的坐席侧)conference.join + 通道变量角色/flags
conference-host-businessconference-host-business.ts会议主持人:邀请 + 启动会议 ASR + 广播originate + conference.*
conference-guest-businessconference-guest-business.ts会议访客:拿到 conferenceId 后入会conference.join
insurance-chat-businessinsurance-chat-business/大模型车险电销对话业务持续 ASR 打断 + 大模型流式对话 + 防复述护栏 + 手机/微信号抽取与改号纠错

2. 测试与演示业务(位于 src/business/demo/ 目录) ​

业务名代码位置用途关键能力
default-call-businessdemo/default-call-business.ts默认循环识别 + 回显hear 循环、关键字挂机
ai-voice-platform-demo-businessdemo/ai-voice-platform-demo/AI 语音平台 + MicroSIP 视频演示流程callHear 持续 ASR、模拟 LLM、演示日志
callout-platform-demo-businessdemo/callout-platform-demo-business.ts外呼平台能力演示外呼变量读取、录音、ASR、结果回写
prompt-then-recognize-echo-businessdemo/prompt-then-recognize-echo-business.ts先播报后识别再回显,一次性顺序 speak/hear,不带 barge-in
verbose-prompt-then-recognize-businessdemo/verbose-prompt-then-recognize-business.ts与上面相同,但回调全开,用于排查SpeakHandlers / HearHandlers 全集
call-hear-businessdemo/call-hear-business.ts整通持续 ASR,按 final 文本驱动callHear + 串行 actionQueue
dtmf-businessdemo/dtmf-business.ts语音 + DTMF 并发,先到先抢占onDtmf + cancelHear
carrier-simulator-businessdemo/carrier-simulator-business/模拟三大运营商早媒体(空号/关机/无法接通/无人接听/忙/停机)pre_answer + TTS 循环播报、按通道变量选样本
carrier-ringback-detection-demo-businessdemo/carrier-ringback-detection-demo-business.ts回铃音检测演示:选类型 → 外呼模拟器 → 早媒体 ASR 检测originateWithRingbackDetection + DTMF/语音菜单
conference-control-demo-businessdemo/conference-control-demo-business.ts会议全量会控演示:DTMF 菜单邀请/锁会/录音/静音/禁听/踢出/公告/状态/结束conference.* 全集 + onDtmf
conference-background-audio-demo-businessdemo/conference-background-audio-demo-business.ts会议背景音演示:入会即注入办公室底噪,TTS 与背景音并发、打断不停底噪conference.startBackgroundAudio + conference.hear(partial)
call-transfer-demo-businessdemo/call-transfer-demo-business.ts转人工(直接 bridge)bridgeToNumber
originate-and-bridge-demo-businessdemo/originate-and-bridge-demo-business.ts转人工(先外呼 B-leg 再桥接)originateAndBridge
parked-dial-demo-businessdemo/parked-dial-demo-business.ts帮拨号:park → 确认 → 桥接originateParkedCall + bridgeCall
duplex-ai-chat-demo-businessdemo/duplex-ai-chat-demo-business.ts原生全双工语音对话演示duplex.start + final 串行回复 + 插话打断
voice-agent-demo-businessdemo/voice-agent-demo-business.tsVoice Agent SDK 演示业务结构化角色定义 + 一行式对话编排 + 持续 ASR 打断 + 即产即播 TTS

default-call-business ​

业务名:default-call-business

目的:调试 / 演示用的默认业务。摘机后循环 hear,识别成功就回显,识别 失败就静默重听,听到 “挂机” 二字则播放结束语后挂机。

关键常量(位于业务文件顶部):

  • TTS_TEXT = "欢迎测试语音合成, 你好世界" — 首轮提示音
  • TTS_REPEAT_PREFIX = "你刚刚说了," — 回显前缀

流程:

  1. answer + sleep settleDelayMs
  2. startRecording(默认目录与文件名)
  3. 循环:
    • hear({ prompt, interruptible: true, bargeInTrigger: "partial", timeoutMs: 10000 })
    • 识别成功且文本 == "挂机" → 播 "好的,再见" → hangup → 跳出循环
    • 识别成功其他文本 → 用 回显前缀 + 文本 作为下一轮提示音继续
    • 超时 / 失败 → 仅落日志、继续监听
  4. 通话由对端挂机或 socket 关闭终止循环

适合场景:开发联调、回归冒烟、TTS / ASR 流程演示。


ai-voice-platform-demo-business ​

业务名:ai-voice-platform-demo-business

目的:专门用于“AI 语音平台 + MicroSIP”项目介绍视频录制。呼入后接通, 启动整通持续 ASR,播放欢迎语;用户说出“帮我查一下,系统里今天有哪些未处理的 告警订单?”后,业务模拟 LLM 思考和流式文本输出,再播报固定的告警订单查询结果。

关键常量:

  • OPENING_PROMPT — 视频脚本中的 AI 首次欢迎语
  • DEMO_ANSWER — 告警订单查询固定答复
  • LLM_STREAM_CHUNKS — 右侧控制台展示用的模拟大模型流式文本片段
  • HANGUP_KEYWORDS — 命中“挂机 / 挂断 / 再见 / 拜拜”后结束通话

流程:

  1. answer + sleep settleDelayMs
  2. startRecording,记录录音文件位置
  3. callHear({ partialResults: true, interruptPlayback: true, bargeInTrigger: "partial" })
  4. 播放欢迎语:“您好!欢迎致电智能语音助手,请问有什么可以帮您?”
  5. ASR partial / final 文本写入 demo_asr_stream_text 日志,供控制台录屏展示
  6. final 文本命中告警 / 订单相关关键词:
    • 写入 demo_llm_thinking
    • 逐段写入 demo_llm_stream_delta
    • 播放固定查询结果
  7. final 文本命中挂机关键词:播放结束语并 hangup("NORMAL_CLEARING")
  8. 其它文本:播报演示提示,引导用户说告警订单查询句式

适合场景:项目介绍视频、MicroSIP 软电话演示、ASR/LLM/TTS/RTP 状态控制台 录屏。该业务不接入真实 LLM 或真实工单系统,查询结果为演示用固定文本。

路由建议:服务启动会把该业务同步到 call_businesses 表,可直接使用 businessCode = ai-voice-platform-demo-business;若要从 MicroSIP 拨固定内线进入, 在 call_business_number_mappings 中把演示内线号码映射到该业务。


llm-chat-business ​

业务名:llm-chat-business(文件夹形态 llm-chat-business/, 入口 index.ts,话术 / 关键词在 prompts.ts,OpenAI 客户端在 openai-chat-client.ts)

目的:接真实大模型的语音对话业务(OpenAI Chat Completions 协议,适配 GPUStack 部署的模型)。与 ai-voice-platform-demo-business 的「模拟 LLM 固定答复」 不同,本业务把识别到的 final 文本送进 OpenAI 的 /v1/chat/completions 流式接口, 模型流式输出经分句缓冲器增量切句、即产即播,是一个最小但可跑通的「ASR → 大模型 → TTS」闭环。 每通电话维护独立的 messages 上下文(含 system prompt),实现多轮记忆。

配置(位于 config.toml.businessConfig["llm-chat-business"],各字段均可选,未填时自动继承全局 [llm]):

  • endpoint — OpenAI 兼容 Chat Completions 端点(服务根地址、以 /v1 或 /v1-openai 结尾的基地址,或完整 /chat/completions 路径)
  • apiKey — 可选模型鉴权 Key,非空时携带 Authorization: Bearer <key>
  • model — 模型名称(GPUStack 注册的模型名),必须是非空字符串
  • requestTimeoutMs — 单轮正整数超时(毫秒,样例为 60000)

若在配置文件中显式声明这些字段,启动期会按统一 URL 归一化规则校验端点、模型与超时格式;运行期仅在业务配置与全局 [llm] 两处均未提供有效 endpoint / model 时报错,不会回退到 localhost。

本业务也可在 call_businesses.businessConfig 和号码映射的 mappingConfig 中直接 覆盖这些字段,例如 { "model": "qwen3.5:2b" }。新通话按“文件 < 业务 < 映射” 深合并;显式 business_code 路由跳过映射层。合并结果无效时不会请求模型, 而是记录 business_config_invalid 并播放“系统错误”。

关键常量(位于 prompts.ts):

  • SYSTEM_PROMPT — 约束模型用简洁口语化中文、避免 Markdown / 表格,适合语音播报
  • OPENING_PROMPT / GOODBYE_PROMPT / ERROR_FALLBACK_PROMPT — 欢迎 / 告别 / 兜底语
  • HANGUP_KEYWORDS = ["挂机", "挂断", "再见", "拜拜"] — 命中即结束通话

流程:

  1. answer + sleep settleDelayMs + startRecording
  2. 初始化每通电话的 messages(首条为 SYSTEM_PROMPT)
  3. callHear({ partialResults: true, interruptPlayback: true, bargeInTrigger: "partial", minTextLength: 2, bargeInFilter: shouldInterruptOnPartial, noSpeechTimeoutMs: 15000, noTextTimeoutMs: 20000 })
  4. 播放欢迎语后 waitForHangup()
  5. onFinal 经串行 actionQueue 触发 handleFinalTranscript:
    • 文本为空 → 跳过;命中挂机关键词 → 播告别语 → hangup("NORMAL_CLEARING")
    • 否则把用户文本追加进 messages,调用 requestOpenAIChat(流式,onDelta 增量切句、即产即播),完整回复追加进 messages 并做防重复兜底校验
    • 模型调用失败 / 空回复 → 落 llm_chat_llm_failed 日志(errorKind 区分 connection/http/timeout/response)并播 ERROR_FALLBACK_PROMPT 兜底;新一轮、 挂机或业务关闭导致的主动取消单独记录 llm_chat_llm_aborted
  6. finally:标记 shuttingDown → asrSession.stop("business-complete") → 等 actionQueue 排空

前置条件:业务配置或全局 [llm] 的 endpoint 可从 callflow-esl 主机访问, 且已加载对应 model;ASR / TTS 服务正常。partial 打断依赖 ASR 持续推送 partial 文本。

适合场景:真实 AI 电话客服 / 语音助手的最小可用样板;想换其它 OpenAI 兼容 模型服务,只改 openai-chat-client.ts 与 prompts.ts 即可。


kb-audio-chat-business ​

业务名:kb-audio-chat-business(文件夹形态 kb-audio-chat-business/, 入口 index.ts,知识库客户端与接口契约在 kb-client.ts;盯屏/介入基础设施 复用 shared/callout/live-session.ts)

目的:知识库对话业务。形态与 llm-chat-business 相同(持续 ASR + 逐轮问答),但回答来自 callout-server 的独立 conversation strategy, 不经过 Chat 节点或模型;策略返回文字后统一由 callflow-esl 本地 TTS 播放。同时内建 「坐席实时盯屏 + 人工介入」:逐轮对话片段实时 上报 callout-server,坐席在盯屏页可一键介入,业务带着指定坐席走会议转人工。

知识库契约(见 kb-client.ts 顶部注释):接通后先发送 { strategy, action: "start", sessionId },成功响应必须包含 { ok: true, data: { openingPrompt, errorPrompt } };后续问答发送 { strategy, action: "query", sessionId, text },响应为 { ok: true, data: { answerText, hangup } }。初始化失败会以 NORMAL_TEMPORARY_FAILURE 挂机。当前样例使用内置 kb-mock 策略;它固定返回 开场白/失败话术,并确定性回显客户原话。

依赖配置:

  • config.businessConfig["kb-audio-chat-business"] 的 endpoint / strategy / token / requestTimeoutMs:conversation strategy 接口、策略 key、 可选内部鉴权 token 和超时;整个子项可缺省,但配置后会在启动期严格校验,开场白与 查询失败话术由策略返回
  • config.callout:callout-server 的 baseUrl / token / requestTimeoutMs; 代码固定调用 /api/call-progress、/api/call-results、/api/transfers/route 与 /api/transfers/end

流程:

  1. answer + settle + 读取 callout 身份变量 + 双声道录音
  2. 登记 liveCallSessionRegistry 会话并上报「通话开始」
  3. 调用 conversation strategy action=start,缓存本通话的开场白与失败话术;成功后才启动 callHear(barge-in)并播放非空开场白,失败则直接挂机并进入清理
  4. 每条 final:写 transcript(同步实时上报)→ 查询策略 → 本地 TTS 播放 answerText → hangup=true 时播完挂机。 每轮返回都经串行门排队按序播放:客户连说多句时各轮话术依次播出(播放中仍 可被 barge-in 打断),不因客户新发言作废后到的应答
  5. 每秒轮询介入信号:命中后等当前播报结束 → agentId 定向转人工 (reserveSpecificAgent)→ 启动两阶段自治看门狗(joinConferenceWithAgentWatchdog)→ 坐席入会成功则 AI 停止应答,客户腿单呼 ASR 切换为会议级转写(startInterventionTranscription), 坐席(speaker=agent)与客户的发言持续写 transcript 实时上报盯屏;失败(坐席超时未入会) 则停会议转写、重启单呼 ASR 恢复 AI 对话
  6. 转人工路由成功后保存会议 ID;客户挂机或通话退出时,finally 调用 requestHumanAgentTransferEnd(发送 POST /api/transfers/end),令该会议全部坐席/主管 SIP 腿结束,调用失败只记结构化日志
  7. 其余 finally 清理:注销会话 + 上报「通话结束」+ 整段结果回写(result.type = "kb_audio_chat",含 intervention 摘要)+ 挂机

适合场景:需要接入确定性问答策略并复用本地 TTS,批量外呼时 坐席「一人盯多路、有意向即插入」的人机协同外呼。


kb-llm-chat-business ​

业务名:kb-llm-chat-business

目的:真实大模型语音对话 + 实时盯屏 + 人工介入。结构骨架来自 kb-audio-chat-business(会话注册表、逐轮进度上报、结果回写、坐席介入), 对话引擎来自 llm-chat-business(OpenAI Chat Completions 流式 + 句切分片 TTS)。 它是当前 AI 外呼/客服链路上默认使用的"能被坐席接管的大模型对话"业务。

配置:businessConfig.kb-llm-chat-business(各字段均可选,未填时自动继承全局 [llm]; systemPrompt 选填),支持 config.toml < call_businesses.businessConfig < mappingConfig 三层合并, 字段说明见 配置参考。systemPrompt 为空时使用 kb-llm-chat-business 自带的默认系统提示词(prompts.ts 的 SYSTEM_PROMPT)。

流程要点:

  1. answer + sleep settleDelayMs,登记实时会话(liveCallSessionRegistry)+ 启动录音
  2. 启动持续 ASR,播开场白;每条 final 先写客户片段并上报盯屏(postCallProgressSafely)
  3. 命中挂机词 → 播告别语并挂机;否则起一个 turn 请求大模型流式输出
  4. 流式结果做防重复校验后切句、分片串行 TTS,逐片上报 bot 片段;出错播兜底语
  5. 每秒检查介入信号:命中则停当前 turn 并把指定坐席拉入会议——成功则 AI 退出应答、 改用会议级转写继续上报;失败则回落单呼 ASR 继续 AI 对话
  6. finally:注销会话 + 上报结束 + 回写 /api/call-results

适合场景:需要"AI 先聊、坐席随时接管"的生产链路。纯 AI 对话(无盯屏/介入)用 llm-chat-business;话术走知识库录音而非大模型时用 kb-audio-chat-business。


callout-flow-business ​

业务名:callout-flow-business(文件夹形态 callout-flow-business/,入口 index.ts,流程图解析在 flow-graph.ts,节点运行辅助在 flow-runtime.ts, callout / NLU HTTP 访问与转人工封装统一复用 src/business/shared/callout/index.ts)

目的:通用外呼流程解释器——系统二「流程脚本」的运行时执行端。业务本身不 写死任何话术:接通后按通道变量 script_id 从 callout-server 拉取流程脚本运行时 JSON(config.callout.baseUrl + POST /api/scripts/runtime), 构建流程图后从 start 节点逐节点执行,支持 start / send / collect / router / chat / end / transfer 七类节点。chat 节点在节点内部执行持续 ASR、Chat SSE、分句 TTS 和语义退出,并在同一通电话内共享受限历史。

预期通道变量(callout-server originate 时经 campaign variables 注入):

  • script_id:要执行的流程脚本 id(必填,缺失直接判失败并回写诊断)
  • callout_attempt_id:外呼尝试记录 id,用于结果回写关联

依赖配置(config.callout):统一配置 baseUrl / token / requestTimeoutMs,覆盖流程脚本拉取、transfer 节点预占坐席、进度/结果回写、NLU(collect.extract_profile 抽取与 router.decision_profile 决策)及 Chat 节点 SSE 对话。

流程要点:

  1. 读 script_id / callout_attempt_id,拉取并解析流程脚本运行时 JSON
  2. answer + startRecording,从 start 节点开始沿出边遍历执行:
    • send:渲染模板变量后 TTS 播报
    • collect:播报问题并 hear,把客户回答交给 NLU extract profile 抽取字段
    • router:把上下文交给 NLU decision profile,按返回分支选择出边
    • transfer:调 requestHumanAgentTransfer 预占坐席,入会前开启会议级 ASR 转写(startTransferConferenceHear)捕获全员通话记录,并通过两阶段看门狗 (joinConferenceWithAgentWatchdog)看护坐席入会与离会(坐席入会即结束 AI 流程; 超时未入会则播 unavailable_prompt 走默认出边兜底)
    • end:播报结束语并收尾
  3. 防循环保护:最多执行 MAX_STEPS = 100 个节点,超限视为失败并回写诊断
  4. 结束(含异常)时把逐句 transcript、抽取字段、分支路径与录音短文件名回写 POST /api/call-results

适合场景:所有由 callout-webpage 可视化编排的外呼话术。节点语义与 NLU profile 约定见 apps/callout-server/docs/flow-script-nlu-development.md。


callout-platform-demo-business ​

业务名:callout-platform-demo-business

目的:AI 外呼平台产品介绍演示业务,专门配合 callout-server 的外呼任务使用。 接通后启动录音 + 实时多轮 ASR + TTS 播报,并在通话过程中实时组装客户/AI 对话 时间线(含 AI 被打断的标记),结束时连同录音回写 POST /api/call-results(携带逐句 transcript)。

为什么时间线必须实时组装:audio_fork 只取主叫单声道,录音里听不到 AI 的 TTS。 客户句来自 ASR final,AI 句来自本业务自己播报的文本,打断判定来自运行时 barge-in 事件——不是事后对混音录音做声音分析。

流程要点:

  1. callout-server originate 时写入 callout_request_id / callout_campaign_id / callout_contact_id,业务接通后读取
  2. callHear 持续 ASR(partial + final,播报期可被打断),客户 final 经 actionQueue 串行处理
  3. 每条 final:解析意图 → 推进对话状态机 → decideDialogAction (transfer 转人工走 ACD 选坐席 + 会议看门狗;conclude 播结束语挂机; speak 按阶段播下一段:产品 / 价值 / 价格 / 部署 / 功能 / 追问)
  4. ASR 超时:首次补播二次机会语,再次超时按"无应答"终态收尾
  5. finally 停 ASR / DTMF 并等队列清空,然后汇总终态与时间线回写结果

适合场景:外呼平台端到端能力(外呼变量、录音、持续 ASR、打断、转人工、结果回写) 的演示与回归。


prompt-then-recognize-echo-business ​

业务名:prompt-then-recognize-echo-business

目的:最简单的顺序业务:先独立播报一次开场白,等播报完成再开一轮 hear, 识别到文本就回显然后挂机。speak 与 hear 是顺序执行的,不存在提示音 期间的 barge-in。

关键常量:

  • TTS_TEXT、TTS_REPEAT_PREFIX

流程:

  1. answer + sleep settleDelayMs
  2. speak({ kind: "tts", text: TTS_TEXT }) — 独立播报,无 barge-in
  3. hear({}) — 默认参数,等用户开口
  4. 识别成功 → speak(${TTS_REPEAT_PREFIX}${text}) → hangup("NORMAL_CLEARING")
  5. 识别失败 / 超时 → 落日志后退出,由 SessionRunner 处理后续挂机

适合场景:跑通最小通话链路;新增业务时常作为模板复制。


verbose-prompt-then-recognize-business ​

业务名:verbose-prompt-then-recognize-business

目的:与 prompt-then-recognize-echo-business 的流程一致,但把 SpeakHandlers 和 HearHandlers 的全部回调都接上、并把内部字段全部打到 ctx.log 里。

适合场景:

  • 第一次接入时验证回调与字段是否符合预期
  • 排查“为什么提示音没被打断”、“为什么 hear 超时”这类问题时直接复制本业务 并替换业务名即可

call-hear-business ​

业务名:call-hear-business

目的:整通通话的“持续 ASR”模式。一旦进入业务,audio_fork 始终开着, final 文本来一条就触发一轮播报(回显或挂机)。onFinal 通过串行 actionQueue 触发,避免连续 final 引发并发 speak。

关键常量:

  • TTS_TEXT、TTS_REPEAT_PREFIX、HANGUP_PROMPT = "好的,再见"

流程:

  1. answer + sleep settleDelayMs + startRecording
  2. 启动 ctx.callHear({ partialResults: true, interruptPlayback: true, bargeInTrigger: "partial" })
  3. 播放欢迎语 + waitForHangup()
  4. onFinal 串行触发:
    • 文本为空 → 跳过
    • 文本 == "挂机" → 标记 shuttingDown → 播报结束语 → hangup
    • 其它 → 回显
  5. finally:session.stop("business-complete") + 等 actionQueue 排空

适合场景:实时闲聊 / 持续答疑 / 边听边说的会议笔记类业务。


verification-code-business ​

业务名:verification-code-business(文件夹形态 verification-code-business/, 入口 index.ts,按职责拆为 asr-collector.ts / dtmf-sender.ts / recognition-result.ts / report-client.ts / utils.ts)

目的:自动化的「验证码 / 短口令语音识别 + 按键应答 + 结果上报」业务。接通后不 播报提示音,直接启动整通持续 ASR;识别到「请按 X」「press X」这类按键提示时自动回拨 对应 DTMF;挂机后再 drain 一段尾部识别,最终把主被叫与识别结果打印到控制台,并按表单 上报到外部接口。常用于自动接听语音验证码电话、按提示输入并回收验证码内容。

关键常量与配置:

  • ASR_POST_HANGUP_DRAIN_MS = 1500(index.ts)— 挂机后继续收尾部 ASR 的窗口
  • 结果上报与防重复配置(config.toml.businessConfig["verification-code-business"]):
    • endpoint:结果上报 URL(如 "https://app.yuntl.cc/push_sms"),未配置时自动跳过上报并记日志
    • requestTimeoutMs:单次上报超时(默认 5000ms)
    • maxAttempts:最大尝试次数(默认 3 次,失败仅记日志不影响通话收尾)
    • antiDuplicate:防重复呼叫/识别拦截配置:
      • enabled:是否开启防重复拦截(默认 true)
      • windowSeconds:滑动时间窗口大小(秒,默认 60)
      • maxCalls:窗口内允许的最大呼叫次数(默认 1,超出即直接挂断不接听)
  • DTMF 发送(dtmf-sender.ts):最多 3 次、重试间隔 [300, 800]ms,用最近 3 段 final 组成短窗口跨片段提取按键,发送成功后对该数字去重

流程:

  1. 防重复检查(接通前):基于 Redis 滑动窗口统计被叫号码(destinationNumber)在 windowSeconds 内呼叫频次;超限时直接 hangup("NORMAL_CLEARING") 挂断并退出(不接听、不录音、不开启 ASR、不上报)
  2. answer + sleep settleDelayMs + startRecording
  3. 启动 ctx.callHear({ partialResults: true, postHangupDrainMs: 1500 }):
    • onResult(含 partial)→ 写入 ASR 采集器并落日志
    • onFinal → 交给 DTMF 发送器:命中中文「按 X」/ 英文「press X」提示就自动 sendDtmf(中文数字「四」、英文「four」均可归一为按键)
    • onPostHangupResult → 挂机后尾段继续写入采集器(不再驱动按键)
    • onError / onStop → 写业务日志
  4. waitForHangup() 等对端挂机
  5. finally:asrSession.stop("business-complete") → 等 DTMF 队列排空 → 汇总识别文本 → 控制台打印 → postVerificationCodeReport 上报

采集与去重(asr-collector.ts):按 serverSessionId/listenerId + utteranceIndex 归并同一 utterance 的 partial/final,final 优先;缺 utteranceIndex 时用 fallback key, 挂机后 final 会尝试并入重叠的尾段,最终按出现顺序输出去重文本列表。

控制台输出:

text
被叫号码:<destinationNumber>
识别结果:<final 文本汇总(空格连接)>
主叫号码:<callerIdNumber>

并输出结构化日志 验证码识别结果,字段含 destinationNumber、recognitionText、 callerIdNumber 与 status(无有效 final 文本时为 no-result,否则 recognized)。

上报报文(report-client.ts,POST application/x-www-form-urlencoded;charset=UTF-8): from_num=主叫、callee=被叫、content=识别文本、sendTime=YYYY-MM-DD HH:mm:ss。

适合场景:自动接听并回收语音验证码 / 短口令、按提示自动按键、把识别结果回传到外部 系统;支持高频防重拦截。业务自身不主动挂机(防重拦截除外)。


dtmf-business ​

业务名:dtmf-business

目的:演示语音 + DTMF 并发抢占。每轮 hear 期间允许 barge-in;任意时刻 按键会立即 cancelHear,播报“您刚刚按了 X 键”,然后进入下一轮 hear。

关键常量:

  • OPENING_PROMPT、HANGUP_PROMPT、DTMF_PROMPT_PREFIX
  • BARGE_IN_ENABLED = true、BARGE_IN_TRIGGER = "partial"
  • HANGUP_KEYWORDS = ["挂断"]

流程:

  1. answer + sleep settleDelayMs
  2. 注册 DTMF 监听:抢占当前轮次、播报数字播报
  3. 循环:
    • 启动 hear({ prompt, interruptible: true, bargeInTrigger: "partial", timeoutMs: 10000 })
    • 若被 DTMF 抢占 → 等 DTMF 动作完成后进入下一轮
    • 识别成功且命中关键词 “挂断” → 播报 “即将挂断电话” → 挂机
    • 其它识别结果 → 落日志、丢弃文本后下一轮
  4. finally 取消 DTMF 订阅

适合场景:IVR 菜单中需要同时接受语音和数字键的场景。


carrier-simulator-business ​

业务名:carrier-simulator-business(文件夹形态 carrier-simulator-business/, 导出名 carrierSimulatorBusiness(兼容别名 carrierEmptyNumberSimulatorBusiness))

目的:模拟三大运营商(移动 / 联通 / 电信)常见的早媒体提示音,作为 carrier-ringback-detection-demo-business 中 originateWithRingbackDetection 链路的被叫陪练。 不发 200 OK,只 pre_answer 发 183 early media,再用项目统一 TTS 链路 循环播报提示音文本;业务自身不主动挂机,由主叫取消或上游检测器清理 B-leg。

样本库(samples.ts,共 19 条):覆盖 empty_number(空号)、powered_off (关机)、unreachable(无法接通)、no_answer(无人接听)、busy(忙)、 out_of_service(停机)六类场景,外加一条电信助理样本 telecom_assistant。 样本文本刻意保留 ringback-detector 依赖的关键词(“空号”“已关机”“暂时无法接通” “正在通话中”等),即便 TTS 音色有差异,ASR 文本仍能被关键字分类命中。

通道变量(由 originate 拨号串注入,constants.ts):

  • carrier_simulator_carrier:cmcc / unicom / telecom,可大小写
  • carrier_simulator_prompt(或 SIP 头 sip_h_X-Carrier-Simulator-Prompt):上述六类 + telecom_assistant
  • carrier_simulator_repeat_gap_ms:提示音重复间隔,默认 1000ms
  • carrier_simulator_no_answer_ring_ms:无人接听场景先放回铃的时长,默认 30000ms
  • carrier_simulator_ringback_tone:回铃 tone_stream,默认 tone_stream://%(1000,4000,450);loops=1

样本选择优先级(selection.ts):carrier+prompt 精确命中 → 仅 carrier 时该运营商内随机 → 仅 prompt 时跨运营商随机 → 都缺失/非法则全量随机。

流程:

  1. 读初始通道变量,selectCarrierPrompt 选样本、resolveSimulatorSettings 解析参数
  2. pre_answer 发 183 early media(注意不是 answer)+ sleep settleDelayMs
  3. 按样本 timeline 分支:
    • ring_then_announcement_loop(仅无人接听场景):先循环 playback 回铃音 noAnswerRingMs 毫秒,再进入提示音播报循环
    • announcement_loop(其余场景):直接进入提示音播报循环
  4. 提示音循环无本地退出条件:speak 文本 + sleep repeatGapMs,直到主叫取消 / 检测器清理 B-leg 导致 socket 关闭,由 SessionRunner 走正常结束路径清理

适合场景:测试回铃 / 早媒体识别链路,做闭环验证。配合 carrier-ringback-detection-demo-business(交互式选类型)联调最方便。


carrier-ringback-detection-demo-business ​

业务名:carrier-ringback-detection-demo-business

目的:回铃音检测能力的交互式自助演示。呼入后让用户从 A-leg 选一种运营商 提示音类型,业务据此外呼到本地 carrier-simulator-business,由 originateWithRingbackDetection 在 B-leg 早媒体阶段执行 ASR 检测,最后把检测 状态、归类和原始识别文本一并播报给用户。是 carrier-simulator-business + originateWithRingbackDetection 的端到端联调闭环。

关键常量:

  • SIMULATOR_NUMBER = "5002"、DIAL_STRING_TEMPLATE = "loopback//default"
  • DETECTION_TIMEOUT_MS = 20000;无人接听场景放宽到 NO_ANSWER_DETECTION_TIMEOUT_MS = 45000
  • 六类可测类型:按 1~6(空号/关机/无法接通/无人接听/忙/停机),按 7 或说“挂断/挂机”退出
  • MAX_SELECTION_ATTEMPTS = 2

流程:

  1. answer + sleep settleDelayMs + 播放开场白
  2. askForSelection:每轮 hear(带提示音 + barge-in)与 onDtmf 并发, DTMF 与语音先到先抢占;语音按关键字匹配类型,DTMF 按 1~6/7 匹配
  3. 选中类型后 runDetectionRound:用 buildSimulatorVariables 注入 business_code / carrier_simulator_prompt 等变量,调用 originateWithRingbackDetection(SIMULATOR_NUMBER, { dialStringTemplate, variables, ... })
  4. 若 status === "answered"(极少数),hangupCall 掉 B-leg
  5. 播报演示结果:选择类型、外呼状态、检测归类(carrierPromptKind)、识别文本、失败原因
  6. 回到菜单继续,直到用户挂机或两次未识别到有效选择

适合场景:演示 / 验收回铃音检测;无需真实外线,单机即可跑通 A-leg 菜单 → B-leg 模拟器 → 早媒体 ASR 的完整链路。


conference-host-business ​

业务名:conference-host-business

目的:会议主持人。验证主叫号是否有主持权限 → 生成 conferenceId → 邀请访客 → 启动会议级 ASR → 主持人入会。

关键常量:

  • HOST_NUMBER = "1000"、GUEST_NUMBER = "1001"
  • GUEST_BUSINESS_CODE = "conference-guest-business"
  • OPENING_PROMPT / JOIN_ANNOUNCEMENT / INVALID_HOST_PROMPT / INVITE_FAILED_PROMPT

流程:

  1. answer + sleep settleDelayMs + startRecording
  2. 校验主叫号 == HOST_NUMBER,否则播放拒绝语 + 挂机
  3. setVariables({ conference_id, conference_role: "host", conference_host_number })
  4. 播开场白
  5. ctx.originate({ destinationNumber: GUEST_NUMBER, businessCode: GUEST_BUSINESS_CODE, variables: { conference_id, conference_role: "guest" } })
  6. 启动会议级 ASR ctx.conference.hear:
    • onResult(partial):落日志
    • onFinal:构造“{ 中文化主叫号 } 说:{ 文本 }” 的播报,再 ctx.conference.play 广播给整个会议
  7. ctx.conference.play({ delaySeconds: 2 }) 预排一段“即将开始”的会议播报
  8. ctx.conference.join({ flags: ["moderator"] })

适合场景:多方语音会议、AI 会议秘书。number2ChinaNum 把号码转成 “一零零一” 便于 TTS 播报。


conference-guest-business ​

业务名:conference-guest-business

目的:会议访客。读 conference_id 后入会。

流程:

  1. answer + sleep settleDelayMs
  2. getVariable("conference_id");若空 → 播放“会议参数缺失” → 挂机
  3. setVariable("conference_role", "guest")
  4. 播放 “正在加入会议”
  5. ctx.conference.join({ conferenceId })

前置条件:必须由 conference-host-business 通过 originate 触发, 并在 variables 中带 conference_id。


conference-control-demo-business ​

业务名:conference-control-demo-business

目的:会议全量会控演示。主持人拨入后创建独立会议,通过会议内 DTMF 菜单演示邀请成员、锁会、录音、成员静音 / 禁听 / 踢出、公告、状态播报和结束会议 等会控能力。被邀请成员仍复用 conference-guest-business 入会。

菜单(DTMF 1~9):

键动作
1邀请嘉宾(originate conference-guest-business 入会)
2锁定 / 解锁会议
3开始 / 停止会议录音
4静音 / 取消静音第一个嘉宾
5禁听 / 恢复第一个嘉宾
6踢出第一个嘉宾
7播放全员会议公告
8播报会议状态(成员数 / 锁定 / 录音)
9结束会议

流程要点:

  1. answer + sleep settleDelayMs + startRecording
  2. 生成 conferenceId,setVariables({ conference_id, conference_role: "host" })
  3. ctx.conference.join({ flags: ["moderator"] })
  4. 进入 DTMF 菜单循环:onDtmf 抢占当前播报,按键分发到对应会控动作
  5. 每个动作调用 ctx.conference.*(inviteMembers / lock / unlock / startRecording / stopRecording / muteMember / unmuteMember / deafMember / undeafMember / kickMember / play / getState / end), 并播报执行结果
  6. 按 9 结束会议或主持人挂机退出

适合场景:会议会控能力的完整演示与回归;可作为「会议主席台」类业务的脚手架。 会控能力的 HTTP 外部控制面见 apps/callflow-esl/src/http/routes/conference-routes.ts (/conferences/* RESTful API)。


conference-background-audio-demo-business ​

业务名:conference-background-audio-demo-business

目的:会议模式通话背景音演示。把呼入通道放进独立会议室,用会议室原生 混音线程持续播放办公室底噪,再在同一个会议室里做 TTS 播报与多轮连续 ASR—— 验证"背景音一直流淌、打断只停 TTS"这一组合。

为什么用会议而不是单通道:单通道上 TTS 合成期间只能送静音载波,客户侧听起来 就是"电话断了一下";会议室的 20ms 混音线程让背景音成为一条独立音轨, conference <id> play <target> async 注入的底噪与业务 TTS 互不影响, 打断(stop current)也不会碰到它。

流程要点:

  1. answer + sleep settleDelayMs,会议名取 room-bg-<uuid 前 8 位>
  2. ctx.conference.startBackgroundAudio({ conferenceId, profile: "office" })—— 此时会议室还不存在,控制器会挂起为 pending,等 add-member 事件到达后自动补发
  3. ctx.conference.hear({ partialResults: true, bargeInTrigger: "partial" }) 起会议级 ASR; 每条 partial 都递增 currentTurnId,让上一轮的异步播报作废
  4. 每条 final:先 stopPlayback({ conferenceId }) 停掉正在播的 TTS(背景音不受影响), 再用 turnId !== currentTurnId 复查是否已被新一轮抢占
  5. 语音命令:关闭背景音 / 停止背景音 → stopBackgroundAudio; 开启背景音 / 恢复背景音 → startBackgroundAudio; 挂机 / 退出 / 再见 → 播道别语后 conference.end + hangup; 其余文本走回显对话(interruptible: true)
  6. ctx.conference.play({ delaySeconds: 1 }) 异步广播欢迎语(失败只记日志)
  7. ctx.conference.join({ flags: ["moderator"] }) 阻塞至会议结束; finally 里 stopBackgroundAudio + hearSession.stop()

前置条件:FreeSWITCH 已加载 mod_local_stream;当前 office profile 指向 local_stream://moh/16000(对应 local_stream.conf.xml 中定义的 moh/16000 目录素材), 仅当 profile 未命中配置时才回退到 local_stream://ambient/<profile>/16000; 背景音 profile 与播放目标的解析规则见 配置参考 的 backgroundAudio 一节,API 语义见 运行时组件。

适合场景:需要"拟真环境音"的 AI 外呼/客服场景的脚手架;也是排查 "背景音没声音 / 打断把背景音也停了"这类问题的最小复现业务。


agent-conference-guest ​

业务名:agent-conference-guest

目的:AI 转人工的坐席侧入会业务。由 callout-server 的 ACD 路由 (POST /api/transfers/route)通过 inbound ESL originate 坐席分机触发,接通后 按 business_code=agent-conference-guest 路由到本业务。坐席与客户进入同一会议室 即完成转人工接入;后续「坐席间流转」(盲转 / 协商转 / 主管监听强插)在同一会议室 上做成员动态进出,本业务无需改动。

预期通道变量(callout-server originate 时注入):

  • conference_id:会议 ID(必填,缺失则播报「转接参数缺失」并挂机)
  • callout_agent_call_record_id:坐席通话记录 ID,供 freeswitch-events 回写接听/挂断状态
  • callout_conference_role:坐席角色(agent / supervisor)
  • callout_conference_flags:会议 flags,| 分隔(如主管静默监听 mute)

流程:

  1. answer + sleep settleDelayMs
  2. getVariable("conference_id");缺失 → 播报兜底语 → hangup("NORMAL_TEMPORARY_FAILURE")
  3. 读取 callout_conference_role / callout_conference_flags(透传角色与会议 flags)
  4. ctx.conference.join({ conferenceId, flags? }),阻塞至坐席退出会议(自然挂机或被踢)

适合场景:AI 外呼通话转人工坐席、坐席间流转、主管监听 / 强插。完整 ACD 链路 (路由、预占、originate、坐席间流转、僵尸会话兜底)见 apps/callout-server/docs/transfer-to-human.md。


browser-softphone-assist-business ​

业务名:browser-softphone-assist-business(单文件形态,配合 src/runtime/browser-softphone-session-hub.ts 会话中枢)

目的:浏览器软电话的通话侧业务。坐席在网页端(callout-webpage 坐席通话台) 发起外呼时,浏览器 SIP UA 先拨固定业务号呼入本业务;业务从 SIP Header 里的 sessionId 在 browserSoftphoneSessionHub 中找回会话,建立独立会议(坐席 = host)、 originate 客户入会(复用 conference-guest-business),开启会议转写实时推送前端, 并持续消费前端下发的控制命令。

流程要点:

  1. 坐席 SIP 呼入 → answer + settle → 读 SIP Header sessionId;缺失或会话失效 → 播错误提示 → 挂机(CALL_REJECTED)
  2. 会话有效 → 建会议(role=host)+ 录音 + originate 客户入会(guest);客户呼叫 失败 → 播报失败 + 结束会话 → 挂机
  3. 成功后开启会议转写:onResult 按角色 upsert 消息推送前端(坐席通话台实时字幕)
  4. 并行两条线:conference.join(host,阻塞至坐席离开)与 runCommandLoop 消费 前端命令——agent_text(打字文本 TTS 仅注入客户腿)、hold / resume (deaf / undeaf 坐席腿)、dtmf(发给客户腿)、transfer(v1 仅记录)、 end(结束会议 + 挂机)
  5. finally:停转写 + 结束会话 + 等命令循环结束

前置条件:callout-server 侧软电话会话与 SIP 配置(/api/softphone-sessions/*、 坐席 SIP 账号),见 apps/callout-server/docs/softphone-sip.md。

适合场景:坐席在浏览器直接外呼客户并全程辅助(实时字幕、打字转语音、按键、 保持 / 恢复);也是「网页 ↔ 通话业务」双向桥接的参考实现。


call-transfer-demo-business ​

业务名:call-transfer-demo-business

目的:转人工最简实现:问一遍是否转接,确认后直接 bridgeToNumber。

关键常量:

  • TRANSFER_TARGET_NUMBER = "1001"
  • MAX_CONFIRM_ATTEMPTS = 2

流程:

  1. answer + sleep settleDelayMs
  2. 欢迎语
  3. askForConfirmation:最多两次 hear,匹配“确认/否”意图
  4. 确认 → 播 “正在为您转接人工” → ctx.bridgeToNumber(TRANSFER_TARGET_NUMBER)
  5. 桥接成功 → 主动 hangup,让两端通话由 FS 自然延续

适合场景:基础转接场景(A → AI → 人工坐席)。


originate-and-bridge-demo-business ​

业务名:originate-and-bridge-demo-business

目的:与 call-transfer-demo-business 的目标一致(接通后转人工),但走 “先 originate 一路独立 B-leg 再 uuid_bridge” 的链路。相比 bridgeToNumber 的「同步阻塞桥接」,业务侧能拿到独立的 B-leg UUID 做后续控制;常用于需要在桥接前后插入额外编排(埋点、第三方录音、座席策略)的场景。

关键常量:

  • TRANSFER_TARGET_NUMBER = "1001"
  • MAX_CONFIRM_ATTEMPTS = 2

流程:

  1. answer + sleep settleDelayMs
  2. 欢迎语
  3. askForConfirmation:最多两次 hear,匹配“确认 / 转人工 / 否”意图
  4. 确认 → 播 “正在为您发起后台外呼” → ctx.originateAndBridge(TRANSFER_TARGET_NUMBER)
  5. status === "bridged" → 落日志后主动 ctx.hangup(),让 FreeSWITCH 完成两端通话
  6. status === "not_answered" / 其它失败 → 播报失败原因后挂机

适合场景:需要拿到 B-leg UUID 做精细控制的转接、外呼后桥接、座席分配演示。


parked-dial-demo-business ​

业务名:parked-dial-demo-business

目的:“帮我拨号” 类业务:先把对方拨通停在 park,再问当前用户是否需要 连接;同意则 bridgeCall,并保留当前通道能在被叫挂机后继续控制;不 同意则 hangupCall。

关键常量:

  • PARKED_DIAL_TARGET_NUMBER = "1001"
  • PARKED_DIAL_TIMEOUT_MS = 20000
  • MAX_CONFIRM_ATTEMPTS = 2

流程要点:

  1. 欢迎语 + 询问是否需要拨号
  2. 进入 while (true) 重试循环:
    • 播 “正在为您拨号”
    • originateParkedCall(target, { dialStringTemplate: "user/" })
    • 不是 parked → 询问是否重试
    • parked → 询问是否连接
      • 确认 → bridgeCall(bLegUuid, { preserveCurrentCallAfterBridge: true })
      • bridge 成功 → Promise.race 等待被叫 / 主叫挂机;被叫先挂可再询问重试
      • bridge 失败 → hangupCall(bLegUuid) + 询问重试
      • 不连接 → hangupCall(bLegUuid, "ORIGINATOR_CANCEL") + 询问重试

适合场景:智能助理代用户拨号、电话秘书。


insurance-chat-business ​

业务名:insurance-chat-business

分类:outbound(代码定义:category: "outbound")

目的:大模型车险电销对话业务。模拟真实车险外呼电销场景,接通后主动播放开场白,介绍车险新优惠活动(老客户续保八折优惠、新客户服务与免费全车检查),引导客户添加微信并支持识别记录微信号(手机号),具备改号纠错闭环能力与道别挂机。

关键能力:

  • 持续 ASR(partial + barge-in 开嗓即时打断);
  • OpenAI /v1/chat/completions 统一流式对话;
  • 流式增量按标点即时分句(StreamingSentenceBuffer);
  • 串行 TTS 播报队列(避免通道并发 speak 冲突);
  • 车险场景专属防重复/复述护栏(guardInsuranceResponse,通过滑动窗口与词集合比对避免机器人反复车轱辘话);
  • 联系方式抽取与纠错闭环(extractWechatContact、isCorrectionIntent,识别“刚才号码不对”并重新核对新号码)。

配置项(config.toml 中 businessConfig["insurance-chat-business"],可选;缺省回退到 llm-chat-business 或全局 [llm]):

  • endpoint:OpenAI 兼容端点 URL(例如 http://127.0.0.1:11434/v1/chat/completions);
  • apiKey:接口鉴权 API Key;
  • model:大模型名称;
  • requestTimeoutMs:请求超时毫秒数。

适合场景:金融保险外呼电销、续保关怀、活动推广与意向线索收集。


duplex-ai-chat-demo-business ​

原生全双工语音对话演示。接通后启动录音与 ctx.duplex,播放可打断的开场白;onFinal 把非空识别结果放进串行队列。普通文本回显,识别到“挂机”“挂断”“再见”“拜拜”或“不需要了”时播放不可打断的告别语并挂机。业务在退出时等待队列完成并停止全双工链路。代码见 src/business/demo/duplex-ai-chat-demo-business.ts。


voice-agent-demo-business ​

业务名:voice-agent-demo-business

分类:demo(代码定义:category: "demo")

目的:Voice Agent SDK 端到端易用性样板业务。演示如何通过声明式结构化智能体定义(defineVoiceAgent)与一行式编排器(runVoiceAgent),快速构建具备开场白、连续 ASR、VAD/文本双打断、流式大模型生成与即产即播 TTS 的完整语音对话闭环,替代传统手写复杂状态机与队列胶水代码。

关键能力:

  • 声明式智能体角色(defineVoiceAgent):角色设定(persona.role)、行为约束(persona.constraints)、对话样例(persona.examples)与采样参数(sampling),由 SDK 统一编译规范化 System Prompt;
  • 一行式对话编排运行(runVoiceAgent):自动托管呼入接通、开场白播报、整通持续 ASR、开嗓即打断(bargeInTrigger: "partial")、流式分句与分片 TTS 播报、静默催问与超时收尾;
  • 双通道 Provider 适配:默认走直连 OpenAI 兼容接口(channel: "openai"),亦可切换为 callout-server 策略通道(channel: "callout")。

适合场景:极简大模型电话助手、Voice Agent 快速验证与开发样板。代码见 src/business/demo/voice-agent-demo-business.ts。


业务路由数据示意 ​

call_businesses 表(启动期由代码侧注册表全量同步,下表即当前全部 26 条业务记录; businessCode 为主键)。businessCategory 是按主场景声明的 代码权威四值分类(outbound / inbound / assist / demo):它只用于 API/UI 展示、 分组和筛选,不参与路由、号码映射、campaign 或 FreeSWITCH 变量。

businessCodebusinessCategorybusinessNameisEnabled
agent-conference-guestassist坐席入会(AI 转人工)业务1
browser-softphone-assist-businessassist浏览器软电话辅助坐席业务1
callout-flow-businessoutbound通用外呼流程解释器1
conference-guest-businessinbound会议嘉宾业务1
conference-host-businessinbound会议主持人业务1
kb-audio-chat-businessoutbound知识库录音对话业务(实时盯屏+人工介入)1
kb-llm-chat-businessoutbound大模型语音对话业务(实时盯屏+人工介入)1
llm-chat-businessinbound真实大模型对话业务1
verification-code-businessinbound验证码识别打印业务1
insurance-chat-businessoutbound大模型车险电销对话业务1
ai-voice-platform-demo-businessdemoAI 语音平台演示业务1
call-hear-businessdemo单呼持续 ASR 演示业务1
call-transfer-demo-businessdemoASR 确认转人工演示业务1
callout-platform-demo-businessdemoAI 外呼平台产品介绍业务1
carrier-ringback-detection-demo-businessdemo运营商回铃音检测演示业务1
carrier-simulator-businessdemo运营商早媒体模拟业务1
conference-background-audio-demo-businessdemo通话背景音演示业务1
conference-control-demo-businessdemo会议全量会控演示业务1
default-call-businessdemo默认回显业务1
dtmf-businessdemo语音与 DTMF 并发输入演示1
originate-and-bridge-demo-businessdemo外呼后桥接演示业务1
parked-dial-demo-businessdemo停泊外呼确认桥接演示业务1
prompt-then-recognize-echo-businessdemo顺序播报识别回显业务1
verbose-prompt-then-recognize-businessdemo详细日志播报识别回显业务1
duplex-ai-chat-demo-businessdemo原生全双工 AI 语音对话演示业务1
voice-agent-demo-businessdemoVoice Agent SDK 演示业务1

call_business_number_mappings 表样例(按被叫号码命中):

iddestinationNumberbusinessCodeisEnabled
19911default-call-business1
29912prompt-then-recognize-echo-business1
31000conference-host-business1
41001conference-guest-business1
59919dtmf-business1

注意:HTTP 外呼时优先使用 businessCode,回拨进入 ESL 后 business_code 通道变量 非空则命中 call_businesses;否则按被叫号查 call_business_number_mappings inner join。

文档与代码在同一仓库维护,现有 Markdown 是唯一内容源。