从Prompt工程到Agent Skills:将AI能力封装成可测试的软件模块

发布时间:2026/10/20 18:26:11
从Prompt工程到Agent Skills:将AI能力封装成可测试的软件模块 在给 Agent 加能力这件事上我过去的第一反应永远是“再写一段 Prompt”。直到某次给一个数据处理类 Agent 增加“PDF 表格抽取”功能我在提示词里反复强调格式、补充了三四页示例结果模型的输出依然时好时坏同一份文档换个排版就崩。那一刻我才意识到如果 Agent 的能力边界全靠提示词去“说服”模型那它永远不够稳定也永远无法像真正的软件模块一样被测试、复用和迭代。后来我接触并实践了 Agent Skills智能体技能包的思路才真正体会到什么叫“从 Prompt 工程走向软件工程”——把技能封装成结构化的、可运行、可测试的软件组件而不是一段靠运气生效的文本指令。这篇就拿我一个实际的可复用技能包为例拆解 Agent Skills 的设计思路、落地步骤以及从提示词思维切换到软件工程思维后我踩过的坑和收获。1. 为什么 Agent 的“技能”不能再靠提示词硬撑1.1 Prompt 工程的天花板不稳定、难复用、不可测试先聊聊我过去几年用 Prompt 工程给 Agent 加能力的真实体感。早期做一个问答机器人要让它能查询内部知识库我的方案很简单把检索到的文档片段拼进系统提示词再写一大段“请基于以下内容回答”。效果尚可但一旦文档数量增加、问题变复杂提示词就开始失控。后来做结构化抽取、多步工具调用问题更明显。Prompt 工程的瓶颈我总结下来就三件事。不稳定。模型对措辞极其敏感换一个同义词、调一下示例顺序输出格式就可能偏离。你没法用“断言”来保证它一定返回合法的 JSON只能说“概率很高”。难复用。一段提示词写完后换一个业务场景就要重新调。项目 A 里调通的情感分析 Prompt项目 B 里几乎没法直接搬因为阈值、示例、输出格式全都依赖当时的数据分布和模型版本。不可测试。你很难对一段 Prompt 写单元测试。它不是确定性的程序没有明确的输入输出契约。你说“输出必须包含三个字段”但模型可能给你输出四个、五个或者把字段名改一改。回归测试这件事在传统提示词方案里基本等同于“人工再看一遍”。这不是说 Prompt 工程没有价值而是它更适合做“模型行为引导”不适合做“能力封装”。当你想给 Agent 增加一个确定性的功能——比如“把 CSV 转成 JSON”或“调用某个 API 并解析结果”——你真正需要的不是一段话术而是一段可靠的代码外加一份让模型知道“何时用、怎么用”的说明。1.2 一个让我转向 Skill 思维的真实场景我在某开源学习社区组织过一次 AI Agent 组队学习当时的任务之一是给 Agent 增加“网页表格抽取”能力。第一次尝试就是纯 Prompt给模型看几个 HTML 片段让它提取表格数据并输出 Markdown。结果很有意思。对规整的表格模型表现很好对带合并单元格、多层表头、或者表格嵌套在复杂布局里的页面输出就开始离谱。更麻烦的是抽取结果里偶尔混入页面导航栏里的文字模型自己根本意识不到。这种“幻觉式抽取”不是靠提示词能修好的因为问题不在模型理解能力而在任务本身需要一个确定性的解析流程。后来我把这个功能重写成一个 Python 脚本用专门的 HTML 解析库去识别表格节点再写一个清晰的能力描述文档告诉 Agent“什么时候调用这段代码传什么参数拿什么结果”。从那以后输出稳定了而且这套逻辑可以给任何 Agent 用不需要重新调 Prompt。这个转变就是“从 Prompt 工程走向软件工程”的起点把模型擅长的事情意图理解、计划制定、内容生成留给模型把模型不擅长的事情精确定位字符、解析结构化数据、调用外部系统交给确定性代码。而 Agent Skills正是承载这种分工的标准容器。1.3 “软件工程”在这里到底指什么很多人一听“走向软件工程”以为是要上微服务、K8s其实没那么玄。在 Agent Skills 的语境下软件工程指的是四件事版本管理技能包有版本号改了逻辑不影响旧调用方可以随时回滚。接口契约技能有明确的入参、出参、错误码调用方模型或代码可以按约定使用。测试机制每个技能可以写自动化测试跑一次就知道有没有改坏。依赖声明技能依赖哪些库、需要什么运行环境写清楚别人拿到就能跑。一句话把“能力”当作“软件模块”来管理而不是当作“灵感”来发挥。Agent Skills 恰好就是这套思维的最小落地单元。2. Agent Skills 到底是什么把能力封装成“可运行的说明书”2.1 核心概念描述文件 脚本 依赖清单我第一次理解 Agent Skills 时脑子里冒出来的类比是 Linux 的 man 手册加一个可执行文件。模型是用户它遇到任务时先去“翻手册”读技能描述发现手册说“这个活我能干”于是按手册里的指引去执行一段脚本最后把结果拿回来处理。一个标准的 Skill 包通常包含三类内容描述文件告诉模型这个技能什么时候用、怎么用、有哪些限制。核心是 SKILL.md 这类 Markdown 文档。可执行脚本真正干活的代码可以是 Python、JavaScript、Shell或者任何能在你的 Agent 运行环境里执行的语言。依赖与配置脚本所需的第三方库、环境变量、配置文件保证技能可以被干净地安装和运行。我见过的开源技能仓库里目录结构一般都长这样agent-skills/ ├── csv-to-json/ │ ├── SKILL.md │ ├── script.py │ └── requirements.txt ├── fetch-webpage/ │ ├── SKILL.md │ ├── fetch.py │ └── requirements.txt └── ...每个子目录就是一个独立技能。Agent 运行时按目录加载把 SKILL.md 的内容注入上下文让模型“知道有这个工具可用”等模型决定调用后再由运行时去执行对应脚本。2.2 一段描述与一段代码的本质区别理解 Agent Skills 的关键是分清“模型负责什么”和“代码负责什么”。描述文件负责的是“决策”。模型读到 SKILL.md认识到当前用户请求“把附件里的 Excel 转成 JSON”正好命中 csv-to-json 的技能描述于是决定调用它。这个决策过程是模型擅长的自然语言理解、意图匹配、参数提取。脚本负责的是“执行”。一旦模型确定了调用意图剩下的就交给脚本读文件、解析内容、做格式转换、输出结果。这段逻辑是确定性的不会因为模型心情不好就漏掉一行数据。传统 Prompt 工程把两件事混在一起决策靠文本执行也靠文本。模型既要理解意图又要“手算”每一个字符结果自然是既不高效也不可靠。Skills 的核心价值是让模型只在它擅长的层面做决策把高精密操作交给代码。2.3 常见的 Skill 类型根据我的观察现有生态里比较常见的技能包大致分四类信息检索类从文件、数据库、网页里提取指定内容输出结构化结果。典型如读取 PDF、抓取网页、查询本地文件。数据转换类把一种格式转成另一种。CSV 转 JSON、Markdown 转 HTML、日志清洗、字段映射。工具封装类把外部 API、命令行工具包成统一接口。比如调用天气接口返回结构化数据或者把图像压缩命令封装成一个函数。流程编排类把多步操作固化成固定流程。比如“下载附件 → 解压 → 校验 → 汇总”一条龙服务。这些技能的一个共同点是执行路径是确定的不需要模型临场发挥。只有“确定能做好的事”才值得封装成技能那些需要开放思考、创意生成的任务反而更适合直接对话式 Prompt。2.4 Prompt 工程与 Agent Skills 的一个直观对比我把两种方式在几个关键维度上的差异整理成了一张表方便对照理解维度Prompt 工程Agent Skills实现方式在提示词里描述指令和示例写代码实现功能写文档描述用途稳定性依赖模型对措辞的理解波动大代码逻辑固定结果可预期复用性换场景基本要重调技能包可跨 Agent、跨项目复用可测试性难以自动化断言可写单元测试和集成测试定位边界适用于开放任务、生成式任务适用于确定性任务、固定流程维护方式调提示词、试错、碰运气改代码、发版本、跑测试这张表可以解释为什么“技能包”的提法越来越流行它不是对 Prompt 工程的否定而是给 Agent 补上了一个可靠的“手和脚”让它不只是一个会说话的脑袋。3. 从零搭建一个可复用 Skill 的完整过程3.1 顶层目录与命名先定规矩再动手我建议在一开始就建一个单独的目录来放所有技能包比如agent-skills/。每一个技能包用短横线命名全部小写一眼能看出功能。我常用规则是“动词-对象”例如fetch-webpage、parse-pdf、csv-to-json。命名这个事看起来无关紧要但等你攒了二十个技能包之后就会明白命名混乱是最令人头疼的问题。模型是通过 SKILL.md 的描述来匹配技能的它不关心目录名但你自己需要一眼找到目标技能。另外很多运行时是按目录名加载技能的如果命名有歧义可能出现两个相似技能同时匹配用户请求的情况导致模型不知道选哪个。3.2 SKILL.md 要写清楚“什么情况用、怎么用、注意什么”SKILL.md 是整个技能包的灵魂但它不是给人类看的开发文档而是给模型看的“操作手册”。模型依据这份文档判断是否调用、如何传参所以写法跟传统 README 很不一样。我一般按这样的结构写# 技能名称 ## 功能概述 这个技能做了什么一句话说清。 ## 适用场景 什么时候应该调用这个技能触发条件是什么 什么时候不应该调用明确写出边界。 ## 输入参数 | 参数名 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | file_path | string | 是 | 待处理的文件路径 | | encoding | string | 否 | 文件编码默认 utf-8 | ## 输出格式 返回什么结构的数据给出一个具体示例。 ## 使用示例 举一个用户请求和技能调用的对应案例。 ## 注意事项 有哪些坑需要提醒模型例如“文件不存在时返回错误码 404不要尝试自行修复”。关键点在于“注意事项”这一节。模型看到技能描述后会在调用时自动遵循这些限制。如果你不写“文件不存在就报错”模型可能自作主张地编造一个空结果这是最典型的幻觉来源。写清楚边界等于把错误处理策略前置到决策阶段。3.3 核心脚本真的写代码而不是写提示词技能的核心脚本要遵循一个原则输入输出尽量接近 SKILL.md 里声明的契约用命令行接收参数用标准输出返回结果。这样无论 Agent 运行时是本地进程、容器还是沙箱都能统一对接。我以自己实现的一个 “markdown 表格转 CSV” 技能为例。脚本很短但足够说明问题#!/usr/bin/env python3 markdown_table_to_csv.py - 把 Markdown 表格转换为 CSV 格式。 import csv import io import re import sys def parse_markdown_table(md_text: str) - list[list[str]]: lines md_text.strip().splitlines() table_lines [line for line in lines if line.startswith(|)] if not table_lines: return [] header _split_row(table_lines[0]) rows [_split_row(line) for line in table_lines[2:]] return [header] rows def _split_row(line: str) - list[str]: stripped line.strip().strip(|) cells [cell.strip() for cell in stripped.split(|)] return cells def to_csv(rows: list[list[str]]) - str: output io.StringIO() writer csv.writer(output, lineterminator\n) writer.writerows(rows) return output.getvalue() def main(): md_text sys.stdin.read() rows parse_markdown_table(md_text) if not rows: print(json.dumps({error: no table found}), filesys.stderr) sys.exit(1) print(to_csv(rows)) if __name__ __main__: main()从这个例子可以看到核心逻辑完全由代码掌控正则匹配表格行、分隔单元格、跳过分隔行、用标准库解析 CSV 输出。没有给模型“发挥”的空间结果自然稳定。现实中更复杂的技能也遵循同一套模型标准输入进、标准输出出错误走 stderr 并附带非零退出码。3.4 依赖与运行环境让别人拿起来就能跑一个技能如果依赖第三方库必须在 requirements.txt 里声明清楚最好再附一句“用 Python 3.10 运行”。依赖声明看着琐碎但它是技能可复用的前提。我曾经接过别人的技能包里面用到了某个特定版本的数据处理库文档里完全没提结果在我本地环境一跑就报错排查了半天。我建议每个技能包里至少包含requirements.txt列出直接依赖的库尽量不要用“大于等于”的范围写法锁定主版本即可。一个简短的运行说明在 SKILL.md 的末尾附上“安装依赖pip install -r requirements.txt”。尽量使用标准库如果标准库能解决就别引入外部依赖。依赖越少技能越稳越不容易出现环境冲突。3.5 本地加载与测试先让脚本能跑再让模型能调技能目录和脚本写好后第一步是本地直接测功能不要立刻接进 Agent。我的做法是先测脚本本身echo | name | age |\n| --- | --- |\n| Alice | 30 | | python markdown_table_to_csv.py确认输出符合预期后再写几组边界输入测异常空表格、没有表头、多余空格。脚本的确定性保证在这里得到验证。下一步是“模拟模型调用”。这一步很关键找一个支持工具调用的 LLM 接口我常用本地模型和主流云模型各测一遍给它注入 SKILL.md 的内容并提供一个执行脚本的函数然后观察模型是否能在合适的时机调用技能、是否用对了参数。这一步能暴露很多问题最常见的是模型把技能描述里没写的参数传进来。模型对多个相似技能产生混淆。模型在技能返回错误后自作主张重试多次而不是把错误返回给用户。发现这些问题后修改的方向不是调提示词而是改 SKILL.md 的描述明确参数、边界和错误处理策略。这个过程很像在调接口文档而不是在调 Prompt。4. 工程化落地时的关键决策与避坑4.1 什么时候该用 Skill什么时候直接写代码一个我反复思考的问题技能包和普通函数到底有什么区别难道把功能写成一个 Python 函数、让 Agent 直接调用不就行了确实可以但 Agent Skills 有一个普通函数不具备的优势它自带“决策说明”。模型不需要在代码里看到函数定义才知道该用它而是通过读 SKILL.md 这样的自然语言描述在对话上下文中主动发现技能的存在。这意味着技能可以被动态加载Agent 遇到新任务时从技能仓库里检索并注入相关描述而不是把所有函数都塞进上下文。当技能数量有几十个上百个时这个差别是致命的。我的判断标准是如果这个功能会被多个 Agent 复用、需要独立于对话逻辑进行测试就封装成 Skill如果只是某个 Agent 内部的一次性 helper写普通函数就够了。4.2 测试策略不要只测代码还要测“模型的理解”技能的测试比普通代码多一个维度。普通代码测试的是“输入输出是否正确”技能测试还要加一条“模型看了 SKILL.md 之后能不能在正确时机调用它”。我用的测试方案分三层第一层是单元测试。只测脚本逻辑相当于你在任何 Python 项目里写的那种 testcase。第二层是描述测试。构造一批模拟的用户请求把 SKILL.md 和请求一起喂给模型看它是否触发了技能调用调用参数是否满足契约。这层测试很灵活我通常对每个技能准备 10 条左右的正例和负例正例里预期技能被触发负例里预期不触发。第三层是端到端测试。把技能装进一个真实的 Agent 应用里模拟用户完整对话流程验证最终体验。这层最花时间但最能发现问题。很多技能写失败不是代码写错了而是 SKILL.md 写得含糊模型压根不知道这个技能的存在或者在错误时机错误调用。每一层测试都能从不同角度拦住问题。4.3 版本管理与兼容性技能也会演化技能一旦接入线上 Agent就要考虑版本管理。我最开始完全忽略这点想改某个技能的行为直接改脚本结果旧对话里已经缓存的 Agent 行为和新行为不一致出了不少问题。现在的做法是给每个技能加version字段写在 SKILL.md 的头部。改代码时更新版本号并在头部记录变更摘要。这样即使运行时只加载了旧描述你也能从日志里追踪到具体版本快速定位问题。另一个跟版本相关的坑是“描述与代码不同步”。有时你只改了脚本逻辑忘了更新 SKILL.md模型还是在按旧描述调用技能参数对不上运行时报错。这个只能靠人为自律每次改脚本必须同步改描述最好在代码评审清单里加上这一条。4.4 安全边界别让模型把技能当万能执行器技能包的引入给 Agent 带来了执行能力也带来了新的安全风险。最典型的风险点有三个第一任意命令执行风险。如果技能包允许传入任意 Shell 命令模型一旦被提示词注入攻击就可能让 Agent 执行恶意命令。我的建议是技能接口尽量收窄不要暴露通用执行接口而是暴露“封装好的具体操作”比如“重命名文件”而不是“执行任意命令”。第二路径穿越风险。文件处理类技能如果直接拼接用户提供的路径可能读取到预期之外的文件。脚本里要校验输入路径是否在允许的根目录之内。第三外部资源访问。涉及网络请求的技能要限制目标域名范围避免 SSRF 类问题。我在封装网页抓取技能时就加了一个简单的域名白名单。安全设计不需要很复杂但必须有边界意识。技能越强大边界越要收紧。4.5 团队协作技能仓库也要有“评审机制”当多个人一起维护技能仓库时就出现了一个非常“软件工程”的问题如何保证技能质量。我所在的团队技能合并到主分支前至少要过三关代码评审看脚本是否有明显 bug、是否有安全隐患。描述评审看 SKILL.md 是否清楚、会不会误导模型。测试通过要求新增技能必须附带至少一层测试。这些流程不是凭空想出来的而是踩过坑之后沉淀的。曾经有一个技能包代码质量很高但 SKILL.md 里把适用场景写得太宽泛结果模型在用户问“怎么炖排骨”时调用了这个“读取菜谱文件”的技能场面一度失控。把描述评审加入流程之后这种低级误用明显少了很多。5. 从 Prompt 工程走向软件工程思维转变的几点体会5.1 关注点从“调模型”转移到“设计系统”当我开始用 Agent Skills 组织能力后一个很明显的感知是我不再那么关心“模型会不会按我的提示词行事”而是更关心“整个系统的边界和接口是否清晰”。Prompt 工程的核心动作是“试”反复修改措辞观察输出变化直到满意。这是调模型。而 Agent Skills 的核心动作是“建”确定边界定义接口实现逻辑测试验证版本管理。这是建系统。这种转变带来的是稳定性和可扩展性。以前每加一个能力都要重新调一遍 Prompt越调越复杂越复杂越不稳定现在加一个能力就是新建一个技能包装上之后哪里都能用。系统的复杂度从“一团提示词”变成了“一组可管理的模块”心智负担完全不同。5.2 对个人开发者来说这意味着“杠杆”技能包还有一个特别吸引我的特性可组合性。做一个调研类 Agent可以挂上“网页抓取”“PDF 解析”“摘要生成”三个技能做一个数据处理类 Agent可以挂上“CSV 清洗”“格式转换”“图表渲染”三个技能。同一套技能在不同 Agent 之间组合复用形成自己的“私人工具链”。技能包本质上是把“会写特定代码的人”的经验固化成可被任意语言模型调用的模块。这意味着你不用每次让模型从零开始理解某个业务领域只需要告诉它“这个活用这个技能干”它就能稳定输出结果。对于个体开发者这种组合式杠杆的价值怎么强调都不为过。5.3 最后分享一个技巧先做减法再做加法如果你正打算开始实践 Agent Skills我的核心建议是不要一开始就追求大而全的技能仓库先从你能想到的、最确定的、最重复性的任务开始。做一个最小技能包跑通从描述到调用到测试的完整流程再逐步扩展。我在实际操作中最常见的问题是“过度设计”把技能描述写得又长又细把脚本抽象得又泛又复杂结果模型反而不知道在什么场景下调用测试也花了大量时间维护。后来我改成“描述只写必要信息脚本只处理一种确定的输入输出”技能包的可用性反而直线上升。另外每次给 Agent 加完一个技能记得留出时间观察真实使用中的错误日志尤其是“模型想调用但没调用”“模型调用了但参数不对”这两类情况。它们是衡量 SKILL.md 质量最重要的信号。把这些信号定期梳理回填到描述文档里技能会越用越顺。这些经验不来自什么高深理论就是一次次改描述、改脚本、跑测试、看日志里磨出来的。Agent 的能力边界说到底是由你封装的这些“技能”决定的。从一段提示词到一个技能包不只是写法的变化更是对“Agent 如何工作”这件事理解的升级。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询