第14篇:AI 智能体对话------Coze API 集成
引言
柚兔学伴的核心亮点在于将 AI 智能体融入英语学习场景。用户可以与 AI 进行口语对话、查询汉字释义、生成每日诗词------这些能力均来自字节跳动 Coze 平台的智能体 API。本篇将深入剖析项目如何集成 Coze API,实现完整的 AI 对话流程,包括会话创建、消息发送、轮询检索、结果解析等关键环节。

整体对话流程
与 Coze 智能体的交互遵循一个严格的四步流程:
conversationCreate → getOnlineInfo → chat → retrieve(轮询)→ messageList
- 创建会话 :调用
conversationCreate获取conversation_id - 获取智能体信息 :调用
getOnlineInfo确认智能体在线状态与配置 - 发送消息 :调用
chat将用户消息发送给指定智能体 - 轮询检索 :由于 AI 生成需要时间,需通过
retrieve轮询查看生成状态 - 获取消息列表 :状态为
completed后,调用messageList获取完整回复
Model 层设计
项目为每个 AI 场景设计了独立的 Model 类,统一遵循单例模式和相同的 API 调用模式。
ChatModel(口语对话)
typescript
// feature/chat/src/main/ets/model/ChatModel.ets
export class ChatModel {
loadingStatus: LoadingStatus = LoadingStatus.OFF;
msgStatus: LoadingStatus = LoadingStatus.OFF;
private static instance: ChatModel;
agentInfo: AgentInfoData = new AgentInfoData()
retrieveData: RetrieveData = new RetrieveData()
msgList: MsgData[] = []
conversation: ConversationData = new ConversationData()
response: ChatResponseData = new ChatResponseData()
public static getInstance(): ChatModel {
if (!ChatModel.instance) {
ChatModel.instance = new ChatModel();
}
return ChatModel.instance;
}
}
StrokeModel(汉字字典)
typescript
// feature/stroke/src/main/ets/model/StrokeModel.ets
export class StrokeModel {
loadingStatus: LoadingStatus = LoadingStatus.OFF;
private static instance: StrokeModel;
wordData: WordData = new WordData()
conversation: ConversationData = new ConversationData()
response: ChatResponseData = new ChatResponseData()
retrieveData: RetrieveData = new RetrieveData()
msgList: MsgData[] = []
msgStatus: LoadingStatus = LoadingStatus.OFF;
public static getInstance(): StrokeModel {
if (!StrokeModel.instance) {
StrokeModel.instance = new StrokeModel();
}
return StrokeModel.instance;
}
}
PoemModel(每日诗词)
typescript
// feature/todo/src/main/ets/model/PoemModel.ets
export class PoemModel {
loadingStatus: LoadingStatus = LoadingStatus.OFF;
private static instance: PoemModel;
conversation: ConversationData = new ConversationData()
response: ChatResponseData = new ChatResponseData()
retrieveData: RetrieveData = new RetrieveData()
msgList: MsgData[] = []
msgStatus: LoadingStatus = LoadingStatus.OFF;
public static getInstance(): PoemModel {
if (!PoemModel.instance) {
PoemModel.instance = new PoemModel();
}
return PoemModel.instance;
}
}
三个 Model 的核心方法签名完全一致:conversationCreate、getOnlineInfo、chat、retrieve、messageList,区别仅在于关联的 Agent ID 不同。
LoadingStatus 枚举
所有 Model 使用统一的加载状态枚举:
typescript
// network/src/main/ets/common/UrlConstants.ets
export enum LoadingStatus {
OFF = 'off',
LOADING = 'loading',
SUCCESS = 'success',
FAILED = 'failed'
}
- OFF:初始状态,尚未发起请求
- LOADING:请求已发出,等待响应
- SUCCESS:请求成功,数据已就绪
- FAILED:请求失败,需提示用户
第一步:创建会话
typescript
async conversationCreate(uuid: string, role: string, content?: string): Promise<string> {
let idInfo: MetaData = {
uuid: uuid
}
let msgList: Msg[] = []
let param: CreatParam = {
meta_data: idInfo,
messages: msgList
}
this.loadingStatus = LoadingStatus.LOADING
return HttpManager.getInstance()
.requestPostBody<ConversationData>({
url: UrlConstants.CONVERSATION_CREATE_URL,
postBody: param
})
.then((result) => {
this.conversation = result
this.loadingStatus = LoadingStatus.SUCCESS
return this.loadingStatus
})
.catch((err: BusinessError) => {
this.loadingStatus = LoadingStatus.FAILED
return this.loadingStatus
});
}
conversationCreate 通过 requestPostBody 向 Coze 的 v1/conversation/create 接口发送 POST 请求,携带用户 ID 作为 meta_data,返回的 ConversationData 包含后续对话所需的 conversation_id。
第二步:获取智能体信息
typescript
async getOnlineInfo(bot_id: string): Promise<void> {
this.loadingStatus = LoadingStatus.LOADING
return new Promise((resolve, reject) => {
HttpManager.getInstance()
.requestGet<AgentInfoData>({
url: UrlConstants.GET_ONLINE_INFO_URL,
queryParams: {
'bot_id': bot_id,
}
})
.then((result) => {
this.loadingStatus = LoadingStatus.SUCCESS
this.agentInfo = result
resolve()
})
.catch((err: BusinessError) => {
this.loadingStatus = LoadingStatus.FAILED
reject(err)
});
});
}
此步确认智能体可用,并获取智能体的配置信息(如开场白 onboarding_info.prologue),在 ChatPage 中用于自动展示 AI 的自我介绍。
第三步:发送消息
typescript
async chat(conversationId: string, uuid: string, role: string, content: string,
agentId: string, bot_name: string): Promise<string> {
let msgList: Msg[] = []
let msg: Msg = {
role: role,
content: content,
content_type: 'text'
}
msgList.push(msg)
let param: ChatParam = {
bot_id: agentId,
user_id: uuid,
stream: false,
auto_save_history: true,
additional_messages: msgList
}
let variables: CustomVariables = {
bot_name: bot_name,
}
param.custom_variables = variables
this.loadingStatus = LoadingStatus.LOADING
return HttpManager.getInstance()
.requestPostBody<ChatResponseData>({
url: UrlConstants.SERVER + UrlConstants.CHAT_URL + `?conversation_id=${conversationId}`,
postBody: param
})
.then((result) => {
this.response = result
this.loadingStatus = LoadingStatus.SUCCESS
return this.loadingStatus
})
.catch((err: BusinessError) => {
this.loadingStatus = LoadingStatus.FAILED
return this.loadingStatus
});
}
关键参数说明:
stream: false:非流式返回,等待完整响应auto_save_history: true:自动保存对话历史custom_variables.bot_name:传入音色名称,影响 AI 说话风格- URL 中拼接
conversation_id关联已有会话
第四步:轮询检索机制
由于 AI 生成回复需要时间,Coze 采用异步模式------chat 接口立即返回 chat_id,客户端需轮询 retrieve 接口检查生成状态:
typescript
// ChatPage.ets 中的轮询逻辑
private sendMsgToAgent(content: string, name: string) {
this.chatModel.chat(this.chatModel.conversation.id!!, this.uid, Role.USER, content,
UrlConstants.COZE_AGENT_ID, name).then((status) => {
if (status === LoadingStatus.SUCCESS) {
let chatData = new ChatData('', '', Role.ASSISTANT, 'text', 'loading')
this.internalId = setInterval(() => {
this.retrieve(chatData);
}, 1100)
}
})
}
轮询间隔为 1100ms ,通过 setInterval 持续调用 retrieve 方法,直到状态变更。
retrieve 状态判断
typescript
private retrieve(chatData: ChatData) {
this.chatModel.retrieve(this.chatModel.response.conversation_id!!, this.chatModel.response.id!!)
.then(() => {
if (this.chatModel.retrieveData.status === 'in_progress') {
// 继续轮询,不做任何操作
} else if (this.chatModel.retrieveData.status === 'failed') {
ToastUtil.showToast('服务器繁忙,请重试')
clearInterval(this.internalId)
} else if (this.chatModel.retrieveData.status === 'completed') {
this.chatList.push(chatData)
this.listScroller.scrollEdge(Edge.Bottom);
clearInterval(this.internalId)
this.chatModel.messageList(this.chatModel.response.conversation_id!!, this.chatModel.response.id!!)
.then(() => {
if (this.chatModel.msgStatus === LoadingStatus.SUCCESS) {
this.chatModel.msgList.forEach(async (item) => {
if (item.type! === 'answer') {
this.chatList[this.chatList.length - 1].status = 'success'
this.chatList[this.chatList.length - 1].content = item.content!!
}
})
}
})
}
});
}
三种状态的处理策略:
| 状态 | 处理 |
|---|---|
in_progress |
继续轮询(setInterval 不清除) |
failed |
停止轮询 + Toast 提示 |
completed |
停止轮询 + 获取消息列表 |
第五步:解析消息列表
messageList 返回的 MsgData[] 包含多种类型的消息,项目只关注 type === 'answer' 的条目:
typescript
this.chatModel.msgList.forEach(async (item) => {
if (item.type! === 'answer') {
this.chatList[this.chatList.length - 1].content = item.content!!
}
})
其他类型如 verbose(调试信息)、follow_up(追问建议)被过滤掉,保证用户只看到 AI 的正式回复。
智能体 ID 管理
项目通过常量类集中管理不同场景的智能体 ID:
typescript
// network/src/main/ets/common/UrlConstants.ets
static readonly COZE_AGENT_ID: string = '7441129457244700706' // 英语口语
static readonly COZE_HELP_AGENT_ID: string = '7443799767710253107' // 提示助手
// common/src/main/ets/constant/AgentConstant.ets
export class AgentConstant {
static readonly COZE_AGENT_ID: string = '7441129457244700706' // 英语口语
static readonly WORD_DIC_ID: string = '7559203607804264488' // 汉字字典
static readonly DAILY_POEM_ID: string = '7562472466233835560' // 每日诗词
}
双智能体模式
ChatPage 采用独特的"双智能体"架构------主智能体负责对话,帮助智能体提供学习提示:
typescript
// 主智能体:英语口语对话
private sendMsgToAgent(content: string, name: string) {
this.chatModel.chat(this.chatModel.conversation.id!!, this.uid, Role.USER, content,
UrlConstants.COZE_AGENT_ID, name)
// ...
}
// 帮助智能体:提供单词/语法提示
private sendMsgToHelpAgent(content: string, name: string) {
this.chatModel.chat(this.chatModel.conversation.id!!, this.uid, Role.USER, content,
UrlConstants.COZE_HELP_AGENT_ID, name)
// ...
}
当主智能体回复后,其内容自动作为帮助智能体的输入,帮助智能体会生成学习提示(如单词解释、语法点),以 Role.TIPS 类型显示在对话中。
AI 生成诗词:结构化输出与持久化
PoemPage 展示了 Coze 智能体结构化输出的能力。诗词智能体被配置为返回 JSON 格式数据:
typescript
// PoemPage.ets
private retrieve() {
this.poemModel.retrieve(this.poemModel.response.conversation_id!!, this.poemModel.response.id!!)
.then(() => {
if (this.poemModel.retrieveData.status === 'completed') {
clearInterval(this.internalId)
this.poemModel.messageList(/*...*/).then(() => {
this.poemModel.msgList.forEach(async (item) => {
if (item.type! === 'answer') {
this.poemInfo = JSON.parse(item.content!!)
const poemDB = PoemDatabase.getInstance(GlobalUIAbilityContext.getContext());
await poemDB.addPoem({
date: DateUtil.getTodayStr('yyyy-MM-dd'),
poem: this.poemInfo?.poem ?? "",
author: this.poemInfo?.author ?? "",
meaning: this.poemInfo?.meaning ?? "",
story: this.poemInfo?.story ?? "",
praise: this.poemInfo?.praise ?? "",
isFavorite: false,
});
}
})
})
}
});
}
关键流程:
- AI 返回的
item.content是 JSON 字符串,包含poem、author、meaning、story、praise字段 - 通过
JSON.parse解析为PoemItem对象 - 使用
PoemDatabase单例将诗词持久化到 SQLite 数据库 - 后续可通过日期查询、收藏、搜索等操作访问历史诗词
语音识别流程
ChatPage 还集成了语音识别,让用户可以通过录音与 AI 对话:
typescript
// ChatPage.ets
RecordUtils.getInstance().stopRecordingProcess().then((voicePath: string) => {
this.chatModel.uploadFile(voicePath, (downloadUrl: string) => {
let uuid = util.generateRandomUUID()
this.chatModel.voiceRecognition(uuid, this.uid, downloadUrl).then((status) => {
if (status === LoadingStatus.SUCCESS) {
this.speechId = setInterval(() => {
this.speechQuery(uuid);
}, 1100)
}
})
})
})
语音识别同样是异步轮询模式:
- 录音 → 上传文件到云存储 → 获取下载 URL
- 调用
voiceRecognition提交识别请求 - 轮询
speechQuery直到识别完成 - 解析
SpeechData获取文本,作为用户消息发送给 AI
typescript
private speechQuery(uuid: string) {
this.chatModel.voiceQuery(uuid).then((data) => {
clearInterval(this.speechId)
let speechData = JSON.parse(data.toString()) as SpeechData
let uerSpeechText = speechData.result?.text!!
this.chatList.push(new ChatData(uerSpeechText, '', Role.USER, 'text', 'success'))
this.sendMsgToAgent(uerSpeechText, this.name);
}).catch((err: BusinessError) => {
clearInterval(this.speechId)
ToastUtil.showToast('没听清楚,请再说一次吧')
});
}
页面初始化流程
ChatPage 在 onReady 回调中完成完整的初始化链:
typescript
.onReady(async (cxt: NavDestinationContext) => {
const data = cxt.pathInfo.param as Record<string, string | number | boolean>;
this.uid = await UserInfoManager.getUserInfo()?.unionID ?? ''
this.waitingUrl = data.waitingGif as string
this.speakingUrl = data.speakingGif as string
if (data.gender === Gender.GIRL) {
this.timbre = 'BV421_streaming'
this.name = 'Elsa'
} else {
this.timbre = 'BV061_streaming'
this.name = 'Daniel'
}
this.chatModel.conversationCreate(this.uid, Role.ASSISTANT).then((status) => {
if (status === LoadingStatus.SUCCESS) {
this.chatModel.getOnlineInfo(UrlConstants.COZE_AGENT_ID).then(() => {
this.sendMsgToAgent('Introduce yourself.', this.name)
})
}
})
})
初始化顺序:获取用户 ID → 设置音色 → 创建会话 → 获取智能体信息 → 自动发送介绍消息。这种链式调用确保了每个步骤在前一步成功后才执行。
小结
本篇详细介绍了柚兔学伴项目中 Coze API 的集成方案:
- 统一流程 :所有 AI 场景遵循
conversationCreate → getOnlineInfo → chat → retrieve → messageList五步流程 - 轮询机制 :1100ms 间隔轮询,根据
in_progress/failed/completed三种状态决定继续、停止或获取结果 - Model 单例:ChatModel、StrokeModel、PoemModel 均采用单例模式,保持会话状态一致性
- 双智能体:主智能体对话 + 帮助智能体提示,增强学习体验
- 结构化输出 :诗词智能体返回 JSON,经
JSON.parse后持久化到本地数据库 - 语音识别:录音 → 上传 → 识别 → 轮询,同样采用异步轮询模式