Roro 是一个实时情感对话智能体,具备持续记忆与人设演化的能力。LLM 在回复中内联情绪标签,由 TTS 模块合成带情感的语音,并通过 WebSocket 流式推送到浏览器/ESP32 设备即时播放。系统支持全双工语音打断(barge-in)、分层记忆、人设演化、声纹识别和 MCP 物联网设备控制,可同时接入 Web 前端与硬件设备,提供连贯、有温度的多模态陪伴体验。
用户(浏览器/ESP32 设备)
↕ WebSocket (/ws/chat 或 /ws/xiaozhi)
↕ 文本 JSON / PCM / 裸 Opus 二进制帧
Orchestrator (FastAPI :8090)
├─ ASR Bridge → FunASR 2-pass Paraformer (WebSocket :10095)
├─ LLM Client → OpenAI-compatible API (SSE 流式, 动态 prompt, 工具调用)
├─ Emotion Parser → 5 类 45 种内联标签解析 + 关键词兜底引擎
├─ Sentence Splitter → 句子级流式切分 (标签保护, 三步阻断)
├─ TTS Bridge → Higgs TTS App Server :8081 (SSE 流式)
├─ Opus Codec → Opus 编解码 (opuslib / ffmpeg 双后端, 设备用)
├─ MCP Session → JSON-RPC 2.0 设备控制 (音量/亮度/拍照等)
├─ Voiceprint → CAM++ 声纹识别 (CPU, 可选)
├─ Persona System → 大五人格 + 情绪指数衰减 + 关系演进
├─ Memory System → Core + Working + Semantic (SQLite)
├─ Admin Module → 记忆管理后台 (/api/admin/* + SPA 前端)
└─ Xiaozhi Handler → 小智 ESP32 设备协议适配 (Opus 音频帧)
数据流: 用户语音/文字 → ASR 语音识别(FunASR 2-pass) → 动态 prompt 组装(人设+情绪+记忆) → LLM 流式生成(含 <|emotion:NAME|> 标签) → 句子切分 → 标签解析 → TTS 流式合成 → WebSocket 推送音频 chunk → 浏览器/设备即时播放。支持全双工语音打断(barge-in),MCP 设备控制。每轮对话后异步提取记忆(事实/情绪/关系信号)。
agent-roro/
├── orchestrator/ # 后端 Python 服务 (FastAPI :8090)
│ ├── main.py # 入口: WebSocket 编排, REST API, 三态状态机
│ ├── config.py # 6 个 dataclass 配置组, 全部 from_env()
│ ├── llm_client.py # LLM OpenAI SSE 流式 + 工具调用循环
│ ├── emotion_parser.py # 情感/风格/韵律/音效/环境标签解析 + 兜底引擎
│ ├── sentence_splitter.py # 流式句子切分 (标签感知, 未闭合保护)
│ ├── tts_bridge.py # Higgs TTS SSE 桥接 (流式/非流式)
│ ├── asr_bridge.py # FunASR 2-pass WebSocket 桥接
│ ├── opus_codec.py # Opus 编解码 (opuslib / ffmpeg 双后端)
│ ├── memory.py # 对话持久化 (JSON 文件, 原子写入)
│ ├── xiaozhi_handler.py # 小智 ESP32 设备协议处理
│ ├── mcp_transport.py # MCP JSON-RPC 2.0 传输层
│ ├── mcp_session.py # MCP 会话管理 (握手/工具发现/执行)
│ ├── persona/ # 人设系统
│ │ ├── character.py # 大五人格 + 说话风格
│ │ ├── emotion.py # 情绪状态 (valence/arousal + 指数衰减)
│ │ └── relationship.py # 亲密度/信任度/熟悉度演进
│ ├── memory_system/ # 分层记忆
│ │ ├── core.py # L5 核心记忆 (人设+情绪+关系, 始终在上下文)
│ │ ├── working.py # L4 工作记忆 (滑动窗口 20 轮)
│ │ ├── semantic.py # L2 语义记忆 (SQLite, 事实+置信度衰减)
│ │ └── extraction.py # 异步 LLM 记忆提取 pipeline
│ ├── voiceprint/ # 声纹识别
│ │ ├── extractor.py # CAM++ / Mock embedding 提取
│ │ └── service.py # 注册 + 1:N 识别
│ ├── admin/ # 管理后台 (已实现)
│ │ ├── auth.py # Token 认证
│ │ ├── routes.py # /api/admin/* CRUD
│ │ ├── services.py # 业务逻辑层
│ │ └── static/ # 管理后台前端 SPA
│ └── requirements.txt
├── frontend/ # 前端
│ ├── index.html # 单文件 UI (约 76KB, HTML+CSS+JS 全内联)
│ └── assets/ # 头像、表情包 PNG
├── asr-server/ # FunASR Docker 部署
│ └── docker-compose.yml
├── docs/ # 设计文档
│ ├── API.md # Orchestrator REST / WebSocket API
│ ├── EMOTION_TAGS_AGENT_PROMPT.md # 情绪标签参考手册
│ ├── full-duplex-architecture.md # 全双工 + 打断技术方案
│ ├── memory-system.md # 分层记忆系统设计
│ └── admin-memory-system.md # 记忆后台管理设计方案
├── scripts/ # 工具脚本
│ ├── generate_roro_emotions.py # 表情包批量生成
│ └── ws_chat_test.py # 端到端对话测试 (连 /ws/chat 校验 LLM+TTS)
├── data/ # 运行时数据 (SQLite, core_memory.json, gitignored)
├── memory/ # 对话历史 JSON (运行时, gitignored)
├── logs/ # 结构化日志 (运行时, gitignored)
├── Dockerfile # Docker 构建镜像 (python:3.10-slim)
├── docker-compose.yml # Docker Compose 编排 (含健康检查)
├── .dockerignore # Docker 构建忽略
├── .env.example # 环境变量模板 (含多 provider 示例)
├── restart.ps1 # PowerShell 后台重启脚本
├── restart.bat # Windows 重启入口 (双击)
├── start.sh # Linux/macOS 前台启动
├── start.bat # Windows 前台启动
├── test_emotion_parser.py # 情绪解析器单元测试 (15 用例, 全部通过)
├── test_sentence_splitter.py # 句子切分器单元测试 (30 用例, 全部通过)
└── test_api_roro.py # Higgs TTS API 集成测试脚本
- Python 3.10+
- Higgs TTS App Server 运行在
:8081 - FunASR 2-pass 服务运行在
:10095(语音输入时需要) - LLM API (OpenAI-compatible, 如 DashScope/DeepSeek/Ollama)
复制 .env.example 为 .env 并填入实际值:
# LLM (任何 OpenAI-compatible API)
LLM_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
LLM_API_KEY=sk-xxx
LLM_MODEL=qwen-plus
# TTS (Higgs App Server)
TTS_SERVER_URL=http://localhost:8081
TTS_API_KEY=higgs_xxx
TTS_DEFAULT_VOICE=roro-01
# ASR (FunASR 2-pass)
ASR_SERVER_URL=ws://localhost:10095
ASR_SAMPLE_RATE=16000
ASR_MODE=2pass
# Server
HOST=0.0.0.0
PORT=8090
SERVER_PUBLIC_HOST=<宿主机局域网 IP>cd asr-server
docker compose up -d# Windows (前台)
start.bat
# Windows (后台,推荐)
双击 restart.bat
# Linux/macOS
bash start.sh
# Docker (一键部署)
docker compose up -d然后打开浏览器访问 http://localhost:8090。
客户端 → 服务端:
| type | 格式 | 说明 |
|---|---|---|
| message | {"type":"message","text":"...","voice":"roro-01","session_id":"...","images":[base64...]} |
文本消息 (可含图片) |
| audio_start | {"type":"audio_start","session_id":"..."} |
开始语音输入 |
| 二进制 | PCM 16kHz 16bit 单声道 | 语音数据帧 |
| audio_end | {"type":"audio_end","session_id":"...","voice":"roro-01"} |
结束语音输入 |
| barge_in | {"type":"barge_in"} |
打断当前播放 |
| ping | {"type":"ping"} |
心跳 |
服务端 → 客户端:
| type | 格式 | 说明 |
|---|---|---|
| text_delta | {"type":"text_delta","content":"token"} |
LLM 流式 token |
| sentence | {"type":"sentence","text":"...","emotion":"joy","index":0} |
完整句子 |
| audio | {"type":"audio","data":"base64...","format":"wav","emotion":"joy","sentence_index":0,"chunk_index":0} |
音频 chunk |
| asr_partial | {"type":"asr_partial","text":"..."} |
ASR 实时部分结果 |
| asr_final | {"type":"asr_final","text":"..."} |
ASR 最终结果 |
| interrupted | {"type":"interrupted"} |
打断确认通知 |
| done | {"type":"done","elapsed_ms":3200,"sentences":2} |
回合完成 |
| error | {"type":"error","message":"..."} |
错误 |
| asr_error | {"type":"asr_error","message":"..."} |
ASR 错误 |
| pong | {"type":"pong"} |
心跳响应 |
握手: 设备发 {"type":"hello","version":1,"features":{"mcp":true}},服务端回 {"type":"hello","transport":"websocket","audio_params":{"format":"opus","sample_rate":24000,"frame_duration":60}}
音频帧: 裸 Opus 二进制(RFC 6716),24kHz/60ms mono,非 OGG 容器。
控制消息: listen/start|stop|detect、abort(打断)、goodbye(断开)、mcp(JSON-RPC 透传)。
采用 FunASR 2-pass Paraformer 流式识别方案,本地 Docker 部署。浏览器通过 ScriptProcessorNode 采集 48kHz → 16kHz 重采样后以 PCM 16bit 单声道发送。前端实现自适应 VAD (噪声底估计 + 动态阈值),~840ms 静音自动判定说话结束,支持最大录音时长保护。小智设备则使用 Opus 编码的音频帧 (24kHz/60ms),服务端解码后送 ASR。
WebSocket 支持混合文本/二进制帧。三态状态机驱动 (IDLE/SPEAKING/LISTENING):
- 非阻塞打断:
interrupt()只set event+cancel task,不 await,主消息循环永不阻塞 - ASR 结果兜底: 打断不由 VAD 决定,必须等 ASR 确认有真实文字后才执行
- 音频降音: VAD 触发 ASR 录音时,roro 音频降到 20% (GainNode),ASR 验证后才决定停或恢复
- 冷却防抖: 前端 3 帧 VAD 确认 + 1 秒打断冷却 (统一应用于文本和语音路径)
详见 docs/full-duplex-architecture.md。
LLM 通过 System Prompt 在每句话前嵌入 <|category:value|> 标签,解析后透传给 TTS:
| 类别 | 数量 | 示例 |
|---|---|---|
| emotion | 21 | affection, amusement, elation, enthusiasm, fear, sadness, surprise ... |
| style | 3 | whispering, shouting, singing |
| prosody | 10 | pause, long_pause, speed_fast, speed_slow, expressive_high ... |
| sfx | 9 | laughter, sigh, humming, crying ... |
| env | 2 | music, noise |
注:
prosody虽含pitch_high/pitch_low,但 System Prompt 已提示 LLM 避免使用——它们会改变声音的性别特征导致"变声"。
当 LLM 未输出标签时,兜底引擎根据关键词自动标注(16 组关键词映射,默认 contentment)。兜底判定基于有效标签校验(无效标签不会误触发)。
支持两种图片处理模式:
- 多模态主链路 (
LLM_MULTIMODAL=true): 主 LLM 直接接收图片 (content array 格式),要求 LLM_MODEL 是多模态模型 - 视觉旁路 (
LLM_MULTIMODAL=false,MCP_VISION_BYPASS=true): 用 VL 模型描述图片后注入纯文本主链路 - 禁用 (
vision_bypass=false): 前端图片被丢弃
前端支持按钮选择、粘贴、拖拽上传图片,base64 data URL,预览条 + 气泡渲染,单张上限 10MB。
每轮对话自动保存到 memory/{YYYYMMDD_HHMMSS}_{session_id}.json。同一 session_id 重连时恢复历史上下文,支持多轮对话。
结构化日志同时写入 logs/roro.log 和 stderr:
2026-06-14 20:36:45 | INFO | roro.ws | [sid/T1] Sentence 0: emotion=enthusiasm | tags=1 | text='嗨!'
2026-06-14 20:36:45 | INFO | roro.tts | [sid/T1] TTS sentence 0: 1 chunks, 0.6s
2026-06-14 20:36:45 | INFO | roro.llm | [sid/T1] LLM done: 8 tokens, 9.1s
2026-06-14 20:36:45 | INFO | roro.ws | [sid/T1] DONE: 2 sentences, 9.1s total, emotions=[enthusiasm]
六个 logger 分区: roro (通用) / roro.ws (WebSocket) / roro.llm (LLM) / roro.tts (TTS) / roro.asr (ASR) / roro.memory (记忆系统)。客户端断连记录为 INFO 级别。
Roro 的人格不是固定 prompt,而是有层次、会演化的系统。大五人格特质(开放性、尽责性、外向性、宜人性、神经质)定义核心性格,交互风格和幽默风格从对话中学习。情绪状态持续存在并自然指数衰减,每轮对话后更新。关系亲密度、信任度和熟悉度通过每次互动缓慢积累,讨论私人话题会加速关系发展。实现与数据边界见 docs/memory-system.md。
| 层级 | 名称 | 内容 | 存储 |
|---|---|---|---|
| L5 | Core Memory | 人设+情绪+关系 | data/core_memory.json (始终在上下文) |
| L4 | Working Memory | 最近 20 轮对话 | 内存滑动窗口 (~4000 tokens) |
| L3 | Procedural Memory | 交互习惯模式 | 预留 |
| L2 | Semantic Memory | 用户事实/偏好 | data/semantic_memory.db (SQLite, 置信度衰减) |
| L1 | Episodic Memory | 具体事件回忆 | memory/*.json (预留 Qdrant 向量检索) |
每轮对话后异步提取记忆: 事实→Semantic, 情绪→Core, 关系信号→Core。事实有置信度衰减机制(30天未确认自动衰减)。详见 docs/memory-system.md。
CAM++ 模型(CPU, ~29MB)提取声纹 embedding,支持用户注册(3句语音取平均)和 1:N 识别(对话开始时自动识别身份)。使用 numpy cosine similarity,阈值 0.75 高置信/0.65 低置信。当前默认使用 Mock 提取器用于测试,生产环境安装 modelscope+torch 后自动切换 CAM++。
通过 MCP (Model Context Protocol) JSON-RPC 2.0 协议控制物联网设备:
- 音量控制:
self.audio_speaker.set_volume - 屏幕亮度:
self.display.set_brightness - 拍照: 调用设备摄像头拍照并传回图片
- 状态查询: 设备电量、WiFi 信号等
LLM 工具调用循环自动决定何时调用设备工具。设备连接断开时自动清理所有 pending 请求。
提供 Web 管理界面 (http://localhost:8090/admin) 管理各层记忆:
- Dashboard: 实时监控活跃会话、记忆量统计、情感/关系仪表盘
- 语义事实: 分页查看、编辑、增删、确认置信度
- 核心记忆: 修改情感状态(valence/arousal)、关系参数、昵称
- 对话历史: 按 session 浏览、全文搜索、导出/删除
- 运维: 全量备份、重置、触发衰减
| 端点 | 说明 |
|---|---|
GET / |
前端页面 |
GET /health |
健康检查 (含 LLM/TTS/ASR 状态, 活跃会话数) |
GET /api/voices |
列出可用声音 |
GET /api/vision/explain |
视觉旁路图片分析 |
GET /api/admin/* |
管理后台 API (需 ADMIN_TOKEN 认证) |
WS /ws/chat |
浏览器对话 WebSocket |
WS /ws/xiaozhi |
小智 ESP32 设备 WebSocket |
# 编辑 .env 配置
cp .env.example .env
# 启动全部服务
docker compose up -d
# 仅启动 Orchestrator (不包含 ASR)
docker compose up -d orchestrator
# 查看日志
docker compose logs -f orchestratorESP32/小智设备接入:Docker 部署时容器无法自动探测宿主机 LAN IP,需在
.env显式设置SERVER_PUBLIC_HOST(值为宿主机局域网 IP)。视觉回调、OTA 等需要设备回连的场景依赖此地址;非 Docker 直跑时留空可自动探测。
# 前台启动
start.bat
# 后台重启 (推荐)
双击 restart.batbash start.sh详见 Dockerfile 和 docker-compose.yml。
# 情绪解析器 (15 用例)
python test_emotion_parser.py
# 句子切分器 (30 用例)
python test_sentence_splitter.py
# Higgs TTS API 集成测试
python test_api_roro.py --api-key xxx [--host localhost] [--port 8081]
# 端到端对话测试 (需服务已启动, 校验 LLM→情绪解析→TTS 全链路)
python scripts/ws_chat_test.py| 特性 | 状态 |
|---|---|
| ASR 语音输入 (FunASR + Paraformer 2-pass) | ✅ 已完成 |
| 全双工对话 (边听边说, 非阻塞打断) | ✅ 已完成 |
| 人设系统 (大五人格 + 情绪衰减 + 关系演进) | ✅ 已完成 |
| 分层记忆 (Core + Working + Semantic + LLM 提取) | ✅ 已完成 |
| 声纹识别 (CAM++ / Mock, 注册 + 1:N 识别) | ✅ 已完成 |
| 图片理解 (多模态 + 视觉旁路双方案) | ✅ 已完成 |
| MCP 设备控制 (音量/亮度/拍照/状态查询) | ✅ 已完成 |
| ESP32 小智设备协议适配 (Opus 帧) | ✅ 已完成 |
| 管理后台 (记忆查看/编辑/备份/重置) | ✅ 已完成 |
| Docker 容器化部署 | ✅ 已完成 |
| 机器人形象 + 口型同步 | ⏳ 待规划 |
| Episodic Memory (Qdrant 向量检索) | ⏳ 待规划 |
| Procedural Memory (交互模式学习) | ⏳ 待规划 |
当前实现会在 LLM 输出完整句子后并行启动 TTS,并按句子顺序发送音频;实际延迟取决于已配置的 LLM、TTS、ASR 服务和网络状况。接口与流式行为见 docs/full-duplex-architecture.md。
- Higgs TTS — Roro 依赖的上游 TTS 引擎(基于 Higgs Audio v3 / 4B 模型),提供带情感标签的内联语音合成(App Server + 模型)。