?? 前言
上篇你已手動(dòng)完成 Skill 從設(shè)計(jì)到「雙 Agent」迭代的全過(guò)程。但 Skill 仍像躺在磁盤(pán)里的一組 Markdown——Agent 怎么發(fā)現(xiàn)它、加載它、在真實(shí)任務(wù)里調(diào)用它? 推廣到團(tuán)隊(duì)后,如何避免 「一個(gè)人的成功,十個(gè)人的混亂」?
4 讓你的 Agent 使用和生成 Skill
到目前為止,你手動(dòng)完成了 Skill 從設(shè)計(jì)到迭代的全過(guò)程:用審查經(jīng)驗(yàn)提煉檢查清單,封裝成 course-review/ 目錄,又通過(guò)「雙 Agent」模式反復(fù)打磨指令。但這個(gè) Skill 還只是 一組 Markdown 文件——Agent 怎么發(fā)現(xiàn)它、加載它、在真實(shí)任務(wù)中調(diào)用它?
4.1 在 AgentScope 中加載 Skill
好消息是,你不需要自己寫(xiě)完整的加載邏輯。AgentScope Java 用 ClasspathSkillRepository 解析 SKILL.md,SkillBox 注冊(cè) Skill 并生成 Agent 可見(jiàn)的提示。
Python 課件 vs AgentScope Java
| Python 課件 | AgentScope Java |
|---|---|
toolkit.register_agent_skill("course-review") |
skillBox.registerSkill(agentSkill) |
toolkit.get_agent_skill_prompt() |
skillBox.getSkillPrompt() |
| 按需讀子文件 |
load_skill_through_path(框架內(nèi)置)+ read_file
|
ReActAgent(..., toolkit=toolkit) |
ReActAgent.builder().skillBox(skillBox).toolkit(toolkit) |
準(zhǔn)備工作:SKILL.md + YAML frontmatter
框架要求入口文件名為 SKILL.md(不是 README.md),并從 YAML frontmatter 提取 name、description。上篇已完成升級(jí);course-review 入口示例:
---
name: course-review
description: |
審查課程內(nèi)容的技術(shù)準(zhǔn)確性、代碼正確性和教學(xué)質(zhì)量。當(dāng)用戶要求審查、審計(jì)或評(píng)估現(xiàn)有的課程或培訓(xùn)材料(含 Jupyter Notebook)時(shí)使用此技能。
owner: 教學(xué)設(shè)計(jì)團(tuán)隊(duì)
maintainers:
- course-quality-lead
version: 2.1.0
last-reviewed: 2026-01-15
---
# CourseReviewSkill
## 審查流程
1. 使用 extract_notebook_toc 提取目錄
2. 按需加載 code-quality.md、content-accuracy.md 等
3. 調(diào)用 validate_notebook_dependencies 做結(jié)構(gòu)化檢查
4. 按模板輸出審查報(bào)告
裝配 SkillBox(只注冊(cè) course-review)
package com.baoma.ai.debug.support;
import io.agentscope.core.ReActAgent;
import io.agentscope.core.memory.InMemoryMemory;
import io.agentscope.core.model.Model;
import io.agentscope.core.skill.AgentSkill;
import io.agentscope.core.skill.SkillBox;
import io.agentscope.core.skill.repository.ClasspathSkillRepository;
import io.agentscope.core.tool.Toolkit;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
public final class CourseReviewSkillSupport {
private CourseReviewSkillSupport() {
}
public static List<AgentSkill> loadSkillsFromClasspath(String skillsRoot) throws IOException {
try (ClasspathSkillRepository repo = new ClasspathSkillRepository(skillsRoot)) {
return repo.getAllSkills();
}
}
public static SkillBoxAndToolkit createSkillBox(Path workspace) throws IOException {
Toolkit toolkit = new Toolkit();
SkillBox skillBox = new SkillBox(toolkit);
AgentSkill courseReview = null;
for (AgentSkill skill : loadSkillsFromClasspath("skills")) {
if (!"course-review".equals(skill.getName())) {
continue;
}
skillBox.registerSkill(skill);
courseReview = skill;
}
if (courseReview != null) {
skillBox.registerSkill(courseReview);
// 注意:registration().tool() 每次賦值會(huì)覆蓋前一個(gè),不能鏈?zhǔn)綊靸蓚€(gè) @Tool 類(lèi)
toolkit.registerTool(new CourseReviewFileTools(workspace));
toolkit.registerTool(new CourseReviewValidateNotebookTool(workspace));
}
return new SkillBoxAndToolkit(skillBox, toolkit);
}
public static ReActAgent createSkillBoundReviewer(
String apiKey,
String modelName,
Toolkit toolkit,
SkillBox skillBox,
int maxIters) {
Model model = AgentScopeDashScopeModels.buildDashScopeChatModel(apiKey, modelName);
return ReActAgent.builder()
.name("CourseReviewer")
.sysPrompt("你是教程審查員。根據(jù)系統(tǒng)注入的 Skill 元數(shù)據(jù)與 load_skill 工具完成審查,輸出結(jié)構(gòu)化報(bào)告。")
.model(model)
.toolkit(toolkit)
.skillBox(skillBox)
.memory(new InMemoryMemory())
.maxIters(maxIters)
.build();
}
public record SkillBoxAndToolkit(SkillBox skillBox, Toolkit toolkit) {
}
}
具體路徑:

