☰
ESP32接入扣子Coze API实战:让物联网开发板开口说话
2026/10/3 5:26:34 网站建设 项目流程

让ESP32开口说话:接入扣子(Coze) API调用自定义智能体实战

先说一下这个项目的价值。ESP32是物联网开发里最常见的Wi-Fi/蓝牙芯片,说它是创客圈的“万金油”一点不过分;Coze(扣子)是字节跳动推出的智能体搭建平台,你可以在上面拖拽式创建自己的Bot、配置人设、挂工作流,然后通过API把智能体能力开放出去。把这两者接起来,意味着你可以让一块几十块钱的开发板,直接具备“AI大脑”——比如做一个能听懂语音指令的桌面助手、一个采集传感器数据后自动给出建议的环境监测站,或者一个离线也能响应基础指令的智能家居中控。这个路子通了之后,所有“硬件+大模型”的想象空间都能落地。

我强烈建议有Arduino基础、想玩硬件AI联动的朋友认真看这篇文章。它不会教你写复杂的算法,而是给出一条最直接、可复现的路径:ESP32通过HTTP请求调用Coze API,把自定义智能体的回复拿回本地。整个项目的核心代码量不大,难点其实在于API鉴权、请求体构造、JSON解析这三件事。我踩过不少坑,接下来把这些细节全部摊开讲。

1. ESP32接入Coze的整体思路与方案选型

1.1 为什么选择Coze API而不是直接在设备上跑模型

先说一个很多人会问的问题:ESP32算力这么弱,为什么不直接在板子上跑一个AI模型,非要绕一圈走API?

我实测下来的结论是:ESP32的CPU主频通常在240MHz左右,内存也就320KB到520KB RAM,跑一个稍微像样的深度学习模型非常吃力。哪怕是最轻量的TinyML方案,也只能处理关键词唤醒、简单的分类任务,距离“智能体”级别的对话、推理和工具调用差得太远。当前主流大模型动辄几十亿参数,根本没法塞进Flash里。

所以“端侧感知+云端智能”才是合理分工。ESP32负责感知物理世界(读传感器、收按键、控制LED),把数据通过Wi-Fi传到云端,Coze平台上的智能体负责理解意图、生成回复、甚至触发工作流调用外部工具。这个架构的好处很明显:设备端代码简单稳定,云端能力可以随时升级,换一个更强的模型或者改智能体人设不需要重新烧录固件。

顺便提一句,Coze平台本身也允许你在工作流里接入数据库、搜索、图像生成等插件,等于说ESP32通过一个API获得的不只是“聊天”,而是整个智能体的完整能力链。

1.2 整体架构和数据流向

这个项目的架构非常清晰,一共就三个环节:

ESP32(HTTP客户端) → Coze API网关 → 自定义智能体(Bot)

数据流向是这样走的:

  1. ESP32 采集或构造一条用户消息(比如按键触发、温湿度数据、串口输入的文字)。
  2. 用HTTP POST方式把消息发送到Coze的Chat API地址,请求体是JSON格式。
  3. Coze平台校验Token和Bot ID后,把消息交给配置好的智能体处理。
  4. 智能体返回JSON响应,ESP32用ArduinoJson库解析,取出其中的回答文本。
  5. 设备端根据文本内容执行动作,比如显示到OLED屏幕、通过扬声器播报,或者解析出指令去控制继电器。

在选型环节,我用的是Arduino框架 + ESP32开发板,因为Arduino生态对新手最友好,库齐全,示例代码多。如果你更习惯ESP-IDF,思路完全一样,只是HTTP客户端的写法略有差异。

1.3 方案选型时需要想清楚的几件事

动手之前,建议先规划好三件事,否则后面会返工。

第一,明确智能体用途。是要做聊天型助手,还是任务型控制?如果是任务型,智能体需要在Coze平台预先配置好工作流和插件,比如“查询天气”“控制灯”等。ESP32那边的代码要能解析智能体返回的指令文本,而不是只把它当纯文本显示。

