feat: add TriageServiceRequest tool and related graph for analyzing service requests and drafting tickets
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
package graphs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"cs-agent/internal/models"
|
||||
"cs-agent/internal/services"
|
||||
)
|
||||
|
||||
type TriageServiceRequestInput struct {
|
||||
Goal string `json:"goal"`
|
||||
ObservedIssue string `json:"observedIssue"`
|
||||
NeedTicket bool `json:"needTicket"`
|
||||
NeedHumanHandoff bool `json:"needHumanHandoff"`
|
||||
AdditionalContext string `json:"additionalContext"`
|
||||
}
|
||||
|
||||
type TriageServiceRequestResult struct {
|
||||
Analysis AnalyzeConversationResult `json:"analysis"`
|
||||
TicketDraft *PrepareTicketDraftResult `json:"ticketDraft,omitempty"`
|
||||
RecommendedAction string `json:"recommendedAction"`
|
||||
Ready bool `json:"ready"`
|
||||
}
|
||||
|
||||
type TriageServiceRequestGraph struct {
|
||||
conversation *models.Conversation
|
||||
}
|
||||
|
||||
func NewTriageServiceRequestGraph(conversation *models.Conversation) *TriageServiceRequestGraph {
|
||||
return &TriageServiceRequestGraph{conversation: conversation}
|
||||
}
|
||||
|
||||
func (g *TriageServiceRequestGraph) Run(_ context.Context, argumentsInJSON string) (string, error) {
|
||||
if g == nil || g.conversation == nil {
|
||||
return "", fmt.Errorf("triage service request graph not initialized")
|
||||
}
|
||||
input, err := g.parseInput(argumentsInJSON)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
messages, _, _ := services.MessageService.FindByConversationIDCursor(g.conversation.ID, 0, 8, "", "")
|
||||
analysis := buildAnalyzeConversationResult(g.conversation, messages, AnalyzeConversationInput{
|
||||
Goal: input.Goal,
|
||||
ObservedIssue: input.ObservedIssue,
|
||||
NeedTicket: input.NeedTicket,
|
||||
NeedHumanHandoff: input.NeedHumanHandoff,
|
||||
AdditionalContext: input.AdditionalContext,
|
||||
})
|
||||
result := TriageServiceRequestResult{
|
||||
Analysis: analysis,
|
||||
RecommendedAction: analysis.RecommendedNextAction,
|
||||
Ready: analysis.RecommendedNextAction == "continue_answering" || analysis.RecommendedNextAction == "handoff_to_human",
|
||||
}
|
||||
if analysis.RecommendedNextAction == "prepare_ticket" {
|
||||
draft := buildPrepareTicketDraftResult(g.conversation, messages, PrepareTicketDraftInput{
|
||||
Issue: input.ObservedIssue,
|
||||
})
|
||||
result.TicketDraft = &draft
|
||||
result.Ready = draft.Ready
|
||||
}
|
||||
buf, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
func (g *TriageServiceRequestGraph) parseInput(argumentsInJSON string) (TriageServiceRequestInput, error) {
|
||||
var input TriageServiceRequestInput
|
||||
if strings.TrimSpace(argumentsInJSON) == "" {
|
||||
return input, nil
|
||||
}
|
||||
if err := json.Unmarshal([]byte(argumentsInJSON), &input); err != nil {
|
||||
return input, fmt.Errorf("invalid triage service request arguments: %w", err)
|
||||
}
|
||||
input.Goal = strings.TrimSpace(input.Goal)
|
||||
input.ObservedIssue = strings.TrimSpace(input.ObservedIssue)
|
||||
input.AdditionalContext = strings.TrimSpace(input.AdditionalContext)
|
||||
return input, nil
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package graphs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"cs-agent/internal/models"
|
||||
"cs-agent/internal/pkg/enums"
|
||||
)
|
||||
|
||||
func TestTriageServiceRequestResult_PrepareTicket(t *testing.T) {
|
||||
conversation := &models.Conversation{
|
||||
Subject: "支付失败需要登记工单",
|
||||
LastMessageSummary: "用户要求建单跟进支付失败问题",
|
||||
}
|
||||
messages := []models.Message{
|
||||
{SenderType: enums.IMSenderTypeCustomer, Content: "帮我建个工单,支付一直失败"},
|
||||
}
|
||||
|
||||
analysis := buildAnalyzeConversationResult(conversation, messages, AnalyzeConversationInput{
|
||||
NeedTicket: true,
|
||||
})
|
||||
if analysis.RecommendedNextAction != "prepare_ticket" {
|
||||
t.Fatalf("expected prepare_ticket, got %q", analysis.RecommendedNextAction)
|
||||
}
|
||||
|
||||
draft := buildPrepareTicketDraftResult(conversation, messages, PrepareTicketDraftInput{})
|
||||
if draft.Title == "" || draft.Description == "" {
|
||||
t.Fatalf("expected draft to be populated, got %#v", draft)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTriageServiceRequestResult_Handoff(t *testing.T) {
|
||||
conversation := &models.Conversation{
|
||||
Subject: "投诉转人工",
|
||||
LastMessageSummary: "用户要求人工处理扣费投诉",
|
||||
}
|
||||
messages := []models.Message{
|
||||
{SenderType: enums.IMSenderTypeCustomer, Content: "我要投诉并转人工,你们重复扣费了"},
|
||||
}
|
||||
|
||||
analysis := buildAnalyzeConversationResult(conversation, messages, AnalyzeConversationInput{
|
||||
NeedHumanHandoff: true,
|
||||
})
|
||||
if analysis.RecommendedNextAction != "handoff_to_human" {
|
||||
t.Fatalf("expected handoff_to_human, got %q", analysis.RecommendedNextAction)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user