diff --git a/README.md b/README.md index d65d7d3..67fdbd3 100644 --- a/README.md +++ b/README.md @@ -1,405 +1,404 @@ -# Duix Mobile for Android SDK Documentation +# 安智伴 · AI 数字人陪护终端(Android) English | [中文](./README_zh.md) -## 1. Product Overview - -`Duix Mobile for Android` is a lightweight, fully offline 2D digital human solution for Android, supporting real-time rendering of digital avatars driven by voice audio. - -### 1.1 Application Scenarios - -- **Low deployment cost**: Suitable for unattended scenarios such as large-screen terminals, government halls, and banks. -- **Minimal network dependency**: Runs entirely locally, no internet required, stable operation in subways and remote areas. -- **Diverse functionality**: Can serve as a guide, Q&A customer service, intelligent companion, and more. - -### 1.2 Core Features - -- Customizable digital avatar and local rendering -- Real-time voice-driven playback (supports WAV playback and PCM streaming) -- Motion playback control (specific or random actions) -- Automatic resource download management +> 本项目与 [digital-human-monitor-v2](https://github.com/user/digital-human-monitor-v2)(家属监护 Web 端)配合使用,共同构成完整的"AI 数字人 + 家庭远程关怀"解决方案。 --- -## 2. Terminology +## 目录 -| Term | Meaning | -|--------------------|----------------------------------------------------------------------------| -| PCM | Pulse-Code Modulation, raw audio stream with 16kHz sample rate, 16-bit depth, Mono channel | -| WAV | An audio file format that supports PCM encoding, suitable for short voice playback | -| RenderSink | Rendering data reception interface, implemented by the SDK, can be used for custom rendering or default display | -| DUIX | Main control object of the digital human, integrates model loading, rendering, broadcasting, and motion control | -| GLES | OpenGL ES, a graphics interface for rendering images on Android | -| SpecialAction | A JSON file attached to the model that marks action intervals (e.g., greetings, waving) | +- [需求分析](#需求分析) +- [设计思路](#设计思路) +- [技术架构](#技术架构) +- [模块说明](#模块说明) +- [全链路数据流](#全链路数据流) +- [通信协议](#通信协议) +- [集成要求](#集成要求) +- [SDK 接口文档](#sdk-接口文档) +- [构建与运行](#构建与运行) --- -## 3. SDK Access +## 需求分析 -### 3.1 Module Reference (Recommended) +### 背景 -1. Obtain the complete source package, unzip it, and copy the `duix-sdk` directory to the project root directory. -2. In the project `settings.gradle`, add: +中国老龄化进程加速,独居或空巢老人的心理陪伴和安全监护成为刚需。子女因工作或地理距离无法陪伴,需要一种低门槛、有温度的技术方案来弥合亲情距离。 -```gradle -include ':duix-sdk' +### 核心需求 + +| 需求维度 | 描述 | +|---------|------| +| **AI 陪伴对话** | 老人可随时与屏幕上的"数字人"自然语音对话,获得情感陪伴和生活助理服务 | +| **家庭留言桥梁** | 子女通过 Web 端发送关怀留言 → 数字人主动转达;老人口述留言 → 子女在 Web 端收到 | +| **使用行为上报** | 设备自动向云端上报使用事件(开屏、对话时长等),供家属了解老人日常 | +| **离线数字人渲染** | 数字人形象的 2D 渲染完全在本地完成(NCNN 推理 + OpenGL),无需 GPU 服务器 | +| **低门槛部署** | 老人无需任何操作,设备预装即可使用;家属扫码绑定即可监护 | + +### 用户角色 + +| 角色 | 使用场景 | +|------|---------| +| **老人**(设备端) | 与数字人语音聊天、听取家人留言、口述回复 | +| **家属**(Web 端) | 发送留言、查看老人使用动态、接收推送通知 | + +--- + +## 设计思路 + +### 1. 端云分离、各司其职 + +- **端侧(本项目)**:承担数字人渲染(完全离线)+ 实时 AI 对话(通过 Qwen Omni-Realtime 云端模型)+ 家庭消息同步 +- **云侧(Monitor Web)**:承担家属身份管理、设备绑定、消息存储与分发、使用数据可视化 + +### 2. AI 对话与数字人解耦 + +DUIX SDK 仅负责"给定 PCM 音频 → 渲染对应口型/表情动画",不关心音频来源。音频可来自: +- Qwen Omni-Realtime 模型的实时语音回复 +- 本地 TTS 合成 +- 任意 WAV 文件 + +这种解耦使得 AI 模型可独立升级,不影响渲染管线。 + +### 3. 工具调用实现双向留言 + +通过 Qwen 的 Function Calling 能力,在 AI 对话中注册两个工具函数: +- `leave_message_for_family`:老人口述 → AI 提取内容 → 调用工具 → 消息存入云端 +- `fetch_family_messages`:老人想听留言 → AI 调用工具 → 拉取消息 → 口播转达 + +AI 自主判断何时调用工具,无需老人学习任何操作指令。 + +### 4. 面向老人的交互设计 + +- **语义 VAD**:采用 `semantic_vad` 而非传统声学 VAD,能过滤电视背景音、咳嗽等噪音,仅在老人真正说话时响应 +- **主动播报**:对话开始时若有未读家人留言,AI 会主动口播转达,无需老人主动查询 +- **温和人设**:系统 Prompt 设定为"面向中国老年群体的心理陪伴 AI",使用温和、简短、生活化的中文回复 + +--- + +## 技术架构 + +### 系统全景 + +``` +┌──────────────────────────────────────────────────────────────────┐ +│ Android 设备端 │ +│ │ +│ ┌─────────────┐ PCM音频 ┌──────────────────────────────┐ │ +│ │ Qwen Omni │ ────────────→ │ DUIX SDK │ │ +│ │ Realtime │ │ ┌────────────────────────┐ │ │ +│ │ (WebSocket) │ AI回复语音 │ │ NCNN 推理引擎 │ │ │ +│ │ │ │ │ (口型/表情/动作 推理) │ │ │ +│ └──────┬──────┘ │ └───────────┬────────────┘ │ │ +│ │ │ │ ImageFrame │ │ +│ 语音 │ 文本 │ ┌───────────▼────────────┐ │ │ +│ 输入 │ +语音 │ │ OpenGL ES 2.0 渲染 │ │ │ +│ │ │ │ (DUIXRenderer) │ │ │ +│ ┌──────▼──────┐ │ └───────────┬────────────┘ │ │ +│ │ 麦克风录音 │ │ │ │ │ +│ │ (16kHz PCM) │ │ ┌───────────▼────────────┐ │ │ +│ └─────────────┘ │ │ GLTextureView (UI) │ │ │ +│ └──────────────────────────────┘ │ +│ │ +│ ┌──────────────────────────────────────────────────────────┐ │ +│ │ FamilyBridgeService (HTTP) │ │ +│ │ • 设备注册 → POST /api/device/register │ │ +│ │ • 拉取留言 → GET /api/device/messages │ │ +│ │ • 标记已读 → POST /api/device/messages/read │ │ +│ │ • 上报事件 → POST /api/device/usage │ │ +│ │ • 对话存档 → POST /api/device/conversations │ │ +│ │ • 工具记录 → POST /api/device/tool-call │ │ +│ └──────────────────────────────┬───────────────────────────┘ │ +└─────────────────────────────────┼────────────────────────────────┘ + │ HTTPS + ▼ +┌──────────────────────────────────────────────────────────────────┐ +│ digital-human-monitor-v2 (Next.js 云端) │ +│ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │ +│ │ 设备管理 │ │ 消息存储 │ │ 行为分析 │ │ Web Push 推送 │ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────┐ │ +│ │ PostgreSQL │ │ +│ └──────────────┘ │ +└──────────────────────────────────────────────────────────────────┘ ``` -3. In the module's `build.gradle`, add the dependency: +### 技术栈 -```gradle -dependencies { - api project(":duix-sdk") -} +| 层级 | 技术 | +|------|------| +| **数字人渲染** | NCNN (端侧推理) + OpenCV 4.6 + OpenGL ES 2.0 + ONNX Runtime | +| **AI 对话** | 千问 Qwen 3.5 Omni Plus Realtime (WebSocket) | +| **音频处理** | AudioRecord (16kHz) → PCM → Base64 → WebSocket;AudioTrack 播放 | +| **网络通信** | OkHttp 4 (HTTP API) + OkHttp WebSocket (Qwen Realtime) | +| **本地存储** | SharedPreferences (设备 UUID、配置) | +| **构建系统** | Gradle 8.13.2 + AGP 8.9.1 + JDK 17 (Temurin) | +| **语言** | Kotlin (应用层) + Java (SDK 层) + C++ (Native 推理层) | + +--- + +## 模块说明 + +### 项目结构 + +``` +dh_aigc_android/ +├── duix-sdk/ # 核心 2D 数字人渲染 SDK(库模块) +│ ├── src/main/java/ # Java SDK 接口层 +│ │ └── ai/guiji/duix/ +│ │ ├── sdk/client/ # DUIX 主控、渲染、回调、工具类 +│ │ └── duix/DuixNcnn.java # JNI 接口 +│ └── src/main/cpp/ # C++ Native 推理引擎 +│ ├── dhcore/ # 核心数字人推理 +│ ├── dhmfcc/ # 音频 MFCC 特征提取 +│ ├── dhunet/ # 口型网络模型 +│ └── third/ # 预编译依赖 (NCNN, OpenCV, ONNX, libjpeg) +│ +├── test/ # 演示应用(应用模块) +│ └── src/main/java/ai/guiji/duix/test/ +│ ├── ui/activity/ +│ │ ├── MainActivity.kt # 主页:模型选择与下载管理 +│ │ ├── CallActivity.kt # 核心:数字人渲染 + AI 对话 + 留言桥梁 +│ │ ├── SettingsActivity.kt # 设备设置 +│ │ └── VoiceCloneSetupActivity.kt # 声音克隆配置 +│ ├── service/ +│ │ ├── FamilyBridgeService.kt # 家庭监护 API 客户端 +│ │ ├── OmniRealtimeClient.kt # Qwen Omni WebSocket 客户端 +│ │ ├── QwenTtsRealtimeClient.kt # Qwen TTS 客户端 +│ │ ├── VoiceCloneService.kt # 声音克隆服务 +│ │ └── DashScopeConfig.java # API 配置常量 +│ ├── audio/ +│ │ ├── AudioResampler.java # PCM 重采样 +│ │ └── StreamingAudioRecorder.java # 实时麦克风录音 +│ └── camera/ +│ └── CameraFrameCapture.java # 摄像头帧采集 +│ +└── qwen-docs/ # 千问 Omni-Realtime API 参考文档 + ├── main.md # 协议总览 + ├── client_events.md # 客户端事件定义 + ├── server_events.md # 服务端事件定义 + └── function_calling.md # 工具调用机制 ``` -### 3.2 AAR Reference (Optional) +### duix-sdk — 数字人渲染引擎 -1. Place the compiled `duix-sdk-release.aar` module into the `libs/` directory. -2. Add the dependency: +| 类 | 职责 | +|----|------| +| `DUIX` | 主控对象:管理初始化、渲染循环、音频驱动、生命周期 | +| `RenderThread` | 独立渲染线程:逐帧推理 + 音频同步 | +| `DUIXRenderer` | OpenGL ES 2.0 渲染器:将 ImageFrame 渲染到 TextureView | +| `DUIXTextureView` | 自定义 TextureView:提供 GL 渲染表面 | +| `VirtualModelUtil` | 模型资源管理:下载、校验、解压 | +| `DuixNcnn` | JNI 桥接:调用 C++ 层 NCNN 推理 | -```gradle -dependencies { - api fileTree(include: ['*.jar', '*.aar'], dir: 'libs') -} +**渲染流水线**: +``` +pushPcm(音频) → MFCC特征提取 → NCNN口型网络推理 +→ 获取ImageFrame(图像+掩码) → OpenGL混合渲染 → TextureView显示 +``` + +### test — 应用集成层 + +| 类 | 职责 | +|----|------| +| `CallActivity` | 整合 DUIX 渲染 + Qwen 对话 + 留言桥梁的核心页面 | +| `OmniRealtimeClient` | Qwen Omni-Realtime WebSocket 客户端,处理实时语音对话 | +| `FamilyBridgeService` | 家庭监护云 API 客户端,负责设备注册、消息同步、事件上报 | +| `MainActivity` | 模型选择与下载管理入口 | + +--- + +## 全链路数据流 + +### 1. AI 实时对话 + +``` +老人说话 → 麦克风录音 (16kHz PCM) +→ Base64 编码 → input_audio_buffer.append (WebSocket) +→ Qwen Omni 模型推理 +→ response.audio.delta (Base64 PCM 回传) +→ 解码为 PCM → DUIX.pushPcm() +→ NCNN 推理口型动画 → OpenGL 渲染 +→ 同时 AudioTrack 播放语音 +``` + +### 2. 留言传递(老人 → 家属) + +``` +老人对 AI 说 "帮我给儿子带个话,说我挺好的" +→ Qwen 识别意图 → 触发 leave_message_for_family 工具 +→ 提取 recipientRelation="儿子", message="我挺好的" +→ FamilyBridgeService → POST /api/device/conversations +→ Monitor Web 存入数据库 +→ Web Push 通知家属 +→ AI 回复老人 "好的,已经帮您告诉儿子了" +``` + +### 3. 留言传递(家属 → 老人) + +``` +家属在 Web 端发送留言 → 存入 FamilyMessage 表 +→ 老人设备启动 AI 对话 +→ FamilyBridgeService 拉取未读消息 +→ 注入 AI 系统 Prompt: "家里人刚留了新留言,请主动转告..." +→ AI 在第一轮回复中口播: "您儿子给您捎了个话——记得吃药" +``` + +### 4. 使用行为上报 + +``` +设备事件 (APP_OPEN / AI_SESSION_STARTED / TOOL_CALLED / ...) +→ FamilyBridgeService → POST /api/device/usage +→ Monitor Web 存入 UsageEvent 表 +→ 家属在"陪伴动态"页面查看趋势图 ``` --- -## 4. Integration Requirements +## 通信协议 -| Item | Description | -|----------------|-----------------------------------------------------------------| -| System | Supports Android 10+ systems. | -| CPU Architecture | armeabi-v7a, arm64-v8a | -| Hardware Requirements | Device CPU with 8 or more cores (Snapdragon 8 Gen 2), 8GB or more memory, available storage space of 1GB or more | -| Network | None (Fully local operation) | -| Development IDE | Android Studio Giraffe 2022.3.1 Patch 2 | -| Memory Requirements | Minimum 800MB memory available for the digital human | +### Qwen Omni-Realtime WebSocket + +| 方向 | 事件 | 用途 | +|------|------|------| +| C→S | `session.update` | 设置模态(text+audio)、语音、VAD、系统 Prompt、工具列表 | +| C→S | `input_audio_buffer.append` | 推送用户麦克风 PCM (Base64) | +| S→C | `response.audio.delta` | 模型语音回复增量块 (Base64 PCM) | +| S→C | `response.text.done` | 模型文本回复完成 | +| S→C | `response.function_call_arguments.done` | 工具调用请求 | +| C→S | `conversation.item.create` | 回传工具执行结果 | +| C→S | `response.create` | 触发模型基于工具结果生成最终回复 | + +### 注册的 AI 工具 + +| 工具名 | 参数 | 用途 | +|--------|------|------| +| `leave_message_for_family` | `recipientRelation` (称谓), `message` (内容), `importance` (优先级) | 老人留言给家人 | +| `fetch_family_messages` | `count` (数量 1-5) | 拉取家人留言 | + +### FamilyBridge HTTP API + +| 端点 | 方法 | 用途 | +|------|------|------| +| `/api/device/register` | POST | 设备注册,返回 `deviceUuid` + `bindUrl` | +| `/api/device/messages` | GET | 拉取待送达消息 | +| `/api/device/messages/read` | POST | 标记消息已读 | +| `/api/device/usage` | POST | 上报使用事件 | +| `/api/device/conversations` | POST | 上传对话轮次 | +| `/api/device/tool-call` | POST | 记录工具调用日志 | --- -## 5. Usage Flow Overview +## 集成要求 -```mermaid -graph TD -A[Check Configuration and Models] --> B[Build DUIX Instance] -B --> C[Call init to Initialize] -C --> D[Display Avatar / Render] -D --> E[PCM or WAV Audio Driving] -E --> F[Playback Control & Motion Triggering] -F --> G[Resource Release] -``` +| 项目 | 要求 | +|------|------| +| 系统版本 | Android 10+ (API 24+) | +| CPU 架构 | arm64-v8a, armeabi-v7a | +| 硬件 | 8 核 CPU (骁龙 8 Gen2), 8GB+ 内存, 1GB+ 存储 | +| 内存 | 数字人运行需 ≥ 800MB 可用内存 | +| 网络 | 数字人渲染:无需网络;AI 对话 + 监护同步:需联网 | +| 开发 IDE | Android Studio Giraffe 2022.3.1+ | +| JDK | OpenJDK 17 (Temurin) | +| Gradle | 8.13.2 (使用项目自带 wrapper) | --- -## 6. Key Interfaces and Example Calls +## SDK 接口文档 -### 6.1 Model Check and Download +### 术语 -Before using the rendering service, ensure that the basic configuration and model files are synchronized to local storage. The SDK provides a simple demonstration of the model download and decompression process using `VirtualModelUtil`. If model download is slow or fails, developers can choose to cache the model package to their own storage service. +| 术语 | 含义 | +|------|------| +| PCM | 16kHz 采样率、16bit 位深、Mono 单通道的原始音频流 | +| WAV | 支持 PCM 编码的音频文件格式 | +| RenderSink | 渲染数据接收接口,SDK 提供默认实现 | +| DUIX | 数字人主控对象 | +| ImageFrame | 包含 rawBuffer (图像) + maskBuffer (掩码) + 宽高的渲染帧 | -> Function Definition: `ai.guiji.duix.sdk.client.VirtualModelUtil` - -``` -// Check if base configuration is downloaded -boolean checkBaseConfig(Context context) - -// Check if the model is downloaded -boolean checkModel(Context context, String name) - -// Base configuration download -void baseConfigDownload(Context context, String url, ModelDownloadCallback callback) - -// Model download -void modelDownload(Context context, String modelUrl, ModelDownloadCallback callback) -``` - -`ModelDownloadCallback` includes progress, completion, failure callbacks, etc., as defined in the SDK. - -``` -interface ModelDownloadCallback { - // Download progress - void onDownloadProgress(String url, long current, long total); - // Unzip progress - void onUnzipProgress(String url, long current, long total); - // Download and unzip complete - void onDownloadComplete(String url, File dir); - // Download and unzip failed - void onDownloadFail(String url, int code, String msg); -} -``` - -**Call Example**: +### 模型检查与下载 ```kotlin -if (!VirtualModelUtil.checkBaseConfig(mContext)){ - VirtualModelUtil.baseConfigDownload(mContext, baseConfigUrl, callback) -} +// 检查基础配置 +VirtualModelUtil.checkBaseConfig(context): Boolean +// 下载基础配置 +VirtualModelUtil.baseConfigDownload(context, url, callback) + +// 检查模型 +VirtualModelUtil.checkModel(context, modelUrl): Boolean +// 下载模型 +VirtualModelUtil.modelDownload(context, modelUrl, callback) ``` +### 初始化与渲染 + ```kotlin -if (!VirtualModelUtil.checkModel(mContext, modelUrl)){ - VirtualModelUtil.modelDownload(mContext, modelUrl, callback) -} -``` - ---- - -### 6.2 Initialization and Rendering Start - -In the `onCreate()` stage of the rendering page, build the DUIX object and call the init interface. - -> Function Definition: `ai.guiji.duix.sdk.client.DUIX` - -``` -// Build DUIX object -public DUIX(Context context, String modelName, RenderSink sink, Callback callback) - -// Initialize DUIX service -void init() -``` - -**DUIX Object Construction Explanation**: - -| Parameter | Type | Description | -|---------------|-----------|----------------------------------------------------------------| -| context | Context | System context | -| modelName | String | Can pass the model download URL (if downloaded) or cached filename | -| render | RenderSink| Rendering data interface, SDK provides a default rendering component inheriting from this interface, or you can implement it yourself | -| callback | Callback | Various callback events handled by the SDK | - -Where **Callback** is defined as: `ai.guiji.duix.sdk.client.Callback` - -``` -interface Callback { - void onEvent(String event, String msg, Object info); -} -``` - -**Call Example**: - -```kotlin -duix = DUIX(mContext, modelUrl, mDUIXRender) { event, msg, info -> +// 构建 DUIX 实例 +val duix = DUIX(context, modelUrl, renderSink) { event, msg, info -> when (event) { - ai.guiji.duix.sdk.client.Constant.CALLBACK_EVENT_INIT_READY -> { - initOK() - } - - ai.guiji.duix.sdk.client.Constant.CALLBACK_EVENT_INIT_ERROR -> { - initError() - } - // ... + Constant.CALLBACK_EVENT_INIT_READY -> { /* 初始化完成 */ } + Constant.CALLBACK_EVENT_INIT_ERROR -> { /* 初始化失败 */ } + Constant.CALLBACK_EVENT_PLAY_START -> { /* 播放开始 */ } + Constant.CALLBACK_EVENT_PLAY_END -> { /* 播放结束 */ } } } -// Asynchronous callback result -duix?.init() +duix.init() ``` -In the `init` callback, confirm the initialization result. +### 音频驱动 + +```kotlin +// PCM 流式推送(用于实时 AI 语音) +duix.pushPcm(pcmBytes) + +// WAV 文件播放 +duix.playWav(wavFilePath) +``` + +### 资源释放 + +```kotlin +// 在 Activity onDestroy 中调用 +duix.release() +``` --- -### 6.3 Digital Human Avatar Display +## 构建与运行 -Use the SDK-provided `DUIXRenderer` and `DUIXTextureView` to quickly implement rendering with transparency support. Alternatively, you can implement the `RenderSink` interface to customize the rendering logic. +### 环境准备 -The **RenderSink** definition is as follows: `ai.guiji.duix.sdk.client.render.RenderSink` +1. 安装 Android Studio Giraffe 2022.3.1+ +2. 配置 JDK 17:`File → Settings → Build → Gradle → Gradle JDK → 选择 JDK 17` +3. 连接 Android 设备(需满足硬件要求) + +### 编译安装 + +```bash +# 使用 Gradle Wrapper 编译安装 test 应用 +./gradlew :test:installDebug +``` + +### 配置说明 + +API 密钥和服务地址在 `DashScopeConfig.java` 中配置: ```java -/** - * Rendering pipeline, returns rendering data through this interface - */ -public interface RenderSink { - - // The frame's buffer data is arranged in BGR order - void onVideoFrame(ImageFrame imageFrame); - -} -``` - -**Call Example**: - -Use `DUIXRenderer` and `DUIXTextureView` to quickly implement rendering. These controls support transparency and can freely set the background and foreground. - -```kotlin -override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - // ... - mDUIXRender = - DUIXRenderer( - mContext, - binding.glTextureView - ) - - binding.glTextureView.setEGLContextClientVersion(GL_CONTEXT_VERSION) - binding.glTextureView.setEGLConfigChooser(8, 8, 8, 8, 16, 0) // Transparency - binding.glTextureView.isOpaque = false // Transparency - binding.glTextureView.setRenderer(mDUIXRender) - binding.glTextureView.renderMode = - GLSurfaceView.RENDERMODE_WHEN_DIRTY // Must be called after setting the renderer - - duix = DUIX(mContext, modelUrl, mDUIXRender) { event, msg, _ -> - } - // ... -} +MONITOR_BASE_URL // 监护 Web 服务地址 +REALTIME_BASE_URL // Qwen Omni WebSocket 地址 +DEFAULT_API_KEY // DashScope API Key +AUDIO_SAMPLE_RATE // 音频采样率 (24000) ``` --- -### 6.4 Broadcasting Control +## 关联项目 -#### Use Streaming PCM to Drive Digital Human Broadcasting - -**PCM Format: 16kHz sample rate, single channel, 16-bit depth** - -> Function Definition: `ai.guiji.duix.sdk.client.DUIX` - -``` -// Notify service to start pushing audio -void startPush() - -// Push PCM data -void pushPcm(byte[] buffer) - -// Finish a segment of audio push (Call this after the audio push is complete, not after playback finishes) -void stopPush() -``` - -`startPush`, `pushPcm`, and `stopPush` need to be called in pairs. `pushPcm` should not be too long. After pushing the entire audio, call `stopPush` to end the session. Use `startPush` again for the next audio. - -**The audio data between each startPush and stopPush segment should be at least 1 second (32000 bytes), otherwise the mouth shape driver cannot be triggered, and blank frames can be used to fill in.** - -**Call Example**: - -```kotlin -val thread = Thread { - duix?.startPush() - val inputStream = assets.open("pcm/2.pcm") - val buffer = ByteArray(320) - var length = 0 - while (inputStream.read(buffer).also { length = it } > 0){ - val data = buffer.copyOfRange(0, length) - duix?.pushPcm(data) - } - duix?.stopPush() - inputStream.close() -} -thread.start() -``` - ---- - -### 6.5 Motion Control - -#### Play Specific Motion Interval - -The model supports new motion intervals marked in `SpecialAction.json` - -> Function Definition: `ai.guiji.duix.sdk.client.DUIX` - -``` -/** - * Play specific motion interval - * @param name The motion interval name, which can be obtained from @{ModelInfo.getSilenceRegion()} after init callback - * @param now Whether to play immediately: true: play now; false: wait for current silent or motion interval to finish - */ -void startMotion(String name, boolean now) -``` - -**Call Example**: - -```kotlin -duix?.startMotion("Greeting", true) -``` - -#### Randomly Play Motion Interval - -> Function Definition: `ai.guiji.duix.sdk.client.DUIX` - -``` -/** - * Randomly play a motion interval - * @param now Whether to play immediately: true: play now; false: wait for current silent or motion interval to finish - */ -void startRandomMotion(boolean now); -``` - -**Call Example**: - -```kotlin -duix?.startRandomMotion(true) -``` - ---- - -## 7. Proguard Configuration - -If using obfuscation, add the following in `proguard-rules.pro`: - -```proguard --keep class ai.guiji.duix.DuixNcnn{*; } -``` - ---- - -## 8. Precautions - -1. Ensure that the base configuration file and model are downloaded to the specified location before driving rendering initialization. -2. PCM audio should not be too long, as PCM buffers are cached in memory; long audio streams may cause memory overflow. -3. To replace the preview model, modify the `modelUrl` value in `MainActivity.kt` and use the SDK's built-in file download and decompression management to obtain the complete model files. -4. Audio driving format: 16kHz sample rate, single channel, 16-bit depth. -5. Insufficient device performance may result in the audio feature extraction speed not matching the playback speed. You can use `duix?.setReporter()` to monitor frame rendering information. - ---- - -## 9. FAQ and Troubleshooting Guide - -| Issue | Possible Cause | Solution | -|---------------------------------|------------------------------|------------------------------| -| init callback failed | Model path error or model not downloaded | Use `checkModel` to check model status | -| Rendering black screen | EGL configuration or texture view error | Use SDK-provided example settings | -| No PCM playback effect | Incorrect format or `startPush` not called | Ensure audio format is correct and call push method | -| Model download slow | Unstable network or restricted CDN | Support self-hosted model file storage service | - ---- - -## 10. Version History - -**4.0.1** - -1. Supports PCM audio stream driving the digital human, improving audio playback response speed. -2. Optimized motion interval playback, allowing specific motion intervals based on model configuration. -3. Custom audio player, removed Exoplayer playback dependency. -4. Provided simplified model download synchronization management tools. -5. The audio data between each startPush and stopPush segment should be at least 1 second (32000 bytes), otherwise the mouth shape driver cannot be triggered, and blank frames can be used to fill in. - -**3.0.5** - -```text -1. Updated arm32 CPU libonnxruntime.so version to fix compatibility issues. -2. Modified motion interval playback function, supports random and sequential playback, requires manual call to stop playback to return to silent interval. -``` - -**3.0.4** - -```text -1. Fixed model display issue due to low float precision on some devices. -``` - -**3.0.3** - -```text -1. Optimized local rendering. -``` - -## 11. 🔗 Open-source Dependencies - -| Module | Description | -|------------------------------------------|--------------------------------| -| [onnx](https://github.com/onnx/onnx) | General AI model standard format | -| [ncnn](https://github.com/Tencent/ncnn) | High-performance neural network computing framework (Tencent) | - ---- - -For more help, please contact the technical support team. \ No newline at end of file +| 项目 | 角色 | 仓库 | +|------|------|------| +| **dh_aigc_android**(本项目) | 老人端:AI 数字人陪护 + 设备行为上报 | 当前仓库 | +| **digital-human-monitor-v2** | 家属端:远程监护 Web 面板 + 消息管理 + 推送通知 | [digital-human-monitor-v2](../digital-human-monitor-v2/) | diff --git a/README_zh.md b/README_zh.md deleted file mode 100644 index d16e9b6..0000000 --- a/README_zh.md +++ /dev/null @@ -1,485 +0,0 @@ -# Duix Mobile for Android SDK 文档 - -中文 | [English](./README.md) - -## 一、产品介绍 - -`Duix Mobile for Android` 是一套轻量级、纯离线的 Android 平台 2D 虚拟人解决方案,支持通过语音音频驱动数字人形象并进行实时渲染。 - -### 1.1 应用场景 - -- **部署成本低**:适用于大屏终端、政务展厅、银行等无人值守场景。 -- **网络依赖小**:完全本地运行,无需联网,可在地铁、偏远地区稳定运行。 -- **功能多样化**:可服务于导览讲解、问答客服、智能陪伴等多种业务形态。 - -### 1.2 核心功能 - -- 数字人形象定制与本地渲染 -- 实时语音驱动播报(支持 WAV 播放和 PCM 推送) -- 动作播放控制(指定动作、随机动作) -- 资源自动下载管理 - ---- - -## 二、术语说明 - -| 术语 | 含义 | -|-------------------|------------------------------------------------------------------------| -| PCM | Pulse-Code Modulation,16kHz 采样率、16bit 位深、Mono 单通道的原始音频流 | -| WAV | 一种音频文件格式,支持 PCM 编码,适合短语音播放 | -| RenderSink | 渲染数据接收接口,由 SDK 提供实现,可用于自定义渲染或默认展示 | -| DUIX | 数字人主控对象,集成了模型加载、渲染、播报、动作等能力 | -| GLES | OpenGL ES,Android 渲染图像用到的图形接口 | -| SpecialAction | 模型附带的 JSON 文件,标注动作区间(例如打招呼、挥手等) | - ---- - -## 三、SDK 获取方式 - -### 3.1 Module 引用(推荐) - -1. 获取完整源码包,解压后将 `duix-sdk` 目录复制到项目根目录下。 -2. 在项目 `settings.gradle` 中添加: - -```gradle -include ':duix-sdk' -``` - -3. 在模块 `build.gradle` 中添加依赖: - -```gradle -dependencies { - api project(":duix-sdk") -} -``` - -### 3.2 AAR 引用(可选) - -1. 将duix-sdk模块编译的 `duix-sdk-release.aar` 放入 `libs/` 目录。 -2. 添加依赖: - -```gradle -dependencies { - api fileTree(include: ['*.jar', '*.aar'], dir: 'libs') -} -``` - ---- - -## 四、集成要求 - -| 项目 | 描述 | -|--------|----------------------------------------------------| -| 系统 | 支持 Android 10+ 系统。 | -| CPU架构 | armeabi-v7a, arm64-v8a | -| 硬件要求 | 要求设备 CPU8 核及以上(骁龙8 Gen2),内存 8G 及以上。可用存储空间 1GB 及以上。 | -| 网络 | 无(完全本地运行) | -| 开发 IDE | Android Studio Giraffe 2022.3.1 Patch 2 | -| 内存要求 | 可用于数字人的内存 >= 800MB | - - -**编译项目的Gradle使用的JDK版本为17,需要在File->Setting->Build,Execution,Deployment->Grade Projects->Gradle JDK: ${选择一个17版本的JDK}** - ---- - -## 五、使用流程概览 - -```mermaid -graph TD -A[检查配置与模型] --> B[构建 DUIX 实例] -B --> C[调用 init 初始化] -C --> D[展示形象 / 渲染] -D --> E[PCM 或 WAV 音频驱动] -E --> F[播放控制与动作触发] -F --> G[资源释放] -``` - ---- - -## 六、关键接口与调用示例 - -### 6.1. 模型检查及下载 - -使用渲染服务前需要将基础配置及模型文件同步到本地存储中,SDK中提供了VirtualModelUtil简单演示了模型下载解压流程。 -若模型下载过慢或无法下载,开发者可以选择将模型包缓存到自己的存储服务。 - -> 函数定义: `ai.guiji.duix.sdk.client.VirtualModelUtil` - -``` -// 检查基础配置是否已下载 -boolean checkBaseConfig(Context context) - -// 检查模型是否已下载 -boolean checkModel(Context context, String name) - -// 基础配置下载 -void baseConfigDownload(Context context, String url, ModelDownloadCallback callback) - -// 模型下载 -void modelDownload(Context context, String modelUrl, ModelDownloadCallback callback) -``` - -`ModelDownloadCallback` 包含进度、完成、失败等回调,详见 SDK 定义。 - -``` -interface ModelDownloadCallback { - // 下载进度 - void onDownloadProgress(String url, long current, long total); - // 解压进度 - void onUnzipProgress(String url, long current, long total); - // 下载解压完成 - void onDownloadComplete(String url, File dir); - // 下载解压失败 - void onDownloadFail(String url, int code, String msg); -} -``` - -**调用示例**: - -```kotlin -if (!VirtualModelUtil.checkBaseConfig(mContext)){ - VirtualModelUtil.baseConfigDownload(mContext, baseConfigUrl, callback) -} -``` - -```kotlin -if (!VirtualModelUtil.checkModel(mContext, modelUrl)){ - VirtualModelUtil.modelDownload(mContext, modelUrl, callback) -} - -``` - ---- - -### 6.2. 初始化与渲染启动 - -在渲染页onCreate()阶段构建DUIX对象并调用init接口 - -> 函数定义: `ai.guiji.duix.sdk.client.DUIX` - -``` -// 构建DUIX对象 -public DUIX(Context context, String modelName, RenderSink sink, Callback callback) - -// 初始化DUIX服务 -void init() -``` - -**DUIX对象构建说明**: - -| 参数 | 类型 | 描述 | -|------------|------------|-------------------------------------| -| context | Context | 系统上下文 | -| modelName | String | 可以传递模型下载的URL(已下载完成)或缓存的文件名 | -| render | RenderSink | 渲染数据接口,sdk提供了默认的渲染组件继承自该接口,也可以自己实现 | -| callback | Callback | SDK处理的各种回调事件 | - - -其中**Callback**的定义: `ai.guiji.duix.sdk.client.Callback` - -``` -interface Callback { - void onEvent(String event, String msg, Object info); -} -``` - -**调用示例**: - -```kotlin -duix = DUIX(mContext, modelUrl, mDUIXRender) { event, msg, info -> - when (event) { - ai.guiji.duix.sdk.client.Constant.CALLBACK_EVENT_INIT_READY -> { - initOK() - } - - ai.guiji.duix.sdk.client.Constant.CALLBACK_EVENT_INIT_ERROR -> { - initError() - } - // ... - - } -} -// 异步回调结果 -duix?.init() -``` - -在init回调中确认初始化结果 - ---- - -### 6.3. 数字人形象展示 - -使用 SDK 提供的 `DUIXRenderer` 和 `DUIXTextureView` 可快速实现支持透明通道的渲染。也可以自己实现RenderSink接口自定义渲染逻辑。 - -其中**RenderSink**的定义如下: `ai.guiji.duix.sdk.client.render.RenderSink` - -```java -/** - * 渲染管道,通过该接口返回渲染数据 - */ -public interface RenderSink { - - // frame中的buffer数据以bgr顺序排列 - void onVideoFrame(ImageFrame imageFrame); - -} -``` - -**调用示例**: - -使用DUIXRenderer及DUIXTextureView控件简单实现渲染展示,该控件支持透明通道可以自由设置背景及前景 - -```kotlin -override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - // ... - mDUIXRender = - DUIXRenderer( - mContext, - binding.glTextureView - ) - - binding.glTextureView.setEGLContextClientVersion(GL_CONTEXT_VERSION) - binding.glTextureView.setEGLConfigChooser(8, 8, 8, 8, 16, 0) // 透明 - binding.glTextureView.isOpaque = false // 透明 - binding.glTextureView.setRenderer(mDUIXRender) - binding.glTextureView.renderMode = - GLSurfaceView.RENDERMODE_WHEN_DIRTY // 一定要在设置完Render之后再调用 - - duix = DUIX(mContext, modelUrl, mDUIXRender) { event, msg, _ -> - } - // ... -} -``` - ---- - -### 6.4 播报控制 - -#### 使用流式推送PCM驱动数字人播报 - -**PCM格式:16k采样率单通道16位深** - -> 函数定义: `ai.guiji.duix.sdk.client.DUIX` - -``` -// 通知服务开始推送音频 -void startPush() - -// 推送PCM数据 -void pushPcm(byte[] buffer) - -// 完成一段音频推送(音频推送完就调要该函数,而不是等播放完成再调用。) -void stopPush() - -``` - -startPush、pushPcm、stopPush需要成对调用,pushPcm不宜过长。可以在一整段音频推送完后调用stopPush结束当前会话,下一段音频再使用startPush重新开启推送。 - -**每段startPush到stopPush中间的音频数据最少要1秒(32000字节)否则无法触发口型驱动,可以自行使用空白帧填充。** - -**调用示例**: - -```kotlin -val thread = Thread { - duix?.startPush() - val inputStream = assets.open("pcm/2.pcm") - val buffer = ByteArray(320) - var length = 0 - while (inputStream.read(buffer).also { length = it } > 0){ - val data = buffer.copyOfRange(0, length) - duix?.pushPcm(data) - } - duix?.stopPush() - inputStream.close() -} -thread.start() -``` - ---- - -#### WAV 播放驱动 - -> 函数定义: `ai.guiji.duix.sdk.client.DUIX` - -``` -void playAudio(String wavPath) -``` - -**该函数兼容旧的wav驱动数字人接口,在内部实际是调用了PCM推流方式实现驱动。** - - -**参数说明**: - -| 参数 | 类型 | 描述 | -|---------|--------|-----------------------| -| wavPath | String | 16k采样率单通道16位深的wav本地文件 | - - -**调用示例**: - -```kotlin -duix?.playAudio(wavPath) -``` - -音频播放状态及进度回调: - -```kotlin -object : Callback { - fun onEvent(event: String, msg: String, info: Object) { - when (event) { - // ... - - "play.start" -> { - // 开始播放音频 - } - - "play.end" -> { - // 完成播放音频 - } - "play.error" -> { - // 音频播放异常 - } - } - } -} -``` - ---- - -#### 终止当前播报 - -当数字人正在播报时调用该接口终止播报。 - -> 函数定义: `ai.guiji.duix.sdk.client.DUIX` - -``` -boolean stopAudio(); -``` - -**调用示例如下**: - -```kotlin -duix?.stopAudio() -``` - ---- - -### 6.5. 动作控制 - - -#### 播放指定动作区间 - -模型中支持新的动作区间标注(SpecialAction.json) - -> 函数定义: `ai.guiji.duix.sdk.client.DUIX` - -``` -/** - * 播放指定动作区间 - * @param name 动作区间名称,在init成功回调时,可以在@{ModelInfo.getSilenceRegion()}中获取到可用的动作区间 - * @param now 是否立即播放 true: 立即播放; false: 等待当前静默区间或动作区间播放完毕后播放 - */ -void startMotion(String name, boolean now) -``` - -**调用示例如下**: - -```kotlin -duix?.startMotion("打招呼", true) -``` - -#### 随机播放动作区间 - -随机播放场景及旧的标注协议(config.json) - -> 函数定义: `ai.guiji.duix.sdk.client.DUIX` - -``` -/** - * 随机播放一个动作区间 - * @param now 是否立即播放 true: 立即播放; false: 等待当前静默区间或动作区间播放完毕后播放 - */ -void startRandomMotion(boolean now); -``` - -**调用示例如下**: - -```kotlin -duix?.startRandomMotion(true) -``` - ---- - -## 七. Proguard配置 - -如果代码使用了混淆,请在proguard-rules.pro中配置: - -```proguard --keep class ai.guiji.duix.DuixNcnn{*; } -``` - ---- - -## 八、注意事项 - -1. 驱动渲染初始化前需要确保基础配置文件及模型下载到指定位置。 -2. 播放的PCM音频不宜过长,播放的PCM缓存在内存中,过长的音频流可能导致内存溢出。 -3. 替换预览模型可以在MainActivity.kt文件中修改modelUrl的值,使用SDK中自带的文件下载解压管理以获得完整的模型文件。 -4. 音频驱动的格式: 16k采样率单通道16位深度 -5. 设备性能不足时可能导致音频特征提取的速度跟不上音频播放的速度,可以使用duix?.setReporter()函数添加一个监控观察帧渲染返回的信息。 -6. 每段startPush到stopPush中间的音频数据最少要1秒(32000字节)否则无法触发口型驱动,可以自行使用空白帧填充。 - ---- - -## 九、常见问题与排查指南 - -| 问题现象 | 可能原因 | 解决方案 | -|---------------------|--------------------------|------------------------| -| init 回调失败 | 模型路径错误或未下载完成 | 使用 `checkModel` 检查模型状态 | -| 渲染黑屏 | EGL 配置或纹理视图设置错误 | 使用 SDK 提供示例中的设置方法 | -| PCM 无播报效果 | 格式不符或未调用 startPush | 确保音频格式正确并调用推送方法 | -| 模型下载过慢 | 网络不稳定或 CDN 受限 | 支持自建模型文件托管服务 | - ---- - -## 十、版本记录 - -**4.0.1** - -```text -1. 支持PCM音频流驱动数字人,提升音频播放响应速度。 -2. 优化动作区间播放,可根据模型配置指定播放动作区间。 -3. 自定义音频播放器,去除Exoplayer播放依赖 -4. 提供简洁的模型下载同步管理工具 -``` - -**3.0.5** - -```text -1. 更新arm32位cpu的libonnxruntime.so版本以修复兼容问题。 -2. 修改动作区间播放函数,可以使用随机播放和顺序播放,需要主动调用停止播放动作区间以回到静默区间。 -``` - -**3.0.4** - -```text -1. 修复部分设备gl默认float低精度导致无法正常显示形象问题。 -``` - -**3.0.3** - -```text -1. 优化本地渲染。 -``` - -## 十一、🔗 开源依赖 - -| 模块 | 描述 | -|-------------------------------------------|-------------------| -| [onnx](https://github.com/onnx/onnx) | 通用AI模型标准格式 | -| [ncnn](https://github.com/Tencent/ncnn) | 高性能神经网络计算框架(腾讯) | - ---- - -如需更多帮助,请联系技术支持团队。 \ No newline at end of file diff --git a/qwen-docs/client_events.md b/qwen-docs/client_events.md new file mode 100644 index 0000000..856479e --- /dev/null +++ b/qwen-docs/client_events.md @@ -0,0 +1,100 @@ +本文介绍 Qwen-Omni-Realtime API 的客户端事件。 + +> 相关文档:[实时(Qwen-Omni-Realtime)](https://help.aliyun.com/zh/model-studio/realtime)。 + +## **session.update** + +客户端建立 WebSocket 连接后,需首先发送该事件,用于更新会话的默认配置。服务端收到 `session.update` 事件后会校验参数。如果参数不合法,则返回错误;如果参数合法,则更新并返回完整的配置。 + +| **type** `*string*` **(必选)** 事件类型,固定为`session.update`。 | ``` { "event_id": "event_ToPZqeobitzUJnt3QqtWg", "type": "session.update", "session": { "modalities": [ "text", "audio" ], "voice": "Chelsie", "input_audio_format": "pcm", "output_audio_format": "pcm", "instructions": "你是某五星级酒店的AI客服专员,请准确且友好地解答客户关于房型、设施、价格、预订政策的咨询。请始终以专业和乐于助人的态度回应,杜绝提供未经证实或超出酒店服务范围的信息。", "turn_detection": { "type": "server_vad", "threshold": 0.5, "silence_duration_ms": 800 }, "enable_search": true, "search_options": { "enable_source": true }, "tools": [ { "type": "function", "function": { "name": "get_current_weather", "description": "当你想查询指定城市的天气时非常有用。", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "城市或县区,比如北京市、杭州市、余杭区等。" } }, "required": ["location"] } } } ], "seed": 1314, "max_tokens": 16384, "repetition_penalty": 1.05, "presence_penalty": 0.0, "top_k": 50, "top_p": 1.0, "temperature": 0.9 } } ``` | +| --- | --- | +| **session** `*object*` (可选) 会话配置。 **属性** **modalities** `*array*` (可选) 模型输出模态设置,可选值: - \\["text"\\] 仅输出文本。 - \\["text","audio"\\](默认值) 输出文本与音频。 **voice** `*string*` (可选) 模型生成音频的音色,支持的音色参见[音色列表](https://help.aliyun.com/zh/model-studio/realtime#f9c68d860a3rs)。 默认音色: - Qwen3.5-Omni-Realtime系列:`Tina` - Qwen3-Omni-Flash-Realtime:`Cherry` - Qwen-Omni-Turbo-Realtime:`Chelsie` **input\\_audio\\_format** `*string*` (可选) 用户输入音频格式,当前仅支持设为`pcm`。 **output\\_audio\\_format** `*string*` (可选) 模型输出音频的格式,当前仅支持设为`pcm`。 **instructions** `*string*` (可选) 系统消息,用于设定模型的目标或角色。 **turn\\_detection** `*object*` (可选) 语音活动检测(VAD)的配置。设置为 `null` 表示禁用 VAD,由用户手动触发模型响应。若未提供本字段,系统将使用以下默认参数启用 VAD。 **属性** **type** `*string*` (可选) VAD类型,取值如下: - `server_vad`(默认值):基于声学特征检测用户语音结束。 - `semantic_vad`:基于语义有效性检测用户语音结束,可过滤无意义语音(如回应语、背景音)。仅`qwen3.5-omni-realtime`模型支持。 **threshold** `*float*` (可选) VAD的灵敏度。值越低,VAD越敏感,更容易将微弱声音(包括背景噪音)识别为语音;值越高,越不敏感,需要更清晰、音量更大的语音才能触发。 取值范围在`[-1.0, 1.0]`,默认值为 0.5。 **silence\\_duration\\_ms** `*integer*` (可选) 语音结束后需保持静音的最短时间,超时即触发模型响应。值越低,响应越快,但可能在语音短暂停顿时误触发模型响应。 默认值为800,参数范围\\[200, 6000\\]。 **enable\\_search** `*boolean*` (可选) **仅在使用 Qwen3.5-Omni-Realtime 模型时生效。** 是否启用联网搜索功能。设置为 `true` 启用,默认为 `false`。启用后,模型可自主判断是否需要搜索来回应用户的即时问题。 > 工具调用(tools)和联网搜索(enable\\_search)不兼容,不可同时开启。 **search\\_options** `*object*` (可选) 联网搜索选项配置。需启用 `enable_search` 后才生效。 **属性** **enable\\_source** `*boolean*` (可选) 是否返回搜索结果来源列表。设置为 `true` 启用。 **tools** `*array*` (可选) 工具定义列表。配置后模型可根据用户输入自主决定是否调用工具。 **属性** **type** `*string*`(必选) 固定为 `function`。 **function.name** `*string*`(必选) 自定义的工具函数名称,建议使用与函数相同的名称,如`get_current_weather`或`get_current_time`。 **function.description** `*string*`(可选) 对工具函数功能的描述,大模型会参考该字段来选择是否使用该工具函数。 **function.parameters** `*object*`(可选) 对工具函数入参的描述,大模型会参考该字段来进行入参的提取。如果工具函数不需要输入参数,则无需指定。 **属性** **type** `*string*`(必选) 固定为 `object`。 **properties** `*object*`(可选) 描述各入参的名称、数据类型与描述。Key 值为入参的名称,Value 值为包含数据类型(`type`)与描述(`description`)的对象。 **required** `*array*`(可选) 指定哪些入参为必填项。 | +| **temperature** `*float*` (可选) 采样温度,控制模型生成内容的多样性。 temperature越高,生成的内容更多样,反之,生成的内容更确定。 取值范围: \\[0, 2) 由于temperature与top\\_p均可以控制生成内容的多样性,因此建议您只设置其中一个值。 temperature默认值: - `qwen3.5-omni-realtime`系列:0.7 - `qwen3-omni-flash-realtime`系列:0.9 - `qwen-omni-turbo-realtime`系列:1.0 > `qwen-omni-turbo` 系列模型**不支持修改**。 | +| **top\\_p** `*float*` (可选) 核采样的概率阈值,控制模型生成内容的多样性。 top\\_p越高,生成的内容更多样。反之,生成的内容更确定。 取值范围:(0,1.0\\] 由于temperature与top\\_p均可以控制生成内容的多样性,因此建议您只设置其中一个值。 top\\_p默认值: - `qwen3.5-omni-realtime`系列:0.8 - `qwen3-omni-flash-realtime`系列:1.0 - `qwen-omni-turbo-realtime`系列:0.01 > `qwen-omni-turbo` 系列模型**不支持修改**。 | +| **top\\_k** `*integer*` (可选) 生成过程中采样候选集的大小。例如,取值为50时,仅将单次生成中得分最高的50个Token组成随机采样的候选集。取值越大,生成的随机性越高;取值越小,生成的确定性越高。取值为`null`或当top\\_k大于100时,表示不启用`top_k`策略,此时仅有`top_p`策略生效。 取值需要大于或等于0。 top\\_k默认值: - `qwen3.5-omni-realtime`系列:20 - `qwen3-omni-flash-realtime`系列:50 - `qwen-omni-turbo-realtime`系列:20 > `qwen-omni-turbo` 系列模型**不支持修改**。 | +| **max\\_tokens** `*integer*` (可选) 本次请求返回的最大 Token 数。 > `max_tokens` 的设置不会影响大模型的生成过程,如果模型生成的 Token 数超过`max_tokens`,本次请求会返回截断后的内容。 默认值和最大值都是模型的最大输出长度。关于各模型的最大输出长度,请参见[模型列表](https://help.aliyun.com/zh/model-studio/models#9f8890ce29g5u)。 max\\_tokens参数适用于需要限制字数(如生成摘要、关键词)、控制成本或减少响应时间的场景。 > `qwen-omni-turbo` 系列模型**不支持修改**。 | +| **repetition\\_penalty** `*float*` (可选) 模型生成时连续序列中的重复度。提高repetition\\_penalty时可以降低模型生成的重复度,1.0表示不做惩罚。没有严格的取值范围,只要大于0即可。 repetition\\_penalty默认值: - `qwen3.5-omni-realtime`系列:1.0 - `qwen3-omni-flash-realtime`系列:1.05 - `qwen-omni-turbo-realtime`系列:1.05 > `qwen-omni-turbo` 系列模型**不支持修改**。 | +| **presence\\_penalty** `*float*` (可选) 控制模型生成内容时的重复度。 取值范围:\\[-2.0, 2.0\\]。正数会减少重复度,负数会增加重复度。 presence\\_penalty默认值: - `qwen3.5-omni-realtime`系列:1.5 - `qwen3-omni-flash-realtime`系列:0.0 - `qwen-omni-turbo-realtime`系列:0.0 适用场景: 较高的presence\\_penalty适用于要求多样性、趣味性或创造性的场景,如创意写作或头脑风暴。 较低的presence\\_penalty适用于要求一致性或专业术语的场景,如技术文档或其他正式文档。 > `qwen-omni-turbo` 系列模型**不支持修改**。 | +| **seed** `*integer*` (可选) 设置seed参数会使大模型的生成过程更具有确定性,通常用于使模型每次运行的结果一致。 在每次模型调用时传入相同的seed值(由您指定),并保持其他参数不变,模型将尽可能返回相同的结果。 取值范围:0到231−1,默认值-1。 > `qwen-omni-turbo` 系列模型**不支持修改**。 | + +## **response.create** + +`response.create` 事件用于指示服务端创建模型响应。在VAD模式下,服务端会自动创建模型响应,无需发送该事件。在工具调用场景中,客户端通过 `conversation.item.create` 回传工具结果后,需发送此事件触发模型生成最终响应。 + +服务端使用 `response.created` 事件、一个或多个项和内容事件(如 `conversation.item.created` 和 `response.content_part.added`)进行响应,最后用一个 `response.done` 事件表示响应已完成。 + +| **type** `*string*` **(必选)** 事件类型,固定为`response.create`。 | ``` { "type": "response.create", "event_id": "event_1718624400000" } ``` | +| --- | --- | + +## **response.cancel** + +客户端发送此事件用以取消正在进行的响应。如果没有任何响应可供取消,服务端将响应错误事件。 + +| **type** `*string*` **(必选)** 事件类型,固定为`response.cancel`。 | ``` { "event_id": "event_B4o9RHSTWobB5OQdEHLTo", "type": "response.cancel" } ``` | +| --- | --- | + +## **input\_audio\_buffer.append** + +用于将音频字节追加到输入音频缓冲区。 + +| **type** `*string*` **(必选)** 事件类型,固定为`input_audio_buffer.append`。 | ``` { "event_id": "event_B4o9RHSTWobB5OQdEHLTo", "type": "input_audio_buffer.append", "audio": "UklGR..." } ``` | +| --- | --- | +| **audio** `*string*` **(必选)** Base64 编码的音频数据。 | + +## **input\_audio\_buffer.commit** + +用于提交用户输入音频缓冲区,在对话中创建新的用户消息项。 如果输入的音频缓冲区为空,服务端会返回错误事件。 + +- [VAD 模式](https://help.aliyun.com/zh/model-studio/realtime#68d826b358q1r):客户端不需要发送此事件,服务端会自动提交音频缓冲区。 + +- [Manual 模式](https://help.aliyun.com/zh/model-studio/realtime#3dbb650fb3ird):客户端必须提交音频缓冲区才能创建用户消息项。 + + +提交输入音频缓冲区不会从模型创建响应,服务端将使用 `input_audio_buffer.committed` 事件进行响应。 + +> 如果客户端发送过[input\_image\_buffer.append](#c28ed38410nfw)事件,input\_audio\_buffer.commit 事件会将图像缓冲区一起提交。 + +| **type** `*string*` **(必选)** 事件类型,固定为`input_audio_buffer.commit`。 | ``` { "event_id": "event_B4o9RHSTWobB5OQdEHLTo", "type": "input_audio_buffer.commit" } ``` | +| --- | --- | + +## **input\_audio\_buffer.clear** + +用于清除缓冲区中的音频字节。服务端发送`input_audio_buffer.cleared` 事件进行响应。 + +| **type** `*string*` **(必选)** 事件类型,固定为`input_audio_buffer.clear`。 | ``` { "event_id": "event_xxx", "type": "input_audio_buffer.clear" } ``` | +| --- | --- | + +## **input\_image\_buffer.append** + +用于将图像数据添加到图像缓冲区。图像可来自本地文件,或从视频流实时采集。 + +目前对图片输入有以下限制: + +- 图像格式必须为 JPG 或 JPEG。建议分辨率为 480p 或 720p以获得最佳性能,最高不超过 1080p; + +- 单张图片大小不大于500KB(Base64编码前); + +- 图片数据需要经过Base64编码; + +- 建议以 1张/秒 的频率向服务端发送图像; + +- 发送 input\_image\_buffer.append 事件前,至少发送过一次 input\_audio\_buffer.append 事件。 + + +> 图像缓冲区与音频缓冲区一起通过[input\_audio\_buffer.commit](#1cbea5fa7fkfl)事件提交。 + +| **type** `*string*` **(必选)** 事件类型,固定为`input_image_buffer.append`。 | ``` { "event_id": "event_xxx", "type": "input_image_buffer.append", "image": "xxx" } ``` | +| --- | --- | +| **image** `*string*` **(必选)** Base64 编码的图像数据。 | + +## **conversation.item.create** + +客户端发送此事件,将工具函数的执行结果回传给服务端。当模型触发工具调用后,客户端需在本地执行工具函数,然后通过此事件将结果发回,再发送 `response.create` 触发模型生成最终响应。 + +**说明** + +当前仅支持 `function_call_output` 类型的 item。 + +| **type** `*string*` **(必选)** 事件类型,固定为`conversation.item.create`。 | ``` { "event_id": "event_55099cddb51b4f208cb95d1a994eef80", "type": "conversation.item.create", "item": { "id": "item_2a80d7682b4e473c9c2154da135041e9", "type": "function_call_output", "call_id": "call_62c24725afdb4c2680ac54", "output": "北京今天天气为霾转晴,气温4/-4℃,微风" } } ``` | +| --- | --- | +| **item** `*object*` **(必选)** 要创建的对话项,不能为空。 **属性** **id** `*string*`(可选) 对话项 ID。客户端可预先指定以便对齐本地状态;若未提供,由服务端生成。 **type** `*string*`(必选) 对话项类型。当前仅支持 `function_call_output`。 **call\\_id** `*string*`(必选) 对应 `response.function_call_arguments.done` 事件中返回的 `call_id`。 **output** `*string*`(必选) 工具函数的执行结果。 | \ No newline at end of file diff --git a/qwen-docs/server_events.md b/qwen-docs/server_events.md new file mode 100644 index 0000000..45d857e --- /dev/null +++ b/qwen-docs/server_events.md @@ -0,0 +1,283 @@ +本文介绍 Qwen-Omni-Realtime API 的服务端事件,包括工具调用(Function Calling)相关事件。 + +> 相关文档:[实时(Qwen-Omni-Realtime)](https://help.aliyun.com/zh/model-studio/realtime)。 + +## **error** + +服务端返回的错误信息。 + +| **event\\_id** `*string*` 本次事件唯一标识符。 | ``` { "event_id": "event_RoUu4T8yExPMI37GKwaOC", "type": "error", "error": { "type": "invalid_request_error", "code": "invalid_value", "message": "Invalid modalities: ['audio']. Supported combinations are: ['text'] and ['audio', 'text'].", "param": "session.modalities" } } ``` | +| --- | --- | +| **type** `*string*` 事件类型,固定为`error`。 | +| **error** `*object*` 错误的详细信息。 **属性** **type** `*string*` 错误类型。 **code** `*string*` 错误码。 **message** `*string*` 错误信息。 **param** `*string*` 与错误相关的参数,如`session.modalities`。 | + +## **session.created** + +客户端连接后,服务端返回的第一个事件,包含本次连接的默认配置信息。 + +| **event\\_id** `*string*` 本次事件唯一标识符。 | ``` { "event_id": "event_RdvlSpbBb2ssyBjYrDHjt", "type": "session.created", "session": { "object": "realtime.session", "model": "qwen3-omni-flash-realtime", "modalities": [ "text", "audio" ], "voice": "Cherry", "input_audio_format": "pcm", "output_audio_format": "pcm", "input_audio_transcription": { "model": "gummy-realtime-v1" }, "turn_detection": { // 取值为server_vad或semantic_vad(仅qwen3.5-omni-realtime支持) "type": "server_vad", "threshold": 0.5, "prefix_padding_ms": 300, "silence_duration_ms": 800, "create_response": true, "interrupt_response": true }, "enable_search": false, "search_options": {}, "tools": [], "temperature": 0.8, "id": "sess_Ov7GOXoNXhNjlxXtOGKQS" } } ``` | +| --- | --- | +| **type** `*string*` 事件类型,固定为`session.created`。 | +| **session** `*object*` 会话的配置信息。 **属性** **object** `*string*` 固定为`realtime.session`。 **model** `*string*` 使用的模型。 **modalities** `*array*` 模型输出模态设置。 **voice** `*string*` 模型生成音频的音色。 **input\\_audio\\_format** `*string*` 输入音频的格式,固定为`pcm`。 **output\\_audio\\_format** `*string*` 模型输出音频的格式,固定为`pcm`。 **input\\_audio\\_transcription** `*object*` 语音转录的配置。 **属性** **model** `*string*` 语音转录模型,固定为`gummy-realtime-v1`。 **turn\\_detection** `*object*` 语音活动检测(VAD)的配置。 **属性** **type** `*string*` VAD类型。取值:`server_vad`(默认值)或 `semantic_vad`。详情请参见[客户端事件](https://help.aliyun.com/zh/model-studio/client-events#7c8172272codm)。 **threshold** `*float*` VAD检测阈值。 **silence\\_duration\\_ms** `*integer*` 检测语音停止的静音持续时间。 **enable\\_search** `*boolean*` 是否启用联网搜索功能。仅 Qwen3.5-Omni-Realtime 模型支持。 **search\\_options** `*object*` 联网搜索选项配置。 **temperature** `*float*` 模型的温度参数。 | + +## **session.updated** + +收到用户的 `session.update` 请求后,若处理成功,则返回此事件;若出错,则返回 `error` 事件。 + +| **event\\_id** `*string*` 本次事件唯一标识符。 | ``` { "event_id": "event_X1HsXS4b4uptp6yo1LgKd", "type": "session.updated", "session": { "id": "sess_Aih6vAcY5Ddt6jwFx1tCa", "object": "realtime.session", "model": "qwen3-omni-flash-realtime", "modalities": [ "text", "audio" ], "instructions": "你是个人助理小云,请你准确且友好地解答用户的问题,始终以乐于助人的态度回应。", "voice": "Cherry", "input_audio_format": "pcm", "output_audio_format": "pcm", "input_audio_transcription": { "model": "gummy-realtime-v1" }, "turn_detection": { // 取值为server_vad或semantic_vad(仅qwen3.5-omni-realtime支持) "type": "server_vad", "threshold": 0.1, "prefix_padding_ms": 500, "silence_duration_ms": 900, "create_response": true, "interrupt_response": true }, "enable_search": true, "search_options": { "enable_source": true }, "tools": [ { "type": "function", "function": { "name": "get_current_weather", "description": "当你想查询指定城市的天气时非常有用。", "parameters": { "type": "object", "properties": { "location": {"type": "string", "description": "城市名称"} }, "required": ["location"] } } } ], "temperature": 0.8, "max_response_output_token": "inf", "max_tokens": 16384, "repetition_penalty": 1.05, "presence_penalty": 0.0, "top_k": 50, "top_p": 1.0, "seed":-1 } } ``` | +| --- | --- | +| **type** `*string*` 事件类型,固定为`session.updated`。 | +| **session** `*object*` 会话的配置信息。 **属性** **temperature** `*float*` 模型的温度参数。 **modalities** `*array*` 模型输出模态设置。 **voice** `*string*` 模型生成音频的音色。 **instructions** `*string*` 模型的目标与角色。 **input\\_audio\\_format** `*string*` 输入音频的格式,当前仅支持设置为`pcm`。 **output\\_audio\\_format** `*string*` 输出音频的格式,当前仅支持设置为`pcm`。 **input\\_audio\\_transcription** `*object*` 语音转录的配置。 **属性** **model** `*string*` 语音转录模型,固定为`gummy-realtime-v1`。 **turn\\_detection** `*object*` 语音活动检测(VAD)的配置。 **属性** **type** `*string*` VAD类型。取值:`server_vad`(默认值)或 `semantic_vad`。详情请参见[客户端事件](https://help.aliyun.com/zh/model-studio/client-events#7c8172272codm)。 **threshold** `*float*` VAD检测阈值。 **silence\\_duration\\_ms** `*integer*` 检测语音停止的静音持续时间。 **enable\\_search** `*boolean*`(可选) 是否启用联网搜索功能。仅 Qwen3.5-Omni-Realtime 模型支持。 **search\\_options** `*object*`(可选) 联网搜索选项配置。 **tools** `*array*`(可选) 工具定义列表。配置后模型可根据用户输入自主决定是否调用工具。 **属性** **type** `*string*`(必选) 固定为 `function`。 **function.name** `*string*`(必选) 自定义的工具函数名称,建议使用与函数相同的名称,如`get_current_weather`或`get_current_time`。 **function.description** `*string*`(可选) 对工具函数功能的描述,大模型会参考该字段来选择是否使用该工具函数。 **function.parameters** `*object*`(可选) 对工具函数入参的描述,大模型会参考该字段来进行入参的提取。如果工具函数不需要输入参数,则无需指定。 **属性** **type** `*string*`(必选) 固定为 `object`。 **properties** `*object*`(可选) 描述各入参的名称、数据类型与描述。Key 值为入参的名称,Value 值为包含数据类型(`type`)与描述(`description`)的对象。 **required** `*array*`(可选) 指定哪些入参为必填项。 **top\\_p**`*float*` 核采样的概率阈值。 **top\\_k** `*integer*` 模型生成过程中,采样候选集的大小。 **max\\_tokens** `*integer*` 模型在本次请求返回的最大 Token 数。 **repetition\\_penalty** `*float*` 控制模型生成时,连续序列中的重复度*。* **presence\\_penalty** `*float*` 控制模型在生成内容时的重复度。 **seed** `*integer*` 模型在每次请求时,运行结果一致性程度。 | + +## **input\_audio\_buffer.speech\_started** + +在 VAD 模式下,当服务端在音频缓冲区中检测到语音开始时,会返回此事件。 + +> 若服务端尚未检测到语音,则每次向缓冲区添加音频时都可能触发此事件。 + +| **event\\_id** `*string*` 本次事件唯一标识符。 | ``` { "event_id": "event_Pvp8nEhsQuGCQbFJ9x58n", "type": "input_audio_buffer.speech_started", "audio_start_ms": 3647, "item_id": "item_YbAiGvK2H7YaS34o4R6Ba" } ``` | +| --- | --- | +| **type** `*string*` 事件类型,固定为`input_audio_buffer.speech_started`。 | +| **audio\\_start\\_ms** `*integer*` 从音频开始写入缓冲区到首次检测到语音所经过的毫秒数。 | +| **item\\_id** `*string*` 语音停止时将创建的用户消息项的 ID。 > 用户消息项用于将用户输入追加到对话历史,供模型后续推理与生成使用。 | + +## **input\_audio\_buffer.speech\_stopped** + +在 VAD 模式下,当音频缓冲区中检测到语音结束时,服务端会返回此事件。 + +同时,服务端还会返回一个 `conversation.item.created` 事件,以创建对应的用户消息项。 + +| **event\\_id** `*string*` 本次事件唯一标识符。 | ``` { "event_id": "event_UhQiqNVRsgUiq4KUS5Xb5", "type": "input_audio_buffer.speech_stopped", "audio_end_ms": 4453, "item_id": "item_YbAiGvK2H7YaS34o4R6Ba" } ``` | +| --- | --- | +| **type** `*string*` 事件类型,固定为`input_audio_buffer.speech_stopped`。 | +| **audio\\_end\\_ms** `*integer*` 语音停止时刻距会话开始经过的毫秒数。 | +| **item\\_id** `*string*` 将创建的用户消息项的 ID。 | + +## **input\_audio\_buffer.committed** + +当输入音频缓冲区被提交时返回此事件。 + +- 在VAD模式下,当检测到用户说话结束时,服务端会自动提交音频缓冲区并返回此事件。 + +- 在 Manual 模式下,当客户端发送`input_audio_buffer.commit`事件后,服务端返回此事件。 + + +| **event\\_id** `*string*` 本次事件唯一标识符。 | ``` { "event_id": "event_Iy6sUzL1nmdFgshFYxJEz", "type": "input_audio_buffer.committed", "item_id": "item_YbAiGvK2H7YaS34o4R6Ba" } ``` | +| --- | --- | +| **type** `*string*` 事件类型,固定为`input_audio_buffer.committed`。 | +| **item\\_id** `*string*` 将创建的用户消息项的 ID。 | + +## **input\_audio\_buffer.cleared** + +客户端发送`input_audio_buffer.clear`事件后,服务端将返回此事件。 + +| **event\\_id** `*string*` 本次事件唯一标识符。 | ``` { "event_id": "event_RoUu4T8yExPMI37GKwaOC", "type": "input_audio_buffer.cleared" } ``` | +| --- | --- | +| **type** `*string*` 事件类型,固定为`input_audio_buffer.cleared`。 | + +## **conversation.item.created** + +当对话项创建时返回此事件。 + +| **event\\_id** `*string*` 本次事件唯一标识符。 | ``` { "event_id": "event_JEfkrr9gO3Ny7Xcv9bGVd", "type": "conversation.item.created", "item": { "id": "item_YbAiGvK2H7YaS34o4R6Ba", "object": "realtime.item", "type": "message", "status": "in_progress", "role": "assistant", "content": [ { "type": "input_audio" } ] } } // 工具调用场景 { "event_id": "event_S1hkaIQgcuQD8OEdOpGHQ", "type": "conversation.item.created", "item": { "id": "item_FEG9qJGNkPcdf4et3p7BV", "object": "realtime.item", "type": "function_call", "status": "in_progress", "call_id": "call_bc0a7fb7235840f69ecfe4", "name": "get_current_weather", "arguments": "" } } ``` | +| --- | --- | +| **type** `*string*` 事件类型,固定为`conversation.item.created`。 | +| **item** `*object*` 要添加到对话中的项。 **属性** **id** `*string*` 对话项的唯一ID。 **object** `*string*` 始终为 `realtime.item` 。 **status** `*string*` 对话项的状态。 **role** `*string*` 消息的角色。 **content** `*string*` 消息的内容。当 type 为 `message` 时存在。 **type** `*string*` 对话项的类型。可选值为 `message`(常规消息)或 `function_call`(工具调用)。 **name** `*string*` 当 type 为 `function_call` 时,被调用的函数名称。 **call\\_id** `*string*` 当 type 为 `function_call` 时,本次函数调用的唯一 ID。 **arguments** `*string*` 当 type 为 `function_call` 时,函数调用的参数(JSON 字符串)。 | + +## **conversation.item.input\_audio\_transcription.completed** + +此事件表示用户音频写入缓冲区后生成的转录结果。其转录由独立的语音识别模型(当前固定为 `gummy-realtime-v1`)处理。 + +> 语音识别模型生成的转录文本可能与 Qwen-Omni-Realtime 模型的理解存在差异,仅供参考。 + +| **event\\_id** `*string*` 本次事件唯一标识符。 | ``` { "event_id": "event_FrrZcxiDfTB9LD9p4pVng", "type": "conversation.item.input_audio_transcription.completed", "item_id": "item_YbAiGvK2H7YaS34o4R6Ba", "content_index": 0, "transcript": "喂,你好。" } ``` | +| --- | --- | +| **type** `*string*` 事件类型,固定为`conversation.item.input_audio_transcription.completed`。 | +| **item\\_id** `*string*` 用户消息项的 ID。 | +| **content\\_index** *integer* 当前固定为0。 | +| **transcript** `*string*` 转录的文本内容。 | + +## **conversation.item.input\_audio\_transcription.failed** + +启用输入音频转录后,若用户音频转录失败,服务端会返回此事件。此事件独立于 `error` 事件,便于客户端识别。 + +| **event\\_id** `*string*` 本次事件唯一标识符。 | ``` { "type": "conversation.item.input_audio_transcription.failed", "item_id": "", "content_index": 0, "error": { "code": "", "message": "", "param": "" } } ``` | +| --- | --- | +| **type** `*string*` 事件类型,固定为`conversation.item.input_audio_transcription.failed`。 | +| **item\\_id** `*string*` 用户消息项的 ID。 | +| **content\\_index** `*integer*` 当前固定为0。 | +| **error** `*object*` 错误信息。 **属性** **code** `*string*` 错误码。 **message** `*string*` 错误消息。 **param** `*string*` 错误相关的参数。 | + +## **response.created** + +当服务端生成新的模型响应时,会返回此事件。 + +| **event\\_id** `*string*` 本次事件唯一标识符。 | ``` { "event_id": "event_XuDavMzQN3KKepqGu3KRh", "type": "response.created", "response": { "id": "resp_HaVOPdbmX6vifiV5pAfJY", "object": "realtime.response", "conversation_id": "conv_FjJaccpnvwHNo9cPVuzGc", "status": "in_progress", "modalities": [ "text", "audio" ], "voice": "Cherry", "output_audio_format": "pcm", "output": [] } } ``` | +| --- | --- | +| **type** `*string*` 事件类型,固定为`response.created`。 | +| **response** `*object*` 响应对象。 **属性** **id** `*string*` 响应的唯一 ID。 **conversation\\_id** `*string*` 当前会话的唯一ID。 **object** `*string*` 对象类型,此事件下固定为`realtime.response`。 **status** `*string*` 响应的状态。在`[completed, failed, in_progress, or incomplete]`范围内。 **modalities** `*array*` 响应的模态。 **voice** `*string*` 模型生成音频的音色。 **output** `*string*` 此事件下目前为空。 | + +## **response.done** + +响应生成完成后,服务端会返回此事件。事件中的 `response` 对象包含除原始音频数据外的全部输出项。 + +| **event\\_id** `*string*` 本次事件唯一标识符。 | ``` { "event_id": "event_CSaxRRYLvbrfexDXAEuDG", "type": "response.done", "response": { "id": "resp_HaVOPdbmX6vifiV5pAfJY", "object": "realtime.response", "conversation_id": "conv_FjJaccpnvwHNo9cPVuzGc", "status": "completed", "modalities": [ "text", "audio" ], "voice": "Cherry", "output_audio_format": "pcm", "output": [ { "id": "item_Ls6MtCUWO7LM4E59QziNv", "object": "realtime.item", "type": "message", "status": "completed", "role": "assistant", "content": [ { "type": "audio", "transcript": "你好呀!有什么我可以帮你的吗?" } ] } ], "usage": { "total_tokens": 377, "input_tokens": 336, "output_tokens": 41, "input_tokens_details": { "text_tokens": 228, "audio_tokens": 108 }, "output_tokens_details": { "text_tokens": 9, "audio_tokens": 32 }, "plugins": { "search": { "count": 1, "strategy": "agent" } } } } } // 工具调用场景 { "event_id": "event_T1EFAJp43X2DWtDRmxTtx", "type": "response.done", "response": { "id": "resp_TucN5QgymL5MA8vkJvFlS", "object": "realtime.response", "conversation_id": "conv_SEDZESRlefT8WvLSmEn6E", "status": "completed", "modalities": ["text", "audio"], "voice": "Ethan", "output_audio_format": "pcm16", "output": [ { "id": "item_FEG9qJGNkPcdf4et3p7BV", "object": "realtime.item", "type": "function_call", "status": "completed", "call_id": "call_bc0a7fb7235840f69ecfe4", "name": "get_current_weather", "arguments": " {\\"location\\": \\"杭州\\"}" } ], "usage": { "total_tokens": 567, "input_tokens": 524, "output_tokens": 43, "input_tokens_details": { "text_tokens": 487, "audio_tokens": 37 }, "output_tokens_details": { "text_tokens": 43 } } } } ``` | +| --- | --- | +| **type** `*string*` 事件类型,固定为`response.done`。 | +| **response** `*object*` 响应对象。 **属性** **id** `*string*` 响应的唯一 ID。 **conversation\\_id** `*string*` 当前会话的唯一ID。 **object** `*string*` 对象类型,此事件下固定为`realtime.response`。 **status** `*string*` 响应的状态。 **modalities** `*array*` 响应的模态。 **voice** `*string*` 模型生成音频的音色。 **output** `*object*` 响应的输出。 **属性** **id** `*string*` 响应输出对应的ID。 **type** `*string*` 输出项的类型,可选值为 `message`(常规消息)或 `function_call`(工具调用)。 **object** `*string*` 输出项的对象类型,当前固定为`realtime.item`。 **status** `*string*` 输出项的状态。 **role** `*string*` 输出项的角色。 **content** `*array*` 输出项的内容。当 type 为 `message` 时存在。 **属性** **type** `*string*` 输出内容的类型。输出为纯文本时,为`text`;输出包含音频时,为`audio`。 **text** `*string*` 输出的文本内容。 **transcript** `*string*` 音频转录为文字后的内容。 **name** `*string*` 当 type 为 `function_call` 时,被调用的函数名称。 **call\\_id** `*string*` 当 type 为 `function_call` 时,函数调用的唯一 ID。 **arguments** `*string*` 当 type 为 `function_call` 时,函数调用的完整参数(JSON 字符串)。 **usage** `*object*` 本次响应的 Token 消耗信息。 **属性** **total\\_tokens** `*integer*` 本次响应消耗的总 Token 数。 **input\\_tokens** `*integer*` 输入 Token 数。 **output\\_tokens** `*integer*` 输出 Token 数。 **input\\_tokens\\_details** `*object*` 输入 Token 的分项详情,包含 `text_tokens`(文本 Token 数)和 `audio_tokens`(音频 Token 数)。 **output\\_tokens\\_details** `*object*` 输出 Token 的分项详情,包含 `text_tokens`(文本 Token 数)和 `audio_tokens`(音频 Token 数)。 **plugins** `*object*`(可选) 插件使用计量信息。启用联网搜索(`enable_search`)时返回。 **属性** **search** `*object*` 联网搜索计量信息。 **属性** **count** `*integer*` 搜索次数。 **strategy** `*string*` 搜索策略。 | + +## **response.text.delta** + +当输出模态仅包含文本,且模型增量生成新的文本时,服务端将返回此事件。 + +| **event\\_id** `*string*` 本次事件唯一标识符。 | ``` { "delta": "喂", "event_id": "event_TH49MauuPmRo1RGaMSlP7", "type": "response.text.delta", "response_id": "resp_PrRSvPVpnCExdUOGHHLuP", "item_id": "item_L8IRm9kRXFpxoOjDqDC96", "output_index": 0, "content_index": 0 } ``` | +| --- | --- | +| **type** `*string*` 事件类型,固定为`response.text.delta`。 | +| **delta** `*string*` 返回的增量文本。 | +| **response\\_id** `*string*` 回复的ID。 | +| **item\\_id** `*string*` 消息项ID,可以关联同一个消息项。 | +| **output\\_index** `*integer*` 响应中输出项的索引, 目前固定为 0。 | +| **content\\_index** `*integer*` 响应中输出项中内部部分的索引, 目前固定为 0。 | + +## **response.text.done** + +当输出模态仅包含文本,且模型生成的文本结束时,服务端将返回此事件。 + +> 当响应中断、不完整或取消时,也会返回此事件。 + +| **event\\_id** `*string*` 本次事件唯一标识符。 | ``` { "event_id": "event_B1lIeE2Nac33zn5V7h2mm", "type": "response.text.done", "response_id": "resp_B1lIdtjF4Noqpn5NOjznj", "item_id": "item_B1lIdJsAJlJiFs8ztWpJt", "output_index": 0, "content_index": 0, "text": "How can I assist you today?" } ``` | +| --- | --- | +| **type** `*string*` 事件类型,固定为`response.text.done`。 | +| **response\\_id** `*string*` 响应的ID。 | +| **item\\_id** `*string*` 消息项ID。 | +| **output\\_index**`*integer*` 响应输出项的索引。 | +| **content\\_index**`*integer*` 响应输出项的索引。 | +| **text** `*string*` 模型输出的完整文本。 | + +## **response.audio.delta** + +当输出模态包含音频,且模型增量生成新的音频数据时,服务端将返回此事件。 + +| **event\\_id** `*string*` 本次事件唯一标识符。 | ``` { "event_id": "event_B1osWMZBtrEQbiIwW0qHQ", "type": "response.audio.delta", "response_id": "resp_P79OOMs8LnrXVpiIHUCKR", "item_id": "item_OFaPGtzfWCPyGzxnuEX9i", "output_index": 0, "content_index": 0, "delta": "{base64 audio}" } ``` | +| --- | --- | +| **type** `*string*` 事件类型,固定为`response.audio.delta`。 | +| **response\\_id** `*string*` 响应的ID。 | +| **item\\_id** `*string*` 消息项ID。 | +| **output\\_index**`*integer*` 响应输出项的索引。 | +| **content\\_index**`*integer*` 响应输出项的索引。 | +| **delta** `*string*` 模型增量输出的音频数据,使用Base64编码。 | + +## **response.audio.done** + +当输出模态包含音频,且模型完成生成音频数据时,服务端将返回此事件。 + +> 当响应中断、不完整或取消时,也会返回此事件。 + +| **event\\_id** `*string*` 本次事件唯一标识符。 | ``` { "event_id": "event_Le1TDl7VfyHQxl47DtGxI", "type": "response.audio.done", "response_id": "resp_HaVOPdbmX6vifiV5pAfJY", "item_id": "item_Ls6MtCUWO7LM4E59QziNv", "output_index": 0, "content_index": 0 } ``` | +| --- | --- | +| **type** `*string*` 事件类型,固定为`response.audio.done`。 | +| **response\\_id** `*string*` 响应的ID。 | +| **item\\_id** `*string*` 消息项ID。 | +| **output\\_index**`*integer*` 响应输出项的索引。 | +| **content\\_index**`*integer*` 响应输出项的索引。 | + +## **response.audio\_transcript.delta** + +当输出模态包含音频,且模型增量生成新的音频对应的文本时,服务端将返回 `response.audio_transcript.delta` 事件。 + +| **event\\_id** `*string*` 本次事件唯一标识符。 | ``` { "event_id": "event_BksW7fOwnyavZdDxIzZYM", "type": "response.audio_transcript.delta", "response_id": "resp_HaVOPdbmX6vifiV5pAfJY", "item_id": "item_Ls6MtCUWO7LM4E59QziNv", "output_index": 0, "content_index": 0, "delta": "有什么" } ``` | +| --- | --- | +| **type** `*string*` 事件类型,固定为`response.audio_transcript.delta`。 | +| **response\\_id** `*string*` 响应的ID。 | +| **item\\_id** `*string*` 消息项ID。 | +| **output\\_index**`*integer*` 响应输出项的索引。 | +| **content\\_index**`*integer*` 响应输出项的索引。 | +| **delta** `*string*` 增量文本。 | + +## **response.audio\_transcript.done** + +当输出模态包含音频,且模型完成音频转录后,服务端将返回 `response.audio_transcript.done` 事件。 + +| **event\\_id** `*string*` 本次事件唯一标识符。 | ``` { "event_id": "event_X49tL2WerT4WjxcmH16lS", "type": "response.audio_transcript.done", "response_id": "resp_HaVOPdbmX6vifiV5pAfJY", "item_id": "item_Ls6MtCUWO7LM4E59QziNv", "output_index": 0, "content_index": 0, "transcript": "你好呀!有什么我可以帮你的吗?" } ``` | +| --- | --- | +| **type** `*string*` 事件类型,固定为`response.audio_transcript.done`。 | +| **response\\_id** `*string*` 响应的ID。 | +| **item\\_id** `*string*` 消息项ID。 | +| **output\\_index**`*integer*` 响应输出项的索引。 | +| **content\\_index**`*integer*` 响应输出项的索引。 | +| **transcript** `*string*` 完整文本。 | + +## **response.function\_call\_arguments.delta** + +当模型以流式方式生成函数调用的参数字符串时,每产生一段新内容,服务端推送一次本事件。客户端应按接收顺序将各事件中的 `delta` 字段拼接,得到与当前进度一致的参数文本;完整内容以随后的 `response.function_call_arguments.done` 为准。 + +| **event\\_id** `*string*` 本次事件唯一标识符。 | ``` { "event_id": "event_SlKoJyEbPEqLq14DSM1u5", "type": "response.function_call_arguments.delta", "response_id": "resp_JnTOsWXlFhKcFohZbtfz6", "item_id": "item_Rhcms7CauTNsQprV5S4Hr", "output_index": 0, "call_id": "call_2be200f4cafe419b9530dd", "delta": " {\\"location\\": \\"北京\\"}" } ``` | +| --- | --- | +| **type** `*string*` 事件类型,固定为`response.function_call_arguments.delta`。 | +| **response\\_id** `*string*` 响应的ID。 | +| **item\\_id** `*string*` 消息项ID。 | +| **output\\_index**`*integer*` 该响应中输出项的索引。 | +| **call\\_id** `*string*` 本次函数调用的唯一 ID,与同一轮中的 `done` 事件保持一致。 | +| **delta** `*string*` 本段新增的参数字符串片段(增量)。需按顺序拼接。 | + +## **response.function\_call\_arguments.done** + +函数调用参数已全部生成完毕。本事件中的 `arguments` 为完整的参数字符串。客户端可在收到本事件后解析参数并调用本地工具函数;应以本事件中的完整 `arguments` 为准,而非 `delta` 拼接结果。 + +| **event\\_id** `*string*` 本次事件唯一标识符。 | ``` { "event_id": "event_X6suLyuL5agdH7r6koesM", "type": "response.function_call_arguments.done", "response_id": "resp_JnTOsWXlFhKcFohZbtfz6", "item_id": "item_Rhcms7CauTNsQprV5S4Hr", "output_index": 0, "name": "get_current_weather", "call_id": "call_2be200f4cafe419b9530dd", "arguments": " {\\"location\\": \\"北京\\"}" } ``` | +| --- | --- | +| **type** `*string*` 事件类型,固定为`response.function_call_arguments.done`。 | +| **response\\_id** `*string*` 响应的ID。 | +| **item\\_id** `*string*` 消息项ID。 | +| **output\\_index**`*integer*` 该响应中输出项的索引。 | +| **call\\_id** `*string*` 本次函数调用的唯一 ID。 | +| **name** `*string*` 被调用的函数名称。 | +| **arguments** `*string*` 函数调用的完整参数,一般以 JSON 字符串形式表示。 | + +## **response.output\_item.added** + +在响应生成过程中创建新项目时,服务端返回此事件。项目类型可以是 `message`(常规消息)或 `function_call`(工具调用)。 + +| **event\\_id** `*string*` 本次事件唯一标识符。 | ``` { "event_id": "event_DsCO341DEVtiATtCB6BUY", "type": "response.output_item.added", "response_id": "resp_HaVOPdbmX6vifiV5pAfJY", "output_index": 0, "item": { "id": "item_Ls6MtCUWO7LM4E59QziNv", "object": "realtime.item", "type": "message", "status": "in_progress", "role": "assistant", "content": [] } } // 工具调用场景 { "event_id": "event_HXmKt5pGoiRtXx7Hq7zpN", "type": "response.output_item.added", "response_id": "resp_TucN5QgymL5MA8vkJvFlS", "output_index": 0, "item": { "id": "item_FEG9qJGNkPcdf4et3p7BV", "object": "realtime.item", "type": "function_call", "status": "in_progress", "call_id": "call_bc0a7fb7235840f69ecfe4", "name": "get_current_weather", "arguments": "" } } ``` | +| --- | --- | +| **type** `*string*` 事件类型,固定为`response.output_item.added`。 | +| **response\\_id** `*string*` 响应的ID。 | +| **output\\_index**`*integer*` 响应输出项的索引。 | +| **item**`*object*` 输出项信息。 **属性** **id** `*string*` 输出项的唯一ID。 **object** `*string*` 始终为 `realtime.item` 。 **status** `*string*` 输出项的状态。 **role** `*string*` 发送消息的角色。 **content** `*string*` 消息的内容。当 type 为 `message` 时存在。 **type** `*string*` 输出项的类型。可选值为 `message`(常规消息)或 `function_call`(工具调用)。 **name** `*string*` 当 type 为 `function_call` 时,被调用的函数名称。 **call\\_id** `*string*` 当 type 为 `function_call` 时,本次函数调用的唯一 ID。 **arguments** `*string*` 当 type 为 `function_call` 时,函数调用的参数(JSON 字符串)。在 added 事件中初始为空字符串。 | + +## **response.output\_item.done** + +当新的项目输出完成时,服务端返回此事件。 + +| **event\\_id** `*string*` 本次事件唯一标识符。 | ``` { "event_id": "event_MEu5nlLw1LsOguHiehIP8", "type": "response.output_item.done", "response_id": "resp_HaVOPdbmX6vifiV5pAfJY", "output_index": 0, "item": { "id": "item_Ls6MtCUWO7LM4E59QziNv", "object": "realtime.item", "type": "message", "status": "completed", "role": "assistant", "content": [ { "type": "audio", "text": "你好呀!有什么我可以帮你的吗?" } ] } } // 工具调用场景 { "event_id": "event_FHspdfAnCyjuME3mmAwSY", "type": "response.output_item.done", "response_id": "resp_TucN5QgymL5MA8vkJvFlS", "output_index": 0, "item": { "id": "item_FEG9qJGNkPcdf4et3p7BV", "object": "realtime.item", "type": "function_call", "status": "completed", "call_id": "call_bc0a7fb7235840f69ecfe4", "name": "get_current_weather", "arguments": " {\\"location\\": \\"杭州\\"}" } } ``` | +| --- | --- | +| **type** `*string*` 事件类型,固定为`response.output_item.done`。 | +| **response\\_id** `*string*` 响应的ID。 | +| **output\\_index**`*integer*` 响应输出项的索引。 | +| **item**`*object*` 输出项信息。 **属性** **id** `*string*` 输出项的唯一ID。 **object** `*string*` 始终为 `realtime.item` 。 **status** `*string*` 输出项的状态。 **role** `*string*` 发送消息的角色。 **content** `*string*` 消息的内容。当 type 为 `message` 时存在。 **type** `*string*` 输出项的类型。可选值为 `message`(常规消息)或 `function_call`(工具调用)。 **name** `*string*` 当 type 为 `function_call` 时,被调用的函数名称。 **call\\_id** `*string*` 当 type 为 `function_call` 时,本次函数调用的唯一 ID。 **arguments** `*string*` 当 type 为 `function_call` 时,函数调用的完整参数(JSON 字符串)。 | + +## **response.content\_part.added** + +在响应生成过程中,向助手消息项中添加新内容部分时,服务端返回此事件。 + +| **event\\_id** `*string*` 本次事件唯一标识符。 | ``` { "event_id": "event_AVBOmrgY3C8bjlRajfSUT", "type": "response.content_part.added", "response_id": "resp_HaVOPdbmX6vifiV5pAfJY", "item_id": "item_Ls6MtCUWO7LM4E59QziNv", "output_index": 0, "content_index": 0, "part": { "type": "audio", "text": "" } } ``` | +| --- | --- | +| **type** `*string*` 事件类型,固定为`response.content_part.added`。 | +| **response\\_id** `*string*` 响应的ID。 | +| **item\\_id** `*string*` 消息项ID。 | +| **output\\_index**`*integer*` 响应输出项的索引,目前固定为 0。 | +| **content\\_index**`*integer*` 响应输出项中内部部分的索引, 目前固定为 0。 | +| **part**`*object*` 输出项信息。 **属性** **type** `*string*` 内容部分的类型。 **text** `*string*` 内容部分的文本。 | + +## **response.content\_part.done** + +在助手消息项中的内容部分完成流式传输时,服务端返回此事件。 + +| **event\\_id** `*string*` 本次事件唯一标识符。 | ``` { "event_id": "event_Il8HD19v58Qr5IBkw7LtN", "type": "response.content_part.done", "response_id": "resp_HaVOPdbmX6vifiV5pAfJY", "item_id": "item_Ls6MtCUWO7LM4E59QziNv", "output_index": 0, "content_index": 0, "part": { "type": "audio", "text": "你好呀!有什么我可以帮你的吗?" } } ``` | +| --- | --- | +| **type** `*string*` 事件类型,固定为`response.content_part.done`。 | +| **response\\_id** `*string*` 响应的ID。 | +| **item\\_id** `*string*` 消息项ID。 | +| **output\\_index**`*integer*` 响应输出项的索引,目前固定为 0。 | +| **content\\_index**`*integer*` 该项内容数组中内容部分的索引,目前固定为 0。 | +| **part**`*object*` 输出项信息。 **属性** **type** `*string*` 内容部分的类型。 **text** `*string*` 内容部分的文本。 |