ACP大模型应用开发之用 Skill 将能力固化为可复用流程整理(上)

🚄 前言

Memory 让 Agent 记住了你的偏好,但具体的工作方法每次还是要在对话里重新交代。Skill 就是解决这个问题的:把「在什么情况下,正确做法是什么」固化为可触发的专属流程。本系列上篇先走完从 Prompt 到可复用知识库的路径——这是写出高质量 Skill 的基础。


🍁 文章目标

你将学到:

  • 理解从「临时 Prompt」到「可复用 Skill」的演进逻辑(上篇:知识库阶段)
  • Skill 的结构:触发条件、工作流、知识库(下篇展开 AgentSkill)
  • 如何写出高质量的 Skill(五步编写法,下篇)
  • Skills-as-Code:让 Skill 进入版本控制和 CI/CD(下篇)
  • Skill 生态:社区共享与复用(下篇)

回顾:Agent 能力已完备,但「标准」在人脑子里

你先为 Agent 装上了工具调用、反思和记忆等单体能力。你大概试过让 AI 审代码、改文章、检查文档——反馈却常隔靴搔痒:要么太泛(「建议增加注释」),要么抓不住重点。

问题不在于 Agent 能力不够,而在于它不知道你的标准是什么。

问题引入:
假设你收到一个任务:审查一份同事写的技术教程。这份教程是 docs 目录下的《Python 数据分析实战》——一个完整的 Jupyter Notebook,包含近 300 个单元格,从环境准备、数据清洗、特征工程一直到可视化和综合业务分析。你最容易想到的方法就是:用你已有的 Agent,直接给它一句话。


1 用 Prompt 做教程审查

最容易想到的办法:给已有的 ReActAgent 装上 read_file,然后一句话交代任务。

Java 单测:通用 Prompt 审查

文件module-ai/src/test/java/com/baoma/ai/debug/CourseReviewGenericPromptAgentScopeTest.java

package com.baoma.ai.debug;

import com.baoma.ai.debug.support.CourseReviewAgentSupport;
import com.baoma.ai.debug.support.CourseReviewFileTools;
import com.baoma.ai.debug.support.CourseReviewScopeTestSupport;
import io.agentscope.core.ReActAgent;
import io.agentscope.core.message.Msg;
import io.agentscope.core.tool.Toolkit;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;

/** §1 用通用 Prompt 审查教程:反馈偏泛,难以命中团队细分标准。 */
class CourseReviewGenericPromptAgentScopeTest {

    @TempDir
    Path workspace;

    @BeforeEach
    void copySampleNotebook() throws Exception {
        Files.createDirectories(workspace);
        try (var in = getClass().getClassLoader()
            .getResourceAsStream("agentscope-skill-demo/sample-course.ipynb")) {
            Assertions.assertNotNull(in);
            Files.copy(in, workspace.resolve("sample-course.ipynb"));
        }
    }

    @Test
    @DisplayName("AgentScope:无审查标准时报告偏通用")
    void genericPromptReview() {
        String apiKey = CourseReviewScopeTestSupport.requireDashScopeApiKey();
        String modelName = CourseReviewScopeTestSupport.reviewModelName();
        CourseReviewScopeTestSupport.printApiKeyLoaded(apiKey);

        Toolkit toolkit = new Toolkit();
        toolkit.registerTool(new CourseReviewFileTools(workspace));

        ReActAgent agent = CourseReviewAgentSupport.createReviewer(
            apiKey,
            modelName,
            toolkit,
            "你是一个 AI 助手,能够帮助用户完成各种任务。",
            10);

        String notebook = workspace.resolve("sample-course.ipynb").toString();
        Msg user = Msg.builder()
            .textContent("帮我审查一下 " + notebook + ",看看质量怎么样、能不能发布。"
                + "你可以用 read_file 读取该文件。")
            .build();

        System.out.println("[user]: " + user.getTextContent());
        Msg reply = Objects.requireNonNull(agent.call(user).block(), "Agent 返回为空");
        String text = Objects.requireNonNullElse(reply.getTextContent(), "");
        System.out.println("\n=== 审查报告(通用 Prompt)===\n" + text);

        Assertions.assertFalse(text.isBlank());
        System.out.println("\n[观测] 若报告多为「补充注释/异常处理」等泛化建议,而未点出 openpyxl/众数/俏皮话,即符合课件预期。");
    }
}

