style: 改进 SGV 相关声明
This commit is contained in:
1 parent
3dd2231c70
commit
016a45bc6e
7 files changed
+260
-168
No files matched your search
@@ -0,0 +1,4 @@
|
|||||||
|
BasedOnStyle: LLVM
|
||||||
|
IndentWidth: 4
|
||||||
|
TabWidth: 4
|
||||||
|
UseTab: Never
|
||||||
+233
-124
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
|
||||||
Reference in new issue
Block a user