// Package intentgate 接入 agent-intent(生态仓):插件意图 overlay → 注册表 → // HITL 自由输入路由的确定性识别(设计 4.5 三分支之「命中其他意图→转轨」)。 // 未配置/未装载意图 = 行为回退(T0:直接按澄清回填),识别失败不阻断。 package intentgate import ( "context" "encoding/json" "fmt" "sync" agentintent "git-ssh.u1680832.nyat.app/weichen/Agent-Intent.git/intent" ) // SlotQuestion 是命中意图后仍缺的必填槽位,以及建议的提问。 type SlotQuestion struct { Key string Question string } // MatchResult 是一次识别。Branch 与 Match 相同。 type MatchResult struct { Branch string ID string Slots map[string]string Missing []SlotQuestion // Commands 是库层展开的命令列表(I1);Router 消费它做唯一裁决。 // guard_hit/expectation_hit 被折叠为 fallback 时为空。 Commands []Command } // 命令类型经闸门投影(别名):生态库按边界规则只许本包导入, // Router/runmgr 等消费方只依赖本包表面。 type ( Command = agentintent.Command ActionRef = agentintent.ActionRef ) const ( CommandStart = agentintent.CommandStart ActionAuto = agentintent.ActionAuto ActionCommand = agentintent.ActionCommand ActionWorkflow = agentintent.ActionWorkflow ActionBlueprint = agentintent.ActionBlueprint ActionHandler = agentintent.ActionHandler ) type Gate struct { mu sync.RWMutex reg *agentintent.Registry rec *agentintent.FunnelRecognizer specs int specsByID map[string]agentintent.IntentSpec // 慢通道(I2):nil = 不启用(默认,行为与 I1 一致)。 sem agentintent.SemanticScorer semThresh float64 gen agentintent.CommandGenerator } func New() *Gate { return &Gate{rec: agentintent.NewRecognizer(agentintent.Options{}, nil, nil)} } // SetSemantic 接线语义打分器(慢通道中位)。识别器随之重建; // 传 nil 关闭语义层。阈值 ≤0 用库默认(0.8)。 func (g *Gate) SetSemantic(s agentintent.SemanticScorer, threshold float64) { g.mu.Lock() defer g.mu.Unlock() g.sem, g.semThresh = s, threshold g.rebuildRecognizerLocked() } // SetGenerator 接线模型命令生成器(慢通道末位)。识别器随之重建; // 传 nil 关闭生成层。 func (g *Gate) SetGenerator(gen agentintent.CommandGenerator) { g.mu.Lock() defer g.mu.Unlock() g.gen = gen g.rebuildRecognizerLocked() } // rebuildRecognizerLocked 按当前慢通道配置重建识别器(调用方持写锁)。 func (g *Gate) rebuildRecognizerLocked() { if g.sem == nil && g.gen == nil { g.rec = agentintent.NewRecognizer(agentintent.Options{}, nil, nil) return } g.rec = agentintent.NewRecognizer(agentintent.Options{ Semantic: g.sem, SemanticThreshold: g.semThresh, Generator: g.gen, }, nil, nil) } // Rebuild 用插件意图 overlay 重建注册表;失败保留上一快照(fail-closed)并返回错误。 func (g *Gate) Rebuild(ctx context.Context, overlay []json.RawMessage) error { if len(overlay) == 0 { g.mu.Lock() g.reg, g.specs, g.specsByID = nil, 0, nil g.mu.Unlock() return nil } specs := make([]agentintent.IntentSpec, 0, len(overlay)) for i, raw := range overlay { var s agentintent.IntentSpec if err := json.Unmarshal(raw, &s); err != nil { return fmt.Errorf("intentgate: spec[%d]: %w", i, err) } specs = append(specs, s) } b := agentintent.NewRegistryBuilder(agentintent.Options{TopN: 3, NFKC: true}) if err := b.Register(specs...); err != nil { return fmt.Errorf("intentgate: register: %w", err) } reg, err := b.Build() if err != nil { return fmt.Errorf("intentgate: build: %w", err) } byID := make(map[string]agentintent.IntentSpec, len(specs)) for _, s := range specs { byID[s.ID] = s } g.mu.Lock() g.reg, g.specs, g.specsByID = reg, len(specs), byID g.mu.Unlock() return nil } // Match 识别自由输入;返回 (branch, topID)。 // branch:matched | ambiguous | fallback | ""(未配置/系统级错误 → 回退通道)。 func (g *Gate) Match(ctx context.Context, input string) (string, string) { res := g.Recognize(ctx, input) return res.Branch, res.ID } // Recognize 在 Match 的结果上带上槽位和缺失提问。 func (g *Gate) Recognize(ctx context.Context, input string) MatchResult { g.mu.RLock() reg, rec, byID := g.reg, g.rec, g.specsByID g.mu.RUnlock() if reg == nil { return MatchResult{} } res, err := rec.Recognize(ctx, input, reg.Snapshot()) if err != nil { return MatchResult{} } out := MatchResult{Branch: "fallback"} switch res.Branch { case agentintent.BranchMatched: out.Branch = "matched" out.Commands = res.Commands case agentintent.BranchAmbiguous: out.Branch = "ambiguous" } if res.Top == nil { return out } out.ID = res.Top.ID if len(res.Top.Slots) > 0 { out.Slots = map[string]string{} for k, v := range res.Top.Slots { out.Slots[k] = v } } spec := byID[out.ID] for _, key := range res.Top.MissingRequiredSlots { q := key for _, sl := range spec.Slots { if sl.Key == key && sl.Question != "" { q = sl.Question } } out.Missing = append(out.Missing, SlotQuestion{Key: key, Question: q}) } return out } // Enabled 报告是否装载了意图(诊断/设置展示用)。 func (g *Gate) Enabled() bool { g.mu.RLock() defer g.mu.RUnlock() return g.reg != nil && g.specs > 0 } // SpecCount 返回已装载意图数。 func (g *Gate) SpecCount() int { g.mu.RLock() defer g.mu.RUnlock() return g.specs }