AGENT 教程 S05 技能系统(Skill)

S05 技能系统(Skill)教程

整体说明

承接前文 S01~S04 已有能力:基础Agent循环、工具路由+安全沙箱、会话待办规划、子代理上下文隔离。

本章核心:把专项领域知识从固定提示词中拆分,实现「轻量发现、按需加载」,解决提示词臃肿、Token 浪费问题。全程在原有代码基础上增量开发,主循环、已有工具逻辑无需改动。


一、提出问题

1. 现有能力回顾

到 S04 为止,Agent 已具备完整基础能力:

  • 自主调用各类工具执行操作;
  • 通过 todo 做会话内多步骤任务规划;
  • 利用子代理隔离局部任务上下文,避免主消息爆炸。

2. 现存痛点

不同任务需要不同专项知识/操作规范 ,例如:代码审查、Git 版本操作、框架搭建等。

如果按照传统写法,把所有领域知识、操作手册全部硬编码写入固定 system prompt,会产生两个严重问题:

  1. Token 严重浪费:大部分场景用不到的知识常驻提示词,增加接口开销、拉长上下文;
  2. 提示词臃肿混乱:全局规则、工具说明、各类专业知识混杂在一起,模型难以区分核心指令,执行逻辑出错。

3. 本章核心目标

  1. 设计技能(Skill) 体系,将「全局固定规则」和「可选专项知识」解耦;
  2. 分为两层设计:先展示轻量化技能目录 (让模型知道有哪些技能可用),需要时再加载完整技能内容;
  3. 技能内容仅在当前任务需要时注入上下文,任务结束不常驻,从根源精简提示词;
  4. 守住教学边界:仅实现基础的「发现+按需加载」,不拓展多源加载、条件激活、技能参数化等复杂功能。

4. 核心诉求总结

不再把所有知识一次性塞进提示词,做到 用什么技能,就加载什么技能。


二、分析问题

1. 核心名词通俗解读

名词 通俗解释
Skill(技能) 针对某一类专项任务的可复用操作手册,包含使用场景、执行步骤、注意事项(例:代码审查清单、Git 提交规范)
Discovery(发现) 只展示技能轻量化元信息(名称+简短描述),体积极小,常驻在系统提示词中,让模型"知道有哪些技能"
Loading(加载) 模型判断需要某份技能时,通过专用工具读取完整技能正文,临时注入上下文使用(会加载大量文本,属于"重操作")
Skill Registry(技能注册表) 统一管理所有技能的容器,负责扫描技能文件、查询目录、加载完整内容,集中管控,避免代码散乱

2. 核心心智模型(两层架构)

整体流程

复制代码
固定系统提示词(常驻)
  ├─ 全局身份/基础规则
  ├─ 工具说明
  └─ 【轻量化技能目录】(仅名称+简介,所有技能都在这里展示)
        ↓
用户发起专项任务 → 模型匹配到对应技能 → 调用 load_skill 工具
        ↓
读取技能完整正文 → 以工具结果形式注入消息上下文
        ↓
模型依据加载后的完整技能手册执行任务

核心原则

  • 常驻层:只放轻量信息(全局规则、技能目录),不占用大量 Token;
  • 按需层:完整技能正文临时加载,任务完成后不长期留存。

3. 三大基础数据结构

(1)SkillManifest 技能元数据(目录条目)

轻量化信息,仅用于"发现阶段",结构极简:

python 复制代码
{
    "name": "code-review",       # 技能名称(唯一标识)
    "description": "代码审查检查清单" # 技能用途描述
}

(2)SkillDocument 完整技能文档

包含元数据 + 详细正文,是真正要加载的内容:

python 复制代码
{
    "manifest": {...},  # 上面的 SkillManifest 元数据
    "body": "这里是完整的技能手册、步骤、规范等大段文本"
}

(3)SkillRegistry 技能注册表

