更新时间:2025-09-22 06:10:16下载pdf
在接入 AI 基座通道前,请先阅读 App SDK 接入文档并完成接入:智能生活 App SDK > 准备工作。
完成接入后,在您的工程中引入 AI 基座通道:
// 6.7.0 及之后的版本均可用
implementation 'com.thingclips.smart:thingsmart:6.7.6'
// 录音组件
implementation 'com.thingclips.smart:thingsmart-audio-engine-sdk:6.7.0+'
implementation 'com.thingclips.smart:thingsmart-avlogger-sdk:6.7.0+'
为了确保智能体在您的 App 中可用,需要完成以下流程:
创建智能体。详细操作流程,请参考文档 智能体开发平台。
发布并投放智能体到指定 App。智能体配置完成后,单击页面顶部的 上架。
请记录智能体的 Agent ID,用于后续 SDK 中 创建 Session 使用。

在 智能体投放 页面,单击 投放 App 将智能体投放到指定 App,确认后,单击 确认投放信息并上架。
请记录小程序 ID,用于后续 SDK 中 创建 Session 使用。


 
 通道连接目前有两种连接类型,即:App、代理设备。
会话是建立在通道之上,一个通道内可以有多个会话 Session,数据流传输需要 Session。
当使用不同智能体、不同配置时,需要使用这些信息请求获取 Agent token 并创建会话。
在数据流传输之前,请先确保已经创建了会话 Session。
下面为您介绍以下概念:
Event)DataChannel)ReuseDataChannel)StreamFlag)Event)Event 可以是 App 向服务端发送,也可以是服务端投递给 App。
发送时,主要包含以下事件:
| 事件 | 说明 | 
|---|---|
| EventStart | 标记事件开始。在传递数据流之前,需要先发送该事件。需要传递一个 EventId,直到发送EventEnd、ChatBreak之前,都需要使用相同的EventId | 
| EventPayloadEnd | 标记 Event 内某一个数据传递完成,需要传递 DataChannel数据 | 
| EventEnd | 标记事件结束。在所有数据流传递完成后,发送该事件。服务端接受到该事件后会将所有数据投递给智能体进行处理 | 
| ChatBreak | 打断事件。既可以在发送数据流时打断,也可以在接受数据流时打断 | 
接收时,主要包含以下事件:
| 事件 | 说明 | 
|---|---|
| EventStart | 标记事件开始。表示云端即将开始向 App 投递数据,其携带 EventId与 App 发送时对应 | 
| EventPayloadEnd | 标记 Event 内某一个数据传递完成 | 
| EventEnd | 标记事件结束。在所有数据流投递完成后,投递该事件 | 
| ChatBreak | 打断事件。表示云端打断 App,App 不再需在这个 EventId上发送或接收数据(需配置开启) | 
| ServerVAD | 云端 VAD,即:云端识别到 App 发送的音频片段结束。接收到该事件后,App 无需再继续向该 EventId投递音频数据(即使投递云端也不再处理);需要配置开启 | 
通道支持多模态,可以在一次 Event 周期内投递多种类型数据流。具体的支持数据类型,详见智能体的配置。
DataChannel)在创建会话阶段获取 AgentToken 时中除了返回 Token 以外,还会返回  bizConfig 信息,它包含了 sendData、revData 两个属性,用于表明传递数据类型。
SDK 会根据 bizConfig.sendData 和 bizConfig.revData 生成数据通道  sendDataChannels 和recvDataChannels。
sendAudioData、sendVideoData 等),可以不用关心。
dataChannel 值,目前暂无该场景。
sendEventPayloadsEnd 方法时,必须传递 dataChannel。
ReuseDataChannel)在创建会话接口中有 reuseDataChannel 参数,通常情况下是传递 false,即:不启用复用。
用途主要针对实时流数据(比如:实时音频流、实时视频流)场景,当有两个或以上的会话需要对同一份实时数据做不同的处理的时候,就需要用到数据复用,对数据时效性以及传输开销友好。
StreamFlag)数据包传递的标记,用于标记某种数据流的传输起止,有以下四种类型:
OnlyOne(0):仅一包,通常发文本、小尺寸图片时可以使用。StreamStart(1):数据流开始,表示开始发送或开始接收。通常第一包数据需要携带数据的额外属性(如:音频流数据需要携带采样率、位深、格式等),此状态数据可以为空。Streaming(2):数据传输中。StreamEnd(3):数据传输结束,此状态数据可以为空。var aiStream: IThingAiStream = ThingAiStream.newInstance()
/**
 * Set stream listener
 *
 * @param listener stream listener
 */
