@myassis/gateway 1.0.103 → 1.0.105

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.
@@ -108,7 +108,7 @@ exports.appConfig = {
108
108
  /** 超出 toolPairKeep 的旧工具调用内容截断长度 */
109
109
  oldToolContentLimit: parseInt(process.env.OLD_TOOL_CONTENT_LIMIT || '200', 10),
110
110
  /** 单次请求内最大工具调用轮数 */
111
- maxToolRounds: parseInt(process.env.MAX_TOOL_ROUNDS || '200', 10),
111
+ maxToolRounds: parseInt(process.env.MAX_TOOL_ROUNDS || '50', 10),
112
112
  /**
113
113
  * 轮内压缩触发阈值(字符)。
114
114
  *
@@ -947,6 +947,12 @@ class Session {
947
947
  let finalizeStatus = 'completed';
948
948
  // 当前裁剪级别,遇到上下文超限时逐级加重
949
949
  let trimLevel = ContextBuilder_js_1.TrimLevel.OldToolPayload;
950
+ // ========== 轮内工具调用重复检测 ==========
951
+ // 模型陷入死循环时会反复用完全相同的参数调用同一个工具。
952
+ // 这里记录每个 工具名+规范化参数 的调用次数,超过阈值后不再真实执行,
953
+ // 而是返回一条明确的提示,迫使模型换思路或直接收尾。
954
+ const toolCallCount = new Map();
955
+ const MAX_SAME_TOOL_REPEAT = 3;
950
956
  /** 判断错误是否为上下文超限 */
951
957
  const isContextOverflow = (error) => {
952
958
  const text = String(error?.message || error);
@@ -1140,6 +1146,22 @@ class Session {
1140
1146
  // 执行工具调用并收集结果
1141
1147
  const toolResults = [];
1142
1148
  const startToolCall = async (pendingToolCall) => {
1149
+ // 重复检测:相同工具+参数在本轮内被反复调用说明模型陷入了死循环
1150
+ const callKey = pendingToolCall.toolName + String.fromCharCode(58) + String(pendingToolCall.input || String()).trim();
1151
+ const prevCount = toolCallCount.get(callKey) || 0;
1152
+ toolCallCount.set(callKey, prevCount + 1);
1153
+ if (prevCount >= MAX_SAME_TOOL_REPEAT) {
1154
+ const repeatMsg = '[重复调用] 此工具在本轮中已用相同参数调用多次,请停止重复调用,换方式或直接汇报。';
1155
+ logger.warn('[工具重复调用] ' + pendingToolCall.toolName);
1156
+ toolResults.push({ id: pendingToolCall.id, name: pendingToolCall.toolName, output: repeatMsg, success: false });
1157
+ toolCall.toolCalls.push({ id: pendingToolCall.id, toolName: pendingToolCall.toolName, input: pendingToolCall.input, output: repeatMsg, status: 'error', actionName: pendingToolCall.actionName });
1158
+ sendSSE(res, { type: 'tool_call_result', id: pendingToolCall.id, toolName: pendingToolCall.toolName, output: repeatMsg, status: 'error', toolCallId: toolCall.id, actionName: pendingToolCall.actionName, modelName });
1159
+ if (prevCount >= MAX_SAME_TOOL_REPEAT + 2) {
1160
+ forceFinalize = true;
1161
+ finalizeStatus = 'error';
1162
+ }
1163
+ return;
1164
+ }
1143
1165
  // 发送工具开始执行事件
1144
1166
  const toolStartEvent = {
1145
1167
  type: 'tool_call_execute',
@@ -1347,6 +1369,12 @@ class Session {
1347
1369
  };
1348
1370
  await Promise.all(llmResult.toolCalls.map(x => startToolCall(x)));
1349
1371
  toolCalls.push(toolCall);
1372
+ // 重置 toolCall:下一轮工具调用必须开启新的分组。
1373
+ // 否则模型连续多轮只返回工具调用(无正文)时,所有轮次的结果会
1374
+ // 累积进同一个 toolCall 对象,并被重复 push 到 toolCalls 数组,
1375
+ // 导致持久化消息出现重复的工具调用组,历史回放时模型看到重复
1376
+ // 的 tool_calls/tool 结果对,进而重复执行工具。
1377
+ toolCall = null;
1350
1378
  // 本轮工具已执行完,增量落库一次。工具轮次可能很多且每轮都耗时,
1351
1379
  // 这里保存能让崩溃后仍保留已完成的工具调用记录。
1352
1380
  if (!childAgent) {
@@ -1374,7 +1402,7 @@ class Session {
1374
1402
  const reminder = this.buildPlanReminder(roundsSincePlanUpdate, forceFinalize);
1375
1403
  if (reminder) {
1376
1404
  messages.push({
1377
- role: 'assistant',
1405
+ role: 'user',
1378
1406
  content: reminder,
1379
1407
  attachments: []
1380
1408
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myassis/gateway",
3
- "version": "1.0.103",
3
+ "version": "1.0.105",
4
4
  "description": "我的助手 Gateway Service - 本地 AI 网关服务,支持认证、WebSocket 实时通信和任务调度",
5
5
  "main": "dist/index.js",
6
6
  "bin": {