全局统一管理器,核心能力:

  1. 自动扫描本地技能目录,加载所有技能;
  2. 对外输出技能目录(供系统提示词使用);
  3. 根据技能名,读取并返回完整技能正文。

4. 易混淆概念区分(初学者必看)

很多人会把 Skill / Memory / CLAUDE.md 弄混,用场景快速区分:

  1. Skill 技能 :某一类任务的操作手册,按需加载,临时使用(例:代码审查步骤、Git 操作规范);
  2. Memory 记忆 :跨会话需要长期记住的事实、用户偏好(例:用户习惯用 Python、项目目录路径);
  3. CLAUDE.md :全局永久规则,整个系统的基础约束,永远常驻提示词(例:工具使用总规则、安全限制)。

5. 新旧方案对比

方案 实现方式 优点 缺点
传统方案 所有技能正文全量写入 system prompt 实现简单 提示词臃肿、Token 浪费、模型易被冗余信息干扰
技能系统方案 常驻轻量目录 + 按需加载完整正文 精简提示词、节约 Token、逻辑清晰 需额外实现技能管理与加载工具

6. 初学者高频踩坑点

  1. 把所有技能正文常驻提示词:违背技能系统设计初衷,回到臃肿老问题;
  2. 技能目录描述过于简单:只写技能名、不写用途,模型无法判断何时该加载对应技能;
  3. 将技能当成强制全局规则:技能是「可选手册」,不是必须全程遵守的硬性规则;
  4. 混淆 Skill / Memory:把临时任务手册当成长期记忆,架构混乱;
  5. 一步到位做复杂加载逻辑:初学先实现"目录展示+单技能加载",不要叠加多源加载、条件激活等复杂功能。

三、解决问题(落地实现 + 完整可运行代码)

1. 整体实现思路

基于 S04 子代理代码 增量开发,原有:安全沙箱、基础工具、Todo待办、子代理、主循环 全部保留,仅新增 4 部分内容:

  1. 本地 skills 目录,存放多个技能文件;
  2. SkillRegistry 技能注册表类,扫描、解析、加载技能;
  3. 新增 load_skill 工具,用于触发技能加载;
  4. 修改系统提示词,嵌入轻量化技能目录。

2. 第一步:规划本地文件目录

在代码同级目录,创建如下文件夹结构(纯文本文件,无需额外依赖):

复制代码
项目根目录
├─ agent_workspace/      # S02 遗留的文件沙箱目录
├─ skills/               # 新增:技能总目录
│  ├─ code-review/       # 技能1:代码审查
│  │  └─ SKILL.md        # 技能文件(元数据 + 正文)
│  └─ git-workflow/      # 技能2:Git 工作流
│     └─ SKILL.md
└─ main.py               # 主代码文件

3. 第二步:编写技能文件(SKILL.md)

文件头部用 --- 包裹元数据(Frontmatter),后面写技能正文(初学者无需第三方解析库,代码手动分割)。

示例1:skills/code-review/SKILL.md

md 复制代码
---
name: code-review
description: 代码审查专用清单,用于检查代码语法、注释、异常处理、代码规范
---
# 代码审查标准流程
1. 检查代码语法是否合法,有无明显报错;
2. 检查函数、类是否添加注释说明;
3. 检查是否包含异常捕获逻辑;
4. 检查变量命名是否符合Python规范;
5. 汇总所有问题,输出审查报告。

示例2:skills/git-workflow/SKILL.md

md 复制代码
---
name: git-workflow
description: Git版本控制操作规范,包含分支创建、提交、推送步骤
---
# Git 标准工作流
1. 新建分支:git checkout -b 分支名
2. 查看变更:git status
3. 暂存文件:git add .
4. 提交代码:git commit -m "提交说明"
5. 推送远程:git push origin 分支名

4. 第三步:完整可运行代码(兼容DeepSeek + Windows编码 + 全历史功能)

python 复制代码
from openai import OpenAI
import json
from pathlib import Path
import subprocess
import locale