fun setStreamListener(listener: ThingAiStreamListener)
/**
 * Remove stream listener
 */
fun unsetStreamListener()
使用样例:在连接之前、接收数据之前,需要先设置监听才能收到相关消息。
// 连接、创建 Session 之前请先设置监听
aiStream.setStreamListener(streamListener)
interface ThingAiStreamListener {
  /**
   * Monitors channel connection state changes
   *
   * @param connectionId The connection identifier
   * @param state The current connection state
   * @param errorCode Error code, if any
   */
  fun onConnectStateChanged(connectionId: String, state: Int, errorCode: Int)
  /**
   * Monitors session state changes
   *
   * @param sessionId The session identifier
   * @param state The current session state
   * @param errorCode Error code, if any
   */
  fun onSessionStateChanged(sessionId: String, state: Int, errorCode: Int)
}
/**
 * Interface for AI Stream event callbacks.
 * Used to monitor and handle various stream data types and connection states.
 */
interface ThingAiStreamListener {
    /**
     * Monitors event data reception
     *
     * @param event The received event data
     */
    fun onEventReceived(event: StreamEvent)
    /**
     * Monitors audio data reception
     * (When flag == start, the audio will be played automatically by the plugin)
     *
     * @param data The received audio data
     */
    fun onAudioReceived(data: StreamAudio)
    /**
     * Monitors video data reception
     * (When flag == start, video playback will begin until completion)
     *
     * @param data The received video data
     */
    fun onVideoReceived(data: StreamVideo)
    /**
     * Monitors image data reception
     *
     * @param data The received image data
     */
    fun onImageReceived(data: StreamImage)
    /**
     * Monitors text data reception
     *
     * @param data The received text data
     */
    fun onTextReceived(data: StreamText)
    /**
     * Monitors file data reception
     * (Currently not supported by the cloud)
     *
     * @param data The received file data
     */
    fun onFileReceived(data: StreamFile)
}
调用流程如下:
/**
 * Check if connected to stream service
 *
 * @param clientType client type: 1-as device agent, 2-as App
 * @return true if connected, false otherwise
 * @see com.thingclips.smart.android.aistream.Constants.ClientType
 */
fun isConnected(
    @ClientType clientType: Int,
    deviceId: String? = null
): Boolean
/**
 * Connect to stream service as App
 *
 * @param callback result callback
 */
fun connectWithApp(callback: ConnectCallback?)
/**
 * Connect to stream service as Device
 *
 * @param deviceId device ID
 * @param callback result callback
 */
fun connectWithDevice(deviceId: String, callback: ConnectCallback?)
/**
 * Connect to stream service
 *
 * @param clientType client type: 1-as device agent, 2-as App
 * @param deviceId device ID, required if acting as device agent, otherwise can be empty string
 * @param connectInfo connection info in JSON format
 * @param callback result callback
 */
fun connect(
    clientType: Int,
    deviceId: String?,
    connectInfo: String?,
    callback: ConnectCallback?
)
/**
 * Disconnect from stream service
 *
 * @return operation result code
 */
