199 lines
5.6 KiB
Go
199 lines
5.6 KiB
Go
package graphs
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"cs-agent/internal/models"
|
|
"cs-agent/internal/pkg/dto"
|
|
"cs-agent/internal/pkg/dto/request"
|
|
"cs-agent/internal/services"
|
|
|
|
componenttool "github.com/cloudwego/eino/components/tool"
|
|
"github.com/cloudwego/eino/schema"
|
|
)
|
|
|
|
type CreateTicketGraphState struct {
|
|
Request request.CreateTicketFromConversationRequest
|
|
}
|
|
|
|
type CreateTicketGraphInterruptInfo struct {
|
|
Type string `json:"type"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
func init() {
|
|
schema.RegisterName[CreateTicketGraphState]("cs_agent_create_ticket_graph_state")
|
|
schema.RegisterName[CreateTicketGraphInterruptInfo]("cs_agent_create_ticket_graph_interrupt_info")
|
|
}
|
|
|
|
type CreateTicketGraph struct {
|
|
conversation *models.Conversation
|
|
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,
|
|
aiAgent: aiAgent,
|
|
}
|
|
}
|
|
|
|
func (g *CreateTicketGraph) Run(ctx context.Context, argumentsInJSON string) (string, error) {
|
|
if g == nil || g.conversation == nil || g.aiAgent == nil {
|
|
return "", fmt.Errorf("create ticket graph not initialized")
|
|
}
|
|
wasInterrupted, hasState, state := componenttool.GetInterruptState[CreateTicketGraphState](ctx)
|
|
if !wasInterrupted {
|
|
req, err := g.buildCreateRequest(argumentsInJSON)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
info := CreateTicketGraphInterruptInfo{
|
|
Type: "ticket_creation_confirmation",
|
|
Message: g.buildConfirmationPrompt(req),
|
|
}
|
|
return "", componenttool.StatefulInterrupt(ctx, info, CreateTicketGraphState{Request: req})
|
|
}
|
|
if !hasState {
|
|
return "", fmt.Errorf("create ticket graph state missing")
|
|
}
|
|
isResumeTarget, hasData, resumeText := componenttool.GetResumeContext[string](ctx)
|
|
if !isResumeTarget {
|
|
info := CreateTicketGraphInterruptInfo{
|
|
Type: "ticket_creation_confirmation",
|
|
Message: g.buildConfirmationPrompt(state.Request),
|
|
}
|
|
return "", componenttool.StatefulInterrupt(ctx, info, state)
|
|
}
|
|
if !hasData {
|
|
info := CreateTicketGraphInterruptInfo{
|
|
Type: "ticket_creation_confirmation",
|
|
Message: "请回复“确认”或“取消”。",
|
|
}
|
|
return "", componenttool.StatefulInterrupt(ctx, info, state)
|
|
}
|
|
decision := ParseConfirmationDecision(resumeText)
|
|
switch decision {
|
|
case DecisionConfirm:
|
|
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
|
|
default:
|
|
info := CreateTicketGraphInterruptInfo{
|
|
Type: "ticket_creation_confirmation",
|
|
Message: "我需要你的明确确认,请直接回复“确认”或“取消”。",
|
|
}
|
|
return "", componenttool.StatefulInterrupt(ctx, info, state)
|
|
}
|
|
}
|
|
|
|
func (g *CreateTicketGraph) buildCreateRequest(argumentsInJSON string) (request.CreateTicketFromConversationRequest, error) {
|
|
req := request.CreateTicketFromConversationRequest{
|
|
ConversationID: g.conversation.ID,
|
|
SyncToConversation: true,
|
|
}
|
|
raw := make(map[string]any)
|
|
if strings.TrimSpace(argumentsInJSON) != "" {
|
|
if err := json.Unmarshal([]byte(argumentsInJSON), &raw); err != nil {
|
|
return req, fmt.Errorf("invalid create ticket arguments: %w", err)
|
|
}
|
|
}
|
|
req.Title = strings.TrimSpace(getStringValue(raw, "title"))
|
|
req.Description = strings.TrimSpace(getStringValue(raw, "description"))
|
|
req.Priority = getInt64Value(raw, "priority")
|
|
req.Severity = int(getInt64Value(raw, "severity"))
|
|
if req.Title == "" {
|
|
req.Title = strings.TrimSpace(g.conversation.Subject)
|
|
}
|
|
if req.Description == "" {
|
|
req.Description = strings.TrimSpace(g.conversation.LastMessageSummary)
|
|
}
|
|
if strings.TrimSpace(req.Title) == "" {
|
|
return req, fmt.Errorf("ticket title is required")
|
|
}
|
|
return req, nil
|
|
}
|
|
|
|
func (g *CreateTicketGraph) buildConfirmationPrompt(req request.CreateTicketFromConversationRequest) string {
|
|
return fmt.Sprintf("我准备为你创建工单。\n标题:%s\n描述:%s\n请直接回复“确认”或“取消”。",
|
|
strings.TrimSpace(req.Title), strings.TrimSpace(req.Description))
|
|
}
|
|
|
|
func (g *CreateTicketGraph) buildAIPrincipal() *dto.AuthPrincipal {
|
|
username := "AI"
|
|
if strings.TrimSpace(g.aiAgent.Name) != "" {
|
|
username = strings.TrimSpace(g.aiAgent.Name)
|
|
}
|
|
return &dto.AuthPrincipal{
|
|
UserID: 0,
|
|
Username: username,
|
|
Nickname: username,
|
|
}
|
|
}
|
|
|
|
func getStringValue(data map[string]any, key string) string {
|
|
if len(data) == 0 {
|
|
return ""
|
|
}
|
|
value, ok := data[key]
|
|
if !ok {
|
|
return ""
|
|
}
|
|
text, _ := value.(string)
|
|
return text
|
|
}
|
|
|
|
func getInt64Value(data map[string]any, key string) int64 {
|
|
if len(data) == 0 {
|
|
return 0
|
|
}
|
|
value, ok := data[key]
|
|
if !ok {
|
|
return 0
|
|
}
|
|
switch v := value.(type) {
|
|
case float64:
|
|
return int64(v)
|
|
case int64:
|
|
return v
|
|
case int:
|
|
return int64(v)
|
|
default:
|
|
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 ""
|
|
}
|