# ===================== 1. 全局基础配置 =====================
DEEPSEEK_API_KEY = "你的DeepSeek API Key"
client = OpenAI(
    api_key=DEEPSEEK_API_KEY,
    base_url="https://api.deepseek.com",
    timeout=30.0
)
MODEL = "deepseek-chat"
WORKDIR = Path("./agent_workspace")
WORKDIR.mkdir(exist_ok=True)
SKILLS_DIR = Path("./skills")  # 技能根目录

# ===================== 2. 新增:技能注册表 SkillRegistry(S05核心) =====================
class SkillRegistry:
    def __init__(self, skills_root: Path):
        self.skills_root = skills_root
        self.skill_data = {}  # 存储所有技能:{技能名: 完整技能文档}
        self._scan_skills()

    def _scan_skills(self):
        """自动扫描所有SKILL.md文件,解析元数据和正文"""
        # 遍历所有子文件夹下的SKILL.md
        for skill_file in self.skills_root.rglob("SKILL.md"):
            content = skill_file.read_text(encoding="utf-8")
            # 简易解析 Frontmatter:以 --- 分割元数据和正文(初学者简化方案)
            parts = content.split("---")
            if len(parts) < 3:
                continue
            # 提取元数据
            meta_lines = parts[1].strip().splitlines()
            meta = {}
            for line in meta_lines:
                if ":" in line:
                    k, v = line.split(":", 1)
                    meta[k.strip()] = v.strip()
            # 提取技能正文
            body = parts[2].strip()
            # 组装完整技能文档
            skill_name = meta.get("name", skill_file.parent.name)
            self.skill_data[skill_name] = {
                "manifest": {
                    "name": skill_name,
                    "description": meta.get("description", "暂无描述")
                },
                "body": body
            }

    def get_skill_list(self) -> str:
        """输出轻量化技能目录(用于嵌入系统提示词)"""
        if not self.skill_data:
            return "暂无可用技能"
        lines = ["可用技能列表:"]
        for name, info in self.skill_data.items():
            manifest = info["manifest"]
            lines.append(f"- {name}:{manifest['description']}")
        return "\n".join(lines)

    def load_skill_body(self, skill_name: str) -> str:
        """根据技能名,加载完整技能正文"""
        if skill_name not in self.skill_data:
            return f"错误:未找到名为【{skill_name}】的技能"
        return f"【{skill_name} 技能手册】\n{self.skill_data[skill_name]['body']}"

# 初始化全局技能注册表
SKILL_REGISTRY = SkillRegistry(SKILLS_DIR)

# ===================== 3. S02 原有:安全沙箱 + 基础工具(完全保留) =====================
def safe_path(file_path: str) -> Path:
    full_path = (WORKDIR / file_path).resolve()
    if not full_path.is_relative_to(WORKDIR):
        raise PermissionError(f"禁止访问工作目录外文件:{file_path}")
    return full_path

def run_bash(command: str) -> str:
    try:
        raw_out = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT, timeout=10)
        sys_encode = locale.getpreferredencoding(False)
        return f"命令执行成功:\n{raw_out.decode(sys_encode, errors='replace')}"
    except subprocess.CalledProcessError as e:
        sys_encode = locale.getpreferredencoding(False)
        return f"命令异常:{e.output.decode(sys_encode, errors='replace')}"
    except Exception as e:
        return f"命令失败:{str(e)}"

def run_read_file(path: str, limit: int = None) -> str:
    try:
        fp = safe_path(path)
        content = fp.read_text(encoding="utf-8")
        if limit:
            content = "\n".join(content.splitlines()[:limit])
        return f"【{path}内容】:\n{content}"
    except Exception as e:
        return f"读取失败:{str(e)}"

def run_write_file(path: str, content: str) -> str:
    try:
        fp = safe_path(path)
        fp.write_text(content, encoding="utf-8")
        return f"✅成功写入{path}"
    except Exception as e:
        return f"写入失败:{str(e)}"