fun disconnect(): Int
一个 连接(Connection) 下最大可同时保持的 Session 数量为 20,请合理创建/关闭会话。
创建会话有两种方式:
方式 1 - 常规用法:先获取 Agent token info,再用其创建 Session 会话。
由于获取 Agent token info 有一定耗时,通过该方法可以前置获取,通常情况下有效期是 24 小时。
/**
* Request agent token
*
* @param requestParams  request parameters, you can see [AgentTokenRequestParams] for details, use [AgentTokenRequestParams.Builder] to build
* @param listener       result listener
* @see AgentTokenRequestParams
*/
fun requestAgentToken(
    requestParams: AgentTokenRequestParams, listener: ResultListener<ThingAgentTokenInfo>
)
/**
* Create session with agent token info
*
* @param bizTag business tag, 0 for default
* @param agentToken Agent token, @see [ThingAgentTokenInfo.agentToken]
* @param bizConfig business config, @see [ThingAgentTokenInfo.BizConfig]
* @param userDataAttributes user data attributes list
* @param callback result callback
* @see requestAgentToken
*/
fun createSession(
    bizTag: Long = 0,
    agentToken: ThingAgentTokenInfo,
    userDataAttributes: List<UserDataAttribute>?,
    reuseDataChannel: Boolean = false,
    callback: SessionCallback?
)
方式 2 - 简化用法:获取 Agent token info 并自动创建 Session 会话。
/**
* Create session with request agent token, it's combine requestAgentToken and createSession
*
* @param ownerId owner ID
* @param aiSolutionCode AI solution code
* @param userDataAttributes user data attributes list
* @param callback result callback
*/
fun createSession(
    requestParams: AgentTokenRequestParams,
    userDataAttributes: List<UserDataAttribute>?,
    callback: SessionCallback?
)
一个 Connection 下最大可同时保持的 Session 数量为 20,请在合适的时候关闭掉不必要的会话。
/**
 * Close session
 *
 * @param sessionId session ID
 * @param callback result callback
 */
fun closeSession(sessionId: String, callback: StreamResultCallback?)
Session 创建需要传 API 和相关参数,以下是调用示例和参数说明:
String api = "m.life.ai.token.get"; // API name
String apiVersion = "1.0"; // API version
String ownerId = "your_home_id"; // Home ID
String aiSolutionCode = "your_agent_id"; // Agent ID
AgentTokenRequestParams params = new AgentTokenRequestParams.Builder()
        .api(api)
        .apiVersion(apiVersion)
        .ownerId(ownerId) // Home ID
        .aiSolutionCode(aiSolutionCode) // Agent ID
        .addExtParam("miniProgramId", "your_mini_app_id") // Mini app ID
        .addExtParam("needTts", "false") // Enable tts, default value is false
        .addExtParam("onlyAsr", "false") // Only ASR or not, default value is false
        .build();
aiStream.createSession(params, null, new SessionCallback() {
    @Override
    public void onSuccess(@NonNull String sessionId, @NonNull Map<String, Integer> sendDataCodes, @NonNull Map<String, Integer> revDataCodes) {
        Log.i(TAG, "createSession onSuccess: " + sessionId);
    }
    @Override
    public void onError(int code, String message) {
        Log.e(TAG, "createSession onError: " + code + ", message: " + message);
        runOnUiThread(() -> Toast.makeText(AiChatActivity.this, "Session creation failed: " + message, Toast.LENGTH_SHORT).show());
    }
});
可发送的 Event 分为:
EventStart:一轮对话开始的标志。可以指定 Event ID,也可以由 SDK 自动生成 Event ID。EventPayloadEnd:告知云端这次对话中某一个数据流传输完成。EventEnd:一轮对话结束。发送后 AI 会处理所有传输的数据,并进行回复。ChatBreak:打断某个对话。可以在发送时打断,也可以在接收时打断。
/**
 * Send event start signal with VAD and interrupt options
 *
 * @param options event start options
 * @param callback result callback
 * @see EventStartOptions
 */
fun sendEventStart(
    options: EventStartOptions,
    callback: EventStartCallback
)
/**
 * Send event payloads end signal
 *
 * @param eventId event ID
 * @param sessionId session ID
 * @param dataChannel data channel
 * @param userDataAttributes user data attributes list
 * @param callback result callback
 */
fun sendEventPayloadsEnd(
    eventId: String, sessionId: String, dataChannel: String,
    userDataAttributes: List<UserDataAttribute>?,
    callback: StreamResultCallback?
)
/**
 * Send event end signal
 *
 * @param eventId event ID
 * @param sessionId session ID
 * @param userDataAttributes user data attributes list
 * @param callback result callback
 */
fun sendEventEnd(
    eventId: String, sessionId: String, userDataAttributes: List<UserDataAttribute>?,
    callback: StreamResultCallback?
)
fun sendEventChatBreak(
    eventId: String, sessionId: String, userDataAttributes: List<UserDataAttribute>?,
    callback: StreamResultCallback?
)
/**
 * Send audio data
 *
 * @param data audio data
 * @param callback result callback
 */
