简介面向初次接触AI电销机器人部署的用户该源码包提供从服务器选型到安装启动的完整部署指导重点解决环境配置、组件安装、站点与数据库设置等常见问题适合希望在Centos系统中快速落地AI语音机器人的入门者。压缩包仅6KB共3个文件包含inscode运行配置、html页面说明和gitignore规则文件配合配套教程文字足以支撑整个部署流程。目前已有113人学习下载具备一定参考价值。通过教程可掌握4核8G Centos7.9环境下宝塔面板、Nginx、MySQL、PHP的组合配置方法理解定时任务维护与自动化安装脚本的执行逻辑并获得安装包地址与清晰的使用说明非专业人员也能按步骤完成部署。1. AI 电销机器人不是对话 Demo部署难点在通话链路AI 电销机器人这几年被说得玄乎真拿到源码开始部署的人会发现难点根本不在模型而在通话链路。表面逻辑很简单用户说话ASR 转文字LLM 回一句TTS 念出来。可电话那头的用户不会按你的节奏来——彩铃、静音、打断、方言、线路抖动任何一环处理不好通话根本走不到「对话」那一步。这套部署教程配套的项目源码把 SIP 电话接入、流式语音识别、话术式对话管理、语音合成和外呼调度串成完整链路解决「拿到代码包到顺畅打通电话」这个最实际的问题。适合有电销场景想试跑机器人外呼的团队也适合想搞懂语音电话链路怎么搭的软件开发者。下文按链路、启动、调参、避坑、验证五段拆开讲。2. 先把通话链路拆明白SIP 媒体流、语音三件套与状态机电销机器人和网页机器人最大的差别是它的输入不是文本消息而是 VoIP 线路上实时到达的 RTP 音频包。第一次拿到源码包我建议先别急着看对话模型先把「电话怎么进、声音怎么走、状态怎么切」三件事搞清楚后面所有排错都会回到这三件事上来。2.1 电话怎么接进来SIP 注册、INVITE 与 RTP 媒体流我一般把整条链路分成三段SIP 信令、RTP 媒体、语音引擎。信令负责建立和拆除通话媒体负责双向传声音语音引擎负责「听懂声音、决定说什么、说出声音」。源码包把这三段分别封装在src/sip_gateway和src/engines两个目录里部署时只需要把线路供应商给的参数填进配置文件不需要改代码。sip: server: sip.trunk.example port: 5060 transport: udp username: 6001001 password: 从供应商处获取的注册密码 local_ip: 0.0.0.0 external_ip: # NAT 环境填公网 IP其他环境留空 codec: PCMU # PCMU / PCMA / OPUS dtmf: rfc2833这段配置的含义是机器人作为 SIP 分机注册到线路平台来电时平台发 INVITE 给机器人机器人回 200 OK 后双方按 PCMU 编码收发 RTP 报文。codec 选 PCMU是因为 G.711 是电话网默认编码兼容性最好、延迟最低如果线路只给 PCMA国内部分线路是这样把 codec 改成 PCMA 即可。dtmf 一般在按键导航场景才需要纯语音外呼保持默认就行。电话进来的完整时序是平台收到用户侧呼叫后向机器人注册的号码发 INVITE机器人回 180 Ringing再回 200 OK随后双方通过 SDP 交换媒体参数协商好编码和端口之后就是持续的 RTP 双向音频流。sip_gateway实际做的就这三件事注册、接 INVITE、收发 RTP。你真正要盯的是 SDP 里通告的媒体地址是不是可达——如果通告的是内网 IPRTP 包就永远回不来表现就是「振铃正常、接通没声」。还有一个新手必踩的点ASR 引擎通常要求 16kHz 单声道 PCM 输入而电话线路默认是 8kHz G.711。常见做法是在网关层做一次重采样把 8kHz 转成 16kHz 再喂给识别引擎而不是让 ASR 去猜采样率。源码包在engines里封装了重采样模块单路 CPU 占用可以忽略。我见过有人跳过这层直接送原始流识别率掉了两成还以为是模型选型不对。注意云主机部署时 external_ip 务必填对。能注册、能振铃、一接通就没声音十次有九次是媒体地址不通第 5 章第一条避坑记录专门讲这个。2.2 语音三件套选型ASR、LLM、TTS 各管哪一段所谓语音三件套是循环工作的三个引擎ASR 把用户的话转成文本LLM 根据话术生成下一句TTS 把文本念给用户。选型别只看识别率和生成质量先看延迟和并发因为这两个是实时通话里的硬约束。ASR 必须选流式方案。流式识别的意思是「边说边出结果」而不是等整段话说完才给一次转写。电销场景里用户随时会打断你需要 partial 中间结果判断「对方是不是要插话」也要用 final 最终结果决定「这句话要不要交给 LLM」。常见做法是本地搭一个流式识别服务做私有化部署内部用长连接对接避免把音频流转到外部接口引入不可控延迟。源码包默认配置就走本地模式。asr: engine: local sample_rate: 16000 language: zh-CN vad_enable: true endpoint_timeout_ms: 800 max_silence_ms: 3000 llm: api_url: http://127.0.0.1:8000/v1/chat/completions model: local_chat_base # 本地私有化部署的对话模型 max_tokens: 512 temperature: 0.7 system_prompt: | 你是外呼电销机器人坐席正在介绍一款产品。 规则1. 先自报身份、说明来意2. 每轮不超过两句话说完等用户反应 3. 用户明确拒绝时礼貌致谢并结束通话4. 不编造优惠不确定的一律不承诺。 tts: engine: local voice: female_neutral rate: 1.0 volume: 1.0 interrupt_enable: true三个配置块对应三种完全不同的接口形态ASR 是长连接流式LLM 是同步请求响应TTS 是「文本进、音频流出」。部署时最容易出错的不是模型本身而是回调接错——只有 ASR 的 final 结果应该送 LLMpartial 结果只用于打断判断。如果把 partial 也送进 LLM模型会被频繁打断话术变得支离破碎用户一听就知道对面是机器人。LLM 的 system_prompt 决定话术质量的上限。写电销话术时我习惯让 prompt 包含三件事角色与来意、单轮长度限制、拒绝时的收尾策略。尤其是第 3 条客户说「不需要」就立刻停写死在 prompt 里比写在业务代码里更不容易漏。做本地部署大语言模型的团队通常会把对话服务单独放一台机器和通话服务解耦避免生成时占用的显存挤压 ASR 的资源。2.3 状态机不是可选项通话生命周期与打断处理只把三个引擎串起来、不做状态管理一定会遇到同一种症状TTS 还在播放用户已经开口机器人继续把下一句播出去两边撞话。解决撞话的唯一办法是把通话过程显式做成状态机。通话生命周期在源码包里的定义是IDLE、DIALING、RINGING、ANSWERED、SPEAKING、LISTENING、ENDED。每次 SIP 事件或语音事件到来状态机先校验当前状态允不允许迁移再触发对应动作。这一层在src/state_machine/call_fsm.py。# 状态机核心逻辑事件驱动迁移 CALL_STATES (IDLE, DIALING, RINGING, ANSWERED, SPEAKING, LISTENING, ENDED) class CallFSM: def __init__(self, call_id: str): self.call_id call_id self.state IDLE def on_user_speech(self): if self.state in (SPEAKING, LISTENING, ANSWERED): self.state LISTENING self.tts.stop() # 先停合成 self.tts.flush() # 再清空播放缓冲 self.asr.resume() # 最后恢复识别 def on_tts_finished(self): if self.state SPEAKING: self.state LISTENING self.asr.resume()这段代码想说明的是打断处理的本质不是「让 TTS 闭嘴」而是状态迁移。用户一开口无论当时在播还是在听都统一进入 LISTENING然后依次触发三个动作停合成、清缓冲、恢复识别。顺序不能反先停播再恢复识别否则 ASR 会把机器人自己的合成尾音识别成用户的话形成自问自答的循环。并发还要注意一点每通电话要独占一个状态机实例不能做成全局单例。8 路并发就是 8 个状态机各自跑它们的共享点只有 SIP 连接池和引擎并发配额这两件事第 4 章展开讲。我第一次跑并发时偷懒用了全局状态结果两通电话互相抢状态一通挂断另一通也跟着结束排查了整整一个晚上。3. 把源码真正跑起来目录装配、依赖安装与第一通测试电话这一章的目标只有一个让源码在你机器上跑起来并成功打通第一通测试电话。前提是你已经有一台 Linux 主机和一个可用的 SIP 线路账号。没有线路账号也不影响走通流程用软电话和源码自带的模拟脚本就能验证大部分逻辑。3.1 目录结构与模块对应先知道出问题该去哪查拿到源码包先别急着装依赖先花三分钟把目录结构过一遍。搞清楚每个目录对应链路的哪一段后面排错才知道该翻哪个日志、改哪个文件。ai_telesales/ ├── config/ │ └── robot.yaml # 全局配置SIP / ASR / LLM / TTS / 外呼策略 ├── src/ │ ├── sip_gateway/ # SIP 注册、呼叫控制、RTP 收发 │ ├── engines/ # asr_engine / llm_engine / tts_engine │ ├── state_machine/ # 通话状态机与事件定义 │ ├── dialer/ # 外呼任务队列、重试、号码调度 │ └── main.py # 服务入口装配各模块 ├── scripts/ │ ├── install_deps.sh # 安装系统级媒体依赖 │ ├── import_numbers.py # 把号码表导入外呼队列 │ └── test_call.py # 模拟来电验证全链路 ├── logs/ # 运行日志与通话录音 └── requirements.txt # Python 依赖清单对照链路看就清楚了sip_gateway是第一层把电话变成音频事件engines是第二层把音频变成文字、把文字变成下一句、把下一句变成音频state_machine和dialer是第三层一个管单通话术节奏一个管批量外呼调度。main.py只是装配入口改话术、改线路、改模型都不需要动它。排错时的日志查看顺序也有讲究call.log能看到每一通电话走到哪一步engines/下三个引擎子目录各有一份模块日志ASR 识别文本、LLM 请求耗时、TTS 播放事件分别落在三份里。多数问题在 call.log 和 asr.log 交叉比对时就能定位不用一开始就上抓包工具。3.2 依赖安装与两个容易卡住的点安装分两步Python 依赖和系统级媒体依赖。Python 环境我习惯用虚拟环境隔离避免和同机其他服务互相污染cd ai_telesales python3 -m venv .venv source .venv/bin/activate pip install --upgrade pip pip install -r requirements.txt bash scripts/install_deps.shrequirements.txt装的是 Python 层依赖install_deps.sh装的是音视频解码相关的系统库。系统库这一步是很多人卡住的地方机器上原先没装过媒体处理库ASR 解码时直接报「找不到文件」一类的错实际是缺底层解码器。装完先验证一下ffmpeg -version | head -n 3能正常打印版本说明系统依赖到位。接着用源码包自带的配置自检命令确认配置能被正确解析、相关端口可达python src/main.py --config config/robot.yaml --check这个--check不走完整启动流程只做三件事解析 YAML、测试 SIP 服务器连通性、逐个 ping 三个引擎的健康接口。看到 all checks passed 再进入下一步别跳过自检直接跑服务——我在生产环境见过太多「启动成功但一个引擎都没连上」的假象。如果你习惯用容器部署源码包里也带了 Dockerfile 和 docker-compose 文件。容器化部署的好处是环境一次构建、随处复用特别适合多套环境反复交付代价是 SIP 的 UDP 端口映射和容器网络要额外配NAT 环境下坑更多。初学者我建议先跑裸进程跑通了再考虑容器否则容易把「代码问题」和「网络问题」搅在一起。3.3 启动服务与打通第一通测试电话启动命令很简单python src/main.py --config config/robot.yaml启动后日志会依次打出三个引擎的连接状态和 SIP 注册结果。看到 register ok说明机器人已经在线路平台上正常注册。此时用任意一款软电话向机器人注册的号码拨一通然后实时盯日志tail -f logs/call.log正常流程应该依次出现INVITE received → ring → answer → user speech detected → asr partial → asr final → llm reply → tts play → hangup。日志停在哪一环问题就在哪一环停在 answer 之前是信令或媒体协商问题停在 asr 之后是识别或 LLM 接口问题停在 tts 之后是合成或打断问题。每通通话的录音默认存在logs/record/下文件名以 call_id 命名。查录音是排查用户侧问题最快的手段用户说识别不到先听录音里有没有声音录音有声音但 ASR 文本为空再查重采样和 VAD 参数而不是一头扎进模型调参。没有外呼线路时可以用源码自带的模拟脚本验证全链路。test_call.py 不真正拨号而是直接向本机 SIP 服务发一个 INVITE验证 ASR、LLM、TTS 能否完整跑一轮python scripts/test_call.py --callee 10086 --script 你好我是测试坐席请问现在方便听电话吗这个脚本的好处是零成本、可循环、可压测缺点是绕过了运营商线路验证不到真实的媒体协商、回声和网络抖动。所以我的习惯是两条腿走路先用 test_call.py 把逻辑链路跑通正式上线前再用真实线路拨 5 到 10 通验证听感。只跑模拟脚本就上线的基本都会在第一批真实通话里翻车。4. 参数调优的四个刻度并发配额、接通判定、超时与话术节奏源码能跑通只是起点真正决定机器人能不能用的是这四组参数并发配额、接通判定、超时策略、话术节奏。每一组都直接影响用户听感也直接影响线路效率和成本。4.1 并发不是开大就完事三层配额要先对齐很多人部署完第一件事就是把并发调到 100然后发现 ASR 先崩。电销机器人的并发能力不是单看线路数而是看三层里最弱的一层SIP 线路并发、ASR 识别并发、TTS 合成并发。任一层的配额小于另外两层多余的并发都会变成排队和超时。源码包在 robot.yaml 里有一组和外呼调度相关的配置dialer: max_concurrent_calls: 8 # 同时保持通话的最大路数 call_interval_sec: 2 # 连续外呼间隔防止瞬间打爆线路 retry_policy: busy: {delay_sec: 300, max_times: 2} no_answer: {delay_sec: 600, max_times: 1} failed: {delay_sec: 1800, max_times: 3} asr: max_concurrent: 8 tts: max_concurrent: 8我一般建议 max_concurrent_calls 从 4 路起观察一两个小时的「接通率」和「平均通话时长」之后再逐步加到 8、12。盲目加并发的常见后果是接通率反而下降——并发上去后 ASR 和 TTS 延迟升高用户等待超过 1.5 秒就会自己挂电话挂机率上去了结果自然难看。这里最值得盯的指标是「首句响应延迟」从用户第一句话说完成到机器人开始播下一句超过 2 秒用户体感就明显变差。call_interval_sec 控制的是外呼发力节奏。短于 1 秒会导致大批号码同时振铃而可用分机就那么多大量来电在队列里等不到应答被运营商记成未接反而拉低线路信誉。一般按 2 到 5 秒设置量大的场景配合多分机号再往下压不要只靠调这一个参数硬撑。4.2 接通判定与静音策略别把彩铃当人声外呼机器人有个经典难题拨出后对端确实有人接听了机器人的开场白也播了但用户那边听到的是「喂喂」机器人却在等 ASR 出结果——因为识别引擎把背景电视声当成了对话内容。更常见的是彩铃阶段就开始识别等真接通时模型已经被彩铃内容污染了。源码包的处理方式是分两个阶段判定。第一阶段是「线路级接通」SIP 收到 200 OK 或对端摘机事件这是电信层面的接通。第二阶段才是「用户级接通」VAD 检测到连续人声。两个阶段都满足才进入正式对话状态机。对应的参数voice: vad_energy_threshold: 0.02 # 低于该能量判为静音 user_voice_duration_ms: 600 # 连续 600ms 人声才算真说话 silence_timeout_sec: 6 # 用户侧静音超过 6 秒启动话术兜底 wait_after_bridge_ms: 300 # 桥接完成后先静默 300msvad_energy_threshold 是 VAD 的灵敏度调太低会把空调声、电视声当成用户说话调太高则用户小声说话识别不到。600ms 的人声持续判定能滤掉爆发噪声。silence_timeout_sec 负责沉默场景用户接了电话但不说话机器人先播一次「您能听到吗」再静默一次仍无回应就礼貌挂断而不是无限等下去。这里有个容易忽略的点接通判定通过后不要立刻播放开场白。wait_after_bridge_ms 这个 300ms 的静默窗口是给对端线路完成音频通道建立的缓冲。很多上线初期「用户一接就听到半句话」的投诉就是这个值配成 0 导致的——机器人前半句播出去时对端的音频通道还没完全建好。4.3 话术节奏与拒绝收尾两个容易被忽略的参数电销和客服机器人的话术节奏差异很大。客服是用户主导机器人回答越短越好电销是机器人主导但每轮也不能太长超过 3 句话用户就受不了。源码包在 llm 配置里加了 response_max_sentences 和 response_max_chars 约束默认分别是 2 句和 60 字。调这两个值时要同步注意 TTS 端的节奏参数tts: rate: 1.0 # 0.8-1.1 之间试快了生硬慢了着急 pause_between_sentences_ms: 450 # 句间停顿也是给用户的插话窗口450ms 的句间停顿是为了留出「插话窗口」。实际听感上机器人一句话说完后 400 到 600ms 的停顿用户才有机会打断或应答停顿太短用户话插不进来停顿太长用户以为断线。这类听感参数没有统一标准我的做法是找 3 到 5 个真实号码试听再定最终值。拒绝收尾除了写进 prompt还需要一个规则层兜底ASR final 结果里出现「不需要、别打、挂了」等关键词无论 LLM 怎么回答都强制切换收尾话术并挂机。这个关键词表放在 robot.yaml 的 refuse_keywords 字段。做外呼合规时这条兜底比 prompt 更可靠因为 prompt 有概率被模型忽略规则是 100% 执行的。5. 部署避坑五条实测翻车记录与排查方法这一章是血泪经验。下面五条都是我在真实部署里遇到过的翻车现场按「现象 → 原因 → 解决」写供你对照排查。5.1 翻车一能拨通但全程无声录音文件是空的现象外呼显示已接通状态机也进了 ANSWERED但通话双方都听不到声音查录音文件是 0 字节。原因RTP 媒体流没有走通。多数情况是 NAT 环境下 SDP 里通告的是内网 IP供应商服务器往内网 IP 发 RTP 包当然找不到人少部分是云主机安全组没有放行 UDP 的大端口范围常见 10000-20000。解决先把 external_ip 填成公网地址重启服务再拨一通还不行就用抓包工具看 SDP 响应里的连接地址。排查顺序是注册是否成功 → 信令是否 200 OK → RTP 是否双向有包。源码日志里带 RTCP 收包统计看到 rx_packets 一直为 0基本就是媒体流问题别去怀疑模型。5.2 翻车二TTS 播完第一句后卡死现象开场白播完机器人既不继续说话也不挂断状态机长时间停在 SPEAKING。原因TTS 播放完成的回调没触发或者 LLM 返回内容里有特殊字符导致合成器生成了空音频段。最常见的是 LLM 输出里带 Markdown 标记和连续换行合成器处理出空片段播放器认为还没播完一直死等。解决先看日志里 tts_finished 事件有没有打出。没打出把 LLM 返回文本做一轮清洗去掉多余换行和 Markdown 标记再送 TTS打出了说明卡在 ASR 的 endpoint 检测上把 endpoint_timeout_ms 从 800 调到 1200 再试。这个坑十个人有八个会踩一句话说三遍LLM 输出别直接送 TTS清洗步骤不能省。5.3 翻车三并发一高所有通话延迟集体上升现象4 路并发时首句响应 1.2 秒加到 12 路直接变 4 到 5 秒用户大量秒挂。原因引擎并发配额和线路并发没对齐。ASR 流式识别是计算密集的12 路通话意味着同时 12 个识别任务在排队而识别服务的批处理没开或 GPU 显存不够任务全部积压。解决先把 asr.max_concurrent 和 tts.max_concurrent 设回和线路并发一致保证已接通通话的质量再给识别服务加显存监控如果占用超过 80%说明是硬件上限而不是参数问题。我在某生产盒子上跑过 16 路ASR 排队直接导致内存暴涨最后是靠降并发加 batch 复用解决的。5.4 翻车四用户一开口机器人开始自言自语现象用户「喂」了一声机器人把自己上一句话重复一遍或者把用户的话原样重复回去。原因打断时 TTS 播放缓冲没清空。停止命令只停了「当前正在合成的那一句」已经在播放队列里的音频还在往外发同时 ASR 恢复识别后把自己 TTS 的尾音识别成了用户说话形成回声环。解决打断动作必须是「停合成 清播放缓冲 延迟恢复 ASR」三步。源码包提供了 flush_audio_buffer 方法打断时调用顺序为 tts.stop() → tts.flush() → 等待 200ms → asr.resume()。顺序反了或少一步就会出现回声。另外软电话场景强烈建议打开 AEC 回声消除别省这一步。5.5 翻车五号码导入成功外呼队列却一动不动现象Excel 号码表导入了 1000 个号码日志一直显示 dialer idle一个电话都没打出去。原因号码表里混了非数字字符比如手机号前带「86」、中间有空格、最后一列有空行。导入逻辑默认「全部非法就放弃本次任务」结果整批号码被静默跳过。解决导入脚本里加了清洗规则——只保留数字、去掉前导 86、过滤空行和短号。如果你自己写导入逻辑记得把清洗失败率打出来超过一定比例就中止任务并提示不要静默跳过。我上线前把「所有号码带 86」的名单导进去过排查了半天才发现是清洗规则太严格。6. 上线前的验证脚本用模拟通话把全链路压出真实问题跑通了、调好了参数别急着上正式线路。我习惯上线前跑一遍源码包里的压力验证脚本用模拟通话把全链路压出真实问题来。这个脚本对每一通模拟呼叫做三件事测首句响应延迟、测打断响应延迟、验证拒绝场景是否 100% 正常收尾。# scripts/stress_verify.py 核心逻辑 import time def verify_one_round(client, call_id: str): stats { first_reply_latency_ms: 0, interrupt_latency_ms: 0, refuse_ok: False, } # 阶段 1模拟用户开场白测首句响应延迟 t0 time.time() client.inject_user_speech(call_id, 你好) stats[first_reply_latency_ms] client.wait_robot_reply_ms(call_id) # 阶段 2机器人话术播到一半模拟用户拒绝 client.inject_user_speech(call_id, 我不需要了别打了) t1 time.time() client.wait_tts_stop_ms(call_id) stats[interrupt_latency_ms] (time.time() - t1) * 1000 # 阶段 3检查是否走了收尾话术并挂机 stats[refuse_ok] client.session_ended_gracefully(call_id) return stats循环跑 50 轮把结果汇总。我用的验收标准是首句响应延迟低于 2000ms 的比例不低于 90%打断响应延迟低于 800ms拒绝场景正常收尾比例为 100%。任何一项不达标都不要上正式线路回去翻对应章节的参数。这个脚本还有个用途就是压引擎的极限。把并发逐步调大同时记录每轮的延迟分布你就能画出「并发-延迟」曲线找到这台机器真正的安全并发点而不是拍脑袋定一个数字。从那以后我每次上线前都会强制把 stress_verify 跑满 50 轮再配合 5 通真实线路人工试听才敢切正式外呼。这套流程虽然麻烦但帮我挡掉了至少三次「上线两小时就被投诉线路有问题」的事故。希望帮到你。本文还有配套的精品资源点击获取