From 5d92917a0b853e7573945db7868e4efa5c9e7e11 Mon Sep 17 00:00:00 2001 From: Bo Date: Wed, 18 Feb 2026 17:16:00 +0800 Subject: [PATCH] feat: update mimi tips in tg. Signed-off-by: Bo --- main/agent/agent_loop.c | 126 ++++++++++++++++++++++++++++++----- main/agent/context_builder.c | 1 + main/cron/cron_service.c | 46 +++++++++++-- main/mimi_config.h | 4 +- 4 files changed, 152 insertions(+), 25 deletions(-) diff --git a/main/agent/agent_loop.c b/main/agent/agent_loop.c index 226cdcc..7e5eae6 100644 --- a/main/agent/agent_loop.c +++ b/main/agent/agent_loop.c @@ -12,7 +12,6 @@ #include "freertos/task.h" #include "esp_log.h" #include "esp_heap_caps.h" -#include "esp_random.h" #include "cJSON.h" static const char *TAG = "agent"; @@ -54,17 +53,107 @@ static cJSON *build_assistant_content(const llm_response_t *resp) return content; } +static void json_set_string(cJSON *obj, const char *key, const char *value) +{ + if (!obj || !key || !value) { + return; + } + cJSON_DeleteItemFromObject(obj, key); + cJSON_AddStringToObject(obj, key, value); +} + +static void append_turn_context_prompt(char *prompt, size_t size, const mimi_msg_t *msg) +{ + if (!prompt || size == 0 || !msg) { + return; + } + + size_t off = strnlen(prompt, size - 1); + if (off >= size - 1) { + return; + } + + int n = snprintf( + prompt + off, size - off, + "\n## Current Turn Context\n" + "- source_channel: %s\n" + "- source_chat_id: %s\n" + "- If using cron_add for Telegram in this turn, set channel='telegram' and chat_id to source_chat_id.\n" + "- Never use chat_id 'cron' for Telegram messages.\n", + msg->channel[0] ? msg->channel : "(unknown)", + msg->chat_id[0] ? msg->chat_id : "(empty)"); + + if (n < 0 || (size_t)n >= (size - off)) { + prompt[size - 1] = '\0'; + } +} + +static char *patch_tool_input_with_context(const llm_tool_call_t *call, const mimi_msg_t *msg) +{ + if (!call || !msg || strcmp(call->name, "cron_add") != 0) { + return NULL; + } + + cJSON *root = cJSON_Parse(call->input ? call->input : "{}"); + if (!root || !cJSON_IsObject(root)) { + cJSON_Delete(root); + root = cJSON_CreateObject(); + } + if (!root) { + return NULL; + } + + bool changed = false; + + cJSON *channel_item = cJSON_GetObjectItem(root, "channel"); + const char *channel = cJSON_IsString(channel_item) ? channel_item->valuestring : NULL; + + if ((!channel || channel[0] == '\0') && msg->channel[0] != '\0') { + json_set_string(root, "channel", msg->channel); + channel = msg->channel; + changed = true; + } + + if (channel && strcmp(channel, MIMI_CHAN_TELEGRAM) == 0 && + strcmp(msg->channel, MIMI_CHAN_TELEGRAM) == 0 && msg->chat_id[0] != '\0') { + cJSON *chat_item = cJSON_GetObjectItem(root, "chat_id"); + const char *chat_id = cJSON_IsString(chat_item) ? chat_item->valuestring : NULL; + if (!chat_id || chat_id[0] == '\0' || strcmp(chat_id, "cron") == 0) { + json_set_string(root, "chat_id", msg->chat_id); + changed = true; + } + } + + char *patched = NULL; + if (changed) { + patched = cJSON_PrintUnformatted(root); + if (patched) { + ESP_LOGI(TAG, "Patched cron_add target to %s:%s", msg->channel, msg->chat_id); + } + } + + cJSON_Delete(root); + return patched; +} + /* 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) +static cJSON *build_tool_results(const llm_response_t *resp, const mimi_msg_t *msg, + 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]; + const char *tool_input = call->input ? call->input : "{}"; + char *patched_input = patch_tool_input_with_context(call, msg); + if (patched_input) { + tool_input = patched_input; + } /* Execute tool */ tool_output[0] = '\0'; - tool_registry_execute(call->name, call->input, tool_output, tool_output_size); + tool_registry_execute(call->name, tool_input, tool_output, tool_output_size); + free(patched_input); ESP_LOGI(TAG, "Tool %s result: %d bytes", call->name, (int)strlen(tool_output)); @@ -105,6 +194,8 @@ static void agent_loop_task(void *arg) /* 1. Build system prompt */ context_build_system_prompt(system_prompt, MIMI_CONTEXT_BUF_SIZE); + append_turn_context_prompt(system_prompt, MIMI_CONTEXT_BUF_SIZE, &msg); + ESP_LOGI(TAG, "LLM turn context: channel=%s chat_id=%s", msg.channel, msg.chat_id); /* 2. Load session history into cJSON array */ session_get_history_json(msg.chat_id, history_json, @@ -122,27 +213,22 @@ static void agent_loop_task(void *arg) /* 4. ReAct loop */ char *final_text = NULL; int iteration = 0; + bool sent_working_status = false; while (iteration < MIMI_AGENT_MAX_TOOL_ITER) { /* Send "working" indicator before each API call */ #if MIMI_AGENT_SEND_WORKING_STATUS - { - static const char *working_phrases[] = { - "mimi\xF0\x9F\x98\x97is working...", - "mimi\xF0\x9F\x90\xBE is thinking...", - "mimi\xF0\x9F\x92\xAD is pondering...", - "mimi\xF0\x9F\x8C\x99 is on it...", - "mimi\xE2\x9C\xA8 is cooking...", - }; - const int phrase_count = sizeof(working_phrases) / sizeof(working_phrases[0]); + if (!sent_working_status && strcmp(msg.channel, MIMI_CHAN_SYSTEM) != 0) { mimi_msg_t status = {0}; strncpy(status.channel, msg.channel, sizeof(status.channel) - 1); strncpy(status.chat_id, msg.chat_id, sizeof(status.chat_id) - 1); - status.content = strdup(working_phrases[esp_random() % phrase_count]); + status.content = strdup("\xF0\x9F\x90\xB1mimi is working..."); if (status.content) { if (message_bus_push_outbound(&status) != ESP_OK) { ESP_LOGW(TAG, "Outbound queue full, drop working status"); free(status.content); + } else { + sent_working_status = true; } } } @@ -174,7 +260,7 @@ static void agent_loop_task(void *arg) cJSON_AddItemToArray(messages, asst_msg); /* Execute tools and append results */ - cJSON *tool_results = build_tool_results(&resp, tool_output, TOOL_OUTPUT_SIZE); + cJSON *tool_results = build_tool_results(&resp, &msg, tool_output, TOOL_OUTPUT_SIZE); cJSON *result_msg = cJSON_CreateObject(); cJSON_AddStringToObject(result_msg, "role", "user"); cJSON_AddItemToObject(result_msg, "content", tool_results); @@ -189,8 +275,16 @@ static void agent_loop_task(void *arg) /* 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); + esp_err_t save_user = session_append(msg.chat_id, "user", msg.content); + esp_err_t save_asst = session_append(msg.chat_id, "assistant", final_text); + if (save_user != ESP_OK || save_asst != ESP_OK) { + ESP_LOGW(TAG, "Session save failed for chat %s (user=%s, assistant=%s)", + msg.chat_id, + esp_err_to_name(save_user), + esp_err_to_name(save_asst)); + } else { + ESP_LOGI(TAG, "Session saved for chat %s", msg.chat_id); + } /* Push response to outbound */ mimi_msg_t out = {0}; diff --git a/main/agent/context_builder.c b/main/agent/context_builder.c index 2dfb6c7..6e81ef9 100644 --- a/main/agent/context_builder.c +++ b/main/agent/context_builder.c @@ -48,6 +48,7 @@ esp_err_t context_build_system_prompt(char *buf, size_t size) "- cron_add: Schedule a recurring or one-shot task. The message will trigger an agent turn when the job fires.\n" "- cron_list: List all scheduled cron jobs.\n" "- cron_remove: Remove a scheduled cron job by ID.\n\n" + "When using cron_add for Telegram delivery, always set channel='telegram' and a valid numeric chat_id.\n\n" "Use tools when needed. Provide your final answer as text after using tools.\n\n" "## Memory\n" "You have persistent memory stored on local flash:\n" diff --git a/main/cron/cron_service.c b/main/cron/cron_service.c index 58b237f..bf33195 100644 --- a/main/cron/cron_service.c +++ b/main/cron/cron_service.c @@ -20,6 +20,36 @@ static cron_job_t s_jobs[MAX_CRON_JOBS]; static int s_job_count = 0; static TaskHandle_t s_cron_task = NULL; +static esp_err_t cron_save_jobs(void); + +static bool cron_sanitize_destination(cron_job_t *job) +{ + bool changed = false; + if (!job) { + return false; + } + + if (job->channel[0] == '\0') { + strncpy(job->channel, MIMI_CHAN_SYSTEM, sizeof(job->channel) - 1); + changed = true; + } + + if (strcmp(job->channel, MIMI_CHAN_TELEGRAM) == 0) { + if (job->chat_id[0] == '\0' || strcmp(job->chat_id, "cron") == 0) { + ESP_LOGW(TAG, "Cron job %s has invalid telegram chat_id, fallback to system:cron", + job->id[0] ? job->id : ""); + strncpy(job->channel, MIMI_CHAN_SYSTEM, sizeof(job->channel) - 1); + strncpy(job->chat_id, "cron", sizeof(job->chat_id) - 1); + changed = true; + } + } else if (job->chat_id[0] == '\0') { + strncpy(job->chat_id, "cron", sizeof(job->chat_id) - 1); + changed = true; + } + + return changed; +} + /* ── Persistence ──────────────────────────────────────────────── */ static void cron_generate_id(char *id_buf) @@ -77,6 +107,7 @@ static esp_err_t cron_load_jobs(void) } s_job_count = 0; + bool repaired = false; cJSON *item; cJSON_ArrayForEach(item, jobs_arr) { if (s_job_count >= MAX_CRON_JOBS) break; @@ -100,6 +131,9 @@ static esp_err_t cron_load_jobs(void) sizeof(job->channel) - 1); strncpy(job->chat_id, chat_id ? chat_id : "cron", sizeof(job->chat_id) - 1); + if (cron_sanitize_destination(job)) { + repaired = true; + } cJSON *enabled_j = cJSON_GetObjectItem(item, "enabled"); job->enabled = enabled_j ? cJSON_IsTrue(enabled_j) : true; @@ -133,6 +167,9 @@ static esp_err_t cron_load_jobs(void) } cJSON_Delete(root); + if (repaired) { + cron_save_jobs(); + } ESP_LOGI(TAG, "Loaded %d cron jobs", s_job_count); return ESP_OK; } @@ -354,13 +391,8 @@ esp_err_t cron_add_job(cron_job_t *job) /* Generate ID */ cron_generate_id(job->id); - /* Set defaults for channel/chat_id if empty */ - if (job->channel[0] == '\0') { - strncpy(job->channel, MIMI_CHAN_SYSTEM, sizeof(job->channel) - 1); - } - if (job->chat_id[0] == '\0') { - strncpy(job->chat_id, "cron", sizeof(job->chat_id) - 1); - } + /* Validate/sanitize channel and chat_id before storing. */ + cron_sanitize_destination(job); /* Compute initial next_run */ job->enabled = true; diff --git a/main/mimi_config.h b/main/mimi_config.h index 7b8fa89..47a3597 100644 --- a/main/mimi_config.h +++ b/main/mimi_config.h @@ -56,7 +56,7 @@ #define MIMI_AGENT_MAX_HISTORY 20 #define MIMI_AGENT_MAX_TOOL_ITER 10 #define MIMI_MAX_TOOL_CALLS 4 -#define MIMI_AGENT_SEND_WORKING_STATUS 0 +#define MIMI_AGENT_SEND_WORKING_STATUS 1 /* Timezone (POSIX TZ format) */ #define MIMI_TIMEZONE "PST8PDT,M3.2.0,M11.1.0" @@ -70,7 +70,7 @@ #define MIMI_LLM_API_VERSION "2023-06-01" #define MIMI_LLM_STREAM_BUF_SIZE (32 * 1024) #define MIMI_LLM_LOG_VERBOSE_PAYLOAD 0 -#define MIMI_LLM_LOG_PREVIEW_BYTES 256 +#define MIMI_LLM_LOG_PREVIEW_BYTES 160 /* Message Bus */ #define MIMI_BUS_QUEUE_LEN 16