AI Agent 的 Skill 到底是什么?以一个 JMX 生成器为例讲清楚
AI Agent 的 Skill 到底是什么?以一个 JMX 生成器为例讲清楚
🔥 难度等级:进阶 | ⏰ 阅读时间:12 分钟 | 💡 实用指数:⭐⭐⭐⭐⭐
之前每次做压测都要手写 JMX 文件,一个接口写半天,改个参数还要翻 XML 文档。后来研究了一下 AI Agent 的 Skill 机制,用几行配置就搞定了从接口文档到 JMX 的自动生成,现在 10 分钟能搞定以前 2 小时的活。今天把这个思路完整分享出来,看完你就知道 Skill 到底是个啥,以及怎么用它解决实际工作里的重复劳动。
一、Skill 是什么?一句话讲清楚
Skill 就是 AI 的外挂技能包。
咱们平时跟 ChatGPT、Claude 聊天,靠的是模型自带的"通识能力"——你问啥它都能答一点,但碰到专业场景就经常"好像懂了又好像没懂"。
Skill 解决的就是这个问题:它把特定领域的知识、工具调用方式、处理流程打包成一个可插拔的模块,AI 遇到对应场景就加载这个模块,执行标准化操作。
打个比方:
- Prompt 工程 = 你每次都要跟新手厨师口述一遍"番茄炒蛋怎么做"
- Skill = 你直接把菜谱贴在墙上,厨师看到"番茄炒蛋"四个字就知道每一步该干嘛
Skill 的特点就三个:
- 可复用——写一次,所有会话都能用
- 确定性——不像 Prompt 那样每次结果都飘,Skill 里写的是死逻辑
- 可组合——多个 Skill 可以叠加,比如"读接口文档"+“生成 JMX”+“上传到 JMeter”
二、Prompt 工程 vs Skill:为什么后者更靠谱
我知道很多人会说:“我用 Prompt 也能让 AI 生成 JMX 啊,写个 Prompt 不就完了?”
确实能。但用 Prompt 搞过生产环境的人都知道,这里有三个坑:
| 坑 | Prompt 方案 | Skill 方案 |
|---|---|---|
| 每次都要重复教 | 每次新开对话都要贴一遍格式要求 | Skill 写好就常驻,自动触发 |
| 结果不稳定 | 这次生成的 XML 格式对了,下次可能就缺个闭合标签 | Skill 里写死 XML 模板,输出格式固定 |
| 没法调用工具 | Prompt 只能让 AI"描述"怎么做,实际执行还得你手动来 | Skill 可以直接调本地脚本、写文件、发请求 |
举个例子,我要把 Swagger 接口文档转成 JMX。用 Prompt 的话,我得每次都说:
“请根据以下 JSON 生成 JMeter 的 JMX 文件,注意 ThreadGroup 要配置 10 个线程,Ramp-Up 5 秒,HTTPSamplerProxy 的 domain 要填 api.example.com…”
用 Skill 的话,AI 看到 Swagger JSON 就直接知道:
- 解析
paths拿到所有接口 - 每个接口生成一个
HTTPSamplerProxy - 统一套
ThreadGroup模板 - 输出标准 JMX XML
不需要你教,它已经会了。
三、实战:我的"接口文档生成 JMX" Skill
好了,说那么多不如直接看代码。下面是我自用的 Skill,功能就是:输入 Swagger/YAPI 接口文档的 JSON,输出可直接导入 JMeter 的 .jmx 文件。
3.1 Skill 的结构
Skill 本质上就是一个带元数据的 Markdown 文件,长这样:
api-to-jmx/
SKILL.md ← 核心逻辑和触发条件
scripts/
generate_jmx.py ← 生成 JMX 的实际脚本
assets/
jmx_template.xml ← JMX 的 XML 模板
3.2 SKILL.md 内容
---
name: api-to-jmx
description: "Parse Swagger/YAPI API docs and generate JMeter .jmx test plans."
---
# API to JMX
Generate JMeter test plans from API documentation.
## When to use
- User provides Swagger JSON or YAPI export
- User says "生成 JMX" / "转成 JMeter" / "压测脚本"
- User pastes API doc and mentions load testing
## Workflow
1. **Parse input**: Accept Swagger 2.0/OpenAPI 3.0 JSON or YAPI JSON.
2. **Extract endpoints**: For each `path` + `method`, collect:
- URL (base + path)
- HTTP method
- Query/body parameters
- Headers (Content-Type, auth tokens)
3. **Build JMX**: Use the XML template, insert one `HTTPSamplerProxy` per endpoint.
4. **Configure ThreadGroup**: Default 10 threads, 5s ramp-up, 1 loop (user can override).
5. **Output**: Write `.jmx` file to workspace.
## Parameters (user can override)
- `threads`: number of threads (default: 10)
- `ramp_up`: ramp-up period in seconds (default: 5)
- `duration`: test duration in seconds (default: 60)
- `base_url`: override the server URL from Swagger
## Output format
Standard JMeter JMX XML (version 5.6+ compatible).
3.3 核心生成脚本
# scripts/generate_jmx.py
import json
import xml.etree.ElementTree as ET
from xml.dom import minidom
def generate_jmx(swagger_json, threads=10, ramp_up=5, duration=60, base_url=None):
"""
从 Swagger JSON 生成 JMeter JMX 文件
"""
# 解析 Swagger
data = json.loads(swagger_json)
# 获取 base URL
if base_url:
server_url = base_url
elif 'servers' in data: # OpenAPI 3.0
server_url = data['servers'][0]['url']
elif 'host' in data: # Swagger 2.0
scheme = data.get('schemes', ['https'])[0]
server_url = f"{scheme}://{data['host']}{data.get('basePath', '')}"
else:
server_url = "https://api.example.com"
# 创建 JMX XML 根节点
jmeter_test_plan = ET.Element("jmeterTestPlan", version="1.2", properties="5.0")
hash_tree = ET.SubElement(jmeter_test_plan, "hashTree")
# 添加 TestPlan
test_plan = ET.SubElement(hash_tree, "TestPlan", guiclass="TestPlanGui",
testclass="TestPlan", testname="API Load Test", enabled="true")
ET.SubElement(test_plan, "stringProp", name="TestPlan.comments").text = "Auto-generated from Swagger"
ET.SubElement(test_plan, "boolProp", name="TestPlan.functional_mode").text = "false"
ET.SubElement(test_plan, "boolProp", name="TestPlan.tearDown_on_shutdown").text = "true"
test_plan_hash = ET.SubElement(hash_tree, "hashTree")
# 添加 ThreadGroup
thread_group = ET.SubElement(test_plan_hash, "ThreadGroup", guiclass="ThreadGroupGui",
testclass="ThreadGroup", testname="Thread Group", enabled="true")
ET.SubElement(thread_group, "stringProp", name="ThreadGroup.on_sample_error").text = "continue"
ET.SubElement(thread_group, "elementProp", name="ThreadGroup.main_controller",
elementType="LoopController", guiclass="LoopControlPanel",
testclass="LoopController", testname="Loop Controller", enabled="true")
ET.SubElement(thread_group, "stringProp", name="ThreadGroup.num_threads").text = str(threads)
ET.SubElement(thread_group, "stringProp", name="ThreadGroup.ramp_time").text = str(ramp_up)
ET.SubElement(thread_group, "boolProp", name="ThreadGroup.scheduler").text = "true"
ET.SubElement(thread_group, "stringProp", name="ThreadGroup.duration").text = str(duration)
thread_hash = ET.SubElement(test_plan_hash, "hashTree")
# 为每个接口生成 HTTPSamplerProxy
paths = data.get('paths', {})
for path, methods in paths.items():
for method, details in methods.items():
if method in ['get', 'post', 'put', 'delete', 'patch']:
sampler = create_http_sampler(path, method.upper(), details, server_url)
thread_hash.append(sampler)
# 每个 sampler 后面加一个空的 hashTree
thread_hash.append(ET.Element("hashTree"))
# 美化输出 XML
rough_string = ET.tostring(jmeter_test_plan, encoding='unicode')
reparsed = minidom.parseString(rough_string)
pretty = reparsed.toprettyxml(indent=" ")
# 去掉空行
lines = [line for line in pretty.split('\n') if line.strip()]
return '\n'.join(lines)
def create_http_sampler(path, method, details, server_url):
"""创建单个 HTTP Sampler"""
sampler = ET.Element("HTTPSamplerProxy", guiclass="HttpTestSampleGui",
testclass="HTTPSamplerProxy",
testname=details.get('summary', details.get('operationId', path)),
enabled="true")
# URL
ET.SubElement(sampler, "stringProp", name="HTTPSampler.domain").text = server_url.replace("https://", "").replace("http://", "").split('/')[0]
ET.SubElement(sampler, "stringProp", name="HTTPSampler.port").text = ""
ET.SubElement(sampler, "stringProp", name="HTTPSampler.protocol").text = "https"
ET.SubElement(sampler, "stringProp", name="HTTPSampler.path").text = path
ET.SubElement(sampler, "stringProp", name="HTTPSampler.method").text = method
# 如果有参数,添加到 arguments
params = details.get('parameters', [])
if params:
args = ET.SubElement(sampler, "elementProp", name="HTTPsampler.Arguments",
elementType="Arguments", guiclass="HTTPArgumentsPanel",
testclass="Arguments", testname="User Defined Variables", enabled="true")
collection = ET.SubElement(args, "collectionProp", name="Arguments.arguments")
for param in params:
if param.get('in') in ['query', 'path']:
arg = ET.SubElement(collection, "elementProp", name=param['name'],
elementType="Argument")
ET.SubElement(arg, "stringProp", name="Argument.name").text = param['name']
ET.SubElement(arg, "stringProp", name="Argument.value").text = "${" + param['name'].upper() + "}"
ET.SubElement(arg, "stringProp", name="Argument.metadata").text = "="
ET.SubElement(sampler, "boolProp", name="HTTPSampler.follow_redirects").text = "true"
ET.SubElement(sampler, "boolProp", name="HTTPSampler.auto_redirects").text = "false"
ET.SubElement(sampler, "boolProp", name="HTTPSampler.use_keepalive").text = "true"
ET.SubElement(sampler, "boolProp", name="HTTPSampler.DO_MULTIPART_POST").text = "false"
return sampler
# 使用示例
if __name__ == "__main__":
swagger = '''
{
"swagger": "2.0",
"host": "api.example.com",
"basePath": "/v1",
"paths": {
"/users": {
"get": {
"summary": "Get user list",
"parameters": [
{"name": "page", "in": "query", "type": "integer"}
]
}
}
}
}
'''
jmx = generate_jmx(swagger, threads=50, ramp_up=10, duration=300)
with open("api_test.jmx", "w", encoding="utf-8") as f:
f.write(jmx)
print("JMX 文件已生成: api_test.jmx")
3.4 使用效果
假设我拿到一个 Swagger JSON,复制粘贴给 AI,说一句"生成 JMX",然后 AI 就会:
- 自动触发
api-to-jmxSkill - 解析 JSON 里的所有接口
- 生成标准的 JMX XML 文件
- 保存到本地
整个过程不需要我手写一行 XML,也不需要我每次都教 AI “JMX 文件长什么样”。
四、我还写了哪些测试相关的 Skill
既然搞清楚了 Skill 的机制,我就顺手搭了一套测试开发常用的 Skill 组合:
| Skill 名称 | 功能 | 触发词 |
|---|---|---|
api-to-jmx |
接口文档 → JMeter 脚本 | “生成 JMX” |
report-pusher |
测试报告 → 飞书/钉钉群 | “推送报告” |
log-analyzer |
分析压测日志,自动出瓶颈点 | “分析日志” |
bug-summarizer |
批量 Bug 列表 → 分类汇总 | “总结 Bug” |
env-checker |
检查测试环境各服务状态 | “检查环境” |
这些 Skill 之间还能联动。比如:
生成 JMX → 执行压测 → 分析日志 → 推送报告
一条指令串下来,以前半天的活现在几分钟搞定。
五、怎么开始写你自己的 Skill
如果你也想试试,步骤很简单:
- 找个重复劳动的场景——你每周都要做、每次都要教 AI 的事
- 写一个 SKILL.md——描述触发条件、处理流程、输出格式
- 配一个脚本(可选)——复杂逻辑用 Python/Shell 写,Skill 里调用
- 放到 AI 的 skill 目录——OpenClaw 这类框架会自动加载
一个好的 Skill 判断标准:
- 你自己用 Prompt 写过 3 次以上
- 每次写 Prompt 都觉得"怎么又要说一遍"
- 有明确的输入格式和输出格式
- 流程固定,不需要 AI “自由发挥”
六、总结
Skill 不是啥高大上的新概念,本质上就是把"反复教 AI 的东西"固化下来。
对于测试开发来说,特别适合做 Skill 的场景:
- 接口文档转各种格式(JMX、Postman collection、YAML)
- 测试报告的格式化与推送
- 环境检查的标准化脚本
- 常见报错的自动诊断
Prompt 工程解决的是"怎么让 AI 听懂",Skill 解决的是"让 AI 记住并自动执行"。 两者不冲突,但到生产环境里,Skill 的稳定性和效率明显更靠谱。
💡 原创不易,如果这篇文章帮到了你,麻烦点个赞、收藏一下,也欢迎关注我的博客,后续会持续分享测试开发相关的实战干货。
更多推荐



所有评论(0)