style: 改进 SGV 相关声明

This commit is contained in:
pluv committed 2026-10-04 13:47:06 +08:00
1 parent 3dd2231c70
commit 016a45bc6e
7 files changed
+260 -168

No files matched your search

+4
View File
@@ -0,0 +1,4 @@
BasedOnStyle: LLVM
IndentWidth: 4
TabWidth: 4
UseTab: Never
+233 -124
View File
@@ -1,19 +1,20 @@
/* agent.c - agent 主循环 */ /* agent.c - agent 主循环 */
#include "agent.h" #include "agent.h"
#include "sgr.h"
#include "tools.h"
#include "i18n.h"
#include "util.h"
#include "http.h"
#include "spinner.h"
#include "confirm.h"
#include "image.h"
#include <limits.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <limits.h>
#include "confirm.h"
#include "http.h"
#include "i18n.h"
#include "image.h"
#include "sgr.h"
#include "spinner.h"
#include "tools.h"
#include "util.h"
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/* 颜色与打印 */ /* 颜色与打印 */
@@ -22,16 +23,19 @@
/* 前置声明: 定义在文件靠后的上下文管理一节 */ /* 前置声明: 定义在文件靠后的上下文管理一节 */
static int role_is(const JsonValue* m, const char* want); static int role_is(const JsonValue* m, const char* want);
static const char *col(const Agent *a, const char *code) static const char*
col(const Agent* a, const char* code)
{ {
return a->color ? code : ""; return a->color ? code : "";
} }
/* 把多行文本缩进显示, 最多 max_lines 行 */ /* 把多行文本缩进显示, 最多 max_lines 行 */
static void print_indented(const char *text, const char *prefix, static void
int max_lines, const char *color_code, int use_color) print_indented(const char* text, const char* prefix, int max_lines,
const char* color_code, int use_color)
{ {
if (!text) return; if (!text)
return;
const char* p = text; const char* p = text;
int lines = 0; int lines = 0;
while (*p) { while (*p) {
@@ -45,7 +49,8 @@ static void print_indented(const char *text, const char *prefix,
printf("%s%s%.*s%s\n", use_color ? color_code : "", prefix, (int)n, p, printf("%s%s%.*s%s\n", use_color ? color_code : "", prefix, (int)n, p,
use_color ? C_OFF : ""); use_color ? C_OFF : "");
lines++; lines++;
if (!nl) break; if (!nl)
break;
p = nl + 1; p = nl + 1;
} }
if (lines == 0) if (lines == 0)
@@ -57,12 +62,14 @@ static void print_indented(const char *text, const char *prefix,
/* 流式回调 */ /* 流式回调 */
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
typedef struct { typedef struct
{
int printed; int printed;
MdRenderer* md; MdRenderer* md;
} DeltaCtx; } DeltaCtx;
static void on_delta(void *ud, const char *text) static void
on_delta(void* ud, const char* text)
{ {
DeltaCtx* c = ud; DeltaCtx* c = ud;
/* 一出正文就把等待动画收掉, 否则动画会和正文抢同一行 */ /* 一出正文就把等待动画收掉, 否则动画会和正文抢同一行 */
@@ -75,7 +82,8 @@ static void on_delta(void *ud, const char *text)
/* 收到思维链: 说明请求早就发出去了, 模型确实在推理, 而不是卡在网络. /* 收到思维链: 说明请求早就发出去了, 模型确实在推理, 而不是卡在网络.
* 只换标签, 不打印内容 —— 思维链不显示. */ * 只换标签, 不打印内容 —— 思维链不显示. */
static void on_think(void *ud, const char *text) static void
on_think(void* ud, const char* text)
{ {
(void)ud; (void)ud;
(void)text; (void)text;
@@ -93,12 +101,25 @@ char *agent_default_system_prompt(void)
"\n" "\n"
"你可以调用工具来读写文件和执行命令. \n" "你可以调用工具来读写文件和执行命令. \n"
"工作原则: \n" "工作原则: \n"
"1. 先观察再动手: 不确定文件内容或目录结构时, 先用 list_dir / read_file 查看, 不要凭猜测写代码. 搜内容用 run_command (如 grep -rn 模式 目录). \n" "1. 先观察再动手: 不确定文件内容或目录结构时, 先用 list_dir / read_file "
"2. 修改已有文件优先使用 edit_file: 改一处就带上 read_file 显示的行号 line (集合型: 给一个整数, 或给一个整数数组把 old_string 覆盖到的那几行都填进来), 替换只发生在这些行里. 省略 line 等于全文件替换所有匹配, 只在你确实要改全文件时才省. 只有创建新文件或需要整体重写时才用 write_file. \n" "查看, 不要凭猜测写代码. 搜内容用 run_command (如 grep -rn 模式 目录). "
"3. 工具报错时, 先读错误信息定位原因, 再决定换一种方式; 不要重复执行同一个失败的命令. \n" "\n"
"4. 回答简洁直接, 不要把大段代码重复贴回来. 完成任务后用一两句话说明改了什么、验证结果如何. \n" "2. 修改已有文件优先使用 edit_file: 改一处就带上 read_file 显示的行号 "
"5. 不要执行破坏性的操作 (例如删除用户数据、强制推送), 除非用户明确要求. \n" "line (集合型: 给一个整数, 或给一个整数数组把 old_string "
"6. read_file / write_file / edit_file 只处理文本: 二进制文件会被拒绝. 看图片用 read_image, 看其它二进制用 run_command (file / xxd / strings). \n" "覆盖到的那几行都填进来), 替换只发生在这些行里. 省略 line "
"等于全文件替换所有匹配, 只在你确实要改全文件时才省. "
"只有创建新文件或需要整体重写时才用 write_file. \n"
"3. 工具报错时, 先读错误信息定位原因, 再决定换一种方式; "
"不要重复执行同一个失败的命令. \n"
"4. 回答简洁直接, 不要把大段代码重复贴回来. "
"完成任务后用一两句话说明改了什么、验证结果如何. \n"
"5. 不要执行破坏性的操作 (例如删除用户数据、强制推送), "
"除非用户明确要求. "
"\n"
"6. read_file / write_file / edit_file 只处理文本: 二进制文件会被拒绝. "
"看图片用 read_image, 看其它二进制用 run_command (file / xxd / "
"strings). "
"\n"
"\n" "\n"
"除非必要, 否则不要执行命令.\n" "除非必要, 否则不要执行命令.\n"
"除非用户说明, 不得直接或间接(如使用curl)获取任何在线资源.\n"); "除非用户说明, 不得直接或间接(如使用curl)获取任何在线资源.\n");
@@ -133,7 +154,8 @@ void agent_set_color(Agent *a, int enabled)
void agent_free(Agent* a) void agent_free(Agent* a)
{ {
if (!a) return; if (!a)
return;
json_free(a->messages); json_free(a->messages);
json_free(a->tools); json_free(a->tools);
md_free(a->md); md_free(a->md);
@@ -144,7 +166,8 @@ void agent_free(Agent *a)
void agent_add_system(Agent* a, const char* prompt) void agent_add_system(Agent* a, const char* prompt)
{ {
if (!prompt) return; if (!prompt)
return;
JsonValue* m = json_new_object(); JsonValue* m = json_new_object();
json_object_set_string(m, "role", "system"); json_object_set_string(m, "role", "system");
json_object_set_string(m, "content", prompt); json_object_set_string(m, "content", prompt);
@@ -155,7 +178,8 @@ void agent_add_system(Agent *a, const char *prompt)
for (size_t i = 0; i < json_array_size(a->messages); i++) { for (size_t i = 0; i < json_array_size(a->messages); i++) {
JsonValue* old = json_array_get(a->messages, i); JsonValue* old = json_array_get(a->messages, i);
const char* role = json_get_string(old, "role"); const char* role = json_get_string(old, "role");
if (i == 0 && role && strcmp(role, "system") == 0) continue; if (i == 0 && role && strcmp(role, "system") == 0)
continue;
json_array_push(tmp, json_clone(old)); json_array_push(tmp, json_clone(old));
} }
json_free(a->messages); json_free(a->messages);
@@ -206,14 +230,21 @@ void agent_reset(Agent *a)
a->started_at = time(NULL); a->started_at = time(NULL);
JsonValue* keep = NULL; JsonValue* keep = NULL;
JsonValue* first = json_array_get(a->messages, 0); JsonValue* first = json_array_get(a->messages, 0);
if (first && strcmp(json_get_string(first, "role") ? json_get_string(first, "role") : "", "system") == 0) if (first
&& strcmp(json_get_string(first, "role")
? json_get_string(first, "role")
: "",
"system")
== 0)
keep = json_clone(first); keep = json_clone(first);
json_free(a->messages); json_free(a->messages);
a->messages = json_new_array(); a->messages = json_new_array();
if (keep) json_array_push(a->messages, keep); if (keep)
json_array_push(a->messages, keep);
} }
size_t agent_history_len(const Agent *a) size_t
agent_history_len(const Agent* a)
{ {
return json_array_size(a->messages); return json_array_size(a->messages);
} }
@@ -226,26 +257,32 @@ size_t agent_history_len(const Agent *a)
* *
* 内部判断"历史变了没有"仍然用 agent_history_len (数组真实长度)—— 那是在比 * 内部判断"历史变了没有"仍然用 agent_history_len (数组真实长度)—— 那是在比
* 大小, 不是在报数. */ * 大小, 不是在报数. */
size_t agent_conversation_len(const Agent *a) size_t
agent_conversation_len(const Agent* a)
{ {
size_t n = json_array_size(a->messages); size_t n = json_array_size(a->messages);
if (n > 0 && role_is(json_array_get(a->messages, 0), "system")) n--; if (n > 0 && role_is(json_array_get(a->messages, 0), "system"))
n--;
return n; return n;
} }
JsonValue *agent_take_last_round(Agent *a) JsonValue*
agent_take_last_round(Agent* a)
{ {
JsonValue* msgs = a->messages; JsonValue* msgs = a->messages;
size_t n = json_array_size(msgs); size_t n = json_array_size(msgs);
size_t start = 0; size_t start = 0;
if (n > 0 && role_is(json_array_get(msgs, 0), "system")) start = 1; if (n > 0 && role_is(json_array_get(msgs, 0), "system"))
start = 1;
/* 最后一条"开启一轮"的 user 是本轮的起点 */ /* 最后一条"开启一轮"的 user 是本轮的起点 */
size_t last_user = (size_t)-1; size_t last_user = (size_t)-1;
for (size_t i = start; i < n; i++) for (size_t i = start; i < n; i++)
if (agent_opens_round(json_array_get(msgs, i))) last_user = i; if (agent_opens_round(json_array_get(msgs, i)))
if (last_user == (size_t)-1 || last_user < start) return NULL; last_user = i;
if (last_user == (size_t)-1 || last_user < start)
return NULL;
JsonValue* removed = json_new_array(); JsonValue* removed = json_new_array();
JsonValue** items = msgs->u.array.items; JsonValue** items = msgs->u.array.items;
@@ -261,16 +298,19 @@ void agent_replace_history(Agent *a, JsonValue *messages)
/* 当前生效的 system 提示词要保留 */ /* 当前生效的 system 提示词要保留 */
JsonValue* sys = NULL; JsonValue* sys = NULL;
JsonValue* first = json_array_get(a->messages, 0); JsonValue* first = json_array_get(a->messages, 0);
if (first && role_is(first, "system")) sys = json_clone(first); if (first && role_is(first, "system"))
sys = json_clone(first);
json_free(a->messages); json_free(a->messages);
a->messages = json_new_array(); a->messages = json_new_array();
if (sys) json_array_push(a->messages, sys); if (sys)
json_array_push(a->messages, sys);
if (messages && messages->type == JSON_ARRAY) { if (messages && messages->type == JSON_ARRAY) {
for (size_t i = 0; i < json_array_size(messages); i++) { for (size_t i = 0; i < json_array_size(messages); i++) {
JsonValue* m = json_array_get(messages, i); JsonValue* m = json_array_get(messages, i);
if (role_is(m, "system")) continue; /* 存档里的 system 不带进来 */ if (role_is(m, "system"))
continue; /* 存档里的 system 不带进来 */
json_array_push(a->messages, json_clone(m)); json_array_push(a->messages, json_clone(m));
} }
} }
@@ -289,7 +329,8 @@ int agent_is_http_4xx(const char *err)
return err && strstr(err, "HTTP 4") != NULL; return err && strstr(err, "HTTP 4") != NULL;
} }
static int role_is(const JsonValue *m, const char *want) static int
role_is(const JsonValue* m, const char* want)
{ {
const char* r = json_get_string(m, "role"); const char* r = json_get_string(m, "role");
return r && strcmp(r, want) == 0; return r && strcmp(r, want) == 0;
@@ -305,12 +346,15 @@ static int role_is(const JsonValue *m, const char *want)
* 判据很直白: content 是数组、且里面一个 text 块都没有. */ * 判据很直白: content 是数组、且里面一个 text 块都没有. */
int agent_opens_round(const JsonValue* m) int agent_opens_round(const JsonValue* m)
{ {
if (!role_is(m, "user")) return 0; if (!role_is(m, "user"))
return 0;
JsonValue* c = json_object_get(m, "content"); JsonValue* c = json_object_get(m, "content");
if (!c || c->type != JSON_ARRAY) return 1; /* 纯字符串 = 真的一轮 */ if (!c || c->type != JSON_ARRAY)
return 1; /* 纯字符串 = 真的一轮 */
for (size_t i = 0; i < json_array_size(c); i++) { for (size_t i = 0; i < json_array_size(c); i++) {
const char* t = json_get_string(json_array_get(c, i), "type"); const char* t = json_get_string(json_array_get(c, i), "type");
if (t && strcmp(t, "text") == 0) return 1; if (t && strcmp(t, "text") == 0)
return 1;
} }
return 0; /* 只有附件 */ return 0; /* 只有附件 */
} }
@@ -330,7 +374,8 @@ int agent_opens_round(const JsonValue *m)
* *
* 现在这里只摆最要紧的那一两项 (改动的具体内容交给 show_edit_diff 去对照), * 现在这里只摆最要紧的那一两项 (改动的具体内容交给 show_edit_diff 去对照),
* 参数解析不出来 (模型给了非法 JSON)时就只有工具名 —— 原因在结果里说. */ * 参数解析不出来 (模型给了非法 JSON)时就只有工具名 —— 原因在结果里说. */
static void show_tool_call(const Agent *a, const char *name, const JsonValue *args) static void
show_tool_call(const Agent* a, const char* name, const JsonValue* args)
{ {
char shown[400] = ""; char shown[400] = "";
const char* path = args ? json_get_string(args, "path") : NULL; const char* path = args ? json_get_string(args, "path") : NULL;
@@ -350,8 +395,9 @@ static void show_tool_call(const Agent *a, const char *name, const JsonValue *ar
} else if (args && strcmp(name, TOOL_WRITE_FILE) == 0) { } else if (args && strcmp(name, TOOL_WRITE_FILE) == 0) {
const char* content = json_get_string(args, "content"); const char* content = json_get_string(args, "content");
snprintf(shown, sizeof shown, "%s", snprintf(shown, sizeof shown, "%s",
tf("agent.call.write", "path", path ? path : "?", tf("agent.call.write", "path", path ? path : "?", "n",
"n", i18n_num((long long)(content ? strlen(content) : 0)), NULL)); i18n_num((long long)(content ? strlen(content) : 0)),
NULL));
} else if (args && strcmp(name, TOOL_RUN_COMMAND) == 0) { } else if (args && strcmp(name, TOOL_RUN_COMMAND) == 0) {
/* 提权必须一眼看见: 这条命令会以 root 跑 */ /* 提权必须一眼看见: 这条命令会以 root 跑 */
const char* cmd = json_get_string(args, "command"); const char* cmd = json_get_string(args, "command");
@@ -376,15 +422,19 @@ static void show_tool_call(const Agent *a, const char *name, const JsonValue *ar
/* 压成一行 */ /* 压成一行 */
for (char* p = shown; *p; p++) for (char* p = shown; *p; p++)
if (*p == '\n' || *p == '\r' || *p == '\t') *p = ' '; if (*p == '\n' || *p == '\r' || *p == '\t')
*p = ' ';
printf("%s%s{/} %s%s", col(a, C_DIM), col(a, C_CYAN), name, col(a, C_OFF)); printf("%s%s{/} %s%s", col(a, C_DIM), col(a, C_CYAN), name,
if (shown[0]) printf("%s %s%s", col(a, C_DIM), shown, col(a, C_OFF)); col(a, C_OFF));
if (shown[0])
printf("%s %s%s", col(a, C_DIM), shown, col(a, C_OFF));
putchar('\n'); putchar('\n');
fflush(stdout); fflush(stdout);
} }
static void show_tool_result(const Agent *a, const char *name, const JsonValue *args, static void
show_tool_result(const Agent* a, const char* name, const JsonValue* args,
const char* result, int is_error, int diff_shown) const char* result, int is_error, int diff_shown)
{ {
if (!a->show_tool_output) { if (!a->show_tool_output) {
@@ -400,8 +450,8 @@ static void show_tool_result(const Agent *a, const char *name, const JsonValue *
* *
* 但**确认提示里已经摆过一次** (默认就开着确认), 就不再重复第二遍: 同一个 * 但**确认提示里已经摆过一次** (默认就开着确认), 就不再重复第二遍: 同一个
* 东西在一屏里出现两次只会让人怀疑"是不是要改两次". */ * 东西在一屏里出现两次只会让人怀疑"是不是要改两次". */
if (!is_error && !diff_shown && name && strcmp(name, TOOL_EDIT_FILE) == 0 && if (!is_error && !diff_shown && name && strcmp(name, TOOL_EDIT_FILE) == 0
args && tool_render_diff(stdout, args, " │ ", a->color)) { && args && tool_render_diff(stdout, args, " │ ", a->color)) {
fflush(stdout); fflush(stdout);
return; return;
} }
@@ -411,20 +461,25 @@ static void show_tool_result(const Agent *a, const char *name, const JsonValue *
} }
/* 执行一批 tool_calls, 把结果作为 tool 消息追加到历史 */ /* 执行一批 tool_calls, 把结果作为 tool 消息追加到历史 */
static int execute_tool_calls(Agent *a, JsonValue *tool_calls) static int
execute_tool_calls(Agent* a, JsonValue* tool_calls)
{ {
size_t count = json_array_size(tool_calls); size_t count = json_array_size(tool_calls);
size_t interrupted_at = count; /* 中断发生在第几个调用之前 */ size_t interrupted_at = count; /* 中断发生在第几个调用之前 */
for (size_t i = 0; i < count; i++) { for (size_t i = 0; i < count; i++) {
if (g_ligent_interrupt) { interrupted_at = i; break; } if (g_ligent_interrupt) {
interrupted_at = i;
break;
}
JsonValue* tc = json_array_get(tool_calls, i); JsonValue* tc = json_array_get(tool_calls, i);
const char* id = json_get_string(tc, "id"); const char* id = json_get_string(tc, "id");
JsonValue* fn = json_object_get(tc, "function"); JsonValue* fn = json_object_get(tc, "function");
const char* name = fn ? json_get_string(fn, "name") : NULL; const char* name = fn ? json_get_string(fn, "name") : NULL;
const char* args_raw = fn ? json_get_string(fn, "arguments") : NULL; const char* args_raw = fn ? json_get_string(fn, "arguments") : NULL;
if (!args_raw) args_raw = "{}"; if (!args_raw)
args_raw = "{}";
char jerr[256]; char jerr[256];
JsonValue* args = json_parse(args_raw, jerr, sizeof jerr); JsonValue* args = json_parse(args_raw, jerr, sizeof jerr);
@@ -438,13 +493,15 @@ static int execute_tool_calls(Agent *a, JsonValue *tool_calls)
if (!args) { if (!args) {
result = xasprintf("错误: 工具参数不是合法 JSON (%s)", jerr); result = xasprintf("错误: 工具参数不是合法 JSON (%s)", jerr);
is_error = 1; is_error = 1;
} else if (a->confirm && a->is_mutating(name) && !confirm_tool(name, args)) { } else if (a->confirm && a->is_mutating(name)
&& !confirm_tool(name, args)) {
/* 拒绝不等于失败: 把结果回填给模型, 让它换个做法, /* 拒绝不等于失败: 把结果回填给模型, 让它换个做法,
* 而不是让整轮对话就此中断 */ * 而不是让整轮对话就此中断 */
if (confirm_turn_mode() == CONFIRM_NEVER) if (confirm_turn_mode() == CONFIRM_NEVER)
/* 用户在确认提示上选了 neVer = 本轮别再改了. /* 用户在确认提示上选了 neVer = 本轮别再改了.
* 话说死一点, 否则模型会一个接一个地试下去. */ * 话说死一点, 否则模型会一个接一个地试下去. */
result = xstrdup("用户已表示这一轮不再执行任何改动. " result = xstrdup(
"用户已表示这一轮不再执行任何改动. "
"不要再尝试改动, 直接说明你的结论或需要用户做什么. "); "不要再尝试改动, 直接说明你的结论或需要用户做什么. ");
else else
result = xstrdup("用户拒绝了这次操作. 请换一种不改动磁盘的做法, " result = xstrdup("用户拒绝了这次操作. 请换一种不改动磁盘的做法, "
@@ -457,7 +514,8 @@ static int execute_tool_calls(Agent *a, JsonValue *tool_calls)
result = a->exec_tool(name, args, &is_error); result = a->exec_tool(name, args, &is_error);
} }
/* args 要活到 show_tool_result 之后: edit_file 的新旧对照就是从这里取的 */ /* args 要活到 show_tool_result 之后: edit_file 的新旧对照就是从这里取的
*/
show_tool_result(a, name, args, result, is_error, diff_shown); show_tool_result(a, name, args, result, is_error, diff_shown);
json_free(args); json_free(args);
@@ -483,8 +541,8 @@ static int execute_tool_calls(Agent *a, JsonValue *tool_calls)
JsonValue* tm = json_new_object(); JsonValue* tm = json_new_object();
json_object_set_string(tm, "role", "tool"); json_object_set_string(tm, "role", "tool");
json_object_set_string(tm, "tool_call_id", id ? id : ""); json_object_set_string(tm, "tool_call_id", id ? id : "");
json_object_set_string(tm, "content", json_object_set_string(
"错误: 用户中断了这一轮, 本次调用没有执行. "); tm, "content", "错误: 用户中断了这一轮, 本次调用没有执行. ");
json_array_push(a->messages, tm); json_array_push(a->messages, tm);
} }
} }
@@ -534,23 +592,29 @@ static int execute_tool_calls(Agent *a, JsonValue *tool_calls)
* 模型明确不收图片时把 read_image 摘掉 —— 留着它只会让模型白调一次, * 模型明确不收图片时把 read_image 摘掉 —— 留着它只会让模型白调一次,
* 然后请求被 400 (虽然我们还有降级兜底, 但那是补救, 不如不给). * 然后请求被 400 (虽然我们还有降级兜底, 但那是补救, 不如不给).
* 模型明确不收 tools 时整张表都不发: 否则每一轮都会以同样的 400 失败. */ * 模型明确不收 tools 时整张表都不发: 否则每一轮都会以同样的 400 失败. */
static JsonValue *tools_for_request(const Agent *a) static JsonValue*
tools_for_request(const Agent* a)
{ {
if (!a->tools || json_array_size(a->tools) == 0) return NULL; if (!a->tools || json_array_size(a->tools) == 0)
if (a->caps.tools == CAP_NO) return NULL; return NULL;
if (a->caps.tools == CAP_NO)
return NULL;
JsonValue* t = json_clone(a->tools); JsonValue* t = json_clone(a->tools);
if (a->caps.vision == CAP_NO) tools_drop(t, TOOL_READ_IMAGE); if (a->caps.vision == CAP_NO)
tools_drop(t, TOOL_READ_IMAGE);
return t; return t;
} }
/* 一次请求要用的一切: 消息副本 (图片引用已展开)+ 收敛过的工具表. /* 一次请求要用的一切: 消息副本 (图片引用已展开)+ 收敛过的工具表.
* 两者都是新分配的, 用完一起释放 —— 三处 llm_chat 调用点各来一份. */ * 两者都是新分配的, 用完一起释放 —— 三处 llm_chat 调用点各来一份. */
typedef struct { typedef struct
{
JsonValue* messages; JsonValue* messages;
JsonValue* tools; JsonValue* tools;
} Req; } Req;
static Req req_prepare(Agent *a) static Req
req_prepare(Agent* a)
{ {
Req r; Req r;
@@ -584,7 +648,8 @@ static Req req_prepare(Agent *a)
return r; return r;
} }
static void req_free(Req *r) static void
req_free(Req* r)
{ {
json_free(r->messages); json_free(r->messages);
json_free(r->tools); json_free(r->tools);
@@ -604,12 +669,19 @@ static void req_free(Req *r)
* 所以发请求之前统一体检一次: 缺的补占位、孤儿 tool 消息丢掉, 并如实说明. * 所以发请求之前统一体检一次: 缺的补占位、孤儿 tool 消息丢掉, 并如实说明.
* *
* 没问题返回 NULL; 修过则返回重建好的数组 (调用方替换旧的). */ * 没问题返回 NULL; 修过则返回重建好的数组 (调用方替换旧的). */
JsonValue *agent_repair_tool_pairing(const JsonValue *messages, int *out_fixed) JsonValue*
agent_repair_tool_pairing(const JsonValue* messages, int* out_fixed)
{ {
if (out_fixed) *out_fixed = 0; if (out_fixed)
if (!messages || messages->type != JSON_ARRAY) return NULL; *out_fixed = 0;
if (!messages || messages->type != JSON_ARRAY)
return NULL;
typedef struct { char *id; int answered; } Pending; typedef struct
{
char* id;
int answered;
} Pending;
Pending* pend = NULL; Pending* pend = NULL;
size_t npend = 0; size_t npend = 0;
JsonValue* out = json_new_array(); JsonValue* out = json_new_array();
@@ -619,18 +691,23 @@ JsonValue *agent_repair_tool_pairing(const JsonValue *messages, int *out_fixed)
#define FLUSH_PENDING(where) \ #define FLUSH_PENDING(where) \
do { \ do { \
for (size_t k_ = 0; k_ < npend; k_++) { \ for (size_t k_ = 0; k_ < npend; k_++) { \
if (pend[k_].answered) continue; \ if (pend[k_].answered) \
continue; \
JsonValue* tm_ = json_new_object(); \ JsonValue* tm_ = json_new_object(); \
json_object_set_string(tm_, "role", "tool"); \ json_object_set_string(tm_, "role", "tool"); \
json_object_set_string(tm_, "tool_call_id", pend[k_].id); \ json_object_set_string(tm_, "tool_call_id", pend[k_].id); \
json_object_set_string(tm_, "content", \ json_object_set_string( \
tm_, "content", \
"错误: 这条工具调用没有得到结果 (历史结构被改坏了, " \ "错误: 这条工具调用没有得到结果 (历史结构被改坏了, " \
"这里补一条占位). 不要假设它执行过. "); \ "这里补一条占位). 不要假设它执行过. "); \
json_array_push(out, tm_); \ json_array_push(out, tm_); \
fixed++; \ fixed++; \
} \ } \
for (size_t k_ = 0; k_ < npend; k_++) free(pend[k_].id); \ for (size_t k_ = 0; k_ < npend; k_++) \
free(pend); pend = NULL; npend = 0; \ free(pend[k_].id); \
free(pend); \
pend = NULL; \
npend = 0; \
} while (0) } while (0)
for (size_t i = 0; i < json_array_size(messages); i++) { for (size_t i = 0; i < json_array_size(messages); i++) {
@@ -642,8 +719,14 @@ JsonValue *agent_repair_tool_pairing(const JsonValue *messages, int *out_fixed)
const char* id = json_get_string(m, "tool_call_id"); const char* id = json_get_string(m, "tool_call_id");
size_t hit = (size_t)-1; size_t hit = (size_t)-1;
for (size_t k = 0; k < npend; k++) for (size_t k = 0; k < npend; k++)
if (!pend[k].answered && id && strcmp(pend[k].id, id) == 0) { hit = k; break; } if (!pend[k].answered && id && strcmp(pend[k].id, id) == 0) {
if (hit == (size_t)-1) { fixed++; continue; } hit = k;
break;
}
if (hit == (size_t)-1) {
fixed++;
continue;
}
pend[hit].answered = 1; pend[hit].answered = 1;
json_array_push(out, json_clone(m)); json_array_push(out, json_clone(m));
continue; continue;
@@ -654,12 +737,15 @@ JsonValue *agent_repair_tool_pairing(const JsonValue *messages, int *out_fixed)
json_array_push(out, json_clone(m)); json_array_push(out, json_clone(m));
if (!role || strcmp(role, "assistant") != 0) continue; if (!role || strcmp(role, "assistant") != 0)
continue;
JsonValue* tcs = json_object_get(m, "tool_calls"); JsonValue* tcs = json_object_get(m, "tool_calls");
if (!tcs || tcs->type != JSON_ARRAY) continue; if (!tcs || tcs->type != JSON_ARRAY)
continue;
for (size_t k = 0; k < json_array_size(tcs); k++) { for (size_t k = 0; k < json_array_size(tcs); k++) {
const char* id = json_get_string(json_array_get(tcs, k), "id"); const char* id = json_get_string(json_array_get(tcs, k), "id");
if (!id || !*id) continue; if (!id || !*id)
continue;
pend = xrealloc(pend, (npend + 1) * sizeof *pend); pend = xrealloc(pend, (npend + 1) * sizeof *pend);
pend[npend].id = xstrdup(id); pend[npend].id = xstrdup(id);
pend[npend].answered = 0; pend[npend].answered = 0;
@@ -669,8 +755,12 @@ JsonValue *agent_repair_tool_pairing(const JsonValue *messages, int *out_fixed)
FLUSH_PENDING(); FLUSH_PENDING();
#undef FLUSH_PENDING #undef FLUSH_PENDING
if (fixed == 0) { json_free(out); return NULL; } if (fixed == 0) {
if (out_fixed) *out_fixed = fixed; json_free(out);
return NULL;
}
if (out_fixed)
*out_fixed = fixed;
return out; return out;
} }
@@ -684,15 +774,17 @@ JsonValue *agent_repair_tool_pairing(const JsonValue *messages, int *out_fixed)
* 1. 它是**兜底**, 主要防线是发送前就把格式和体积查掉; * 1. 它是**兜底**, 主要防线是发送前就把格式和体积查掉;
* 2. 关键词只收那些几乎不可能出现在别处的词; * 2. 关键词只收那些几乎不可能出现在别处的词;
* 3. 不确定时**不动手** —— 宁可让错误如实报出来, 也不误删用户的图. */ * 3. 不确定时**不动手** —— 宁可让错误如实报出来, 也不误删用户的图. */
static int err_mentions_image(const char *err) static int
err_mentions_image(const char* err)
{ {
static const char *kw[] = { static const char* kw[] = { "image", "vision", "multimodal",
"image", "vision", "multimodal", "content type", "media type", "content type", "media type", "图片",
"图片", "图像", "模态", NULL "图像", "模态", NULL };
}; if (!err)
if (!err) return 0; return 0;
for (size_t i = 0; kw[i]; i++) for (size_t i = 0; kw[i]; i++)
if (strcasestr(err, kw[i]) != NULL) return 1; if (strcasestr(err, kw[i]) != NULL)
return 1;
return 0; return 0;
} }
@@ -706,26 +798,29 @@ static int err_mentions_image(const char *err)
* 两条理由不同: 开关说的是"启动时别自动探测", 不是"禁止探测"; 而缓存是 * 两条理由不同: 开关说的是"启动时别自动探测", 不是"禁止探测"; 而缓存是
* "这次进程里已经测过" —— 用户显式敲了 /probe, 就该真的重测一遍, 否则 * "这次进程里已经测过" —— 用户显式敲了 /probe, 就该真的重测一遍, 否则
* 敲了等于没敲. */ * 敲了等于没敲. */
static void ensure_caps(Agent *a, int force) static void
ensure_caps(Agent* a, int force)
{ {
if (!force && !a->probe_caps) return; /* 关掉探测: 不下任何结论, 一切照旧放行 */ if (!force && !a->probe_caps)
if (force) cap_cache_clear(); /* 手动触发不吃缓存 */ return; /* 关掉探测: 不下任何结论, 一切照旧放行 */
if (force)
cap_cache_clear(); /* 手动触发不吃缓存 */
char cerr[256] = ""; char cerr[256] = "";
spinner_start(t("spinner.probe")); spinner_start(t("spinner.probe"));
int probed = cap_get(&a->llm, 1, &a->caps, a->llm.verbose, cerr, sizeof cerr); int probed
= cap_get(&a->llm, 1, &a->caps, a->llm.verbose, cerr, sizeof cerr);
spinner_stop(); spinner_stop();
if (!probed) return; /* 命中缓存, 不用再啰嗦一遍 */ if (!probed)
return; /* 命中缓存, 不用再啰嗦一遍 */
/* 一行灰字把结论说清楚. 探测到"不支持"时把**因此做了什么**也写在同一行里, /* 一行灰字把结论说清楚. 探测到"不支持"时把**因此做了什么**也写在同一行里,
* 否则用户只看到"不支持", 不知道我们顺手把工具藏了. */ * 否则用户只看到"不支持", 不知道我们顺手把工具藏了. */
const char *vis = const char* vis = a->caps.vision == CAP_YES ? t("agent.caps.yes")
a->caps.vision == CAP_YES ? t("agent.caps.yes") : : a->caps.vision == CAP_NO ? t("agent.caps.no.vision")
a->caps.vision == CAP_NO ? t("agent.caps.no.vision")
: t("agent.caps.unknown"); : t("agent.caps.unknown");
const char *tl = const char* tl = a->caps.tools == CAP_YES ? t("agent.caps.yes")
a->caps.tools == CAP_YES ? t("agent.caps.yes") : : a->caps.tools == CAP_NO ? t("agent.caps.no.tools")
a->caps.tools == CAP_NO ? t("agent.caps.no.tools")
: t("agent.caps.unknown"); : t("agent.caps.unknown");
char note[320] = ""; char note[320] = "";
@@ -734,13 +829,20 @@ static void ensure_caps(Agent *a, int force)
tf("agent.caps.note", "why", tf("agent.caps.note", "why",
cerr[0] ? cerr : t("agent.caps.baseline_failed"), NULL)); cerr[0] ? cerr : t("agent.caps.baseline_failed"), NULL));
fprintf(stderr, "%s%s%s\n", col(a, C_DIM), fprintf(
stderr, "%s%s%s\n", col(a, C_DIM),
tf("agent.caps.line", "vision", vis, "tools", tl, "note", note, NULL), tf("agent.caps.line", "vision", vis, "tools", tl, "note", note, NULL),
col(a, C_OFF)); col(a, C_OFF));
} }
void agent_ensure_caps(Agent *a) { ensure_caps(a, 0); } void agent_ensure_caps(Agent* a)
void agent_probe_now(Agent *a) { ensure_caps(a, 1); } {
ensure_caps(a, 0);
}
void agent_probe_now(Agent* a)
{
ensure_caps(a, 1);
}
int agent_run(Agent* a) int agent_run(Agent* a)
{ {
@@ -753,7 +855,6 @@ int agent_run(Agent *a)
agent_ensure_caps(a); agent_ensure_caps(a);
/* max_steps <= 0 时不限往返次数 (默认). 计数用 long long: 不限次数的会话 /* max_steps <= 0 时不限往返次数 (默认). 计数用 long long: 不限次数的会话
* 真的可能跑很多步, 而 int 溢出是 UB. */ * 真的可能跑很多步, 而 int 溢出是 UB. */
for (long long step = 0;; step++) { for (long long step = 0;; step++) {
@@ -763,10 +864,9 @@ int agent_run(Agent *a)
spinner_start(t("spinner.waiting")); spinner_start(t("spinner.waiting"));
Req req = req_prepare(a); Req req = req_prepare(a);
LlmResponse *resp = llm_chat(&a->llm, req.messages, req.tools, LlmResponse* resp = llm_chat(
a->llm.stream ? on_delta : NULL, &dc, &a->llm, req.messages, req.tools, a->llm.stream ? on_delta : NULL,
a->llm.stream ? on_think : NULL, NULL, &dc, a->llm.stream ? on_think : NULL, NULL, err, sizeof err);
err, sizeof err);
req_free(&req); req_free(&req);
spinner_stop(); spinner_stop();
@@ -777,7 +877,8 @@ int agent_run(Agent *a)
* *
* 1. 判断依据只是错误文案里的关键词 ("too long"、"上下文"、"超出"…), * 1. 判断依据只是错误文案里的关键词 ("too long"、"上下文"、"超出"…),
* 任何 4xx 蹭到这些词都会触发裁剪 —— 而裁剪是把最老的几轮从历史里 * 任何 4xx 蹭到这些词都会触发裁剪 —— 而裁剪是把最老的几轮从历史里
* **真的删掉**, 不可逆. 用一句猜出来的判据去毁掉对话记录, 代价不对等. * **真的删掉**, 不可逆. 用一句猜出来的判据去毁掉对话记录,
* 代价不对等.
* 2. 它把"历史太大"这个本该由用户决定的事 (/back 摘掉哪一轮、/reset * 2. 它把"历史太大"这个本该由用户决定的事 (/back 摘掉哪一轮、/reset
* 重来、还是换个窗口更大的模型)自动化掉了, 用户看到的是对话莫名其妙 * 重来、还是换个窗口更大的模型)自动化掉了, 用户看到的是对话莫名其妙
* 少了几轮. * 少了几轮.
@@ -790,9 +891,8 @@ int agent_run(Agent *a)
* 这个变体不支持……). 那条引用躺在历史里, 不修的话之后每一轮都会以 * 这个变体不支持……). 那条引用躺在历史里, 不修的话之后每一轮都会以
* 同样的 4xx 失败、历史却原样保留 —— 用户只剩 /reset 一条路. * 同样的 4xx 失败、历史却原样保留 —— 用户只剩 /reset 一条路.
* 这里宁可丢掉这些图, 也不能让整段对话死在这儿. */ * 这里宁可丢掉这些图, 也不能让整段对话死在这儿. */
if (!resp && !dc.printed && !img_dropped && if (!resp && !dc.printed && !img_dropped && agent_is_http_4xx(err)
agent_is_http_4xx(err) && image_has_refs(a->messages) && && image_has_refs(a->messages) && err_mentions_image(err)) {
err_mentions_image(err)) {
int n = image_degrade_all(a->messages); int n = image_degrade_all(a->messages);
if (n > 0) { if (n > 0) {
img_dropped = 1; img_dropped = 1;
@@ -810,8 +910,8 @@ int agent_run(Agent *a)
Req req3 = req_prepare(a); Req req3 = req_prepare(a);
resp = llm_chat(&a->llm, req3.messages, req3.tools, resp = llm_chat(&a->llm, req3.messages, req3.tools,
a->llm.stream ? on_delta : NULL, &dc, a->llm.stream ? on_delta : NULL, &dc,
a->llm.stream ? on_think : NULL, NULL, a->llm.stream ? on_think : NULL, NULL, err,
err, sizeof err); sizeof err);
req_free(&req3); req_free(&req3);
spinner_stop(); spinner_stop();
} }
@@ -826,7 +926,8 @@ int agent_run(Agent *a)
if (resp->interrupted) { if (resp->interrupted) {
md_finish(a->md); md_finish(a->md);
if (dc.printed) fputc('\n', stdout); if (dc.printed)
fputc('\n', stdout);
printf("%s%s%s\n", col(a, C_DIM), t("agent.interrupted"), printf("%s%s%s\n", col(a, C_DIM), t("agent.interrupted"),
col(a, C_OFF)); col(a, C_OFF));
/* 把已经生成的部分内容留在历史里, 保持上下文连贯 */ /* 把已经生成的部分内容留在历史里, 保持上下文连贯 */
@@ -850,7 +951,8 @@ int agent_run(Agent *a)
} }
} }
md_finish(a->md); md_finish(a->md);
if (wrote_text) fputc('\n', stdout); if (wrote_text)
fputc('\n', stdout);
fflush(stdout); fflush(stdout);
if (resp->incomplete) { if (resp->incomplete) {
@@ -862,10 +964,14 @@ int agent_run(Agent *a)
} }
if (resp->usage) { if (resp->usage) {
a->tok_prompt += (long long)json_get_number(resp->usage, "prompt_tokens", 0); a->tok_prompt
a->tok_completion += (long long)json_get_number(resp->usage, "completion_tokens", 0); += (long long)json_get_number(resp->usage, "prompt_tokens", 0);
a->tok_total += (long long)json_get_number(resp->usage, "total_tokens", 0); a->tok_completion += (long long)json_get_number(
a->tok_last_prompt = (long long)json_get_number(resp->usage, "prompt_tokens", 0); resp->usage, "completion_tokens", 0);
a->tok_total
+= (long long)json_get_number(resp->usage, "total_tokens", 0);
a->tok_last_prompt
= (long long)json_get_number(resp->usage, "prompt_tokens", 0);
a->usage_reported = 1; a->usage_reported = 1;
} }
a->api_calls++; a->api_calls++;
@@ -873,16 +979,18 @@ int agent_run(Agent *a)
json_array_push(a->messages, json_clone(resp->message)); json_array_push(a->messages, json_clone(resp->message));
JsonValue* tool_calls = json_object_get(resp->message, "tool_calls"); JsonValue* tool_calls = json_object_get(resp->message, "tool_calls");
int has_tools = a->use_tools && tool_calls && int has_tools
json_array_size(tool_calls) > 0; = a->use_tools && tool_calls && json_array_size(tool_calls) > 0;
if (has_tools) tool_calls = json_clone(tool_calls); if (has_tools)
tool_calls = json_clone(tool_calls);
llm_response_free(resp); llm_response_free(resp);
if (!has_tools) { if (!has_tools) {
if (step == 0 && a->llm.stream && !dc.printed) { if (step == 0 && a->llm.stream && !dc.printed) {
/* 既没输出也没调工具, 提示一下, 避免看起来像卡死 */ /* 既没输出也没调工具, 提示一下, 避免看起来像卡死 */
const char* c = json_get_string( const char* c = json_get_string(
json_array_get(a->messages, json_array_size(a->messages) - 1), json_array_get(a->messages,
json_array_size(a->messages) - 1),
"content"); "content");
if (!c || !*c) if (!c || !*c)
printf("%s%s%s\n", col(a, C_DIM), t("agent.empty_reply"), printf("%s%s%s\n", col(a, C_DIM), t("agent.empty_reply"),
@@ -893,7 +1001,8 @@ int agent_run(Agent *a)
int rc = execute_tool_calls(a, tool_calls); int rc = execute_tool_calls(a, tool_calls);
json_free(tool_calls); json_free(tool_calls);
if (rc != 0) return 1; if (rc != 0)
return 1;
/* 只有设了正数上限才可能"到顶". 不限时这一轮一直跑到模型自己收尾 /* 只有设了正数上限才可能"到顶". 不限时这一轮一直跑到模型自己收尾
* (或用户 Ctrl-C)—— 每一次工具调用都会回到这个循环顶端, 中断检查 * (或用户 Ctrl-C)—— 每一次工具调用都会回到这个循环顶端, 中断检查
+4 -4
View File
@@ -684,8 +684,8 @@ M("agent.caps.baseline_failed",
"the baseline request did not go through", "the baseline request did not go through",
"基线请求未通过"), "基线请求未通过"),
M("agent.caps.line", M("agent.caps.line",
"model capabilities: multimodal (image input) {vision} * tool calls {tools}{note}", "model capabilities: \n image input {vision} \n tool calls {tools}{note}",
"模型能力: 多模态 (图片输入){vision} * 工具调用 {tools}{note}"), "模型能力: \n 图片输入 {vision} \n 工具调用 {tools}{note}"),
M("agent.caps.no.tools", M("agent.caps.no.tools",
"no (tool table no longer sent)", "no (tool table no longer sent)",
"不支持 (不再发送工具表)"), "不支持 (不再发送工具表)"),
@@ -694,7 +694,7 @@ M("agent.caps.no.vision",
"不支持 (已隐藏 read_image)"), "不支持 (已隐藏 read_image)"),
M("agent.caps.note", M("agent.caps.note",
" — anything untested is treated as supported ({why})", " — anything untested is treated as supported ({why})",
" —— 没测出来的一律按「支持」处理 ({why})"), " —- 没测出来的一律按 \"支持\" 处理 ({why})"),
M("agent.caps.unknown", M("agent.caps.unknown",
"untested", "untested",
"未测"), "未测"),
@@ -1141,7 +1141,7 @@ M("meta.help",
"/help 显示本帮助\n" "/help 显示本帮助\n"
"\n" "\n"
"其余输入都是给 meta 模型的指令, 例如: \n" "其余输入都是给 meta 模型的指令, 例如: \n"
" 把第 3 轮里我说的 "用 Python" 删掉\n" " 把第 3 轮里我说的 \"用 Python\" 删掉\n"
" 第 5 轮助手那个结论是错的, 改掉\n" " 第 5 轮助手那个结论是错的, 改掉\n"
" 第 2 到 4 轮在绕圈子, 压成一段\n"), " 第 2 到 4 轮在绕圈子, 压成一段\n"),
M("meta.turn_failed", M("meta.turn_failed",
+1 -1
View File
@@ -45,7 +45,7 @@ const char *t(const char *key);
/* 带占位符: 模板里写 {name}, 调用时按名字成对给值, 最后传 NULL. /* 带占位符: 模板里写 {name}, 调用时按名字成对给值, 最后传 NULL.
* tf("err.unknown_option", "option", arg, NULL) * tf("err.unknown_option", "option", arg, NULL)
* 按名字匹配 (不是按位置), 所以译文可以调整语序. */ * 按名字而非位置匹配, 所以译文可以调整语序. */
const char *tf(const char *key, ...); const char *tf(const char *key, ...);
/* 整数转字符串 (静态轮转缓冲), 专供 tf 的参数位置用: /* 整数转字符串 (静态轮转缓冲), 专供 tf 的参数位置用:
+15 -28
View File
@@ -6,47 +6,34 @@
*/ */
#include "markdown.h" #include "markdown.h"
#include "util.h" #include "util.h"
#include "sgr.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
/* 样式定义 (硬编码 SGR, 不依赖 terminfo) #define MD_RESET C_OFF
* #define MD_BOLD C_BOLD
* **只用 16 色范围内的码**: 前景 30-37, 加粗/暗淡/斜体用 1/2/3. #define MD_DIM C_DIM
* 不用 90-97 那一段 (aixterm 的"亮色")—— 它虽然到处都是, 但严格讲不属于 #define MD_ITALIC C_ITALIC
* 最窄的 16 色 ANSI, 而这段代码是硬编码 SGR、不做终端能力探测的 (连 terminfo #define MD_CODE C_CYAN
* 都不查). 既然不探测, 就该待在**所有终端都认**的那一档里. #define MD_BULLET C_CYAN
* #define MD_LINK C_UNDERLINE C_CYAN
* 原来链接用的是亮蓝 (aixterm 的 94), 是这里唯一一个越界的码. 改成 #define MD_H1 C_BOLD C_CYAN
* "\033[4;36m" (下划线 + 青): 链接在终端里的传统表示本来就是下划线, #define MD_H2 C_BOLD C_MAGENTA
* 而且青色和行内代码、列表符号同色系, 视觉上"这些是标记不是正文"是一致的. #define MD_H3 C_BOLD C_BLUE
* 更重要的是下划线是**属性** (SGR 4), 比颜色更可靠 —— 单色终端、色盲用户、 #define MD_H4 C_BOLD
* 以及把颜色重映射过的主题里, 下划线都还在.
*
* (这里不写出那个越界码的字面量, 以免以后 grep `\[9[0-7]` 做审计时被注释
* 里的这一行报成假阳性. ) */
#define MD_RESET "\033[0m"
#define MD_BOLD "\033[1m"
#define MD_DIM "\033[2m"
#define MD_ITALIC "\033[3m"
#define MD_CODE "\033[36m" /* 行内代码: 青 */
#define MD_BULLET "\033[36m"
#define MD_LINK "\033[4;36m" /* 链接文字: 下划线 + 青 */
#define MD_H1 "\033[1;36m" /* 粗体青 */
#define MD_H2 "\033[1;35m" /* 粗体品红 */
#define MD_H3 "\033[1;34m" /* 粗体蓝 */
#define MD_H4 "\033[1m" /* 粗体 */
/* 等待更多数据 (当前字节可能是一个被切断的标记) */ /* 等待更多数据 (当前字节可能是一个被切断的标记) */
#define MD_NEED_MORE ((size_t)-1) #define MD_NEED_MORE ((size_t)-1)
/* 待处理缓冲上限: 遇到始终等不到结束标记的超长行时直接吐出, 保证内存有界 */ /* 待处理缓冲上限: 遇到始终等不到结束标记的超长行时直接吐出, 保证内存有界 */
#define MD_MAX_PENDING (64 * 1024) #define MD_MAX_PENDING (64 * 1024)
/* 候选表格行超过这个长度就不再当表格等下去, 避免长行被卡住 */ /* 候选表格行超过这个长度就不再当表格等下去, 避免长行被卡住 */
#define MD_MAX_TABLE_LINE 4096 #define MD_MAX_TABLE_LINE 4096
/* 表格累积多少行后强制渲染 (防止超大表格一直不输出) */ /* 表格累积多少行后强制渲染 (防止超大表格一直不输出) */
#define MD_MAX_TABLE_ROWS 200 #define MD_MAX_TABLE_ROWS 256
struct MdRenderer { struct MdRenderer {
int enabled; int enabled;
+3 -3
View File
@@ -31,7 +31,7 @@ static const char *META_SYSTEM_PROMPT =
"\n" "\n"
"## 为什么会有你\n" "## 为什么会有你\n"
"用户想改这段历史, 通常是因为助手沿着一个他不满意的方向走下去了. 他要的\n" "用户想改这段历史, 通常是因为助手沿着一个他不满意的方向走下去了. 他要的\n"
"往往不是 "省 token" , 而是**让这段历史不再诱导助手重蹈覆辙**——\n" "往往不是 \"省 token\" , 而是**让这段历史不再诱导助手重蹈覆辙**——\n"
"删掉那句说错的前提、去掉那个被推翻的方案、删掉助手那次自信但错误的结论. \n" "删掉那句说错的前提、去掉那个被推翻的方案、删掉助手那次自信但错误的结论. \n"
"另外 你拥有修改assistant的消息的能力, 这是常规的修改所不能完成的\n" "另外 你拥有修改assistant的消息的能力, 这是常规的修改所不能完成的\n"
"\n" "\n"
@@ -41,7 +41,7 @@ static const char *META_SYSTEM_PROMPT =
"## 工作方式\n" "## 工作方式\n"
"1. 动手前先看清楚. 先 list_rounds 看目录、再 read_messages 读原文. \n" "1. 动手前先看清楚. 先 list_rounds 看目录、再 read_messages 读原文. \n"
" 不要凭用户的转述猜内容. \n" " 不要凭用户的转述猜内容. \n"
"2. 改最小必要范围. 用户说 "删掉那句用 Python" , 就删那一句, \n" "2. 改最小必要范围. 用户说 \"删掉那句用 Python\" , 就删那一句, \n"
" 不要顺手把整轮都删了. \n" " 不要顺手把整轮都删了. \n"
"3. 用户没要求压缩就不要压缩 —— 压缩会丢证据. \n" "3. 用户没要求压缩就不要压缩 —— 压缩会丢证据. \n"
"4. 每次改动后消息编号会重排. 要继续改, 先重新 list_rounds. \n" "4. 每次改动后消息编号会重排. 要继续改, 先重新 list_rounds. \n"
@@ -49,7 +49,7 @@ static const char *META_SYSTEM_PROMPT =
"\n" "\n"
"## 红线\n" "## 红线\n"
"- 带 tool_calls 的 assistant 消息和紧随其后的 tool 结果是不可拆的整体, 否则会造成400错误. \n" "- 带 tool_calls 的 assistant 消息和紧随其后的 tool 结果是不可拆的整体, 否则会造成400错误. \n"
"- 不要为了 "整齐" 重排顺序. \n" "- 不要为了 \"整齐\" 重排顺序. \n"
"\n" "\n"
"## 拿不准就问\n" "## 拿不准就问\n"
"如果删掉某个点会让后面的回答失去上下文, 把这个后果指出来, 让用户决定. \n" "如果删掉某个点会让后面的回答失去上下文, 把这个后果指出来, 让用户决定. \n"
-8
View File
@@ -37,11 +37,3 @@
#define C_BRIGHT_MAGENTA "\033[95m" #define C_BRIGHT_MAGENTA "\033[95m"
#define C_BRIGHT_CYAN "\033[96m" #define C_BRIGHT_CYAN "\033[96m"
#define C_BRIGHT_WHITE "\033[97m" #define C_BRIGHT_WHITE "\033[97m"
#define C_OFF "\033[0m" // 复原
#define C_BOLD "\033[1m" // 粗体
#define C_DIM "\033[2m" // 半亮度
#define C_ITALIC "\033[3m" // 斜体
#define C_CYAN "\033[36m" //
#define C_GREEN "\033[32m"
#define C_RED "\033[31m"