5.3 AgentSkillsProvider 技能注入
`AgentSkillsProvider` 实现 RAF 的 Agent 技能系统——它将可复用的技能包(Skills)注入 Agent 的 system prompt 和工具列表,使 Agent 能动态加载和使用外部知识。
AgentSkill 技能结构
/// AgentSkill — 一个技能对象,持有元数据 + 技能目录路径
///
/// 可对接多种来源:本地目录、数据库、远程 API 等。
pub struct AgentSkill {
pub metadata: SkillMetadata,
pub(crate) root_dir: Option<PathBuf>, // 技能根目录(from_dir 时设置)
instructions: Option<String>, // 自定义指令内容(dynamic 时设置)
resources: HashMap<String, Vec<u8>>, // 自定义资源内容表
}
/// 技能元信息(从 SKILL.md frontmatter 解析)
pub struct SkillMetadata {
pub name: String, // 技能名称
pub description: String, // 技能描述
pub license: Option<String>, // 许可协议
pub compatibility: Option<String>, // 兼容性信息
pub metadata: HashMap<String, String>, // 扩展元数据
}
技能加载方式
| 方法 | 说明 | 适用场景 | |------|------|----------| | `AgentSkill::from_dir(path)` | 从磁盘目录加载,解析 SKILL.md frontmatter | 文件系统中的技能包 | | `AgentSkill::dynamic(metadata, instructions)` | 动态创建,指定元数据和指令文本 | 数据库/远程 API 加载的技能 | | `with_resource(path, content)` | 添加内联资源(仅 dynamic 技能) | 不依赖文件系统的资源 |
SKILL.md 文件格式
---
name: code-review
description: Review code for quality, bugs, and best practices.
license: MIT
compatibility: all
metadata:
author: team-qa
version: "1.2"
---
# Code Review Skill
## Instructions
1. Read the code carefully.
2. Check for:
- Logic errors
- Security vulnerabilities
- Performance issues
3. Provide specific line references in feedback.
- `---` 之间的 YAML frontmatter 解析为 `SkillMetadata` - `---` 之后的内容是技能指令,通过 `load_skill` 工具返回给 LLM
标准技能目录结构
skills/
└── code-review/
├── SKILL.md # 技能元数据 + 指令(必须)
├── references/ # 参考文档(可选)
│ └── style-guide.md
├── assets/ # 资源文件(可选)
│ └── template.html
└── scripts/ # 可执行脚本(可选)
└── lint.sh
`has_resources()` 检查 `references/` 和 `assets/` 目录是否存在;`has_scripts()` 检查 `scripts/` 目录是否非空。
AgentSkillsProvider
/// AgentSkillsProvider — IContextProvider 实现
///
/// 注入:
/// 1. advertise 文本(技能名称 + 描述 + 路径指引)
/// 2. load_skill / read_skill_resource / run_skill_script 工具(按需)
pub struct AgentSkillsProvider {
pub skills: Vec<AgentSkill>,
script_runner: Option<Arc<dyn AgentSkillScriptRunner>>,
}
扫描模式
自动发现目录下所有含 `SKILL.md` 的子目录:
/// 批量扫描目录,自动发现所有含 SKILL.md 的子目录。
pub fn scan(root: impl AsRef<Path>) -> Result<Self> {
let mut provider = Self::new();
let root = root.as_ref();
for entry in std::fs::read_dir(root)? {
let path = entry.path();
if path.is_dir() && path.join("SKILL.md").exists() {
let skill = AgentSkill::from_dir(&path)?;
provider = provider.with_skill(skill);
}
}
Ok(provider)
}
/// 从多个目录扫描。
pub fn scan_dirs(roots: &[impl AsRef<Path>]) -> Result<Self> {
let mut all_skills = Vec::new();
for root in roots {
let provider = Self::scan(root)?;
all_skills.extend(provider.skills);
}
Ok(Self { skills: all_skills, script_runner: None })
}
IContextProvider 实现
#[async_trait]
impl IContextProvider for AgentSkillsProvider {
fn name(&self) -> &str { "AgentSkillsProvider" }
fn kind(&self) -> &str { "skills" }
async fn on_invoking(
&self, _agent: &dyn IAgent, _session: &dyn ISession,
_messages: &[ChatMessage], _options: &AgentRunOptions,
) -> Result<ContextResult> {
Ok(ContextResult {
instructions: Some(self.build_advertise()),
tools: self.build_tools(),
..Default::default()
})
}
async fn on_invoked(&self, ...) -> Result<()> { Ok(()) }
}
advertise 文本
`build_advertise()` 生成注入 system prompt 的文本:
## Available Skills
Use load_skill(name) to get full instructions for any skill listed below.
- **code-review**: Review code for quality, bugs, and best practices.
Resources: use read_skill_resource("code-review", "<path>") to read reference documents.
- **deploy**: Automated deployment to staging environments.
Scripts: use run_skill_script("deploy", "<path>") to execute bundled scripts.
动态工具注入
`build_tools()` 根据技能内容注入工具:
pub fn build_tools(&self) -> Vec<Arc<dyn ITool>> {
let mut tools = vec![self.create_load_skill_tool()]; // 始终注入
let has_resources = self.skills.iter().any(|s| s.has_resources());
if has_resources {
tools.push(self.create_read_resource_tool());
}
let has_scripts = self.skills.iter().any(|s| s.has_scripts());
if has_scripts && self.script_runner.is_some() {
tools.push(self.create_run_skill_tool());
}
tools
}
**三个技能工具:**
| 工具 | 名称 | 触发条件 | 功能 | |------|------|----------|------| | `load_skill` | LoadSkillTool | 始终注入 | 根据 skill_name 加载技能完整指令 | | `read_skill_resource` | ReadSkillResourceTool | 至少一个技能有 resources | 读取技能的资源文件 | | `run_skill_script` | RunSkillScriptTool | 至少一个技能有 scripts 且配置了 runner | 执行技能目录中的脚本 |
完整使用示例
use std::sync::Arc;
use rust_agent_core::{AgentBuilder, WorkspaceScope};
use rust_agent_framework::context::{
AgentSkillsProvider,
agent_skill::{AgentSkill, SkillMetadata},
};
// ── 方式一:目录扫描 ──
let skills_provider = AgentSkillsProvider::scan("./skills")?;
// ── 方式二:动态创建 ──
let custom_skill = AgentSkill::dynamic(
SkillMetadata {
name: "db-query".into(),
description: "查询数据库并返回结果".into(),
..Default::default()
},
"# Database Query Skill\n\n## Instructions\n...",
)
.with_resource("schema.sql", "CREATE TABLE users (...);");
let provider = AgentSkillsProvider::new()
.with_skill(custom_skill)
.with_skills(skills_provider.skills); // 合并扫描结果
// ── 注册到 Agent ──
let agent = AgentBuilder::new()
.with_context_provider(Arc::new(provider))
.build()?;
// ── 运行 Agent ──
// LLM 会在 system prompt 中看到 "## Available Skills" 列表,
// 并拥有 load_skill / read_skill_resource 工具。
// LLM 可以先调用 load_skill("db-query") 加载完整指令,
// 然后按指令执行数据库查询。
load_skill 工具的执行
当 LLM 调用 `load_skill("code-review")` 时:
impl LoadSkillTool {
async fn call(&self, arguments: Value) -> Result<ToolResult> {
#[derive(Deserialize)]
struct Args { skill_name: String }
let args: Args = serde_json::from_value(arguments)?;
let skill = self.skills.iter()
.find(|s| s.metadata.name == args.skill_name)
.ok_or_else(|| AgentError::ToolError(
format!("Skill '{}' not found", args.skill_name)
))?;
let instructions = skill.load_instructions()?;
Ok(ToolResult::success(serde_json::json!({
"skill_name": args.skill_name,
"instructions": instructions,
})))
}
}
返回的技能完整指令作为 tool result 注入 LLM 对话,模型据此"学会"如何使用该技能。
技能调用流程
sequenceDiagram
participant LLM as LLM
participant ASP as AgentSkillsProvider
participant LST as LoadSkillTool
participant Skill as AgentSkill
Note over ASP: on_invoking() 注入 advertise 文本 + 工具
ASP->>LLM: system prompt 含 "## Available Skills"
LLM->>LST: 调用 load_skill("code-review")
LST->>Skill: load_instructions()
Skill-->>LST: 完整指令文本
LST-->>LLM: ToolResult { instructions: "..." }
Note over LLM: 现在 LLM 了解 code-review 技能的全部细节
LLM->>LST: 调用 read_skill_resource("code-review", "style-guide.md")
LST->>Skill: read_resource("style-guide.md")
Skill-->>LST: 资源文件内容
LST-->>LLM: ToolResult { content: "..." }
Note over LLM: LLM 按技能指令 + 参考资源执行 code review
关键要点
1. **技能是惰性加载的**——LLM 先看到摘要(advertise),需要时才通过 `load_skill` 加载完整指令 2. **`scan()` 实现自动发现**——只需将技能包放入指定目录,无需手动注册 3. **`dynamic()` 支持非文件来源**——数据库、远程 API、内存创建的技能同样可用 4. **工具按需注入**——只有存在 resources 时才注入 `read_skill_resource`,只有存在 scripts 且配置 runner 时才注入 `run_skill_script` 5. **技能指令是纯文本上下文**——加载后以 tool result 形式注入 LLM 对话,与普通消息无异