运行:

mvn -pl module-ai test -Dtest=CourseReviewGenericPromptAgentScopeTest

运行后你会看到什么

Agent 往往给出「放之四海而皆准」的建议:补充注释、增加异常处理、优化图表等——换成任何教程都成立,是正确的废话

而人工抽查样例 Notebook 会发现它没提的、仅对本教程成立的问题:

问题类型 样例中的表现
代码跑不通 使用 pd.ExcelWriter(..., engine='openpyxl'),环境准备却未安装 openpyxl
说一套做一套 计划写「众数填充」,代码却 fillna('未知')
讲解风格 开篇小说式描写、俏皮话(「数据自带惊喜」)

团队真正在乎的标准(通用模型不知道)

  • 代码可执行性:依赖、路径、自上而下能否执行
  • 内容准确性:API 与文档一致、文字与代码一致
  • 学习曲线:渐进式编排、有无跳跃
  • 讲解风格:禁止俏皮话与多余情景描写

小贴士:审查任务要获得有针对性的反馈,关键不是措辞技巧,而是提供明确的审查标准

把标准写进 Prompt 仍会遇到的麻烦

即便写出很长的 Prompt(含 .ipynb JSON 结构说明、四项标准、输出格式),仍会面临:

痛点 说明
找不到 标准散落在聊天记录、文档或脑子里
每次重贴 新会话要复制整段 Prompt
口径不一 同事各写一版,结果不可比
版本混乱 上次补充的规则不知进了哪一版

根源:Prompt 是会话级的,对话结束知识就散。你需要的是可复用、基于文件的专业知识模块——写一次、进 Git、按需加载。


2 把审查经验沉淀下来

2.1 从聊天记录到独立文件

把审查标准保存为 course-review-single.md,Agent 用 read_file 按文件审查。

知识库文件(classpath)module-ai/src/test/resources/course-review/course-review-single.md

单测module-ai/src/test/java/com/baoma/ai/debug/CourseReviewWithSingleGuideAgentScopeTest.java

package com.baoma.ai.debug;

import com.baoma.ai.debug.support.CourseReviewAgentSupport;
import com.baoma.ai.debug.support.CourseReviewFileTools;
import com.baoma.ai.debug.support.CourseReviewKnowledgeBaseWriter;
import com.baoma.ai.debug.support.CourseReviewScopeTestSupport;
import io.agentscope.core.ReActAgent;
import io.agentscope.core.message.Msg;
import io.agentscope.core.tool.Toolkit;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;

class CourseReviewWithSingleGuideAgentScopeTest {

    @TempDir
    Path workspace;

    @BeforeEach
    void prepareWorkspace() throws Exception {
        CourseReviewKnowledgeBaseWriter.copyKnowledgeBaseTo(workspace);
        try (var in = getClass().getClassLoader()
            .getResourceAsStream("agentscope-skill-demo/sample-course.ipynb")) {
            Assertions.assertNotNull(in);
            Files.copy(in, workspace.resolve("sample-course.ipynb"));
        }
    }

    @Test
    @DisplayName("AgentScope:按 course-review-single.md 审查样例 Notebook")
    void reviewWithSingleGuideFile() {
        String apiKey = CourseReviewScopeTestSupport.requireDashScopeApiKey();
        String modelName = CourseReviewScopeTestSupport.reviewModelName();
        CourseReviewScopeTestSupport.printApiKeyLoaded(apiKey);

        Toolkit toolkit = new Toolkit();
        toolkit.registerTool(new CourseReviewFileTools(workspace));

        ReActAgent agent = CourseReviewAgentSupport.createReviewer(
            apiKey, modelName, toolkit,
            "你是教程审查员。必须先 read_file 读取审查标准与 Notebook,再按标准输出结构化报告。",
            16);

        Msg user = Msg.builder()
            .textContent("""
                按照 course-review/course-review-single.md 的标准,
                审查工作区中的 sample-course.ipynb。
                先用 read_file 读取标准与 Notebook;输出:状态/位置/说明。
                """)
            .build();

        Msg reply = Objects.requireNonNull(agent.call(user).block(), "Agent 返回为空");
        System.out.println(Objects.requireNonNullElse(reply.getTextContent(), ""));
        Assertions.assertFalse(Objects.requireNonNullElse(reply.getTextContent(), "").isBlank());
    }
}

