Details (original message): Add catalog-driven orchestration: intent catalog injection, deterministic plan validation, and system-function steps.
152 lines
5.3 KiB
Go
152 lines
5.3 KiB
Go
package squad
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"agent-native/exec"
|
|
)
|
|
|
|
// IntentRejection 守卫拦截:拒绝该次运行并给出引导("识别即拒绝"语义)。
|
|
type IntentRejection struct {
|
|
// Reason 稳定拒因(进入运行结果与事件文本)。
|
|
Reason string
|
|
// Guidance 给用户的引导文案(进入运行结果错误详情)。
|
|
Guidance string
|
|
}
|
|
|
|
// IntentDecision 意图闸门的分流决策;nil 表示不干预(完全走原路径)。
|
|
type IntentDecision struct {
|
|
// Reject 非空时拒绝该次运行(守卫拦截)。
|
|
Reject *IntentRejection
|
|
// BlueprintKey 非空时定向使用该蓝图模板(等价显式 TaskKey;不存在的蓝图 fail-closed)。
|
|
BlueprintKey string
|
|
// IntentID / Handler / Metadata 透传识别元数据,供运行审计与日志。
|
|
IntentID string
|
|
Handler string
|
|
Metadata map[string]string
|
|
}
|
|
|
|
// IntentGate 可选意图闸门:在 Run 入口对目标文本做一次确定性识别。
|
|
// 入口语义:澄清/确认后的续跑不再过闸;显式 TaskKey 优先于意图定向(引擎保证)。
|
|
// 实现必须无副作用、可并发调用;系统错误以 error 返回(引擎 fail-closed 标记运行失败)。
|
|
// 宿主实现见 squadintent 适配器;本接口不依赖 agent-intent。
|
|
type IntentGate interface {
|
|
Decide(ctx context.Context, goal Goal, opts RunOptions) (*IntentDecision, error)
|
|
}
|
|
|
|
// ---- 能力目录(目录驱动的编排)----
|
|
|
|
// IntentSlotView 目录中的槽位视图:规划者/领导据此知道缺什么信息、建议怎么问。
|
|
type IntentSlotView struct {
|
|
Key string `json:"key"`
|
|
Required bool `json:"required,omitempty"`
|
|
Label string `json:"label,omitempty"`
|
|
Question string `json:"question,omitempty"`
|
|
Enum []string `json:"enum,omitempty"`
|
|
Format string `json:"format,omitempty"`
|
|
}
|
|
|
|
// IntentSpecView 目录中的单条可调用能力。
|
|
type IntentSpecView struct {
|
|
ID string `json:"id"`
|
|
Description string `json:"description,omitempty"`
|
|
Risk string `json:"risk,omitempty"`
|
|
RequiresConfirm bool `json:"requires_confirm,omitempty"`
|
|
Handler string `json:"handler,omitempty"`
|
|
Slots []IntentSlotView `json:"slots,omitempty"`
|
|
RequiredSlots []string `json:"required_slots,omitempty"`
|
|
Metadata map[string]string `json:"metadata,omitempty"`
|
|
}
|
|
|
|
// IntentCatalog 意图能力目录:squadintent 从识别快照渲染的数据视图,
|
|
// squad 只消费不构造(不 import agent-intent)。Markdown 注入规划者/领导提示词;
|
|
// Specs 用于计划的确定性校验(意图存在、必填槽位齐、枚举合法)。
|
|
type IntentCatalog struct {
|
|
Revision int64
|
|
Markdown string
|
|
Specs map[string]IntentSpecView
|
|
}
|
|
|
|
// intentCatalogMd nil 安全的目录文本(未启用时为空串)。
|
|
func (c *IntentCatalog) intentCatalogMd() string {
|
|
if c == nil {
|
|
return ""
|
|
}
|
|
return c.Markdown
|
|
}
|
|
|
|
// Get 按 id 查目录能力。
|
|
func (c *IntentCatalog) Get(id string) (IntentSpecView, bool) {
|
|
if c == nil {
|
|
return IntentSpecView{}, false
|
|
}
|
|
v, ok := c.Specs[id]
|
|
return v, ok
|
|
}
|
|
|
|
// IntentStep 系统功能步:不经 BackendResolver/智能体,由宿主 IntentRunner 直接执行。
|
|
type IntentStep struct {
|
|
IntentID string
|
|
Slots map[string]string
|
|
Title string
|
|
WorkDir string
|
|
Targets []string
|
|
}
|
|
|
|
// IntentRunner 系统功能执行器(squadintent 提供实现:查 spec.Handler 并调宿主处理函数)。
|
|
type IntentRunner interface {
|
|
Run(ctx context.Context, step IntentStep) (*exec.Result, error)
|
|
}
|
|
|
|
// DefaultIntentRole 系统功能步的默认角色 key(无后端语义,仅满足编制覆盖)。
|
|
const DefaultIntentRole = "intent"
|
|
|
|
// DefaultIntentRoleSpec 返回系统功能步的默认角色(组合根在启用意图目录时注册)。
|
|
func DefaultIntentRoleSpec() RoleSpec {
|
|
return RoleSpec{Role: DefaultIntentRole, Name: "系统能力", Backend: "stub", Keywords: []string{"系统功能"}}
|
|
}
|
|
|
|
// validateIntentSteps 计划落盘前的确定性校验:意图步必须命中目录、必填槽位齐、枚举合法。
|
|
// fail-closed:任一不通过即整计划拒绝(与未知 backend 拒绝同构)。
|
|
func validateIntentSteps(plan *SquadPlan, catalog *IntentCatalog) error {
|
|
if plan == nil {
|
|
return nil
|
|
}
|
|
for _, step := range plan.Workflow.Steps {
|
|
if step.IntentID == "" {
|
|
continue
|
|
}
|
|
if catalog == nil {
|
|
return fmt.Errorf("intent 步骤 %s:引擎未装配意图目录(IntentCatalog),无法执行系统功能步", step.ID)
|
|
}
|
|
view, ok := catalog.Get(step.IntentID)
|
|
if !ok {
|
|
return fmt.Errorf("intent 步骤 %s:目录中不存在意图 %s", step.ID, step.IntentID)
|
|
}
|
|
for _, sl := range view.Slots {
|
|
v, given := step.IntentSlots[sl.Key]
|
|
if sl.Required && (!given || v == "") {
|
|
q := sl.Question
|
|
if q == "" {
|
|
q = "请提供 " + sl.Key
|
|
}
|
|
return fmt.Errorf("intent 步骤 %s:意图 %s 缺必填槽位 %s(澄清建议:%s)", step.ID, step.IntentID, sl.Key, q)
|
|
}
|
|
if given && len(sl.Enum) > 0 && !containsString(sl.Enum, v) {
|
|
return fmt.Errorf("intent 步骤 %s:槽位 %s 的值 %q 不在枚举 %v 内", step.ID, sl.Key, v, sl.Enum)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func containsString(list []string, s string) bool {
|
|
for _, v := range list {
|
|
if v == s {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|