fun sendAudioData(sessionId: String, data: StreamAudio, callback: StreamResultCallback?)
/**
 * Send video data
 *
 * @param data video data
 * @param callback result callback
 */
fun sendVideoData(sessionId: String, data: StreamVideo, callback: StreamResultCallback?)
/**
 * Send text data
 *
 * @param data text data
 * @param dataChannel customized data channel (optional), using default "text" if not specified
 * @param callback result callback
 */
fun sendTextData(sessionId: String, data: StreamText, callback: StreamResultCallback?)
/**
 * Send image data
 *
 * @param data image data
 * @param callback result callback
 */
fun sendImageData(sessionId: String, data: StreamImage, callback: StreamResultCallback?)
/**
 * Send file data
 *
 * @param data file data
 * @param dataChannel customized data channel (optional), using default "file" if not specified
 * @param callback result callback
 */
fun sendFileData(sessionId: String, data: StreamFile, callback: StreamResultCallback?)
其中音频数据 Android 支持的是实时录制的音频流,可以使用涂鸦封装的 “音频流录制 + 发送” 方法:
/**
 * Initializes the audio recorder.
 *
 * This method is optional and not required before starting recording. It should be called only when
 * you want to prepare the recorder in advance before actual recording starts.
 * Initializing the audio recorder is a relatively time-consuming operation (about 300ms).
 * Calling this method in advance can eliminate the delay when starting to record.
 * You can call this method while showing a loading state, so that when the user actually starts
 * recording, it can respond immediately.
 * If not called in advance, the startRecordAndSendAudioData method will automatically complete
 * the initialization internally, but this will cause a noticeable delay before recording actually begins.
 *
 * @param callback Operation result callback, reporting initialization success or failure
 */
fun initAudioRecorder(callback: StreamResultCallback?)
/**
 * Starts recording audio and sending data, without triggering events.
 *
 * This method will start recording and transmitting audio data. If initAudioRecorder has not been
 * called for initialization previously, this method will first complete the initialization of the
 * recorder (about 300ms) before starting to record.
 * For better user experience, it is recommended to call initAudioRecorder to complete
 * initialization before displaying the recording button in the UI.
 *
 * @param sessionId Session ID, used to identify the session to which the current audio transmission belongs
 * @param dataChannel Custom data channel (optional), if not specified, the default "audio" channel will be used
 * @param userDataAttributes List of user data attributes, which may contain metadata related to the audio
 * @param callback Operation result callback, reporting whether recording started successfully or failed
 */
fun startRecordAndSendAudioData(
    sessionId: String,
    dataChannel: String? = null,
    userDataAttributes: List<UserDataAttribute>?,
    callback: StreamResultCallback?
)
/**
 * Stop recording audio and sending data, without ending event
 *
 * @param dataChannel customized data channel (optional), using default "audio" if not specified
 * @param userData user data attributes list
 * @param callback result callback
 */
fun stopRecordAndSendAudioData(
    sessionId: String,
    dataChannel: String? = null,
    userData: List<UserDataAttribute>?,
    callback: StreamResultCallback?
)
一次完整的文本发送示例如下:
/**
 * 发送文本消息到 AI 服务
 * @param textMessage 要发送的文本内容
 */