运行:

mvn -pl module-ai test -Dtest=CourseReviewWithSingleGuideAgentScopeTest

核心用户消息:

按照 course-review/course-review-single.md 的标准,审查 sample-course.ipynb。
先用 read_file 读取标准与 Notebook。

收益:文件有固定位置、可 Git 管理、团队可共享。代价:单文件会继续膨胀。


2.2 从单文件到知识库目录

主文件保持精简,细节拆到子文件:

course-review/
├── README.md              # 审查流程入口
├── code-quality.md        # 代码可执行性
├── content-accuracy.md    # 内容准确性
├── style-guide.md         # 讲解风格正/反例
├── outdated-api.md        # pandas/numpy 过时 API
└── course-review-single.md

Java 侧用 CourseReviewKnowledgeBaseWriter 将 classpath 资源复制到单测工作区(对应 Python 课件「脚本生成目录」):

文件module-ai/src/test/java/com/baoma/ai/debug/support/CourseReviewKnowledgeBaseWriter.java

package com.baoma.ai.debug.support;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;

/** 将 classpath 下 course-review/ 复制到磁盘工作区。 */
public final class CourseReviewKnowledgeBaseWriter {

    private static final String[] KB_FILES = {
        "README.md", "code-quality.md", "content-accuracy.md",
        "style-guide.md", "outdated-api.md", "course-review-single.md"
    };

    public static Path copyKnowledgeBaseTo(Path targetDir) throws IOException {
        Path kbRoot = targetDir.resolve("course-review");
        Files.createDirectories(kbRoot);
        ClassLoader cl = CourseReviewKnowledgeBaseWriter.class.getClassLoader();
        for (String name : KB_FILES) {
            String cp = "course-review/" + name;
            try (InputStream in = cl.getResourceAsStream(cp)) {
                if (in == null) {
                    continue;
                }
                Files.copy(in, kbRoot.resolve(name), StandardCopyOption.REPLACE_EXISTING);
            }
        }
        return kbRoot;
    }
}

2.3 应对长文档:分段检查

Notebook 近 300 单元格时,不宜一次性塞进上下文(Lost in the Middle:中段信息易被忽略)。

方法一:提取目录 — Java 实现 NotebookTocExtractor(对应 Python extract_toc.py):

文件module-ai/src/test/java/com/baoma/ai/debug/NotebookTocExtractorTest.java

package com.baoma.ai.debug;

import com.baoma.ai.debug.support.NotebookTocExtractor;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

class NotebookTocExtractorTest {

    @Test
    @DisplayName("从样例 Notebook 提取 Markdown 标题目录")
    void extractTocFromSampleNotebook() throws Exception {
        String toc = NotebookTocExtractor.extractTocFromClasspath(
            "agentscope-skill-demo/sample-course.ipynb");

        System.out.println("=== Notebook 目录 ===\n" + toc);

        Assertions.assertTrue(toc.contains("[Cell 0]"));
        Assertions.assertTrue(toc.contains("环境准备") || toc.toLowerCase().contains("环境"));
    }
}

运行(无需 API Key):

mvn -pl module-ai test -Dtest=NotebookTocExtractorTest

示例输出:

[Cell 0] # Python 数据分析实战(样例)
[Cell 1] ## 环境准备

Agent 还可通过工具 extract_notebook_toc 在审查流程中调用(见 CourseReviewFileTools)。

方法二:jupytext 转 Markdown(课件建议):适合人工批量编辑;Java 单测未封装,可在 CI 中用 shell 调用 jupytext --to md


2.4 按需加载:不要一次塞全部

README.md 用表格索引子文件,Agent 按检查阶段 read_file

