package intent import ( "regexp" "strings" "unicode/utf8" ) // isSecurityViolation 检查是否命中敏感安全与权限红线。 // 中文无词边界:动词仅用复合形(改成/改为/换成/重置/设为/修改等),桥接限制在不跨子句标点的短窗; // 受控资产名词(系统配置/知识库连接等)须与写动词共现,避免正常撰写/排查误拦。 func isSecurityViolation(input string) bool { lower := strings.ToLower(input) patterns := []string{ "修改身份", "身份修改", "修改职位", "职位修改", "修改岗位", "岗位修改", "修改姓名", "姓名修改", "修改名字", "名字修改", "修改部门", "部门修改", "修改资质", "资质修改", "改成批准人", "改成审核人", "改成编制人", "工号改成", "修改工号", "工号修改", "修改设置", "修改配置", "修改系统设置", "修改系统配置", "修改网关", "修改网关配置", "修改网关密钥", "修改大模型网关", "修改模型网关", "修改密码", "重置密码", "提升权限", "越权", "修改向量库", "修改向量库连接", "修改向量库配置", "修改知识库连接", "修改知识库配置", } for _, p := range patterns { if strings.Contains(lower, p) { return true } } // 桥接窗:不跨子句标点(含斜杠),避免「整改/角色」类远距误配。 const bridge = `[一-鿿A-Za-z0-9_、  ]{0,16}` secRegexes := []string{ `把` + bridge + `(?:身份|职位|角色|工号|权限|姓名|名字|部门|资质)` + bridge + `(?:修改|改成|改为|换成|换为|重置|设为|变为)`, `把` + bridge + `(?:向量库连接|向量库|知识库连接|大模型网关|模型网关|网关密钥|网关|密钥|apikey|api_key)` + bridge + `(?:修改|改成|改为|换成|换为|重置|设为|变为)`, `(?:修改|改成|改为|换成|换为|更换|重置|设为)` + bridge + `(?:我的)?(?:身份|职位|角色|工号|权限|姓名|名字|部门|资质|网关|向量库|知识库连接|密钥|apikey|api_key)`, `(?:修改|改成|改为|换成|换为|更换|重置|设为)` + bridge + `(?:向量库连接|向量库|知识库连接|大模型网关密钥|网关密钥)`, } for _, r := range secRegexes { if matched, _ := regexp.MatchString(r, input); matched { return true } } return false } // isPureUICommand 检查是否为纯前端UI视窗布局与主题控制指令 func isPureUICommand(input string) bool { patterns := []string{ "打开工具栏", "关闭工具栏", "折叠侧边栏", "展开侧边栏", "隐藏侧边栏", "显示侧边栏", "折叠左侧栏", "展开左侧栏", "折叠右侧栏", "展开右侧栏", "折叠工作台", "展开工作台", "切换全屏", "全屏显示", "退出全屏", "打开控制台", "关闭控制台", "切换主题", "换成深色", "换成浅色", "打开抽屉", "展开抽屉", "关闭抽屉", "深色模式", "浅色模式", "深色主题", "浅色主题", } for _, p := range patterns { if strings.Contains(input, p) { return true } } return false } // resolveTargetDoc 优先从输入中提取显式受控文档名称,提取不到时回退至上下文的 ActiveDocID func resolveTargetDoc(input string, userCtx *ContextSnapshot) string { if doc := extractDocName(input); doc != "" { return doc } if userCtx != nil && userCtx.ActiveDocID != "" { return userCtx.ActiveDocID } return "" } // isRejectReasonQuery 判定是否为询问驳回/退回原因(而非发起驳回执行) func isRejectReasonQuery(input string) bool { markers := []string{ "原因", "为何", "为什么", "怎么回事", "什么情况", "什么意见", "哪点", "哪些问题", "整改", "意见是什么", "理由", "怎么改", "如何修改", "查一下驳回", "看看驳回", } for _, m := range markers { if strings.Contains(input, m) { return true } } return strings.Contains(input, "被驳回") } // extractFactSlots 从文本中提取结构化事实槽位 (支持补丁合并与补充信息) func extractFactSlots(input string) map[string]string { slots := make(map[string]string) marketPrefixes := []string{ "目标市场不仅是", "目标市场不仅有", "目标市场还包括", "目标市场确定为", "目标市场确定是", "目标市场改成", "目标市场改为", "目标市场是", "目标市场为", "市场改成", "市场改为", "市场是", "市场为", "目标市场除了国内还想考虑", } for _, p := range marketPrefixes { if idx := strings.Index(input, p); idx != -1 { val := strings.TrimSpace(input[idx+len(p):]) val = strings.Trim(val, "::,,。 ") if val != "" { slots["target_market"] = val slots[SlotFactKey] = "target_market" slots[SlotFactValue] = val break } } } budgetPrefixes := []string{"预算改成", "预算改为", "预算是", "预算为", "预算只有", "预算大概", "预算"} for _, p := range budgetPrefixes { if idx := strings.Index(input, p); idx != -1 && slots["budget"] == "" { val := strings.TrimSpace(input[idx+len(p):]) val = strings.Trim(val, "::,,。 ") if val != "" { slots["budget"] = val slots[SlotFactKey] = "budget" slots[SlotFactValue] = val break } } } usePrefixes := []string{"用途是", "用途为", "预期用途是", "场景是", "场景为"} for _, p := range usePrefixes { if idx := strings.Index(input, p); idx != -1 && slots["intended_use"] == "" { val := strings.TrimSpace(input[idx+len(p):]) val = strings.Trim(val, "::,,。 ") if val != "" { slots["intended_use"] = val slots[SlotFactKey] = "intended_use" slots[SlotFactValue] = val break } } } return slots } func isGreetingOrHelp(input string) bool { greetings := []string{"你好", "您好", "早上好", "下午好", "晚上好", "早啊", "在吗", "hi", "hello", "你是谁"} for _, g := range greetings { if strings.Contains(strings.ToLower(input), g) { return true } } return false } func sortCandidates(c []CandidateIntent) { for i := 0; i < len(c)-1; i++ { for j := i + 1; j < len(c); j++ { if c[j].Confidence > c[i].Confidence { c[i], c[j] = c[j], c[i] } } } } // IsSmallTalkOrGeneralQuestion 判定是否为通用非业务问句(闲聊/常识提问)。 func IsSmallTalkOrGeneralQuestion(input string) bool { trimmed := strings.TrimSpace(input) if trimmed == "" || utf8.RuneCountInString(trimmed) < 3 { return false } if hasStrongBusinessLexicon(trimmed) { return false } questionMarkers := []string{ "怎么样", "好吗", "什么是", "为什么", "怎么", "哪些", "多少", "吗", "呢", "?", "?", } for _, m := range questionMarkers { if strings.Contains(trimmed, m) { return true } } return false } // HasStrongBusinessLexicon 是否含强业务词典(项目/法规/文档治理等),供闲聊与实时检索判定复用。 func HasStrongBusinessLexicon(input string) bool { return hasStrongBusinessLexicon(input) } func hasStrongBusinessLexicon(input string) bool { markers := []string{ "项目", "阶段", "推进", "法规", "注册", "文档", "审核", "批准", "驳回", "报价", "需求", "型检", "成熟度", "工作流", "交付", "立项", "送审", "认领", "方案成型", "V0.1", "V0.5", "V1.0", "v0.1", "v0.5", "v1.0", "进度", "风险", "预算", "目标市场", "受控", } for _, m := range markers { if strings.Contains(input, m) { return true } } return false }