feat: enhance event consumption logic and add success reply handling

This commit is contained in:
mlogclub
2026-04-14 19:05:05 +08:00
parent 1f894ef7f0
commit 3cdd31786a
6 changed files with 103 additions and 2 deletions
@@ -4,6 +4,8 @@ import (
"strings"
"cs-agent/internal/ai/runtime/internal/impl/callbacks"
"cs-agent/internal/pkg/enums"
"cs-agent/internal/pkg/toolx"
"github.com/cloudwego/eino/adk"
"github.com/cloudwego/eino/schema"
@@ -56,6 +58,12 @@ func consumeAgentEvents(events *adk.AsyncIterator[*adk.AgentEvent], summary *Run
toolCode = strings.TrimSpace(mappedCode)
}
summary.InvokedToolCodes = appendIfMissing(summary.InvokedToolCodes, toolCode)
if strings.TrimSpace(summary.ReplyText) == "" && toolx.ResolveToolSourceType(toolCode) == enums.ToolSourceTypeGraph {
toolReplyText := strings.TrimSpace(messageOutput.Message.Content)
if toolReplyText != "" {
summary.ReplyText = toolReplyText
}
}
}
}
if summary.Status == "started" {
@@ -0,0 +1,41 @@
package executor
import (
"testing"
"cs-agent/internal/pkg/toolx"
"github.com/cloudwego/eino/adk"
"github.com/cloudwego/eino/schema"
)
func TestConsumeAgentEventsUsesGraphToolTextAsReplyFallback(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("unexpected reply text: %q", summary.ReplyText)
}
if summary.Status != "completed" {
t.Fatalf("unexpected summary status: %q", summary.Status)
}
}
+5 -1
View File
@@ -82,7 +82,7 @@ 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 "已为你转接人工客服,请稍候。", nil
return g.buildSuccessReply(), nil
case ConfirmationDecisionCancel:
return CancelHandoffReply, nil
default:
@@ -112,6 +112,10 @@ 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)
}
@@ -29,3 +29,13 @@ func TestHandoffGraphBuildReasonFallback(t *testing.T) {
t.Fatalf("unexpected fallback reason: %q", reason)
}
}
func TestHandoffGraphBuildSuccessReply(t *testing.T) {
graph := NewHandoffGraph(&models.Conversation{ID: 1}, &models.AIAgent{Name: "AI"})
got := graph.buildSuccessReply()
want := "已为你转接人工客服,请稍候。,请稍候。"
if got != want {
t.Fatalf("unexpected success reply: %q", got)
}
}
+10 -1
View File
@@ -514,7 +514,7 @@ func (s *messageService) ValidateConversationSender(conversationID int64, sender
if operator == nil {
return nil, errorsx.Unauthorized("未登录或登录已过期")
}
if conversation.Status != enums.IMConversationStatusAIServing {
if conversation.Status != enums.IMConversationStatusAIServing && !allowAIMessageOnPendingHandoff(conversation) {
return nil, errorsx.Forbidden("当前会话不处于 AI 接待状态")
}
if conversation.CurrentAssigneeID != 0 {
@@ -530,6 +530,15 @@ func (s *messageService) ValidateConversationSender(conversationID int64, sender
return conversation, nil
}
func allowAIMessageOnPendingHandoff(conversation *models.Conversation) bool {
if conversation == nil {
return false
}
return conversation.Status == enums.IMConversationStatusPending &&
conversation.HandoffAt != nil &&
conversation.CurrentAssigneeID == 0
}
func suffixFilenameForSummary(filename string) string {
filename = strings.TrimSpace(filename)
if filename == "" {
+29
View File
@@ -0,0 +1,29 @@
package services
import (
"testing"
"time"
"cs-agent/internal/models"
"cs-agent/internal/pkg/enums"
)
func TestAllowAIMessageOnPendingHandoff(t *testing.T) {
conversation := &models.Conversation{
Status: enums.IMConversationStatusPending,
CurrentAssigneeID: 0,
HandoffAt: ptrTime(time.Now()),
}
if !allowAIMessageOnPendingHandoff(conversation) {
t.Fatalf("expected pending handoff conversation to allow ai handoff notice")
}
conversation.Status = enums.IMConversationStatusAIServing
if allowAIMessageOnPendingHandoff(conversation) {
t.Fatalf("expected ai serving conversation not to use pending handoff allowance")
}
}
func ptrTime(v time.Time) *time.Time {
return &v
}