feat: add ReAct agent loop and build-time config (mimi_secrets.h)
Rewrite agent_loop.c with ReAct tool use loop: LLM call → tool execution → repeat until end_turn (max 10 iterations). Add tool guidance to system prompt. Add set_search_key CLI command. Add mimi_secrets.h for build-time credentials with highest priority over NVS/CLI values. All modules (wifi, telegram, llm, proxy, web_search) check build-time secrets first, fall back to NVS. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,30 +4,95 @@
|
||||
#include "bus/message_bus.h"
|
||||
#include "llm/llm_proxy.h"
|
||||
#include "memory/session_mgr.h"
|
||||
#include "tools/tool_registry.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "cJSON.h"
|
||||
|
||||
static const char *TAG = "agent";
|
||||
|
||||
#define TOOL_OUTPUT_SIZE (8 * 1024)
|
||||
|
||||
/* Build the assistant content array from llm_response_t for the messages history.
|
||||
* Returns a cJSON array with text and tool_use blocks. */
|
||||
static cJSON *build_assistant_content(const llm_response_t *resp)
|
||||
{
|
||||
cJSON *content = cJSON_CreateArray();
|
||||
|
||||
/* Text block */
|
||||
if (resp->text && resp->text_len > 0) {
|
||||
cJSON *text_block = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(text_block, "type", "text");
|
||||
cJSON_AddStringToObject(text_block, "text", resp->text);
|
||||
cJSON_AddItemToArray(content, text_block);
|
||||
}
|
||||
|
||||
/* Tool use blocks */
|
||||
for (int i = 0; i < resp->call_count; i++) {
|
||||
const llm_tool_call_t *call = &resp->calls[i];
|
||||
cJSON *tool_block = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(tool_block, "type", "tool_use");
|
||||
cJSON_AddStringToObject(tool_block, "id", call->id);
|
||||
cJSON_AddStringToObject(tool_block, "name", call->name);
|
||||
|
||||
cJSON *input = cJSON_Parse(call->input);
|
||||
if (input) {
|
||||
cJSON_AddItemToObject(tool_block, "input", input);
|
||||
} else {
|
||||
cJSON_AddItemToObject(tool_block, "input", cJSON_CreateObject());
|
||||
}
|
||||
|
||||
cJSON_AddItemToArray(content, tool_block);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/* Build the user message with tool_result blocks */
|
||||
static cJSON *build_tool_results(const llm_response_t *resp, char *tool_output, size_t tool_output_size)
|
||||
{
|
||||
cJSON *content = cJSON_CreateArray();
|
||||
|
||||
for (int i = 0; i < resp->call_count; i++) {
|
||||
const llm_tool_call_t *call = &resp->calls[i];
|
||||
|
||||
/* Execute tool */
|
||||
tool_output[0] = '\0';
|
||||
tool_registry_execute(call->name, call->input, tool_output, tool_output_size);
|
||||
|
||||
ESP_LOGI(TAG, "Tool %s result: %d bytes", call->name, (int)strlen(tool_output));
|
||||
|
||||
/* Build tool_result block */
|
||||
cJSON *result_block = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(result_block, "type", "tool_result");
|
||||
cJSON_AddStringToObject(result_block, "tool_use_id", call->id);
|
||||
cJSON_AddStringToObject(result_block, "content", tool_output);
|
||||
cJSON_AddItemToArray(content, result_block);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
static void agent_loop_task(void *arg)
|
||||
{
|
||||
ESP_LOGI(TAG, "Agent loop started on core %d", xPortGetCoreID());
|
||||
|
||||
/* Allocate large buffers from PSRAM */
|
||||
char *system_prompt = heap_caps_calloc(1, MIMI_CONTEXT_BUF_SIZE, MALLOC_CAP_SPIRAM);
|
||||
char *messages_json = heap_caps_calloc(1, MIMI_LLM_STREAM_BUF_SIZE, MALLOC_CAP_SPIRAM);
|
||||
char *history_json = heap_caps_calloc(1, MIMI_LLM_STREAM_BUF_SIZE, MALLOC_CAP_SPIRAM);
|
||||
char *response_buf = heap_caps_calloc(1, MIMI_LLM_STREAM_BUF_SIZE, MALLOC_CAP_SPIRAM);
|
||||
char *tool_output = heap_caps_calloc(1, TOOL_OUTPUT_SIZE, MALLOC_CAP_SPIRAM);
|
||||
|
||||
if (!system_prompt || !messages_json || !history_json || !response_buf) {
|
||||
if (!system_prompt || !history_json || !tool_output) {
|
||||
ESP_LOGE(TAG, "Failed to allocate PSRAM buffers");
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
const char *tools_json = tool_registry_get_tools_json();
|
||||
|
||||
while (1) {
|
||||
mimi_msg_t msg;
|
||||
esp_err_t err = message_bus_pop_inbound(&msg, UINT32_MAX);
|
||||
@@ -38,36 +103,81 @@ static void agent_loop_task(void *arg)
|
||||
/* 1. Build system prompt */
|
||||
context_build_system_prompt(system_prompt, MIMI_CONTEXT_BUF_SIZE);
|
||||
|
||||
/* 2. Load session history */
|
||||
/* 2. Load session history into cJSON array */
|
||||
session_get_history_json(msg.chat_id, history_json,
|
||||
MIMI_LLM_STREAM_BUF_SIZE, MIMI_AGENT_MAX_HISTORY);
|
||||
|
||||
/* 3. Build messages array (history + current message) */
|
||||
context_build_messages(history_json, msg.content,
|
||||
messages_json, MIMI_LLM_STREAM_BUF_SIZE);
|
||||
cJSON *messages = cJSON_Parse(history_json);
|
||||
if (!messages) messages = cJSON_CreateArray();
|
||||
|
||||
/* 4. Call Claude API */
|
||||
err = llm_chat(system_prompt, messages_json, response_buf, MIMI_LLM_STREAM_BUF_SIZE);
|
||||
/* 3. Append current user message */
|
||||
cJSON *user_msg = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(user_msg, "role", "user");
|
||||
cJSON_AddStringToObject(user_msg, "content", msg.content);
|
||||
cJSON_AddItemToArray(messages, user_msg);
|
||||
|
||||
if (err == ESP_OK && response_buf[0]) {
|
||||
/* 5. Save to session */
|
||||
session_append(msg.chat_id, "user", msg.content);
|
||||
session_append(msg.chat_id, "assistant", response_buf);
|
||||
/* 4. ReAct loop */
|
||||
char *final_text = NULL;
|
||||
int iteration = 0;
|
||||
|
||||
/* 6. Push response to outbound */
|
||||
mimi_msg_t out = {0};
|
||||
strncpy(out.channel, msg.channel, sizeof(out.channel) - 1);
|
||||
strncpy(out.chat_id, msg.chat_id, sizeof(out.chat_id) - 1);
|
||||
out.content = strdup(response_buf);
|
||||
if (out.content) {
|
||||
message_bus_push_outbound(&out);
|
||||
while (iteration < MIMI_AGENT_MAX_TOOL_ITER) {
|
||||
llm_response_t resp;
|
||||
err = llm_chat_tools(system_prompt, messages, tools_json, &resp);
|
||||
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "LLM call failed: %s", esp_err_to_name(err));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
/* Send error response */
|
||||
|
||||
if (!resp.tool_use) {
|
||||
/* Normal completion — save final text and break */
|
||||
if (resp.text && resp.text_len > 0) {
|
||||
final_text = strdup(resp.text);
|
||||
}
|
||||
llm_response_free(&resp);
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Tool use iteration %d: %d calls", iteration + 1, resp.call_count);
|
||||
|
||||
/* Append assistant message with content array */
|
||||
cJSON *asst_msg = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(asst_msg, "role", "assistant");
|
||||
cJSON_AddItemToObject(asst_msg, "content", build_assistant_content(&resp));
|
||||
cJSON_AddItemToArray(messages, asst_msg);
|
||||
|
||||
/* Execute tools and append results */
|
||||
cJSON *tool_results = build_tool_results(&resp, tool_output, TOOL_OUTPUT_SIZE);
|
||||
cJSON *result_msg = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(result_msg, "role", "user");
|
||||
cJSON_AddItemToObject(result_msg, "content", tool_results);
|
||||
cJSON_AddItemToArray(messages, result_msg);
|
||||
|
||||
llm_response_free(&resp);
|
||||
iteration++;
|
||||
}
|
||||
|
||||
cJSON_Delete(messages);
|
||||
|
||||
/* 5. Send response */
|
||||
if (final_text && final_text[0]) {
|
||||
/* Save to session (only user text + final assistant text) */
|
||||
session_append(msg.chat_id, "user", msg.content);
|
||||
session_append(msg.chat_id, "assistant", final_text);
|
||||
|
||||
/* Push response to outbound */
|
||||
mimi_msg_t out = {0};
|
||||
strncpy(out.channel, msg.channel, sizeof(out.channel) - 1);
|
||||
strncpy(out.chat_id, msg.chat_id, sizeof(out.chat_id) - 1);
|
||||
out.content = strdup(response_buf[0] ? response_buf : "Sorry, I encountered an error.");
|
||||
out.content = final_text; /* transfer ownership */
|
||||
message_bus_push_outbound(&out);
|
||||
} else {
|
||||
/* Error or empty response */
|
||||
free(final_text);
|
||||
mimi_msg_t out = {0};
|
||||
strncpy(out.channel, msg.channel, sizeof(out.channel) - 1);
|
||||
strncpy(out.chat_id, msg.chat_id, sizeof(out.chat_id) - 1);
|
||||
out.content = strdup("Sorry, I encountered an error.");
|
||||
if (out.content) {
|
||||
message_bus_push_outbound(&out);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user