Files
Agent-System/internal/intent/workflow_matcher.go
T

301 lines
8.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package intent
import (
"fmt"
"strings"
)
var workflowAliasMap = map[string]string{
"产品经理": "product_manager",
"智能产品经理": "product_manager",
"产品工作流": "product_manager",
"产品经理工作流": "product_manager",
"urs": "product_manager",
"需求规格": "product_manager",
"测试经理": "test_manager",
"智能测试经理": "test_manager",
"测试工作流": "test_manager",
"测试经理工作流": "test_manager",
"验证方案": "test_manager",
"主工作流": "main",
"默认工作流": "main",
"main": "main",
}
// extractWorkflowKey 从输入中解析工作流键
func extractWorkflowKey(input string) string {
lower := strings.ToLower(input)
for phrase, key := range workflowAliasMap {
if strings.Contains(lower, strings.ToLower(phrase)) || strings.Contains(input, phrase) {
return key
}
}
return ""
}
func isWorkflowControlInput(input string) bool {
if strings.Contains(input, "工作流") {
return true
}
return extractWorkflowKey(input) != ""
}
func matchSwitchWorkflow(input string, snap *ContextSnapshot) *CandidateIntent {
act := detectDialogueAct(input)
if act == DialogueActAsk {
return nil
}
if !isWorkflowControlInput(input) {
return nil
}
switchMarkers := []string{"切换", "换成", "改用", "激活", "切到", "切换到", "切换至"}
hasSwitch := false
for _, m := range switchMarkers {
if strings.Contains(input, m) {
hasSwitch = true
break
}
}
if !hasSwitch {
return nil
}
wfKey := extractWorkflowKey(input)
slots := map[string]string{}
if wfKey != "" {
slots[SlotTargetWorkflow] = wfKey
}
reasoning := "用户请求切换项目激活工作流"
if wfKey == "" {
return &CandidateIntent{
Intent: IntentSwitchWorkflow,
Confidence: 0.88,
FilledSlots: slots,
Reasoning: reasoning,
ClarificationQuestion: "请说明要切换到哪个工作流:产品经理(product_manager)、测试经理(test_manager)还是主工作流(main)?",
}
}
return &CandidateIntent{
Intent: IntentSwitchWorkflow,
Confidence: 0.93,
FilledSlots: slots,
Reasoning: reasoning + " → " + wfKey,
}
}
func matchTriggerWorkflow(input string, snap *ContextSnapshot) *CandidateIntent {
if detectDialogueAct(input) == DialogueActAsk {
return nil
}
if !isWorkflowControlInput(input) {
return nil
}
runMarkers := []string{"运行", "启动", "执行", "跑一下", "跑一版", "开始跑"}
hasRun := false
for _, m := range runMarkers {
if strings.Contains(input, m) {
hasRun = true
break
}
}
wfKey := extractWorkflowKey(input)
if !hasRun && wfKey == "" {
return nil
}
if !hasRun && wfKey != "" {
// 「生成 URS」类可能走 TRIGGER_TASK,不强行 workflow run
if strings.Contains(input, "切换") {
return nil
}
}
slots := map[string]string{}
if wfKey != "" {
slots[SlotTargetWorkflow] = wfKey
} else if snap != nil && snap.AvailableActions != nil {
// 默认当前上下文工作流由 L2 解析
for _, a := range snap.AvailableActions {
if strings.HasPrefix(a, "TRIGGER_WORKFLOW:") {
slots[SlotTargetWorkflow] = strings.TrimPrefix(a, "TRIGGER_WORKFLOW:")
break
}
}
}
if wfKey == "" && slots[SlotTargetWorkflow] == "" {
return &CandidateIntent{
Intent: IntentTriggerWorkflow,
Confidence: 0.86,
FilledSlots: slots,
Reasoning: "用户请求运行工作流但未指定目标",
ClarificationQuestion: "请说明要运行哪个工作流:产品经理(product_manager)还是测试经理(test_manager)?",
}
}
return &CandidateIntent{
Intent: IntentTriggerWorkflow,
Confidence: 0.91,
FilledSlots: slots,
Reasoning: "用户请求运行/启动项目工作流",
}
}
func matchQueryWorkflowStatus(input string, snap *ContextSnapshot) *CandidateIntent {
if detectDialogueAct(input) != DialogueActAsk {
return nil
}
markers := []string{
"当前工作流", "哪个工作流", "什么工作流", "激活的工作流", "激活的是哪个",
"有哪些工作流", "可用工作流", "工作流列表", "工作流状态",
}
hasWF := strings.Contains(input, "工作流")
matched := false
for _, m := range markers {
if strings.Contains(input, m) {
matched = true
break
}
}
if !matched && !(hasWF && (strings.Contains(input, "吗") || strings.Contains(input, "?") || strings.Contains(input, "?"))) {
return nil
}
return &CandidateIntent{
Intent: IntentQueryWorkflowStatus,
Confidence: 0.92,
FilledSlots: map[string]string{
SlotQueryTopic: "workflow_status",
},
Reasoning: "用户查询当前或可用工作流状态",
}
}
func matchQueryDocStatus(input string, snap *ContextSnapshot) *CandidateIntent {
// “总结/概括/摘要”是正文只读操作,即使语气是命令式也不得落入写任务。
// 若同时出现明确写动作,则交由写任务意图继续裁决。
summaryMarkers := []string{"总结", "概括", "摘要", "提炼要点", "归纳"}
writeMarkers := []string{"编写", "编制", "生成", "起草", "创建", "撰写", "重写", "更新", "修改", "完善", "补充", "刷新", "重做"}
isSummary := false
for _, marker := range summaryMarkers {
if strings.Contains(input, marker) {
isSummary = true
break
}
}
if isSummary {
for _, marker := range writeMarkers {
if strings.Contains(input, marker) {
isSummary = false
break
}
}
}
if isSummary {
target := extractDocName(input)
if target != "" || strings.Contains(input, "文档") || strings.Contains(input, "报告") {
slots := map[string]string{SlotQueryTopic: "doc_summary"}
if target != "" {
slots[SlotTargetDoc] = target
} else if snap != nil && snap.ActiveDocID != "" {
slots[SlotTargetDoc] = snap.ActiveDocID
}
return &CandidateIntent{
Intent: IntentQueryDocStatus,
Confidence: 0.97,
FilledSlots: slots,
Reasoning: "用户请求只读总结受控文档正文",
}
}
}
if detectDialogueAct(input) != DialogueActAsk {
return nil
}
// 已由 DOCGOV 状态查询覆盖的认领/送审不重复
if strings.Contains(input, "认领") || strings.Contains(input, "送审") {
return nil
}
markers := []string{
"文档状态", "审核状态", "什么状态", "当前状态", "版本状态",
"血缘", "stale", "过期原因", "完成了吗", "生成完了吗",
}
matched := false
for _, m := range markers {
if strings.Contains(input, m) {
matched = true
break
}
}
if !matched {
return nil
}
slots := map[string]string{SlotQueryTopic: "doc_status"}
if snap != nil && snap.ActiveDocID != "" {
slots[SlotTargetDoc] = snap.ActiveDocID
}
return &CandidateIntent{
Intent: IntentQueryDocStatus,
Confidence: 0.90,
FilledSlots: slots,
Reasoning: "用户查询受控文档或交付物状态/血缘(只读)",
}
}
func matchHelpCapabilities(input string) *CandidateIntent {
helpMarkers := []string{
"能做什么", "可以做什么", "有什么功能", "怎么用", "如何使用",
"帮助", "使用说明", "能力清单", "支持哪些", "你会什么",
}
for _, m := range helpMarkers {
if strings.Contains(input, m) {
return &CandidateIntent{
Intent: IntentHelpCapabilities,
Confidence: 0.94,
FilledSlots: map[string]string{
SlotQueryTopic: "help",
},
Reasoning: "用户询问系统当前可执行能力",
}
}
}
return nil
}
func matchUnsupportedActionCandidate(input string) *CandidateIntent {
unsupportedMarkers := []string{
"下载回复", "下载下来", "复制回复", "保存为私密文档", "共享到项目", "取消共享",
"删除私密文档", "批量导出", "修改正文", "直接改文档正文",
}
for _, m := range unsupportedMarkers {
if strings.Contains(input, m) {
return &CandidateIntent{
Intent: IntentUnsupportedAction,
Confidence: 0.93,
FilledSlots: map[string]string{
SlotQueryTopic: "unsupported",
},
Reasoning: "用户请求的能力当前仅支持显式 UI 操作,不支持自然语言自动执行",
}
}
}
return nil
}
// applyCapabilityHint 若上下文宣告了 AvailableActions,为已识别动作附加证据(不篡改语义)
func applyCapabilityHint(top *CandidateIntent, snap *ContextSnapshot) {
if top == nil || snap == nil || len(snap.AvailableActions) == 0 {
return
}
spec, ok := DefaultRegistry().GetByLegacyIntent(top.Intent)
if !ok || spec.CapabilityKey == "" {
return
}
capKey := spec.CapabilityKey
if top.Intent == IntentTriggerWorkflow {
if wf := top.FilledSlots[SlotTargetWorkflow]; wf != "" {
capKey = "TRIGGER_WORKFLOW:" + wf
}
}
for _, a := range snap.AvailableActions {
if a == capKey || strings.HasPrefix(a, capKey) {
return
}
}
top.Reasoning += fmt.Sprintf(";注意:当前上下文 AvailableActions 未包含 %s(L2 将做权限/能力裁决)", capKey)
}