一个注解搞定 AGI,两个注解搞定 AMI —— SmartCall 让 Java 对接 Asterisk 简单到离谱

简介: SmartCall 是基于 Java + Asterisk 的开源智能客服呼叫中心,其核心模块 `jpower-core-asterisk` 将繁琐的 AGI/AMI 对接封装为 `@Agi` 和 `@AmiEvent` 等注解,几行代码即可实现通话控制、TTS/ASR、事件监听与外呼,大幅降低开发门槛。Apache 2.0 协议,支持商用。

做过呼叫中心 / 智能外呼 / 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 分钟接入:一个依赖 + 一段配置

引入依赖:

<dependency>
    <groupId>top.jpower</groupId>
    <artifactId>jpower-core-asterisk</artifactId>
</dependency>

写一段配置(Spring Boot 自动装配,无需任何 @Bean 手写):

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 的写法(简化):

// 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 的写法:

@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 里把电话交给它:

[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 就是这么干的:

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 对比一下

❌ 原生写法:

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 写法:

@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());
    }
}

就这么简单。框架会自动:

  1. 收集所有带 @AmiListener 的 Spring Bean;
  2. 按注解里的事件类型过滤(只有你订阅的事件才投递给你);
  3. 反射派发到对应的 @AmiEvent 方法。

你只写「处理逻辑」,订阅、过滤、分发、连接管理全是框架的事。

4.2 两个注解的分工

  • @AmiListener({...})(类级):订阅白名单,声明这个监听器关心哪些事件,留空则收全部。
  • @AmiEvent(Xxx.class)(方法级):分发路由,一个事件进来自动调到标注了它的方法。

一个类可以同时处理十几种事件,各自独立成方法,清爽得不得了。SmartCall 的话单监听器 RecordCallListener 就是这样,一个方法管一个事件:

@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 聚合的整条事件链:

// 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),一边外呼一边监听挂机回填结果:

@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 开源仓库:https://gitee.com/gdzWork/SmartCall


写在最后

Java 对接 Asterisk 曾经是件「脏活累活」,但有了 jpower-core-asterisk 这样的注解化封装,AGI 一个注解、AMI 两个注解,就能把复杂的电话通信能力稳稳地跑起来。而 SmartCall 正是把这套「简单」发挥到极致的开源实践——它让你不必再纠结底层通信,而是把精力真正放在「让 AI 讲好每一通电话」上。

如果你正在做智能客服、AI 外呼、IVR 语音机器人,或者受够了原生 asteriskjava 的繁琐,强烈建议去演示站点点看,或者给项目点个 Star ⭐,你的支持是它持续迭代的最大动力。

相关文章
|
5天前
|
云安全 人工智能 运维
当攻击全面Agent化,你的安全防御能否跟上AI Speed?
多起客户侧AK泄露事件应急的真实观察
1241 4
|
28天前
|
人工智能 JSON API
全网刷屏的 Jev 模型正式开放!一手实战测评 + 保姆级教程
全网爆火的 Jev 模型是什么?有什么用?怎么使用?怎么接入 AI 编程工具?效果真的好么?傻子可懂的 Jev 保姆级实战教程 + 项目实战测评来啦
9782 32
|
8天前
|
存储 人工智能 安全
千问办公官网入口网页版登录:qwenwork.cn 注册送2000积分、登录送100积分
阿里云千问办公(QwenWork)是AI智能工作平台,支持一句话生成PPT、表格、网页、视频及数据分析。个人免费版送2000积分+每日登录赠100分,含1GB存储、5个发布页;企业版198元/席/月起。阿里千问办公QwenWork官网:https://t.aliyun.com/U/0VCTGt 阿里AI工作平台,一句话完成数据分析、PPT 生成、视频剪辑、网页搭建等复杂任务
|
3天前
|
人工智能 API 开发工具
DeepSeek Harness 神插件:白嫖 13 家大厂模型
`dsh-our-free-model` 是 DeepSeek Harness 神级插件:零配置、零API Key、零账号,聚合13家大厂免费模型(含DeepSeek V4.1 Flash、Kimi K3等),支持本地网关、用量看板与自动降级,真正实现“白嫖即用”。
|
14天前
|
存储 人工智能 并行计算
大模型本地部署终端选型方法论:以 Qwen3.8-27B 为例的四档分层完整流程
本文提出一套大模型本地部署终端选型方法论:定约束、定档位、定框架、定参数四步决策法,配合入门、主力、质量、无损四档分层模型。以 Qwen3.8-27B 实测数据为例,逐环节解读显存、带宽、存储、散热、系统、预算等要素,给出面向不同预算的优选方案、决策自查清单与市场观察框架。文末前瞻 AI 笔记本的 CPU+GPU 与统一内存两条路线,论证四步决策法在新品类上的延续性。
|
5天前
|
Web App开发 人工智能 JavaScript
太猛了,一个月干到15W+,给大家推荐一个最近爆火的开源逆向工具:REA
REA(Reverse Engineer Anything)是近期爆火的开源AI逆向工具,4月发布,1个月内斩获15万+ Star。它让AI Agent无需源码即可分析二进制、APK、JS/Electron、HAR包等17类目标,自动反编译、生成可运行代码,并强制返回带证据与局限的结论。全本地执行,MIT协议,支持Hopper/Ghidra/IDA等引擎,一行命令即可上手。
621 1
太猛了,一个月干到15W+,给大家推荐一个最近爆火的开源逆向工具:REA
|
12天前
|
人工智能 JSON 自然语言处理
2026 年 Jev 决策模型深度拆解:原理解读、实战测评与保姆级落地教程
有一款特殊AI模型在开发者圈子刷屏,它摒弃传统大模型擅长的对话聊天能力,专注做高速结构化决策,它就是TypeSafe AI推出的Jev模型。该模型由ChatGPT共同发明人Diogo Almeida主导研发,定位为**System One Model(系统一模型)**,对标人类大脑快速直觉判断的思维模式,在响应延迟、调用成本、结构化输出稳定性上相比传统生成式大模型有着巨大差异。本文会完整拆解Jev底层原理、三大核心原语能力、适用业务场景,同时提供可直接运行的curl、Python代码示例,并且结合多组实测数据,客观分析模型优势与能力边界,帮助普通开发者和AI应用从业者快速上手落地。
927 1
|
9天前
|
人工智能 JSON 自然语言处理
2026 年 Jev 决策模型全解析:核心原理拆解,性能实战测评与落地部署教程
有一款特殊AI模型在开发者圈子刷屏,它摒弃传统大模型擅长的对话聊天能力,专注做高速结构化决策,它就是TypeSafe AI推出的Jev模型。该模型由ChatGPT共同发明人Diogo Almeida主导研发,定位为System One Model(系统一模型),对标人类大脑快速直觉判断的思维模式,在响应延迟、调用成本、结构化输出稳定性上相比传统生成式大模型有着巨大差异。本文会完整拆解Jev底层原理、三大核心原语能力、适用业务场景,同时提供可直接运行的curl、Python代码示例,并且结合多组实测数据,客观分析模型优势与能力边界,帮助普通开发者和AI应用从业者快速上手落地。
592 0

热门文章

最新文章