跳转到内容

Skills 系统三触发模式

Skills 系统三触发模式

学习目标

本章聚焦 LibreChat 的 Skills 系统。你将了解:

  • 三种触发模式(手动、模型、always-apply)的工作原理与优先级
  • 技能目录的预算感知注入策略
  • ACL 权限与用户级活跃状态管理
  • 同名技能碰撞处理

前置知识

下文假设你已理解上述概念,直接聚焦 LibreChat 的具体实现。


项目实践

三种触发模式

LibreChat 的 Skills 系统支持三种触发模式,每种有不同的激活时机:

模式触发方式注入位置适用场景
手动调用用户通过 $skill-name popover 选择紧贴用户消息之前用户明确需要某技能
模型调用模型通过 skill tool 自主选择模型工具调用轮次模型判断需要某技能
always-applyfrontmatter 标记 alwaysApply: true比手动调用更远的位置持续生效的领域知识

注入优先级:always-apply 先注入(更远),手动调用后注入(更近)。LLM 倾向于关注最近的上下文,所以用户明确选择的技能获得更高的注意力权重。

技能目录注入

injectSkillCatalog 函数负责将技能列表格式化为 SKILL.md 目录,注入到 Agent 的 additional_instructions 中:

1. 分页查询 accessibleSkills(每页 100 条,最多 10 页)
2. 过滤活跃技能(resolveSkillActive)
3. 格式化目录(formatSkillCatalog),控制上下文预算
4. 注入到 additional_instructions
5. 注册 skill tool 定义

上下文预算控制

  • SKILL_CATALOG_LIMIT = 100:最多注入 100 个技能
  • MAX_CATALOG_PAGES = 10:最多扫描 10 页
  • formatSkillCatalog 根据 contextWindowTokens 自动截缩描述

ACL 权限与活跃状态

技能访问控制通过 resolveAgentScopedSkillIds 实现三层过滤:

1. 管理员能力开关(skillsCapabilityEnabled)
└─ false → 所有 Agent 无权使用技能
2. Agent 级开关(skills_enabled + skills 列表)
├─ 临时 Agent → 由对话级 toggle 控制
├─ 持久 Agent → 由 builder 的 skills_enabled 控制
└─ skills = [] → 完整目录回退
3. 用户级活跃状态(resolveSkillActive)
├─ skillStates 显式覆盖 → 最高优先级
├─ 所有者技能 → 默认活跃
└─ 共享技能 → 由 defaultActiveOnShare 控制

同名技能碰撞处理

当同一租户内不同作者创建了同名的技能时:

  • getSkillByNameupdatedAt 降序返回最新的一个
  • injectSkillCatalogdisableModelInvocation: true 的技能进行去重:如果存在同名的可调用技能,丢弃不可调用的版本
  • 对目录中可见的重复名技能发出 warn 日志

disableModelInvocationuserInvocable

每个技能有两个独立控制字段:

字段含义影响路径
disableModelInvocation: true模型不能通过 skill tool 调用不影响手动调用
userInvocable: false用户不能通过 $ popover 调用不影响模型调用

这使得技能作者可以精细控制:“这个技能只能由模型自动使用,用户不需要看到”或”这个技能只允许用户手动触发”。

手动调用的粘性跨 Turn 保持

手动选择的技能通过 buildSkillPrimeContentParts 构造 tool_call 格式的 content parts,写入响应消息。这导致 extractInvokedSkillsFromPayload 在后续 turn 中将其识别为”已调用的技能”,从而自动重新注入——技能在当前对话中持续生效,直到用户开始新对话或编辑原始消息。


问题与规避

问题 1:技能目录为空但仍然注册了 skill tool

规避catalogVisibleSkills.length > 0 条件控制——当所有技能都是 disableModelInvocation: true 时,不注册 skill tool(避免浪费 prompt 空间)。但 read_filebash_tool 仍然注册,因为手动调用的技能可能需要读取文件。

问题 2:手动调用和 always-apply 重复注入同一技能

规避injectSkillPrimes 执行交叉列表去重——如果同一技能名同时出现在手动和 always-apply 列表中,丢弃 always-apply 版本(manual 优先级更高)。去重发生在 cap 限制之前,确保 cap 反映真实数量。

问题 3:技能数量过多导致上下文溢出

规避:三级防御:

  1. SKILL_CATALOG_LIMIT = 100 硬性上限
  2. formatSkillCatalog 根据 contextWindowTokens 截缩描述
  3. MAX_PRIMED_SKILLS_PER_TURN = 30 限制每 turn 实际注入的技能体数量

设计取舍

三触发模式 vs 单一触发

三触发模式增加了系统复杂度,但提供了灵活性:

场景单一触发的问题三触发的优势
临时需要模型可能遗漏用户手动触发,确定性高
持续领域知识每个 turn 都要模型判断always-apply 自动注入,零延迟
模型自主判断用户不知道有哪些可用技能目录让模型自行选择

LibreChat 的选择适合其多用户、多 Agent 场景:不同用户、不同对话需要不同的技能组合。


参考来源