Skip to content

Commit 936090c

Browse files
LegnaOSclaude
andcommitted
fix: v3.4.1 — GLM tool calling 多轮回放修复
GLM coding 端点不支持标准 OpenAI tool calling 多轮回放格式, continuation 时 assistant(tool_calls) + tool(results) 触发 400。 - GLM 历史消息折叠:tool_calls + tool results → 纯文本 assistant + user - GLM 工具循环内部同步折叠:拦截工具回传也用纯文本格式 - reasoning_content 只对 deepseek/kimi 注入 - 添加 [GLM-DEBUG] 诊断日志 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent daf67d0 commit 936090c

3 files changed

Lines changed: 71 additions & 9 deletions

File tree

readme.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,9 +223,10 @@ v3.4.0 Agent 工具系统进化 — Tool 类型系统 + ToolRegistry
223223

224224
### v3.4.1 — GLM 工具循环 messages 参数修复
225225

226-
- **GLM 400 修复** — GLM (glm-5.1) 不支持 `reasoning_content` 字段和 `content: null`,工具循环第二轮回传 assistant 消息时触发 `messages 参数非法` 400 错误
227-
- **assistant content 修复** — 工具循环和历史回放中 `content: null` 改为 `content: ''`(空字符串),兼容所有 provider
228-
- **reasoning_content 条件注入** — 只对 DeepSeek/Kimi 注入 `reasoning_content`,GLM/OpenAI/其他 provider 跳过,避免不兼容字段污染请求
226+
- **GLM tool calling 多轮回放修复** — GLM coding 端点 (`/api/coding/paas/v4`) 不支持标准 OpenAI tool calling 多轮回放格式(`assistant(tool_calls) + tool(results)`),continuation 时稳定触发 `messages 参数非法` 400 错误
227+
- **GLM 消息折叠策略** — 对 GLM provider,历史中的 `assistant(tool_calls) + tool(results)` 自动折叠为纯文本 `assistant + user` 消息对,完全绕开多轮回放兼容性问题
228+
- **工具循环内部同步修复** — 代理内部工具循环(拦截工具执行后回传 AI)也对 GLM 使用纯文本折叠,确保工具链闭环
229+
- **reasoning_content 条件注入** — 只对 DeepSeek/Kimi 注入,GLM/OpenAI 跳过
229230

230231
### v3.4.0 — Agent 工具系统进化
231232

