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
|
aiAgent *models.AIAgent
|
||||||
}
|
}
|
||||||
|
|
||||||
type Decision string
|
|
||||||
|
|
||||||
const (
|
|
||||||
DecisionConfirm Decision = "confirm"
|
|
||||||
DecisionCancel Decision = "cancel"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewCreateTicketGraph(conversation *models.Conversation, aiAgent *models.AIAgent) *CreateTicketGraph {
|
func NewCreateTicketGraph(conversation *models.Conversation, aiAgent *models.AIAgent) *CreateTicketGraph {
|
||||||
return &CreateTicketGraph{
|
return &CreateTicketGraph{
|
||||||
conversation: conversation,
|
conversation: conversation,
|
||||||
@@ -59,7 +52,7 @@ func (g *CreateTicketGraph) Run(ctx context.Context, argumentsInJSON string) (st
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
info := CreateTicketGraphInterruptInfo{
|
info := CreateTicketGraphInterruptInfo{
|
||||||
Type: "ticket_creation_confirmation",
|
Type: InterruptTypeTicketCreationConfirmation,
|
||||||
Message: g.buildConfirmationPrompt(req),
|
Message: g.buildConfirmationPrompt(req),
|
||||||
}
|
}
|
||||||
return "", componenttool.StatefulInterrupt(ctx, info, CreateTicketGraphState{Request: 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)
|
isResumeTarget, hasData, resumeText := componenttool.GetResumeContext[string](ctx)
|
||||||
if !isResumeTarget {
|
if !isResumeTarget {
|
||||||
info := CreateTicketGraphInterruptInfo{
|
info := CreateTicketGraphInterruptInfo{
|
||||||
Type: "ticket_creation_confirmation",
|
Type: InterruptTypeTicketCreationConfirmation,
|
||||||
Message: g.buildConfirmationPrompt(state.Request),
|
Message: g.buildConfirmationPrompt(state.Request),
|
||||||
}
|
}
|
||||||
return "", componenttool.StatefulInterrupt(ctx, info, state)
|
return "", componenttool.StatefulInterrupt(ctx, info, state)
|
||||||
}
|
}
|
||||||
if !hasData {
|
if !hasData {
|
||||||
info := CreateTicketGraphInterruptInfo{
|
info := CreateTicketGraphInterruptInfo{
|
||||||
Type: "ticket_creation_confirmation",
|
Type: InterruptTypeTicketCreationConfirmation,
|
||||||
Message: "请回复“确认”或“取消”。",
|
Message: ConfirmOrCancelPrompt,
|
||||||
}
|
}
|
||||||
return "", componenttool.StatefulInterrupt(ctx, info, state)
|
return "", componenttool.StatefulInterrupt(ctx, info, state)
|
||||||
}
|
}
|
||||||
decision := ParseConfirmationDecision(resumeText)
|
decision := ParseConfirmationDecision(resumeText)
|
||||||
switch decision {
|
switch decision {
|
||||||
case DecisionConfirm:
|
case ConfirmationDecisionConfirm:
|
||||||
item, err := services.TicketService.CreateFromConversation(state.Request, g.buildAIPrincipal())
|
item, err := services.TicketService.CreateFromConversation(state.Request, g.buildAIPrincipal())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("工单已创建,工单号:%s,标题:%s。", strings.TrimSpace(item.TicketNo), strings.TrimSpace(item.Title)), nil
|
return fmt.Sprintf("工单已创建,工单号:%s,标题:%s。", strings.TrimSpace(item.TicketNo), strings.TrimSpace(item.Title)), nil
|
||||||
case DecisionCancel:
|
case ConfirmationDecisionCancel:
|
||||||
return "已取消本次工单创建。", nil
|
return CancelCreateTicketReply, nil
|
||||||
default:
|
default:
|
||||||
info := CreateTicketGraphInterruptInfo{
|
info := CreateTicketGraphInterruptInfo{
|
||||||
Type: "ticket_creation_confirmation",
|
Type: InterruptTypeTicketCreationConfirmation,
|
||||||
Message: "我需要你的明确确认,请直接回复“确认”或“取消”。",
|
Message: NeedExplicitConfirmationPrompt,
|
||||||
}
|
}
|
||||||
return "", componenttool.StatefulInterrupt(ctx, info, state)
|
return "", componenttool.StatefulInterrupt(ctx, info, state)
|
||||||
}
|
}
|
||||||
@@ -176,23 +169,3 @@ func getInt64Value(data map[string]any, key string) int64 {
|
|||||||
return 0
|
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
|
return "", err
|
||||||
}
|
}
|
||||||
info := HandoffGraphInterruptInfo{
|
info := HandoffGraphInterruptInfo{
|
||||||
Type: "handoff_confirmation",
|
Type: InterruptTypeHandoffConfirmation,
|
||||||
Message: g.buildConfirmationPrompt(reason),
|
Message: g.buildConfirmationPrompt(reason),
|
||||||
}
|
}
|
||||||
return "", componenttool.StatefulInterrupt(ctx, info, HandoffGraphState{Reason: 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)
|
isResumeTarget, hasData, resumeText := componenttool.GetResumeContext[string](ctx)
|
||||||
if !isResumeTarget {
|
if !isResumeTarget {
|
||||||
info := HandoffGraphInterruptInfo{
|
info := HandoffGraphInterruptInfo{
|
||||||
Type: "handoff_confirmation",
|
Type: InterruptTypeHandoffConfirmation,
|
||||||
Message: g.buildConfirmationPrompt(state.Reason),
|
Message: g.buildConfirmationPrompt(state.Reason),
|
||||||
}
|
}
|
||||||
return "", componenttool.StatefulInterrupt(ctx, info, state)
|
return "", componenttool.StatefulInterrupt(ctx, info, state)
|
||||||
}
|
}
|
||||||
if !hasData {
|
if !hasData {
|
||||||
info := HandoffGraphInterruptInfo{
|
info := HandoffGraphInterruptInfo{
|
||||||
Type: "handoff_confirmation",
|
Type: InterruptTypeHandoffConfirmation,
|
||||||
Message: "请回复“确认”或“取消”。",
|
Message: ConfirmOrCancelPrompt,
|
||||||
}
|
}
|
||||||
return "", componenttool.StatefulInterrupt(ctx, info, state)
|
return "", componenttool.StatefulInterrupt(ctx, info, state)
|
||||||
}
|
}
|
||||||
switch parseHandoffDecision(resumeText) {
|
switch parseHandoffDecision(resumeText) {
|
||||||
case graphDecisionConfirm:
|
case ConfirmationDecisionConfirm:
|
||||||
if err := services.ConversationService.HandoffByAI(g.conversation.ID, g.aiAgent, state.Reason); err != nil {
|
if err := services.ConversationService.HandoffByAI(g.conversation.ID, g.aiAgent, state.Reason); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return "已为你转接人工客服,请稍候。", nil
|
return "已为你转接人工客服,请稍候。", nil
|
||||||
case graphDecisionCancel:
|
case ConfirmationDecisionCancel:
|
||||||
return "已取消本次转人工。", nil
|
return CancelHandoffReply, nil
|
||||||
default:
|
default:
|
||||||
info := HandoffGraphInterruptInfo{
|
info := HandoffGraphInterruptInfo{
|
||||||
Type: "handoff_confirmation",
|
Type: InterruptTypeHandoffConfirmation,
|
||||||
Message: "我需要你的明确确认,请直接回复“确认”或“取消”。",
|
Message: NeedExplicitConfirmationPrompt,
|
||||||
}
|
}
|
||||||
return "", componenttool.StatefulInterrupt(ctx, info, state)
|
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))
|
return fmt.Sprintf("我准备为你转接人工客服。\n原因:%s\n请直接回复“确认”或“取消”。", strings.TrimSpace(reason))
|
||||||
}
|
}
|
||||||
|
|
||||||
type graphDecision string
|
func parseHandoffDecision(value string) ConfirmationDecision {
|
||||||
|
return ParseConfirmationDecision(value)
|
||||||
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 graphGetStringValue(data map[string]any, key string) string {
|
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"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"cs-agent/internal/ai/runtime/graphs"
|
||||||
"cs-agent/internal/models"
|
"cs-agent/internal/models"
|
||||||
"cs-agent/internal/pkg/dto"
|
"cs-agent/internal/pkg/dto"
|
||||||
"cs-agent/internal/pkg/enums"
|
"cs-agent/internal/pkg/enums"
|
||||||
@@ -172,7 +173,7 @@ func (s *aiReplyService) resumePendingInterrupt(ctx context.Context, conversatio
|
|||||||
if isCheckpointMissingError(err) {
|
if isCheckpointMissingError(err) {
|
||||||
summary = &Summary{
|
summary = &Summary{
|
||||||
Status: "expired",
|
Status: "expired",
|
||||||
ReplyText: "本次确认已失效,请重新发起。",
|
ReplyText: graphs.ConfirmationExpiredReply,
|
||||||
}
|
}
|
||||||
*summaryRef = summary
|
*summaryRef = summary
|
||||||
trace.Status = "interrupt_expired"
|
trace.Status = "interrupt_expired"
|
||||||
@@ -220,7 +221,7 @@ func (s *aiReplyService) resumePendingInterrupt(ctx context.Context, conversatio
|
|||||||
if replyMessage != nil {
|
if replyMessage != nil {
|
||||||
replyMessageID = replyMessage.ID
|
replyMessageID = replyMessage.ID
|
||||||
}
|
}
|
||||||
if isCancellationReply(summary.ReplyText) {
|
if graphs.IsCancellationReply(summary.ReplyText) {
|
||||||
return svc.ConversationInterruptService.MarkCancelled(pendingInterrupt.ID, replyMessageID)
|
return svc.ConversationInterruptService.MarkCancelled(pendingInterrupt.ID, replyMessageID)
|
||||||
}
|
}
|
||||||
return svc.ConversationInterruptService.MarkResolved(pendingInterrupt.ID, replyMessageID)
|
return svc.ConversationInterruptService.MarkResolved(pendingInterrupt.ID, replyMessageID)
|
||||||
@@ -652,11 +653,6 @@ func parseRuntimeTraceData(raw string) runtimeTraceProjection {
|
|||||||
return trace
|
return trace
|
||||||
}
|
}
|
||||||
|
|
||||||
func isCancellationReply(replyText string) bool {
|
|
||||||
replyText = strings.TrimSpace(replyText)
|
|
||||||
return strings.Contains(replyText, "已取消本次工单创建") || strings.Contains(replyText, "已取消本次转人工")
|
|
||||||
}
|
|
||||||
|
|
||||||
func isCheckpointMissingError(err error) bool {
|
func isCheckpointMissingError(err error) bool {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return false
|
return false
|
||||||
|
|||||||
Reference in New Issue
Block a user