diff --git a/backend/agent-runtime/pi/agent-runtime.mjs b/backend/agent-runtime/pi/agent-runtime.mjs index 4e87c810e..dda6995d3 100644 --- a/backend/agent-runtime/pi/agent-runtime.mjs +++ b/backend/agent-runtime/pi/agent-runtime.mjs @@ -132,7 +132,7 @@ async function run() { description: tool.description, parameters: tool.parameters, })), - thinkingLevel: options?.reasoning, + thinkingLevel: options?.reasoning ?? "off", }, options?.signal); for (const text of result.steeringMessages ?? []) { await session.steer(text); diff --git a/backend/internal/app/cloud_agent_pi_model.go b/backend/internal/app/cloud_agent_pi_model.go index 926753495..acbcf2c39 100644 --- a/backend/internal/app/cloud_agent_pi_model.go +++ b/backend/internal/app/cloud_agent_pi_model.go @@ -135,7 +135,7 @@ func (s *Service) runCloudAgentModelStep(ctx context.Context, userID, runID stri }, "textOptions": map[string]any{ "stream": true, - "thinking": thinkingLevel != "off", + "thinking": cloudAgentPiThinkingEnabled(thinkingLevel), "maxOutputTokens": cloudAgentStepOutputBudget(state.StepLimits, state.BoostStepOutputBudget), }, } @@ -198,6 +198,13 @@ func (s *Service) runCloudAgentModelStep(ctx context.Context, userID, runID stri return map[string]any{"text": result.Text, "reasoning": result.Reasoning, "toolCalls": runtimeToolCalls(result.ToolCalls)}, false, nil } +// cloudAgentPiThinkingEnabled treats an omitted thinking level the same as Pi's +// explicit "off" value. Pi omits the field when thinking is disabled, and the +// Go JSON decoder represents that omitted field as an empty string. +func cloudAgentPiThinkingEnabled(thinkingLevel string) bool { + return thinkingLevel != "" && thinkingLevel != "off" +} + // cloudAgentModelTaskRetryable 按上游真实 HTTP 状态判断失败是否是临时性的: // 5xx、429、408 与没有拿到任何响应的网络错误可以重试;空回复也可以重试。 // 其余 4xx(参数、鉴权、模型不存在)重试结果不会变,直接失败。 diff --git a/backend/internal/app/cloud_agent_pi_truncated_args_test.go b/backend/internal/app/cloud_agent_pi_truncated_args_test.go index a4f5779b8..74b13295a 100644 --- a/backend/internal/app/cloud_agent_pi_truncated_args_test.go +++ b/backend/internal/app/cloud_agent_pi_truncated_args_test.go @@ -10,6 +10,25 @@ import ( "infinite-canvas/backend/internal/model" ) +func TestCloudAgentPiThinkingEnabled(t *testing.T) { + tests := []struct { + name string + level string + want bool + }{ + {name: "omitted", level: "", want: false}, + {name: "off", level: "off", want: false}, + {name: "enabled", level: "medium", want: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := cloudAgentPiThinkingEnabled(tt.level); got != tt.want { + t.Fatalf("cloudAgentPiThinkingEnabled(%q) = %v, want %v", tt.level, got, tt.want) + } + }) + } +} + // 线上复现(诊断号 ag5865c256d9fb9bcbf2f1719a4ec1023c):模型一次写了很长的工具参数, // 流式拼出来的 JSON 多了一个括号,任务报"Agent 工具参数不是完整 JSON"。Go 执行循环 // 会带 truncated_tool_arguments 纠偏重做,但 Pi 模型桥接把它当成不可重试的失败,整轮 @@ -64,6 +83,21 @@ func TestCloudAgentPiModelRetriesTruncatedToolArguments(t *testing.T) { } first := nextStep("") + var firstTask model.Task + if err := db.First(&firstTask, "id = ?", first).Error; err != nil { + t.Fatal(err) + } + var firstInput map[string]any + if err := json.Unmarshal([]byte(firstTask.InputJSON), &firstInput); err != nil { + t.Fatalf("decode first model task input: %v", err) + } + textOptions, ok := firstInput["textOptions"].(map[string]any) + if !ok { + t.Fatalf("first model task textOptions = %#v", firstInput["textOptions"]) + } + if thinking, ok := textOptions["thinking"].(bool); !ok || thinking { + t.Fatalf("disabled thinking should be false, got %#v", textOptions["thinking"]) + } if err := db.Model(&model.Task{}).Where("id = ?", first).Updates(map[string]any{ "status": model.TaskStatusFailed, "error": "Agent 工具参数不是完整 JSON:invalid character '}' after array element",