fix(agent): Agent 运行 - 重复读取改为软收敛并保留可纠正的媒体失败原因

重复只读和同版本识图不再把运行标成失败,下一步暂时收起只读工具。媒体能力错误改为可纠正,准入失败保留安全原因。
This commit is contained in:
ddcat666
2026-10-10 20:19:32 +08:00
parent 37b44444f0
commit 1bbc95b996
15 changed files with 394 additions and 52 deletions

View File

@@ -169,6 +169,31 @@ func cloudAgentToolResult(runID string, state *cloudAgentRuntime, call cloudAgen
return exhausted
}
func cloudAgentMarkReadLoopNudge(state *cloudAgentRuntime, call cloudAgentCall, count int) {
if state == nil || state.ReadLoopNudge {
return
}
state.ReadLoopNudge = true
state.ReadLoopToolName = call.Function.Name
state.ReadLoopCount = max(1, count)
}
func cloudAgentApplyReadLoopNudge(canonical *canonicalAgentRequest, state *cloudAgentRuntime) {
if state == nil || !state.ReadLoopNudge {
return
}
toolName := state.ReadLoopToolName
if toolName == "" {
toolName = "只读工具"
}
canonical.Tools = cloudAgentToolsWithoutReadTools(canonical.Tools)
// 提示只进入下一次模型输入,避免插入尚未收齐结果的工具批次或堆积历史。
canonical.Messages = append(append([]map[string]any(nil), canonical.Messages...), cloudAgentRuntimeMessage(cloudAgentRuntimeContext{
Kind: cloudAgentContextReadLoop,
Detail: fmt.Sprintf("%s 触发读取收敛(读取或回放计数 %d);本次模型请求暂时不提供只读工具,运行状态仍为 running。", toolName, max(1, state.ReadLoopCount)),
}))
}
// cloudAgentCompactSupersededReadResult bounds repeated snapshots without
// changing the transcript shape. A newer read with the same cache key makes an
// older canvas projection stale; keep the old tool message for provider pairing,
@@ -389,30 +414,22 @@ func (s *Service) advanceCloudAgentReadBatch(run *model.CloudAgentExecution, sta
for _, item := range outcomes {
var readLoopErr *cloudAgentReadLoopError
if errors.As(item.err, &readLoopErr) {
// A provider tool-call turn is atomic from the transcript's point of
// view: every declared tool_call_id needs a tool message, even when a
// read guard terminates the run. Record the triggering error and then
// explicit skipped receipts before appending run_failed, otherwise a
// later resume/replay produces an invalid provider transcript.
cloudAgentToolResult(current.ID, state, item.call, item.result, item.err)
for state.CallIndex < len(state.Calls) {
pending := state.Calls[state.CallIndex]
cloudAgentToolResult(current.ID, state, pending,
map[string]any{"skipped": true, "reason": readLoopErr.reasonCode()},
nil)
}
current.Status = "failed"
current.FailureMessage = truncateRunes(readLoopErr.Error(), 1000)
cloudAgentDropInterjections(run.ID, "本轮已结束:"+truncateRunes(current.FailureMessage, 120), state)
reason := readLoopErr.reasonCode()
state.event(run.ID, "run_failed", map[string]any{
"text": current.FailureMessage, "reason": reason,
"toolName": item.call.Function.Name, "readCount": readLoopErr.Count,
})
// 只收敛触发护栏的读取;同批剩余调用仍由调度器处理,不能吞掉写操作。
cloudAgentToolResult(current.ID, state, item.call, map[string]any{
"readLoopGuard": true,
"message": "重复只读调用已软收敛,请使用已有结果继续;运行仍可继续",
}, nil)
cloudAgentMarkReadLoopNudge(state, item.call, readLoopErr.Count)
break
}
cloudAgentRecordToolResult(current, state, item.call, item.result, item.err)
}
if state.ReadLoopNudge && len(outcomes) > 0 {
state.event(run.ID, "read_loop_guard", map[string]any{
"text": "重复只读调用已软收敛,运行继续",
"toolName": state.ReadLoopToolName, "readCount": state.ReadLoopCount,
})
}
return cloudAgentSave(current, state)
})
return true, err
@@ -675,7 +692,12 @@ func (s *Service) advanceCloudAgentTool(run *model.CloudAgentExecution, state *c
if allowed && call.Function.Name == "canvas_inspect_image" && state.Request.VisionEnabled {
inspectionResult, inspectionErr = s.prepareCloudAgentImageInspection(run.UserID, state.Request.CanvasID, state, call)
if errors.Is(inspectionErr, errCloudAgentImageInspectionBudget) {
return s.failCloudAgent(run, state, cloudAgentImageInspectionBudgetMessage)
inspectionErr = &cloudAgentReadLoopError{
ToolName: "canvas_inspect_image",
Count: state.cloudAgentImageInspectionCalls() + 1,
Budget: true,
ReasonCode: "vision_read_budget_exceeded",
}
}
}
// Service-backed reads may open resources or use domain repositories.
@@ -731,6 +753,9 @@ func (s *Service) advanceCloudAgentTool(run *model.CloudAgentExecution, state *c
}
state.ImageInspectionReads[inspection.CacheKey]++
}
if inspection.Receipt["repeat"] == true {
cloudAgentMarkReadLoopNudge(state, call, state.ImageInspectionReads[inspection.CacheKey])
}
}
}
case call.Function.Name == "skill_read_file", call.Function.Name == "model_list", call.Function.Name == "image_annotation_render", call.Function.Name == "canvas_read_text":
@@ -745,13 +770,13 @@ func (s *Service) advanceCloudAgentTool(run *model.CloudAgentExecution, state *c
}
var readLoopErr *cloudAgentReadLoopError
if errors.As(toolErr, &readLoopErr) {
cloudAgentRecordToolResult(current, state, call, result, toolErr)
current.Status = "failed"
current.FailureMessage = truncateRunes(readLoopErr.Error(), 1000)
cloudAgentDropInterjections(run.ID, "本轮已结束:"+truncateRunes(current.FailureMessage, 120), state)
reason := readLoopErr.reasonCode()
state.event(run.ID, "run_failed", map[string]any{
"text": current.FailureMessage, "reason": reason,
cloudAgentRecordToolResult(current, state, call, map[string]any{
"readLoopGuard": true,
"message": "重复只读调用已软收敛,请使用已有结果继续;运行仍可继续",
}, nil)
cloudAgentMarkReadLoopNudge(state, call, readLoopErr.Count)
state.event(run.ID, "read_loop_guard", map[string]any{
"text": "重复只读调用已软收敛,运行继续",
"toolName": call.Function.Name, "readCount": readLoopErr.Count,
})
return cloudAgentSave(current, state)
@@ -774,6 +799,12 @@ func (s *Service) advanceCloudAgentTool(run *model.CloudAgentExecution, state *c
return cloudAgentSave(current, state)
}
cloudAgentRecordToolResult(current, state, call, result, toolErr)
if state.ReadLoopNudge {
state.event(run.ID, "read_loop_guard", map[string]any{
"text": "重复只读调用已软收敛,运行继续",
"toolName": state.ReadLoopToolName, "readCount": state.ReadLoopCount,
})
}
return cloudAgentSave(current, state)
})
}