更新时间:2025-09-16 05:53:30下载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 使用。
注意,小程序 ID 需要使用 v2.0 Beta 小程序,需要点击“更新版本”使用 v2.0 以上小程序,以便启用新版本的智能体。
通道连接目前有两种连接类型,即: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)
}
接收事件Event、接收数据流消息
/**
* 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,请合理创建/关闭会话。
创建会话
创建会话有两种方式:
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?
)
/**
* 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 创建示例
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 is false
.addExtParam("onlyAsr", "false") // only ASR or not, default 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
:一轮对话开始的标志,可以指定 EventId、也可以由 SDK 自动生成 EventIdEventPayloadEnd
:告知云端这次对话中某一个数据流传输完成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")
}
})
}
如果有多种数据发送,类似如下流程:
您可以访问如下地址获取 Demo:
使用说明
appKey=your_app_key
appSecret=your_app_secret
aiSolutionCode=your_agent_id
miniProgramId=your_mini_program_id
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 |
|
文件数据读取异常 | 发送 Data 时,如:图片、文件文件不存在,或读取过程中出现异常TTT:播放音频所需文件不存在(startPlayAudio ) |
39010 |
|
发送数据失败 | Socket 发送数据失败,内含 Socket 错误信息 |
39012 |
|
Connection 被远端关闭 | 详细错误对应云端错误码:200、400、401、404、408、500、504、601、602、603、604、605对 TTT 来说没有 39012,是通过 onConnectStateChanged 报的详细错误码 200、400… |
本文档详细说明了 AI 聊天组件中处理的各种 JSON 格式及其处理逻辑。
当用户发送语音输入后,系统会返回 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 | 😀 |
该内容对您有帮助吗?
是意见反馈该内容对您有帮助吗?
是意见反馈