做过呼叫中心 / 智能外呼 / IVR 语音机器人的同学都知道,Java 对接 Asterisk 是个体力活:AGI 要自己注册脚本、管理线程和生命周期、自己轮询检测挂机;AMI 要自己建连接、写一个巨大的
onManagerEvent,然后在里面堆一屏if (event instanceof XxxEvent)。光是把这些样板代码搭起来,一两天就没了,还处处是坑。今天介绍的开源项目 SmartCall (AI 客服呼叫中心),把这一切收敛成了几个注解 。它的通信基座
jpower-core-asterisk让「实现一个 AGI 脚本」「监听一个 AMI 事件」都变成了几行代码的事。这篇文章就用最直白的对比,带你看清它到底有多简单。
一、SmartCall 是什么?
SmartCall 是一套基于 AI 大模型 + Asterisk 通信引擎 的新一代智能客服呼叫中心系统,深度融合了 AI 语音机器人、可视化 IVR 流程编排、实时 ASR/TTS、大模型意图识别、端到端实时对话模型等能力,覆盖从「呼入智能应答」到「智能外呼」的全链路。
- 技术栈:Java 17 + Spring Boot 3.5 + Spring Cloud 2025 + MyBatis-Flex + Asterisk(PJSIP) + Spring AI,前端 Vue 3。
- 架构 :微服务 / 单体双模,可独立部署也能一键启动;原生多租户、RBAC、OAuth2。
- 协议:Apache 2.0,允许商用。
而它「打电话」这件事的所有底座能力,都来自 smart-aster 模块 + jpower-core-asterisk 这个 jar。下面我们就聚焦这个 jar,看看 AGI 和 AMI 是怎么被它「注解化」的。
二、5 分钟接入:一个依赖 + 一段配置
引入依赖:
xml
<dependency>
<groupId>top.jpower</groupId>
<artifactId>jpower-core-asterisk</artifactId>
</dependency>
写一段配置(Spring Boot 自动装配,无需任何 @Bean 手写):
yaml
asterisk:
ami: # AMI:监听全局事件、主动外呼
host: 127.0.0.1
port: 5038
username: admin
password: ******
event: true
agi: # AGI:IVR 交互
port: 4573
context:
in: inbound-dialer
out: outbound-dialer
exten: robot
voice-root-dir: /data/ # 录音根目录
启动后,模块自动帮你做完了这些「脏活」:
- 登录 AMI,创建好
ManagerConnection和AsteriskServer两个 Bean(外呼、查询直接注入即用); - 拉起 FastAGI Server 监听 4573 端口,随 Spring 生命周期自动启停;
- 内置挂机事件管理器、实时音频(TTS/ASR)媒体流通道。
你要做的,只剩写业务。
三、AGI:一个注解 + 一个类,搞定一路通话
3.1 对比一下就知道有多爽
❌ 原生 asteriskjava 的写法(简化):
java
// 1. 实现 AgiScript
public class MyScript implements AgiScript {
public void service(AgiRequest request, AgiChannel channel) {
try {
channel.answer();
channel.exec("Playback", "hello");
// 挂机检测?自己轮询 channel.getStatus()...
} catch (AgiHangupException e) { /* 到处 try-catch */ }
}
}
// 2. 自己 new DefaultAgiServer,注册 MappingStrategy,起线程,管理生命周期
// 3. TTS 放音、ASR 识别、录音、收号?全部自己造轮子
✅ 用 jpower-core-asterisk 的写法:
java
@Slf4j
@Agi("call.agi") // ← 一个注解,绑定 dialplan 脚本名
@Component // ← 交给 Spring,自动注册进 AGI 路由表
public class AgiHandler extends AgiAbstractScript {
@Override
public void service(AgiSupport support) {
support.answer(); // 接听
support.playback("您好,欢迎致电"); // TTS 实时合成放音
String said = support.radio("请说出您的需求"); // ASR 识别(可被打断)
log.info("用户说:{}", said);
support.hangup(); // 挂断
}
@Override
public void hangup(AgiSupport support) {
// 挂机回调:框架已用「AMI 事件驱动 + 轮询兜底」保证幂等,你只管清理
support.thread().interrupt();
}
}
没有线程管理、没有生命周期、没有 instanceof、没有挂机轮询、没有 try-catch 地狱 ------@Agi + @Component + 继承 AgiAbstractScript,一个能接听、放音、识别、挂断的 IVR 脚本就成了。
3.2 dialplan 只需一行
Asterisk 的 extensions.conf 里把电话交给它:
ini
[inbound-dialer]
exten = _X.,1,AGI(agi://127.0.0.1/call.agi?type=1) ; 名字对上 @Agi("call.agi") 即可
same = n,Hangup()
@Agi 的 value 就是这里的脚本名,框架启动时自动建好「脚本名 → Bean」的路由表。
3.3 AgiSupport:一行代码一个能力
service(AgiSupport support) 里的 support 是通话级对象(每来一通电话框架自动实例化),把 IVR 里最常用的动作全封装成了方法,真正做到「一行代码一个功能」:
| 一行代码 | 干的事 |
|---|---|
support.answer() / support.hangup() |
接听 / 挂断 |
support.playback("文本") |
TTS 实时合成并播放 |
support.radio("提示语") |
边播边 ASR 识别,用户一说话立即打断(barge-in) |
support.received("请输入工号", "#") |
放音后 DTMF 收号 |
support.exec("queue", "kf", ...) |
执行任意 dialplan 应用(转人工队列等) |
support.mixMonitor(url) / stopMixMonitor(id) |
双向录音并自动上传 |
support.getVariable(k) / setVariable(k,v) |
读写通道变量 |
support.streamSession() |
拿全双工裸音频流,对接端到端语音大模型 |
尤其是 radio()------TTS 播报和 ASR 识别是全双工并行的,用户一开口就打断机器人,纯 Java 侧控制、无磁盘中转。做「能被打断的语音机器人」最难的一块,这里一个方法搞定。
3.4 需要定制?继承一下就行
@Agi(value = "call.agi", support = AgiSupportImpl.class) 可以指定你自己的支撑类,用来注入 ASR/TTS 引擎、加业务回调。SmartCall 就是这么干的:
java
public class AgiSupportImpl extends AgiSupport {
public AgiSupportImpl(AgiChannel channel, AgiRequest request, Thread thread) {
super(channel, request, thread);
// support 是框架反射 new 的,用 SpringUtil 取依赖即可
this.processService = SpringUtil.getBean(IProcessService.class);
}
@Override protected AsrClient initAsr() { /* 按流程配置构建 ASR */ }
@Override protected TtsClient initTts() { /* 构建 TTS */ }
@Override public void realtimeData(Boolean isBot, byte[] pcm, String content) {
// 每一句话(含音频)都回调这里:实时推前端、落库做通话转写
}
}
四、AMI:两个注解,彻底告别 instanceof 地狱
4.1 对比一下
❌ 原生写法:
java
connection.addEventListener(event -> {
if (event instanceof NewChannelEvent e) { /* ... */ }
else if (event instanceof HangupEvent e) { /* ... */ }
else if (event instanceof AgentConnectEvent e) { /* ... */ }
// ...几十行 else if,还得自己管理连接、重连、线程
});
✅ jpower-core-asterisk 写法:
java
@Slf4j
@Component
@AmiListener({NewChannelEvent.class, HangupEvent.class}) // ← 类上声明「我关心哪些事件」
public class MyListener extends EventAbstractListener {
@AmiEvent(NewChannelEvent.class) // ← 方法上声明「我处理哪个事件」
public void onNewChannel(NewChannelEvent event) {
log.info("新通道:{}", event.getChannel());
}
@AmiEvent(HangupEvent.class)
public void onHangup(HangupEvent event) {
log.info("挂断:{},原因:{}", event.getChannel(), event.getCauseTxt());
}
}
就这么简单。框架会自动:
- 收集所有带
@AmiListener的 Spring Bean; - 按注解里的事件类型过滤(只有你订阅的事件才投递给你);
- 反射派发到对应的
@AmiEvent方法。
你只写「处理逻辑」,订阅、过滤、分发、连接管理全是框架的事。
4.2 两个注解的分工
@AmiListener({...})(类级):订阅白名单,声明这个监听器关心哪些事件,留空则收全部。@AmiEvent(Xxx.class)(方法级):分发路由,一个事件进来自动调到标注了它的方法。
一个类可以同时处理十几种事件,各自独立成方法,清爽得不得了。SmartCall 的话单监听器 RecordCallListener 就是这样,一个方法管一个事件:
java
@Component
@Order(20)
@AmiListener({NewChannelEvent.class, NewCallerIdEvent.class, QueueCallerJoinEvent.class,
AgentConnectEvent.class, VarSetEvent.class, HangupRequestEvent.class, HangupEvent.class})
public class RecordCallListener extends LinkedEventListener {
@AmiEvent(NewChannelEvent.class) public void newChannel(NewChannelEvent e) { /* 建话单 */ }
@AmiEvent(QueueCallerJoinEvent.class) public void join(QueueCallerJoinEvent e) { /* 转人工入队 */ }
@AmiEvent(AgentConnectEvent.class) public void connect(AgentConnectEvent e) { /* 坐席接通 */ }
@AmiEvent(HangupEvent.class) public void hangup(HangupEvent e) { /* 结算话单 */ }
}
4.3 需要「跨事件推理」?换个基类即可
如果业务需要回看「同一通电话之前发生过什么」(比如判断是用户先挂还是坐席先挂),把基类从 EventAbstractListener 换成 LinkedEventListener ,就能拿到按 linkedId 聚合的整条事件链:
java
// CACHE 里是这通电话按时序排列的所有事件,随便回溯
boolean userHungFirst = CACHE.get(event.getLinkedId()).stream()
.filter(e -> e instanceof HangupRequestEvent)
.anyMatch(e -> isUserChannel(((HangupRequestEvent) e).getChannel()));
框架内部用「监听器计数」保证所有监听器都处理完,事件才写入链路缓存,多监听器读到的视图完全一致。你不用管这些细节,继承就完事了。
4.4 主动外呼?注入 Bean 直接用
模块已经装配好 AsteriskServer,配合 OriginateAction 几行就能发起外呼。SmartCall 的 CallServiceImpl 甚至本身就是一个监听器 (@Service + @AmiListener),一边外呼一边监听挂机回填结果:
java
@Service
@AmiListener({HangupRequestEvent.class, HangupEvent.class})
public class CallServiceImpl extends EventAbstractListener implements CallService {
private final AsteriskServer asteriskServer; // 直接注入
public CallVO call(String phone, String lineId) {
OriginateAction action = new OriginateAction();
action.setChannel("PJSIP/" + phone + "@" + lineId);
action.setContext(asteriskProperties.getContext().getOut());
action.setExten(asteriskProperties.getContext().getExten());
action.setPriority(1);
action.setTimeout(60000L);
asteriskServer.originateAsync(action, callback); // 异步发起
// ...
}
@AmiEvent(HangupEvent.class)
public void onHangup(HangupEvent e) { /* 回填呼叫结果 */ }
}
一个小提醒:
@AmiListener不只能打在专门的 Listener 上,任何 Spring Bean(含@Service)都可以,非常灵活。
五、「简单」到底体现在哪?一张表说清
| 能力 | 原生 asteriskjava | jpower-core-asterisk |
|---|---|---|
| AGI 脚本注册 | 手写 Server + MappingStrategy + 起线程 | @Agi("x.agi") + @Component |
| AGI 生命周期 | 自己管理 | 随 Spring 自动启停 |
| 挂机检测 | 自己轮询通道状态 | 框架 AMI 事件驱动 + 轮询兜底,幂等 |
| TTS 放音 / ASR 识别 | 自己对接、自己造轮子 | playback() / radio() 一行搞定 |
| AMI 连接 | 自己建、自己登录、自己重连 | 自动装配成 Bean |
| AMI 事件分发 | 一屏 if (event instanceof ...) |
@AmiListener + @AmiEvent |
| 事件链路回溯 | 自己缓存、自己管理 | 继承 LinkedEventListener |
| 主动外呼 | 自己拼 Action、自己管回调 | 注入 AsteriskServer 即用 |
结论很直白:原本要写几百行样板 + 踩无数坑的活儿,现在几个注解、几行代码就搞定。 这正是 SmartCall 能把精力集中在「AI 能力」和「IVR 编排」上的原因------通信底座足够简单可靠。
六、这些能力,在 SmartCall 里长成了什么样
上面这些「简单的注解」,最终支撑起了 SmartCall 一整套生产级功能:
- 🧠 AI 智能应答 :集成通义千问、DeepSeek 等大模型做意图识别;
radio()的全双工打断能力让 AI 机器人对话接近真人;对接 MaxKB 知识库做多轮智能问答。 - 🔊 端到端实时对话模型 :基于
streamSession()的全双工裸流,音频直通大模型服务端,由模型端到端完成「听-想-说」,首响延迟更低,支持服务端 VAD 打断。 - 📞 可视化 IVR 流程编排 :基于 LogicFlow 拖拽式设计,Say / Answer / Intention / Agent / Transfer / Extract / Script 等丰富节点,每个节点背后就是
AgiSupport的一个方法。 - 📋 通话记录(CDR) :
LinkedEventListener的事件链路缓存,支撑起复杂的话单归因、转接记录、呼损统计。 - 📤 智能外呼 :
OriginateAction+ AMI 挂机监听,实现批量外呼、智能重拨、多机器人并发。 - 👥 坐席管理 / 转人工:AMI 事件驱动的坐席状态监控、队列分配、通话保持与转接。
一句话:你看到的每一个「智能」功能,底层都是这几个注解在稳稳地跑。
七、为什么值得试试 SmartCall?
- ✅ 通信底座极简:AGI/AMI 注解化,二次开发门槛低,本文的例子拿来即用。
- ✅ AI 能力齐全:大模型意图识别、ASR/TTS 多厂商可插拔、端到端实时对话、知识库智能体,全都开箱即用。
- ✅ 架构够企业级:微服务 + 单体双模、多租户、RBAC、OAuth2、网关限流、SkyWalking 链路追踪。
- ✅ 部署够省心 :提供完整 Docker Compose 编排,Asterisk 镜像一键拉起,完整支持内网私有化部署。
- ✅ 开源可商用:Apache 2.0 协议。
八、立即体验
⭐ Gitee 开源仓库:gitee.com/gdzWork/Sma...
🌐 官方网站:qidiangk.com
写在最后
Java 对接 Asterisk 曾经是件「脏活累活」,但有了 jpower-core-asterisk 这样的注解化封装,AGI 一个注解、AMI 两个注解,就能把复杂的电话通信能力稳稳地跑起来。而 SmartCall 正是把这套「简单」发挥到极致的开源实践------它让你不必再纠结底层通信,而是把精力真正放在「让 AI 讲好每一通电话」上。
如果你正在做智能客服、AI 外呼、IVR 语音机器人,或者受够了原生 asteriskjava 的繁琐,强烈建议去演示站点点看,或者给项目点个 Star ⭐,你的支持是它持续迭代的最大动力。