首页/Agent 能力/Skills

Skills

Skills 是组织的方法论:怎么规划一个查询、怎么诊断一次失败、某个口径在本组织里到底怎么算。 它与知识库的分界很清楚——知识库装某个数据源的事实, Skills 装跨数据源通用做法。

放哪:Skills 还是 KB?

这是使用中最常被问的问题,判据只有一条:这条知识换一个数据源还成立吗?

内容放哪理由
「loan.amount 单位是元」 KB 这是某张表的字段事实
「比率类指标的分母要排除已核销」 KB(rules.yml)或 skills 取决于它是这个库的口径还是全组织的口径
「遇到多跳问题先确定事实表再补维度表」 Skills 换任何库都成立的方法论
「执行报字段不存在时先查 schema 再改,不要猜列名」 Skills 诊断方法,与具体表无关

代码里自带的两个 code skill 正好是后两类的例子:plan_query(怎么规划)、 diagnose_failure(怎么诊断失败)。 它们放在 trove/prompts/skills/ 下,随代码走,是事实上的必需品。

两个来源,合并渲染

来源位置谁能改
code skills trove/prompts/skills/ 改代码(只读资产,事实上的 required)
org skills .trove/skills/<name>/SKILL.md 管理端,走确认门禁

service.py:168 SkillService service.py:302 list_code_skills service.py:275 list_org

四档:注入、按需,以及两种不注入只断言

每个 skill 声明自己的档位。这个选择直接影响生成提示的长度, 而提示长度直接影响成本与注意力分配——所以不该「全都塞进去」。 前两档是给模型读的,后两档不是——validator 判结果、guard 判 SQL, 都由引擎执行、零 LLM。

tier进什么什么时候用
required 正文全文注入到匹配节点的系统提示里 每次都该遵守的硬约束(如「不许猜列名」)
available 只把描述作为 <available_skills> 块广告出去,正文按需取 场景性的方法论,多数提问用不上
validator 正文从不进模型——它是一份给引擎执行的断言清单,跑在结果上 组织自己不可让步的口径(如「金额不许为负」),要拦在交付前
guard 正文从不进模型——同样是给引擎执行的断言清单,但跑在生成的 SQL 上 组织不可让步的写法底线(如「聚合查询必须带 WHERE」),要拦在执行前——执行与交付都还没发生

service.py:96 _TIERS