fun sendTextMessage(textMessage: String) {
    // Check if session is valid
    if (TextUtils.isEmpty(currentSessionId) || !aiStream.isConnected(Constants.ClientType.APP, null)) {
        Log.e(TAG, "Cannot send message: session not initialized or connection lost")
        return
    }
    
    // Step 1: Start the event
    val eventStartOptions = EventStartOptions.Builder(currentSessionId).build()
    aiStream.sendEventStart(eventStartOptions, object : EventStartCallback {
        override fun onSuccess(eventId: String) {
            Log.i(TAG, "Event started successfully: $eventId")
            
            // Step 2: Send text data
            val streamText = StreamText().apply {
                text = textMessage
            }
            
            aiStream.sendTextData(currentSessionId, streamText, object : StreamResultCallback {
                override fun onSuccess() {
                    Log.i(TAG, "Text data sent successfully")
                    
                    // Step 3: End text payload
                    aiStream.sendEventPayloadsEnd(eventId, currentSessionId,
                            ThingAiStream.DATA_CHANNEL_TEXT, null, object : StreamResultCallback {
                        override fun onSuccess() {
                            Log.i(TAG, "Text payload ended successfully")
                            
                            // Step 4: End the event
                            aiStream.sendEventEnd(eventId, currentSessionId, null, object : StreamResultCallback {
                                override fun onSuccess() {
                                    Log.i(TAG, "Event ended successfully: $eventId")
                                }
                                
                                override fun onError(errorCode: Int, errorMessage: String) {
                                    Log.e(TAG, "Failed to end event: $errorMessage, code: $errorCode")
                                }
                            })
                        }
                        
                        override fun onError(errorCode: Int, errorMessage: String) {
                            Log.e(TAG, "Failed to end text payload: $errorMessage, code: $errorCode")
                            
                            // Still try to end the event even if payload end fails
                            aiStream.sendEventEnd(eventId, currentSessionId, null, object : StreamResultCallback {
                                override fun onSuccess() {
                                    Log.i(TAG, "Event ended successfully after payload error: $eventId")
                                }
                                
                                override fun onError(code: Int, message: String) {
                                    Log.e(TAG, "Failed to end event after payload error: $message, code: $code")
                                }
                            })
                        }
                    })
                }
                
                override fun onError(errorCode: Int, errorMessage: String) {
                    Log.e(TAG, "Failed to send text data: $errorMessage, code: $errorCode")
                    
                    // End the event if text data sending fails
                    aiStream.sendEventEnd(eventId, currentSessionId, null, object : StreamResultCallback {
                        override fun onSuccess() {
                            Log.i(TAG, "Event ended successfully after text send error: $eventId")
                        }
                        
                        override fun onError(code: Int, message: String) {
                            Log.e(TAG, "Failed to end event after text send error: $message, code: $code")
                        }
                    })
                }
            })
        }
        
        override fun onError(errorCode: Int, errorMessage: String) {
            Log.e(TAG, "Failed to start event: $errorMessage, code: $errorCode")
        }
    })
}
如果有多种数据发送,示例流程如下:
您可以前往 homesdk_sample 获取 Demo 示例。
请先阅读上文并完成 前置准备。
使用 Android Studio 打开工程后,将生成一个 local.properties 文件,在 local.properties 文件中添加配置:
appKey=your_app_key
appSecret=your_app_secret
aiSolutionCode=your_agent_id
miniProgramId=your_mini_program_id
运行 Demo。登录后,前往 AI Assistant 页面使用。
| code | msg | 含义 | 备注 | 
|---|---|---|---|
| 39001 | / | 通用错误 | 用于一些杂项错误 | 
| 39002 | 
 | 参数不合法 | 
 | 
| 39003 | 云端错误文案 | http 请求失败 | Agent token 获取失败:请求 agent token云端接口返回失败 | 
| 39004 | not connect | Connection 未连接 | 创建/关闭 Session、发送 Data、Event 时 | 
| 39005 | session is invalid | SessionId 不存在 | 关闭 Session、发送 Data、Event 时 | 
| 39006 | eventId is invalid | EventId 为空 | 发送 Event 时 | 
| 39007 | dataChannel is invalid | DataChannel 不合法 | 发送 Data、 PayloadEnd时 | 
| 39008 | packet is invalid | 数据包不合法 | 发 Data 时,比如:首包/仅一包数据时需要固定的参数,文本内容为空 | 
| 39009 | 
 | 文件数据读取异常 | 
 | 
| 39010 | 
 | 发送数据失败 | Socket 发送数据失败,内含 Socket 错误信息 | 
| 39012 | 
 | Connection 被远端关闭 | 
 | 
