AgentScope Java Harness:8. Skill技能让 Agent 从“会说话“进化为“会做事“
目录:
1. 如何优雅地驾驭长期运行的 AI Agent
2. 上下文压缩:让长期 Agent 永不“失忆“
3. 工作区(Workspace)文件即真理,目录即架构
4. 双层记忆系统 让 Agent 拥有真正的“长期大脑“
5. 文件系统一套代码,三种部署,零改动切换
6. 沙箱(Sandbox)让 Agent 在安全笼子里自由奔跑
7. 子 Agent 编排 文件驱动的多智能体协作架构
8. Skill技能让 Agent 从“会说话“进化为“会做事“
9. Plan Mode 让 Agent 先想清楚再动手
10. Channel Agent 通信的“神经系统“设计
工具(Tool)是 Agent 的双手,而技能(Skill)是 Agent 的"操作手册"。没有技能的 Agent 拿着锤子不知道该怎么敲;有了技能的 Agent,才能把工具组合成可复用的工作流。
一、引言:Tool ≠ Skill,这是两个层次的能力
在 Agent 开发中,一个常见的误区是把"注册了工具"等同于"具备了能力"。
// 注册了三个工具
agent.registerTool(new SearchFlightsTool());
agent.registerTool(new BookHotelTool());
agent.registerTool(new GetWeatherTool());
Agent 现在"有手"了,但它知道什么时候用哪个工具、按什么顺序、传什么参数、如何处理异常吗?
| 维度 | Tool(工具) | Skill(技能) |
|---|---|---|
| 本质 | 原子操作 | 工作流编排知识 |
| 粒度 | 单次函数调用 | 多步骤流程 + 决策逻辑 |
| 载体 | Java/Python 代码 | Markdown 文档 |
| 谁编写 | 开发者 | 开发者 / 领域专家 / Agent 自己 |
| 可复用性 | 代码级复用 | 知识级复用,跨 Agent 共享 |
| 运行时开销 | 函数调用 | Prompt 注入(零执行开销) |
AgentScope Harness 的技能系统正是为了填补这个鸿沟:用 Markdown 文件定义可复用的工作流知识,让 Agent 不仅"有手",还"知道怎么用"。
二、核心设计:文件即技能
2.1 技能的物理形态
每个技能是一个独立的 Markdown 文件,存放在工作区的 skills/ 目录下:
workspace/
└── skills/
├── flight-booking.md ← 机票预订技能
├── hotel-recommendation.md ← 酒店推荐技能
├── expense-report.md ← 报销单生成技能
└── data-pipeline.md ← 数据处理流水线技能
2.2 技能文件结构
每个 .md 文件遵循 Front Matter + Body 的标准格式:
---
name: flight-booking
description: 根据用户需求搜索、比较并预订航班,支持多条件筛选和价格优化
tags: [travel, booking, flight]
tools:
- search_flights
- get_flight_details
- book_flight
- get_airport_info
version: "1.2"
author: travel-team
---
# Flight Booking Skill
## Trigger
当用户表达以下意图时激活本技能:
- 查询/搜索航班
- 比较机票价格
- 预订机票
- 询问航线信息
## Workflow
### Step 1: 需求澄清
确认以下必要信息(缺失则主动询问):
- 出发城市、到达城市
- 出发日期(返程日期,如适用)
- 乘客人数和舱位偏好
- 预算范围(可选)
### Step 2: 搜索与筛选
1. 调用 `search_flights` 获取候选航班列表
2. 如果用户指定了机场偏好,调用 `get_airport_info` 验证机场代码
3. 按用户优先级排序(价格 / 时间 / 直飞优先)
### Step 3: 结果呈现
以表格形式展示 Top 3 选项:
| 航班号 | 起飞-到达 | 时长 | 价格 | 备注 |
每个选项附带简短推荐理由。
### Step 4: 确认与预订
1. 用户选择后,调用 `get_flight_details` 获取详细信息
2. 向用户确认关键信息(日期、乘客、价格)
3. 调用 `book_flight` 完成预订
4. 返回预订确认号和行程摘要
## Error Handling
- `search_flights` 无结果:建议调整日期或邻近机场
- `book_flight` 失败:保留候选列表,提示用户重新选择
- 超时:告知用户稍后重试,不自动重复预订
## Constraints
- 绝不未经用户确认就执行 `book_flight`
- 价格信息必须来自工具返回值,禁止估算
- 儿童/婴儿票价需特别标注
2.3 Front Matter 字段详解
| 字段 | 必填 | 类型 | 说明 |
|---|---|---|---|
| name | ✅ | String | 技能唯一标识 |
| description | ✅ | String | 能力描述,用于匹配和展示 |
| tags | ❌ | List | 标签,支持分类检索 |
| tools | ❌ | List | 技能依赖的工具白名单 |
| version | ❌ | String | 版本号,支持演进管理 |
| author | ❌ | String | 作者/团队 |
| enabled | ❌ | Boolean | 是否启用(默认 true) |
三、技能装配机制:从文件到 Prompt
3.1 构建期扫描
HarnessAgent agent = HarnessAgent.builder()
.name("travel-assistant")
.model(model)
.workspace(Path.of("./workspace"))
// 框架自动扫描 workspace/skills/*.md
// 无需手动注册任何技能
.build();
扫描流程:
HarnessAgent.build()
│
▼ WorkspaceContextHook
│
├── listDir("skills/")
│ → [flight-booking.md, hotel-recommendation.md, ...]
│
├── 逐个解析 Front Matter
│ → SkillSpec(name, description, tools, ...)
│
├── 过滤 enabled=true 的技能
│
└── 注入主 Agent system prompt
→ 技能摘要列表(name + description)
3.2 两阶段注入策略
技能内容不会全部塞进 system prompt(那会撑爆上下文)。框架采用两阶段注入:
┌─────────────────────────────────────────────────────────────┐
│ Stage 1: System Prompt(每轮都有) │
│ │
│ ## Available Skills │
│ - flight-booking: 根据用户需求搜索、比较并预订航班... │
│ - hotel-recommendation: 基于偏好推荐酒店... │
│ - expense-report: 自动生成差旅报销单... │
│ │
│ Use `load_skill` tool to load full skill instructions. │
└─────────────────────────────────────────────────────────────┘
↓ Agent 判断需要某个技能时
┌─────────────────────────────────────────────────────────────┐
│ Stage 2: load_skill 工具调用(按需加载) │
│ │
│ Agent 调用: load_skill(name="flight-booking") │
│ 框架返回: flight-booking.md 的完整 Body 内容 │
│ Agent 将完整工作流纳入当前推理上下文 │
└─────────────────────────────────────────────────────────────┘
3.3 为什么是两阶段?
| 方案 | Token 消耗 | 信息完整性 | 适用场景 |
|---|---|---|---|
| 全量注入所有技能 | 💥 爆炸 | ✅ 完整 | 技能极少(<3个) |
| 仅注入摘要 + 按需加载 | ✅ 可控 | ✅ 完整 | 技能较多(推荐) |
| 完全不注入 | ✅ 零 | ❌ Agent 不知道有哪些技能 | 不可用 |
两阶段注入在 token 效率和信息完整性之间取得了最佳平衡:
- 摘要列表让 Agent “知道自己会什么”;
- load_skill 让 Agent “在需要时学会怎么做”。
四、技能 vs 工具 vs 子 Agent:三者定位辨析
这是 Harness 中最容易混淆的三个概念。它们不是替代关系,而是互补的不同抽象层:
┌─────────────────────────────────────────────────────────────┐
│ 能力分层模型 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Skill(技能)= 工作流知识 │ │
│ │ "如何组合工具完成一个业务目标" │ │
│ │ 载体:Markdown · 注入方式:Prompt · 执行者:当前 Agent │ │
│ └──────────────────────┬──────────────────────────────┘ │
│ │ 使用 │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Tool(工具)= 原子操作 │ │
│ │ "执行一个具体的函数调用" │ │
│ │ 载体:代码 · 注入方式:Function Schema · 执行者:运行时 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Sub-Agent(子 Agent)= 独立能力单元 │ │
│ │ "委派一个完整任务给另一个 Agent" │ │
│ │ 载体:Markdown · 注入方式:委派工具 · 执行者:独立实例 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
4.1 决策指南
| 你的需求 | 选择 | 理由 |
|---|---|---|
| 封装一个 API 调用 | Tool | 原子操作,不需要流程知识 |
| 定义"如何用多个工具完成一个业务目标" | Skill | 工作流知识,当前 Agent 自己执行 |
| 需要一个完全独立的能力单元 | Sub-Agent | 独立上下文、独立工具集、独立推理 |
| 让非技术人员定义业务流程 | Skill | Markdown 编辑,无需写代码 |
| 需要跨 Agent 复用工作流 | Skill | 复制 .md 文件即可 |
| 需要并行执行多个独立任务 | Sub-Agent | 子 Agent 可并行委派 |
4.2 协作示例
用户:"帮我订明天去上海的出差行程"
主 Agent 推理:
→ 加载 skill: travel-planning(工作流知识)
→ 按技能定义的 Workflow 执行:
Step 1: 调用 search_flights(Tool)
Step 2: 调用 search_hotels(Tool)
Step 3: delegate_to_expense_calculator(Sub-Agent)
Step 4: 整合结果,生成行程单
技能定义了"做什么、怎么做",工具提供了"做的能力",子 Agent 承担了"独立子任务"。
五、技能的运行时生命周期
5.1 完整执行流程
用户发送消息
│
▼ WorkspaceContextHook
│ 扫描 skills/ → 注入摘要列表到 system prompt
│
▼ ReAct 推理循环
│
├── Agent 判断需要某个技能
│ │
│ ▼ 调用 load_skill(name="flight-booking")
│ │
│ ▼ 框架读取 skills/flight-booking.md
│ 返回完整 Body 内容
│ │
│ ▼ Agent 将工作流纳入推理上下文
│ 按 Workflow 步骤依次执行
│ │
│ ├── 调用 search_flights(Tool)
│ ├── 调用 get_flight_details(Tool)
│ └── 调用 book_flight(Tool)
│
▼ 返回结果给用户
5.2 技能内容的缓存
load_skill 的结果在当前会话内缓存,避免重复读取文件:
第 1 轮: load_skill("flight-booking") → 读文件 → 缓存
第 2 轮: load_skill("flight-booking") → 命中缓存 → 零 IO
第 3 轮: 新会话 → 缓存失效 → 重新读文件
5.3 热更新
由于技能是从文件实时读取的,修改 .md 文件后,下一次 load_skill 立即获取最新内容:
# 更新技能
vim workspace/skills/flight-booking.md
# 无需重启服务
# 下一次 call() 中 Agent 调用 load_skill 时自动获取新版本
六、高级特性
6.1 技能依赖声明
技能可以在 Front Matter 中声明对其他技能的依赖:
---
name: travel-planning
description: 端到端差旅规划,包含机票、酒店、报销
depends_on:
- flight-booking
- hotel-recommendation
- expense-report
---
当 Agent 加载 travel-planning 时,框架可以:
- 自动预加载依赖技能的摘要
- 在技能 Body 中引用依赖技能的名称
- 校验依赖技能是否存在
6.2 技能模板变量
技能 Body 支持运行时变量替换:
## 约束
- 当前用户:{{user.name}}
- 报销标准:{{policy.max_flight_price}} 元/程
- 当前日期:{{current_date}}
变量来源:
- RuntimeContext 中的用户信息
- 工作区中的配置文件
- 框架内置变量(如 current_date)
6.3 技能版本管理
---
name: flight-booking
version: "1.2"
---
结合 Git 版本控制,技能的演进历史完全可追溯:
git log --oneline workspace/skills/flight-booking.md
# a3f2c1d v1.2: 增加儿童票价标注
# b8e4a2f v1.1: 增加邻近机场建议
# c9d5b3e v1.0: 初始版本
6.4 技能禁用与灰度
---
name: experimental-booking
enabled: false # 暂时禁用
---
或通过标签实现灰度:
---
name: flight-booking-v2
tags: [travel, booking, beta]
enabled: true
---
框架可以根据配置决定是否加载带特定标签的技能。
七、与其他子系统的协作
┌─────────────────────────────────────────────────────────────┐
│ 技能系统生态 │
│ │
│ ┌──────────────┐ │
│ │ skills/*.md │ ← 技能文件(人类/Agent 编辑) │
│ └──────┬───────┘ │
│ │ 构建期扫描 │
│ ▼ │
│ ┌──────────────┐ Stage 1: 摘要注入 │
│ │ System Prompt│ ◀── name + description │
│ └──────┬───────┘ │
│ │ Agent 判断需要 │
│ ▼ │
│ ┌──────────────┐ Stage 2: load_skill │
│ │ 完整技能内容 │ ──▶ Agent 推理上下文 │
│ └──────┬───────┘ │
│ │ 按 Workflow 执行 │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 执行面 │ │
│ │ ┌──────┐ ┌──────────┐ ┌──────────────────┐ │ │
│ │ │Tools │ │Sub-Agents│ │ Memory/Knowledge │ │ │
│ │ │原子 │ │委派 │ │ 查阅/写入 │ │ │
│ │ │操作 │ │独立任务 │ │ │ │ │
│ │ └──────┘ └──────────┘ └──────────────────┘ │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
| 子系统 | 与技能的关系 |
|---|---|
| Workspace | 技能文件存储在工作区,天然 Git 友好 |
| 工具 | 技能声明依赖的工具白名单,框架可校验 |
| 子 Agent | 技能 Workflow 中可以包含委派步骤 |
| 记忆 | 技能执行过程中可读写 MEMORY.md |
| 知识 | 技能 Body 可引用 knowledge/ 中的领域知识 |
| 沙箱 | 技能中涉及的命令执行在沙箱内隔离运行 |
| 压缩 | 技能内容作为工具结果参与压缩策略 |
八、实战:从零构建一个技能
8.1 场景:报销单生成
Step 1: 创建技能文件
touch workspace/skills/expense-report.md
Step 2: 编写规格
---
name: expense-report
description: 根据差旅行程自动生成符合公司政策的报销单
tags: [finance, travel, reimbursement]
tools:
- calculate_expense
- validate_policy
- generate_pdf
version: "1.0"
---
# Expense Report Generation
## Trigger
当用户要求生成报销单、提交差旅费用时激活。
## Workflow
### Step 1: 收集行程信息
从对话上下文中提取或主动询问:
- 出差日期范围
- 交通费用明细(航班/火车/打车)
- 住宿费用明细
- 餐饮补贴天数
### Step 2: 政策校验
调用 `validate_policy` 检查:
- 机票是否超出经济舱标准
- 酒店单价是否在限额内
- 餐饮补贴天数是否合理
如有违规项,列出并请求用户确认或调整。
### Step 3: 费用计算
调用 `calculate_expense` 汇总:
- 交通费小计
- 住宿费小计
- 餐饮补贴小计
- 总计
### Step 4: 生成报销单
调用 `generate_pdf` 生成 PDF 文件。
返回文件路径和费用摘要。
## Output Format
📋 差旅报销单
━━━━━━━━━━━━━━━
出差人:{{user.name}}
日期:{{trip.start_date}} ~ {{trip.end_date}}
交通费:¥{{transport_total}}
住宿费:¥{{hotel_total}}
餐饮补贴:¥{{meal_total}}
━━━━━━━━━━━━━━━
合计:¥{{grand_total}}
📎 报销单 PDF:{{pdf_path}}
## Constraints
- 所有金额必须来自工具计算结果,禁止手动加总
- 政策校验未通过时,不得生成报销单
- PDF 文件名格式:报销单_姓名_YYYYMMDD.pdf
Step 3: 验证生效
// 无需改代码,下一轮 call() 自动发现新技能
agent.call(List.of(new UserMessage("帮我生成这次出差的报销单")));
// Agent 会自动 load_skill("expense-report") 并按 Workflow 执行
九、最佳实践
9.1 技能编写指南
| 原则 | 说明 |
|---|---|
| Trigger 要明确 | 清晰定义激活条件,避免误触发 |
| Workflow 要分步 | 每步有明确的输入、动作、输出 |
| Error Handling 要覆盖 | 每个工具调用都要考虑失败场景 |
| Constraints 要具体 | “不要编造"不如"价格必须来自工具返回值” |
| Output Format 要标准化 | 便于下游消费和用户阅读 |
| description 要精确 | 这是 Agent 决定是否加载技能的唯一依据 |
9.2 技能组织策略
| 策略 | 适用场景 |
|---|---|
| 一个业务目标一个技能 | 大多数场景 |
| 大技能拆分为小技能 + 组合技能 | 复杂流程 |
| 通用技能放公共目录,专用技能放 Agent 专属目录 | 多 Agent 共享 |
| 技能命名使用 - 格式 | 提高可读性和检索效率 |
9.3 常见反模式
# ❌ description 太模糊
description: 处理各种事务
# ❌ Workflow 没有分步
## Workflow
调用所有相关工具然后生成结果。 # Agent 不知道顺序和逻辑
# ❌ 缺少 Error Handling
# 工具失败了怎么办?Agent 只能猜
# ❌ Constraints 太笼统
## Constraints
- 要准确 # 什么叫"准确"?
- 要安全 # 什么叫"安全"?
# ❌ 技能体过大(>2000 tokens)
# 考虑拆分为多个小技能 + 组合技能
十、设计哲学总结
1. 知识外置,能力内化
技能将"怎么做"的知识从代码和模型权重中外置为 Markdown 文件。这使得知识可以被人类审查、编辑、版本管理,也使得同一个模型可以通过加载不同技能获得不同能力。
2. 按需加载,Token 友好
两阶段注入确保了只有真正需要的技能才会占用上下文窗口。摘要列表是"目录",完整内容是"正文"——你不会把整本书塞进脑子里,只在需要时翻开对应章节。
3. 文件即接口,人机共编
技能文件是人类和 Agent 的共同接口。开发者写初始版本,领域专家补充业务细节,Agent 自己在实践中提出改进建议。这种"人机共编"模式让技能持续进化。
4. 组合优于继承
技能之间是组合关系,不是继承关系。一个大技能可以引用多个小技能,一个小技能可以被多个大技能复用。这种扁平的组合模型比深层继承更容易理解和维护。
5. 技能是可治理的能力单元
每个技能有名称、版本、作者、标签、启用状态。这不是"随意的 prompt 片段",而是可治理、可审计、可演进的能力资产。
十一、结语
AgentScope Harness 的技能系统,回答了一个关键问题:
如何让 Agent 从"拥有工具"进化为"掌握工作流"?
答案是:把工作流知识从代码和模型中解放出来,变成人类可读、机器可解析、Agent 可按需加载的文件。
当你把技能看作"操作手册"而非"prompt 模板"时,很多设计决策就变得自然而然了:
- 操作手册可以独立于执行者存在 → 跨 Agent 复用
- 操作手册可以分章节按需查阅 → 两阶段注入
- 操作手册可以由专家编写 → 领域知识沉淀
- 操作手册可以有版本号 → 能力演进可追溯
- 操作手册可以包含错误处理 → 鲁棒性工作流
如果你正在构建需要复杂工作流的 Agent 系统,这套"文件即技能"的设计思路值得深入研究和借鉴。它让 Agent 的能力建设从"写代码"变成了"写文档"——这不仅是技术范式的转变,更是让 AI 能力回归人类可理解、可参与、可治理的工程哲学。
更多推荐



所有评论(0)