服务定位

emotion-analysis-server 是一个本地 C++ HTTP 情绪分析服务,对外呼通话做通话后情绪判断:
文本走 BERT 多语种情感模型,音频走 wav2vec2 情绪识别,两路结果统一归一为极性
(positive / neutral / negative)+ 0–100 分后融合输出,供外呼平台按”客户情绪”筛选线索、
质检与重试决策。

与 ASR / TTS 两个服务不同,本服务直接调用 ONNX Runtime(C++ API)跑模型,不依赖
sherpa-onnx
。它不在通话媒体主链路上,而是被 智能外呼 的后台工作线程在通话结束后调用。

一句话:ASR/TTS 负责”听见、说出”,情绪分析负责”听完之后,判断客户是正面还是负面”。

它和其它组件的关系

1
2
3
4
5
6
7
8
9
10
┌────────────────────┐  通话结束后排队   ┌─────────────────────────┐
│ callout-server │ ───────────────► │ emotion-analysis-server │
│ (后台情绪分析 worker)│ POST /analyze │ (C++ HTTP :9090) │
└─────────┬──────────┘ ◄─────────────── └─────────────────────────┘
│ 融合极性 + 分数 │ 直接调用
│ 写回 call_attempts.emotion_* ▼
▼ ┌─────────────────────────┐
PostgreSQL(callout schema) │ ONNX Runtime (C++ API) │
│ emotion-text / emotion-audio │
└─────────────────────────┘
  • 启用方式在消费方 callout-server:emotionAnalysis.enabled=true 并配置本服务端点(详见 智能外呼);
  • callout-server 把 attemptId、尽力提取的转写文本、recordingUrltranscriptUrl 发到 POST /analyze
  • 返回结果写回 call_attempts.emotion_* 字段,前端可按情绪极性筛选与展示。

端点

服务监听默认 127.0.0.1:9090,对外暴露两个端点:

方法 路径 用途
GET /health 返回模型就绪状态(textReady / audioReady)与模型文件探测结果
POST /analyze 分析单条通话,返回融合后的情绪极性与分数

POST /analyze

请求体:

1
2
3
4
5
6
{
"attemptId": 1,
"text": "客户说暂时不需要",
"recordingUrl": "http://127.0.0.1/recordings/a.wav",
"transcriptUrl": "http://127.0.0.1/transcripts/a.json"
}
  • textrecordingUrl 至少提供一个
  • 两者都有时融合输出(文本权重 0.6、音频 0.4)。

响应体:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"ok": true,
"label": "positive",
"score": 81,
"confidence": 0.81,
"reason": "text=positive(97, 4.9star); audio=neutral(56, calm 0.76); fused=positive(81)",
"text": { "available": true, "label": "positive", "score": 97, "raw": "5 stars", "stars": 4.88 },
"audio": { "available": true, "label": "neutral", "score": 56, "raw": "calm", "confidence": 0.76 },
"fused": { "label": "positive", "score": 81 },
"risks": [],
"model": { "text": "emotion-text/model.onnx", "audio": "emotion-audio/model.onnx" },
"elapsedMs": 755.6
}
字段 说明
label / score 顶层 / fused 的归一极性与极性分(0–100,50 为中性),供业务直接使用
confidence 融合结果置信度
reason 人类可读的判定依据(文本 / 音频 / 融合各自结论)
text 文本模型原始输出:raw"4 stars"stars 为期望星级 1–5
audio 音频模型原始输出:raw"happy"confidence 为该类置信度
risks 命中的风险标记(如有)
  • 某一模态不可用时该子对象 available:false,并在 reason 中说明原因;
  • 两路都不可用时返回 ok:falselabel:"unavailable"

推理细节

