实时通话 ASR 事件监控

一个基于 Spring Boot、React 和 AC 自动机的实时通话关键词监控系统。 前端持续提交市民与坐席的 ASR Final 文本,后端按通话维护上下文,匹配当前生效规则,并返回本次新增告警和通话累计结果。

技术栈

模块 技术
后端 Java 21、Spring Boot 4、Spring MVC、Spring Data JPA
匹配 Aho-Corasick 自动机
数据库 H2 文件数据库
前端 React 19、TypeScript、Vite、Tailwind CSS、Motion
Excel ExcelJS

目录结构

.
├── frontend/                         # React 前端
├── src/main/java/com/example/demo/
│   ├── api/                          # REST 接口
│   ├── application/                  # 监控、规则和会话服务
│   ├── domain/                       # 请求、响应和领域模型
│   ├── matcher/                      # AC 自动机及匹配器
│   ├── repository/                   # 规则版本和会话存储
│   ├── job/                          # 过期会话清理
│   └── support/                      # 异常、文本标准化等
├── src/main/resources/
│   ├── application.yml              # 服务配置
│   └── rules/default-rules.json      # 初始规则
└── pom.xml

本地运行

环境要求

  • JDK 21
  • Node.js 18 或更高版本
  • npm

1. 启动后端

在项目根目录执行:

./mvnw spring-boot:run

后端默认地址:http://localhost:8080

健康检查:

http://localhost:8080/actuator/health

2. 启动前端

另开一个终端:

cd frontend
npm install
npm run dev

访问:http://localhost:3000

开发环境下Vite 会将 /api 自动代理到 http://localhost:8080

打包为单个 Spring Boot JAR

先构建前端并复制静态文件:

cd frontend
npm install
npm run build
cd ..
cp -R frontend/dist/. src/main/resources/static/

再打包和启动后端:

./mvnw clean package
java -jar target/demo-0.0.1-SNAPSHOT.jar

访问:http://localhost:8080

ASR 接口

提交一条 ASR Final

POST /api/v1/asr-events
Content-Type: application/json
{
  "callId": "call-001",
  "seq": 1,
  "speaker": "citizen",
  "text": "我的顺丰快递一直没有收到",
  "final": true
}

speaker 只支持:

  • citizen:市民
  • agent:坐席

同一 callId 下,两个角色必须共享一套全局唯一的 seq

seq 1  citizen  市民发言
seq 2  agent    坐席发言
seq 3  citizen  市民发言
seq 4  agent    坐席发言

响应中的主要字段:

  • newAlerts:本次新触发、需要前端提醒的事件
  • currentResults:该通话当前累计命中的事件
  • duplicate:是否为重复的 callId + seq
  • activeRuleVersion:本次匹配使用的规则版本

通话结束后应主动释放会话:

POST /api/v1/calls/{callId}/close

算法流程

flowchart TD
    A["接收 ASR 请求"] --> B{"final=true?"}
    B -- 否 --> C["返回当前结果,不写入上下文"]
    B -- 是 --> D{"callId + seq 是否重复?"}
    D -- 是 --> E["返回 duplicate=true"]
    D -- 否 --> F["写入该通话的共享上下文窗口"]
    F --> G["拼接最近 N 条 Final"]
    G --> H["文本标准化"]
    H --> I["AC 自动机匹配全部关键词"]
    I --> J["合并事件并进行通话级去重"]
    J --> K["返回 newAlerts 和 currentResults"]

1. 通话隔离与并发

每个 callId 拥有独立会话状态和锁。同一通话按锁串行处理,不同通话可以并行处理。

2. 共享上下文窗口

市民和坐席文本进入同一个、按 seq 排序的最近 N 条窗口。默认保留 5 条,能够识别被 ASR 切分到相邻 Final 中的关键词。

3. 文本标准化

匹配前会进行 Unicode NFKC 标准化、转小写,并移除空白和常见中英文标点。例如:

E M S       → ems
顺丰,快递  → 顺丰快递

4. AC 自动机匹配

所有已启用关键词被一次性构建为不可变 AC 自动机快照。每条匹配文本只需扫描一次即可发现多个关键词,适合规则和关键词数量持续增加的场景。

5. 告警去重

匹配结果按业务事件聚合。同一个事件在同一次通话中只进入一次 newAlerts,重复命中仍保留在累计状态中,但不会重复提醒。

规则管理与版本回溯

规则保存采用完整文档发布,并携带 baseVersion 做乐观并发控制。事务提交成功后才切换运行时 AC 自动机快照。

主要接口:

方法 路径 说明
GET /api/v1/admin/rules 获取当前生效规则
PUT /api/v1/admin/rules 保存并发布新版本
GET /api/v1/admin/rules/versions?limit=20 获取历史版本
POST /api/v1/admin/rules/versions/{version}/restore 回溯历史版本

回溯不会修改或删除旧记录。例如当前 V8 回溯到 V3系统会复制 V3 的内容并创建新的 V9然后切换 V9 为生效版本。

配置

主要配置位于 src/main/resources/application.yml

配置 默认值 说明
server.port 8080 HTTP 端口
monitor.recent-final-window-size 5 每通话保留的最近 Final 数量
monitor.max-processed-seqs 500 幂等序列号历史上限
monitor.session-ttl PT2H 会话空闲过期时间
monitor.session-cleanup-interval PT10M 过期会话清理周期

规则数据库默认保存在项目运行目录的 data/rules.mv.db

扩展性

扩展规则

直接通过前端页面或 Excel 导入新增关键词规则,无需修改匹配代码。发布后自动构建新快照并热切换。

扩展匹配算法

当前 matcherac-keyword。可以继续实现正则、语义分类或大模型匹配器,并通过统一的匹配接口进行组合。建议将确定性关键词匹配保持在同步主链路,将耗时模型调用放入异步链路。

扩展事件处理

可以在产生 newAlerts 后增加消息队列、WebSocket、工单、短信或外部系统通知。建议通过领域事件异步处理避免阻塞 ASR 接口。

扩展存储

开发环境使用 H2。生产环境可以迁移到 MySQL 或 PostgreSQLJPA 领域模型和服务层不需要大幅调整。

扩展为多实例部署

当前通话上下文保存在单机 JVM 内存。多实例部署时需要选择以下方案之一:

  • callId 做一致性路由,保证同一通话始终进入同一实例
  • 将会话上下文和幂等状态迁移到 Redis 等共享存储
  • 使用分布式事件流按 callId 分区处理

安全与治理

生产环境建议增加管理员认证、规则变更审计、接口限流、数据库迁移工具,以及历史版本保留策略。

其他说明

  • 前端对话中的关键词高亮是本地视觉提示。
  • 真实告警始终以后端 /api/v1/asr-events 响应为准。
  • 更完整的设计背景见 asr_event_monitor_confirmed_technical_solution.md
Description
No description provided
Readme 848 KiB
Languages
TypeScript 49.6%
Java 49.5%
Shell 0.4%
CSS 0.4%
HTML 0.1%