第一步:注冊(cè) Skill 并查看生成的 Prompt
package com.baoma.ai.debug;
import com.baoma.ai.debug.support.CourseReviewSkillSupport;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.nio.file.Path;
class CourseReviewSkillPromptAgentScopeTest {
@Test
@DisplayName("SkillBox.getSkillPrompt 包含 course-review 的 name 與 description")
void skillPromptContainsMetadata() throws Exception {
var box = CourseReviewSkillSupport.createSkillBox(Path.of("."));
String prompt = box.skillBox().getSkillPrompt();
System.out.println("=== Skill Prompt(節(jié)選)===\n");
System.out.println(prompt.length() > 2000
? prompt.substring(0, 2000) + "\n...(truncated)"
: prompt);
Assertions.assertTrue(prompt.contains("course-review") || prompt.contains("course_review"));
Assertions.assertFalse(prompt.contains("反模式清單") && prompt.length() < 500,
"完整 SKILL body 不應(yīng)全部塞進(jìn) prompt(漸進(jìn)式披露)");
}
}
執(zhí)行。
框架讀取 frontmatter,生成類(lèi)似結(jié)構(gòu)(節(jié)選):
<available_skills>
<skill>
<name>course-review</name>
<description>審查課程內(nèi)容的技術(shù)準(zhǔn)確性……</description>
<owner>教學(xué)設(shè)計(jì)團(tuán)隊(duì)</owner>
<version>2.1.0</version>
</skill>
</available_skills>
提示告訴 Agent「這里有一個(gè) Skill,要用時(shí)再讀詳情」——而不是把整個(gè) SKILL.md 和所有子文件塞進(jìn) system prompt。
第二步:集成 ReActAgent 并執(zhí)行審查
package com.baoma.ai.debug;
import com.baoma.ai.debug.support.CourseReviewScopeTestSupport;
import com.baoma.ai.debug.support.CourseReviewSkillSupport;
import io.agentscope.core.ReActAgent;
import io.agentscope.core.message.Msg;
import io.agentscope.core.skill.SkillBox;
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 CourseReviewSkillBoxAgentScopeTest {
@TempDir
Path workspace;
@BeforeEach
void prepareWorkspace() throws Exception {
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:SkillBox 加載 course-review 并審查 Notebook")
void skillBoxCourseReview() throws Exception {
String apiKey = CourseReviewScopeTestSupport.requireDashScopeApiKey();
String modelName = CourseReviewScopeTestSupport.reviewModelName();
CourseReviewScopeTestSupport.printApiKeyLoaded(apiKey);
var box = CourseReviewSkillSupport.createSkillBox(workspace);
SkillBox skillBox = box.skillBox();
System.out.println("=== Skill 注入 Prompt 預(yù)覽(節(jié)選)===\n");
String skillPrompt = skillBox.getSkillPrompt();
System.out.println(skillPrompt.length() > 1200
? skillPrompt.substring(0, 1200) + "\n...(truncated)"
: skillPrompt);
ReActAgent agent = CourseReviewSkillSupport.createSkillBoundReviewer(
apiKey, modelName, box.toolkit(), skillBox, 28);
Msg user = Msg.builder()
.textContent("""
請(qǐng)審查工作區(qū)中的 sample-course.ipynb,評(píng)估能否發(fā)布。
使用 course-review 技能;按需加載子規(guī)則文件;輸出結(jié)構(gòu)化審查報(bào)告。
""")
.build();
System.out.println("\n[user]: " + user.getTextContent());
Msg reply = Objects.requireNonNull(agent.call(user).block(), "Agent 返回為空");
String text = Objects.requireNonNullElse(reply.getTextContent(), "");
System.out.println("\n=== 審查報(bào)告(SkillBox)===\n" + text);
Assertions.assertFalse(text.isBlank());
}
}
執(zhí)行以上java
這次 Agent 不應(yīng)再給出「整體不錯(cuò),建議發(fā)布」的敷衍結(jié)論。它會(huì)按需加載 code-quality.md、content-accuracy.md、style-guide.md,并調(diào)用 validate_notebook_dependencies,點(diǎn)出 openpyxl 依賴(lài)、眾數(shù)填充不一致、風(fēng)格問(wèn)題等。
三個(gè)設(shè)計(jì)決策
為什么只注入 description 而非全文?
上下文窗口是公共資源??蚣苤话?description(約數(shù)十 token)注入可見(jiàn)提示;工作流程、反模式清單等內(nèi)容,由 Agent 決定使用 Skill 后再通過(guò)load_skill/read_file按需加載。為什么 Agent 需要文件讀取工具?
Agent 先從 description 判斷是否需要這個(gè) Skill,再讀SKILL.md獲取詳細(xì)指令,需要更細(xì)節(jié)時(shí)再讀子文件——漸進(jìn)式披露在框架層的落地。為什么 description 字段的質(zhì)量關(guān)鍵?
當(dāng) Agent 裝備了多個(gè) Skill 時(shí),它根據(jù) description 判斷當(dāng)前任務(wù)應(yīng)該使用哪一個(gè)。模糊描述(如「處理課程相關(guān)任務(wù)」)會(huì)導(dǎo)致匹配失??;具體描述(說(shuō)明做什么、什么時(shí)候觸發(fā))才能確保正確激活。
4.2 讓 Agent 自動(dòng)生成 Skill
手動(dòng)編寫(xiě) Skill 是必要的學(xué)習(xí)過(guò)程。熟練之后,可以讓 Agent 輔助完成初稿:本倉(cāng)庫(kù)提供 skill-creator 元 Skill(保留意圖捕獲、SKILL.md 規(guī)范、漸進(jìn)式披露;去掉與 Java 環(huán)境無(wú)關(guān)的 CLI 鏈)。
元 Skill:skill-creator.md 的具體內(nèi)容
---
name: skill-creator
description: 幫助用戶創(chuàng)建新的 Agent Skill,包括需求分析、SKILL.md 編寫(xiě)和結(jié)構(gòu)設(shè)計(jì)。當(dāng)用戶想要從零創(chuàng)建一個(gè) Skill、改進(jìn)現(xiàn)有 Skill 的結(jié)構(gòu)或內(nèi)容時(shí)使用。
---
# Skill Creator
一個(gè)幫助你創(chuàng)建高質(zhì)量 Agent Skill 的工具。
創(chuàng)建 Skill 的核心流程:
1. 明確 Skill 的目標(biāo)和觸發(fā)場(chǎng)景
2. 收集需求細(xì)節(jié)和邊界條件
3. 編寫(xiě) SKILL.md 初稿
4. 創(chuàng)建測(cè)試用例驗(yàn)證效果
5. 根據(jù)反饋迭代改進(jìn)
你的職責(zé)是判斷用戶當(dāng)前處于哪個(gè)階段,然后幫助他們推進(jìn)。如果用戶已經(jīng)有了初稿,可以直接進(jìn)入改進(jìn)階段。
---
## 創(chuàng)建 Skill
### Capture Intent(捕獲意圖)
首先理解用戶的意圖。當(dāng)前對(duì)話中可能已經(jīng)包含了用戶想要封裝的工作流(比如他們說(shuō)"把這個(gè)流程變成一個(gè) Skill")。如果是這樣,先從對(duì)話歷史中提取信息——使用了哪些工具、步驟順序、用戶做了哪些修正、觀察到的輸入輸出格式。用戶可能需要補(bǔ)充細(xì)節(jié),確認(rèn)后再進(jìn)入下一步。
1. 這個(gè) Skill 要讓 Agent 完成什么任務(wù)?
2. 什么情況下應(yīng)該觸發(fā)這個(gè) Skill?(哪些用戶提問(wèn)/場(chǎng)景)
3. 期望的輸出格式是什么?
4. 是否需要設(shè)置測(cè)試用例來(lái)驗(yàn)證 Skill 的效果?有客觀可驗(yàn)證輸出的 Skill(文件轉(zhuǎn)換、數(shù)據(jù)提取、代碼生成、固定流程步驟)適合測(cè)試用例;主觀輸出的 Skill(寫(xiě)作風(fēng)格、設(shè)計(jì))通常不需要。根據(jù) Skill 類(lèi)型建議合適的默認(rèn)方案,但讓用戶決定。
### Interview and Research(訪談與調(diào)研)
主動(dòng)詢(xún)問(wèn)邊界情況、輸入輸出格式、示例文件、成功標(biāo)準(zhǔn)和依賴(lài)關(guān)系。在搞清楚這些之前,不要急著寫(xiě)測(cè)試用例。
如果有可用的工具(搜索文檔、查找類(lèi)似 Skill、查閱最佳實(shí)踐),先做調(diào)研,帶著充分的上下文來(lái)減輕用戶的負(fù)擔(dān)。
### Write the SKILL.md(編寫(xiě) SKILL.md)
基于用戶訪談,填寫(xiě)以下組成部分:
- **name**:Skill 標(biāo)識(shí)符
- **description**:觸發(fā)條件和功能描述。這是主要的觸發(fā)機(jī)制——既要說(shuō)明 Skill 做什么,也要說(shuō)明何時(shí)使用。所有"何時(shí)使用"的信息都放在這里,而不是放在正文中。注意:為了確保觸發(fā)率,description 應(yīng)該適度"積極"。例如,與其寫(xiě)"構(gòu)建數(shù)據(jù)可視化面板",不如寫(xiě)"構(gòu)建數(shù)據(jù)可視化面板。當(dāng)用戶提到儀表盤(pán)、數(shù)據(jù)展示、指標(biāo)監(jiān)控,或想要展示任何類(lèi)型的數(shù)據(jù)時(shí)都應(yīng)使用此 Skill,即使他們沒(méi)有明確要求'面板'。"
- **compatibility**:所需工具、依賴(lài)(可選,很少需要)
- **正文內(nèi)容**
---
## Skill 編寫(xiě)指南
### Skill 的結(jié)構(gòu)
skill-name/
├── SKILL.md (必需)
│ ├── YAML frontmatter (name, description 必填)
│ └── Markdown 指令
└── Bundled Resources (可選)
├── scripts/ - 用于確定性/重復(fù)性任務(wù)的可執(zhí)行代碼
├── references/ - 按需加載到上下文中的文檔
└── assets/ - 輸出中使用的文件(模板、圖標(biāo)、字體)
### Progressive Disclosure(漸進(jìn)式披露)
Skill 使用三級(jí)加載系統(tǒng):
1. **元數(shù)據(jù)**(name + description)- 始終在上下文中(約 100 詞)
2. **SKILL.md 正文** - Skill 觸發(fā)時(shí)加載(理想情況 <500 行)
3. **捆綁資源** - 按需加載(不限大小,腳本可以在不加載的情況下執(zhí)行)
這些字?jǐn)?shù)是近似值,如果需要可以更長(zhǎng)。
**關(guān)鍵模式:**
- 保持 SKILL.md 在 500 行以?xún)?nèi);如果接近這個(gè)限制,添加層級(jí)結(jié)構(gòu)并指明下一步應(yīng)該閱讀哪些文件
- 在 SKILL.md 中清晰引用文件,并說(shuō)明何時(shí)應(yīng)該讀取它們
- 對(duì)于大型參考文件(>300 行),包含目錄
**領(lǐng)域組織**:當(dāng) Skill 支持多個(gè)領(lǐng)域/框架時(shí),按變體組織:
cloud-deploy/
├── SKILL.md (工作流 + 選擇邏輯)
└── references/
├── aws.md
├── gcp.md
└── azure.md
Agent 只讀取相關(guān)的參考文件。
### 安全原則
Skill 不得包含惡意軟件、利用代碼或任何可能危害系統(tǒng)安全的內(nèi)容。Skill 的內(nèi)容在被描述時(shí)不應(yīng)讓用戶感到意外。不要?jiǎng)?chuàng)建誤導(dǎo)性的 Skill 或旨在促進(jìn)未授權(quán)訪問(wèn)的 Skill。
### 編寫(xiě)模式
在指令中優(yōu)先使用祈使句。
**定義輸出格式**——可以這樣做:
markdown
## 報(bào)告結(jié)構(gòu)
始終使用這個(gè)模板:
# [標(biāo)題]
## 摘要
## 關(guān)鍵發(fā)現(xiàn)
## 建議
**示例模式**——包含示例很有用:
markdown
## Commit 消息格式
**示例 1:**
Input: Added user authentication with JWT tokens
Output: feat(auth): implement JWT-based authentication
### 編寫(xiě)風(fēng)格
盡量向模型解釋"為什么"事情很重要,而不是堆砌強(qiáng)硬的 MUST/NEVER。利用心智理論,讓 Skill 通用化而非局限于特定示例。先寫(xiě)初稿,然后用新鮮的眼光審視并改進(jìn)。
---
## 迭代改進(jìn)
### 如何思考改進(jìn)方向
1. **從反饋中泛化。** 我們創(chuàng)建的 Skill 將被在各種不同的場(chǎng)景中使用。不要針對(duì)測(cè)試用例過(guò)度擬合,而是理解用戶反饋背后的通用模式。與其添加僵硬的限制條件,不如嘗試不同的表達(dá)方式或工作模式。
2. **保持指令精簡(jiǎn)。** 移除沒(méi)有發(fā)揮作用的內(nèi)容。如果 Agent 在執(zhí)行 Skill 時(shí)浪費(fèi)時(shí)間在無(wú)用的步驟上,考慮刪除導(dǎo)致這種行為的指令部分。
3. **解釋為什么。** 盡量解釋每條指令背后的原因。當(dāng)前的大模型很聰明,給出好的上下文后能超越機(jī)械執(zhí)行。如果你發(fā)現(xiàn)自己在寫(xiě) ALWAYS 或 NEVER(全大寫(xiě)),這是一個(gè)信號(hào)——重新表述,解釋推理過(guò)程,讓模型理解為什么這很重要。
4. **尋找重復(fù)工作。** 如果多個(gè)測(cè)試用例中 Agent 都獨(dú)立編寫(xiě)了類(lèi)似的輔助腳本,這強(qiáng)烈暗示 Skill 應(yīng)該捆綁該腳本。寫(xiě)一次,放在 `scripts/` 中,讓 Skill 指引 Agent 使用它。
---
## 測(cè)試用例
編寫(xiě) Skill 初稿后,構(gòu)思 2-3 個(gè)真實(shí)的測(cè)試提示——真正的用戶會(huì)實(shí)際說(shuō)的話。與用戶分享:"這是我想測(cè)試的幾個(gè)用例,看起來(lái)合理嗎?需要補(bǔ)充嗎?"
好的測(cè)試用例是具體的、有細(xì)節(jié)的,而不是抽象的請(qǐng)求。
不好:`"格式化這個(gè)數(shù)據(jù)"`, `"從 PDF 中提取文本"`
好:`"我老板剛發(fā)了一個(gè) xlsx 文件給我(在我的下載目錄里,叫'Q4銷(xiāo)售數(shù)據(jù)_最終版v2.xlsx'),她讓我加一列利潤(rùn)率百分比。營(yíng)收在 C 列,成本在 D 列。"
write_file 工具(與 read_file 對(duì)稱(chēng))
package com.baoma.ai.debug.support;
import io.agentscope.core.tool.Tool;
import io.agentscope.core.tool.ToolParam;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class CourseReviewWriteFileTools {
private final Path workspaceRoot;
public CourseReviewWriteFileTools(Path workspaceRoot) {
this.workspaceRoot = workspaceRoot;
}
@Tool(name = "write_file", description = "將內(nèi)容寫(xiě)入工作區(qū)相對(duì)路徑;目錄不存在則自動(dòng)創(chuàng)建")
public String writeFile(
@ToolParam(name = "path", description = "相對(duì)路徑,如 skills/api-doc-review/SKILL.md")
String path,
@ToolParam(name = "content", description = "文件全文")
String content) throws IOException {
Path target = workspaceRoot.resolve(path).normalize();
if (!target.startsWith(workspaceRoot.normalize())) {
return "錯(cuò)誤:路徑越界";
}
if (target.getParent() != null) {
Files.createDirectories(target.getParent());
}
Files.writeString(target, content);
return "已寫(xiě)入 " + path;
}
}
裝配 skill-creator + 創(chuàng)建 Agent
package com.baoma.ai.debug.support;
import io.agentscope.core.ReActAgent;
import io.agentscope.core.memory.InMemoryMemory;
import io.agentscope.core.model.Model;
import io.agentscope.core.skill.AgentSkill;
import io.agentscope.core.skill.SkillBox;
import io.agentscope.core.skill.repository.ClasspathSkillRepository;
import io.agentscope.core.tool.Toolkit;
import java.io.IOException;
import java.nio.file.Path;
public final class SkillCreatorSupport {
private SkillCreatorSupport() {
}
public static SkillBoxAndToolkit createCreatorSkillBox(Path workspace) throws IOException {
Toolkit toolkit = new Toolkit();
SkillBox skillBox = new SkillBox(toolkit);
AgentSkill creator = null;
try (ClasspathSkillRepository repo = new ClasspathSkillRepository("skills")) {
for (AgentSkill skill : repo.getAllSkills()) {
if ("skill-creator".equals(skill.getName())) {
creator = skill;
skillBox.registerSkill(skill);
}
}
}
if (creator != null) {
skillBox.registerSkill(creator);
toolkit.registerTool(new CourseReviewFileTools(workspace));
toolkit.registerTool(new CourseReviewWriteFileTools(workspace));
}
return new SkillBoxAndToolkit(skillBox, toolkit);
}
public static ReActAgent createSkillCreatorAgent(
String apiKey,
String modelName,
Toolkit toolkit,
SkillBox skillBox) {
Model model = AgentScopeDashScopeModels.buildDashScopeChatModel(apiKey, modelName);
return ReActAgent.builder()
.name("SkillCreator")
.sysPrompt("你是一個(gè) Agent Skill 設(shè)計(jì)師。遵循 skill-creator 技能指引生成符合規(guī)范的 Skill 目錄。")
.model(model)
.toolkit(toolkit)
.skillBox(skillBox)
.memory(new InMemoryMemory())
.maxIters(16)
.build();
}
public record SkillBoxAndToolkit(SkillBox skillBox, Toolkit toolkit) {
}
}
加載 skill-creator 并生成新 Skill
package com.baoma.ai.debug;
import com.baoma.ai.debug.support.CourseReviewScopeTestSupport;
import com.baoma.ai.debug.support.SkillCreatorSupport;
import io.agentscope.core.ReActAgent;
import io.agentscope.core.message.Msg;
import org.junit.jupiter.api.Assertions;
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 SkillCreatorAgentScopeTest {
@TempDir
Path workspace;
@Test
@DisplayName("AgentScope:skill-creator 生成 api-doc-review Skill 目錄")
void generateApiDocReviewSkill() throws Exception {
String apiKey = CourseReviewScopeTestSupport.requireDashScopeApiKey();
String modelName = CourseReviewScopeTestSupport.reviewModelName();
CourseReviewScopeTestSupport.printApiKeyLoaded(apiKey);
var box = SkillCreatorSupport.createCreatorSkillBox(workspace);
ReActAgent creator = SkillCreatorSupport.createSkillCreatorAgent(
apiKey, modelName, box.toolkit(), box.skillBox());
Msg user = Msg.builder()
.textContent("""
幫我創(chuàng)建一個(gè)審核 REST API 文檔的 Skill,目錄名 api-doc-review。
要檢查:1. 所有端點(diǎn)是否有示例請(qǐng)求和響應(yīng);
2. 錯(cuò)誤碼是否有說(shuō)明;3. 參數(shù)是否有類(lèi)型標(biāo)注。
請(qǐng)用 write_file 寫(xiě)入 skills/api-doc-review/SKILL.md 及必要的子文件。
""")
.build();
System.out.println("[user]: " + user.getTextContent());
Msg reply = Objects.requireNonNull(creator.call(user).block());
System.out.println("\n=== Skill Creator 回復(fù) ===\n" + reply.getTextContent());
Path skillMd = workspace.resolve("skills/api-doc-review/SKILL.md");
if (Files.isRegularFile(skillMd)) {
System.out.println("\n=== 已生成 SKILL.md ===\n" + Files.readString(skillMd));
Assertions.assertTrue(Files.readString(skillMd).contains("api-doc-review")
|| Files.readString(skillMd).contains("API"));
} else {
System.out.println("\n[提示] 模型可能未調(diào)用 write_file;請(qǐng)檢查回復(fù)或重跑。");
Assertions.assertFalse(Objects.requireNonNullElse(reply.getTextContent(), "").isBlank());
}
}
}
執(zhí)行上面的最終方法。
Agent 可能生成完整的 api-doc-review/ 目錄;也可能只回復(fù)計(jì)劃而未調(diào)用 write_file——可多跑幾次。
這是加速,不是替代。 你仍需理解上篇五步編寫(xiě)法,才能判斷 Agent 生成的內(nèi)容是否合理。專(zhuān)家用 skill-creator 快速產(chǎn)出高質(zhì)量 Skill;不理解設(shè)計(jì)原則的新手,即使有元 Skill 的創(chuàng)建的skill也很難產(chǎn)出可用結(jié)果。
小貼士:生成的 Skill 初稿一定要配合上篇 「雙 Agent」評(píng)測(cè) + 本篇中的skill的規(guī)范。自動(dòng)生成只解決「寫(xiě)出來(lái)」;評(píng)測(cè)解決「寫(xiě)得對(duì)不對(duì)」。
4.3 Skill 市場(chǎng):復(fù)用社區(qū)的專(zhuān)家知識(shí)
前兩節(jié)介紹了注冊(cè)和生成自建 Skill。在更廣的 Coding Agent 生態(tài)中,社區(qū)已形成 Skill 共享平臺(tái)。
發(fā)現(xiàn) Skill
# 按關(guān)鍵詞搜索
npx skills find typescript
# 輸出示例:
# Install with npx skills add <owner/repo@skill>
# vercel-labs/agent-skills@typescript-best-practices
不帶參數(shù)運(yùn)行 npx skills find 可進(jìn)入交互式模糊搜索。找到感興趣的 Skill 后,在平臺(tái)詳情頁(yè)查看完整內(nèi)容與安全審計(jì)評(píng)級(jí)。
安裝和使用
npx skills add vercel-labs/agent-skills --skill vercel-react-best-practices
npx skills update
安裝后,Skill 就是一組 Markdown 文件,與上篇結(jié)構(gòu)一致。Java 側(cè)將目錄放入工程的 skills/ 資源,用 ClasspathSkillRepository(開(kāi)發(fā))或 FileSystemSkillRepository(部署)加載。
社區(qū) Skill 與自建 Skill 的分工
| 場(chǎng)景 | 推薦方式 |
|---|---|
| 通用技術(shù)最佳實(shí)踐(React 性能、代碼規(guī)范) | 社區(qū) Skill |
| 成熟的行業(yè)標(biāo)準(zhǔn)流程(安全審計(jì)、SEO) | 社區(qū) Skill |
| 企業(yè)內(nèi)部業(yè)務(wù)流程和質(zhì)量標(biāo)準(zhǔn) | 自建 Skill |
| 團(tuán)隊(duì)特有的代碼規(guī)范和架構(gòu)約定 | 自建 Skill |
| 領(lǐng)域?qū)<业碾[性知識(shí)(如課程審核) | 自建 Skill |
使用時(shí)的注意事項(xiàng)
- 避免 Skill 過(guò)多:過(guò)多 Skill 會(huì)撐滿 system prompt,稀釋核心指令
- 注意版本匹配:依賴(lài)的工具或框架更新后,Skill 內(nèi)命令可能過(guò)時(shí)
- 合理預(yù)期:Skill 提升的是下限(少犯低級(jí)錯(cuò)誤),不是無(wú)限抬高上限
4.4 本節(jié)回顧
本節(jié)圍繞 Skill 的「用起來(lái)」展開(kāi)三個(gè)層次:
| 層次 | 要點(diǎn) |
|---|---|
| 注冊(cè)與加載(§4.1) |
SkillBox + getSkillPrompt 只注入 description;Agent 按需讀詳細(xì)指令——漸進(jìn)式披露在框架層的落地 |
| 自動(dòng)生成(§4.2) | skill-creator 元 Skill + write_file 生成初稿;仍需評(píng)測(cè)驗(yàn)證 |
| 社區(qū)復(fù)用(§4.3) |
npx skills 發(fā)現(xiàn)/安裝;與自建 Skill 互補(bǔ) |
5 當(dāng) Skill 遇上團(tuán)隊(duì)
5.1 一個(gè)人的成功,十個(gè)人的混亂
你在 §4 中成功構(gòu)建了 course-review,把課程審核時(shí)間從 2 小時(shí)縮短到約 20 分鐘。主管很滿意,讓你把這個(gè) Skill 推廣給整個(gè)教學(xué)設(shè)計(jì)團(tuán)隊(duì)——10 個(gè)人。你把 SKILL.md 發(fā)到群里,附了一段簡(jiǎn)短的使用說(shuō)明。
一個(gè)月后,問(wèn)題接踵而至。
版本混亂:你根據(jù)最新的課程標(biāo)準(zhǔn)更新了評(píng)分權(quán)重,但有 3 位同事還在用舊版 Skill。同一門(mén)課程被審出矛盾的結(jié)論——你的報(bào)告說(shuō)「結(jié)構(gòu)合理」,同事的報(bào)告說(shuō)「章節(jié)劃分不當(dāng)」。團(tuán)隊(duì)不得不花半天時(shí)間排查,才發(fā)現(xiàn)是 Skill 版本不一致。
誤修改:實(shí)習(xí)生覺(jué)得 SKILL.md 中「避免使用 print() 進(jìn)行調(diào)試」這條規(guī)則太嚴(yán)格,自行改成了「允許在開(kāi)發(fā)階段使用 print()」。結(jié)果 Agent 開(kāi)始把所有 print() 調(diào)試代碼標(biāo)記為「符合規(guī)范」,三周后才被發(fā)現(xiàn)。
知識(shí)斷檔:你休假兩周,團(tuán)隊(duì)遇到 3 個(gè)審核邊界問(wèn)題——「代碼示例中的注釋算不算教學(xué)內(nèi)容?」「引用外部 API 文檔需不需要檢查鏈接有效性?」——沒(méi)人能回答,因?yàn)檫@些判斷邏輯只存在于你的經(jīng)驗(yàn)中,沒(méi)有寫(xiě)進(jìn) Skill。
這三個(gè)問(wèn)題有一個(gè)共同點(diǎn):CourseReviewSkill 作為一個(gè)人的作品運(yùn)轉(zhuǎn)良好,但它沒(méi)有為團(tuán)隊(duì)協(xié)作而設(shè)計(jì)。
5.2 樸素修補(bǔ):為什么「群里發(fā)一下」行不通
面對(duì)這些問(wèn)題,你可能會(huì)想到幾個(gè)直覺(jué)性的修補(bǔ)方案。
統(tǒng)一存放位置 — 把 Skill 放到共享網(wǎng)盤(pán)。但誰(shuí)有權(quán)修改?如果 10 個(gè)人都能改,就等于沒(méi)人負(fù)責(zé)。
發(fā)一份使用說(shuō)明文檔 — 文檔和 Skill 分離,兩周后 Skill 迭代了,文檔還停留在舊版本。
由你一個(gè)人負(fù)責(zé)所有修改 — 你成了瓶頸,且你不是所有審核領(lǐng)域的專(zhuān)家。
這些修補(bǔ)失敗的根本原因在于:Skill 不是普通配置文件,而是封裝了專(zhuān)家知識(shí)的可執(zhí)行資產(chǎn)。管理它需要明確的所有權(quán)和工程化的生命周期。
換一個(gè)角度:Skill 是能力放大器——資深設(shè)計(jì)師一周審 10 門(mén)課,封裝成 Skill 后,10 名初級(jí)成員都能調(diào)用。但要讓放大器在團(tuán)隊(duì)中穩(wěn)定運(yùn)轉(zhuǎn),需要解決 所有權(quán)、協(xié)作流程、質(zhì)量保障。

5.3 領(lǐng)域團(tuán)隊(duì)所有權(quán):讓最懂業(yè)務(wù)的人管 Skill
誰(shuí)最有能力定義和維護(hù) course-review?教學(xué)設(shè)計(jì)團(tuán)隊(duì)——他們最了解「什么樣的課程才算好」。
新的分工模式
| 角色 | 職責(zé) |
|---|---|
| 專(zhuān)家 (Expert) | 定義標(biāo)準(zhǔn)、封裝 Skill、審核變更 |
| 團(tuán)隊(duì)成員 (Member) | 調(diào)用 Skill、反饋問(wèn)題、提交改進(jìn)建議 |
專(zhuān)家從「親自執(zhí)行每一次審核」轉(zhuǎn)變?yōu)椤妇S護(hù)審核標(biāo)準(zhǔn)」,影響力從線性增長(zhǎng)變?yōu)榭蓮?fù)制的規(guī)?;?。
不同領(lǐng)域的 Skill 由對(duì)應(yīng)業(yè)務(wù)團(tuán)隊(duì)負(fù)責(zé)
| 領(lǐng)域 | 所有權(quán)團(tuán)隊(duì) | 維護(hù)的 Skill 示例 |
|---|---|---|
| 課程質(zhì)量 | 教學(xué)設(shè)計(jì)團(tuán)隊(duì) | CourseReviewSkill |
| 數(shù)據(jù)分析 | 數(shù)據(jù)團(tuán)隊(duì) | DataCleaningSkill |
| 安全合規(guī) | 安全團(tuán)隊(duì) | CodeSecurityScanSkill |
業(yè)務(wù)專(zhuān)家 + 技術(shù)人員協(xié)作
| 角色 | 職責(zé) |
|---|---|
| 業(yè)務(wù)專(zhuān)家 (SOP Owner) | 制定流程標(biāo)準(zhǔn)與邊界條件 |
| 技術(shù)人員 (Agent Architect) | Skill 結(jié)構(gòu)調(diào)優(yōu)與工具掛載 |
協(xié)作流程分三步:
- 1、業(yè)務(wù)專(zhuān)家用自然語(yǔ)言描述"一個(gè)優(yōu)秀的審核員會(huì)怎么做"
- 2、技術(shù)人員將其轉(zhuǎn)化為結(jié)構(gòu)化的 Skill 格式
- 3、雙方一起測(cè)試、迭代、根據(jù) Agent 實(shí)際產(chǎn)出調(diào)整指令
為了在 Skill 文件中明確所有權(quán)信息,可以在 YAML frontmatter 中添加管理元數(shù)據(jù):
在 frontmatter 中明確所有權(quán)
---
name: course-review
description: |
審核課程內(nèi)容的質(zhì)量和結(jié)構(gòu)完整性。當(dāng)用戶要求檢查、審核或評(píng)估課程材料時(shí)觸發(fā)。
owner: 教學(xué)設(shè)計(jì)團(tuán)隊(duì)
maintainers:
- zhang-wei
- li-na
version: 2.1.0
last-reviewed: 2026-01-15
---
這些字段讓任何人打開(kāi)文件就能知道:有問(wèn)題找誰(shuí)、當(dāng)前是什么版本、上次審查是什么時(shí)候。
小貼士:GitHub 可配置 CODEOWNERS,例如
/skills/course-review/ @teaching-design-team,對(duì)該路徑的修改自動(dòng)要求教學(xué)設(shè)計(jì)團(tuán)隊(duì)審批。
/skills/course-review/ @org/course-quality
/skills/skill-creator/ @org/agent-platform
5.4 Skills-as-Code:用工程方法管理 Skill 資產(chǎn)
所有權(quán)明確后,下一個(gè)問(wèn)題是:團(tuán)隊(duì)如何協(xié)作迭代 Skill?答案是把 Skill 當(dāng)作代碼來(lái)管理——version control、code review、CI/CD。這些軟件工程的成熟實(shí)踐同樣適用于 Skill。
Skills-as-Code 生命周期

| 階段 | 說(shuō)明 |
|---|---|
| 1、本地迭代 | 維護(hù)者在本地修改 Skill,用幾個(gè)代表性案例快速驗(yàn)證效果 |
| 2、PR / Review | 提交 Pull Request,由團(tuán)隊(duì)其他成員(尤其是業(yè)務(wù)專(zhuān)家)審查變更 |
| 3、CI 評(píng)測(cè) | 自動(dòng)化流水線運(yùn)行評(píng)測(cè)用例,確保修改沒(méi)有引入回歸問(wèn)題 |
| 4、部署上線 | 合并到主分支后,所有使用該 Skill 的 Agent 自動(dòng)獲取最新版本 |
| 5、反饋收集 | 跟蹤使用情況,驅(qū)動(dòng)改進(jìn) |
將 classpath 中的 skills 部署到磁盤(pán)(模擬 CI 構(gòu)建拷貝)
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;
public final class SkillsAsCodeDeploySupport {
private SkillsAsCodeDeploySupport() {
}
public static Path deployClasspathSkillsTo(Path targetSkillsRoot) throws IOException {
Files.createDirectories(targetSkillsRoot);
ClassLoader cl = SkillsAsCodeDeploySupport.class.getClassLoader();
deploySkillDir(cl, targetSkillsRoot, "course-review");
deploySkillDir(cl, targetSkillsRoot, "skill-creator");
return targetSkillsRoot;
}
private static void deploySkillDir(ClassLoader cl, Path targetRoot, String skillName) throws IOException {
Path skillDir = targetRoot.resolve(skillName);
Files.createDirectories(skillDir);
String prefix = "skills/" + skillName + "/";
copyResource(cl, prefix + "SKILL.md", skillDir.resolve("SKILL.md"));
copyIfExists(cl, prefix + "code-quality.md", skillDir.resolve("code-quality.md"));
copyIfExists(cl, prefix + "content-accuracy.md", skillDir.resolve("content-accuracy.md"));
copyIfExists(cl, prefix + "style-guide.md", skillDir.resolve("style-guide.md"));
copyIfExists(cl, prefix + "outdated-api.md", skillDir.resolve("outdated-api.md"));
}
private static void copyResource(ClassLoader cl, String cp, Path dest) throws IOException {
try (InputStream in = cl.getResourceAsStream(cp)) {
if (in == null) {
throw new IOException("classpath 資源不存在: " + cp);
}
Files.copy(in, dest, StandardCopyOption.REPLACE_EXISTING);
}
}
private static void copyIfExists(ClassLoader cl, String cp, Path dest) throws IOException {
try (InputStream in = cl.getResourceAsStream(cp)) {
if (in != null) {
Files.copy(in, dest, StandardCopyOption.REPLACE_EXISTING);
}
}
}
public static void copyDirectoryFromClasspath(Path targetSkillsRoot) throws IOException {
deployClasspathSkillsTo(targetSkillsRoot);
}
}
從磁盤(pán)加載:FileSystemSkillRepository
package com.baoma.ai.debug;
import com.baoma.ai.debug.support.SkillsAsCodeDeploySupport;
import io.agentscope.core.skill.AgentSkill;
import io.agentscope.core.skill.repository.FileSystemSkillRepository;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Path;
class FileSystemSkillRepositoryAgentScopeTest {
@TempDir
Path skillsRoot;
@Test
@DisplayName("FileSystemSkillRepository 從磁盤(pán)加載 course-review")
void loadSkillFromFilesystem() throws Exception {
SkillsAsCodeDeploySupport.deployClasspathSkillsTo(skillsRoot);
try (FileSystemSkillRepository repo = new FileSystemSkillRepository(skillsRoot)) {
Assertions.assertTrue(repo.skillExists("course-review"));
AgentSkill skill = repo.getSkill("course-review");
Assertions.assertEquals("course-review", skill.getName());
Assertions.assertTrue(skill.getDescription().contains("審查"));
Assertions.assertTrue(skill.getSkillContent().contains("審查流程"));
System.out.println("Repository source: " + repo.getSource());
System.out.println("Skill version metadata: " + skill.getMetadataValue("version"));
}
}
}
frontmatter CI 校驗(yàn)器
package com.baoma.ai.debug.support;
import io.agentscope.core.skill.util.MarkdownSkillParser;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
public final class SkillFrontmatterValidator {
private static final Set<String> REQUIRED_FIELDS = Set.of(
"name", "description", "owner", "version", "last-reviewed"
);
private SkillFrontmatterValidator() {
}
public static List<String> validateSkillFile(Path skillMdPath) throws IOException {
List<String> errors = new ArrayList<>();
if (!Files.isRegularFile(skillMdPath)) {
errors.add("文件不存在: " + skillMdPath);
return errors;
}
String content = Files.readString(skillMdPath);
var parsed = MarkdownSkillParser.parse(content);
Map<String, Object> meta = parsed.getMetadata();
for (String field : REQUIRED_FIELDS) {
if (!meta.containsKey(field) || meta.get(field) == null
|| meta.get(field).toString().isBlank()) {
errors.add("缺少必需字段: " + field);
}
}
if (parsed.getContent() == null || parsed.getContent().isBlank()) {
errors.add("Markdown body 為空");
}
return errors;
}
}
單測(cè):PR 階段 frontmatter 門(mén)禁
package com.baoma.ai.debug;
import com.baoma.ai.debug.support.SkillFrontmatterValidator;
import com.baoma.ai.debug.support.SkillsAsCodeDeploySupport;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Path;
import java.util.List;
class SkillFrontmatterValidationTest {
@TempDir
Path skillsRoot;
@Test
@DisplayName("course-review SKILL.md 通過(guò) frontmatter 校驗(yàn)")
void courseReviewSkillPassesValidation() throws Exception {
SkillsAsCodeDeploySupport.deployClasspathSkillsTo(skillsRoot);
Path skillMd = skillsRoot.resolve("course-review/SKILL.md");
List<String> errors = SkillFrontmatterValidator.validateSkillFile(skillMd);
System.out.println("校驗(yàn)結(jié)果: " + (errors.isEmpty() ? "通過(guò)" : errors));
Assertions.assertTrue(errors.isEmpty(), () -> String.join("; ", errors));
}
}
GitHub Actions 示例(Maven 評(píng)測(cè))
name: validate-skills
on:
pull_request:
paths:
- 'module-ai/src/test/resources/skills/**'
- 'skills/**'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- name: Check SKILL.md metadata
run: mvn -pl module-ai test -Dtest=SkillFrontmatterValidationTest
- name: Run skill evaluation
env:
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
run: mvn -pl module-ai test -Dtest=CourseReviewSkillBoxAgentScopeTest
廢棄與清理(建議每季度)
- 過(guò)去 3 個(gè)月未被調(diào)用 → 考慮歸檔
- 對(duì)應(yīng)的業(yè)務(wù)流程已取消 → 立即刪除
- 與其他 Skill 功能重疊 → 合并精簡(jiǎn)
核心度量
| 指標(biāo) | 定義 | 意義 |
|---|---|---|
| 復(fù)用率 | Skill 被不同成員調(diào)用的頻次 | 只有被反復(fù)調(diào)用的 Skill 才是有效資產(chǎn) |
| 交付標(biāo)準(zhǔn)一致性 | 新手使用 Skill 后的產(chǎn)出 vs 專(zhuān)家基線 | 衡量 Skill 是否真正傳遞了專(zhuān)家能力 |
5.5 落地路徑:從種子團(tuán)隊(duì)到全面推廣
理解了所有權(quán)和工程化管理之后,推薦 「種子 Skill」 策略,不要一步全員鋪開(kāi):
| 步驟 | 動(dòng)作 |
|---|---|
| 1 | 選 3~5 人先鋒隊(duì) |
| 2 | 聚焦一個(gè)高價(jià)值痛點(diǎn)(課程審核、Code Review、數(shù)據(jù)清洗) |
| 3 | 跑通閉環(huán):識(shí)別專(zhuān)家知識(shí) → 封裝 Skill → 評(píng)測(cè)驗(yàn)證 → 團(tuán)隊(duì)試用 |
| 4 | 用數(shù)據(jù)說(shuō)服其余團(tuán)隊(duì)(審核時(shí)間縮短 X%、質(zhì)量達(dá)專(zhuān)家水平 Y%) |
4 周落地清單
| 周次 | 動(dòng)作 | 產(chǎn)出 |
|---|---|---|
| W1 | 課程審查場(chǎng)景 |
course-review + 至少 1 個(gè) @Tool
|
| W2 | frontmatter + CODEOWNERS | frontmatter 單測(cè)進(jìn) CI |
| W3 | 2 個(gè)黃金 Notebook 樣例 | SkillBox 審查回歸門(mén)禁 |
| W4 | skill-creator 第二個(gè) Skill |
api-doc-review 初稿 + 人審 + 評(píng)測(cè) |
治理演進(jìn)
| 維度 | 試點(diǎn)期 | 推廣期 |
|---|---|---|
| 誰(shuí)寫(xiě) | 技術(shù)骨干 + 業(yè)務(wù)專(zhuān)家 | 任何成員可提交 |
| 誰(shuí)審 | 同行 Code Review | Skill 審核組 / CODEOWNERS |
| 誰(shuí)批準(zhǔn) | 團(tuán)隊(duì)負(fù)責(zé)人 | 領(lǐng)域負(fù)責(zé)人 |
引入 Skill 初期(1~3 個(gè)月)效率可能不升反降——學(xué)習(xí)規(guī)范、調(diào)試 Agent、建評(píng)測(cè)都需要時(shí)間。讓管理層有預(yù)期,用試點(diǎn)數(shù)據(jù)建立信心。
責(zé)任歸屬:Agent 調(diào)用 Skill 產(chǎn)出經(jīng)人合并上線時(shí),Merge 即負(fù)責(zé)——點(diǎn)合并的人對(duì)結(jié)果承擔(dān)最終責(zé)任。
小結(jié)回顧:
1、推廣困境:個(gè)人構(gòu)建的 Skill 在團(tuán)隊(duì)中使用時(shí),會(huì)遇到版本混亂、誤修改和知識(shí)斷檔三類(lèi)問(wèn)題.
2、能力放大器:Skill 的核心價(jià)值是將專(zhuān)家能力規(guī)?;瘡?fù)制,但需要所有權(quán)和工程化流程來(lái)保障
3、領(lǐng)域團(tuán)隊(duì)所有權(quán):最有度量能力的團(tuán)隊(duì)擁有并維護(hù)對(duì)應(yīng)領(lǐng)域的 Skill,通過(guò) YAML 元數(shù)據(jù)和 CODEOWNERS 明確權(quán)責(zé)。
4、Skills-as-Code:將 Skill 當(dāng)作代碼管理,走 PR → Review → CI 評(píng)測(cè) → 部署的完整工程生命周期。
5、落地策略:從種子團(tuán)隊(duì)開(kāi)始,聚焦高價(jià)值痛點(diǎn),用數(shù)據(jù)說(shuō)話,逐步推廣到全組織。