package intent import ( "regexp" "strings" "sync" ) // SlotValidator 宿主注册的槽位值校验器。必须纯函数、确定性、无 I/O。 type SlotValidator func(value string) error var slotValidators sync.Map // name → SlotValidator // RegisterSlotValidator 注册进程级具名校验器;spec 通过 SlotSpec.Validator 引用。 // 必须在快照构建前注册(构建期会校验名字存在)。 // // Deprecated: 校验器应随 Registry 隔离。请改用 Options.SlotValidators。 // 全局表只作回落:构建与校验先查作用域表,查不到再查这里。 func RegisterSlotValidator(name string, fn SlotValidator) { slotValidators.Store(name, fn) } // LookupSlotValidator 按名查找已注册的全局校验器。 // // Deprecated: 请改用 Options.SlotValidators。本函数只服务全局回落。 func LookupSlotValidator(name string) (SlotValidator, bool) { fn, ok := slotValidators.Load(name) if !ok { return nil, false } v, ok := fn.(SlotValidator) return v, ok } // resolveSlotValidator 先查作用域表,查不到再回落已弃用的全局表。 func resolveSlotValidator(scoped map[string]SlotValidator, name string) (SlotValidator, bool) { if fn, ok := scoped[name]; ok && fn != nil { return fn, true } return LookupSlotValidator(name) } // 内置归一化步骤:trim / lower / upper。 var builtinNormalizers = map[string]func(string) string{ "trim": strings.TrimSpace, "lower": strings.ToLower, "upper": strings.ToUpper, } func normalizeSlotValue(v string, ops []string) string { for _, op := range ops { if fn, ok := builtinNormalizers[op]; ok { v = fn(v) } } return v } // compiledSlot 编译后的槽位抽取与校验产物。 type compiledSlot struct { key string required bool label string question string re *regexp.Regexp group int enumSet map[string]bool enum []string formatRe *regexp.Regexp normalize []string validator SlotValidator } // validate 对归一化后的值做确定性校验;返回(原因, 是否合法)。 func (s *compiledSlot) validate(v string) (string, bool) { if s.formatRe != nil && !s.formatRe.MatchString(v) { return "format mismatch: " + s.formatRe.String(), false } if s.enumSet != nil && !s.enumSet[v] { return "value not in enum", false } if s.validator != nil { if err := s.validator(v); err != nil { return "validator: " + err.Error(), false } } return "", true } // slotCapture 一次槽位抽取的捕获区间。多槽位各自第一次匹配;区间重叠时 // 两边都标 span overlap,不写入 slots。库内不做重叠消解。 type slotCapture struct { key string start int end int value string ok bool reason string required bool matched bool } // extractSlots 抽取并归一化槽位。 // missing:必填槽位未捕获(模式没匹配上);invalid:捕获到但校验不合法,或与另一槽位区间重叠(key→原因)。 // 必填且非法只进 invalid(宿主可区分"没给"与"给错了"来决定澄清话术)。 func (cs *compiledSpec) extractSlots(input string) (slots map[string]string, missing []string, invalid map[string]string) { if len(cs.slots) == 0 { return nil, nil, nil } caps := make([]slotCapture, 0, len(cs.slots)) for _, s := range cs.slots { loc := s.re.FindStringSubmatchIndex(input) g := s.group if loc == nil || 2*g+1 >= len(loc) || loc[2*g] < 0 || loc[2*g] == loc[2*g+1] { caps = append(caps, slotCapture{key: s.key, required: s.required}) continue } raw := input[loc[2*g]:loc[2*g+1]] v := normalizeSlotValue(raw, s.normalize) reason, ok := s.validate(v) caps = append(caps, slotCapture{ key: s.key, start: loc[2*g], end: loc[2*g+1], value: v, ok: ok, reason: reason, required: s.required, matched: true, }) } overlap := map[int]bool{} for i := range caps { if !caps[i].matched { continue } for j := i + 1; j < len(caps); j++ { if !caps[j].matched { continue } if caps[i].start < caps[j].end && caps[j].start < caps[i].end { overlap[i] = true overlap[j] = true } } } for i, c := range caps { if !c.matched { if c.required { missing = append(missing, c.key) } continue } if overlap[i] { if invalid == nil { invalid = map[string]string{} } invalid[c.key] = "span overlap" continue } if !c.ok { if invalid == nil { invalid = map[string]string{} } invalid[c.key] = c.reason continue } if slots == nil { slots = map[string]string{} } slots[c.key] = c.value } return slots, missing, invalid } func compileSlot(spec SlotSpec, scoped map[string]SlotValidator) (compiledSlot, error) { re, err := regexp.Compile(spec.Pattern) if err != nil { return compiledSlot{}, newError(InvalidSpec, "slot pattern 编译失败", "field", "slots", "key", spec.Key, "error", err.Error()) } cs := compiledSlot{ key: spec.Key, required: spec.Required, label: spec.Label, question: spec.Question, re: re, group: effectiveGroup(spec.Group), normalize: append([]string(nil), spec.Normalize...), } if len(spec.Enum) > 0 { cs.enum = append([]string(nil), spec.Enum...) cs.enumSet = make(map[string]bool, len(spec.Enum)) for _, e := range spec.Enum { cs.enumSet[e] = true } } if spec.Format != "" { fre, err := regexp.Compile(spec.Format) if err != nil { return compiledSlot{}, newError(InvalidSpec, "slot format 编译失败", "field", "slots", "key", spec.Key, "format", spec.Format, "error", err.Error()) } cs.formatRe = fre } if spec.Validator != "" { fn, ok := resolveSlotValidator(scoped, spec.Validator) if !ok { return compiledSlot{}, newError(InvalidSpec, "slot validator 未注册", "field", "slots", "key", spec.Key, "validator", spec.Validator, "hint", "在 Options.SlotValidators 登记,或调用已弃用的 intent.RegisterSlotValidator") } cs.validator = fn } return cs, nil }