更新时间: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 | 😀 |
该内容对您有帮助吗?
是意见反馈该内容对您有帮助吗?
是意见反馈