跳到正文

emotion-analysis-server

本地 HTTP 服务,对外呼通话做情绪分析。文本走 BERT 多语种情感模型,音频走 wav2vec2 情绪识别, 两者结果统一归一为极性(positive / neutral / negative)+ 0–100 分后融合输出。

直接调用 ONNX Runtime(C++ API)跑模型,不依赖 sherpa-onnx。

默认监听 127.0.0.1:9090config.jsonserver.host / server.port), 由 apps/callout-server 的情绪分析 worker 调用;跨机部署时把 server.host 改为可被访问的地址。

端点

  • GET /health — 返回模型就绪状态(textReady / audioReady)与模型文件探测结果。
  • POST /analyze — 分析单条通话。
  • GET /emotion-audio/<fileName>.wav — 试听本次音频情绪分析实际送入模型前保存的 16k 单声道 WAV。

默认读取运行目录下的 config.jsonlogging.dir 可配置日志目录,默认 "logs"; 相对路径按运行目录解析,日志文件按天滚动。

音频分析默认按配置中的客户声道执行。示例配置:

json
{
  "audio": {
    "channel": "customer",
    "channelRoles": {
      "customer": "right",
      "agent": "left"
    },
    "trimSilence": true,
    "silenceThreshold": 0.01,
    "processedDir": "public/emotion-audio"
  }
}
  • audio.channel 支持 mixleftrightchannel0channel1customeragent
  • left 等价于 channel0right 等价于 channel1customer / agent 通过 audio.channelRoles 映射到实际声道。
  • 会议 WAV 有几个声道取决于 FreeSWITCH 录音方式,不保证“一个参与者一个声道”。上线前请用真实录音听音确认 客户 / 坐席分别在哪个声道,再调整 audio.channelRoles
  • 如果录音只有单声道但配置为 right / channel1,音频模态会失败并在 reason 中说明声道不存在,不会静默回退。
  • audio.processedDir 用于保存声道选择/混音、重采样、静音裁剪、30s 截断后的最终分析音频(归一化前)。 分析响应里的 audio.url 返回短文件名,浏览器播放完整 URL 由调用方系统按自身媒体配置拼接。

请求示例:

json
{
  "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)。

响应示例:

json
{
  "ok": true,
  "label": "positive",          // 融合后的极性
  "score": 81,                  // 融合极性分 0-100(50 为中性)
  "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,
    "analyzedText": "客户说暂时不需要"
  },
  "audio": {
    "available": true,
    "label": "neutral",
    "score": 56,
    "raw": "calm",
    "confidence": 0.76,
    "fileName": "attempt-1-1782748800000-0.wav",
    "url": "attempt-1-1782748800000-0.wav",
    "processed": true,
    "processing": { "channel": "customer(right)", "targetSampleRate": 16000, "trimmed": true, "truncated": false }
  },
  "fused": { "label": "positive", "score": 81 },
  "risks": [],
  "model": { "text": "emotion-text/model.onnx", "audio": "emotion-audio/model.onnx" },
  "elapsedMs": 755.6
}
  • 顶层 / fusedlabelscore 是归一后的极性,供业务直接使用。
  • text / audio 子对象保留各模型的原始输出:文本是星级(raw"4 stars"stars 为期望星级 1–5), 音频是情绪类别(raw"happy"confidence 为该类置信度)。
  • text.analyzedText 是本次实际送入文本模型的文本;audio.fileName / audio.url 是本次保存的复核音频短文件名。
  • 某一模态不可用时 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 + 置信度 → 情绪映射到极性基准分并按置信度向中性收缩。

  • 仅支持 http://(无 TLS;https:// 会被拒绝并在 reason 说明)。
  • WAV 支持 PCM 8/16/24/32-bit 与 IEEE float32;压缩格式(MP3/Opus 等)不支持。
  • 静音过滤按 20ms 短帧 RMS 判断,并保留语音段前后约 200ms;如需保持旧行为,可设 audio.trimSilencefalse
  • 重采样为线性插值;静音过滤后的录音超过 30s 截断。

模型目录结构

text
onnx-platform/models/emotion-text/
  model.onnx  labels.json  vocab.txt
onnx-platform/models/emotion-audio/
  model.onnx  labels.json  preprocess.json

依赖与构建

  • ONNX Runtime 发行版置于 onnx-platform/onnxruntime/<平台>/(含 include/lib/)。CMake 变量 ONNXRUNTIME_ROOT 可覆盖默认路径。
  • 构建(Windows):pwsh build.ps1,产物安装到 target/win_x64/(含 onnxruntime.dll)。
  • 构建(Linux x64):bash emotion-analysis-server/build.sh,或使用 Ubuntu 22.04 / Debian 12 / Debian 13 Docker 镜像;镜像命令和运行库兼容性见 onnx-platform/README.md。 产物安装到 target/linux_x64/,并包含 libonnxruntime.so 的完整版本化链接链。
  • 运行时需能定位 onnx-platform/models/(按工作目录向上查找 models/),例如从对应平台的 target/win_x64/target/linux_x64/ 启动。

文档与代码在同一仓库维护,现有 Markdown 是唯一内容源。