diff --git a/internal/multiagent/eino_adk_run_loop.go b/internal/multiagent/eino_adk_run_loop.go index eb0a7f1a..71ab381f 100644 --- a/internal/multiagent/eino_adk_run_loop.go +++ b/internal/multiagent/eino_adk_run_loop.go @@ -1027,9 +1027,32 @@ func runEinoADKAgentLoop(ctx context.Context, args *einoADKRunLoopArgs, baseMsgs orchMode, runAccumulatedMsgs, persistTraceSource(args, runAccumulatedMsgs), lastAssistant, lastPlanExecuteExecutor, emptyHint, ids, false, ) + if shouldEinoEmptyResponseContinue(out, emptyHint, len(runAccumulatedMsgs), baseAccumulatedCount) { + if logger != nil { + logger.Info("eino empty response, ending run segment for handler resume", + zap.String("conversationId", conversationID), + zap.String("orchestration", orchMode), + zap.Int("traceMessages", len(runAccumulatedMsgs))) + } + if progress != nil { + progress("eino_empty_response_continue", "会话已结束但未产生助手正文,正在基于轨迹自动续跑…", map[string]interface{}{ + "conversationId": conversationID, + "source": "eino", + "resumeKind": "trace_segment", + }) + } + return out, ErrEmptyResponseContinue + } return out, nil } +func shouldEinoEmptyResponseContinue(out *RunResult, emptyHint string, accumulatedLen, baseCount int) bool { + if out == nil || accumulatedLen <= baseCount { + return false + } + return strings.TrimSpace(out.Response) == strings.TrimSpace(emptyHint) +} + func persistTraceSource(args *einoADKRunLoopArgs, fallback []adk.Message) []adk.Message { if args != nil && args.ModelFacingTrace != nil { if snap := args.ModelFacingTrace.Snapshot(); len(snap) > 0 { diff --git a/internal/multiagent/eino_empty_response_test.go b/internal/multiagent/eino_empty_response_test.go new file mode 100644 index 00000000..47de9e20 --- /dev/null +++ b/internal/multiagent/eino_empty_response_test.go @@ -0,0 +1,21 @@ +package multiagent + +import "testing" + +func TestShouldEinoEmptyResponseContinue(t *testing.T) { + t.Parallel() + hint := "(empty hint)" + out := &RunResult{Response: hint} + if !shouldEinoEmptyResponseContinue(out, hint, 3, 1) { + t.Fatal("expected continue when response is empty hint and trace grew") + } + if shouldEinoEmptyResponseContinue(out, hint, 1, 1) { + t.Fatal("expected no continue when trace did not grow") + } + if shouldEinoEmptyResponseContinue(&RunResult{Response: "hello"}, hint, 3, 1) { + t.Fatal("expected no continue when response has content") + } + if shouldEinoEmptyResponseContinue(nil, hint, 3, 1) { + t.Fatal("expected no continue for nil result") + } +} diff --git a/internal/multiagent/interrupt.go b/internal/multiagent/interrupt.go index e58b6ea9..dc9bc348 100644 --- a/internal/multiagent/interrupt.go +++ b/internal/multiagent/interrupt.go @@ -9,3 +9,7 @@ var ErrInterruptContinue = errors.New("agent interrupt: continue with user-suppl // ErrTransientRetryContinue 表示 Run 因 429/网络等临时错误结束,应由 handler 落库轨迹后 // loadHistoryFromAgentTrace 再开下一轮 Run(与 ErrInterruptContinue 同级的「分段续跑」语义)。 var ErrTransientRetryContinue = errors.New("agent transient: retry after persisting trace") + +// ErrEmptyResponseContinue 表示 Eino ADK 会话正常结束但未捕获到助手正文,应由 handler 落库轨迹后 +// loadHistoryFromAgentTrace 再开下一轮 Run(与 ErrInterruptContinue / ErrTransientRetryContinue 同级)。 +var ErrEmptyResponseContinue = errors.New("agent empty response: continue after persisting trace")