
过去做 AI 应用大家习惯把什么都塞进一个巨大的 system prompt——角色设定、业务规则、输出格式、示例样本全往里堆模型像个独臂巨人什么都知道一点但什么都做得不彻底。后来工具调用function calling普及了算是给模型装上了手但那只手依然长在一个固定的躯干上换一个场景就得重新长一遍。直到我最近在折腾 Gemini CLI 这种支持技能skills的智能体框架才意识到我们可能真的进入了模块化 AI 智能体时代模型不再是唯一的主角真正值钱的是那些可以被自由组合、按需加载、独立升级的技能单元。这篇文章想把 Gemini CLI 的技能机制掰开揉碎聊聊它到底解决了什么问题技能包的内部长什么样以及开发者从写函数到编排技能生态的范式转变到底意味着什么。如果你是正在做智能体应用、或者对 AI 工程化工作流感兴趣的开发者这篇应该能给你不少可以直接落地的思路。1. 为什么智能体需要技能聊聊单体对话架构的瓶颈1.1 传统智能体一个 Prompt 打天下的隐患早期做一个客服机器人或者文档助手最常见的做法是把知识库内容、问答规则、处理流程全部拼进一段超长 Prompt。一开始效果确实惊艳模型什么都懂。但随着业务复杂问题很快冒出来。首先是上下文膨胀。每次对话都要携带这几十 KB 的固定指令Token 消耗大响应变慢更关键的是模型在长上下文中会出现注意力分散——开头定义的规则到后面往往被遗忘。我见过一个项目Prompt 里清清楚楚写了遇到售后问题请先引导用户提供订单号结果模型在处理第十轮对话时突然跳过了订单号直接给补偿方案原因就是中间插入的临时信息把那条规则挤出了注意力窗口。其次是更新困难。哪怕只改一个术语表也要重新编辑整个 Prompt 并回归所有场景。因为改动是全局的你无法确认这个修改会不会影响其他原本正常的任务。这就像在一个大泥潭里搅动一个角落整个潭水都会变浑。更难受的是无法复用。同一个项目里可能有十几个智能体场景分别是商品推荐、订单查询、售后处理。它们的底层能力大量重叠比如都需要理解用户情绪、都需要格式化输出、都需要调用订单接口。但在单体架构下每个场景都是独立的 Prompt重复内容写了一百遍改一处就要同步一百处。1.2 技能化拆解的底层逻辑把认知能力装进独立模块模块化智能体的思路是把能力而非角色作为基本单元。角色是一个固定的壳能力是一个可插拔的部件。Gemini CLI 的技能就是这么一种单元每个技能封装一项特定领域能力包含触发条件、上下文知识和执行逻辑平时不占内存等到用户意图匹配时才被加载并激活。这个逻辑和人类的工作方式很像。一个医生接诊时不会把全部医学知识从头背一遍而是根据症状调取对应的诊断路径一个工程师修设备时也不会把整本手册读完而是先看现象再翻相关章节。技能化就是让 AI 智能体学会按需调用自己的一部分大脑。这样的好处是显而易见的。第一上下文瘦身每个技能只在被需要时注入日常对话无需携带全量知识。第二更新隔离改一个技能不影响其他技能除非显式声明了依赖关系。第三组合复用一个订单查询技能可以被客服场景用也可以被售后场景用甚至可以被另一个智能体用。1.3 Gemini CLI 的技能定位从工具调用到能力单元工具调用和技能的区别在哪这是我一开始最困惑的地方。后来我理解为工具是动作技能是完整的做某件事的过程。工具回答能做什么技能回答在什么场景下依据什么规则按什么步骤把事办成什么样。举个例子一个天气查询工具核心动作就是调用天气 API 返回数据。而天气分析技能则进一步包含判断用户问的是哪座城市要不要默认补上时区降雨概率超过多少需要特别提醒风速数据如何格式化呈现以及如果 API 挂了该怎么降级到历史数据。技能是工具 规则 上下文 边界条件的综合体。Gemini CLI 把技能设计成了第一公民意味着智能体的核心能力不再是模型本身而是模型周围的技能生态。模型变成了一个调度器 执行器真正发挥作用的是那些可以被任何人编写和贡献的技能模块。2. 拆开 Gemini CLI 的技能包结构、生命周期与运行机制2.1 一个技能文件里有什么声明、上下文、执行体三件套我第一次创建技能包时按照官方社区推荐的标准结构写成了一个独立目录里面包含三个核心部分技能清单文件、上下文模板、以及可选的执行脚本。技能清单文件是技能的身份证通常命名为 SKILL.md里面用 YAML 或 TOML 格式声明元数据技能名称、一句话描述、触发关键词或意图正则、依赖的其他技能、所需工具权限、以及上下文模板的引用路径。触发条件写得好不好直接决定智能体会不会在错误的时候激活这个技能。上下文模板是一段 Markdown 或文本会被注入到模型输入中。它包含该领域的知识要点、few-shot 示例、输出格式规范。比如日志分析技能的上下文模板里会写明常见日志级别、时间戳格式样例、如何区分崩溃和警告、报告输出的 Markdown 结构。这部分是技能发挥效果的认知组件。执行脚本则是可选的因为有些技能纯靠 Prompt 就能完成但更多技能需要实际调用代码——读取文件、访问 API、运行数据转换。Gemini CLI 允许在技能里挂 Python 或 JavaScript 脚本通过标准输入接收对话上下文通过标准输出返回结构化结果模型再基于结果生成最终回复。2.2 技能加载与启用的完整流程技能不是一启动就全部装进内存的那样又回到了上下文爆炸的老路。Gemini CLI 的加载流程我梳理后大概有四步。第一步是扫描CLI 启动时会遍历配置指定的技能目录读取所有 SKILL.md 进行语法校验和依赖检查。第二步是索引把所有技能的描述、触发条件、依赖关系构建成一个内部索引这个索引本身很小不会占用太多 token。第三步是路由在每一轮对话中路由模块会先做一次意图预判把用户输入和所有技能的触发条件做匹配。匹配的逻辑不依赖模型通常是轻量的正则加嵌入相似度计算所以速度很快。第四步是激活选定技能后才把技能上下文模板注入到当前上下文并按需运行执行脚本完成一轮增强推理。这个按需加载的设计非常关键。我做了个粗略对比加载技能前后普通闲聊的上下文长度几乎不变只有真正触发日志分析时那 800 字的模板才会进入模型视线。相比之下传统架构里那几千字规则是永远挂在那儿的成本不可同日而语。2.3 技能之间的依赖与优先级别让模块打架技能多了以后第一个问题就是冲突。比如一个数据可视化技能和日志分析技能都想处理日志输出——前者想画图后者想写排查报告。如果同时触发智能体可能手忙脚乱。Gemini CLI 给的解决方案是依赖声明和优先级排序。每个技能可以声明depends_on和conflicts_with。同时索引表里为每个技能设置了优先级数值。当多个技能匹配时会先按优先级排序高优先级的技能优先激活如果高优先级技能明确声明了与另一个冲突则低优先级技能被挂起。我实际测试时发现合理设置优先级能解决大部分冲突但更重要的是技能触发条件要写得精准。不要写包含日志两个字就触发这种泛条件而是写用户要求分析日志文件、梳理错误、定位故障原因这类包含明确意图的语义描述。模糊的条件会让路由模块反复犹豫最终产生一系列不稳定的激活行为。3. 开发者的新范式从写函数到编排技能生态3.1 声明式配置代替硬编码技能即插即用传统开发模式里我们要给智能体增加一个能力通常要改代码、加分支、重新部署。比如在客服智能体里加一个发票查询功能得在业务逻辑层写一个新方法然后在 Prompt 里加说明再在工具调用注册表里新增一个函数签名还要处理各种错误分支。而 Gemini CLI 的技能范式是声明式的。你只需要写一个技能包放到约定目录然后在配置里引用它。注册、路由、激活、释放这些过程都由 CLI 框架自动完成。技能和智能体之间的耦合度极低小到改一个技能里的示例文案大到下架整个技能都只是在做目录层面的增删。这种即插即用的体验特别像早期前端开发引入组件化的过程。以前写页面是一整块 HTML后来拆成组件哪里需要哪里引。技能的粒度控制得当一个老项目甚至可以零改造地逐步迁移——把旧 Prompt 按领域切成几个技能包再删掉原来那段大水漫灌的 system prompt智能体不仅没变笨反而因为上下文干净而变聪明了。3.2 复用与共享技能市场化的雏形模块化带来的第二重改变是复用。以前做 AI 项目团队内部的 Prompt 都是祖传代码一个人写其他人在其基础上小改最后没人敢动。现在技能包被设计成独立单元后天然就适合做成共享产物。我所在的小组做了一个SQL 生成与校验技能后来被另一个项目直接拿去用了只改了一个数据字典文件。这种复用效率单体架构下根本不敢想。更进一步技能包的元数据格式统一理论上完全可以做成一个公开的注册表开发者可以把技能发布上去其他人通过一条命令安装。这就像编程领域从函数库时代进化到包管理器时代——技能即插件的生态一旦形成智能体开发的边际成本会大幅下降。当然技能共享也带来安全与信任问题。引用了不干净的技能相当于引用了不安全的依赖。所以我在实操中会特别注意技能包的来源尽量只从可信渠道拉取且每次更新前要 diff 一下变更内容。3.3 测试与调试的思维转变单测不再是终点单体 Prompt 时代的测试很玄学基本靠多聊几轮看看效果。技能化之后测试变得可以结构化但也产生了新的复杂度。我现在测试一个技能会拆成三层来验证。第一层校验清单和模板格式确保能被 CLI 正确解析和加载——这一步已经可以自动化了。第二层校验单技能效果用预置的一批用户输入调用这个技能单独跑检查输出是否符合预期格式这一步可以理解为技能单测。第三层做集成测试验证技能放在完整的智能体里在与其他技能共存时能否被正确路由和激活会不会产生上下文污染。这比过去的端到端聊天测试要直观得多。但有个值得注意的新坑模型输出的不确定性意味着就算单测通过集成时也可能因为上下文变化导致行为漂移。所以技能内的 Prompt 要尽量用确定性强的措辞少用你觉得呢之类的开放引导多用结构化输出指令。我甚至会把关键输出调整为 JSON 格式方便用代码断言验证而不是用肉眼看模型写了啥。4. 动手做一个自定义技能完整实操记录4.1 场景选择为什么我挑了日志分析这个技能理论讲再多不如跑一个例子。我决定给 Gemini CLI 写一个日志分析技能初衷很单纯我的本地开发环境经常产生各种乱糟糟的日志文件之前都是复制粘贴给模型分析但每次都要把日志格式、常见错误类型解释一遍很浪费。日志分析这个场景特别适合做技能因为它的领域知识稳定不需要经常改动它的输入输出边界清晰输入是日志文本输出是问题列表与排查建议而且它需要执行体参与可以从纯 Prompt 技能练到带脚本的技能正好覆盖完整链路。我给自己定的目标是以后只要跟 CLI 说一句看看今天的 error 日志它就能自动定位文件、解析日志、统计错误频率、输出一份带时间线的简要报告全程不需要我再手动贴文件内容。4.2 技能清单编写与上下文设计我先创建了目录结构大致是这样skills/ └── log-analyzer/ ├── SKILL.md ├── assets/ │ ├── system_context.md │ └── examples/ │ └── sample_report.md └── run.pySKILL.md 里的核心内容我这样写name: log-analyzer description: 适用于分析本地日志文件, 提取错误, 统计频率, 输出排查报告。 triggers: - intent: analyze_logs patterns: - 分析日志 - 看看.*log - 排查.*错误 - 日志里有什么问题 priority: 80 context_template: assets/system_context.md executor: type: python entry: run.py params: max_lines: 5000触发条件这块我斟酌了很久。一开始写得很宽泛结果发现聊到最近项目的进展时路由模块竟然因为项目进展中的进字匹配到日志分析闹出不少笑话。后来我改成上面这种以明确意图词为核心的写法只保留分析日志、看看日志、排查错误这样的动作词组误触率才降下来。system_context.md 里放了领域知识模板我提炼了日志分析最常用的规则你是日志分析助手。收到日志内容后 1. 优先识别日志级别标记ERROR, WARN, INFO, DEBUG, TRACE 等。 2. 按照时间排序整理每个错误出现的时间点。 3. 统计各错误码或错误签名出现的次数按频率降序排列。 4. 对 TOP 3 高频错误给出可能的根因提示。 5. 输出格式统一使用 Markdown 列表包含时间、级别、摘要、建议字段。 如果日志中存在堆栈信息只保留关键异常类型和出错函数名。这里有个经验上下文模板不必长篇大论300-500 字足够。模板太长会挤压对话空间模板太短又约束不了模型的行为。我试过把每一步都洋洋洒洒写得极其详细结果模型在处理日志时反而变得僵化看到一点不符合模板的情况就开始乱套。适度留白让模型依靠基础能力去判断效果反而更自然。4.3 执行体接入 CLI 的两种方式日志分析技能需要实际读文件所以必须写执行脚本。Gemini CLI 支持的执行体一般有两种方式一种是通过标准输入输出做无状态命令行调用另一种是通过本地 HTTP 服务做有状态长连接调用。我这次用的是前者因为日志分析是一次性请求不需要记忆什么状态。run.py 的作用是接收 CLI 传来的目标文件路径读取文件并做初步清洗然后把清理后的日志文本返回给模型。脚本本身不负责最终分析那是模型的工作。这么做的好处是执行体只做确定性工作所有需要理解的部分都留给模型职责边界清晰。下面是我提炼过的脚本逻辑import re import sys from pathlib import Path def extract_log_lines(file_path, max_lines5000): 读取日志文件, 剔除空行和无关注释, 限制行数, 返回正文。 text Path(file_path).read_text(encodingutf-8, errorsreplace) lines text.splitlines() filtered [] for line in lines: if not line.strip(): continue if line.startswith(#): continue filtered.append(line) if len(filtered) max_lines: break return \n.join(filtered) if __name__ __main__: file_arg sys.argv[1] # 解析传入的 JSON 参数 json_part sys.stdin.read() if path in json_part: file_arg json.loads(json_part).get(path) print(extract_log_lines(file_arg))CLI 把用户指令转成对脚本的调用脚本输出日志文本再连同技能上下文一起喂给模型。整个过程对用户是透明的我只说了句分析一下 /var/log/app/error.log后排脑子里CLI 自动把文件内容取了出来。这里想特别提醒一下路径安全。执行体能访问本地文件后一定要限制可读范围。我在技能配置里加了一层白名单规则只允许访问项目目录下的 logs 目录防止技能被诱导读取系统敏感文件。这个坑后面会细说。4.4 验证与效果对比装技能前后差异装好技能后我准备了一份模拟日志来验证效果。日志里有 200 行正常访问记录掺杂了几个偶发的 500 错误和一个数据库连接超时问题。在没有技能加持时我把日志直接丢给模型要求分析模型也能给出一些泛泛的结论比如出现了一些 500 错误建议查看服务日志但如果我追问具体错误分布它就会含糊其辞。加上日志分析技能后输出变成了这样日志分析结果时间范围今天 10:02 - 15:47 - ERROR 高频GET /api/order/detail 返回 500 共 12 次 - 时间点10:02, 10:15, 10:18, 11:03, 11:22... - 建议优先检查该接口的数据库查询逻辑, 偶发超时指向连接池占用。 - ERROR 中频DB connection timeout 共 7 次 - 时间点集中在 10:00 和 14:30 两个时段疑似与定时任务重叠。 - 建议查看定时任务调度与数据库连接池的上限。 - WARN 提醒跨域请求被拒绝共 23 次不影响主要流程。模型能在脚本提供的完整日志文本基础上自动按时间排序、聚合统计错误码、给出高价值根因提示这就是技能上下文模板起了作用。相比之下没技能时模型只能凭记忆里的知识猜装了技能后它是在规则约束真实数据上分析准确率和可执行性完全不一样。5. 我在实战中踩过的坑与设计建议5.1 技能粒度过粗又回到大 Prompt老路第一个坑是技能粒度的把控。我最初图省事把一个日志分析性能诊断部署建议的巨型技能整个塞进一个包里。结果路由是没问题但激活时的上下文模板长达 2000 多字把大量和当前任务无关的部署建议也注入了和之前的大 Prompt 没有本质区别。后来我把这个巨型技能拆成了三个小技能日志分析、性能画像、部署巡检。每个技能只关注自己最擅长的事触发条件更清晰注入的模板短了模型回复也更聚焦。我得到的核心教训是技能的边界应该以一次任务能稳定满足用户预期为准不要试图在一个技能里塞进所有相关但非必要的知识。5.2 上下文污染技能之间互相干扰第二个坑来自上下文污染。我在一个会话里依次触发了日志分析技能和数据库诊断技能因为两个技能都包含了对时间戳的解析规则而且都提到建议检查数据库连接池导致第二次激活时模型竟然分不清当前分析任务的对象是日志还是数据库把上一段日志示例当成当前输入来处理。复盘后发现问题出在技能模板里的措辞太通用。我给日志分析模板里写了时间戳格式常见为 ISO8601给数据库诊断模板里也写了同样的句子两者在连续激活时产生了记忆叠加。解决办法是让每个技能模板都加上明确的边界声明比如以下规则只适用于日志文本分析不适用于其他输入。同时尽量缩小技能的比喻、示例范围减少与其他技能的词汇重叠。5.3 回退机制缺失技能失效后整个智能体瘫痪第三个坑是回退机制。一次测试中我让智能体分析一个超大日志文件执行脚本因为读取文件超时抛了异常。由于技能配置里没有定义错误回退策略CLI 直接把异常结果当成对话内容返回整个会话瞬间乱套。这个问题让我真正意识到技能不仅是增强,也可能是累赘。没有设计良好的降级路径技能一挂模型反而被绑死。我的解决办法是在技能包配置里增加了一个fallback字段指定当执行体出错时可以切换到纯 Prompt 模式继续分析或者直接提示用户换一个小文件。还加了一个超时参数防止脚本卡死拖累整个会话。5.4 一点建议给技能设计健康检查与说明文档踩过这些坑后我总结了一套技能设计的小清单。第一个建议是给每个技能写一个README说明它的职责、触发场景、已知限制和依赖关系。表面看是文档实际上当你面对几十个技能时这份 README 是救命稻草。第二个建议是给技能加一个自检指令。我在每个技能的上下文模板末尾加了一段如果是测试模式请先输出技能版本号和准备应用的规则列表。这样我可以快速验证技能有没有被正确加载、上下文有没有被污染。对调试非常有帮助。第三个建议是把技能的工具权限降到最低。日志分析技能只需要读特定目录文件就绝不给它写文件或访问网络的权限。技能就像小程序权限越小越安全越不容易被恶意prompt注入利用。最后聊点感性的体会。技能化的本质是把智能体从一个巨大的不可拆分模型变成一群可以独立演化的小能力单元。它让开发者第一次有了把思考过程模块化的抓手。刚开始做的时候可能觉得多了一层配置麻烦但一旦建立了技能生态后面每一个新场景的边际成本都越来越低。我个人最大的感受是以后再也不用靠背 prompt来维护 AI 应用了而是在搭积木每一块都可以单独打磨、替换和分享。模块化 AI 智能体的时代确实已经开始了。