def run_edit_file(path: str, old_text: str, new_text: str) -> str:
    try:
        fp = safe_path(path)
        txt = fp.read_text("utf-8")
        txt = txt.replace(old_text, new_text)
        fp.write_text(txt, "utf-8")
        return f"✅{path}文本替换完成"
    except Exception as e:
        return f"编辑失败:{str(e)}"

# ===================== 4. S03 原有:Todo待办管理类(完全保留) =====================
class TodoManager:
    def __init__(self):
        self.items = []
    def update(self, items: list):
        valid_items = []
        in_progress_cnt = 0
        if len(items) > 20:
            raise ValueError("任务清单不能超过20项")
        for item in items:
            content = item.get("content", "").strip()
            status = item.get("status", "pending")
            if not content:
                raise ValueError("任务内容不能为空")
            if status not in ["pending", "in_progress", "completed"]:
                raise ValueError("状态只能是pending/in_progress/completed")
            if status == "in_progress":
                in_progress_cnt +=1
            valid_items.append({"content": content, "status": status})
        if in_progress_cnt > 1:
            raise ValueError("同一时间只能有1个in_progress任务")
        self.items = valid_items
        return self.render()
    def render(self):
        if not self.items:
            return "暂无待办任务"
        mark_map = {"pending":"[ ]", "in_progress":"[>]", "completed":"[x]"}
        res = []
        for task in self.items:
            res.append(f"{mark_map[task['status']]} {task['content']}")
        return "\n".join(res)
TODO_MGR = TodoManager()

# ===================== 5. S04 原有:子代理 run_subagent(完全保留) =====================
SUB_TOOL_HANDLER = {
    "bash": run_bash,
    "read_file": run_read_file,
    "write_file": run_write_file,
    "edit_file": run_edit_file,
    "todo": lambda **kw: TODO_MGR.update(kw["items"])
}
SUB_TOOLS = [
    {"type":"function","function":{"name":"bash","description":"执行终端命令","parameters":{"type":"object","properties":{"command":{"type":"string"}},"required":["command"]}}},
    {"type":"function","function":{"name":"read_file","description":"读取文件","parameters":{"type":"object","properties":{"path":{"type":"string"},"limit":{"type":"integer"}},"required":["path"]}},
    {"type":"function","function":{"name":"write_file","description":"写入文件","parameters":{"type":"object","properties":{"path":{"type":"string"},"content":{"type":"string"}},"required":["path","content"]}},
    {"type":"function","function":{"name":"edit_file","description":"编辑文件","parameters":{"type":"object","properties":{"path":{"type":"string"},"old_text":{"type":"string"},"new_text":{"type":"string"}},"required":["path","old_text","new_text"]}},
    {"type":"function","function":{"name":"todo","description":"更新子任务待办清单","parameters":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"content":{"type":"string"},"status":{"type":"string"}},"required":["content","status"]}}},"required":["items"]}}
]

def run_subagent(sub_prompt: str, max_turn=5) -> str:
    sub_messages = [{"role":"user","content":sub_prompt}]
    sub_sys = "你是文件调研子代理,完成任务后只输出精简调研总结"
    turn = 0
    while turn < max_turn:
        resp = client.chat.completions.create(model=MODEL,messages=[{"role":"system","content":sub_sys}]+sub_messages,tools=SUB_TOOLS)
        msg = resp.choices[0].message
        sub_messages.append(msg.model_dump(exclude_none=True))
        if not msg.tool_calls:
            return f"【子代理调研总结】\n{msg.content}"
        sub_res = []
        for call in msg.tool_calls:
            args = json.loads(call.function.arguments)
            func = SUB_TOOL_HANDLER.get(call.function.name)
            ret = func(**args) if func else f"未知工具{call.function.name}"
            sub_res.append({"role":"tool","tool_call_id":call.id,"content":ret})
        sub_messages.extend(sub_res)
        turn +=1
    return "【子代理超时总结】:子任务达到最大运行轮次,根据已有信息汇总"

