Files
Agent-System-Backend/internal/intentgate/gate.go
T
weichen f1302ed5b3 Enable canonical versioned Go module consumption
Why: Allow consumers to download an immutable module revision without nested source checkouts or local path replacements.

Evidence: SELF package tests and the prior canonical-path rejection are recorded in the Dev dependency-workspace task card; remote consumption is replayed against the committed candidate.

Impact: Change the module identity and its Go imports while preserving API behavior. Existing historical submodule revisions remain buildable.

Refs: Versioned dependencies U1/U3; feature/dependency-workspace
2026-10-13 15:47:16 +00:00

192 lines
5.5 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 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
}