文本(emotion-text

模型为 bert-base-multilingual-uncased-sentiment。内置 BERT WordPiece 分词器
(清理 → CJK 按字切分 → 空白切分 → 小写 → 标点切分 → 贪心 WordPiece),输出 5 类星级 logits,
softmax 后取期望星级 E,score = round((E-1)/4*100),再按分数映射极性(<40 负、>60 正、其余中性)。

注意

分词仅做 ASCII 小写,未做全 Unicode 大小写 / 去音标——对中文、英文无影响,
带音标的拉丁文本可能略有偏差。

音频(emotion-audio

模型为 wav2vec2-emotion-recognition。拉取 recordingUrl → 解码 WAV → 按配置选择声道或混音
重采样到 16k → 静音过滤(默认开启)→ 最多截取 30s → 归一化 → 7 类情绪 logits → argmax + 置信度 →
情绪映射到极性基准分,并按置信度向中性收缩。

  • 声道选择audioChannel 支持 mix / left / right / channel0 / channel1 / customer / agent
    leftchannel0rightchannel1customer / agent 通过 audioChannelRoles 映射到实际声道。
    会议 WAV 几个声道、客户 / 坐席各在哪个声道取决于 FreeSWITCH 录音方式,上线前务必用真实录音听音确认;
    若录音只有单声道却配置成 right / channel1,音频模态会失败并在 reason 说明,不会静默回退;
  • 静音过滤:按 20ms 短帧 RMS 判断并保留语音段前后约 200ms,可用 audioTrimSilence:false 关闭;
  • 仅支持 http://无 TLShttps:// 会被拒绝并在 reason 说明);
  • WAV 支持 PCM 8/16/24/32-bit 与 IEEE float32;压缩格式(MP3 / Opus 等)不支持
  • 重采样为线性插值;静音过滤后的录音超过 30s 截断

模型目录结构

模型放在仓库 onnx-platform/models/ 下:

1
2
3
4
onnx-platform/models/emotion-text/
model.onnx labels.json vocab.txt
onnx-platform/models/emotion-audio/
model.onnx labels.json preprocess.json

配置

onnx-platform/emotion-analysis-server/config.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"listenHost": "127.0.0.1",
"listenPort": 9090,
"workerThreads": 2,
"maxQueuedRequests": 32,
"maxRequestBodyBytes": 2097152,
"logDir": "logs",
"textModelDir": "emotion-text",
"audioModelDir": "emotion-audio",
"audioChannel": "customer",
"audioChannelRoles": { "customer": "right", "agent": "left" },
"audioTrimSilence": true,
"audioSilenceThreshold": 0.01
}
字段 默认 说明
listenHost / listenPort 127.0.0.1 / 9090 HTTP 监听地址
workerThreads 2 HTTP 处理线程数
maxQueuedRequests 32 等待队列上限,超出立即 503
maxRequestBodyBytes 2097152 单请求体最大字节数
logDir logs 日志目录,相对路径按运行目录解析,日志按天滚动
textModelDir / audioModelDir emotion-text / emotion-audio 相对 onnx-platform/models/ 的模型目录
audioChannel customer 音频分析使用的声道:mix/left/right/channel0/channel1/customer/agent
audioChannelRoles {customer:right, agent:left} customer / agent 角色到实际声道的映射
audioTrimSilence / audioSilenceThreshold true / 0.01 静音过滤开关与 RMS 阈值

依赖与构建

  • ONNX Runtime 发行版置于 onnx-platform/onnxruntime/<平台>/(含 include/lib/);
    CMake 变量 ONNXRUNTIME_ROOT 可覆盖默认路径。这一点与 ASR/TTS 服务使用仓库内
    sherpa-onnx 预编译包不同——本服务直接依赖 ONNX Runtime

  • 构建(Windows):

    1
    2
    cd onnx-platform\emotion-analysis-server
    .\build.ps1

    产物安装到 target\win_x64\(含 onnxruntime.dll)。bun run dev / bun run dev:emotion
    即从这里启动它。

  • 运行时需能定位 onnx-platform/models/(按工作目录向上查找 models/),例如从 target\win_x64\ 启动。

验证

启动后查看健康状态:

1
curl http://127.0.0.1:9090/health

分析一条文本:

1
2
3
curl -s -X POST http://127.0.0.1:9090/analyze \
-H "Content-Type: application/json" \
-d '{"attemptId":1,"text":"你们的服务真好"}'

仓库内还提供了 request-emotion.js(兼容 Node 18+ / Bun),可跑内置中英文正负样本套件、
单条文本分析、文本 + 录音融合:

1
2
3
4
node .\request-emotion.js                                  # 内置样本套件(/health + 样本)
node .\request-emotion.js "你们的服务真好" # 分析一条文本
node .\request-emotion.js "文本" --rec http://127.0.0.1:8099/a.wav # 文本 + 录音融合
node .\request-emotion.js --health # 仅查看健康状态

局限

注意

  • 仅做通话后离线分析,不是实时情绪(不在媒体主链路上);
  • 音频侧只接受 http:// 的未压缩 WAV,录音超过 30s 截断;
  • 与 ASR/TTS 服务一样无鉴权、无限流(除 maxQueuedRequests),建议仅在内网 / 可信网络运行;
  • 文本极性基于多语种情感星级模型,属于”情感倾向”而非细粒度情绪标签。

下一步