feat: refactor handoff logic and update UI descriptions for Graph Tool integration
This commit is contained in:
@@ -103,14 +103,6 @@ func (s *aiReplyService) TriggerReply(ctx context.Context, conversation models.C
|
||||
if pendingInterrupt := svc.ConversationInterruptService.FindLatestPendingByConversationID(conversation.ID); pendingInterrupt != nil {
|
||||
return s.resumePendingInterrupt(ctx, conversation, message, aiAgent, pendingInterrupt, trace, &summary)
|
||||
}
|
||||
if s.shouldHandoffByQuestion(message.Content, aiAgent) {
|
||||
return s.handoffConversation(conversation, aiAgent, "用户主动要求人工")
|
||||
}
|
||||
if aiAgent.ServiceMode != enums.IMConversationServiceModeAIOnly &&
|
||||
aiAgent.MaxAIReplyRounds > 0 &&
|
||||
conversation.AIReplyRounds >= aiAgent.MaxAIReplyRounds {
|
||||
return s.handoffConversation(conversation, aiAgent, "达到AI最大回复轮次")
|
||||
}
|
||||
aiConfig := svc.AIConfigService.Get(aiAgent.AIConfigID)
|
||||
if aiConfig == nil {
|
||||
return fmt.Errorf("ai config is nil")
|
||||
@@ -295,23 +287,6 @@ func (s *aiReplyService) sendAIReply(conversation models.Conversation, message m
|
||||
return replyMessage, err
|
||||
}
|
||||
|
||||
func (s *aiReplyService) shouldHandoffByQuestion(question string, aiAgent models.AIAgent) bool {
|
||||
if aiAgent.ServiceMode == enums.IMConversationServiceModeAIOnly {
|
||||
return false
|
||||
}
|
||||
normalized := strings.ReplaceAll(strings.ToLower(strings.TrimSpace(question)), " ", "")
|
||||
if normalized == "" {
|
||||
return false
|
||||
}
|
||||
keywords := []string{"转人工", "人工客服"}
|
||||
for _, keyword := range keywords {
|
||||
if strings.Contains(normalized, keyword) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *aiReplyService) writeRunLog(startedAt time.Time, message models.Message, conversation models.Conversation, aiAgent models.AIAgent,
|
||||
question string, runErr error, trace *aiReplyTraceData, summary *Summary) {
|
||||
errorMessage := ""
|
||||
@@ -661,7 +636,7 @@ func parseRuntimeTraceData(raw string) runtimeTraceProjection {
|
||||
|
||||
func isCancellationReply(replyText string) bool {
|
||||
replyText = strings.TrimSpace(replyText)
|
||||
return strings.Contains(replyText, "已取消本次工单创建")
|
||||
return strings.Contains(replyText, "已取消本次工单创建") || strings.Contains(replyText, "已取消本次转人工")
|
||||
}
|
||||
|
||||
func isCheckpointMissingError(err error) bool {
|
||||
@@ -672,16 +647,6 @@ func isCheckpointMissingError(err error) bool {
|
||||
return strings.Contains(message, "failed to load from checkpoint") && strings.Contains(message, "not exist")
|
||||
}
|
||||
|
||||
func (s *aiReplyService) handoffConversation(conversation models.Conversation, aiAgent models.AIAgent, reason string) error {
|
||||
if err := svc.ConversationService.HandoffByAI(conversation.ID, &aiAgent, reason); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := svc.MessageService.SendAIMessage(conversation.ID, aiAgent.ID, fmt.Sprintf("ai_handoff_%d", conversation.LastMessageID), enums.IMMessageTypeText, "已为你转接人工客服,请稍候。", "", s.buildAIPrincipal(aiAgent)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *aiReplyService) buildAIPrincipal(aiAgent models.AIAgent) *dto.AuthPrincipal {
|
||||
username := "AI"
|
||||
if strings.TrimSpace(aiAgent.Name) != "" {
|
||||
|
||||
@@ -95,7 +95,7 @@ func (s *service) prepareToolsForRun(req *Request) error {
|
||||
AIAgent: req.AIAgent,
|
||||
AIConfig: req.AIConfig,
|
||||
UserMessage: req.UserMessage,
|
||||
AllowedToolCodes: resolveAllowedToolCodes(req.AIAgent, req.SelectedSkill),
|
||||
AllowedToolCodes: ensureCoreGraphToolCodes(resolveAllowedToolCodes(req.AIAgent, req.SelectedSkill)),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -113,7 +113,7 @@ func (s *service) prepareToolsForResume(req *ResumeRequest) error {
|
||||
Conversation: req.Conversation,
|
||||
AIAgent: req.AIAgent,
|
||||
AIConfig: req.AIConfig,
|
||||
AllowedToolCodes: parseAgentAllowedToolCodes(req.AIAgent),
|
||||
AllowedToolCodes: ensureCoreGraphToolCodes(parseAgentAllowedToolCodes(req.AIAgent)),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -281,3 +281,18 @@ func resolveAllowedToolCodes(aiAgent *models.AIAgent, skill *models.SkillDefinit
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
||||
func ensureCoreGraphToolCodes(toolCodes []string) []string {
|
||||
if len(toolCodes) == 0 {
|
||||
return nil
|
||||
}
|
||||
ret := make([]string, 0, len(toolCodes)+1)
|
||||
ret = append(ret, toolCodes...)
|
||||
for _, item := range ret {
|
||||
if strings.TrimSpace(item) == toolx.GraphHandoffConversationToolCode {
|
||||
return ret
|
||||
}
|
||||
}
|
||||
ret = append(ret, toolx.GraphHandoffConversationToolCode)
|
||||
return ret
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ const fallbackModeOptions = getEnumOptions(AIAgentFallbackModeLabels).map(
|
||||
value: String(option.value),
|
||||
label: option.label,
|
||||
}),
|
||||
);
|
||||
).filter((option) => option.value !== String(AIAgentFallbackMode.Handoff));
|
||||
|
||||
function buildForm(item: AIAgent | null): EditForm {
|
||||
if (!item) {
|
||||
@@ -973,7 +973,7 @@ function EditDialogBody({
|
||||
|
||||
<SectionCard
|
||||
title="服务策略"
|
||||
description="控制转人工规则、兜底策略和自动回复行为边界。"
|
||||
description="控制 Graph Tool 转人工配置、兜底策略和自动回复边界。"
|
||||
>
|
||||
<div className="grid grid-cols-1 gap-4 xl:grid-cols-4">
|
||||
<Field data-invalid={!!errors.handoffMode}>
|
||||
@@ -1050,7 +1050,7 @@ function EditDialogBody({
|
||||
/>
|
||||
<PopoverContent side="top" align="start" className="max-w-xs">
|
||||
<PopoverDescription>
|
||||
单个会话内 AI 成功回复达到该次数后,下一条客户消息会自动转人工。填 0 表示不限制。
|
||||
该字段不再触发系统自动转人工,当前仅保留为兼容配置。转人工统一通过 AI 的 handoff_to_human Graph Tool 执行。
|
||||
</PopoverDescription>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
@@ -1163,7 +1163,7 @@ function EditDialogBody({
|
||||
</FieldLabel>
|
||||
<FieldContent>
|
||||
<div className="text-xs text-muted-foreground mb-1">
|
||||
仅在兜底模式为“直接声明无答案”或“引导补充信息”时使用;如果兜底模式配置为转人工,这里的文案不会生效。
|
||||
仅在兜底模式为“直接声明无答案”或“引导补充信息”时使用。转人工已统一改为 AI Graph Tool,不再通过兜底模式直接触发。
|
||||
</div>
|
||||
<Textarea
|
||||
id="ai-agent-fallback-message"
|
||||
|
||||
Reference in New Issue
Block a user