src/messages.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -723,9 +723,24 @@ export function splitReasoningContentFromText(text?: string): { content?: string
723723
// ===== 将 Augment 请求转换为 OpenAI 格式消息 =====
724724
export function augmentToOpenAIMessages(req: any) {
725725
const messages: any[] = [];
726+
const isGlm = state.currentConfig.provider === 'glm';
727+
726728
for (const turn of normalizeAugmentTimeline(req)) {
727729
if (turn.role === 'assistant') {
728730
if (turn.toolUses && turn.toolUses.length > 0) {
731+
// GLM coding 端点不支持 tool calling 多轮回放
732+
// 将 assistant(tool_calls) + tool(results) 折叠为纯文本 assistant 消息
733+
if (isGlm) {
734+
const toolSummary = turn.toolUses.map((tu: any) => {
735+
let args = '';
736+
try { args = typeof tu.inputJson === 'string' ? tu.inputJson : JSON.stringify(tu.inputJson); } catch {}
737+
return `[Called ${tu.name}(${args.substring(0, 200)})]`;
738+
}).join('\n');
739+
const textPart = turn.text ? splitReasoningContentFromText(turn.text).content || '' : '';
740+
messages.push({ role: 'assistant', content: (textPart + '\n' + toolSummary).trim() });
741+
continue;
742+
}
743+
729744
const { content, reasoningContent } = splitReasoningContentFromText(turn.text);
730745
const assistantMessage: any = {
731746
role: 'assistant',
@@ -738,10 +753,8 @@ export function augmentToOpenAIMessages(req: any) {
738753
if (content) {
739754
assistantMessage.content = content;
740755
} else {
741-
assistantMessage.content = '';
756+
assistantMessage.content = null;
742757
}
743-
// reasoning_content 只有部分 provider 支持回传(DeepSeek/Kimi)
744-
// GLM 不支持,会导致 "messages 参数非法" 400 错误
745758
const supportsReasoningReplay = ['deepseek', 'kimi'].includes(state.currentConfig.provider);
746759
if (reasoningContent && supportsReasoningReplay) {
747760
assistantMessage.reasoning_content = reasoningContent;
@@ -756,13 +769,25 @@ export function augmentToOpenAIMessages(req: any) {
756769
continue;
757770
}
758771

772+
// GLM: tool results 折叠为 user 消息(与上面的 assistant 折叠配对)
773+
if (isGlm && turn.toolResults && turn.toolResults.length > 0) {
774+
const resultsSummary = turn.toolResults.map((tr: any) => {
775+
const content = (tr.content || '').substring(0, 2000);
776+
return `[Result of ${tr.name || 'tool'}]: ${content}`;
777+
}).join('\n\n');
778+
messages.push({ role: 'user', content: resultsSummary });
779+
if (turn.text) {
780+
messages.push({ role: 'user', content: turn.text });
781+
}
782+
continue;
783+
}
784+
759785
for (const toolResult of turn.toolResults || []) {
760786
const toolMsg: any = {
761787
role: 'tool',
762788
tool_call_id: toolResult.id,
763789
content: toolResult.content || ''
764790
};
765-
// GLM 不支持 tool 消息中的 name 字段,会导致 "messages 参数非法"
766791
if (state.currentConfig.provider !== 'glm') {
767792
toolMsg.name = toolResult.name || 'unknown';
768793
}

src/providers/openai.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -591,8 +591,11 @@ export async function executeOpenAIRequest(
591591
const contentType = m?.content === null ? 'null' : typeof m?.content;
592592
const hasToolCalls = Array.isArray(m?.tool_calls) ? m.tool_calls.length : 0;
593593
const hasReasoning = m?.reasoning_content ? 'yes' : 'no';
594-
log(`[GLM-DEBUG] msg[${mi}] role=${m?.role} keys=[${keys}] content=${contentType}(${String(m?.content).substring(0, 50)}) tool_calls=${hasToolCalls} reasoning=${hasReasoning}`);
594+
const tcIds = Array.isArray(m?.tool_calls) ? m.tool_calls.map((tc: any) => `${tc?.function?.name}:${tc?.id}`).join(', ') : '';
595+
const toolCallId = m?.tool_call_id || '';
596+
log(`[GLM-DEBUG] msg[${mi}] role=${m?.role} keys=[${keys}] content=${contentType}(${String(m?.content).substring(0, 80)}) tool_calls=${hasToolCalls}[${tcIds}] tool_call_id=${toolCallId} reasoning=${hasReasoning}`);
595597
}
598+
log(`[GLM-DEBUG] Full request body (first 2000 chars): ${apiBody.substring(0, 2000)}`);
596599
}
597600

598601
const url = new URL(resolvedEndpoint);
@@ -1069,6 +1072,39 @@ export async function forwardToOpenAIStream(augmentReq: any, res: any) {
10691072
}
10701073

10711074
// ✅ 所有工具都被拦截了 → 把结果送回 AI 继续生成
1075+
// GLM: 折叠为纯文本格式(不支持 tool calling 多轮回放)
1076+
if (state.currentConfig.provider === 'glm') {
1077+
const toolSummary = [...interceptedTools.map(({ tc, toolNode }) => {
1078+
const content = (toolNode.tool_result_node.content || '').substring(0, 2000);
1079+
return `[Result of ${tc.name}]: ${content}`;
1080+
}), ...await Promise.all(codebaseSearchCalls.map(async (cs: any) => {
1081+
const searchResult = await executeRAGSearch(cs.query);
1082+
return `[Result of codebase_search("${cs.query}")]: ${searchResult.substring(0, 2000)}`;
1083+
}))].join('\n\n');
1084+
1085+
const textPart = result.text ? splitReasoningContentFromText(result.text).content || '' : '';
1086+
const callsSummary = [...otherToolCalls, ...codebaseSearchCalls.map((cs: any) => ({
1087+
name: 'codebase_search', arguments: JSON.stringify({ query: cs.query })
1088+
}))].map((tc: any) => `[Called ${tc.name}(${(typeof tc.arguments === 'string' ? tc.arguments : JSON.stringify(tc.arguments || {})).substring(0, 200)})]`).join('\n');
1089+
1090+
currentMessages.push({ role: 'assistant', content: (textPart + '\n' + callsSummary).trim() });
1091+
currentMessages.push({ role: 'user', content: toolSummary });
1092+
1093+
// 流式显示
1094+
for (const { tc, toolNode } of interceptedTools) {
1095+
try {
1096+
const resultObj = JSON.parse(toolNode.tool_result_node.content);
1097+
const diffText = renderDiffText(resultObj, tc.name);
1098+
res.write(JSON.stringify({ text: diffText, nodes: [], stop_reason: 0 }) + '\n');
1099+
} catch {
1100+
res.write(JSON.stringify({ text: `\n✅ ${tc.name} executed\n`, nodes: [], stop_reason: 0 }) + '\n');
1101+
}
1102+
}
1103+
1104+
log(`[LOOP] GLM: folded ${interceptedTools.length} tools + ${codebaseSearchCalls.length} searches into text messages`);
1105+
continue;
1106+
}
1107+
10721108
// 1. 构建 assistant message(包含 tool_calls)
10731109
const allToolCalls = [...otherToolCalls, ...codebaseSearchCalls.map((cs: any) => ({
10741110
id: cs.id, name: 'codebase_search', arguments: JSON.stringify({ query: cs.query })
@@ -1084,7 +1120,7 @@ export async function forwardToOpenAIStream(augmentReq: any, res: any) {
10841120
const assistantReplay = splitReasoningContentFromText(result.text);
10851121
const assistantReplayMessage: any = {
10861122
role: 'assistant',
1087-
content: assistantReplay.content || '',
1123+
content: assistantReplay.content || null,
10881124
tool_calls: assistantToolCallsMsg
10891125
};
10901126
// reasoning_content 只有部分 provider 支持回传(DeepSeek/Kimi)

0 commit comments

Comments
 (0)