第二,确定交互模式。Coze API支持流式(stream)和非流式返回。流式模式适合网页端打字机效果,但对ESP32这种资源紧张的设备并不友好——每次数据块到达都要处理,稍有不慎缓冲区就溢出。我强烈建议先用非流式模式把整条链路跑通,再考虑升级。

第三,考虑网络环境。家里的路由器如果信号差,ESP32的HTTP请求会频繁超时,排查起来非常痛苦。可以先用手机热点测试,稳定之后再切到路由器环境。

2. 环境准备与前置配置

2.1 硬件清单和开发环境搭建

我这次用的硬件非常普通,任何一个玩过ESP32的人手里应该都有:

  • ESP32 DevKitC开发板(核心是ESP-WROOM-32模组),某宝二十多块就能拿下
  • 一根Micro USB数据线(注意要选支持数据传输的,不能是纯充电线)
  • 可选:0.96寸OLED屏幕(I2C接口),用来显示智能体回复

开发环境我用的是Arduino IDE 2.x,装好之后要先把ESP32开发板支持包加进去。在“文件 → 首选项 → 附加开发板管理器网址”里填入:

https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

然后到“开发板管理器”里搜索“esp32”,安装Espressif Systems提供的支持包。这里有个坑:如果你在国内网络环境下安装经常失败,可以试试手动下载压缩包后放到指定目录,或者多试几次让管理器断点续传。安装完成之后,在“工具 → 开发板”里选择“ESP32 Dev Module”就能开始写代码了。

2.2 Coze平台侧:创建智能体并发布为API

在Coze平台创建一个自定义智能体是免费的,具体流程不复杂。

登录Coze后点击“创建智能体”,填写名称、功能介绍、设定人设回复逻辑。人设这块对最终效果影响很大,建议写具体一点,比如“你是一个设备控制助手,用户指令中包含‘开灯’时,你的回复必须以【CMD:ON】开头”,这样ESP32解析状态指令更容易。如果是聊天型应用,人设可以自由一些。

创建完成之后,进入智能体的“发布”页面。这里的核心是生成API访问凭证。Coze平台提供两种凭证:

凭证类型适用场景有效期
Personal Access Token个人开发测试、非生产环境长期有效,可控管理
OAuth Token生产环境、多用户场景按签发有效期,支持刷新

对ESP32这种设备端应用来说,个人令牌足够了。生成Token的入口在平台个人设置或密钥管理页面,拿到那一串形如pat_xxxxxxxx的密钥后,一定要立刻复制保存好。页面刷新后就看不到了,只能重新生成。

最后要记下两个关键信息:Bot ID(智能体ID,在智能体URL或API调试页面能看到,形如7451234567890123456),以及API的完整URL。国内版Coze的API地址是https://api.coze.cn/v3/chat,国际版是https://api.coze.com/v3/chat,别搞混了。

2.3 安装ESP32侧需要的第三方库

代码里要用到两个关键的第三方库,在Arduino IDE的“库管理器”里可以安装:

  • WiFi库:ESP32开发板支持包自带,不需要单独装。
  • HTTPClient库:同样自带。
  • ArduinoJson库:重点装一下,我用的是Benoit Blanchon的版本。注意ArduinoJson的V6和V7语法略有差异,建议直接装最新的V7版本。

ArduinoJson库是用来构造和解析JSON的,这个库几乎是ESP32做API交互的标配。如果你没有它,手拼JSON字符串和手拆JSON响应会非常痛苦,稍微多一个空格或者少一个引号就出错。

# 在Arduino IDE库管理器里搜索关键词: ArduinoJson by Benoit Blanchon

安装完成之后,可以在“文件 → 示例 → ArduinoJson”里找到官方示例,先跑一个JsonParserExample确认环境没有装错。

3. 核心API配置与请求体细节

3.1 Coze Chat API的鉴权机制

Coze的API鉴权使用标准的Bearer Token机制,也就是在HTTP请求头里加上Authorization字段:

Authorization: Bearer pat_xxxxxxxxxxxxxxxx Content-Type: application/json

这里有两个新手最容易踩的坑。

