diff --git a/internal/ai/runtime/executor/event_consumer.go b/internal/ai/runtime/executor/event_consumer.go index 41280a1..2ec4a67 100644 --- a/internal/ai/runtime/executor/event_consumer.go +++ b/internal/ai/runtime/executor/event_consumer.go @@ -74,6 +74,8 @@ func consumeAgentEvents(events *adk.AsyncIterator[*adk.AgentEvent], summary *Run summary.Status = "interrupted" case strings.TrimSpace(summary.ReplyText) != "": summary.Status = "completed" + case hasInvokedGraphTool(summary.InvokedToolCodes): + summary.Status = "completed" default: summary.Status = "fallback" } @@ -81,6 +83,15 @@ func consumeAgentEvents(events *adk.AsyncIterator[*adk.AgentEvent], summary *Run summary.ToolCallCount = len(summary.InvokedToolCodes) } +func hasInvokedGraphTool(toolCodes []string) bool { + for _, toolCode := range toolCodes { + if toolx.ResolveToolSourceType(toolCode) == enums.ToolSourceTypeGraph { + return true + } + } + return false +} + func buildInterruptSummaries(event *adk.AgentEvent) []InterruptContextSummary { if event == nil || event.Action == nil || event.Action.Interrupted == nil { return nil diff --git a/internal/ai/runtime/executor/event_consumer_test.go b/internal/ai/runtime/executor/event_consumer_test.go index 2e2de1b..59bfcdb 100644 --- a/internal/ai/runtime/executor/event_consumer_test.go +++ b/internal/ai/runtime/executor/event_consumer_test.go @@ -39,3 +39,34 @@ func TestConsumeAgentEventsUsesGraphToolTextAsReplyFallback(t *testing.T) { t.Fatalf("unexpected summary status: %q", summary.Status) } } + +func TestConsumeAgentEventsCompletesGraphToolWithNoVisibleReply(t *testing.T) { + summary := &RunResult{ + Status: "started", + InvokedToolCodes: make([]string, 0), + } + events, gen := adk.NewAsyncIteratorPair[*adk.AgentEvent]() + gen.Send(&adk.AgentEvent{ + Output: &adk.AgentOutput{ + MessageOutput: &adk.MessageVariant{ + Role: schema.Tool, + ToolName: toolx.GraphHandoffConversation.Name, + Message: &schema.Message{ + Content: "", + }, + }, + }, + }) + gen.Close() + + consumeAgentEvents(events, summary, nil, map[string]string{ + toolx.GraphHandoffConversation.Name: toolx.GraphHandoffConversation.Code, + }) + + if summary.ReplyText != "" { + t.Fatalf("expected no reply text, got %q", summary.ReplyText) + } + if summary.Status != "completed" { + t.Fatalf("unexpected summary status: %q", summary.Status) + } +} diff --git a/internal/ai/runtime/graphs/handoff_graph.go b/internal/ai/runtime/graphs/handoff_graph.go index e8c6ee0..3ba353d 100644 --- a/internal/ai/runtime/graphs/handoff_graph.go +++ b/internal/ai/runtime/graphs/handoff_graph.go @@ -79,7 +79,8 @@ func (g *HandoffGraph) Run(ctx context.Context, argumentsInJSON string) (string, if err := services.ConversationService.HandoffByAI(g.conversation.ID, g.aiAgent, state.Reason); err != nil { return "", err } - return g.buildSuccessReply(), nil + // ConversationService sends the customer-visible handoff notice according to the dispatch decision. + return "", nil case ConfirmationDecisionCancel: return CancelHandoffReply, nil default: @@ -109,10 +110,6 @@ func (g *HandoffGraph) buildConfirmationPrompt(reason string) string { return fmt.Sprintf("我准备为你转接人工客服。\n原因:%s\n请直接回复“确认”或“取消”。", strings.TrimSpace(reason)) } -func (g *HandoffGraph) buildSuccessReply() string { - return "已为你转接人工客服,请稍候。,请稍候。" -} - func parseHandoffDecision(value string) ConfirmationDecision { return ParseConfirmationDecision(value) }