@saber2pr/ai-agent 0.0.68 → 0.0.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -36,6 +36,8 @@ export declare abstract class AgentGraphModel extends BaseChatModel {
|
|
|
36
36
|
callApiStream(prompt: string, lastMsg: BaseMessage, onChunk: StreamChunkCallback): Promise<AgentGraphLLMResponse>;
|
|
37
37
|
_generate(messages: BaseMessage[]): Promise<ChatResult>;
|
|
38
38
|
private parseToolCalls;
|
|
39
|
+
/** 从 text[start] 开始,按括号配对提取完整的 JSON 对象字符串 */
|
|
40
|
+
private extractBalancedJson;
|
|
39
41
|
/**
|
|
40
42
|
* 流式生成:调用 callApiStream 进行流式输出,完成后构建完整的 ChatResult。
|
|
41
43
|
* @param messages LangChain 消息列表
|
|
@@ -96,37 +96,38 @@ class AgentGraphModel extends chat_models_1.BaseChatModel {
|
|
|
96
96
|
}
|
|
97
97
|
parseToolCalls(text) {
|
|
98
98
|
const actionMatch = text.match(/Action:\s*(\w+)/);
|
|
99
|
-
const argsMatch = text.match(/Arguments:\s*({[\s\S]*?})(?=\n|$)/);
|
|
100
99
|
if (!actionMatch)
|
|
101
100
|
return [];
|
|
102
101
|
let args = {};
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
102
|
+
const argsIdx = text.search(/Arguments:\s*\{/);
|
|
103
|
+
if (argsIdx !== -1) {
|
|
104
|
+
const jsonStart = text.indexOf('{', argsIdx);
|
|
105
|
+
const jsonStr = this.extractBalancedJson(text, jsonStart);
|
|
106
|
+
if (jsonStr) {
|
|
107
|
+
try {
|
|
108
|
+
let raw = jsonStr;
|
|
109
|
+
let safetyDepth = 0;
|
|
110
|
+
while (typeof raw === 'string' && safetyDepth < 5) {
|
|
111
|
+
try {
|
|
112
|
+
const parsed = JSON.parse(raw);
|
|
113
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
114
|
+
raw = parsed;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
113
117
|
raw = parsed;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
114
120
|
break;
|
|
115
121
|
}
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
catch {
|
|
119
|
-
break; // 解析不动了,跳出
|
|
122
|
+
safetyDepth++;
|
|
120
123
|
}
|
|
121
|
-
|
|
124
|
+
args = raw;
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
args = {};
|
|
122
128
|
}
|
|
123
|
-
args = raw;
|
|
124
|
-
}
|
|
125
|
-
catch (e) {
|
|
126
|
-
args = {};
|
|
127
129
|
}
|
|
128
130
|
}
|
|
129
|
-
// ✅ 此时返回的 args 必须是 object 类型
|
|
130
131
|
return [
|
|
131
132
|
{
|
|
132
133
|
name: actionMatch[1],
|
|
@@ -136,6 +137,39 @@ class AgentGraphModel extends chat_models_1.BaseChatModel {
|
|
|
136
137
|
},
|
|
137
138
|
];
|
|
138
139
|
}
|
|
140
|
+
/** 从 text[start] 开始,按括号配对提取完整的 JSON 对象字符串 */
|
|
141
|
+
extractBalancedJson(text, start) {
|
|
142
|
+
if (start < 0 || start >= text.length || text[start] !== '{')
|
|
143
|
+
return null;
|
|
144
|
+
let depth = 0;
|
|
145
|
+
let inString = false;
|
|
146
|
+
let escape = false;
|
|
147
|
+
for (let i = start; i < text.length; i++) {
|
|
148
|
+
const ch = text[i];
|
|
149
|
+
if (escape) {
|
|
150
|
+
escape = false;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (ch === '\\' && inString) {
|
|
154
|
+
escape = true;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (ch === '"') {
|
|
158
|
+
inString = !inString;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (inString)
|
|
162
|
+
continue;
|
|
163
|
+
if (ch === '{')
|
|
164
|
+
depth++;
|
|
165
|
+
else if (ch === '}') {
|
|
166
|
+
depth--;
|
|
167
|
+
if (depth === 0)
|
|
168
|
+
return text.substring(start, i + 1);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
139
173
|
/**
|
|
140
174
|
* 流式生成:调用 callApiStream 进行流式输出,完成后构建完整的 ChatResult。
|
|
141
175
|
* @param messages LangChain 消息列表
|
|
@@ -211,8 +245,8 @@ ${systemContext}
|
|
|
211
245
|
${format(lastMsg)}
|
|
212
246
|
# Output Requirement
|
|
213
247
|
1. Reasoning in <think> tags.
|
|
214
|
-
2. Action:
|
|
215
|
-
3. Arguments: {JSON
|
|
248
|
+
2. Action: ToolName
|
|
249
|
+
3. Arguments: {"key": "value"} (MUST be a single-line compact JSON, no newlines inside)
|
|
216
250
|
`.trim();
|
|
217
251
|
}
|
|
218
252
|
_llmType() {
|
|
@@ -4,10 +4,44 @@ exports.batchRunTools = void 0;
|
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
const createTool_1 = require("../../utils/createTool");
|
|
6
6
|
const kit_1 = require("../../utils/kit");
|
|
7
|
+
const formatOutput = (output) => {
|
|
8
|
+
// 1. 处理空值
|
|
9
|
+
if (output === null || output === undefined)
|
|
10
|
+
return String(output);
|
|
11
|
+
// 2. 处理数组 (MCP 常见返回格式)
|
|
12
|
+
if (Array.isArray(output)) {
|
|
13
|
+
// 尝试提取所有 text 类型的 content 并合并,如果不是 MCP 格式则递归处理
|
|
14
|
+
const isMcpStyle = output.every(item => item && typeof item === 'object' && 'type' in item);
|
|
15
|
+
if (isMcpStyle) {
|
|
16
|
+
return output
|
|
17
|
+
.map(item => {
|
|
18
|
+
if (item.type === 'text')
|
|
19
|
+
return item.text;
|
|
20
|
+
if (item.type === 'resource')
|
|
21
|
+
return `[Resource: ${item.resource?.uri}]`;
|
|
22
|
+
return JSON.stringify(item); // 无法识别的 MCP 类型(如 image)保持 JSON
|
|
23
|
+
})
|
|
24
|
+
.join('\n');
|
|
25
|
+
}
|
|
26
|
+
// 普通数组递归处理
|
|
27
|
+
return output.map(item => formatOutput(item)).join('\n');
|
|
28
|
+
}
|
|
29
|
+
// 3. 处理单个对象
|
|
30
|
+
if (typeof output === 'object') {
|
|
31
|
+
// 处理单个 MCP 文本对象
|
|
32
|
+
if (output.type === 'text' && typeof output.text === 'string') {
|
|
33
|
+
return output.text;
|
|
34
|
+
}
|
|
35
|
+
// 其他对象转为格式化 JSON
|
|
36
|
+
return JSON.stringify(output, null, 2);
|
|
37
|
+
}
|
|
38
|
+
// 4. 基本类型直接转 String
|
|
39
|
+
return String(output);
|
|
40
|
+
};
|
|
7
41
|
exports.batchRunTools = (0, createTool_1.createTool)({
|
|
8
42
|
name: 'batch_run_tools',
|
|
9
43
|
// 使用更具指令性的英文描述
|
|
10
|
-
description: `Execute multiple tools in parallel.
|
|
44
|
+
description: `Execute multiple read-only tools in parallel. Such as reading multiple files or fetching diagnostics simultaneously.`,
|
|
11
45
|
parameters: zod_1.z.object({
|
|
12
46
|
actions: zod_1.z.array(zod_1.z.object({
|
|
13
47
|
// 字段名和描述对齐 AI 习惯
|
|
@@ -18,6 +52,11 @@ exports.batchRunTools = (0, createTool_1.createTool)({
|
|
|
18
52
|
handler: async ({ actions }, context) => {
|
|
19
53
|
// 1. 获取所有可用工具的映射
|
|
20
54
|
const toolMap = (0, kit_1.getArray)(context?.allTools).reduce((acc, tool) => ({ ...acc, [tool.function.name]: tool }), {});
|
|
55
|
+
// 2. 预检:拦截并发写入行为
|
|
56
|
+
const writeTools = actions.filter(action => /edit|write|replace|delete/i.test(action.tool_name));
|
|
57
|
+
if (writeTools.length > 0) {
|
|
58
|
+
return `Error: Parallel write operations detected (${writeTools.map(t => t.tool_name).join(', ')}). Batching is ONLY allowed for READ-ONLY operations.`;
|
|
59
|
+
}
|
|
21
60
|
if (Object.keys(toolMap).length === 0) {
|
|
22
61
|
return 'Error: Failed to retrieve tool execution context.';
|
|
23
62
|
}
|
|
@@ -49,8 +88,6 @@ exports.batchRunTools = (0, createTool_1.createTool)({
|
|
|
49
88
|
};
|
|
50
89
|
}
|
|
51
90
|
}));
|
|
52
|
-
|
|
53
|
-
// 虽然参数改成了英文,但返回给 AI 的结果标题可以保留清晰的结构
|
|
54
|
-
return results.map(res => `### Tool: ${res.tool_name} (${res.status})\n${res.output}\n---`).join('\n');
|
|
91
|
+
return results.map(res => `### Tool: ${res.tool_name} (${res.status})\n${formatOutput(res.output)}\n---`).join('\n');
|
|
55
92
|
}
|
|
56
93
|
});
|
|
@@ -15,7 +15,7 @@ Operating System: ${osPlatform}
|
|
|
15
15
|
Before providing any output or calling a tool, you **MUST** conduct a deep logical analysis. Wrap your thought process within <think> tags.
|
|
16
16
|
|
|
17
17
|
# Tool Call Specifications
|
|
18
|
-
1. **Pure JSON Arguments**: Arguments
|
|
18
|
+
1. **Pure JSON Arguments**: Arguments MUST be a single-line compact JSON object (no newlines, no pretty-printing). NEVER wrap the entire JSON object in a string or quotes. Example: Arguments: {"path": "/foo", "content": "bar"}
|
|
19
19
|
2. **No Double Escaping**: Do not double-escape characters within the JSON.
|
|
20
20
|
3. **No Idle Operations**: If the task is complete or no tool is needed, DO NOT output any "Action" structure. Never use "None", "null", or empty strings as a tool name.
|
|
21
21
|
4. **Important Note**: For optimal efficiency, when performing multiple operations, use batch_run_tools to invoke all relevant tools in parallel, rather than sequentially. Prioritize parallel tool invocation whenever possible. For example, when reading three files, run three tool invocations in parallel to read all three files into the context simultaneously. When running multiple read-only commands (such as read_file_range, grep_search, or read_text_file), always run all commands in parallel. Use parallel tool invocations whenever possible, rather than running too many tools sequentially.
|