第一个坑是忘记加“Bearer ”前缀。我之前调试时直接把Token塞进去,不加Bearer三个字,服务器返回401 Unauthorized,我排查了半天才发现是这个细节问题。

第二个坑是Content-Type必须设置成application/json。如果你用的是HTTPClient库,需要调用http.addHeader("Content-Type", "application/json"),漏掉这行会导致服务端无法解析请求体,返回415之类错误。

另外,Coze API对请求头里的Accept字段也有要求,最好设置为application/json。用代码表示就是:

http.addHeader("Content-Type", "application/json"); http.addHeader("Accept", "application/json"); http.addHeader("Authorization", String("Bearer ") + apiToken);

3.2 请求体JSON的完整结构

调用Chat API的核心请求体长这样(我这里用的是/v3/chat接口,也就是新版对话接口):

{ "bot_id": "7451234567890123456", "user_id": "esp32-device-001", "stream": false, "auto_save": false, "additional_messages": [ { "role": "user", "content": "你好,请介绍一下你自己", "content_type": "text" } ] }

逐个字段解释一下:

  • bot_id:你在Coze平台创建的那个智能体的ID,这个必须和你想要调用的Bot匹配。
  • user_id:调用方的用户标识,你可以随便填一个字符串,但建议填有意义的设备编号。Coze用它来区分不同用户,也能保持多轮对话的上下文。
  • stream:是否开启流式返回。ESP32场景强烈建议设成false,让服务器一次性返回完整结果,省去解析流式数据的麻烦。
  • auto_save:是否自动保存会话历史。设为false时,每次请求都是独立的新对话;设为true时,Coze会为每个user_id自动维护会话上下文。如果你的智能体需要记住用户之前说过什么,就设成true。但要注意,这样会增加Token消耗。
  • additional_messages:本次请求要发送的消息数组。角色可以是user或assistant,内容放在content字段里。

这里涉及到一个实际使用的细节:既然auto_save可以保存上下文,为什么ESP32场景里我建议先关掉?原因是设备端调试的时候,你很难直观看到“上下文”到底保存成了什么样子,经常出现“咦,它怎么还记得三分钟前那句话”的困惑,逻辑链路不清楚。先把上下文关掉,每次测试都是全新对话,确认基本功能没问题之后,再按需打开。

3.3 响应体JSON解析要点

当Coze服务器处理完请求,会返回一个标准的JSON响应。如果在非流式模式下,核心响应结构大致像下面这样(我简化掉了无关字段):

{ "code": 0, "msg": "success", "data": { "conversation_id": "7406714987895603240", "id": "7406714987895603240", "status": "completed", "last_error": null, "usage": { "token_count": 1024, "output_count": 128, "input_count": 896 }, "messages": [ { "id": "7406714987895603241", "role": "assistant", "type": "answer", "content": "我是你的智能助手,可以帮你解答各种问题...", "content_type": "text" } ] } }

解析的时候重点关注三个地方:

  • 最外层的code字段:为0表示请求成功,非0表示出错,具体的错误信息在msg字段里。
  • data.status字段:表示对话状态,completed代表正常完成,in_progress则需要继续轮询。
  • data.messages数组:其中role为assistant、type为answer的那条消息,就是智能体最终的回复文本,取它的content字段即可。

需要注意的是,非流式模式下,Chat API的返回可能是“异步”的。也就是说,你发出请求后,服务器可能立即返回一个status为in_progress的响应,真正的回答结果要等几秒、甚至几十秒后才出来。Coze的官方文档里提到的做法是轮询/v3/chat/retrieve接口查看最终结果。

我在实际使用中发现,大部分情况下/v3/chat接口会同步返回完整结果,尤其是问题比较简单时。但如果你想稳妥一点,代码逻辑里就要做好两种情况的处理:如果返回的data.status是in_progress,等待2秒后再去查询会话结果。文章后面给出的核心代码里会包含这个处理思路。

4. 代码实现:从Wi-Fi连接到智能体回复

4.1 整体代码框架

我下面提供一份可以直接编译烧录的完整代码。它做的事情是:连接Wi-Fi,构造一个HTTP POST请求,把“请用一句话介绍你自己”发给Coze智能体,然后从JSON响应里解析出回答并打印到串口。

