外观
现有业务说明文档
本文档列出 src/business/ 下当前实现并注册到 business-registry.ts 的所有 业务,包含:业务名(用于数据库路由)、目的、关键参数、流程要点。
业务一览:
| 业务名 | 用途 | 关键能力 |
|---|---|---|
default-call-business | 默认循环识别 + 回显 | hear 循环、关键字挂机 |
ai-voice-platform-demo-business | AI 语音平台 + MicroSIP 视频演示流程 | callHear 持续 ASR、模拟 LLM、演示日志 |
ollama-chat-business | 真实大模型语音对话(接本地 Ollama LLM) | callHear 持续 ASR + Ollama /api/chat 流式 + TTS 播报 |
kb-audio-chat-business | 知识库录音对话(客户话术→知识库→录音播放)+ 实时盯屏/人工介入 | callHear 持续 ASR + 知识库 HTTP 查询 + 录音 playback + 逐轮进度上报 + 会话注册表介入 |
callout-platform-demo-business | 外呼平台能力演示 | 外呼变量读取、录音、ASR、结果回写 |
callout-flow-business | 通用外呼流程解释器:按 callout-server 流程脚本逐节点执行 | 流程图 JSON + NLU decision/extract + transfer 转人工 + 结果回写 |
prompt-then-recognize-echo-business | 先播报后识别再回显,一次性 | 顺序 speak/hear,不带 barge-in |
verbose-prompt-then-recognize-business | 与上面相同,但回调全开,用于排查 | SpeakHandlers / HearHandlers 全集 |
call-hear-business | 整通持续 ASR,按 final 文本驱动 | callHear + 串行 actionQueue |
dtmf-business | 语音 + DTMF 并发,先到先抢占 | onDtmf + cancelHear |
carrier-simulator-business | 模拟三大运营商早媒体(空号/关机/无法接通/无人接听/忙/停机) | pre_answer + TTS 循环播报、按通道变量选样本 |
carrier-ringback-detection-demo-business | 回铃音检测演示:选类型 → 外呼模拟器 → 早媒体 ASR 检测 | originateWithRingbackDetection + DTMF/语音菜单 |
conference-host-business | 会议主持人:邀请 + 启动会议 ASR + 广播 | originate + conference.* |
conference-guest-business | 会议访客:拿到 conferenceId 后入会 | conference.join |
conference-control-demo-business | 会议全量会控演示:DTMF 菜单邀请/锁会/录音/静音/禁听/踢出/公告/状态/结束 | conference.* 全集 + onDtmf |
agent-conference-guest-business | 坐席入会(AI 转人工的坐席侧) | conference.join + 通道变量角色/flags |
browser-softphone-assist-business | 浏览器软电话辅助坐席:坐席网页呼入建会并外呼客户 | SIP Header 会话恢复 + 会议转写推流 + 前端命令循环 |
call-transfer-demo-business | 转人工(直接 bridge) | bridgeToNumber |
originate-and-bridge-demo-business | 转人工(先外呼 B-leg 再桥接) | originateAndBridge |
parked-dial-demo-business | 帮拨号:park → 确认 → 桥接 | originateParkedCall + bridgeCall |
verification-code-business | 验证码识别:持续 ASR + 自动按键应答 + 结果上报 | callHear + postHangupDrainMs 尾部补收 + 自动 DTMF + HTTP 上报 |
default-call-business
业务名:default-call-business
目的:调试 / 演示用的默认业务。摘机后循环 hear,识别成功就回显,识别 失败就静默重听,听到 “挂机” 二字则播放结束语后挂机。
关键常量(位于业务文件顶部):
TTS_TEXT = "欢迎测试语音合成, 你好世界"— 首轮提示音TTS_REPEAT_PREFIX = "你刚刚说了,"— 回显前缀
流程:
answer+sleep settleDelayMsstartRecording(默认目录与文件名)- 循环:
hear({ prompt, interruptible: true, bargeInTrigger: "vad", timeoutMs: 10000 })- 识别成功且文本 == "挂机" → 播 "好的,再见" →
hangup→ 跳出循环 - 识别成功其他文本 → 用
回显前缀 + 文本作为下一轮提示音继续 - 超时 / 失败 → 仅落日志、继续监听
- 通话由对端挂机或 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— 命中“挂机 / 挂断 / 再见 / 拜拜”后结束通话
流程:
answer+sleep settleDelayMsstartRecording,记录录音文件位置callHear({ partialResults: true, interruptPlayback: true, bargeInTrigger: "vad" })- 播放欢迎语:“您好!欢迎致电智能语音助手,请问有什么可以帮您?”
- ASR partial / final 文本写入
demo_asr_stream_text日志,供控制台录屏展示 - final 文本命中告警 / 订单相关关键词:
- 写入
demo_llm_thinking - 逐段写入
demo_llm_stream_delta - 播放固定查询结果
- 写入
- final 文本命中挂机关键词:播放结束语并
hangup("NORMAL_CLEARING") - 其它文本:播报演示提示,引导用户说告警订单查询句式
适合场景:项目介绍视频、MicroSIP 软电话演示、ASR/LLM/TTS/RTP 状态控制台 录屏。该业务不接入真实 LLM 或真实工单系统,查询结果为演示用固定文本。
路由建议:服务启动会把该业务同步到 call_businesses 表,可直接使用 businessCode = ai-voice-platform-demo-business;若要从 MicroSIP 拨固定内线进入, 在 call_business_number_mappings 中把演示内线号码映射到该业务。
ollama-chat-business
业务名:ollama-chat-business(文件夹形态 ollama-chat-business/, 入口 index.ts,话术 / 关键词在 prompts.ts,Ollama 客户端在 ollama-chat-client.ts)
目的:接真实本地大模型的语音对话业务。与 ai-voice-platform-demo-business 的「模拟 LLM 固定答复」不同,本业务把识别到的 final 文本送进 Ollama 的 /api/chat 流式接口,拿到完整回复后再用 TTS 播报,是一个最小但可跑通的 「ASR → 本地 LLM → TTS」闭环。每通电话维护独立的 messages 上下文(含 system prompt),实现多轮记忆。
必填配置(位于 config.json.businessConfig["ollama-chat-business"]):
endpoint— 完整的 HTTP(S) Ollama/api/chat地址model— 模型名称,必须是非空字符串requestTimeoutMs— 单轮正整数超时(毫秒,样例为60000)
缺少配置、URL 非法、URL 不是 /api/chat、模型为空或超时不是正整数时, callflow-esl 会在启动期直接失败,不会回退到 localhost。
本业务也可在 call_businesses.businessConfig 和号码映射的 mappingConfig 中直接 覆盖这三个字段,例如 { "model": "qwen3.5:2b" }。新通话按“文件 < 业务 < 映射” 深合并;显式 business_code 路由跳过映射层。合并结果无效时不会请求 Ollama, 而是记录 business_config_invalid 并播放“系统错误”。
关键常量(位于 prompts.ts):
SYSTEM_PROMPT— 约束模型用简洁口语化中文、避免 Markdown / 表格,适合语音播报OPENING_PROMPT/GOODBYE_PROMPT/ERROR_FALLBACK_PROMPT— 欢迎 / 告别 / 兜底语HANGUP_KEYWORDS = ["挂机", "挂断", "再见", "拜拜"]— 命中即结束通话
流程:
answer+sleep settleDelayMs+startRecording- 初始化每通电话的
messages(首条为SYSTEM_PROMPT) callHear({ partialResults: true, interruptPlayback: true, bargeInTrigger: "begin-speaking", noSpeechTimeoutMs: 15000, noTextTimeoutMs: 20000 })- 播放欢迎语后
waitForHangup() onFinal经串行actionQueue触发handleFinalTranscript:- 文本为空 → 跳过;命中挂机关键词 → 播告别语 →
hangup("NORMAL_CLEARING") - 否则把用户文本追加进
messages,调用requestOllamaChat(流式,onDelta逐段落日志),回复追加进messages后用 TTS 播报 - Ollama 调用失败 / 空回复 → 落
ollama_chat_llm_failed日志(errorKind区分connection/http/timeout/response)并播ERROR_FALLBACK_PROMPT兜底;新一轮、 挂机或业务关闭导致的主动取消单独记录ollama_chat_llm_aborted
- 文本为空 → 跳过;命中挂机关键词 → 播告别语 →
finally:标记shuttingDown→asrSession.stop("business-complete")→ 等actionQueue排空
前置条件:businessConfig["ollama-chat-business"].endpoint 可从 callflow-esl 主机访问, 且 Ollama 已准备该子项 model 指定的模型;ASR / TTS 服务正常。begin-speaking 打断要求 ASR 推送 speech_started 事件(1.0 已支持)。
适合场景:真实 AI 电话客服 / 语音助手的最小可用样板;想换成 OpenAI 兼容 接口或其它 LLM,只改 ollama-chat-client.ts 与 prompts.ts 即可。
kb-audio-chat-business
业务名:kb-audio-chat-business(文件夹形态 kb-audio-chat-business/, 入口 index.ts,知识库客户端与接口契约在 kb-client.ts;盯屏/介入基础设施 复用 shared/live-session.ts)
目的:知识库对话业务。形态与 ollama-chat-business 相同(持续 ASR + 逐轮问答),但回答来自 llm-chat-server 的独立 conversation strategy, 不经过 Chat 节点或 Ollama;策略返回文字后统一由 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.calloutServer:callout-server 的baseUrl / token / requestTimeoutMs; 代码固定调用/api/call-progress、/api/call-results、/api/transfers/route与/api/transfers/end
流程:
answer+ settle + 读取 callout 身份变量 + 双声道录音- 登记
liveCallSessionRegistry会话并上报「通话开始」 - 调用 conversation strategy
action=start,缓存本通话的开场白与失败话术;成功后才启动callHear(barge-in)并播放非空开场白,失败则直接挂机并进入清理 - 每条 final:写 transcript(同步实时上报)→ 查询策略 → 本地 TTS 播放
answerText→hangup=true时播完挂机。 每轮返回都经串行门排队按序播放:客户连说多句时各轮话术依次播出(播放中仍 可被 barge-in 打断),不因客户新发言作废后到的应答 - 每秒轮询介入信号:命中后等当前播报结束 →
agentId定向转人工 (reserveSpecificAgent)→ 坐席入会成功则 AI 停止应答,客户腿单呼 ASR 切换 为会议级转写(conference.hear),坐席(speaker=agent)与客户的发言持续 写 transcript 实时上报盯屏;失败则停会议转写、重启单呼 ASR 恢复 AI 对话 - 转人工路由成功后保存会议 ID;客户在坐席振铃或已入会阶段挂机时,
finally调用POST /api/transfers/end,令该会议全部坐席/主管 SIP 腿结束,调用失败只记结构化日志 - 其余
finally清理:注销会话 + 上报「通话结束」+ 整段结果回写(result.type = "kb_audio_chat",含 intervention 摘要)+ 挂机
适合场景:需要接入确定性问答策略并复用本地 TTS,批量外呼时 坐席「一人盯多路、有意向即插入」的人机协同外呼。
业务名:callout-platform-demo-business
目的:配合 apps/callout-server 的外呼任务演示平台能力。任务接通后进入 本业务,播报外呼平台能力说明,识别客户回应,启动通话录音,并把业务结果回写到 callout 外呼记录,供页面展示摘要、意向和录音回放。
依赖配置:
config.callout.calloutServer.baseUrl:默认http://127.0.0.1:9920,结果回写路径/api/call-results由代码追加config.callout.calloutServer.token:callout-server 配置internalApi.callResultToken后必须同步填写;请求会携带X-Callout-Internal-Tokenconfig.callout.calloutServer.requestTimeoutMs:callout-server 请求独立超时(默认 10000ms)recording.directory:FreeSWITCH 进程可写的录音目录。外呼结果回写只携带 录音短文件名,播放 URL 由 callout-server 的媒体配置统一拼接。
流程:
answer+sleep settleDelayMs- 读取
callout_request_id、callout_campaign_id、callout_contact_id startRecording,录音文件名带外呼 requestId,便于排查- 播放外呼平台能力介绍
hear识别客户回应- 根据文本归类
interested/not_interested/completed POST /api/call-results回写businessStatus、intentLevel、intentCategory、summary、recordingUrl、逐句transcript与直接承载结构化演示数据的businessResult- 播放结束语并
hangup("NORMAL_CLEARING")
适合场景:外呼平台演示、录音质检回放验证、callout 与 callflow 联调冒烟。
路由建议:在 callout-webpage 新建外呼任务时选择 callout-platform-demo-business。该路径依赖 callout-server originate 时注入 的外呼变量,不建议作为普通呼入号码映射业务使用。
callout-flow-business
业务名:callout-flow-business(文件夹形态 callout-flow-business/,入口 index.ts,流程图解析在 flow-graph.ts,节点运行辅助在 flow-runtime.ts, callout / NLU HTTP 访问在 http-client.ts)
目的:通用外呼流程解释器——系统二「流程脚本」的运行时执行端。业务本身不 写死任何话术:接通后按通道变量 script_id 从 callout-server 拉取流程脚本运行时 JSON(config.callout.calloutServer.baseUrl + GET /api/v1/scripts/{id}/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.*):calloutServer(脚本运行时、transfer 节点预占坐席、 进度和结果回写)、nlu(collect.extract_profile 抽取与 router.decision_profile 决策, 走 llm-nlu-server)、chat(Chat 节点 SSE 对话)。每组分别配置 baseUrl / token / requestTimeoutMs。
流程要点:
- 读
script_id/callout_attempt_id,拉取并解析流程脚本运行时 JSON answer+startRecording,从start节点开始沿出边遍历执行:send:渲染模板变量后 TTS 播报collect:播报问题并hear,把客户回答交给 NLUextractprofile 抽取字段router:把上下文交给 NLUdecisionprofile,按返回分支选择出边transfer:调requestHumanAgentTransfer预占坐席并把客户腿入会(成功即 结束流程;失败播unavailable_prompt走默认出边兜底)end:播报结束语并收尾
- 防循环保护:最多执行
MAX_STEPS = 100个节点,超限视为失败并回写诊断 - 结束(含异常)时把逐句 transcript、抽取字段、分支路径与录音短文件名回写
POST /api/call-results
适合场景:所有由 callout-webpage 可视化编排的外呼话术。节点语义与 NLU profile 约定见 apps/callout-server/docs/flow-script-nlu-development.md。
prompt-then-recognize-echo-business
业务名:prompt-then-recognize-echo-business
目的:最简单的顺序业务:先独立播报一次开场白,等播报完成再开一轮 hear, 识别到文本就回显然后挂机。speak 与 hear 是顺序执行的,不存在提示音 期间的 barge-in。
关键常量:
TTS_TEXT、TTS_REPEAT_PREFIX
流程:
answer+sleep settleDelayMsspeak({ kind: "tts", text: TTS_TEXT })— 独立播报,无 barge-inhear({})— 默认参数,等用户开口- 识别成功 →
speak(${TTS_REPEAT_PREFIX}${text})→hangup("NORMAL_CLEARING") - 识别失败 / 超时 → 落日志后退出,由 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 = "好的,再见"
流程:
answer+sleep settleDelayMs+startRecording- 启动
ctx.callHear({ partialResults: true, interruptPlayback: true, bargeInTrigger: "vad" }) - 播放欢迎语 +
waitForHangup() onFinal串行触发:- 文本为空 → 跳过
- 文本 == "挂机" → 标记 shuttingDown → 播报结束语 →
hangup - 其它 → 回显
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 的窗口REPORT_URL = "https://app.yuntl.cc/skkks"(report-client.ts)— 结果上报地址, 示例接口,部署时改成自己的回收端点;超时 5s、最多 3 次、失败仅记日志不影响收尾- DTMF 发送(
dtmf-sender.ts):最多 3 次、重试间隔[300, 800]ms,用最近 3 段 final 组成短窗口跨片段提取按键,发送成功后对该数字去重
流程:
answer+sleep settleDelayMs+startRecording- 启动
ctx.callHear({ partialResults: true, postHangupDrainMs: 1500 }):onResult(含 partial)→ 写入 ASR 采集器并落日志onFinal→ 交给 DTMF 发送器:命中中文「按 X」/ 英文「press X」提示就自动sendDtmf(中文数字「四」、英文「four」均可归一为按键)onPostHangupResult→ 挂机后尾段继续写入采集器(不再驱动按键)onError/onStop→ 写业务日志
waitForHangup()等对端挂机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_PREFIXBARGE_IN_ENABLED = true、BARGE_IN_TRIGGER = "vad"HANGUP_KEYWORDS = ["挂断"]
流程:
answer+sleep settleDelayMs- 注册 DTMF 监听:抢占当前轮次、播报数字播报
- 循环:
- 启动
hear({ prompt, interruptible: true, bargeInTrigger: "vad", timeoutMs: 10000 }) - 若被 DTMF 抢占 → 等 DTMF 动作完成后进入下一轮
- 识别成功且命中关键词 “挂断” → 播报 “即将挂断电话” → 挂机
- 其它识别结果 → 落日志、丢弃文本后下一轮
- 启动
finally取消 DTMF 订阅
适合场景:IVR 菜单中需要同时接受语音和数字键的场景。
carrier-simulator-business
业务名:carrier-simulator-business(文件夹形态 carrier-simulator-business/, 旧名 carrier-empty-number-simulator-business 已废弃,导出符号仍兼容别名 carrierEmptyNumberSimulatorBusiness)
目的:模拟三大运营商(移动 / 联通 / 电信)常见的早媒体提示音,作为 carrier-ringback-detection-demo-business 中 originateWithRingbackDetection 链路的被叫陪练。 不发 200 OK,只 pre_answer 发 183 early media,再用项目统一 TTS 链路 循环播报提示音文本;业务自身不主动挂机,由主叫取消或上游检测器清理 B-leg。
样本库(samples.ts,共 18 条):覆盖 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_assistantcarrier_simulator_repeat_gap_ms:提示音重复间隔,默认 1000mscarrier_simulator_no_answer_ring_ms:无人接听场景先放回铃的时长,默认 30000mscarrier_simulator_ringback_tone:回铃 tone_stream,默认tone_stream://%(1000,4000,450);loops=1
样本选择优先级(selection.ts):carrier+prompt 精确命中 → 仅 carrier 时该运营商内随机 → 仅 prompt 时跨运营商随机 → 都缺失/非法则全量随机。
流程:
- 读初始通道变量,
selectCarrierPrompt选样本、resolveSimulatorSettings解析参数 pre_answer发 183 early media(注意不是answer)+sleep settleDelayMs- 按样本
timeline分支:ring_then_announcement_loop(仅无人接听场景):先循环playback回铃音noAnswerRingMs毫秒,再进入提示音播报循环announcement_loop(其余场景):直接进入提示音播报循环
- 提示音循环无本地退出条件:
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 = "sofia/gateway/local-freeswitch-internal/"DETECTION_TIMEOUT_MS = 20000;无人接听场景放宽到NO_ANSWER_DETECTION_TIMEOUT_MS = 45000- 六类可测类型:按 1~6(空号/关机/无法接通/无人接听/忙/停机),按 7 或说“挂断/挂机”退出
MAX_SELECTION_ATTEMPTS = 2
流程:
answer+sleep settleDelayMs+ 播放开场白askForSelection:每轮hear(带提示音 + barge-in)与onDtmf并发, DTMF 与语音先到先抢占;语音按关键字匹配类型,DTMF 按 1~6/7 匹配- 选中类型后
runDetectionRound:用buildSimulatorVariables注入business_code/carrier_simulator_prompt等变量,调用originateWithRingbackDetection(SIMULATOR_NUMBER, { dialStringTemplate, variables, ... }) - 若
status === "answered"(极少数),hangupCall掉 B-leg - 播报演示结果:选择类型、外呼状态、检测归类(
carrierPromptKind)、识别文本、失败原因 - 回到菜单继续,直到用户挂机或两次未识别到有效选择
适合场景:演示 / 验收回铃音检测;无需真实外线,单机即可跑通 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
流程:
answer+sleep settleDelayMs+startRecording- 校验主叫号 ==
HOST_NUMBER,否则播放拒绝语 + 挂机 setVariables({ conference_id, conference_role: "host", conference_host_number })- 播开场白
ctx.originate({ destinationNumber: GUEST_NUMBER, businessCode: GUEST_BUSINESS_CODE, variables: { conference_id, conference_role: "guest" } })- 启动会议级 ASR
ctx.conference.hear:onResult(partial):落日志onFinal:构造“{ 中文化主叫号 } 说:{ 文本 }” 的播报,再ctx.conference.play广播给整个会议
ctx.conference.play({ delaySeconds: 2 })预排一段“即将开始”的会议播报ctx.conference.join({ flags: ["moderator"] })
适合场景:多方语音会议、AI 会议秘书。number2ChinaNum 把号码转成 “一零零一” 便于 TTS 播报。
conference-guest-business
业务名:conference-guest-business
目的:会议访客。读 conference_id 后入会。
流程:
answer+sleep settleDelayMsgetVariable("conference_id");若空 → 播放“会议参数缺失” → 挂机setVariable("conference_role", "guest")- 播放 “正在加入会议”
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 | 结束会议 |
流程要点:
answer+sleep settleDelayMs+startRecording- 生成
conferenceId,setVariables({ conference_id, conference_role: "host" }) ctx.conference.join({ flags: ["moderator"] })- 进入 DTMF 菜单循环:
onDtmf抢占当前播报,按键分发到对应会控动作 - 每个动作调用
ctx.conference.*(inviteMembers/lock/unlock/startRecording/stopRecording/muteMember/unmuteMember/deafMember/undeafMember/kickMember/play/getState/end), 并播报执行结果 - 按 9 结束会议或主持人挂机退出
适合场景:会议会控能力的完整演示与回归;可作为「会议主席台」类业务的脚手架。 会控能力的 HTTP 外部控制面见 apps/callflow-esl/src/http/conference-server.ts (/conferences/* RESTful API)。
agent-conference-guest-business
业务名: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)
流程:
answer+sleep settleDelayMsgetVariable("conference_id");缺失 → 播报兜底语 →hangup("NORMAL_TEMPORARY_FAILURE")- 读取
callout_conference_role/callout_conference_flags(透传角色与会议 flags) 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),开启会议转写实时推送前端, 并持续消费前端下发的控制命令。
流程要点:
- 坐席 SIP 呼入 →
answer+ settle → 读 SIP Header sessionId;缺失或会话失效 → 播错误提示 → 挂机(CALL_REJECTED) - 会话有效 → 建会议(role=host)+ 录音 + originate 客户入会(guest);客户呼叫 失败 → 播报失败 + 结束会话 → 挂机
- 成功后开启会议转写:
onResult按角色 upsert 消息推送前端(坐席通话台实时字幕) - 并行两条线:
conference.join(host,阻塞至坐席离开)与runCommandLoop消费 前端命令——agent_text(打字文本 TTS 仅注入客户腿)、hold/resume(deaf / undeaf 坐席腿)、dtmf(发给客户腿)、transfer(v1 仅记录)、end(结束会议 + 挂机) 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
流程:
answer+sleep settleDelayMs- 欢迎语
askForConfirmation:最多两次hear,匹配“确认/否”意图- 确认 → 播 “正在为您转接人工” →
ctx.bridgeToNumber(TRANSFER_TARGET_NUMBER) - 桥接成功 → 主动
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
流程:
answer+sleep settleDelayMs- 欢迎语
askForConfirmation:最多两次hear,匹配“确认 / 转人工 / 否”意图- 确认 → 播 “正在为您发起后台外呼” →
ctx.originateAndBridge(TRANSFER_TARGET_NUMBER) status === "bridged"→ 落日志后主动ctx.hangup(),让 FreeSWITCH 完成两端通话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 = 20000MAX_CONFIRM_ATTEMPTS = 2
流程要点:
- 欢迎语 + 询问是否需要拨号
- 进入
while (true)重试循环:- 播 “正在为您拨号”
originateParkedCall(target, { dialStringTemplate: "user/" })- 不是
parked→ 询问是否重试 parked→ 询问是否连接- 确认 →
bridgeCall(bLegUuid, { preserveCurrentCallAfterBridge: true }) - bridge 成功 →
Promise.race等待被叫 / 主叫挂机;被叫先挂可再询问重试 - bridge 失败 →
hangupCall(bLegUuid)+ 询问重试 - 不连接 →
hangupCall(bLegUuid, "ORIGINATOR_CANCEL")+ 询问重试
- 确认 →
适合场景:智能助理代用户拨号、电话秘书。
业务路由数据示意
call_businesses 表样例(按 businessCode 命中):
| id | businessCode | businessName | isEnabled |
|---|---|---|---|
| 1 | default-call-business | 默认回显业务 | 1 |
| 2 | prompt-then-recognize-echo-business | 顺序播报识别回显业务 | 1 |
| 3 | conference-host-business | 会议主持人业务 | 1 |
| 4 | conference-guest-business | 会议嘉宾业务 | 1 |
| 5 | dtmf-business | 语音与 DTMF 并发输入演示 | 1 |
| 6 | call-hear-business | 单呼持续 ASR 演示业务 | 1 |
| 7 | call-transfer-demo-business | ASR 确认转人工演示业务 | 1 |
| 8 | originate-and-bridge-demo-business | 外呼后桥接演示业务 | 1 |
| 9 | parked-dial-demo-business | 停泊外呼确认桥接演示业务 | 1 |
| 10 | carrier-simulator-business | 运营商早媒体模拟业务 | 1 |
| 11 | ai-voice-platform-demo-business | AI 语音平台演示业务 | 1 |
| 12 | callout-platform-demo-business | AI 外呼平台产品介绍业务 | 1 |
| 13 | carrier-ringback-detection-demo-business | 运营商回铃音检测演示业务 | 1 |
| 14 | conference-control-demo-business | 会议全量会控演示业务 | 1 |
| 15 | agent-conference-guest | 坐席入会(AI 转人工)业务 | 1 |
| 16 | ollama-chat-business | 真实大模型对话业务 | 1 |
| 17 | verification-code-business | 验证码识别打印业务 | 1 |
| 18 | verbose-prompt-then-recognize-business | 详细日志播报识别回显业务 | 1 |
| 19 | callout-flow-business | 通用外呼流程解释器 | 1 |
| 20 | browser-softphone-assist-business | 浏览器软电话辅助坐席业务 | 1 |
call_business_number_mappings 表样例(按被叫号码命中):
| id | destinationNumber | businessId | isEnabled |
|---|---|---|---|
| 1 | 9911 | 1 | 1 |
| 2 | 9912 | 2 | 1 |
| 3 | 1000 | 3 | 1 |
| 4 | 1001 | 4 | 1 |
| 5 | 9919 | 5 | 1 |
注意:HTTP 外呼时优先使用 businessCode,回拨进入 ESL 后 business_code 通道变量 非空则命中 call_businesses;否则按被叫号查 call_business_number_mappings inner join。