1. 为什么纯聊天的 Agent 只是“半成品”
1.1 聊天型 Agent 的三道天花板
做过一段时间 AI 应用的人应该都有这种体验:把一个大模型接上对话框之后,它确实什么都能“聊”,但只要你让它真正完成一件有交付物的事——比如“把这份 CSV 里的重复用户去掉,按省份聚合出报表,再生成一封邮件草稿”——它就开始在文字里打转。给出的建议很专业,但没有一件事是真正被执行的。这就是聊天型 Agent 的根本局限,它不是能力不够,而是结构上就缺了“行动”这一层。
我把这类 Agent 的问题归纳成三道天花板。
第一道是记忆天花板。聊天型 Agent 的所有状态都依赖上下文窗口,一次会话里塞多少东西,它就记住多少。可一旦任务持续多轮、中间还要读写文件、查询状态、校验中间结果,上下文就会被撑爆。更麻烦的是,上下文里的信息是“自然语言描述”,不是结构化状态,模型容易在长对话后期忘记前面已经做到哪一步。你让它先做 A 再做 B 再根据 B 的结果做 C,它经常做到 B 的时候就开始自由发挥了。
第二道是行为天花板。模型本身的输出只有 token,它没有手,没有权限,没法写文件、跑命令、调接口。要让模型“干活”,必须有人把它的意图翻译成真实的系统动作。早期做法是把 API 一个个暴露给模型,但这本质上是在让模型“自己点按钮”,点错、漏点、重复点都是常态。
第三道是可靠性天花板。自由文本输出是不稳定的,同一个任务让模型跑十次,十次可能给你十种格式的结果。如果下游还有程序要消费这些结果,解析成本会高到你怀疑人生。
这三道天花板的共同根源是:模型负责了太多的“决策”和“执行细节”,而系统本身没有把“怎么干一件具体的事”固化下来。
1.2 Skill 的第一性:把“教会一次”固化成“永久能力”
Skill 这个设计,本质上就是来拆这三道天花板的。
我第一次认真理解 Skill,是在折腾 Claude 和 Codex 的扩展能力时。当时我反复问自己:同样是把一个任务教给模型,我写一段很长的 system prompt 不就行了吗?为什么还要专门搞一套 Skill 体系?
后来我意识到关键区别在于:Prompt 是教你“听懂”,Skill 是教你“干活”。
更好的类比是公司里给新员工写 SOP。你不会只给新员工一段“你要好好对待客户”的价值观宣讲,你会给他一张表:客户投诉进来之后,第一步先登记工单,第二步判断严重级别,第三步分发给对应组,第四步在规定时间内回访。有了这张 SOP,新人哪怕没经验,也能按流程产出及格的结果。Skill 对 Agent 来说,就是这张 SOP。
一个完整的 Skill,通常会同时包含三样东西:
- 一段“什么时候用我”的描述,让 Agent 能在正确的场景里把它找出来;
- 一组“具体怎么做”的步骤说明,把模型需要完成的子任务拆成可执行的顺序;
- 若干“真正动手”的脚本或命令行调用,把模型的决策落到对文件、接口、系统的实际操作上。
也就是说,Skill 把“知识”和“行动”绑在了一起。模型不再需要临时思考“这一步该怎么处理这个数据文件”,它只需要学会“识别该用哪个 Skill,然后按 Skill 里的步骤走”。而步骤里的稳定部分,比如文件解析、数据清洗、API 调用,都已经由脚本固化好了,不依赖模型当时的状态和心情。
这也是为什么我在这篇文章的标题里强调“从聊天进化到干活”——聊天只需要模型有语言能力,干活则需要模型在一个有约束、有流程、有反馈的系统里工作,而 Skill 就是这个系统里最基本的能力单元。
2. Skill 和 Function Calling、Tool、Plugin、Harness 到底是什么关系
2.1 很多人把 Skill 当 Tool 用,方向就错了
在网上搜“skill”和“agent”相关的内容时,能看到大量的提问:“Skill 和 agent 有什么区别”“Skill 和 Function Calling 有什么区别”“Harness 和 Agent 有什么区别”。这些词看起来很像,但解决的问题完全不在一个层次上。我先给出一张对照表,再展开说。
| 概念 | 核心作用 | 粒度 | 典型形态 |
|---|---|---|---|
| Function Calling | 让模型学会调用外部函数的机制 | 原子接口,一次一调 | 函数定义 + 参数 schema |
| Tool | 可被模型调用的一组外部能力 | 单点能力 | 搜索、计算器、API 封装 |
| Plugin | 平台级的功能扩展单元 | 集成单元,带生命周期 | 带安装/启用/卸载的完整模块 |
| Skill | 面向“任务”的完整操作方案 | 任务级,包含多个步骤和脚本 | 描述文档 + 步骤说明 + 脚本资产 |
| Harness | 承载 Skill 和 Agent 运行的运行时环境 | 整个执行环境 | 调度循环、状态管理、执行沙箱 |
你看 Function Calling,它是模型与外部世界交互的“协议层”。一次调用解决一个动作,比如“查天气”。它不负责回答用户“要出行的这个周末天气适不适合露营”,也不负责决定“要不要顺便帮你订个露营地”。
Tool 更是一个“工具箱”里的单个工具。模型可以决定“现在该用搜索”,于是调用 Search Tool;搜索完发现还需要“打开网页”,于是再调用 Browser Tool。每个 Tool 都是独立存在的,但没有人告诉模型“在整个任务里,你要先用搜索,再打开网页,然后提取文本,最后按指定格式汇总”——这个“流程编排”的责任,模型自己去扛了,结果就是它经常编排错。
Skill 则完全是另一层东西。它是把“完成一个任务所需的全部决策和操作”打包成了一个整体。一个 Skill 内部可以包含描述文档、步骤拆解、提示词片段、Python 脚本、Shell 命令、输出模板,甚至验收标准。Agent 只要定位到正确的 Skill,执行路径就被 Skill 内部定义好的流程牢牢兜住。
2.2 为什么业界越来越强调 Skill 而不是 Tool
最近几个月观察下来的趋势很明显:主流 Agent 产品都在往 Skill 方向收敛。Claude 有 Skills,Codex 项目里有 skills 目录,Cursor 的推荐里也大量出现 skill 类扩展。原因很简单——Tool 解决的是“能不能做”,Skill 解决的是“做得稳不稳”。
一个典型例子:你给 Agent 配了 CSV 读取工具、数据清洗工具、图表绘制工具、Markdown 生成工具。理论上它什么都能干。但实际跑起来你会发现,它不知道清洗到什么程度算合格,不知道图表标题该用什么格式,不知道最后输出文件应该放在哪个目录。工具越多,模型的选择负担越重,出错的概率也越高。
Skill 的思路是把这些工具调用和决策逻辑“封装进流程”。我写一个weekly_report_generator的 Skill,里面的文档明确规定:先读哪个文件,字段缺失怎么处理,数据量超过多少行需要抽样,图表配色用什么风格,最终 Markdown 存在哪里。模型在大部分环节里不需要自由发挥,它只需要执行。
所以如果你正在做 Agent 开发,我建议你重新审视一下自己的架构:如果现在还停留在给模型堆工具的阶段,那只是完成了“能力接入”,距离“可靠的自动化干活”还差一个 Skill 层的封装。
2.3 Harness 在其中的位置
搜“harness 和 agent 区别”“deepseek harness 用 skill”的人不少。我简单说下我的理解。
Harness 更像是一个“容器”。它负责运行 Agent 的主循环,管理 LLM 调用的上下文,拦截模型的工具调用请求,并把 Skill 文档和脚本加载进可执行环境。你可以把 Harness 理解为 Skill 的“运行时”——Skill 定义了任务该怎么做,Harness 则保证这些步骤真的被顺序执行、错误能被捕获、结果能被回传。
这也引出一个开发时的习惯问题:写 Skill 时,不能只考虑“模型读不读得懂”,还得考虑“你的 Harness 认不认得这套格式”。不同框架对 Skill 的加载方式有差异,有的要求 SKILL.md 里写固定的 YAML frontmatter,有的要求必须有单独的 scripts 目录。动手前先把宿主环境的约定摸清楚,能省很多事。
3. 手把手从零写一个可落地的 Skill
3.1 先定目标与边界
理论说了那么多,现在开始操作。我以一个最典型的场景为例:“月度运营数据汇报生成”。输入是一份 CSV 格式的运营数据表,包含日期、渠道、用户数、订单数、GMV 等字段;输出是一份 Markdown 格式的月度汇报草稿,外加一个图表数据 JSON 文件。
为什么选这个场景?因为它在工作里出现的频率极高,又恰好能体现 Skill 的价值:数据读取是确定的,清洗规则是确定的,输出格式是确定的——一切都不该让模型临时发挥,但大多数 Agent 在实际处理时偏偏喜欢临时发挥。
开始写之前,先想清楚三个问题:
- 这个 Skill 的输入是什么?格式是什么?哪些字段必须存在?
- 这个 Skill 的输出是什么?给谁看?
- 哪些情况属于这个 Skill 的边界,遇到就该拒绝执行而不是硬处理?
我建议你把这些问题的答案写在一张纸上,再开始建目录。很多人上来就写代码,写到一半发现边界不清晰,回头改结构的成本非常高。
3.2 Skill 的目录与文件结构
一个规范的 Skill 目录,我通常这样组织:
monthly_report_skill/ ├── SKILL.md ├── scripts/ │ ├── preprocess.py │ ├── generate_chart_data.py │ └── render_report.py ├── assets/ │ ├── report_template.md │ └── chart_config.json └── tests/ ├── sample_data.csv └── test_preprocess.py分工很明确:
SKILL.md是给模型看的“说明书”,也是整个 Skill 的入口文件;scripts/放真正执行的脚本,每个脚本只干一件事,便于测试和替换;assets/放模板和配置文件,把“内容”和“代码”分开;tests/放测试数据和测试脚本,方便你验证,也方便模型在不确定时自检。
这份结构不是强制的,每个框架有自己的约定,比如 Codex 的 Skills 目前更接近一个包含 SKILL.md 的独立仓库。但只要遵循“描述在外、脚本在内、测试兜底”的原则,大致方向就不会错。
3.3 SKILL.md 怎么写才容易被模型正确触发
SKILL.md 是整个 Skill 的灵魂。同样的能力,描述写得好不好,直接决定模型会不会在正确时机使用它。
我见过的最常见问题是描述写得太“功能导向”,比如:
# Monthly Report Generator Generates monthly report.这种描述不是不能用,但太弱了。模型面对一个模糊任务时,需要靠 description 判断“这个 Skill 是否适用于当前场景”。只写一句“生成月度报告”,模型不知道什么时候该调用,也不知道给它的参数应该长什么样,最后大概率就是瞎猜。
我自己写 SKILL.md 习惯了用下面这种结构:
--- name: monthly-report-generator description: 当用户需要基于运营数据生成月度汇报时使用。适用场景包括:按月度维度汇总渠道、用户数、订单数、GMV,并输出 Markdown 汇报草稿和图表数据 JSON。 --- # Monthly Report Generator ## 适用条件 - 输入必须是 CSV 文件,包含且仅包含以下字段:date, channel, users, orders, gmv。 - 如果数据缺失字段,先执行 scripts/preprocess.py 做字段补齐,补齐不了则拒绝执行并说明原因。 - 本 Skill 只处理月度汇总,不处理周报、日报。 ## 执行步骤 1. 阅读输入的 CSV 文件,确认字段齐全。 2. 运行 `python scripts/preprocess.py <input.csv> <output_clean.csv>` 做数据清洗。 3. 运行 `python scripts/generate_chart_data.py <output_clean.csv> <chart_data.json>` 生成图表数据。 4. 运行 `python scripts/render_report.py <output_clean.csv> <chart_data.json> <report.md>` 生成汇报草稿。 5. 将 report.md 的内容返回给用户,并附上 chart_data.json 的文件路径。 ## 输出格式 - 汇报包含:本月总览、渠道环比、Top 3 渠道明细、异常提醒。 - 所有百分比保留一位小数。 - 如果某渠道 GMV 环比下降超过 20%,在“异常提醒”中标红提示。这里有几个关键技巧值得展开说。
第一,适用条件要写“哪些不能用”。很多人在描述里只写“能用什么场景”,不写边界,结果模型在明明不适合的场景里也强行调用。加上“只处理月度汇总,不处理周报、日报”这种负面约束,能明显降低误触发概率。
第二,执行步骤要写成模型不需要动脑也能照做的样子。每一步都给出具体的命令模板、参数占位符、输出文件路径。这看起来啰嗦,但正是这种确定性让 Skill 在不同模型、不同版本上都能稳定产出结果。
第三,输出格式要给细则。模型非常擅长“看起来交付了”,但格式细节经常偷懒。你定义了“保留一位小数”“Top 3 渠道明细”“异常提醒标红”,它就有明确的执行参照物。
3.4 核心脚本:从“参考实现”到“防御式编程”
SKILL.md 给了模型流程指引,但真正让结果稳定的,是脚本把脏活累活接过去。
我拿preprocess.py举例,这个脚本负责数据清洗。写的时候要特别注意:脚本绝不能假设数据是干净的。
import csv import sys import json from collections import Counter def load_csv(path): with open(path, newline='', encoding='utf-8-sig') as f: reader = csv.DictReader(f) rows = list(reader) if not rows: raise ValueError("CSV 文件为空") return rows def clean(rows, required_fields): # 字段缺失检查 missing = [f for f in required_fields if f not in rows[0]] if missing: raise ValueError(f"缺少必要字段: {missing}") # 数字字段清洗:去逗号、去货币符号、处理空值 for row in rows: for field in ['users', 'orders', 'gmv']: val = row.get(field, '0').strip().replace(',', '').replace('$', '') row[field] = float(val) if val else 0.0 # 日期字段排序,确保输出稳定 rows.sort(key=lambda r: r.get('date', '')) return rows if __name__ == '__main__': input_path, output_path = sys.argv[1], sys.argv[2] rows = clean(load_csv(input_path), ['date', 'channel', 'users', 'orders', 'gmv']) with open(output_path, 'w', newline='') as f: writer = csv.DictWriter(f, fieldnames=rows[0].keys()) writer.writeheader() writer.writerows(rows) print(json.dumps({"status": "ok", "rows": len(rows)}))写这类脚本有几个经验:
- 编码问题一定要处理,带 BOM 的 CSV 是家常便饭,
utf-8-sig能省很多事。 - 数字字段里混入逗号、货币符号、空值太常见了。清洗逻辑要把这些情况全兜住。
- 脚本必须输出结构化状态信息,比如
{"status": "ok", "rows": 10}。这样模型在执行完脚本后才能判断“是否需要继续下一步”,而不是闷头往下冲。 - 宁可报错终止,也不要静默产出错误结果。
raise ValueError听着不友好,但对 Agent 来说,明确报错比悄悄出错好一万倍。
3.5 验证 Skill:三种测试路径
Skill 写完不能直接上生产。我习惯跑三遍验证。
第一遍是脚本级验证。直接手动执行python scripts/preprocess.py tests/sample_data.csv /tmp/clean.csv,确保每个脚本单独跑都能得到预期结果。只要有一个脚本报错,都不用麻烦模型。
第二遍是模拟调用验证。把 SKILL.md 里的步骤当成 API 文档,照着它手动执行一遍,确认步骤里写的命令、参数占位符都是真实可用的。很多人这一步偷懒,结果模型按 SKILL.md 生成的命令执行时才发现路径错了、参数名不匹配。
第三遍是端到端验证。让自己所在框架的 Agent 真实调用这个 Skill,输入一个稍复杂的任务描述,观察模型是否能正确定位 Skill、按步骤执行到最后。这遍是检查 SKILL.md 的描述是否足够清晰的终极手段——如果模型在“该不该用这个 Skill”“参数怎么填”上犹豫,基本就是描述写得不够好。
4. Agent 看到了 Skill 却不用,问题出在哪
4.1 模型调用 Skill 的完整决策链路
开发 Skill 一段时间后,你会发现最大的困惑不是“写不出来”,而是“写出来了模型不用”。要解决这个问题,得先理解模型在什么情况下会调用一个 Skill。
一次完整的调用决策链路大致是这样的:
- 模型读取用户当前请求,结合历史对话,形成“意图表示”;
- 模型扫描候选 Skill 列表,逐个比较每个 Skill 的 name 和 description 与当前意图的匹配度;
- 如果匹配度高,模型会生成一次调用动作,并把参数按步骤填入;
- 执行结果返回后,模型根据返回内容决定下一步做什么。
这条链路里,真正容易被我们忽视的是第二步。模型并不是“看到了 Skill 列表”,它是“读到了你给的每条描述文本”。描述文本的质量,比 Skill 本身的实现质量更影响调用率。
4.2 描述与路由:搜索 query 的写法才是正解
我自己的经验是:Skill 的 name 和 description 要按搜索引擎的写法来,而不是按文档目录的写法来。
什么意思?搜索引擎的 query 是反直觉的:用户不会搜“MonthlyReportGenerator”,他会搜“这个月运营数据报告怎么生成”“帮我做一张月度 GMV 变化的图”。所以你的 description 里要多用自然语言的长尾短语,覆盖用户可能的说法。
我推荐的做法是,描述里至少包含三组信息:
- 触发场景的自然语言描述,比如“月度汇报”“月度运营数据汇总”;
- 核心名词和同义词,比如“GMV”“订单”“渠道”“环比”;
- 一个反例说明,比如“只处理月度数据,不处理日报”。
这样写出来的 description 信息密度高,模型用语义匹配时更容易命中。
另外,不要忽视参数 schema 的设计。Skill 如果需要输入文件路径、日期范围、汇总维度等参数,schema 里该标required的必须标,该给enum的必须给。模型填参数报错的大部分原因,都是 schema 约束太松,给了模型过度自由发挥的空间。
4.3 执行结果回写与错误恢复
模型调用 Skill 之后,链条并没有结束。脚本产生的输出需要被模型读取,才能生成最终面向用户的答复。这里有两个常见问题。
第一个问题是输出太长。脚本打印了一大堆日志,模型要读完才能判断结果,但上下文窗口有限,日志被截断后模型就“失忆”了。我的习惯是脚本只输出结构化摘要,比如处理了多少行、输出了哪些文件、有没有异常,详细日志写进文件而不是塞给模型。
第二个问题是执行失败后的恢复路径。你搜过 “agent execution terminated due to error” 这个热搜词,应该能理解我的意思——真实环境里错误太多了:文件不存在、权限不足、接口超时、依赖没装。Skill 设计时必须预设错误恢复方案。
我的做法是在 SKILL.md 里写明“执行失败后该怎么做”。比如:
## 错误处理 - 如果 `preprocess.py` 报错,检查 CSV 是否为空、字段是否齐全,修复后重试一次。 - 如果重试仍失败,停止执行,向用户说明失败原因,并给出需要补充的信息,而不是猜测继续。这一步是在给模型划定“错误时的行为边界”,避免它在出错后自行创造一套不稳定的流程。
5. 踩坑实录:让 Skill 从“能跑”到“稳跑”
5.1 坑一:描述太“文艺”,模型总是误触发
我早期写的一个 Skill 是专门做“邮件草稿润色”的,description 写的是“polish email drafts with tone awareness and clarity improvements”。听起来很漂亮,但实际运行时,用户只是随手说了句“帮我把这段话改通顺一点”,模型就自动调用了这个 Skill,结果把一段本来只是微调的文字重写了一大遍。
后来我把 description 改成了更“笨”的写法:
description: 当用户明确要求修改一封邮件的语气、结构或礼貌度时使用。如果用户只是要求改错别字或通顺度,不要使用本 Skill,直接修改即可。加了“什么时候不要用”之后,误触发率明显下降。这个教训我复用过很多次:描述里给反例,比给正例更能约束模型行为。
5.2 坑二:Skill 输出把上下文塞爆
有一版 Skill 要读取一个 5 万行的日志文件来汇总异常,脚本确实跑出来了 JSON 结果,但模型需要把结果读进上下文才能回复用户。5 万行塞进去的结果就是,后续对话全程变慢,最后直接超过上下文限制。
这事的解法很简单:Skill 的设计者必须在脚本侧做“面向模型的压缩”。我先在脚本里聚合出摘要,比如“共发现 3 类异常,最多的一类出现 421 次,样例见输出文件”,然后只把这个摘要返回给模型。完整的明细写进文件,用户需要时再去读。
记住一个原则:模型是汇报者,不是数据仓库。凡是要给模型看的内容,都应该是筛选、汇总、提炼后的;原始数据放在文件里,而不是塞进对话里。
5.3 坑三:脚本权限过大,酿成安全事故
Skill 的核心价值是让 Agent“动手”,但“动手”意味着代码有真实的系统权限。我见过有人写的 Skill 直接在脚本里用os.system执行任意用户输入的命令,这等于把安全大门敞开了。你搜“agent安全”时会看到很多讨论,其实问题的根子不是 Agent 本身,而是 Skill 脚本的权限设计。
我的几条底线:
- 脚本不要用管理员权限运行,给一个受限用户账号更稳妥;
- 脚本里禁止执行来自模型输入层的任意 shell 命令,必须做严格的参数校验和白名单;
- 涉及写文件的操作,路径要固定白名单目录,防止路径穿越;
- 输出结果里的敏感信息,像 API key 之类的,一律脱敏后再回传。
Skill 不是玩具,会和真实系统打交道,权限设计得保守一点,永远比你事后补救便宜。
5.4 坑四:异步任务没人管,卡死了也看不出来
有些 Skill 会触发长时间任务,比如让脚本跑数小时的数据处理,或者调用外部 API 等回调。第一次实际使用这种异步 Skill 时,我踩过坑:模型已经把任务交出去了,然后它自己在对话里“等结果”,但脚本那边因为网络抖动一直没返回,整个 Agent 就像卡死了一样。
后来我在 SKILL.md 里强制加了两条规则:
- 异步任务必须在规定时间后主动检查状态,写清楚“超过 X 分钟后查询一次进度,最多查询 N 次”;
- 如果查询 N 次后仍无结果,标记为失败并告知用户,而不是无限等待。
这类细节不在代码里,而在 Skill 的流程设计里。但恰恰是这些细节,决定了你的 Agent 是“偶尔卡死”还是“一直稳跑”。
5.5 调试方法论:日志、Dry-run 与回放
最后分享一套调试 Skill 的方法。我建议每个 Skill 至少带三层日志:
- 第一层是脚本内部的运行日志,记录每一步处理的输入输出摘要;
- 第二层是 SKILL.md 执行过程的调用日志,记录模型选择了哪些步骤、跳过了哪些步骤;
- 第三层是端到端的任务回放,把用户输入、模型决策、脚本输出全部留存。
有了这三层日志,Skill 出问题时能迅速定位是“模型选错了 Skill”“脚本报错”还是“参数传错了”。我自己在工作中经常做“dry-run 模式”:给 Skill 加一个前置开关,让它跑流程但不真正写文件、不真正调用外部 API,只打印“如果执行会做什么”。这层保护能让测试成本大幅降低,也方便给其他开发者演示。
6. Skill 生态盘点与个人 Skill 库的沉淀思路
6.1 主流运行环境对 Skill 的支持差异
现在 Skill 这个概念已经被各家采纳,但落地的形态差异不小。我整理一下目前常见的方向,大家选型时可以做个参照。
| 环境/框架 | Skill 的承载方式 | 典型特点 |
|---|---|---|
| Claude 类产品 | 通过指令/项目级扩展加载 Skill | 强调文档描述与交互一致性,适用自然语言任务 |
| Codex 类工具 | 仓库内skills目录 + SKILL.md | 贴近开发流程,适合代码类任务 |
| Cursor 等编辑器 | 通过插件或规则导入 Skill | 强调编辑场景内的快捷调用 |
| DeepSeek 等国产框架 | 结合 Harness 加载 Skill | 灵活性高,可自定义程度强 |
| 自研 Agent 框架 | 自行定义 Skill 的结构与加载逻辑 | 定制性最强,代价是需要自己维护 |
这些差异不意味着谁比谁好,而是提醒你:Skill 不是写一份就能到处跑的。它的格式、加载方式、能力边界都跟着宿主环境走。建议先在你目标框架的最小例子里跑通一个最简单的 Skill,再扩展它的结构。
6.2 用“Book to Skill”的思路沉淀个人工作流
最近我在热搜里看到 “book to skill” 和 “workbuddy skill” 这类词,感触挺深。它们背后其实是同一个趋势:把个人或团队反复操作的流程,从一次性 Prompt 升级为可复用的 Skill 资产。
我现在建个人 Skill 库的习惯是:凡是重复超过三次的手工操作,就考虑写成 Skill。比如我每个月都要做一次数据分析、写一次汇报提纲、整理一次浏览器书签,以前是在对话里反复用同几段 Prompt,现在我直接把这些沉淀为 Skill 文件。受益是长远的——下次做同样的事,不需要重新组织语言,也不需要模型重新理解一遍流程。
建库的时候我建议按“领域 + 动词”分类,比如>