检查阶段 建议加载
代码质量 code-quality.md + outdated-api.md
讲解风格 style-guide.md
长 Notebook extract_notebook_toc,再按章节细读

Java 单测:知识库 + 按需加载

文件module-ai/src/test/java/com/baoma/ai/debug/CourseReviewKnowledgeBaseAgentScopeTest.java

package com.baoma.ai.debug;

import com.baoma.ai.debug.support.CourseReviewAgentSupport;
import com.baoma.ai.debug.support.CourseReviewFileTools;
import com.baoma.ai.debug.support.CourseReviewKnowledgeBaseWriter;
import com.baoma.ai.debug.support.CourseReviewScopeTestSupport;
import io.agentscope.core.ReActAgent;
import io.agentscope.core.message.Msg;
import io.agentscope.core.tool.Toolkit;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;

/** §2.2 + §2.4:审查知识库目录 + 按需加载子文件。 */
class CourseReviewKnowledgeBaseAgentScopeTest {

    @TempDir
    Path workspace;

    @BeforeEach
    void prepareWorkspace() throws Exception {
        CourseReviewKnowledgeBaseWriter.copyKnowledgeBaseTo(workspace);
        try (var in = getClass().getClassLoader()
            .getResourceAsStream("agentscope-skill-demo/sample-course.ipynb")) {
            Assertions.assertNotNull(in);
            Files.copy(in, workspace.resolve("sample-course.ipynb"));
        }
    }

    @Test
    @DisplayName("AgentScope:知识库按需加载审查")
    void reviewWithKnowledgeBaseOnDemand() {
        String apiKey = CourseReviewScopeTestSupport.requireDashScopeApiKey();
        String modelName = CourseReviewScopeTestSupport.reviewModelName();
        CourseReviewScopeTestSupport.printApiKeyLoaded(apiKey);

        Toolkit toolkit = new Toolkit();
        toolkit.registerTool(new CourseReviewFileTools(workspace));

        ReActAgent agent = CourseReviewAgentSupport.createReviewer(
            apiKey,
            modelName,
            toolkit,
            """
                你是教程审查员。工作流:
                1. read_file course-review/README.md;
                2. extract_notebook_toc sample-course.ipynb;
                3. 按需 read_file 子规则文件,不要一次读完所有文件;
                4. read_file sample-course.ipynb;
                5. 输出结构化报告。
                """,
            24);

        Msg user = Msg.builder()
            .textContent("请审查工作区 sample-course.ipynb,使用 course-review/ 知识库,按需加载规则。")
            .build();

        Msg reply = Objects.requireNonNull(agent.call(user).block(), "Agent 返回为空");
        String text = Objects.requireNonNullElse(reply.getTextContent(), "");
        System.out.println("\n=== 审查报告(知识库按需加载)===\n" + text);
        Assertions.assertFalse(text.isBlank());
    }
}

运行:

mvn -pl module-ai test -Dtest=CourseReviewKnowledgeBaseAgentScopeTest

2.5 对比:改造前 vs 改造后

维度 改造前(一次性 Prompt) 改造后(知识库目录)
存放 散落在聊天记录 固定路径,可版本管理
结构 所有内容混在一起 README 精简,子文件拆分
长 Notebook 易截断 / 漏中间段 先 TOC,再分段检查
加载 每次塞全部 按需 read_file
协作 一人一套标准 团队共享、可 PR 评审
flowchart LR
  A[用户:审查教程] --> B[ReActAgent]
  B --> C[read_file README]
  C --> D[extract_notebook_toc]
  D --> E[按需 read 子规则]
  E --> F[read_file .ipynb]
  F --> G[结构化审查报告]

共享工具类(Support)

审查相关单测共用以下类(完整源码见仓库):

文件 职责
CourseReviewFileTools.java read_fileextract_notebook_toc 工具
CourseReviewAgentSupport.java 创建 CourseReviewer ReActAgent
CourseReviewScopeTestSupport.java API Key、模型名
NotebookTocExtractor.java 解析 .ipynb 提取标题目录

与 Python 对照await agent(msg)agent.call(msg).block()toolkit.register_tool_functiontoolkit.registerTool(new CourseReviewFileTools(...))


©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容