外观
配置参考
配置
config.json 字段:
json
{
"esl-server": {
"host": "0.0.0.0",
"port": 9911,
"commandTimeoutMs": 15000,
"asrTimeoutMs": 30000,
"settleDelayMs": 1000
},
"logging": {
"dir": "./logs",
"level": "info",
"maxSize": "20m",
"maxFiles": "14d"
},
"http-server": {
"host": "0.0.0.0",
"port": 9912,
"authToken": ["esl-0x21"]
},
"freeswitch": {
"host": "192.168.2.184",
"port": 8021,
"password": "ClueCon",
"callbackHost": "192.168.2.246",
"callbackPort": 9911,
"originateDialStringTemplate": "user/{{destinationNumber}}"
},
"audioFork": {
"wsUrl": "ws://192.168.2.246:10096/audio",
"bugName": "callflow_asr",
"mixType": "mono",
"sampleRate": "16k",
"bidirectionalAudioEnabled": false,
"bidirectionalAudioStreamEnabled": false,
"bidirectionalAudioStreamSampleRate": 16000,
"connectTimeoutMs": 5000,
"eventSubscriptionMode": "all"
},
"tts": {
"defaults": {
"speakerId": 0,
"speed": 1,
"requestTimeoutMs": 10000
},
"use": {
"call": "shout",
"conference": "wav"
},
"profiles": {
"wav": {
"endpoint": "http://127.0.0.1:9080/tts",
"playbackTarget": "wav-url",
"fsPlaybackBaseDir": ""
},
"shout": {
"endpoint": "http://127.0.0.1:9080/tts-stream",
"playbackPrefix": "shout://"
}
}
},
"businessConfig": {
"llm-chat-business": {
"endpoint": "http://127.0.0.1:8000/v1/chat/completions",
"apiKey": "",
"model": "qwen3.5:2b",
"requestTimeoutMs": 60000
},
"kb-audio-chat-business": {
"endpoint": "http://127.0.0.1:9930/api/v1/conversation",
"strategy": "kb-mock",
"token": "chat-token",
"requestTimeoutMs": 10000
},
"verification-code-business": {
"endpoint": "https://app.yuntl.cc/push_sms",
"requestTimeoutMs": 5000,
"maxAttempts": 3
}
},
"recording": {
"directory": "/usr/local/freeswitch/record"
},
"callout": {
"calloutServer": {
"baseUrl": "http://127.0.0.1:9920",
"token": "callout-token",
"requestTimeoutMs": 10000
},
"nlu": {
"baseUrl": "http://127.0.0.1:9930",
"token": "nlu-token",
"requestTimeoutMs": 30000
},
"chat": {
"baseUrl": "http://127.0.0.1:9930",
"token": "chat-token",
"requestTimeoutMs": 60000
}
},
"db": {
"url": "postgres://postgres:postgres@localhost:5432/freeswitch"
},
"redis": {
"url": "redis://default:redis@127.0.0.1:6379/0",
"keyPrefix": "callflow",
"ttlMs": 600000
}
}esl-server
host/port:ESL outbound 监听地址与端口commandTimeoutMs:ESL 命令等待 reply 的超时asrTimeoutMs:单轮 hear 的默认等待超时(业务可在hear()入参覆盖)settleDelayMs:手动answer后建议的等待,给信令稳定
http-server
host/port:HTTP 监听地址,承载/outbound-calls与运行时配置端点authToken:HTTP 接口共享令牌数组。未配置或为空时不启用鉴权;配置一个或多个 非空值时,请求必须在token请求头中携带其中一个匹配值,否则返回 401
freeswitch
host/port/password:inbound ESL(用于 originate)callbackHost/callbackPort:FreeSWITCH 在外呼接通后回拨当前 ESL 服务 使用的地址,必须可以从 FreeSWITCH 进程访问到originateDialStringTemplate:所有外呼场景的默认拨号串模板,必须包含占位符;业务可在调用入参中传dialStringTemplate覆盖(同样需包含占位符)
audioFork
wsUrl:mod_audio_fork 要连接的 ASR WebSocket 地址(即sherpa-asr-online-server)bugName:media bug 名称,用于uuid_audio_fork stop精确定位mixType/sampleRate:上行音频通道与采样率,mono/16k适合大多数 ASRbidirectionalAudioEnabled/bidirectionalAudioStreamEnabled:下行音频通 道,hear-only 模式保持关闭connectTimeoutMs:等待 WebSocket 连接成功事件的超时eventSubscriptionMode:ESL 事件订阅策略"all":event plain ALL,再在应用侧按 UUID 过滤;最稳但高并发下 事件量较大"channel-plus-custom":myevents+ CUSTOM,保留当前通道基础事件并额外 订阅 audio_fork / conference 相关 CUSTOM 事件
tts
采用 defaults / use / profiles 三段结构:defaults 是业务未指定时的合成默认值 (speakerId / speed / requestTimeoutMs);use.call / use.conference 分别选择 单呼与会议使用的 profile("wav" 或 "shout");profiles.wav.endpoint 对应 sherpa-tts-server 的 POST /tts,profiles.shout.endpoint 对应 POST /tts-stream, profiles.shout.playbackPrefix 在启用 mod_shout 时通常为 "shout://"。 逐字段说明与播放目标映射(wav-url / file-path)见 TTS 对接。
businessConfig
按 registry / 数据库中的稳定业务编码保存各业务的文件层默认配置。容器和每个子项都必须是 JSON 对象;出现未注册业务编码会在启动期直接报错,避免拼写错误被静默忽略。
llm-chat-business 子项为可选项,包含:
endpoint:完整 OpenAIPOST /v1/chat/completions地址(GPUStack 部署模型的对外协议)apiKey:可选模型鉴权 Key;非空时写入Authorization: Bearer <key>model:单阶段流式对话模型名称(GPUStack 注册的模型名)requestTimeoutMs:单轮请求超时(正整数毫秒)
若在配置文件中填写,启动期会严格校验 URL、模型和超时格式(非法或不是 /v1/chat/completions 时报错退出); 若配置文件中未填写,启动期允许缺省。llm-chat-business 还支持数据库覆盖,call_businesses.businessConfig 与 call_business_number_mappings.mappingConfig 直接填写上述字段。最终配置按 config.json.businessConfig["llm-chat-business"] < call_businesses.businessConfig < call_business_number_mappings.mappingConfig 深合并:普通对象递归合并,数组、标量和 null 由高层整体覆盖。显式 business_code 路由不会读取号码映射层。若三层合并后最终值仍无效,通话记录 business_config_invalid、播放“系统错误”并结束,且不会请求模型。
kb-audio-chat-business 子项可省略;一旦填写,endpoint、strategy 和正整数 requestTimeoutMs 必须有效,token 可选且不会写入启动日志。路由命中该业务时仍会 对三层合并结果再次校验;若文件层缺省且数据库层不能补齐,通话按 business_config_invalid 结束,不发送知识库请求。
verification-code-business 子项为可选项,用于验证码识别结果外部 HTTP 表单上报与防重复呼叫拦截:
endpoint:必填,合法 HTTP(S) URL 接口地址requestTimeoutMs:可选单次上报请求超时(正整数毫秒,缺省 5000)maxAttempts:可选最大尝试次数包含首次(正整数,缺省 3)antiDuplicate:可选防重复呼叫拦截配置:enabled:可选布尔值,是否启用防重拦截(缺省 true)windowSeconds:可选正整数秒,滑动时间窗口大小(缺省 60)maxCalls:可选正整数次,时间窗口内最大允许呼叫数(缺省 1)
若未在配置中指定 endpoint,业务在通话挂机后会跳过上报并记录日志,不影响正常识别打印与挂机收尾。 若触发防重复呼叫拦截,通话在接通前直接挂机释放(NORMAL_CLEARING),不启动录音、识别与上报。
recording
directory:FreeSWITCH 进程可写的录音目录ctx.startRecording()只返回 FreeSWITCH 落盘路径filePath和短文件名fileName;完整播放 URL 由调用方媒体配置拼接
callout
外部依赖按 calloutServer、nlu、chat 三个服务边界配置,为选配(Optional); 未接入 callout-server 与 callai-server 时可整体缺省。nlu 与 chat 在当前部署里都指向 同一个 callai-server(默认 9930),但仍作为两组独立配置维护,便于分别设置超时与令牌。 若配置,每组统一包含:
baseUrl:服务基地址,只允许 HTTP(S),可带反向代理路径前缀;启动时去除尾斜杠, 不允许用户名、密码、query 或 fragmenttoken:该服务独立共享令牌;非空时请求携带X-Callout-Internal-Token,为空则不发requestTimeoutMs:该服务独立的正整数请求超时
API 路径由代码固定维护,不属于配置项。callout-server 使用 /api/call-results、 /api/call-progress、/api/transfers/route、/api/transfers/end 和 /api/v1/scripts/{id}/runtime;NLU 使用 /api/v1/nlu;Chat 使用 /api/v1/chat/stream。三个服务之间不会回退或混用 token、超时。
示例 token 仅用于本地联调;生产环境必须逐服务替换为高强度凭证,并限制配置文件读取权限。
db
url:PostgreSQL 连接串(当前配置只使用该字段)。业务路由会查call_businesses与call_business_number_mappings,schema 见src/db/schema.ts
redis
Redis 跨实例协调配置,是 callflow-esl 的必选依赖:会议协调、坐席实时盯屏 会话注册表、运行时配置重载广播都建立在它之上。缺配置或启动时连不上会阻止服务启动 (进程以非零码退出,交给 systemd/k8s 重启):
url:Redis 连接 URL,仅支持redis:/rediss:协议,例如redis://default:redis@127.0.0.1:6379/0keyPrefix:会议运行态、实例心跳、盯屏会话、reload 频道等 key/channel 的统一前缀ttlMs:会议运行态、实例心跳、盯屏会话等 key 的 TTL(毫秒)