跳到正文

配置参考 ​

配置 ​

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 = 1

eslServer ​

  • 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 适合大多数 ASR
  • connectTimeoutMs:等待 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)拦截,阻止发起无效连接或误连普通单向 ASR
  • wsUrl:全双工网关 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_shout reader 关闭时阻塞混音线程导致全体卡顿)
  • 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,交由 FreeSWITCH mod_audio_fork 原生 SWITCH_FILE_INTERFACE 文件驱动播放,安全打断且无阻塞;
    • "shout://"(传统兼容模式,MP3 流):请求端请求 MP3 格式,服务端调用 ffmpeg 转码为 MP3,转换为 shout://host:port/... 交由 FreeSWITCH mod_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 或 fragment
  • apiKey:可选鉴权 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 或 fragment
  • token:服务间内部鉴权令牌(对应 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/0
  • keyPrefix:会议运行态、实例心跳、盯屏会话等 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 文件。

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