# ===================== 6. 工具路由字典:新增 load_skill 工具(S05核心) =====================
TOOL_HANDLERS = {
    "bash": run_bash,
    "read_file": run_read_file,
    "write_file": run_write_file,
    "edit_file": run_edit_file,
    "todo": lambda **kw: TODO_MGR.update(kw["items"]),
    "task": lambda **kw: run_subagent(kw["prompt"]),
    # S05 新增:加载技能工具
    "load_skill": lambda **kw: SKILL_REGISTRY.load_skill_body(kw["name"])
}

# ===================== 7. 工具描述列表:新增 load_skill 说明 =====================
TOOLS = [
    {"type":"function","function":{"name":"bash","description":"执行终端命令","parameters":{"type":"object","properties":{"command":{"type":"string"}},"required":["command"]}}},
    {"type":"function","function":{"name":"read_file","description":"读取文件","parameters":{"type":"object","properties":{"path":{"type":"string"},"limit":{"type":"integer"}},"required":["path"]}},
    {"type":"function","function":{"name":"write_file","description":"写入文件","parameters":{"type":"object","properties":{"path":{"type":"string"},"content":{"type":"string"}},"required":["path","content"]}},
    {"type":"function","function":{"name":"edit_file","description":"编辑文件","parameters":{"type":"object","properties":{"path":{"type":"string"},"old_text":{"type":"string"},"new_text":{"type":"string"}},"required":["path","old_text","new_text"]}},
    {"type":"function","function":{"name":"todo","description":"更新任务清单","parameters":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"content":{"type":"string"},"status":{"type":"string"}},"required":["content","status"]}}},"required":["items"]}},
    {"type":"function","function":{"name":"task","description":"创建独立上下文子代理","parameters":{"type":"object","properties":{"prompt":{"type":"string"}},"required":["prompt"]}}},
    # S05 新增:技能加载工具
    {
        "type": "function",
        "function": {
            "name": "load_skill",
            "description": "加载指定技能的完整手册,传入技能名称,按需读取专项任务规范",
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": "要加载的技能名称"}
                },
                "required": ["name"]
            }
        }
    }
]

# ===================== 8. 系统提示词:嵌入【轻量化技能目录】(S05关键改动) =====================
# 把技能目录动态拼入提示词,只放简介,不放完整正文
PARENT_SYS = f"""
你是全能智能体,遵守以下规则:
1. 多步骤任务优先使用todo拆分计划;
2. 复杂调研任务调用task交给子代理处理;
3. 执行专项任务前,必须先调用load_skill加载对应技能手册;
4. 严格按照技能手册的步骤完成任务。

{SKILL_REGISTRY.get_skill_list()}
"""

# ===================== 9. 主循环(历史版本完全不变) =====================
def agent_loop(user_input):
    state = {
        "messages": [{"role": "user", "content": user_input}],
        "turn_count": 1,
        "planning_state": {"rounds_since_update": 0}
    }
    while True:
        print(f"\n=====第{state['turn_count']}轮【父Agent】推理=====")
        resp = client.chat.completions.create(
            model=MODEL,
            messages=[{"role":"system","content":PARENT_SYS}] + state["messages"],
            tools=TOOLS
        )
        msg = resp.choices[0].message
        state["messages"].append(msg.model_dump(exclude_none=True))

        if not msg.tool_calls:
            print("\n🎉最终回复:",msg.content)
            return

        tool_results = []
        todo_used = False
        for tool_call in msg.tool_calls:
            func = tool_call.function
            args = json.loads(func.arguments)
            handler = TOOL_HANDLERS.get(func.name)
            ret = handler(**args) if handler else f"未知工具:{func.name}"
            if func.name == "todo": todo_used = True
            tool_results.append({"role":"tool","tool_call_id":tool_call.id,"content":ret})

        # 待办清单提醒逻辑
        if todo_used:
            state["planning_state"]["rounds_since_update"] = 0
        else:
            state["planning_state"]["rounds_since_update"] +=1
            if state["planning_state"]["rounds_since_update"] >=3:
                remind = "<reminder>连续3轮未更新待办清单,请调用todo刷新</reminder>"
                tool_results.insert(0,{"role":"tool","tool_call_id":"remind_001","content":remind})

        state["messages"].extend(tool_results)
        state["turn_count"] +=1