下文详细说明了 AI 聊天组件中处理的各种 JSON 格式及其处理逻辑。
当用户发送语音输入后,系统会返回自动语音识别(Automatic Speech Recognition,ASR)结果,格式如下:
{
    "bizId": "asr-1754380053514",
    "bizType": "ASR",
    "eof": 0,
    "data": {
        "text": "what's the weather today?"
    }
}
| 字段 | 字段含义 | 字段类型 | 示例值 | 
|---|---|---|---|
| bizId | 业务 ID,用于标识一次交互 | String | asr-1754380053514 | 
| bizType | 业务类型,ASR 表示语音识别结果 | String | ASR | 
| eof | 结束标志, 0表示未结束,1表示已结束 | Int | 0 或 1 | 
| data | 返回的 ASR 结果对象 | Object | - | 
| text | 识别出的文本内容 | String | what’s the weather today? | 
eof = 0 时,保存临时结果,显示中间识别结果。eof = 1 时,显示最终识别结果并添加到聊天列表中。AI 模型响应用户查询后,返回的数据格式:
{
    "bizId": "nlg-1754380053514",
    "bizType": "NLG",
    "eof": 0,
    "data": {
        "appendMode": "append",
        "reasoningContent": "思考过程内容",
        "content": "模型回复的文本内容",
        "images": [
            {
                "url": "https://www.tuya.com/image1.jpg"
            }
        ]
    }
}
| 字段 | 字段含义 | 字段类型 | 示例值 | 
|---|---|---|---|
| bizId | 业务 ID,用于关联同一次交互的消息 | string | nlg-1754380053514 | 
| bizType | 业务类型,NLG 表示自然语言生成 | string | NLG | 
| eof | 结束标志, 0表示流式输出中,1表示结束 | int | 0 或 1 | 
| data | 返回的数据对象 | object | - | 
| appendMode | 文本追加模式 | string | append(追加)或其他 | 
| reasoningContent | 大模型思考过程(思维链) | string | 思考过程内容 | 
| content | 大模型生成的实际响应内容 | string | 模型回复的文本内容 | 
| images | 大模型返回的图片数据数组 | array | - | 
| url | 图片的网址 | string | https://www.tuya.com/image1.jpg | 
appendMode 为 append 时,将新内容追加到已有消息,否则创建新消息。eof = 1 时,标记该 NLG 流结束。AI 可调用各种技能,如表情展示等功能。
这里仅举例说明表情类型的技能,其他技能根据智能体的定义 data 有所不同。
{
    "bizId": "skill-1754380053514",
    "bizType": "SKILL",
    "eof": 0,
    "data": {
        "code": "llm_emo",
        "skillContent": {
            "text": "😀",
            "startTime": 1000,
            "endTime": 2000,
            "sequence": 1
        }
    }
}
| 字段 | 字段含义 | 字段类型 | 示例值 | 
|---|---|---|---|
| bizId | 业务 ID | string | skill-1754380053514 | 
| bizType | 业务类型, SKILL表示技能调用 | string | SKILL | 
| eof | 结束标志 | int | 0 或 1 | 
| data | 返回数据 | object | - | 
| code | 技能代码 | string | llm_emo | 
| skillContent | 技能内容 | object | - | 
| text | 表情符号 | string | 😀 | 
| startTime | 表情显示开始时间(毫秒) | long | 1000 | 
| endTime | 表情显示结束时间(毫秒) | long | 2000 | 
| sequence | 序列号, 1表示序列开始 | int | 1 | 
sequence = 1 时,清空现有表情步骤列表。eof = 1 时,开始按时间顺序展示表情。系统支持以下表情类型:
| 情绪类型 | Unicode 表情 | 显示效果 | 
|---|---|---|
| SAD | \uD83D\uDE22 | 😢 | 
| ANGRY | \uD83D\uDE20 | 😠 | 
| NEUTRAL | \uD83D\uDE10 | 😐 | 
| FEARFUL | \uD83D\uDE28 | 😨 | 
| SURPRISE | \uD83D\uDE32 | 😲 | 
| CONFUSED | \uD83D\uDE15 | 😕 | 
| DISAPPOINTED | \uD83D\uDE1E | 😞 | 
| ANNOYED | \uD83D\uDE21 | 😡 | 
| THINKING | \uD83E\uDD14 | 🤔 | 
| HAPPY | \uD83D\uDE00 | 😀 | 
该内容对您有帮助吗?
是意见反馈该内容对您有帮助吗?
是意见反馈