#include <WiFi.h> #include <HTTPClient.h> #include <ArduinoJson.h> // ====== 配置区 ====== const char* ssid = "你的WiFi名称"; const char* password = "你的WiFi密码"; const char* apiHost = "https://api.coze.cn/v3/chat"; // 国际版改为 api.coze.com const char* botId = "你的Bot ID"; const char* apiToken = "pat_你的Token"; void setup() { Serial.begin(115200); delay(1000); // 连接Wi-Fi WiFi.begin(ssid, password); Serial.print("正在连接Wi-Fi"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\nWi-Fi连接成功,IP地址: " + WiFi.localIP().toString()); } void loop() { if (WiFi.status() == WL_CONNECTED) { String reply = askCoze("请用一句话介绍你自己"); Serial.println("智能体回复: " + reply); } else { Serial.println("Wi-Fi连接断开,等待重连..."); } delay(10000); // 10秒后再发一次 } // ====== 核心函数:向Coze发送消息并获取回复 ====== String askCoze(String userMessage) { HTTPClient http; http.begin(apiHost); http.setTimeout(30000); // 30秒超时,Coze处理需要时间 http.addHeader("Content-Type", "application/json"); http.addHeader("Accept", "application/json"); http.addHeader("Authorization", String("Bearer ") + apiToken); // 构造请求体 JsonDocument doc; doc["bot_id"] = botId; doc["user_id"] = "esp32-device-001"; doc["stream"] = false; doc["auto_save"] = false; JsonArray messages = doc["additional_messages"].to<JsonArray>(); JsonObject msg = messages.add<JsonObject>(); msg["role"] = "user"; msg["content"] = userMessage; msg["content_type"] = "text"; String requestBody; serializeJson(doc, requestBody); Serial.println("请求体: " + requestBody); int httpCode = http.POST(requestBody); if (httpCode <= 0) { Serial.print("HTTP请求失败,错误码: "); Serial.println(http.errorToString(httpCode).c_str()); http.end(); return ""; } String responseBody = http.getString(); http.end(); Serial.print("HTTP状态码: "); Serial.println(httpCode); Serial.println("响应体: " + responseBody); // 解析响应 JsonDocument respDoc; DeserializationError error = deserializeJson(respDoc, responseBody); if (error) { Serial.print("JSON解析失败: "); Serial.println(error.c_str()); return ""; } int code = respDoc["code"] | -1; if (code != 0) { Serial.print("API返回错误: "); Serial.println(respDoc["msg"].as<String>()); return ""; } // 从messages数组中提取assistant的answer String replyText = ""; JsonArray allMessages = respDoc["data"]["messages"].as<JsonArray>(); for (JsonObject item : allMessages) { const char* role = item["role"] | ""; const char* type = item["type"] | ""; if (strcmp(role, "assistant") == 0 && strcmp(type, "answer") == 0) { replyText = item["content"].as<String>(); break; } } return replyText; }

把配置区的ssid、password、botId、apiToken换成你自己的,编译烧录就能跑。串口监视器波特率设为115200,你会看到Wi-Fi连接日志、请求体、响应体,以及最终解析出来的智能体回复。

4.2 逐步拆解:为什么这样写

这个代码看起来不长,但每一部分都有讲究。我挑几个关键环节说说。

Wi-Fi连接部分用了一个while循环不断探测WiFi.status()状态,直到变成WL_CONNECTED才往下走。这个做法简单粗暴,但很可靠。要注意的是,ESP32上电后Wi-Fi模块需要一点时间初始化,所以我在开头加了一个delay(1000),避免在Wi-Fi栈还没准备好时就开始连接导致偶发失败。

HTTPClient的setTimeout(30000)很多人会漏掉。Coze智能体在处理复杂工作流时,响应时间可能超过默认的5秒超时,如果超时设得太短,你在日志里会看到HTTP请求失败,错误码: -11之类的报错(这其实表示连接超时/读取超时)。我在第一次调试时就遇到过这个问题,把超时调大到30秒之后,再也没有无端超时的现象。

