做AI Agent开发有一段时间的朋友应该都有同感:模型本身的能力很强,但真正让Agent“干成事”的,往往是外围那层工程化能力。同样是调用一个工具、执行一段流程,为什么有些Agent稳定、可控、可复用,有些Agent换个任务就崩?我自己的答案很简单——中间缺了一层“技能”抽象。
“agent-skills”这个话题,简单说就是给Agent定义一套标准化的可复用技能单元:每个技能封装好名称、描述、参数、执行逻辑和反馈格式,Agent在运行时按需加载、动态调用。它不是一个新的算法,也不是某个特定框架的专有概念,而是一套工程规范。它解决的不只是“怎么调用”,还包括“什么时候调用哪个”“参数怎么传”“失败怎么回退”“效果怎么评估”这一整串问题。
这篇文章我会从设计思路、核心拆解、代码实现、常见坑位四个层面,把我实际搭建技能系统时的经验和取舍完整讲一遍。适合已经在做Agent应用、被“提示词越写越长但行为越来越飘”困扰的开发者,也适合准备入行、想从工程视角理解Agent架构的同学。不会涉及某个具体平台的黑话,所有方案都基于通用能力和常见开源工具,你完全可以在自己的项目里直接落地。
1. 技能系统是什么:先搞清楚边界再动手
1.1 技能和“提示词”“工具”不是一回事
很多初学者容易把这几个概念混在一起。提示词是给模型的行为指令,它是静态的;工具是模型可以调用的外部函数,它是离散的;而技能是“提示词 + 工具的封装 + 执行策略”,它是动态的、可组合的。
举个例子。你给Agent写了一段“帮用户查询天气”的提示词,再提供一个get_weather(city)函数,这只是有了“工具”。但真实场景里Agent还需要判断用户说的是不是城市名、城市名需不需要纠偏、接口超时怎么办、返回结果要不要二次加工——这些逻辑散落在代码里,每次新项目都要重写一遍,这就是缺少技能化。
我把技能定义为:“一个可以被自然语言触发、拥有明确输入输出契约、内部包含完整执行逻辑的可复用能力单元。”它把模型决策和外部执行解耦。模型只负责“决定用哪个技能、填入哪些参数”,而技能内部的具体实现完全由开发人员控制。这样模型的行为边界是清晰的,工程上也能做类型检查、超时管理、日志追踪。
1.2 技能化改造到底解决了什么
抛开术语,技能系统带来的好处是实打实的,我归纳为三类。
第一,行为可控。没有技能层时,模型可能会用五花八门的方式完成任务,今天写代码,明天调API,后天直接瞎编。有了技能层,完成任务的主路径是被限定的——模型只能从注册表里选技能,而每个技能的内部实现是确定性的代码。模型“怎么想”不重要,“怎么做”已经被我们焊死了。
第二,能力可复用。你会发现大量Agent任务其实共享底层能力,比如“从一段文本中抽JSON”“按条件查数据库”“生成结构化报告”。这些能力封装成技能后,不同业务Agent可以直接引用,不必每个项目从零写一遍。我维护的技能库越用越厚,新项目启动速度明显加快。
第三,效果可度量。普通提示词跑得好不好,基本靠感觉;技能化之后,每个技能独立上线、独立打点、独立评估。哪个技能失败率高、哪个技能耗时异常,一目了然,可以直接针对性优化。这对生产环境太重要了。
1.3 什么时候需要上技能系统
不是所有项目都需要技能系统。如果只是搭个Demo,让模型回答几个固定问题,那提示词 + 工具函数完全够用。技能系统有它的心智成本和学习曲线,过早引入反而让简单问题复杂化。
我的判断标准是:当Agent的工具数量超过5个,或者同一套能力需要在多个Agent实例中重复使用时,就该认真做技能化。另一个标志是,你的工具调用开始出现“相似代码复制粘贴”“参数规则不统一”“失败处理逻辑散落各处”这些现象。这时候技能系统的收益会迅速超过成本。反之,只有一两个工具、任务单一的项目,老老实实写清楚工具函数就行了。
2. 技能系统的整体设计:注册、运行时与元数据
2.1 注册表与运行时是技能系统的两根支柱
一个技能系统最核心的部件有两个:技能注册表和技能运行时。
注册表负责“登记和管理”技能。它维护一份技能清单,记录每个技能的元数据、依赖关系、版本信息,并对外提供查询接口。运行时负责“执行”技能。它接收模型或用户的技能调用请求,解析参数、加载对应技能、执行内部逻辑、返回结构化结果。
为什么要把这两个东西分开?因为它们的演进节奏完全不同。注册表需要稳定、可查询、适合做权限管理;运行时而临的是高频调用,要求快速、健壮、能容忍异常。分开之后,你可以单独给注册表加管理界面,单独给运行时做并发扩容,互不影响。我在项目里也确实是这么组织的:注册表是一个纯数据服务,运行时是另一个执行服务。
2.2 技能粒度:太大和太小都不行
技能设计里最容易被忽视的是粒度。粒度太大,技能变成“万能处理器”,内部塞满if-else,模型只负责喊一句口号,可复用性反而差。粒度太小,技能退化成普通工具函数,技能与技能的差异几乎不存在,Agent每步都要做大量选择,决策成本飙升。
我的经验是让技能形成一个“动词级”的能力边界。一个技能对应一个明确动作,比如“查询订单状态”“计算两个日期差”“生成周报摘要”——而不是一个大而全的“处理订单”。动词级技能的好处是描述清晰、参数简单、测试容易。当业务复杂时,用多个技能组合完成流程,而不是把一个技能做复杂。
下面是我常用的粒度对照参考:
| 粒度 | 技能示例 | 优点 | 缺点 |
|---|---|---|---|
| 粗粒度 | 处理订单、管理客户 | 模型决策简单 | 内部逻辑复杂,复用性差 |
| 中粒度 | 查询订单、修改订单、导出订单 | 边界清晰,可组合 | 数量较多,需要管理 |
| 细粒度 | 解析订单号、格式化日期 | 高度复用 | 对模型而言太碎,调用频繁 |
| 混合 | 中粒度为主,细粒度做内部依赖 | 兼顾可控与复用 | 需要设计依赖关系 |
2.3 技能元数据字段该怎么设计
技能元数据是模型和系统之间沟通的桥梁,质量直接决定模型能不能选对技能。我把元数据分为三类。
第一类是标识类:技能名、技能ID、版本号、作者或归属模块。这部分是给系统看的,字段要精简、稳定,尤其是技能ID,一旦发布尽量不要改动,因为线上日志、监控、缓存都依赖它。
第二类是描述类:一句话简介、详细说明、适用场景、参数定义。这部分是给模型看的,质量要求最高。一句话简介要包含“动作 + 对象 + 关键限定”,比如“按用户ID查询近30天订单列表”。详细说明要写清楚“这个技能在什么情况下用、什么情况下不用、边界在哪里”。参数定义要用严格的JSON Schema,每个字段必须有类型、必填性、默认值、取值范围说明。
第三类是运行类:超时时间、重试策略、并发限制、安全等级、白名单。这部分是给运行时看的,确保技能在执行时不会拖垮系统。
我在每个技能的元数据里至少保留这几个字段:
{ "id": "order_query", "name": "订单查询", "version": "1.3.0", "description": "按条件查询订单列表,支持订单号、用户ID、时间范围筛选", "tags": ["订单", "查询"], "input_schema": { "type": "object", "properties": { "order_id": { "type": "string", "description": "订单号,精确匹配" }, "user_id": { "type": "string", "description": "用户ID,可选" }, "start_time": { "type": "string", "format": "date", "description": "开始时间,可选" } }, "required": [] }, "timeout_ms": 3000, "max_retry": 1, "enabled": true }2.4 技能描述怎么写,模型才“看得懂”
技能描述写得好不好,直接影响模型选的准不准。我见过太多人把描述写成“这是一个查询订单的技能”,太笼统,模型遇到模糊任务时根本不知道该不该用。
写描述有几个实用技巧。第一,用负面描述把不适用场景框出来,比如“本技能不处理订单创建、退款”。模型在做选择时,排除法比归纳法更可靠。第二,把关键前置条件写进描述,比如“必须先完成用户身份校验,且校验通过的会话才能调用”。第三,参数示例要跟描述配合,描述里说“支持时间范围”,参数示例里就一定要出现时间的完整写法。
还有一个注意点:描述不是越长越好。太长的描述会稀释关键信息,模型在有限的上下文里反而抓不住重点。我的经验是,一句话简介控制在20个字以内,详细说明控制在100个字左右,把最重要的限定条件放在最前面。
3. 实操:从零搭一个技能库
3.1 技术选型:框架能帮你,但核心是约定
市面上有不少Agent开发框架自带技能或工具管理能力。我的建议是:早期项目可以依赖框架,但技能系统的核心结构一定要自己掌握。因为技能系统本质上是一组约定,框架可以帮你省掉一部分样板代码,可一旦超出了框架的设计范围,如果你不理解底层约定,排查问题会非常痛苦。
下面我用一个简单、可运行的示例,展示不依赖任何重型框架时怎么搭技能库。选用Python生态,只依赖pydantic做数据校验。这个示例精简了数据库和API调用,重点展示技能系统的骨架。
3.2 第一步:定义技能基类
所有技能都继承同一个基类,这样运行时才能统一处理。基类需要包含:元数据属性、参数解析方法、执行方法、格式化输出方法。
from abc import ABC, abstractmethod from typing import Any, Dict, Optional from pydantic import BaseModel, ValidationError, create_model class BaseSkill(ABC): # 子类必须覆写这两个属性 name: str = "" description: str = "" input_schema: Dict = {} def __init__(self): self.model = self._build_input_model() def _build_input_model(self): # 将JSON Schema动态转成pydantic模型,用于参数校验 fields = {} props = self.input_schema.get("properties", {}) required = self.input_schema.get("required", []) for field_name, prop in props.items(): # 这里直接使用Any类型,生产环境应根据prop["type"]做更精细映射 fields[field_name] = (Any, ... if field_name in required else None) return create_model(f"{self.name}_input", **fields) def parse_args(self, raw_args: dict) -> dict: try: validated = self.model(**raw_args) return validated.model_dump() except ValidationError as e: raise ValueError(f"参数校验失败: {e}") @abstractmethod def execute(self, args: dict, context: dict) -> Dict[str, Any]: pass def call(self, raw_args: dict, context: Optional[dict] = None) -> Dict[str, Any]: context = context or {} args = self.parse_args(raw_args) result = self.execute(args, context) return {"skill": self.name, "status": "ok", "data": result}这个基类解决了一个关键问题:参数强校验。外部传进来的参数,哪怕模型生成时漏了字段、填错类型,也会在进入业务逻辑之前被拦住,而不是在内部代码里爆炸。
3.3 第二步:实现一个具体技能
拿“计算两个日期之间的工作日天数”来举例,这是一个复用率很高的技能。
from datetime import date, datetime, timedelta from typing import Dict, Any class CountWorkdaysSkill(BaseSkill): name = "count_workdays" description = "计算两个日期之间的工作日天数,适用于考勤、排班、时效统计等场景。不适用于自然日计算。" input_schema = { "type": "object", "properties": { "start_date": {"type": "string", "format": "date", "description": "开始日期,格式YYYY-MM-DD"}, "end_date": {"type": "string", "format": "date", "description": "结束日期,格式YYYY-MM-DD"} }, "required": ["start_date", "end_date"] } def execute(self, args: dict, context: dict) -> Dict[str, Any]: start = datetime.strptime(args["start_date"], "%Y-%m-%d").date() end = datetime.strptime(args["end_date"], "%Y-%m-%d").date() if end < start: raise ValueError("end_date必须大于等于start_date") days = 0 cur = start while cur <= end: if cur.weekday() < 5: days += 1 cur += timedelta(days=1) return {"workdays": days, "total_days": (end - start).days + 1}注意这里我在描述里加了“不适用于自然日计算”,就是负面描述。模型看到这句话,在需要算自然日时就不会误选这个技能。这种细节在实际运行里非常管用。
3.4 第三步:注册表维护技能清单
注册表不需要复杂功能,能用字典存、能按条件查就行。
class SkillRegistry: def __init__(self): self._skills = {} def register(self, skill: BaseSkill) -> None: if skill.name in self._skills: raise RuntimeError(f"技能重复注册: {skill.name}") self._skills[skill.name] = skill def get(self, name: str) -> BaseSkill: return self._skills.get(name) def list_skills_for_model(self) -> list: # 返回给模型看的一行一条,控制长度 return [ {"name": s.name, "description": s.description} for s in self._skills.values() ] def call_skill(self, name: str, args: dict, context: dict = None) -> Dict[str, Any]: skill = self.get(name) if not skill: return {"skill": name, "status": "error", "error": "skill_not_found"} try: return skill.call(args, context or {}) except Exception as e: return {"skill": name, "status": "error", "error": str(e)}注册表还有一个实用功能:list_skills_for_model返回的是一个精简列表,不会把完整的JSON Schema全部塞给模型。完整Schema在模型决定调用后、由运行时自行加载,这样既省token,又不让模型被无关参数干扰。
3.5 第四步:让模型学会选技能
模型选技能,本质上是把它自己生成的“意图”映射到技能列表上。这个环节有两种主流做法。
做法A:让模型直接输出技能调用。如果用工具调用能力,就把技能描述当作工具定义传给模型,模型会自动生成技能名 + 参数的结构。这是最省事的方式,适合有工具调用支持的大模型。缺点是你把选择权完全交给了模型,如果模型能力弱,选错概率会很高。
做法B:先做一层意图路由。用一个小的路由模型或分类规则,先把用户问题归到某个技能域,再让主模型填充参数。这相当于给技能加了个“门卫”。适合技能数量多、区分度不高的情况。
我在项目里用的是混合方案:主模型仍然负责最终调用,但在系统提示词里明确给出“技能选择优先级”和“当前任务上下文”。比如告诉模型:“查询类需求首选query前缀技能;只有明确需要修改数据时才允许调用update前缀技能。”模型有了这些约束,技能选择的准确率会明显提升。
下面这个函数演示怎么做最简化的技能选择:
def choose_skill(user_input: str, registry: SkillRegistry, llm_call): skills_desc = registry.list_skills_for_model() prompt = f""" 你是技能路由助手。根据用户问题,从以下技能中选择最合适的一个,输出技能名和参数JSON。 技能列表: {skills_desc} 用户问题:{user_input} 只输出JSON,格式为:{{"skill": 技能名, "arguments": {{...}}}} """ resp = llm_call(prompt) parsed = json.loads(resp) skill = registry.get(parsed["skill"]) if not skill: return None result = skill.call(parsed["arguments"], {"user_input": user_input}) return result这个做法实测下来,对小规模技能列表足够可靠。技能超过30个之后,单纯靠提示词路由会开始飘,那时候可以考虑向量检索做候选召回,再用模型精排。
3.6 上下文传递:技能之间怎么共享数据
一个完整任务往往需要串起多个技能,这就涉及上下文传递。我常用的模式是:主Agent维护一个context dict,里面存用户画像、会话ID、会话历史摘要、中间结果等字段。技能执行时传入这个context,技能既可以读取其中已有信息,也可以把执行结果写回去。
需要小心的是:context会跟着模型上下文内容一起被序列化,所以要控制大小,别把一大堆中间结果无脑往里塞。我的经验是只放跨技能需要的字段,比如user_id、session_id、cached_query_result。单个技能内部产生的临时变量不要放入context,用完即弃。
def main_agent_pipeline(user_input, registry): context = { "user_id": "u_123", "session_id": "s_456", "history_summary": "用户想统计自己上个月的工作情况" } skill_name = "count_workdays" args = {"start_date": "2026-01-01", "end_date": "2026-01-31"} result = registry.call_skill(skill_name, args, context) context["workday_count"] = result["data"]["workdays"] # 后续技能可以直接用到context["workday_count"] return context4. 关键技术细节:稳定性与安全
4.1 参数校验:别让模型直接碰你的函数
模型生成的参数天生具有不确定性。一个字段类型写错、一个必填字段漏传、一个枚举值超出范围,都是高频问题。所以技能系统的第一道防线就是参数校验,必须放在业务逻辑之前。
我用pydantic做了一层标准的Schema校验,但这还不够。业务规则校验同样重要。比如日期范围类参数,只校验类型格式还不够,还要校验业务含义:结束日期不能早于开始日期、查询时间跨度不能超过90天。这些校验逻辑放在技能内部,作为execute的入口守卫。
下面是一个组合校验的示例:
def execute(self, args: dict, context: dict) -> Dict[str, Any]: start = datetime.strptime(args["start_date"], "%Y-%m-%d").date() end = datetime.strptime(args["end_date"], "%Y-%m-%d").date() if end < start: raise ValueError("结束日期不能早于开始日期") if (end - start).days > 90: raise ValueError("查询跨度不能超过90天,请分批查询") # 业务逻辑...这种双层校验(格式校验 + 业务校验)会把大部分脏数据拦截在门外。别嫌麻烦,生产环境的稳定性就是这么一点一点攒出来的。
4.2 失败反馈:让错误成为模型可以理解的信号
技能执行失败时,直接把Python异常堆栈扔给模型是最差的做法。模型看不懂traceback,也没法根据它修正行动。更好的方式是:把异常转换为结构化错误码 + 人类可读的错误说明 + 修正建议。
我在技能系统的返回值里统一用这种格式:
{ "skill": "count_workdays", "status": "error", "error_code": "INVALID_DATE_RANGE", "error_message": "结束日期不能早于开始日期", "suggestion": "请检查start_date和end_date,交换两者或重新输入" }模型拿到这个反馈之后,往往能自己修正参数重新调用一次,整个过程不需要用户介入。这就是“失败反馈闭环”,它比单纯重试要聪明得多。对无法自愈的错误,比如“权限不足”,就不要让模型反复重试,直接返回给用户并给出原因。
4.3 技能版本与灰度
技能一定会迭代。你今天改了某个技能内部逻辑,模型还是按老描述在调,可能就出现行为不一致。所以版本管理是生产环境绕不开的问题。
我在每个技能上留了version字段,并且遵循一个原则:元数据变更必须升级版本,行为逻辑变更必须升级版本,只有修Bug可以原地更新。为什么?因为元数据变了,模型看到的技能描述就变了,旧版调用日志会失真;行为变了,线上效果评估的基准就变了。版本号是排查问题的锚点,不能省。
类与类的匹配,我建议用语义化版本号。技能发布流程里加一步“变更说明”,记录这次版本改了什么、影响哪些调用方。新版本技能先在小范围灰度,观察失败率和耗时表现,稳定后再全量。这个过程和正常后端服务发布完全一致,只是很多人做Agent时忘了这一层,结果线上出了问题都不知从何查起。
4.4 并发、超时与资源隔离
技能执行必然涉及外部依赖:数据库、第三方API、内部HTTP服务。Agent场景里,模型可能会并发调多个技能,或者同一技能短时间内被调多次。如果不加控制,上游接口分分钟被打爆,或者技能A因为慢查询拖垮整个Agent响应。
我常用的控制策略有几条。第一,给每个技能配置独立的超时时间,短任务3秒,长任务可以放宽到30秒甚至分钟级,但不允许无超时。第二,用信号量做并发限制,比如技能A最多同时跑5个实例,超出就排队或快速失败。第三,技能内部的服务调用都要带链路ID,方便追踪一次Agent会话里所有技能调用的完整链路。
超时策略还要想清楚“超时后干什么”。是重试?降级返回?还是直接报错?我的建议是:幂等类的查询技能可以安全重试,非幂等类的写操作技能不要盲目重试,宁可报错也别重复执行产生脏数据。
5. 常见问题与排查实录
5.1 模型为什么老选错技能
选错技能是技能系统最常见的问题,通常是注册表里技能描述太相近、区分度不够,或者当前上下文的提示不够明确。
排查路径我一般是这样:先看模型实际收到的技能列表是什么样,有没有把完整的描述截断;再看用户原始输入和模型output,判断模型是基于什么理由选的;最后对比选错技能和正确技能的描述,找出重合信息太多的地方。
修复手段也直接:一是给两个容易混淆的技能加上互相排斥的负面描述;二是提高描述的歧义覆盖度,比如“按用户ID查询”和“按手机号查询”要写明各自的边界;三是在系统提示词里给技能选择加导向,比如明确说“当前会话的用户身份是客服,只允许调用客服域技能”。
| 症状 | 可能原因 | 排查方法 | 解决建议 |
|---|---|---|---|
| 两个技能反复选错 | 描述重叠度高 | 对比两者description | 加负面描述,互斥场景写清 |
| 新技能永远不被选中 | 描述风格与旧技能不一致 | 检查新技能description是否符合既有模式 | 参照高频技能改写描述 |
| 特定场景选错 | 上下文信息缺失 | 查看context关键字段是否为空 | 在context中补充必要的业务状态 |
| 偶尔选错,无明显规律 | 模型温度过高 | 检查生成参数temperature | 将决策类任务temperature调低,比如0.1 |
5.2 参数传错、漏传怎么兜底
模型生成参数时,漏掉一个非必填字段往往没事,但漏掉必填字段或者传了错误枚举值,技能就会直接报错。我的兜底策略是分三层。
第一层:Schema校验拦截,确保字段类型正确。第二层:技能内部给非核心字段做默认值处理,比如“用户ID没传但context里有,就自动补上”而不是报错。第三层:对仍无法解析的参数,返回上面说的结构化错误码和修正建议,让模型有机会重新生成一次。
这里有个小心得:必填参数尽量少。每个必填参数都是模型生成出错的机会,能通过context拿到的就不要设为必填,能从其他参数推导出来的就让技能内部去算。参数面上越宽松,模型执行成功率越高。
5.3 技能执行成功了,但用户不满意
这类问题最隐蔽。技能正确执行、返回了正确数据,但Agent最终回答没让用户满意。原因通常是技能返回结构和Agent的话术拼接没对齐。
比如查询订单技能的返回数据里有“订单状态码”,是数字类型,但用户期望看到的是“已发货”这样的中文状态;或者技能返回了多个订单,但Agent在总结时只随机挑选了一个,没告诉用户总数。这些问题不发生在技能内部,但需要技能设计者把“展示层友好字段”也纳入返回结构。
我通常会在技能的返回值里同时提供“原始数据”和“摘要字段”。比如订单列表技能返回{"total": 12, "items": [...], "summary": "您共有12笔订单,其中3笔待发货"}。模型直接用summary组织回答,既准确又高效。
5.4 性能瓶颈出现在哪里
技能系统常见性能瓶颈有三个:注册表查询、参数校验、技能内部外部依赖的耗时。
注册表查询一般是毫秒级,问题不大;参数校验如果用纯Python遍历复杂Schema,几百个技能也不会太慢,但如果数据量大、校验逻辑复杂,就要考虑缓存。最大的瓶颈几乎总是外部依赖。我的优化顺序永远是:先优化外部调用(加缓存、加超时、改批量接口),再优化内部逻辑,最后才考虑优化框架本身。
缓存这块值得多说一句。对查询类技能,可以加一层结果缓存,Key用“技能名 + 参数哈希 + 业务上下文关键字段”。缓存命中率高了,整体响应时间能下降一个量级。但要注意缓存失效策略:订单状态这类实时性要求高的数据,缓存时间要短;统计类数据可以缓存久一点。
5.5 技能库膨胀以后怎么办
技能数量上到几十上百时,维护成本会急剧上升。不可能让模型每次都看完整技能列表,也不能靠手写一堆if-else做路由。
我的方案是给技能打标签,按业务域分组,同时维护一份“技能总索引”。主模型只在进入某个业务域时才加载这一组技能描述。或者在技能选择之前,先用检索模型从所有技能描述里召回Top 5候选,再把候选列表交给主模型做最终选择。这相当于给技能系统加了一个索引层,可扩展性会好很多。
另一个预防膨胀的手段是定期清理。我用两个指标判断技能是否该下架:近30天调用次数过低,并且能找到替代技能,就标记为废弃;描述没有任何区分度、候选场景全被其他技能覆盖的,直接合并。技能库不是越厚越好,而是要让每个技能都有清晰的存在理由。
最后补几句自己的体会
做了几个Agent项目之后,我对技能系统最深的感受是:它真正解决的不是“技术”问题,而是“协作”问题。模型负责判断,代码负责执行,而技能层是两者之间的翻译官和契约管理人。如果没有这层契约,模型越聪明,系统越不可控;有了这层契约,即使模型偶尔犯错,系统也能把损失限制在最小范围内。
一个我自己一直在用的习惯是:每上线一个新技能,都随手写一个自测用例文件,覆盖正常输入、边界输入、错误输入三种情况。这些用例不用多,每个技能几十行,但积累起来非常值钱。模型选技能对不对,改了一行逻辑会不会影响历史行为,跑一遍自测就能给出明确信号。
如果你正在搭建自己的Agent,我建议从最小的技能库开始,先封装两三个真正高频的能力,跑通注册、执行、错误反馈这条链路,再慢慢扩大。没必要一开始就追求大而全的技能框架,因为你对技能粒度的理解,一定是在实际项目里被毒打之后才准确的。把这套结构想清楚,Payoff会持续很久。