# ===================== 10. 测试用例 =====================
if __name__ == "__main__":
    # 测试1:触发代码审查技能加载
    agent_loop("对当前项目做一次完整的代码审查")

    # 测试2:触发Git工作流技能加载(注释上面一行,单独运行)
    # agent_loop("告诉我标准的Git版本操作流程")

5. 运行步骤 & 效果说明

前置准备

  1. 替换代码中 DEEPSEEK_API_KEY 为你的有效密钥;
  2. 按照前文要求,创建 skills 目录和两个 SKILL.md 文件;
  3. 安装依赖:pip install openai。

运行流程(以「代码审查」为例)

  1. 第一轮 :系统提示词展示轻量化技能目录,模型识别任务需要 code-review 技能;
  2. 调用工具 :执行 load_skill(name="code-review"),注册表读取完整技能手册;
  3. 注入上下文:技能完整正文作为工具结果写入消息列表;
  4. 执行任务:模型依据加载的审查清单,完成代码审查并输出报告;
  5. 任务结束:完整技能内容不会常驻,下一轮无关任务不会重复加载。

核心效果验证

  • 系统提示词只有技能名称和简介,无大段正文;
  • 仅在对应任务触发时,才加载完整技能内容,完美实现「按需加载」。

四、拓展练习(巩固知识点)

  1. 新增自定义技能 :在 skills 下新建文件夹和 SKILL.md,补充「Python脚本编写规范」技能,测试模型自动识别并加载;
  2. 优化技能解析 :拓展 SkillRegistry,支持更多格式的元数据;
  3. 权限控制:限制部分技能仅子代理可加载,区分父子代理的技能权限。

五、本章一句话总结

技能系统的核心不是新增一个工具,而是把「各类专项知识」从常驻提示词中剥离,拆分为「轻量目录发现 + 完整内容按需加载」,精简上下文、节约 Token,让 Agent 架构更灵活易扩展。

相关推荐
Navicat中国1 小时前
Navicat 18 内置 AI 助手让数据库开发从繁琐操作迈向智能协作
大数据·人工智能·数据库开发
4SAPI1 小时前
多模型请求路由怎么设计?成本、速度和能力如何平衡
人工智能·python·gateway·php
魔乐社区2 小时前
翼芯・智算测试与适配优化专区,让国产算力“选得准、跑得通、用得好“
人工智能·开源·大模型
XCDoris3 小时前
郴州logo设计机构
大数据·人工智能·python
Goboy4 小时前
一个人带三个Agent,我用全新TRAE从0到1搭了个资讯周刊站
人工智能·trae
俊哥V4 小时前
每日 AI 研究简报 · 2026-10-19
人工智能·ai
AI_AGENT_DEV_AI4 小时前
AI英语教培智能体的开发
人工智能
物联网软硬件开发-轨物科技4 小时前
【技术白皮书】光伏清检一体化机器人系统解决方案技术白皮书
大数据·运维·网络·人工智能·机器人
海宇数据4 小时前
零信任架构实战:基于海宇网上踪迹欺诈行为识别构建自动化信贷资信评估网关
运维·人工智能·架构·自动化
冬奇Lab4 小时前
开源项目第242期:HyperFrames —— 写 HTML 就能渲染视频,专为 AI Agent 设计的确定性视频框架
人工智能·开源·音视频开发