JSON构造部分,我用的是ArduinoJson V7的JsonDocument。注意V7里JsonDocument和JsonArray的用法和V6有些差异,如果你装的是V6,需要把doc["additional_messages"].to<JsonArray>()改成doc.createNestedArray("additional_messages"),把messages.add<JsonObject>()改成messages.createNestedObject()。编译报错的话,多半是版本语法不匹配这一点导致的。

响应解析的部分,我用了respDoc["code"] | -1这种写法,作用是在字段不存在时返回默认值-1,避免because of空指针崩溃。ArduinoJson的|运算符是它的一个特色功能,能非常优雅地处理缺省情况。在解析嵌套数组时,直接遍历data.messages,找到角色为assistant、类型为answer的那条数据,取content字段。如果你勾选了Coze工作流里的其他输出,messages里可能还有tool_call之类的消息类型,但只要过滤条件写对,取到的就是最终回答。

4.3 多轮对话与会话ID的进阶用法

如果你想让智能体记住上下文,而不是每次都“失忆”,有两种方式。

第一种,把auto_save设为true,Coze会为同一个user_id自动维护会话记录。这个方式最省事,但缺点是每次请求都会把历史上下文重新计费,Token消耗大,而且你无法精确控制对话范围。

第二种,每次请求后从响应里取到data.conversation_id,下次请求时在additional_messages前面带上之前的历史消息记录。这种方式更灵活,但ESP32端要自己维护一个消息列表,内存开销不小。对于大多数场景,我推荐用第一种,即auto_save: true。ESP32那点内存实在不适合保存大量聊天历史。

实践中还有一种场景:智能体的回复文本里带结构化指令,比如“打开3号灯”返回内容中包含{"action": "turn_on", "device": "light", "num": 3}这样的字符串。你可以在ESP32端用JSON.parse再解析一次,也可以用简单的字符串匹配处理。前者更规范,后者更快。我的经验是:如果智能体是你自己创建的,完全可以在人设里约定输出格式为JSON,ESP32端直接二次解析,指令控制非常顺滑。

5. 常见问题与排查技巧

5.1 API报错最全对照表

我前前后后调试这个项目遇到了不少奇怪的报错,把典型问题整理成一张表,方便大家直接排查。

现象可能原因解决方法
HTTP状态码401Token错误、Token过期、Authorization头格式不对检查Token是否带Bearer前缀,重新生成Token
HTTP状态码404API地址写错,或用了已废弃的接口版本确认URL是/v3/chat,注意是api.coze.cn还是api.coze.com
HTTP状态码400请求体JSON格式错误、字段名拼错、bot_id缺失打印请求体,和官方文档逐字比对
返回code=-1或非0Bot ID无效、Token无权限确认Bot已发布并开放API调用
JSON解析失败响应体不是合法JSON,或响应体太大超出内存打印原始响应,检查是否截断;考虑精简回复
in_progress状态迟迟不变智能体工作流执行时间较长轮询/v3/chat/retrieve接口,或把超时适当调大
请求间歇性失败Wi-Fi信号不稳定使用手机热点测试,检查路由器2.4G频段

这里特别说一下400 Invalid schema for function这类报错。我在用支持函数调用的智能体时遇到过一次,原因是请求体里某个字段的数据类型和API定义的Schema不匹配。排查方法是把HTTPClient打印出来的请求体JSON复制到Coze的API调试页面,看它在网页端是否返回同样的错误。网页端能通过,那就是ESP32端的构造代码有问题,重点检查JSON的嵌套层级和数组写法。

5.2 串口监视器中文乱码

这个问题几乎是ESP32新手都会遇到的。代码里如果使用Serial.println("中文"),串口监视器显示乱码,99%的原因是波特率不匹配。代码里Serial.begin(115200),串口监视器右下角就必须选115200。如果波特率对的还乱码,检查是不是开发板选择错误,比如你实际用的是ESP32-S3,却在工具里选了ESP32。

