feat: enhance ticket draft workflow with new routing and confirmation prompts

This commit is contained in:
mlogclub
2026-06-30 17:26:28 +08:00
parent 94d36d6ca4
commit fa0010e8be
5 changed files with 220 additions and 19 deletions
+69 -10
View File
@@ -27,6 +27,7 @@ import (
const maxWorkflowSteps = 128 const maxWorkflowSteps = 128
var workflowHTMLTagPattern = regexp.MustCompile(`<[^>]+>`) var workflowHTMLTagPattern = regexp.MustCompile(`<[^>]+>`)
var workflowTemplateVariablePattern = regexp.MustCompile(`\{\{\s*([a-zA-Z0-9_]+)\s*\}\}`)
type Input struct { type Input struct {
Definition dsl.Definition Definition dsl.Definition
@@ -624,18 +625,28 @@ func (e *Executor) executePrepareTicketDraft(ctx context.Context, state *runStat
return err return err
} }
state.setNodeVars(node.ID, map[string]any{ state.setNodeVars(node.ID, map[string]any{
"ticketDraft": map[string]any{ "ticketDraft": ticketDraftWorkflowOutput(result),
"ready": result.Ready, "ready": result.Ready,
"title": strings.TrimSpace(result.Title), "title": strings.TrimSpace(result.Title),
"description": strings.TrimSpace(result.Description), "description": strings.TrimSpace(result.Description),
"missingFields": result.MissingFields, "missingFields": result.MissingFields,
"followUpQuestions": result.FollowUpQuestions, "followUpQuestions": result.FollowUpQuestions,
"conversationFacts": result.ConversationFacts, "conversationFacts": result.ConversationFacts,
},
}) })
return nil return nil
} }
func ticketDraftWorkflowOutput(result graphs.PrepareTicketDraftResult) map[string]any {
return map[string]any{
"ready": result.Ready,
"title": strings.TrimSpace(result.Title),
"description": strings.TrimSpace(result.Description),
"missingFields": result.MissingFields,
"followUpQuestions": result.FollowUpQuestions,
"conversationFacts": result.ConversationFacts,
}
}
func (e *Executor) executeAnalyzeConversation(ctx context.Context, state *runState, node dsl.Node) error { func (e *Executor) executeAnalyzeConversation(ctx context.Context, state *runState, node dsl.Node) error {
userMessage := strings.TrimSpace(toString(state.resolveInput(node, "userMessage"))) userMessage := strings.TrimSpace(toString(state.resolveInput(node, "userMessage")))
input := graphs.AnalyzeConversationInput{ input := graphs.AnalyzeConversationInput{
@@ -761,7 +772,7 @@ func (e *Executor) executeAnswerabilityGate(state *runState, node dsl.Node) erro
func (e *Executor) executeLLMReply(ctx context.Context, state *runState, node dsl.Node) error { func (e *Executor) executeLLMReply(ctx context.Context, state *runState, node dsl.Node) error {
if staticReply := strings.TrimSpace(readStringConfig(node.Data.Config, "staticReply")); staticReply != "" { if staticReply := strings.TrimSpace(readStringConfig(node.Data.Config, "staticReply")); staticReply != "" {
state.setNodeVars(node.ID, map[string]any{"replyText": staticReply}) state.setNodeVars(node.ID, map[string]any{"replyText": renderWorkflowTemplate(staticReply, state.resolvedInputs(node))})
return nil return nil
} }
userPrompt := strings.TrimSpace(toString(state.resolveInput(node, "userMessage"))) userPrompt := strings.TrimSpace(toString(state.resolveInput(node, "userMessage")))
@@ -942,11 +953,16 @@ func (s *runState) resolveInput(node dsl.Node, inputName string) any {
return s.resolveValue(value) return s.resolveValue(value)
} }
func (s *runState) nodeInputPreview(node dsl.Node) map[string]any { func (s *runState) resolvedInputs(node dsl.Node) map[string]any {
inputs := make(map[string]any, len(node.Data.InputsValues)) inputs := make(map[string]any, len(node.Data.InputsValues))
for name, value := range node.Data.InputsValues { for name, value := range node.Data.InputsValues {
inputs[name] = s.resolveValue(value) inputs[name] = s.resolveValue(value)
} }
return inputs
}
func (s *runState) nodeInputPreview(node dsl.Node) map[string]any {
inputs := s.resolvedInputs(node)
ret := map[string]any{ ret := map[string]any{
"inputs": inputs, "inputs": inputs,
} }
@@ -961,6 +977,49 @@ func (s *runState) nodeInputPreview(node dsl.Node) map[string]any {
return ret return ret
} }
func renderWorkflowTemplate(template string, values map[string]any) string {
if strings.TrimSpace(template) == "" || len(values) == 0 {
return template
}
return workflowTemplateVariablePattern.ReplaceAllStringFunc(template, func(match string) string {
parts := workflowTemplateVariablePattern.FindStringSubmatch(match)
if len(parts) < 2 {
return match
}
name := strings.TrimSpace(parts[1])
value, ok := values[name]
if !ok {
return ""
}
return workflowTemplateValueString(value)
})
}
func workflowTemplateValueString(value any) string {
switch v := value.(type) {
case nil:
return ""
case string:
return strings.TrimSpace(v)
case []string:
return strings.TrimSpace(strings.Join(v, "\n"))
case []any:
parts := make([]string, 0, len(v))
for _, item := range v {
if text := workflowTemplateValueString(item); text != "" {
parts = append(parts, text)
}
}
return strings.Join(parts, "\n")
default:
raw, err := json.Marshal(v)
if err != nil {
return strings.TrimSpace(fmt.Sprint(v))
}
return strings.TrimSpace(string(raw))
}
}
func (s *runState) nodeOutputPreview(nodeID string) map[string]any { func (s *runState) nodeOutputPreview(nodeID string) map[string]any {
ret := map[string]any{ ret := map[string]any{
"outputs": s.vars[nodeID], "outputs": s.vars[nodeID],
@@ -239,6 +239,58 @@ func TestExecutorPrepareTicketDraftOutputsDraftVariable(t *testing.T) {
assertPath(t, result.NodePath, []string{"start_1", "draft_1", "draft_route_1", "ready_end"}) assertPath(t, result.NodePath, []string{"start_1", "draft_1", "draft_route_1", "ready_end"})
} }
func TestExecutorPrepareTicketDraftRoutesIncompleteDraftToFollowUp(t *testing.T) {
db := setupWorkflowExecutorHandoffDB(t)
aiAgent := createWorkflowExecutorHandoffAIAgent(t, db, "1")
conversation := createWorkflowExecutorHandoffConversation(t, db, aiAgent.ID)
userMessage := createWorkflowExecutorCustomerMessage(t, db, conversation.ID, "")
result, err := NewExecutor().Execute(context.Background(), Input{
Definition: ticketDraftReadyWorkflowDefinition(),
Conversation: conversation,
UserMessage: userMessage,
AIAgent: aiAgent,
})
if err != nil {
t.Fatalf("execute workflow: %v", err)
}
if result.Interrupted {
t.Fatalf("expected incomplete draft to avoid confirmation interrupt")
}
if !strings.Contains(result.ReplyText, "Please provide") {
t.Fatalf("expected follow-up questions in reply, got %q", result.ReplyText)
}
assertPath(t, result.NodePath, []string{"start_1", "draft_1", "draft_route_1", "followup_1", "send_followup_1", "end_1"})
}
func TestExecutorTicketConfirmationPromptIncludesDraftTitleAndDescription(t *testing.T) {
db := setupWorkflowExecutorHandoffDB(t)
aiAgent := createWorkflowExecutorHandoffAIAgent(t, db, "1")
conversation := createWorkflowExecutorHandoffConversation(t, db, aiAgent.ID)
userMessage := createWorkflowExecutorCustomerMessage(t, db, conversation.ID, "订单支付失败,请帮我登记工单")
result, err := NewExecutor().Execute(context.Background(), Input{
Definition: ticketDraftReadyWorkflowDefinition(),
Conversation: conversation,
UserMessage: userMessage,
AIAgent: aiAgent,
})
if err != nil {
t.Fatalf("execute workflow: %v", err)
}
if !result.Interrupted {
t.Fatalf("expected ready draft to interrupt for confirmation")
}
if len(result.Interrupts) != 1 {
t.Fatalf("expected one interrupt, got %#v", result.Interrupts)
}
prompt := result.Interrupts[0].InfoPreview
if !strings.Contains(prompt, "订单支付失败") || !strings.Contains(prompt, "Issue: 订单支付失败") {
t.Fatalf("expected confirmation prompt to include draft title and description, got %q", prompt)
}
assertPath(t, result.NodePath, []string{"start_1", "draft_1", "draft_route_1", "prompt_1", "confirm_1"})
}
func TestExecutorPolicyFirstWorkflowRoutesGreetingToDirectReply(t *testing.T) { func TestExecutorPolicyFirstWorkflowRoutesGreetingToDirectReply(t *testing.T) {
result, err := NewExecutor().Execute(context.Background(), Input{ result, err := NewExecutor().Execute(context.Background(), Input{
Definition: policyFirstWorkflowDefinition(), Definition: policyFirstWorkflowDefinition(),
@@ -512,6 +564,40 @@ func conditionalReplyDefinition() dsl.Definition {
) )
} }
func ticketDraftReadyWorkflowDefinition() dsl.Definition {
return wfTestDefinition(
[]dsl.Node{
wfTestNode("start_1", workflowregistry.NodeTypeStart, "Start", nil, nil),
wfTestNode("draft_1", workflowregistry.NodeTypePrepareTicketDraft, "Draft", wfTestInputs("issue", "start_1", "userMessage"), nil),
wfTestNode("draft_route_1", workflowregistry.NodeTypeCondition, "Draft Route", nil, dsl.ConditionConfig{Branches: []dsl.ConditionBranch{
wfTestConditionBranch("ready", "Ready", "prompt_1", "draft_1", "ready", "is_true", nil),
{ID: "default", Name: "Need More Info", TargetNodeID: "followup_1", Default: true},
}}),
wfTestNode("prompt_1", workflowregistry.NodeTypeLLMReply, "Prompt", map[string]dsl.Value{
"userMessage": dsl.RefValue("start_1", "userMessage"),
"ticketTitle": dsl.RefValue("draft_1", "title"),
"ticketDescription": dsl.RefValue("draft_1", "description"),
}, map[string]any{"staticReply": "请确认创建工单:\n标题:{{ticketTitle}}\n描述:{{ticketDescription}}"}),
wfTestNode("confirm_1", workflowregistry.NodeTypeHumanConfirm, "Confirm", wfTestInputs("prompt", "prompt_1", "replyText"), nil),
wfTestNode("followup_1", workflowregistry.NodeTypeLLMReply, "Follow Up", map[string]dsl.Value{
"userMessage": dsl.RefValue("start_1", "userMessage"),
"followUpQuestions": dsl.RefValue("draft_1", "followUpQuestions"),
}, map[string]any{"staticReply": "{{followUpQuestions}}"}),
wfTestNode("send_followup_1", workflowregistry.NodeTypeSendReply, "Send Follow Up", wfTestInputs("replyText", "followup_1", "replyText"), nil),
wfTestNode("end_1", workflowregistry.NodeTypeEnd, "End", nil, nil),
},
[]dsl.Edge{
wfTestEdge("start_1", "draft_1", "edge_start_draft"),
wfTestEdge("draft_1", "draft_route_1", "edge_draft_route"),
wfTestEdge("draft_route_1", "prompt_1", "ready"),
wfTestEdge("draft_route_1", "followup_1", "default"),
wfTestEdge("prompt_1", "confirm_1", "edge_prompt_confirm"),
wfTestEdge("followup_1", "send_followup_1", "edge_followup_send"),
wfTestEdge("send_followup_1", "end_1", "edge_followup_end"),
},
)
}
func createTicketWorkflowDefinition() dsl.Definition { func createTicketWorkflowDefinition() dsl.Definition {
return wfTestDefinition( return wfTestDefinition(
[]dsl.Node{ []dsl.Node{
@@ -200,6 +200,11 @@ func DefaultRegistry() *Registry {
}, },
OutputSchema: []VariableSpec{ OutputSchema: []VariableSpec{
output("ticketDraft", "工单草稿", VariableTypeObject, "根据会话内容整理出的待确认工单草稿。"), output("ticketDraft", "工单草稿", VariableTypeObject, "根据会话内容整理出的待确认工单草稿。"),
output("ready", "草稿就绪", VariableTypeBoolean, "工单草稿是否已具备创建所需的关键信息。"),
output("title", "工单标题", VariableTypeString, "工单草稿标题。"),
output("description", "工单描述", VariableTypeString, "工单草稿描述。"),
output("missingFields", "缺失字段", VariableTypeStringArray, "仍需客户补充的字段列表。"),
output("followUpQuestions", "追问问题", VariableTypeStringArray, "用于补齐工单信息的追问问题。"),
}, },
}, },
NodeSpec{ NodeSpec{
@@ -2,6 +2,7 @@ package services
import ( import (
"encoding/json" "encoding/json"
"strings"
"testing" "testing"
"agent-desk/internal/ai/workflow/dsl" "agent-desk/internal/ai/workflow/dsl"
@@ -84,12 +85,15 @@ func TestAIAgentServiceCreatesDefaultWorkflow(t *testing.T) {
assertConditionBranchToNodeType(t, stored, "policy_route_1", workflowregistry.NodeTypeSendReply, "eq", "direct_reply") assertConditionBranchToNodeType(t, stored, "policy_route_1", workflowregistry.NodeTypeSendReply, "eq", "direct_reply")
assertConditionBranchToNodeType(t, stored, "policy_route_1", workflowregistry.NodeTypeHandoffToHuman, "eq", "handoff_to_human") assertConditionBranchToNodeType(t, stored, "policy_route_1", workflowregistry.NodeTypeHandoffToHuman, "eq", "handoff_to_human")
assertConditionBranchToNodeType(t, stored, "policy_route_1", workflowregistry.NodeTypePrepareTicketDraft, "eq", "prepare_ticket") assertConditionBranchToNodeType(t, stored, "policy_route_1", workflowregistry.NodeTypePrepareTicketDraft, "eq", "prepare_ticket")
assertConditionBranchToNodeID(t, stored, "ticket_draft_route_1", "ticket_confirm_prompt_1", "is_true", nil)
assertDefaultBranchToNodeID(t, stored, "ticket_draft_route_1", "ticket_followup_reply_1")
assertConditionBranchToNodeID(t, stored, "answerability_route_1", "reply_1", "eq", "answerable") assertConditionBranchToNodeID(t, stored, "answerability_route_1", "reply_1", "eq", "answerable")
assertDefaultBranchToNodeID(t, stored, "answerability_route_1", "fallback_reply_1") assertDefaultBranchToNodeID(t, stored, "answerability_route_1", "fallback_reply_1")
if !workflowEdgeExists(stored, "create_ticket_1", "ticket_result_reply_1") { if !workflowEdgeExists(stored, "create_ticket_1", "ticket_result_reply_1") {
t.Fatalf("expected create_ticket to flow into a customer-visible result reply") t.Fatalf("expected create_ticket to flow into a customer-visible result reply")
} }
assertConditionBranchesHavePortEdges(t, stored, "policy_route_1") assertConditionBranchesHavePortEdges(t, stored, "policy_route_1")
assertConditionBranchesHavePortEdges(t, stored, "ticket_draft_route_1")
assertConditionBranchesHavePortEdges(t, stored, "ticket_confirm_route_1") assertConditionBranchesHavePortEdges(t, stored, "ticket_confirm_route_1")
assertConditionBranchesHavePortEdges(t, stored, "answerability_route_1") assertConditionBranchesHavePortEdges(t, stored, "answerability_route_1")
assertConditionBranchOrder(t, stored, "policy_route_1", []string{ assertConditionBranchOrder(t, stored, "policy_route_1", []string{
@@ -135,6 +139,25 @@ func TestAIWorkflowServiceDefaultAgentWorkflowDefinitionIsValid(t *testing.T) {
} }
} }
func TestAIWorkflowServiceDefaultAgentWorkflowTicketPromptIncludesDraftFields(t *testing.T) {
definition := AIWorkflowService.DefaultAgentWorkflowDefinition()
prompt := workflowNodeByID(t, definition, "ticket_confirm_prompt_1")
if _, ok := prompt.Data.InputsValues["ticketTitle"]; !ok {
t.Fatalf("expected ticket confirm prompt to map ticketTitle")
}
if _, ok := prompt.Data.InputsValues["ticketDescription"]; !ok {
t.Fatalf("expected ticket confirm prompt to map ticketDescription")
}
config := map[string]any{}
if err := json.Unmarshal(prompt.Data.Config, &config); err != nil {
t.Fatalf("unmarshal prompt config: %v", err)
}
staticReply, _ := config["staticReply"].(string)
if !strings.Contains(staticReply, "{{ticketTitle}}") || !strings.Contains(staticReply, "{{ticketDescription}}") {
t.Fatalf("expected prompt template to include ticket title and description, got %q", staticReply)
}
}
func TestAIWorkflowServiceDefaultAgentWorkflowLayoutDoesNotOverlap(t *testing.T) { func TestAIWorkflowServiceDefaultAgentWorkflowLayoutDoesNotOverlap(t *testing.T) {
definition := AIWorkflowService.DefaultAgentWorkflowDefinition() definition := AIWorkflowService.DefaultAgentWorkflowDefinition()
assertWorkflowLayoutDoesNotOverlap(t, definition) assertWorkflowLayoutDoesNotOverlap(t, definition)
@@ -273,6 +296,17 @@ func nodeTypeByID(def dsl.Definition, nodeID string) string {
return "" return ""
} }
func workflowNodeByID(t *testing.T, def dsl.Definition, nodeID string) dsl.Node {
t.Helper()
for _, node := range def.Nodes {
if node.ID == nodeID {
return node
}
}
t.Fatalf("workflow node not found: %s", nodeID)
return dsl.Node{}
}
func assertConditionBranchToNodeType(t *testing.T, def dsl.Definition, sourceID string, targetType string, operator string, right any) { func assertConditionBranchToNodeType(t *testing.T, def dsl.Definition, sourceID string, targetType string, operator string, right any) {
t.Helper() t.Helper()
nodeTypes := workflowNodeTypeMap(def) nodeTypes := workflowNodeTypeMap(def)
+26 -9
View File
@@ -446,19 +446,32 @@ func defaultAgentWorkflowDefinition() dsl.Definition {
workflowNode("policy_reply_1", workflowregistry.NodeTypeSendReply, "发送策略回复", 4320, 98.5, workflowInputs("replyText", "policy_1", "replyText"), nil), workflowNode("policy_reply_1", workflowregistry.NodeTypeSendReply, "发送策略回复", 4320, 98.5, workflowInputs("replyText", "policy_1", "replyText"), nil),
workflowNode("handoff_end_1", workflowregistry.NodeTypeEnd, "结束", 2480, 0, nil, nil), workflowNode("handoff_end_1", workflowregistry.NodeTypeEnd, "结束", 2480, 0, nil, nil),
workflowNode("draft_ticket_1", workflowregistry.NodeTypePrepareTicketDraft, "整理工单草稿", 2020, 379, workflowInputs("issue", "start_1", "userMessage"), nil), workflowNode("draft_ticket_1", workflowregistry.NodeTypePrepareTicketDraft, "整理工单草稿", 2020, 379, workflowInputs("issue", "start_1", "userMessage"), nil),
workflowNode("ticket_confirm_prompt_1", workflowregistry.NodeTypeLLMReply, "建单确认文案", 2480, 379, workflowInputs("userMessage", "start_1", "userMessage"), map[string]any{"staticReply": "我已整理工单草稿。请回复“确认”创建工单,或回复“取消”放弃。"}), workflowNode("ticket_draft_route_1", workflowregistry.NodeTypeCondition, "草稿就绪分流", 2480, 329, nil, dsl.ConditionConfig{Branches: []dsl.ConditionBranch{
workflowNode("ticket_confirm_1", workflowregistry.NodeTypeHumanConfirm, "确认建单", 2940, 379, workflowInputs("prompt", "ticket_confirm_prompt_1", "replyText"), nil), workflowConditionBranch("ready", "草稿完整", "ticket_confirm_prompt_1", "draft_ticket_1", "ready", "is_true", nil),
workflowNode("ticket_confirm_route_1", workflowregistry.NodeTypeCondition, "建单确认分流", 3400, 329, nil, dsl.ConditionConfig{Branches: []dsl.ConditionBranch{ {ID: "default", Name: "补充信息", TargetNodeID: "ticket_followup_reply_1", Default: true},
}}),
workflowNode("ticket_confirm_prompt_1", workflowregistry.NodeTypeLLMReply, "建单确认文案", 2940, 285.5, map[string]dsl.Value{
"userMessage": dsl.RefValue("start_1", "userMessage"),
"ticketTitle": dsl.RefValue("draft_ticket_1", "title"),
"ticketDescription": dsl.RefValue("draft_ticket_1", "description"),
}, map[string]any{"staticReply": "我已整理工单草稿,请确认是否创建:\n标题:{{ticketTitle}}\n描述:{{ticketDescription}}\n请回复“确认”创建工单,或回复“取消”放弃。"}),
workflowNode("ticket_confirm_1", workflowregistry.NodeTypeHumanConfirm, "确认建单", 3400, 285.5, workflowInputs("prompt", "ticket_confirm_prompt_1", "replyText"), nil),
workflowNode("ticket_confirm_route_1", workflowregistry.NodeTypeCondition, "建单确认分流", 3860, 235.5, nil, dsl.ConditionConfig{Branches: []dsl.ConditionBranch{
workflowConditionBranch("confirmed", "已确认", "create_ticket_1", "ticket_confirm_1", "confirmed", "is_true", nil), workflowConditionBranch("confirmed", "已确认", "create_ticket_1", "ticket_confirm_1", "confirmed", "is_true", nil),
{ID: "default", Name: "取消或未确认", TargetNodeID: "ticket_cancel_reply_1", Default: true}, {ID: "default", Name: "取消或未确认", TargetNodeID: "ticket_cancel_reply_1", Default: true},
}}), }}),
workflowNode("create_ticket_1", workflowregistry.NodeTypeCreateTicket, "创建工单", 3860, 285.5, map[string]dsl.Value{ workflowNode("create_ticket_1", workflowregistry.NodeTypeCreateTicket, "创建工单", 4780, 192, map[string]dsl.Value{
"ticketDraft": dsl.RefValue("draft_ticket_1", "ticketDraft"), "ticketDraft": dsl.RefValue("draft_ticket_1", "ticketDraft"),
"confirmed": dsl.RefValue("ticket_confirm_1", "confirmed"), "confirmed": dsl.RefValue("ticket_confirm_1", "confirmed"),
}, nil), }, nil),
workflowNode("ticket_result_reply_1", workflowregistry.NodeTypeSendReply, "发送建单结果", 4320, 285.5, workflowInputs("replyText", "create_ticket_1", "message"), nil), workflowNode("ticket_result_reply_1", workflowregistry.NodeTypeSendReply, "发送建单结果", 5240, 192, workflowInputs("replyText", "create_ticket_1", "message"), nil),
workflowNode("ticket_cancel_reply_1", workflowregistry.NodeTypeLLMReply, "取消建单提示", 3860, 472.5, workflowInputs("userMessage", "start_1", "userMessage"), map[string]any{"staticReply": "已取消创建工单。你可以继续补充问题,我会继续帮你处理。"}), workflowNode("ticket_cancel_reply_1", workflowregistry.NodeTypeLLMReply, "取消建单提示", 4320, 379, workflowInputs("userMessage", "start_1", "userMessage"), map[string]any{"staticReply": "已取消创建工单。你可以继续补充问题,我会继续帮你处理。"}),
workflowNode("send_ticket_cancel_1", workflowregistry.NodeTypeSendReply, "发送取消提示", 4320, 472.5, workflowInputs("replyText", "ticket_cancel_reply_1", "replyText"), nil), workflowNode("send_ticket_cancel_1", workflowregistry.NodeTypeSendReply, "发送取消提示", 4780, 379, workflowInputs("replyText", "ticket_cancel_reply_1", "replyText"), nil),
workflowNode("ticket_followup_reply_1", workflowregistry.NodeTypeLLMReply, "追问工单信息", 3860, 1033.5, map[string]dsl.Value{
"userMessage": dsl.RefValue("start_1", "userMessage"),
"followUpQuestions": dsl.RefValue("draft_ticket_1", "followUpQuestions"),
}, map[string]any{"staticReply": "为了创建工单,还需要补充以下信息:\n{{followUpQuestions}}"}),
workflowNode("send_ticket_followup_1", workflowregistry.NodeTypeSendReply, "发送工单追问", 4780, 1033.5, workflowInputs("replyText", "ticket_followup_reply_1", "replyText"), nil),
workflowNode("retrieve_1", workflowregistry.NodeTypeKnowledgeRetrieve, "知识检索", 2480, 753, workflowInputs("query", "start_1", "userMessage"), nil), workflowNode("retrieve_1", workflowregistry.NodeTypeKnowledgeRetrieve, "知识检索", 2480, 753, workflowInputs("query", "start_1", "userMessage"), nil),
workflowNode("answerability_1", workflowregistry.NodeTypeAnswerabilityGate, "可回答判断", 2940, 753, map[string]dsl.Value{ workflowNode("answerability_1", workflowregistry.NodeTypeAnswerabilityGate, "可回答判断", 2940, 753, map[string]dsl.Value{
"userMessage": dsl.RefValue("start_1", "userMessage"), "userMessage": dsl.RefValue("start_1", "userMessage"),
@@ -478,7 +491,7 @@ func defaultAgentWorkflowDefinition() dsl.Definition {
"knowledgeItems": dsl.RefValue("retrieve_1", "items"), "knowledgeItems": dsl.RefValue("retrieve_1", "items"),
}, nil), }, nil),
workflowNode("send_fallback_1", workflowregistry.NodeTypeSendReply, "发送兜底", 4320, 846.5, workflowInputs("replyText", "fallback_reply_1", "replyText"), nil), workflowNode("send_fallback_1", workflowregistry.NodeTypeSendReply, "发送兜底", 4320, 846.5, workflowInputs("replyText", "fallback_reply_1", "replyText"), nil),
workflowNode("end_1", workflowregistry.NodeTypeEnd, "结束", 4780, 472.5, nil, nil), workflowNode("end_1", workflowregistry.NodeTypeEnd, "结束", 5700, 472.5, nil, nil),
}, },
Edges: []dsl.Edge{ Edges: []dsl.Edge{
workflowEdge("start_1", "understanding_1"), workflowEdge("start_1", "understanding_1"),
@@ -493,7 +506,9 @@ func defaultAgentWorkflowDefinition() dsl.Definition {
workflowPortEdge("policy_route_1", "policy_reply_1", "default"), workflowPortEdge("policy_route_1", "policy_reply_1", "default"),
workflowEdge("policy_reply_1", "end_1"), workflowEdge("policy_reply_1", "end_1"),
workflowEdge("handoff_1", "handoff_end_1"), workflowEdge("handoff_1", "handoff_end_1"),
workflowEdge("draft_ticket_1", "ticket_confirm_prompt_1"), workflowEdge("draft_ticket_1", "ticket_draft_route_1"),
workflowPortEdge("ticket_draft_route_1", "ticket_confirm_prompt_1", "ready"),
workflowPortEdge("ticket_draft_route_1", "ticket_followup_reply_1", "default"),
workflowEdge("ticket_confirm_prompt_1", "ticket_confirm_1"), workflowEdge("ticket_confirm_prompt_1", "ticket_confirm_1"),
workflowEdge("ticket_confirm_1", "ticket_confirm_route_1"), workflowEdge("ticket_confirm_1", "ticket_confirm_route_1"),
workflowPortEdge("ticket_confirm_route_1", "create_ticket_1", "confirmed"), workflowPortEdge("ticket_confirm_route_1", "create_ticket_1", "confirmed"),
@@ -502,6 +517,8 @@ func defaultAgentWorkflowDefinition() dsl.Definition {
workflowEdge("ticket_result_reply_1", "end_1"), workflowEdge("ticket_result_reply_1", "end_1"),
workflowEdge("ticket_cancel_reply_1", "send_ticket_cancel_1"), workflowEdge("ticket_cancel_reply_1", "send_ticket_cancel_1"),
workflowEdge("send_ticket_cancel_1", "end_1"), workflowEdge("send_ticket_cancel_1", "end_1"),
workflowEdge("ticket_followup_reply_1", "send_ticket_followup_1"),
workflowEdge("send_ticket_followup_1", "end_1"),
workflowEdge("retrieve_1", "answerability_1"), workflowEdge("retrieve_1", "answerability_1"),
workflowEdge("answerability_1", "answerability_route_1"), workflowEdge("answerability_1", "answerability_route_1"),
workflowPortEdge("answerability_route_1", "reply_1", "answerable"), workflowPortEdge("answerability_route_1", "reply_1", "answerable"),