13.5 Agent 技能系统
RAF 的技能系统允许将可复用的 Agent 能力打包为标准的 `SKILL.md` 格式,支持动态创建和从目录加载。
技能架构
graph TB
subgraph "技能定义"
SKMD[SKILL.md 文件]
FM[YAML Frontmatter]
BODY[Markdown 正文]
end
subgraph "技能加载"
FD[from_dir 目录加载]
DY[dynamic 动态创建]
end
subgraph "技能资源"
REF[references/ 参考文档]
AST[assets/ 静态资源]
SCR[scripts/ 可执行脚本]
end
subgraph "Agent 集成"
SP[AgentSkillsProvider]
INJ[ContextResult]
TOOL[技能工具]
end
SKMD --> FM
SKMD --> BODY
FD --> SKMD
FD --> REF
SKMD --> AST
SKMD --> SCR
FD --> SP
DY --> SP
SP --> INJ
SP --> TOOL
AgentSkill 类型
#[derive(Clone)]
pub struct AgentSkill {
pub metadata: SkillMetadata,
root_dir: Option<PathBuf>, // from_dir 时设置
instructions: Option<String>, // dynamic 时设置
resources: HashMap<String, Vec<u8>>, // 内联资源
}
SkillMetadata — 技能元信息
#[derive(Debug, Clone, Default)]
pub struct SkillMetadata {
pub name: String, // 技能名称
pub description: String, // 技能描述
pub license: Option<String>, // 许可证
pub compatibility: Option<String>, // 兼容性说明
pub metadata: HashMap<String, String>, // 自定义元数据
}
SKILL.md 格式
每个技能目录下必须有一个 `SKILL.md` 文件,包含 YAML frontmatter 和 Markdown 正文:
---
name: code-review
description: Review code for quality, security, and best practices
license: MIT
compatibility: Any
metadata:
author: team-platform
version: "1.2.0"
category: development
---
# 代码审查技能
## 职责
你是一个经验丰富的代码审查员。当你被激活时,请遵循以下流程:
1. **理解代码**:阅读并理解待审查的代码
2. **检查结构**:评估代码组织、命名和架构
3. **安全审查**:识别潜在的安全漏洞
4. **性能分析**:指出性能瓶颈
5. **最佳实践**:检查是否符合语言和框架的最佳实践
## 输出格式
代码审查报告
总体评分:X/10
优点
- ...
问题
- [严重] ... - [警告] ...
建议
- ...
技能目录结构
skills/
└── code-review/
├── SKILL.md # 技能定义(必需)
├── references/
│ ├── style-guide.md # 参考文档(可选)
│ └── security-checklist.md
├── assets/
│ └── review-template.json # 静态资源(可选)
└── scripts/
└── lint-check.sh # 可执行脚本(可选)
加载方式
from_dir — 从目录加载
use rust_agent_framework::context::skill::AgentSkill;
// 从文件系统加载技能
let skill = AgentSkill::from_dir("./skills/code-review")?;
println!("技能名称: {}", skill.metadata.name);
println!("技能描述: {}", skill.metadata.description);
// 获取完整的技能指令
let instructions = skill.load_instructions()?;
// 读取参考资源
let style_guide = skill.read_resource("references/style-guide.md")?;
// 检查资源
if skill.has_resources() {
println!("技能包含参考资源");
}
if skill.has_scripts() {
println!("技能包含可执行脚本");
}
dynamic — 动态创建
适用于从数据库、远程 API 或其他非文件系统来源加载技能:
let metadata = SkillMetadata {
name: "database-query".into(),
description: "生成和优化 SQL 查询".into(),
license: Some("MIT".into()),
..Default::default()
};
let skill = AgentSkill::dynamic(metadata, r#"
# SQL 查询生成技能
## 原则
1. 始终使用参数化查询
2. 添加适当的索引提示
3. 限制结果集大小
"#)
.with_resource("examples/query.sql", b"SELECT * FROM users WHERE id = ?")
.with_resource("examples/join.sql", b"SELECT u.*, o.total FROM users u JOIN orders o ON u.id = o.user_id");
println!("技能名称: {}", skill.metadata.name);
assert!(skill.has_resources());
AgentSkillsProvider — Agent 集成
`AgentSkillsProvider` 是技能与 Agent 之间的桥梁,作为 `IContextProvider` 将技能注入 Agent:
use rust_agent_framework::AgentSkillsProvider;
// 创建技能提供器
let skills_provider = AgentSkillsProvider::new()
.with_skill(code_review_skill)
.with_skill(testing_skill)
.with_skill(deployment_skill);
// 注册到 Agent
let agent = AgentBuilder::new("multi_skill_agent")
.chat_client(client)
.instructions("你可以使用已加载的技能来完成任务。")
.with_context_provider(skills_provider)
.build()?;
`AgentSkillsProvider` 在每次 Agent 调用时: 1. 将每个技能的 `SKILL.md` 正文注入到 system prompt 2. 注册 `load_skill`、`read_skill_resource` 两个工具 3. 使 Agent 能够动态加载和使用技能
技能相关工具
| 工具 | 用途 | |------|------| | `load_skill(name)` | 加载指定技能的指令内容 | | `read_skill_resource(skill, path)` | 读取技能的资源文件 |
> **注意**:`run_skill_script` 已删除。如需执行 skill 目录下的脚本,请使用 `RunCommand` + `WorkspaceScope` 组合。将 `RunCommand` 注册为 `WorkspaceContextProvider` 的工具,并将 scope 设置为 skill 目录,即可安全地运行脚本。
let scope = Arc::new(WorkspaceScope::new(skill_dir, "skill-scripts")
.with_policy(ScopePolicy::ApproveOutside));
let provider = WorkspaceContextProvider::new(scope)
.add_tool(RunCommand { scope: None, timeout_secs: Some(30) });
// LLM can now run: run_command("python scripts/validate.py --check")
完整示例
use rust_agent_framework::{
AgentBuilder,
context::{
agent_skill::AgentSkill,
skills_provider::AgentSkillsProvider,
},
};
use futures_util::StreamExt;
async fn skill_based_agent() -> anyhow::Result<()> {
let client = DeepSeekChatClient::new(/* ... */)?;
// 1. 从文件系统加载技能
let code_review = AgentSkill::from_dir("./skills/code-review")?;
let security_audit = AgentSkill::from_dir("./skills/security-audit")?;
// 2. 动态创建技能
let deployment = AgentSkill::dynamic(
SkillMetadata {
name: "deployment".into(),
description: "应用程序部署流程".into(),
..Default::default()
},
"你是部署专家。执行以下步骤: 1) 验证构建产物 2) 运行健康检查 3) 滚动更新 4) 监控指标",
);
// 3. 创建技能提供器
let skills = AgentSkillsProvider::new()
.with_skill(code_review)
.with_skill(security_audit)
.with_skill(deployment);
// 4. 构建 Agent
let agent = AgentBuilder::new("devops_agent")
.chat_client(client)
.instructions(
"你是 DevOps 工程师。可以使用 load_skill 加载相关技能来完成任务。"
)
.with_context_provider(skills)
.with_tool(RunCommand::default())
.with_tool(ReadFile::default())
.build()?;
// 5. 运行
let input = vec![ChatMessage::user(
"请审查 src/ 目录下的代码,并给出安全和部署建议。"
)];
let mut stream = agent.run(input, None, None).await?;
while let Some(chunk) = stream.next().await {
if let Ok(result) = chunk {
for content in &result.contents {
if let rust_agent_core::Content::Text(ref t) = content {
print!("{}", t.delta);
}
}
}
}
Ok(())
}
路径守卫
Agent 读取技能资源文件时使用统一的路径守卫:
// read_resource 内部使用 path_guard 进行安全检查
pub(crate) fn read_resource(&self, resource_path: &str) -> Result<String> {
let root = self.root_dir.as_ref().ok_or(/* ... */)?;
// 路径守卫:确保解析后的路径在技能根目录内
let (resolved, _scope) =
crate::tools::path_guard::resolve_safe(root, resource_path, None)?;
std::fs::read_to_string(&resolved).map_err(/* ... */)
}
这确保了 Agent 无法通过路径遍历攻击读取技能目录之外的文件。
注意事项
1. **SKILL.md 必需**:`from_dir()` 要求目录下存在 `SKILL.md` 文件 2. **Frontmatter 解析**:目前仅支持简单的 YAML key: value 格式,不支持嵌套结构 3. **资源路径**:资源读取受路径守卫保护,不能访问技能根目录外的文件 4. **技能大小**:大型技能(如几 MB 的 SKILL.md)会显著增加每次调用的 token 消耗 5. **动态 vs 目录**:动态技能适用于程序化生成的内容,目录技能适用于版本控制的文件存储