中文显示乱码还有一个隐蔽原因:Arduino IDE的串口监视器默认用UTF-8编码,而有些终端工具用的是GBK。如果显示是“锟斤拷”这种鬼画符,那是典型的编码不匹配,换用VS Code的串口监视器插件或者关掉重开Arduino IDE就能解决。

5.3 内存不足导致设备重启

ESP32的RAM有限,而JSON解析恰好是吃内存的大户。如果你使用Coze智能体返回了很长的回复(比如几千字),响应体的String变量加上ArduinoJson的解析文档对象,可能把堆内存耗尽,造成设备死机或重启。

几个规避措施:

  • 在Coze智能体的人设里明确限制回复长度,例如“回复控制在100字以内”。
  • 解析完成后及时清理资源,respDoc.clear()可以释放JSON文档对象占用的内存。
  • 不要用全局大String变量保存响应体,用完就释放。
  • 串口打印响应体有助于调试,但在正式使用时应注释掉打印语句,因为Serial.println本身也占不少CPU和内存开销。

实测我发现,ESP32在非流式模式下处理512字节以内的JSON响应很轻松,超过2KB就有明显卡顿风险。所以最好的办法还是在智能体侧就把回复控制在合理长度内。

5.4 网络连接超时的深层问题

如果ESP32能连上Wi-Fi但HTTP请求一直超时,除了信号问题外,还有一个原因值得注意:ESP32默认的HTTP请求走的是80端口,而Coze的API是HTTPS(443端口)。我在代码里用的是https://开头的地址,HTTPClient库会自动切换到TLS连接。TLS握手需要额外的时间,如果超时设置太短,极容易在握手阶段就断开。

另外,TLS握手需要校验服务器证书,ESP32固件里内置了常用的根证书。如果你用的是某些裁剪版固件,可能缺少Coze服务器证书链的根证书,导致SSL握手失败。这种情况的排查方法是:把http.begin()改成http.begin(apiHost, rootCACert),手动传入Coze域名的根证书。但说实话,出厂固件一般够用,极少遇到需要手动传证书的场景。

6. 功能扩展:从“能对话”到“能干活”

6.1 让智能体控制ESP32外设

打通API之后,最自然的一个进阶玩法是让智能体控制硬件。比如你问它“主人回来了,帮我开灯”,智能体返回文本“已为您打开客厅灯”,但真实世界里灯是怎么开的?答案是在ESP32端做指令解析。

具体做法是:在Coze平台创建智能体时,把系统人设写成下面这样的格式:

你是智能家居控制助手。用户的请求中包含开灯/关灯/调节亮度等意图时, 你必须在回复末尾单独一行输出JSON指令,格式: [控制指令]{"device":"led","action":"on","brightness":255}

然后ESP32端的代码在拿到replyText后,检测其中是否包含[控制指令]标记。如果有,就把[控制指令]之后的内容截取出来,再用ArduinoJson解析,最后根据device和action字段去控制GPIO引脚。

我实际测试过,这种“文本携带指令”的方式很可靠,天然规避了API调用返回二进制数据的问题。相比Coze的Function Calling(函数调用)机制,这种方式在硬件控制场景下更简单。Coze的Function Calling更适合云端工具调用,比如查天气、查数据库;而让ESP32本地执行动作,用文本指令解析就足够了。

6.2 把传感器数据带上对话

另一个实用玩法是把ESP32读取到的传感器数据塞进请求消息里,让智能体基于真实环境数据做回答。举个例子,我在一个环境监测项目里,ESP32每隔5分钟把DHT22温湿度传感器的读数发给Coze智能体,附带的问题是:

当前温度为26.5摄氏度,湿度为68%,请给出舒适度评价和通风建议。

智能体回复的内容就直接基于实时数据,而不是凭空瞎说。这一招特别适合做农业大棚监测、室内空气质量提醒、儿童房环境监控等场景。实现上没有额外难度,就是在构造additional_messages时,把传感器读数拼进content字符串里。

唯一要注意的是,如果传感器数据更新频繁,每次都走Coze API会消耗大量Token。实际项目里最好设置合理的轮询间隔,比如温度变化不超过0.5度就不上报。