flowchart TB
    S["SKILL.md 声明 tier"]:::entry --> R["required
正文全文注入匹配节点的系统提示"]:::gen S --> A["available
只广告描述,正文经 load_skill 工具按需取"]:::gen S --> V["validator
正文从不进模型,是给引擎执行的断言清单"]:::gate S --> G["guard
正文从不进模型,是给引擎执行的 SQL 断言清单"]:::gate R --> P["模型读 → 生成 SQL"]:::gen A --> P P --> E["execute_sql 节点
guard 在授权门之后、执行之前判定"]:::exec G --> E E --> E1["通过 → 执行"]:::entry E --> E2["违反 · blocking → 回滚重算(目标可收窄)"]:::err E --> E3["违反 · advisory → 只进 guard_hits 附注"]:::entry E --> E4["判不了(SQL 解析失败)→ 绝不拦"]:::entry E1 --> X["validate 节点
零 LLM,逐条判定结果"]:::exec V --> X X --> X1["通过 → 交付"]:::entry X --> X2["违反 · blocking → 回滚重算"]:::err X --> X3["违反 · advisory → 回答顶上一行提示"]:::entry X --> X4["判不了 → 只进 validator_hits,不上屏"]:::entry classDef entry fill:#71717a14,stroke:#71717a59,stroke-width:1px; classDef gen fill:#6366f114,stroke:#6366f199,stroke-width:1.5px; classDef exec fill:#14b8a614,stroke:#14b8a699,stroke-width:1.5px; classDef gate fill:#f59e0b14,stroke:#f59e0b99,stroke-width:1.5px; classDef err fill:#dc262614,stroke:#dc262699,stroke-width:1.5px;
图 1 · 前两档是给模型读的(全文注入 / 按需取正文),后两档不是:validator 在结果上断言、拦在交付前;guard 在 SQL 上断言、拦在执行前。两道断言闸先后相接,把同一条答案夹在中间。 上面这段以源码显示:图渲染库没加载起来(离线、CDN 被拦或浏览器禁用了 JavaScript 时会这样)。这段 Mermaid 文本本身就说明了上面那些框与连线。

required 级的 skill 不是全局生效的——它可以声明 triggers.node 来限定只注入到某几个节点(查询计划、失败分析、agentic 生成,以及展示环的 洞察 / 图表 / 结论 / 归因)。 没写 triggers.node 的才是全局。这个粒度是有必要的: 「怎么诊断失败」这条方法论对规划节点毫无用处,注进去只会稀释它的注意力。

按需加载怎么工作

available 级的 skill 靠一个工具取正文。这个工具只在真的有 confirmed 的 available skill 时才注册——没有的时候,模型看到的工具列表里根本没有它。

gen_sql.py:1185 load_skill_tool gen_sql.py:1051 build_sql_registry

「零工具噪音」是个设计目标

一个用不到的工具不是中性的——它会占提示空间,还会给模型多一个犹豫的选项。所以这套系统里工具的注册普遍是条件的:按复杂度档位、按角色、按「有没有内容可返回」。

这和工具治理是同一个思路的两面。

validator 怎么运行

validator 档是一份声明,不是一段提示词:文件里写清要检查什么 (checks,逐条带 expr 与可选的 columns / severity), 由 validate 节点在规则链那一遍之后执行,零 LLM, 复用决策规则的同一个表达式引擎。可用的量就是结果自身的聚合:min / max / sum / avg / null_count / row_count / col_count。

每条断言给三值判定之一,落点各不相同:

判定什么情况落点
通过 断言为真 交付
违反 · blocking 明确违反,且声明为拦 判词带进 error_feedback 回滚重算,同时作为一条拦截进 validation_hits;只有预算耗尽时才升级成交付给用户的错误
违反 · advisory 明确违反,但声明只报告 不拦,渲染成回答顶上的一行「⚠️ 口径提示」
判不了 缺列、没声明 columns、结果超出展示窗口、结果本来就为空、列里没有数值、配置写错…… 只进 validator_hits 供质检统计,永远不进用户屏幕

第三值单独占一格,不塌向任何一边——塌成「通过」是没查却报平安, 塌成「违反」是查不了却拦下正确结果,两种都比不检查更坏。它带一个机器可读的原因码, 因为「配了却判不了」的占比是这套机制唯一的质量信号:一句话盖住所有原因, 就再也数不出分布。

判不了不进附注的理由是噪声:一个高频出现、又多半只说明管理员该改配置的提示, 两周内就会把真正有意义的告警一起淹掉。

为什么另起一遍,而不是规则链的第 N 条规则

规则链(verify)是第一条失败即止、只回一个 reason,那是给「形状」用的——单值问题的结果必须一行一列。validator 要的是逐条判定 + 逐条 severity,一个 reason 装不下,所以它是紧挨着 verify() 的另一遍,不是它的扩展。

写入面与执行面共用同一份 severity 闭集。severity: Blocking(大小写错了)既未被拦、也不会被渲染——写入放行、执行侧当未知,正是这类「配了却静默失效」事故的来源,所以两处必须看同一份名单。

guard 怎么运行

第四档 guard 与 validator 是一对镜像:同样是正文从不进模型的 声明清单、同样零 LLM、同样三值判定与两档 severity。区别只在判什么、在哪判—— validator 看结果、在 validate 节点拦在交付前; guard 看 SQL、在 execute_sql 节点拦在执行前。 宿主位置是写死的(GUARD_HOST = "execute_sql"),闸序是「授权门之后、 执行之前」——安全先于质量:一条没通过授权门的 SQL,轮不到守卫发表意见。

guard 能用的量是一组 SQL 结构变量的闭集,全部是 int 的 0/1: select_star / table_count / join_count / has_where / has_limit / has_time_predicate / has_aggregate / cte_count / sql_len。 布尔语义用 0/1 而不是 true/false 是刻意的:引擎的取值函数对布尔类型返回 None——写成 true 的守卫不会报错,只会每条都判不了、永不生效。 这正是本页反复出现的「配了却静默失效」事故类型,所以从类型上就把域收死; 写入口(create / 导入)对域外标识符还有一道位置级校验,直接 400 指到出错的位置。

判定的落点沿用与 validator 相同的三值规矩:

判定什么情况落点
通过 断言为真 继续执行
违反 · blocking 明确违反,且声明为拦 记为 ORG_GUARD 类错误回滚重算;失败分析节点对它走零 LLM 的确定性分流——指出是哪条守卫、哪个变量越了线,不把它当普通报错猜。回滚目标在声明里可收窄到 gen_sql / query_sketch / schema_linking(默认回到生成)
违反 · advisory 明确违反,但声明只报告 命中记进 guard_hits 附注,不改执行
判不了 SQL 解析失败(此时全部变量为 UNKNOWN) 绝不拦——守卫的失败方向是「放过」,而不是误杀一条正确的 SQL

guards.py:45 GUARD_HOST guards.py:55 GUARD_VARIABLES execute_sql.py:62 ORG_GUARD_TAG analyze_error.py:496 ORG_GUARD_TAG

确认门禁:草案不进提示

org skill 的写入走的是和 KB 教训/示例同一套门禁: 新建即 pending,待确认的草案既不会进提示、也不会出现在 load_skill 工具的可取范围里——等于不存在。

这条一致性很重要:Skills 直接写进系统提示,影响力比 KB 条目更大。 如果草案能进提示,那「AI 自己写方法论给自己看」就成了一个自我强化的回路。

管理端方法论技能页:左侧列表三列(名称 / 描述 / 生效节点),四条内置技能分别绑定 query_sketch、analyze_error、gen_sql 或标「(全局)」;右侧预览抽屉打开着 answer-structure-zh 的正文原文 1 2 3 4
图 2 · 管理端方法论技能页:列表 + 预览抽屉——确认门禁另一头的样子(待确认的草案不进提示、也不进 load_skill 的可取范围,所以这一屏是「已经生效」的资产)。
  1. 1列表三列:名称 / 描述 / 生效节点——「内置」徽标标的是来源(代码技能,随仓库走);组织技能排在同一张表里,各自带状态徽标(pending / confirmed / rejected / disabled)与相应动作。
  2. 2生效节点——技能绑定到管线节点:plan_query → query_sketch、diagnose_failure → analyze_error、sql_construction → gen_sql。required 档注入对应节点的系统提示,available 档只被广告、由 load_skill 按需取正文。
  3. 3「(全局)」——没有 triggers.node 的技能对所有节点生效(图中 answer-structure-zh 就是一条中文回答结构约定,作用于整条生成链)。
  4. 4预览抽屉——打开看到的是正文原文:模型读到的就是这段文字,改一句话就是改一次行为;正文的每次修改自动 git 提交,可回滚。

颗粒停用:停一个资产,不是停一层

治理动作有两个粒度:整层总开关停的是全部 org 资产 (见 深潜 · 扩展与治理); 颗粒停用停的是某一条已确认的资产——status: disabled 是继 pending / confirmed / rejected 之后的第四个状态。不删内容、不退成草案, 只是让它从消费面上消失。

状态机是严格且非幂等的:disable 只接受 confirmed → disabled,enable 只接受 disabled → confirmed;对已停用的再停一次是显式错误,不是无声成功 (把状态机做成幂等,「谁什么时候关的、谁又开的」就从审计史里消失了)。 恢复走 enable 而不是再确认一次——停用是暂停投递,不是撤回确认。 每次变更都是一条 git 提交;自动路径(起草、学习、记忆回写)绝不写入 disabled, 停用永远是管理员的显式动作。

停用后四个消费面同断:节点提示词注入、load_skill 的广告 与按名直取、validator / guard 的执行、以及信封枚举与试跑语料。 消费者共用同一个过滤点(list_org(confirmed_only=True))—— 停用一个资产等于四处同时熄灭。按名直取也拒绝这一点值得单说: 点名不是提权,已停用的资产即使被工具按名字精确命中也不会返回正文, 判词还会分方向(「还没确认」与「已被管理员停用」是管理员的两个不同下一步)。

service.py:295 confirmed_only service.py:731 disable skills.py:173 disable

起草:LLM 帮忙,但只到草案为止

管理端可以用 LLM 起草一份 skill。起草用的提示模板按语言分开:

prompts/skills/draft.zh.j2 起草模板 prompts/skills/draft.en.j2 draft template

但起草的产物落在 pending,需要人确认。这个边界和 KB 是同一句话: 模型可以提议,不能决定。

管理端 API

端点作用
GET /v1/admin/skills列出全部(含 code 与 org)
GET /v1/admin/skills/org只列 org skills
POST /v1/admin/skills/draft手工创建草案
POST /v1/admin/skills/llm-draft让 LLM 起草一份草案
GET /v1/admin/skills/{name}/body读正文
POST /v1/admin/skills/{name}/confirm确认——此时才可能进入提示
POST /v1/admin/skills/{name}/reject驳回
POST /v1/admin/skills/{name}/disable颗粒停用一条已确认的资产(status → disabled,写一条 git 提交)
POST /v1/admin/skills/{name}/enable恢复(disabled → confirmed)——暂停投递的逆操作,不走确认门,但它本身必须是显式管理动作
POST /v1/admin/skills/{name}/tier在 required ↔ available 之间搬——validator 与 guard 走不了这个接口

skills.py:84 draft skills.py:102 llm_draft_skill skills.py:131 confirm skills.py:219 tier

前端对应「Skills」页面。修改 tier 是一个值得留意的操作: 把一个 available 的 skill 提成 required,等于给每次生成都加一段固定文本—— 确认它真的每次都用得上再提。

这个接口只在 required 与 available 两档之间搬。 validator 与 guard 两档都不在这里切换—— validator 的 mode / severity / targets / checks 四个字段、guard 的 guard 块(表达式 + severity + targets), 都必须与 tier 在 SKILL.md 里一起手写:升档时这些字段还没被投影到条目上, 降档时又已经在条目上,两个方向都无法在这条路径上校验,所以两个方向都直接拒。 真相是 tier 与那些投影字段只能同一次写入。 校验本体见 service.py:758 set_tier 与 service.py:396 _validate_guard_spec。

继续读

想了解去哪
工具治理与 agent 循环Agent 能力总览
KB 的两档门禁(同一套机制)知识库
四个资产面共用的治理形状(pending→确认→版本化→回滚)深潜 · 扩展与治理
上下文预算怎么分配给各信息块查询工作流 · 上下文预算
模型与 provider 配置LLM 网关
本页源码锚点的基准提交:anchor-base: 7c6df87 GitHub 仓库 文档首页