第14篇:AI 智能体对话——Coze API 集成

引言

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

整体对话流程

与 Coze 智能体的交互遵循一个严格的四步流程:

conversationCreate → getOnlineInfo → chat → retrieve(轮询)→ messageList
  1. 创建会话:调用 conversationCreate 获取 conversation_id
  2. 获取智能体信息:调用 getOnlineInfo 确认智能体在线状态与配置
  3. 发送消息:调用 chat 将用户消息发送给指定智能体
  4. 轮询检索:由于 AI 生成需要时间,需通过 retrieve 轮询查看生成状态
  5. 获取消息列表:状态为 completed 后,调用 messageList 获取完整回复

Model 层设计

项目为每个 AI 场景设计了独立的 Model 类,统一遵循单例模式和相同的 API 调用模式。

ChatModel(口语对话)

// 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(汉字字典)

// 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(每日诗词)

// 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 的核心方法签名完全一致:conversationCreategetOnlineInfochatretrievemessageList,区别仅在于关联的 Agent ID 不同。

LoadingStatus 枚举

所有 Model 使用统一的加载状态枚举:

// network/src/main/ets/common/UrlConstants.ets
export enum LoadingStatus {
  OFF = 'off',
  LOADING = 'loading',
  SUCCESS = 'success',
  FAILED = 'failed'
}
  • OFF:初始状态,尚未发起请求
  • LOADING:请求已发出,等待响应
  • SUCCESS:请求成功,数据已就绪
  • FAILED:请求失败,需提示用户

第一步:创建会话

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

第二步:获取智能体信息

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 的自我介绍。

第三步:发送消息

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 接口检查生成状态:

// 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 状态判断

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' 的条目:

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:

// 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 采用独特的"双智能体"架构——主智能体负责对话,帮助智能体提供学习提示:

// 主智能体:英语口语对话
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 格式数据:

// 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,
              });
            }
          })
        })
      }
    });
}

关键流程:

  1. AI 返回的 item.content 是 JSON 字符串,包含 poemauthormeaningstorypraise 字段
  2. 通过 JSON.parse 解析为 PoemItem 对象
  3. 使用 PoemDatabase 单例将诗词持久化到 SQLite 数据库
  4. 后续可通过日期查询、收藏、搜索等操作访问历史诗词

语音识别流程

ChatPage 还集成了语音识别,让用户可以通过录音与 AI 对话:

// 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)
      }
    })
  })
})

语音识别同样是异步轮询模式:

  1. 录音 → 上传文件到云存储 → 获取下载 URL
  2. 调用 voiceRecognition 提交识别请求
  3. 轮询 speechQuery 直到识别完成
  4. 解析 SpeechData 获取文本,作为用户消息发送给 AI
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 回调中完成完整的初始化链:

.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 后持久化到本地数据库
  • 语音识别:录音 → 上传 → 识别 → 轮询,同样采用异步轮询模式
Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