feat: refactor confirmation handling and add new constants for ticket creation and handoff processes
This commit is contained in:
@@ -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 ""
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user