259 lines
7.7 KiB
Markdown
259 lines
7.7 KiB
Markdown
# 实时通话 ASR 事件监控
|
||
|
||
一个基于 Spring Boot、React 和 AC 自动机的实时通话关键词监控系统。
|
||
前端持续提交市民与坐席的 ASR Final 文本,后端按通话维护上下文,匹配当前生效规则,并返回本次新增告警和通话累计结果。
|
||
|
||
## 主要功能
|
||
|
||
- 同时处理市民(`citizen`)和坐席(`agent`)文本
|
||
- 按 `callId` 维护共享的最近 ASR Final 上下文
|
||
- AC 自动机多关键词高效匹配
|
||
- 同一通话、同一事件只首次告警
|
||
- `callId + seq` 请求幂等
|
||
- 页面化规则新增、编辑、启停、删除与热发布
|
||
- 新增规则自动生成稳定的事件 ID,无需人工填写
|
||
- Excel 模板下载、规则导入和当前规则导出
|
||
- 历史版本查看与回溯;回溯旧内容时创建新版本
|
||
- 通话主动关闭与 TTL 自动清理
|
||
- H2 文件数据库持久化规则版本
|
||
|
||
## 技术栈
|
||
|
||
| 模块 | 技术 |
|
||
| --- | --- |
|
||
| 后端 | Java 21、Spring Boot 4、Spring MVC、Spring Data JPA |
|
||
| 匹配 | Aho-Corasick 自动机 |
|
||
| 数据库 | H2 文件数据库 |
|
||
| 前端 | React 19、TypeScript、Vite、Tailwind CSS、Motion |
|
||
| Excel | ExcelJS |
|
||
|
||
## 目录结构
|
||
|
||
```text
|
||
.
|
||
├── 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. 启动后端
|
||
|
||
在项目根目录执行:
|
||
|
||
```bash
|
||
./mvnw spring-boot:run
|
||
```
|
||
|
||
后端默认地址:`http://localhost:8080`
|
||
|
||
健康检查:
|
||
|
||
```text
|
||
http://localhost:8080/actuator/health
|
||
```
|
||
|
||
### 2. 启动前端
|
||
|
||
另开一个终端:
|
||
|
||
```bash
|
||
cd frontend
|
||
npm install
|
||
npm run dev
|
||
```
|
||
|
||
访问:`http://localhost:3000`
|
||
|
||
开发环境下,Vite 会将 `/api` 自动代理到 `http://localhost:8080`。
|
||
|
||
## 打包为单个 Spring Boot JAR
|
||
|
||
先构建前端并复制静态文件:
|
||
|
||
```bash
|
||
cd frontend
|
||
npm install
|
||
npm run build
|
||
cd ..
|
||
cp -R frontend/dist/. src/main/resources/static/
|
||
```
|
||
|
||
再打包和启动后端:
|
||
|
||
```bash
|
||
./mvnw clean package
|
||
java -jar target/demo-0.0.1-SNAPSHOT.jar
|
||
```
|
||
|
||
访问:`http://localhost:8080`
|
||
|
||
## ASR 接口
|
||
|
||
提交一条 ASR Final:
|
||
|
||
```http
|
||
POST /api/v1/asr-events
|
||
Content-Type: application/json
|
||
```
|
||
|
||
```json
|
||
{
|
||
"callId": "call-001",
|
||
"seq": 1,
|
||
"speaker": "citizen",
|
||
"text": "我的顺丰快递一直没有收到",
|
||
"final": true
|
||
}
|
||
```
|
||
|
||
`speaker` 只支持:
|
||
|
||
- `citizen`:市民
|
||
- `agent`:坐席
|
||
|
||
同一 `callId` 下,两个角色必须共享一套全局唯一的 `seq`:
|
||
|
||
```text
|
||
seq 1 citizen 市民发言
|
||
seq 2 agent 坐席发言
|
||
seq 3 citizen 市民发言
|
||
seq 4 agent 坐席发言
|
||
```
|
||
|
||
响应中的主要字段:
|
||
|
||
- `newAlerts`:本次新触发、需要前端提醒的事件
|
||
- `currentResults`:该通话当前累计命中的事件
|
||
- `duplicate`:是否为重复的 `callId + seq`
|
||
- `activeRuleVersion`:本次匹配使用的规则版本
|
||
|
||
通话结束后应主动释放会话:
|
||
|
||
```http
|
||
POST /api/v1/calls/{callId}/close
|
||
```
|
||
|
||
## 算法流程
|
||
|
||
```mermaid
|
||
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 标准化、转小写,并移除空白和常见中英文标点。例如:
|
||
|
||
```text
|
||
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 导入新增关键词规则,无需修改匹配代码。发布后自动构建新快照并热切换。
|
||
|
||
### 扩展匹配算法
|
||
|
||
当前 `matcher` 为 `ac-keyword`。可以继续实现正则、语义分类或大模型匹配器,并通过统一的匹配接口进行组合。建议将确定性关键词匹配保持在同步主链路,将耗时模型调用放入异步链路。
|
||
|
||
### 扩展事件处理
|
||
|
||
可以在产生 `newAlerts` 后增加消息队列、WebSocket、工单、短信或外部系统通知。建议通过领域事件异步处理,避免阻塞 ASR 接口。
|
||
|
||
### 扩展存储
|
||
|
||
开发环境使用 H2。生产环境可以迁移到 MySQL 或 PostgreSQL;JPA 领域模型和服务层不需要大幅调整。
|
||
|
||
### 扩展为多实例部署
|
||
|
||
当前通话上下文保存在单机 JVM 内存。多实例部署时需要选择以下方案之一:
|
||
|
||
- 按 `callId` 做一致性路由,保证同一通话始终进入同一实例
|
||
- 将会话上下文和幂等状态迁移到 Redis 等共享存储
|
||
- 使用分布式事件流按 `callId` 分区处理
|
||
|
||
### 安全与治理
|
||
|
||
生产环境建议增加管理员认证、规则变更审计、接口限流、数据库迁移工具,以及历史版本保留策略。
|
||
|
||
## 其他说明
|
||
|
||
- 前端对话中的关键词高亮是本地视觉提示。
|
||
- 真实告警始终以后端 `/api/v1/asr-events` 响应为准。
|
||
- 更完整的设计背景见 `asr_event_monitor_confirmed_technical_solution.md`。
|