6.3 关于低功耗和长时间运行的建议

有些读者可能想把设备做成电池供电的低功耗产品,这就涉及ESP32的功耗管理。

先说结论:如果要长期在线等待语音或按键唤醒,建议使用支持ESP32-C3或ESP32-C5这类新芯片的模组,它们的低功耗表现明显优于老款ESP32。我在测试的时候用ESP32-C3跑同样的Coze API调用,整体功耗比老款ESP32低不少。

功耗优化的实操思路是:不需要唤醒的时候让ESP32进入深度睡眠,定时唤醒后连接Wi-Fi发送请求,收到回复后再次睡下。Coze API每次请求本身只耗时几秒,这个时间窗口里才需要全功率运行。我把这个方案用在了一个环境监测节点上,3.7V锂电池供电,每30分钟上报一次数据,实测能撑两个月以上。

如果要进一步降低功耗,可以考虑把ESP32的工作模式调整成“ApMode关闭、ModemSleep开启、CPU频率降到80MHz”,虽然处理速度慢点,但API调用完全够用。

6.4 多智能体协同的前景

Coze平台支持一个项目里创建多个智能体,每个智能体专精一个领域。在ESP32侧,你完全可以给每个智能体申请一个独立的Bot ID,然后根据用户指令的意图,动态选择调用哪个智能体。

比如设备默认调用“闲聊助手”,但当检测到用户消息里有“控制”两个字时,就改调“设备控制助手”。代码层面上只需要把botId变量换成对应的智能体ID,其余逻辑完全复用。这种方式相当于用ESP32做了一台轻量级“路由器”,按请求分发到不同云端大脑。

我印象很深的一次测试,是在一个机器人小车项目里挂了三个智能体:一个负责对话闲聊、一个负责路径规划建议、一个负责传感器数据解释。ESP32收到用户语音指令后,先做意图判断,再选择对应智能体调用。多智能体的切换延迟大约只有几十毫秒,用户体验上基本无感。

7. 验证功能与后续优化建议

讲到这里,核心功能已经全部实现了。最后分享几条我在实操中的体会。

第一次跑通的时候,建议先用手机热点做网络环境,排除路由器干扰,只验证“ESP32 → Coze API → 智能体”这条链路是否通畅。等日志输出里的HTTP状态码: 200稳定出现,再切回到实际使用环境。

串口日志是我调试这个项目最大的帮手。我习惯把请求体和响应体完整打印出来,尤其在API调用失败的时候,原始的返回信息比任何推理都更有用。等一切稳定之后,再把这些打印注释掉,减少IO开销。

关于文本编码,Coze返回的content默认是UTF-8格式文本,ArduinoJSON库解析时能正确还原。如果你要在OLED上显示中文,需要注意OLED驱动库的字体是点阵还是Unicode,可能需要额外字库文件。我用的OLED屏显示中文时,需要烧录一个中文字模库,这跟本项目的API链路无关,但会影响最终体验。

关于安全问题,Token不要硬编码在公共代码仓库里。如果你的项目要开源,建议把Token放在单独的头文件里,并且用gitignore排除掉。Coze的Token权限很大,拿到的人可以直接用你的账号额度调用智能体,务必保管好。

关于开发效率,建议在Coze平台调试好智能体的人设和回复质量之后,再去碰ESP32代码。两边同时调试容易把问题混在一起,一会儿改人设、一会儿改代码,效率很低。我通常是先在网页版Coze的调试窗口把智能体调到满意,再固化一份请求体格式到ESP32端。

这个项目后续还能怎么扩展?可以试试把Coze工作流里的“Markdown转Word”之类能力接到ESP32上,让智能体生成结构化文档;也可以把ESP32的传感器数据通过API推送给Coze,在云端形成长期趋势分析后再反馈给设备端。本质上,ESP32只是你的“眼睛和手”,Coze智能体是“大脑”,两者之间这条API通道一旦打通,能玩的花样几乎没有上限。希望这篇文章能帮你少走点弯路,早点让你的硬件说出第一句“AI的话”。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询