feat: refactor confirmation handling and add new constants for ticket creation and handoff processes

This commit is contained in:
mlogclub
2026-04-10 16:24:49 +08:00
parent 023ba802e4
commit 47a99a4372
4 changed files with 68 additions and 77 deletions
@@ -34,13 +34,6 @@ type CreateTicketGraph struct {
aiAgent *models.AIAgent
}
type Decision string
const (
DecisionConfirm Decision = "confirm"
DecisionCancel Decision = "cancel"
)
func NewCreateTicketGraph(conversation *models.Conversation, aiAgent *models.AIAgent) *CreateTicketGraph {
return &CreateTicketGraph{
conversation: conversation,
@@ -59,7 +52,7 @@ func (g *CreateTicketGraph) Run(ctx context.Context, argumentsInJSON string) (st
return "", err
}
info := CreateTicketGraphInterruptInfo{
Type: "ticket_creation_confirmation",
Type: InterruptTypeTicketCreationConfirmation,
Message: g.buildConfirmationPrompt(req),
}
return "", componenttool.StatefulInterrupt(ctx, info, CreateTicketGraphState{Request: req})
@@ -70,32 +63,32 @@ func (g *CreateTicketGraph) Run(ctx context.Context, argumentsInJSON string) (st
isResumeTarget, hasData, resumeText := componenttool.GetResumeContext[string](ctx)
if !isResumeTarget {
info := CreateTicketGraphInterruptInfo{
Type: "ticket_creation_confirmation",
Type: InterruptTypeTicketCreationConfirmation,
Message: g.buildConfirmationPrompt(state.Request),
}
return "", componenttool.StatefulInterrupt(ctx, info, state)
}
if !hasData {
info := CreateTicketGraphInterruptInfo{
Type: "ticket_creation_confirmation",
Message: "请回复“确认”或“取消”。",
Type: InterruptTypeTicketCreationConfirmation,
Message: ConfirmOrCancelPrompt,
}
return "", componenttool.StatefulInterrupt(ctx, info, state)
}
decision := ParseConfirmationDecision(resumeText)
switch decision {
case DecisionConfirm:
case ConfirmationDecisionConfirm:
item, err := services.TicketService.CreateFromConversation(state.Request, g.buildAIPrincipal())
if err != nil {
return "", err
}
return fmt.Sprintf("工单已创建,工单号:%s,标题:%s。", strings.TrimSpace(item.TicketNo), strings.TrimSpace(item.Title)), nil
case DecisionCancel:
return "已取消本次工单创建。", nil
case ConfirmationDecisionCancel:
return CancelCreateTicketReply, nil
default:
info := CreateTicketGraphInterruptInfo{
Type: "ticket_creation_confirmation",
Message: "我需要你的明确确认,请直接回复“确认”或“取消”。",
Type: InterruptTypeTicketCreationConfirmation,
Message: NeedExplicitConfirmationPrompt,
}
return "", componenttool.StatefulInterrupt(ctx, info, state)
}
@@ -176,23 +169,3 @@ func getInt64Value(data map[string]any, key string) int64 {
return 0
}
}
func ParseConfirmationDecision(value string) Decision {
value = strings.ToLower(strings.TrimSpace(value))
if value == "" {
return ""
}
confirmWords := []string{"确认", "是", "好的", "可以", "ok", "yes", "继续", "同意"}
for _, item := range confirmWords {
if strings.Contains(value, item) {
return DecisionConfirm
}
}
cancelWords := []string{"取消", "不用", "不需要", "算了", "no"}
for _, item := range cancelWords {
if strings.Contains(value, item) {
return DecisionCancel
}
}
return ""
}
+11 -34
View File
@@ -50,7 +50,7 @@ func (g *HandoffGraph) Run(ctx context.Context, argumentsInJSON string) (string,
return "", err
}
info := HandoffGraphInterruptInfo{
Type: "handoff_confirmation",
Type: InterruptTypeHandoffConfirmation,
Message: g.buildConfirmationPrompt(reason),
}
return "", componenttool.StatefulInterrupt(ctx, info, HandoffGraphState{Reason: reason})
@@ -61,30 +61,30 @@ func (g *HandoffGraph) Run(ctx context.Context, argumentsInJSON string) (string,
isResumeTarget, hasData, resumeText := componenttool.GetResumeContext[string](ctx)
if !isResumeTarget {
info := HandoffGraphInterruptInfo{
Type: "handoff_confirmation",
Type: InterruptTypeHandoffConfirmation,
Message: g.buildConfirmationPrompt(state.Reason),
}
return "", componenttool.StatefulInterrupt(ctx, info, state)
}
if !hasData {
info := HandoffGraphInterruptInfo{
Type: "handoff_confirmation",
Message: "请回复“确认”或“取消”。",
Type: InterruptTypeHandoffConfirmation,
Message: ConfirmOrCancelPrompt,
}
return "", componenttool.StatefulInterrupt(ctx, info, state)
}
switch parseHandoffDecision(resumeText) {
case graphDecisionConfirm:
case ConfirmationDecisionConfirm:
if err := services.ConversationService.HandoffByAI(g.conversation.ID, g.aiAgent, state.Reason); err != nil {
return "", err
}
return "已为你转接人工客服,请稍候。", nil
case graphDecisionCancel:
return "已取消本次转人工。", nil
case ConfirmationDecisionCancel:
return CancelHandoffReply, nil
default:
info := HandoffGraphInterruptInfo{
Type: "handoff_confirmation",
Message: "我需要你的明确确认,请直接回复“确认”或“取消”。",
Type: InterruptTypeHandoffConfirmation,
Message: NeedExplicitConfirmationPrompt,
}
return "", componenttool.StatefulInterrupt(ctx, info, state)
}
@@ -108,31 +108,8 @@ func (g *HandoffGraph) buildConfirmationPrompt(reason string) string {
return fmt.Sprintf("我准备为你转接人工客服。\n原因:%s\n请直接回复“确认”或“取消”。", strings.TrimSpace(reason))
}
type graphDecision string
const (
graphDecisionConfirm graphDecision = "confirm"
graphDecisionCancel graphDecision = "cancel"
)
func parseHandoffDecision(value string) graphDecision {
value = strings.ToLower(strings.TrimSpace(value))
if value == "" {
return ""
}
confirmWords := []string{"确认", "是", "好的", "可以", "ok", "yes", "继续", "同意"}
for _, item := range confirmWords {
if strings.Contains(value, item) {
return graphDecisionConfirm
}
}
cancelWords := []string{"取消", "不用", "不需要", "算了", "no"}
for _, item := range cancelWords {
if strings.Contains(value, item) {
return graphDecisionCancel
}
}
return ""
func parseHandoffDecision(value string) ConfirmationDecision {
return ParseConfirmationDecision(value)
}
func graphGetStringValue(data map[string]any, key string) string {
+45
View File
@@ -0,0 +1,45 @@
package graphs
import "strings"
const (
InterruptTypeTicketCreationConfirmation = "ticket_creation_confirmation"
InterruptTypeHandoffConfirmation = "handoff_confirmation"
ConfirmOrCancelPrompt = "请回复“确认”或“取消”。"
NeedExplicitConfirmationPrompt = "我需要你的明确确认,请直接回复“确认”或“取消”。"
ConfirmationExpiredReply = "本次确认已失效,请重新发起。"
CancelCreateTicketReply = "已取消本次工单创建。"
CancelHandoffReply = "已取消本次转人工。"
)
type ConfirmationDecision string
const (
ConfirmationDecisionConfirm ConfirmationDecision = "confirm"
ConfirmationDecisionCancel ConfirmationDecision = "cancel"
)
func ParseConfirmationDecision(value string) ConfirmationDecision {
value = strings.ToLower(strings.TrimSpace(value))
if value == "" {
return ""
}
confirmWords := []string{"确认", "是", "好的", "可以", "ok", "yes", "继续", "同意"}
for _, item := range confirmWords {
if strings.Contains(value, item) {
return ConfirmationDecisionConfirm
}
}
cancelWords := []string{"取消", "不用", "不需要", "算了", "no"}
for _, item := range cancelWords {
if strings.Contains(value, item) {
return ConfirmationDecisionCancel
}
}
return ""
}
func IsCancellationReply(replyText string) bool {
replyText = strings.TrimSpace(replyText)
return strings.Contains(replyText, CancelCreateTicketReply) || strings.Contains(replyText, CancelHandoffReply)
}
+3 -7
View File
@@ -8,6 +8,7 @@ import (
"strings"
"time"
"cs-agent/internal/ai/runtime/graphs"
"cs-agent/internal/models"
"cs-agent/internal/pkg/dto"
"cs-agent/internal/pkg/enums"
@@ -172,7 +173,7 @@ func (s *aiReplyService) resumePendingInterrupt(ctx context.Context, conversatio
if isCheckpointMissingError(err) {
summary = &Summary{
Status: "expired",
ReplyText: "本次确认已失效,请重新发起。",
ReplyText: graphs.ConfirmationExpiredReply,
}
*summaryRef = summary
trace.Status = "interrupt_expired"
@@ -220,7 +221,7 @@ func (s *aiReplyService) resumePendingInterrupt(ctx context.Context, conversatio
if replyMessage != nil {
replyMessageID = replyMessage.ID
}
if isCancellationReply(summary.ReplyText) {
if graphs.IsCancellationReply(summary.ReplyText) {
return svc.ConversationInterruptService.MarkCancelled(pendingInterrupt.ID, replyMessageID)
}
return svc.ConversationInterruptService.MarkResolved(pendingInterrupt.ID, replyMessageID)
@@ -652,11 +653,6 @@ func parseRuntimeTraceData(raw string) runtimeTraceProjection {
return trace
}
func isCancellationReply(replyText string) bool {
replyText = strings.TrimSpace(replyText)
return strings.Contains(replyText, "已取消本次工单创建") || strings.Contains(replyText, "已取消本次转人工")
}
func isCheckpointMissingError(err error) bool {
if err == nil {
return false