配置参考
配置
config.toml 字段:
toml
[eslServer]
host = "0.0.0.0"
port = 9911
commandTimeoutMs = 15000
drainTimeoutMs = 30000
asrTimeoutMs = 30000
settleDelayMs = 1000
[logging]
dir = "./logs"
level = "info"
maxSize = "20m"
maxFiles = "14d"
[httpServer]
host = "0.0.0.0"
port = 9912
authToken = ["esl-0x21"]
[freeswitch]
host = "127.0.0.1"
port = 8021
password = "ClueCon"
callbackHost = "127.0.0.1"
callbackPort = 9911
originateDialStringTemplate = "user/{{destinationNumber}}"
ignoreEarlyMedia = true
[audioFork]
wsUrl = "ws://127.0.0.1:10096/audio"
bugName = "callflow_asr"
mixType = "mono"
sampleRate = "16k"
connectTimeoutMs = 5000
eventSubscriptionMode = "channel-plus-custom"
[duplex]
enabled = true
wsUrl = "ws://127.0.0.1:10091/audio"
healthUrl = "http://127.0.0.1:10091/health"
bugName = "audio_fork"
bargeInTrigger = "partial"
sampleRate = "16k"
mixType = "mono"
connectTimeoutMs = 5000
[tts.defaults]
speakerId = 0
speed = 1.1
requestTimeoutMs = 10000
[tts.use]
call = "stream"
conference = "stream"
[tts.profiles.wav]
endpoint = "http://127.0.0.1:9080/tts"
playbackTarget = "wav-url"
fsPlaybackBaseDir = ""
[tts.profiles.stream]
endpoint = "http://127.0.0.1:9080/tts-stream"
playbackPrefix = "audio_fork://"
[backgroundAudio]
enabled = true
defaultProfile = "office"
[backgroundAudio.profiles.office]
streamUrl = "local_stream://moh/16000"
volume = -36
[recording]
directory = "/usr/local/freeswitch/var/recordings"
[callout]
baseUrl = "http://127.0.0.1:9920"
token = "callout-token"
requestTimeoutMs = 10000
[db]
maxConnections = 30
idleTimeoutSeconds = 60
url = "postgres://postgres:postgres@localhost:5432/dev-freeswitch"
[redis]
url = "redis://default:redis@127.0.0.1:6379/0"
keyPrefix = "callflow"
ttlMs = 600000
[llm]
endpoint = "https://api.deepseek.com"
apiKey = ""
model = "deepseek-flash"
requestTimeoutMs = 60000
maxRetries = 2
# 业务自定义配置:支持自动继承全局 [llm] 或 [callout],也可声明局部字段覆盖
[businessConfig.llm-chat-business]
[businessConfig.kb-audio-chat-business]
strategy = "kb-mock"
[businessConfig.kb-llm-chat-business]
[businessConfig.verification-code-business]
endpoint = "https://app.yuntl.cc/push_sms"
requestTimeoutMs = 5000
[businessConfig.verification-code-business.antiDuplicate]
enabled = true
windowSeconds = 60
maxCalls = 1eslServer
host/port:ESL outbound 监听地址与端口commandTimeoutMs:ESL 命令等待 reply 的超时;超时位置继续占位一个同等周期,迟到回复被丢弃,无法恢复则关闭连接,不自动重试控制命令drainTimeoutMs:退出信号后的自然排空期限,默认 30000,必须为正整数;到期主动结束剩余会话并给予最多 5 秒收尾asrTimeoutMs:单轮 hear 的默认等待超时(业务可在hear()入参覆盖)settleDelayMs:手动answer后建议的等待,给信令稳定
logging
结构化日志配置(Bun 原生滚动文件日志器):
dir:日志存储目录(字符串,相对或绝对路径,默认"./logs")level:日志输出门限级别("debug"|"info"|"warn"|"error",默认"info")maxSize:单个日志文件切分的最大容量上限(字符串,如"20m"、"100m",默认"20m")maxFiles:历史日志归档保留周期(字符串,如"14d"表示保留 14 天,默认"14d")
日志文件按天切分(application-YYYY-MM-DD.log),单日文件超出 maxSize 时自动分卷(.1.log);跨天与启动时自动清理超过 maxFiles 的历史归档。
httpServer
host/port:HTTP 监听地址,承载健康检查、外呼与控制面接口;路由与鉴权见HTTP 接口authToken:HTTP 接口共享令牌数组。未配置或为空时不启用鉴权;配置一个或多个 非空值时,请求必须在token请求头中携带其中一个匹配值,否则返回 401
freeswitch
host/port/password:inbound ESL;常驻连接接收跨腿和会议事件,短连接用于 originate、会议控制与状态查询。启动必须完成常驻连接认证订阅callbackHost/callbackPort:FreeSWITCH 在外呼接通后回拨当前 ESL 服务 使用的地址,必须可以从 FreeSWITCH 进程访问到originateDialStringTemplate:所有外呼场景的默认拨号串模板,必须包含占位符;业务可在调用入参中传dialStringTemplate覆盖(同样需包含占位符)ignoreEarlyMedia:普通外呼是否忽略早期媒体,缺省true;开启后等待被叫应答再进入 outbound 业务
audioFork
wsUrl:mod_audio_fork 要连接的 ASR WebSocket 地址;示例指向sherpa-asr-server,也可按实际链路指向 兼容的 ASR 服务或语音网关bugName:media bug 名称,用于uuid_audio_fork stop精确定位mixType/sampleRate:上行音频通道与采样率,mono/16k适合大多数 ASRconnectTimeoutMs:等待 WebSocket 连接成功事件的超时eventSubscriptionMode:ESL 事件订阅策略"channel-plus-custom":生产默认。发送myevents、PLAYBACK_STOP、DETECTED_TONE和必要的CUSTOM子类,保留当前通道基础事件并补齐 audio_fork / conference 扩展事件。myevents已由 FreeSWITCH 按通道 UUID 过滤,应用层EventRouter仍会做最终归属校验;默认不会追加filter Unique-ID"all":仅作显式应急回退,发送event plain ALL,应用层按 UUID 过滤; 高并发时会增加无关事件复制和解析开销
字段缺省时归一化为 "channel-plus-custom"。字段必须是上述两个字符串之一; 空值、非字符串或未知值会在启动阶段直接报错。订阅命令返回错误也会使当前会话 失败,系统不会自动降级为 event plain ALL。
duplex
原生全双工音频网关专属配置段,供 ctx.duplex.* 全双工状态机独立使用,与单向 [audioFork] 识别链路物理分离:
enabled:是否启用全双工能力。未配置或为false时,调用ctx.duplex.start()触发快速失败(Fail-Fast)拦截,阻止发起无效连接或误连普通单向 ASRwsUrl:全双工网关 WebSocket 地址(必填;示例ws://127.0.0.1:10091/audio,对应audio_fork_server端口10091;缺失时ctx.duplex.start()直接抛错)。若误配为已知单向 ASR 端口(10096/10099),ctx.duplex.start()时输出高优先级启发式告警(事件duplex_ws_url_unidirectional_asr_warning)healthUrl:全双工网关 HTTP 健康探针地址(如http://127.0.0.1:10091/health)bugName:mod_audio_fork 在 FreeSWITCH 通道上挂载的 media bug 标识,缺省"audio_fork"bargeInTrigger:服务端打断判定策略("speech-start"|"partial"|"final"),缺省推荐"partial"(首字打断)sampleRate/mixType:采样率("16k"|"8k")与混音模式("mono"|"mixed")connectTimeoutMs:WebSocket 建连超时时长(毫秒),缺省 5000
tts
采用 defaults / use / profiles 三段结构:defaults 是业务未指定时的合成默认值 (speakerId / speed / requestTimeoutMs);use.call / use.conference 分别选择 单呼与会议使用的 profile("wav" 或 "stream");profiles.wav.endpoint 对应 sherpa-tts-server 的 POST /tts,profiles.stream.endpoint 对应 POST /tts-stream。
defaults.speakerId:默认发音人音色 ID(非负整数,必填;示例值0)defaults.speed:默认语速倍率(浮点数,1.0 为原速,必填;推荐值1.1)defaults.requestTimeoutMs:请求 TTS 引擎合成的超时时长(正整数毫秒,必填;示例值10000)use.call:单呼场景下激活的 TTS Profile("stream"|"wav",必填;推荐值"stream"流式起播降低延迟)use.conference:多方会议场景下激活的 TTS Profile("stream"|"wav",必填;推荐值"stream";避坑说明:配合audio_fork://驱动时播放无阻塞丝滑流畅,若底层配置为传统shout://则会议场景必须配置为"wav",防止mod_shoutreader 关闭时阻塞混音线程导致全体卡顿)profiles.wav.endpoint:整段 WAV 静态合成 HTTP 接口(如http://127.0.0.1:9080/tts)profiles.wav.playbackTarget:回放目标格式("wav-url"|"file-path",必填;示例值"wav-url")profiles.wav.fsPlaybackBaseDir:当playbackTarget="file-path"时指定的挂载目录(需与 FreeSWITCH 共享文件卷)profiles.stream.endpoint:实时流式语音广播 HTTP 接口(如http://127.0.0.1:9080/tts-stream)profiles.stream.playbackPrefix:FreeSWITCH 流式回放协议前缀驱动("audio_fork://"|"shout://"|"",必填;强力推荐"audio_fork://"):"audio_fork://"(强力推荐,原生 HTTP Chunked PCM16 流平替 mod_shout):当本字段以"audio_fork://"开头时,callflow-esl调用POST /tts-stream会自动协商传入{"format": "pcm"},请求裸 PCM16 流;服务端直接在内存将浮点量化为 PCM16 输出,消除 ffmpeg 进程拉起与 MP3 转码延迟,首包延迟进一步压降 200~300ms,CPU 消耗降为零;自动组装为audio_fork://${streamUrl}?rate=${sampleRate}&channels=1,交由 FreeSWITCHmod_audio_fork原生SWITCH_FILE_INTERFACE文件驱动播放,安全打断且无阻塞;"shout://"(传统兼容模式,MP3 流):请求端请求 MP3 格式,服务端调用 ffmpeg 转码为 MP3,转换为shout://host:port/...交由 FreeSWITCHmod_shout播放;""(空字符串):直接使用原始http(s)://URL 交付给 FreeSWITCH 核心播放。
详细播放目标映射与协议协商调用链见 TTS 对接。
llm
全局大模型服务基础设施连接配置,为选配(Optional);作为整个 callflow-esl 原生直连 OpenAI 兼容大模型服务的唯一事实来源(SSOT)。llm-chat-business、kb-llm-chat-business 等业务在未声明私有参数时,自动继承此处的端点与凭据。
endpoint:必填,OpenAI Chat Completions HTTP(S) 地址;支持服务根地址、以/v1或/v1-openai结尾的基地址,以及以/chat/completions结尾的完整路径,启动时归一化为完整接口 URL;不允许用户名、密码、query 或 fragmentapiKey:可选鉴权 Key;非空时请求携带Authorization: Bearer <key>,私有部署无鉴权时可省略或留空model:必填,默认模型标识符(如qwen3.6-27b-fp8)requestTimeoutMs:单轮流式生成/调用最大超时时间(正整数毫秒,缺省 60000)maxRetries:瞬态错误(HTTP 429 限流或网络瞬断)指数退避最大重试次数(正整数,缺省 2)
businessConfig
按 registry / 数据库中的稳定业务编码保存各业务的文件层默认配置。容器和每个子项都必须是 JSON 对象;出现未注册业务编码会在启动期直接报错,避免拼写错误被静默忽略。
三层合并(config.toml.businessConfig[<code>] < call_businesses.businessConfig < call_business_number_mappings.mappingConfig)是通用机制,对所有用 defineBusiness 声明了配置校验器的业务一律生效;当前声明了校验器的业务是 llm-chat-business、 kb-llm-chat-business、kb-audio-chat-business、insurance-chat-business、verification-code-business。合并规则: 普通对象递归合并,数组、标量和 null 由高层整体覆盖;显式 business_code 路由不读号码映射层。
llm-chat-business 子项为可选项: 所有字段(endpoint、apiKey、model、requestTimeoutMs)均支持缺省,未填时自动继承全局 [llm] 配置;亦可在此声明局部字段覆盖。若显式填写,启动期按同一 URL 归一化规则校验端点、模型和超时格式。llm-chat-business 还支持数据库三层合并覆盖。
kb-llm-chat-business 子项为可选项: 是带实时盯屏与人工介入的大模型对话业务,字段与 llm-chat-business 相同并自动继承全局 [llm] 配置,另支持:
systemPrompt:可选系统提示词覆盖;为空或缺省时使用内置通用助手提示词
kb-audio-chat-business 子项为可选项:
strategy:必填,已在策略服务中注册的独立策略名(如"kb-mock")endpoint、token、requestTimeoutMs:均为可选项。未显式配置endpoint时按全局[callout]派生${baseUrl}/api/v1/conversation;接入 callout-server 的kb-audio-chat-business应显式配置endpoint指向POST /api/ai/chat/completions,token/requestTimeoutMs未填时同样回退全局[callout]
insurance-chat-business 子项为可选项: 大模型车险电销对话业务,自动继承全局 [llm] 基础设施配置;亦可在此声明局部参数(如 endpoint、apiKey、model、requestTimeoutMs)进行业务级覆盖。
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 由调用方媒体配置拼接
backgroundAudio
会议背景音(拟真办公室环境音等)配置,可选;整节缺省时等价于 { "enabled": false }。
enabled:是否默认启用背景音。⚠️ 当前解析后没有任何消费方:背景音是否播放完全由业务 显式调用ctx.conference.startBackgroundAudio()/join({ backgroundAudio })决定,把它 改成false不会关掉已经这么调用的业务defaultProfile:调用方未显式指定profile时使用的 profile 名,缺省"office"profiles.<name>.streamUrl:该 profile 实际下发给conference <id> play <target> async的播放目标,通常是mod_local_stream目录,例如local_stream://moh/16000(WSL/生产环境 local_stream 已原生定义moh/16000高清 16kHz 流)。profiles.<name>.volume:⚠️ 当前同样是惰性字段,解析后不会拼进任何 FreeSWITCH 命令; 默认-36。要调音量请直接处理素材或改 conference profile。
混音时钟对齐指南:FreeSWITCH 会议室(mod_conference)默认混音采样率为 16000Hz(rate=16000)。背景音流必须同样采用 16000Hz 音频源(local_stream://moh/16000),避免触发动态软件重采样导致抖动与音质劣化;切勿配置未定义的流名称,否则 FreeSWITCH 将产生 Unknown source 告警并强制回退到 8000Hz 产生顿挫。
播放目标的解析优先级是:调用入参 target > profiles[profile].streamUrl > profiles[defaultProfile].streamUrl > 兜底 local_stream://ambient/<profile>/16000。 local_stream:// 目标要求 FreeSWITCH 已加载 mod_local_stream 且对应目录有素材, 配置见 deploy/freeswitch/;API 行为见 运行时组件 的 ctx.conference.startBackgroundAudio 一节。
callout
外呼与 AI 策略中台微服务端点配置,为选配(Optional);未接入 callout-server 时可整体缺省。当前为统一单端点配置(baseUrl / token / requestTimeoutMs):
baseUrl:callout-server 服务基地址(默认http://127.0.0.1:9920),只允许 HTTP(S),可带反向代理路径前缀;启动时去除尾斜杠,不允许用户名、密码、query 或 fragmenttoken:服务间内部鉴权令牌(对应 callout-server 的server.serviceApi.authToken);非空时请求携带X-Callout-Internal-Token,为空则不发requestTimeoutMs:统一请求超时(正整数毫秒,缺省通常配置 10000),覆盖流程脚本拉取、NLU 意图决策/槽位抽取、Chat 多轮流式对话与结果/进度回写
API 路径由代码固定维护(src/config/callout-api.ts),不属于配置项:流程脚本使用 POST /api/scripts/runtime;通话结果与进度使用 POST /api/call-results 与 POST /api/call-progress;人工转接使用 POST /api/transfers/route 与 POST /api/transfers/end;NLU 使用 POST /api/ai/nlu/analyze;Chat 使用 POST /api/ai/chat/stream。全部请求统一面向同一个基地址与鉴权令牌。各个接口的详细入参、响应体结构、通道变量注入与容灾机制详见 与 callout-server 协同交互与接口规范。
db
url:PostgreSQL 连接串。本地开发默认连接dev-freeswitch库,线上生产环境统一使用freeswitch库。业务路由会查call_businesses与call_business_number_mappings,schema 见src/db/schema.ts
数据库连接池与路由缓存
db.maxConnections 默认 30,db.idleTimeoutSeconds 默认 60(秒),均须为正整数,重启生效。连接字符串仍以 db.url 为准。
路由按查询类型和 trim 后的业务编码/号码缓存数据库行及未命中结果,TTL 为 30 秒,上限 4096 项。同键冷请求共享一次查询,数据库错误不缓存;每个会话获得独立配置副本。业务编码始终优先于号码映射。管理端写库最长 30 秒生效,可通过鉴权 POST /business-routes/reload 提前失效并广播。
redis
Redis 跨实例协调配置,是 callflow-esl 的必选依赖:会议协调、实例心跳、坐席实时盯屏 会话注册表都建立在它之上。缺配置或启动时连不上会阻止服务启动 (进程以非零码退出,交给 systemd/k8s 重启):
url:Redis 连接 URL,仅支持redis:/rediss:协议,例如redis://default:redis@127.0.0.1:6379/0keyPrefix:会议运行态、实例心跳、盯屏会话等 key 的统一前缀ttlMs:会议运行态、实例心跳、盯屏会话等 key 的 TTL(毫秒)
配置路径与环境变量
常规 bun run dev/start 优先读取工作目录中的 config.toml,找不到时尝试模块目录;独立可执行文件优先读取可执行文件同目录中的 config.toml。CALLFLOW_ESL_CONFIG_PATH 可显式指定配置文件路径。当前配置加载器不按 DATABASE_URL 或 REDIS_URL 覆盖 [db].url、[redis].url,连接地址应写入所选 TOML 文件。