尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

FreeSWITCH 语音大模型机器人模块

FreeSWITCH 语音大模型机器人模块 mod_llm_robotFreeSWITCH 语音大模型机器人模块。通过兼容 OpenAI Chat Completions 接口的 LLM 服务在 FreeSWITCH 会话内完成多轮语音对话、话术打断、静音兜底、关键词挂机、模型工具调用和通话记录回传。模块注册的拨号计划应用actionapplicationllm_robotdatarobot_id/robot_id对应llm_robot.conf中robot id...的id属性无id属性时兼容回退name属性。功能概览功能说明多轮语音对话ASR LLM TTS 闭环支持对话上下文话术打断用户说话时可中断机器人播放静音兜底连续静音轮播提示达到上限后播放结束语挂机关键词挂机Aho-Corasick 多关键词匹配识别本地挂机意图工具调用支持模型调用hangup/transfer内置工具识别超时整通总超时控制聊天记录通话退出序列化为 JSON 写入通道变量chat_message__配置隔离每次 application 调用创建独立实例会话状态完全隔离架构总览┌─────────────────────────────────┐ │ Dialplan │ │ llm_robot(robot_id) │ └──────────────┬──────────────────┘ │ ┌──────────────▼──────────────────┐ │ mod_llm_robot.cpp │ │ (模块入口 / application) │ └──────────────┬──────────────────┘ │ ┌──────────────▼──────────────────┐ │ LLMRobotManager │ │ (全局管理器 / 单例) │ │ ┌──────────────────────────┐ │ │ │ 本地配置 JSON 快照 │ │ │ │ 运行实例索引 (by UUID) │ │ │ │ 共享事件线程池 │ │ │ └──────────────────────────┘ │ └──────────────┬──────────────────┘ │ 每次调用创建 ┌──────────────▼──────────────────┐ │ LLMRobot (单呼叫实例) │ │ ┌──────────────────────────┐ │ │ │ ASR 事件 / LLM 调用 │ │ │ │ TTS 播放 / 工具执行 │ │ │ │ 状态机 / 对话历史 │ │ │ │ KeepAliveHttpClient │ │ │ └──────────────────────────┘ │ └──┬────────┬────────┬────────────┘ │ │ │ ┌──────────────▼──┐ ┌───▼─────┐ ┌▼──────────────┐ │ FreeSWITCH ASR │ │ LLM API │ │ FreeSWITCH │ │ (语音识别) │ │(Chat │ │ TTS/播放 │ │ │ │Complet.)│ │ │ └─────────────────┘ └─────────┘ └───────────────┘模块内分层层次主要文件职责FreeSWITCH 入口mod_llm_robot.cpp模块加载/卸载、注册llm_robotapplication全局管理llm_robot_manager.h/.cpp本地配置文本、运行实例索引、共享线程池、事件分发单呼叫领域对象llm_robot.h/.cppASR、LLM、TTS、工具、超时、状态机、历史对话封装chat_app.h基于 olrea/openai-cpp(header-only libcurl) 的对话与工具调用enable_thinking等扩展字段直接入请求体事件与数据llm_robot_event.h、llm_chat_message.h事件所有权、聊天记录 DTO类型声明ptrs.h共享指针类型和前置声明唯一定义点核心设计原理配置与运行实例分离LLMRobotManager不缓存可运行的机器人原型对象。模块加载时只把robot节点的原始 JSON 保存到_local_robot_configsapplication 每次调用都复制 JSON 文本创建新的LLMRobot并执行DeSerialize()。Session、Channel、对话历史、Token 统计和播放状态都属于本次通话实例。_running_robots只用于按呼叫 UUID 索引正在运行的实例供 FreeSWITCH 调度任务和事件回调定位对象。它不是配置缓存实例在通话停止时删除。事件所有权与异步分发播放回调收到的 FreeSWITCH 事件只在回调期间有效。模块先通过switch_event_dup()复制事件再由LLMRobotEventRAII接管switch_event_t*析构调用switch_event_destroy()。事件进入管理器的共享线程池避免在媒体回调中直接执行阻塞的 LLM 请求。每个机器人使用BeginAsyncOperation()/EndAsyncOperation()记录已入队和执行中的任务。退出时先禁止接收新任务再等待计数归零之后才允许释放 Session 和 Channel 裸指针。这是模块避免异步回调访问已释放 FreeSWITCH 会话的核心生命周期约束。状态协调单呼叫实例用原子变量、互斥锁和条件变量协调以下状态状态含义Started实例仍可处理通话逻辑ReplyBusy正在构建或等待 LLM 回复Playing正在 TTS 或播放音频SilencePlaying当前播放由静音兜底触发UserSpeaking已收到用户开始说话事件Breaking当前播放正在被用户打断PlaybackMutex和PlaybackCv用于避免普通回复、静音提示和用户讲话互相覆盖。UUID 间接查找静音和识别超时任务只携带复制后的 UUID不长期捕获 FreeSWITCH 会话裸指针。任务触发后通过_running_robots重新查找实例并核对 task id再进入异步操作计数。旧任务、已取消任务或已退出的呼叫会被丢弃。LLM 调用每个ChatApp各自持有一个openai::OpenAI实例独立 curl session天然并发安全base_url/model/api_key/temperature/max_tokens/enable_thinking全部经配置传入。日志只记录目标、状态和响应长度不记录 API Key 或完整请求体。当前主对话路径调用同步、非流式 Chat Completions。SpeakStart/Feed/End/Wait和 HTTP 流式回调能力已存在但尚未接入ThinkReply()主回复链路。呼叫流程Dialplan mod_llm_robot LLMRobotManager LLMRobot FreeSWITCH ASR/TTS Event Pool LLM API │ │ │ │ │ │ │ │── llm_robot(id) ──────│ │ │ │ │ │ │ │── GetConfig(id) ────│ │ │ │ │ │ │── local JSON ───────│ │ │ │ │ │ │── DeserializeRobot ──────────────────────│ │ │ │ │ │ │── register UUID ──│ │ │ │ │ │ │── Start ──────────│ │ │ │ │ │ │ │── start ASR welcome │ │ │ │ │ │ │ │── speech event ───│ │ │ │ │ │── OnEvent ───────────│── copyenqueue ──│ │ │ │ │ │── LLM call ──────────────────────────────────────────────────│ │ │ │ │── reply text ────────────────────────────────────────────────│ │ │ │ │── TTS/playback ──────│ │ │ │ │ │ │ │ │ │ │ │ │ │── stop timers ASR ─│ │ │ │ │ │── wait async ops ──│ │ │ │ │ │ │ │── set chat_message__ ─│ │ │ │ │ │── remove UUID ────│ │ │ │ASR 事件处理事件当前行为begin-speaking标记用户讲话、取消静音任务启用打断或正在播放静音话术时中止播放detected-speech记录用户文本、清零静音计数、匹配挂机词否则调用 LLMclosed清除用户讲话状态CHANNEL_HANGUP停止实例并从运行缓存移除DTMF当前只记录按键事件不执行业务逻辑当ReplyBusy为真时新识别文本追加到历史但不会并行发起第二个 LLM 请求。播放、打断与静音播放内容以http://或https://开头时调用switch_ivr_play_file()。其他内容先 URL 解码和 inja 渲染再调用switch_ivr_speak_text()。模板上下文包含caller和callee并注册了get()、post()和递归render()回调。is_breaktrue时用户说话会中断普通播放静音提示始终允许被打断。每次有效识别重置静音计数。连续静音先轮播silences达到max_silence_count后播放goodbye并挂机。detect_timeout是整通识别阶段的总超时不因单次成功识别而重新计时。配置加载模块使用的 FreeSWITCH 配置名称是llm_robot.conf。仓库内的llm_robot.conf.xml是静态配置示例实际运行环境也可以由 XML 绑定模块动态返回配置。加载和调用分为三个阶段模块加载仅解析全局settings如threads不缓存任何机器人条目。application 调用按robot_id现读本地llm_robot.conf中的robot id...JSON 原文兼容回退name属性命中后立即创建新对象并反序列化修改本地静态配置后无需重载模块即可生效。动态 XML 回退本地未命中时把Robot-Id和通道变量放入SWITCH_EVENT_REQUEST_PARAMS请求 XML registry返回的 JSON 只用于本次调用同样不缓存。mod_xml_local已实现llm_robot.conf的动态处理器。本地与动态配置均按需现读模块内不保留机器人配置快照。全局设置配置默认值有效范围说明threads101..128共享事件线程池大小机器人字段JSON 字段默认值说明name空机器人名称建议与 XMLrobotname一致asr_engine空FreeSWITCH ASR 引擎运行必需tts_engine空普通 TTS 引擎flowing_tts_engine空流式 TTS 引擎主回复链路当前未启用voice空TTS 音色volume500..100TTS 音量speech_rate0-500..500TTS 语速pitch_rate0-500..500TTS 音调base_url空OpenAI 兼容服务基础 URL运行必需api_key空LLM 凭据必须通过安全配置渠道提供model空Chat Completions 模型名运行必需temperature0.3生成温度max_tokens512→256反序列化后截断到最多256max_history_turns200..100保留的对话轮数0不保留历史enable_thinkingfalse注入 Chat Completions 根请求体system_prompt空每次 LLM 请求首部的 system 消息welcome空启动后首次播放内容goodbye空关键词、静音或识别超时时的结束语goodbye_keys空数组Aho-Corasick 挂机关键词goodbye_delay10..60秒结束语播放后到挂机的延时0立即挂机在Hangup()内统一生效覆盖关键词、静音、识别超时和hangup工具全部挂机路径is_breakfalse是否允许用户讲话中断播放silence_timeout51..20秒单次静音等待max_silence_count31..20达到次数后播放结束语并挂机silences空数组静音提示语按顺序轮播detect_timeout6000..1200秒总识别超时0关闭tools空数组暴露给模型的函数工具配置工具调用每个tools元素包含字段说明name工具名称description发送给模型的工具说明parametersOpenAI function tool 的 JSON Schemaarguments服务端固定参数覆盖模型生成的同名参数工具必须同时存在于机器人配置和本地ToolFunctions白名单中才能执行工具行为hangup播放message缺省使用goodbye写入聊天记录然后挂机transfer播放message按to转入拨号计划并停止机器人transfer.arguments.to应由服务端配置固定避免模型改写目标号码。请求设置parallel_tool_callsfalse同时兼容tool_calls和function_call响应。配置其他名称的工具只暴露给模型执行阶段被白名单拒绝。聊天记录输出模块维护两套数据ChatHistory发给模型的有限上下文仅保留 user/assistant 文本。ChatMessages通话审计记录保存全部已识别用户文本和实际播放文本。退出时ChatMessages序列化为 JSON 数组写入chat_message__字段说明uuidFreeSWITCH 呼叫 UUIDroleuser或assistantcontent识别文本或播放文本create_time记录创建时间break机器人播放是否被打断elapseLLM 响应耗时毫秒tool播放来源welcome/hangup/transfer/silenceusageprompt_tokens、completion_tokens、total_tokens该变量可能包含客户对话内容后续消费按敏感业务数据处理。停止与卸载顺序单通呼叫结束时管理器执行标记实例为停止取消静音和识别超时任务。从_running_robots删除 UUID阻止后续调度任务获得实例。禁止新异步任务并等待现有任务完成。把聊天记录写入仍然有效的 FreeSWITCH channel。返回 application让 FreeSWITCH 会话线程继续释放资源。模块卸载时先阻止新分发清理本地配置快照停止全部运行实例等待异步任务完成再销毁线程池。必须保持停止生产 → 等待消费 → 释放资源的顺序。依赖与构建依赖用途FreeSWITCHlibfreeswitch核心平台openai-cppChat Completions 客户端cpp-httplibOpenSSLHTTP 连接复用nlohmann/jsonJSON 解析inja模板渲染ThreadPool共享事件线程池easycpp缓存、序列化、工具类aho-corasick关键词匹配fmt格式化licensecc授权校验libcurl、pthread网络传输、线程按照仓库规范只能从/root/fs使用统一脚本编译./fs.sh compile freeswitch编译成功不等于镜像已发布、打标签或服务已重启这些步骤需要分别授权。开发检查清单修改本模块时至少确认配置字段是否同时更新了反序列化、默认值/范围和本文档。新事件是否复制或明确转移了switch_event_t所有权。新异步任务是否进入BeginAsyncOperation()/EndAsyncOperation()计数。调度任务是否通过 UUID 查找实例并校验 task id。停止流程是否拒绝新任务、等待旧任务并最终释放机器人实例。播放、用户讲话、静音提示和 LLM 回复是否可能互相覆盖或死锁。工具固定参数是否覆盖模型参数终止工具是否正确停止机器人。日志是否包含 UUID 和底层错误同时避免请求体、凭据及客户隐私泄漏。通话退出前是否等待异步操作完成并成功写入chat_message__。使用git diff --check做静态检查只有得到明确授权后才执行仓库编译脚本。这篇文章把 AI 外呼的链路和落地要点讲清楚了。mod_llm_robot 这个开源模块的完整源码我已开源可以在下面的仓库获取GitHubhttps://github.com/pzhu1015/salesGitee国内访问更快https://gitee.com/pzhu1015/sales如果你正在做或打算做智能外呼 / 呼叫中心除了这个模块还有 FreeSWITCH 部署、ASR/TTS 对接、Kamailio 负载、录音上云等相关方案欢迎到仓库交流和看更多实现。欢迎技术交流一起把 AI 外呼做好。本文为技术经验分享欢迎收藏转发。
返回列表