feat: update mimi tips in tg.

Signed-off-by: Bo <boironic@gmail.com>
This commit is contained in:
Bo
2026-02-18 17:16:00 +08:00
parent cba7444b26
commit 5d92917a0b
4 changed files with 152 additions and 25 deletions

View File

@@ -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};

View File

@@ -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"

View File

@@ -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 : "<new>");
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;

View File

@@ -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