
如何读懂zclaw代码agent.c工具循环驱动ESP32 AI助手多轮LLM调用的完整剖析【免费下载链接】zclawYour personal AI assistant at all-in 888KiB (~35KB in app code). Running on an ESP32. GPIO, cron, custom tools, memory, and more.项目地址: https://gitcode.com/gh_mirrors/zc/zclawzclaw 是一个运行在 ESP32 上的个人 AI 助手全部固件仅约 888 KiB。它的核心是 main/agent.c 里一段紧凑的工具循环反复向大模型发起多轮 LLM 调用模型要么直接回答要么要求执行工具设备执行完工具后把结果回灌给模型继续下一轮直到拿到最终回答。本文带你完整读懂这套循环的设计与实现。吉祥物画面一只龙虾用钳子操作着 Seeed XIAO ESP32-C3 开发板——zclaw 就住在这块小小的板子里。整体架构一个 FreeRTOS 任务一条消息一个循环 zclaw 由多个 FreeRTOS 任务协作但真正思考的只有一个agent 任务。入口是 agent.c 中的agent_task启动时接收三个队列用户输入队列、本地输出队列、Telegram 输出队列之后死循环地从输入队列取消息逐条交给process_message()处理。这保证了同一时刻只有一条消息在走工具循环天然避免了并发写历史。进入 process_message循环开始前的准备工作每条消息进入 process_message() 后先做四件事拦截斜杠命令/start、/help、/stop 等和 USB 本地管理命令直接应答不走 LLM省流量取工具清单tools_get_all()拿到内置工具表和数量暂停 Telegram 轮询避免 LLM 长请求期间 TLS 握手互相挤占内存把用户消息写入会话历史并记录起点history_turn_start——这是出错时回滚用的存档点。核心 while 循环5 轮上限的 agent.c 工具循环真正的核心是这段循环位于 agent.cwhile (!done rounds MAX_TOOL_ROUNDS) { rounds; // 1. 构建请求 JSON系统提示 全部历史 工具定义 // 2. 限流检查 // 3. 调用 LLM带重试 // 4. 解析响应是工具调用→ 执行并把结果写回历史继续下一轮 // 是普通文本→ 回复用户done true }MAX_TOOL_ROUNDS在 config.h 中定义为5一条消息最多触发 5 轮 LLM 调用。这个刹车是防失控的关键后面细说。构建请求系统提示 滚动历史 工具定义每轮都会重新组装一次请求 JSON系统提示词由agent_build_system_prompt()按当前人格neutral / friendly / technical / witty生成告诉模型你是一台 400 KB RAM 的 ESP32 上的 agent回答要简短、只用纯文本会话历史把s_history里已有的全部消息原样带上——包括上一轮的工具调用和工具结果这是多轮的记忆来源工具清单内置工具由 builtin_tools.def 宏展开进 tools.c 的注册表名称、描述、参数 JSON Schema、执行函数用户自定义工具一并附上。限流检查与带预算的重试机制发送前先过ratelimit_check()默认100 次/小时、1000 次/天超限直接回滚并告知用户。每轮 LLM 调用自带重试机制参数见 config.h参数值含义LLM_MAX_RETRIES3每轮最多尝试次数含首次LLM_RETRY_BASE_MS2000首次失败后等 2 秒LLM_RETRY_MAX_MS10000退避间隔封顶 10 秒LLM_RETRY_BUDGET_MS45000单轮重试总时间预算 45 秒失败后按指数退避2s → 4s → 8s…但总耗时不超过 45 秒预算防止网络故障时卡死设备。两种响应分支工具调用 或 最终回答json_parse_response()解析出响应后循环走向两个分支之一分支一模型要调工具工具名非空且有参数把这条 tool_use 消息工具名 参数 JSON以 assistant 身份写入历史执行工具结果写入 512 字节的s_tool_result_buf把工具结果以 user 身份的 tool_result 消息写回历史不回复用户直接continue进入下一轮 LLM 调用——让模型看到自己工具的结果。工具执行分三类见 agent.c️内置工具gpio_write、cron_set、memory_set 等直接在芯片上执行用户自定义工具不直接执行而是把它的 action 描述变成Execute this action now: …交还给模型由模型再拆解成内置工具完成——这就是自定义工具组合的实现方式特殊规则定时任务触发的消息里禁止再调cron_set防止定时任务不断繁殖新的定时任务。分支二模型返回普通文本写入历史 → 经队列发回 Telegram / 本地串口 →done true循环结束。滚动历史24 条消息槽位就是全部记忆ESP32 内存紧张zclaw 的历史就是一个固定数组s_history[24]MAX_HISTORY_TURNS 12见 config.h每条消息最长 1024 字符。满了怎么办history_add() 的做法是丢弃最旧的一条其余整体前移。注意它刻意不按一问一答成对删除——因为一次工具交互会跨多条消息user → assistant 工具调用 → user 工具结果按对删除会把配对拆散导致 API 报错。这个细节是嵌入式上做 LLM agent 很容易踩的坑。防失控设计回滚、上限与指标️ 工具循环的可靠性不靠运气靠三道保险轮次上限达到 5 轮仍没拿到文本回复循环强制终止回复用户(Reached max tool iterations)避免模型陷入无限调工具历史回滚构建请求失败、被限流、LLM 重试耗尽、响应解析失败任何一个环节出错都会调用history_rollback_to()把历史恢复到本轮起点agent.c保证不会把半截的 tool_use 留在记忆里污染后续对话指标统计每次请求都有request_metrics计时——LLM 总耗时、工具总耗时、轮数、调用次数请求结束时打一行 METRIC 日志方便你快速定位为什么这次回答特别慢。关键常量与源码地图常量值作用位置MAX_TOOL_ROUNDS5每请求最多工具轮数config.hMAX_HISTORY_TURNS12保留 12 轮24 条历史config.hLLM_MAX_RETRIES3每轮 LLM 最多尝试次数config.hLLM_RETRY_BUDGET_MS45000单轮重试时间预算毫秒config.hTOOL_RESULT_BUF_SIZE512工具结果缓冲区字节config.h核心文件一览main/agent.cagent 任务、消息预处理与工具循环主体main/config.h循环轮数、重试、历史长度等全部预算参数main/tools.c内置工具注册表与执行分发main/json_util.c请求构建与响应解析main/llm.c多后端Anthropic / OpenAI / OpenRouter / OllamaHTTP 调用。总结小设备上的 Agent 循环设计范式zclaw 把多轮 LLM 调用压缩成了一个固定缓冲 固定上限的循环FreeRTOS 单任务串行处理消息每轮构建请求 → 调 LLM → 解析响应工具结果回灌历史后进入下一轮普通文本则直接收尾滚动历史、限流、带时间预算的重试和历史回滚保证了整个循环在 400 KB RAM 的芯片里稳定可靠。读懂这 200 行核心代码你就掌握了在资源受限设备上构建 LLM agent 的完整范式。【免费下载链接】zclawYour personal AI assistant at all-in 888KiB (~35KB in app code). Running on an ESP32. GPIO, cron, custom tools, memory, and more.项目地址: https://gitcode.com/gh_mirrors/zc/zclaw创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考