这次我们直接切入一个 AI 工程化里越来越痛的问题:Agent 的 Skills 到底怎么测?
很多团队做 Agent 开发时,Skill 写了一大堆,跑起来也像模像样,但一上线就出问题。要么 Skill 在独立调用时正常,放进 Agent 工作流里就抽风;要么大模型返回的结果格式时好时坏,手工点几轮没问题,一到批量任务就翻车。原因很简单:Skills 是给 LLM 用的函数,但它本质还是代码,代码就要测试,而且不能只测一种维度。
这篇文章不聊概念,只讲三套能落地的测试方案:Pytest 单元测试、LLM/LVM 自动评价、Agent 集成测试。每一套都会给出环境、代码、运行方式和验证标准,你能直接抄进自己的项目里。
核心关注点有三个:怎么用 Pytest 把 Skill 的纯逻辑测清楚;怎么引入 LLM 和 LVM 当“自动阅卷老师”,检查多模态输出质量;怎么在完整 Agent 调用链里验证 Skill 是否被正确触发和返回。读者对象是正在做 AI Agent 开发、Skill 中间件封装或 LLM 应用测试的工程师。如果你团队里已经有“Skill 写了没人敢改”的问题,这篇文章建议直接收藏。
1. Skill 测试三大方案核心能力速览
先给一张总表,把三套方案分别解决什么问题、需要什么工具、成本高不高说清楚。
| 测试方案 | 核心定位 | 主要工具 | 验证目标 | 成本与门槛 |
|---|---|---|---|---|
| 方案一:Pytest 单元测试 | 验证 Skill 的确定性逻辑 | Pytest、pytest-asyncio、pytest-cov | 输入输出格式、边界条件、异常处理、纯函数正确性 | 低,CPU 即可运行,速度快 |
| 方案二:LLM/LVM 自动评价 | 验证 Skill 与大模型协作后的输出质量 | LLM API、LVM 多模态模型、结构化评测脚本 | 输出是否符合指令、是否包含关键字段、多模态内容是否准确 | 中,需要 API 额度或本地模型,结果有概率波动 |
| 方案三:Agent 集成测试 | 验证 Skill 在真实调用链中的行为 | Agent 框架、Mock 工具、端到端测试脚本 | Skill 是否被正确触发、参数传递是否正确、异常是否被兜底 | 高,需要完整运行环境,耗时较长 |
三套方案不是替代关系,而是分层关系。Pytest 管“代码正确性”,LLM/LVM 评价管“输出合理性”,Agent 集成测试管“链路稳定性”。三套都做齐,Skill 才算真正有质量保障。
方案二里需要多说一句 LVM 和 LLM 的区别。LLM 处理的是文本输入输出,负责理解指令、生成结构化内容;LVM(Large Vision Model,大视觉模型)则能直接接收图片、视频等视觉输入,在图生文、图片理解、视觉问答这类任务上表现更强。如果你们的 Skill 涉及图像分析、文档 OCR、截图理解,那么方案二的评测模型优先考虑 LVM;如果只是纯文本生成,LLM 就够用。
2. Skill 测试适用场景与合规边界
2.1 适合谁用
这套测试体系并不是所有项目都需要全套上马,按阶段选择即可:
- 个人开发者或小团队:如果 Skill 数量少于 10 个,建议先跑方案一。保证每个 Skill 的纯逻辑不出错,已经能规避大部分低级故障。
- 中大型 Agent 项目:Skill 数量多、相互依赖复杂,必须叠加方案三。尤其是多个 Skill 共享上下文、交叉调用的时候,单测覆盖不到的集成问题会集中爆发。
- 对外提供 API 服务的团队:方案二建议长期运行。因为外部用户会输入各种意想不到的文本和图片,LLM/LVM 评价能帮你提前拦截“答非所问”和“格式不符合要求”的输出。
2.2 不适合什么场景
不是所有环节都适合自动化测试。比如:
- 非常依赖 LLM 随机创造力的功能:像头脑风暴、开放式文案生成,用固定评测标准去打分反而会误伤。这种场景应该用人工抽检。
- 延迟极其敏感的服务:方案二和方案三会引入模型推理时间,不适合放在请求链路的同步环节里做实时校验。
- 尚未稳定的原型阶段:如果 Skill 的输入输出协议还在频繁变动,先别急着写测试,否则每改一次接口就要重写一批用例。
2.3 使用边界与合规提醒
涉及 LLM/LVM 测试,有几点必须反复强调:
- 数据隐私:生产环境的用户输入可能包含个人信息、商业机密。评测数据要先做脱敏处理,原则上不允许直接把真实用户数据发送给第三方模型 API。
- 版权授权:如果 Skill 是图像生成、音色克隆、视频处理类,测试素材必须确认版权归属。拿他人作品当测试集,风险很高。
- 模型输出不可控:LLM/LVM 的判定结果天然带有概率性。评测失败不代表功能一定坏,要结合日志和人工复核,不能盲目用模型评价结果去卡发布流程。
- 安全边界:不要用测试代码去尝试绕过模型的内容安全策略。测试目标是验证功能符合预期,不是攻击模型。
3. Skill 测试环境准备与前置条件
在写测试代码之前,先把环境准备好。这里给出一个经过验证的通用配置思路。
3.1 操作系统与 Python 版本
建议使用 Linux 或 macOS 作为开发和 CI 运行环境。Windows 也可以跑,但遇到 asyncio 事件循环和模型 SDK 时,偶尔会有进程管理上的差异。
Python 版本建议 3.10 及以上。原因是 AI Agent 相关框架对 3.10+ 的支持最稳定,类型注解语法也更完整。
3.2 Python 依赖清单
创建虚拟环境后,安装下面这些依赖:
python -m venv .venv source .venv/bin/activate # 核心测试框架 pip install pytest pytest-asyncio pytest-cov # HTTP 请求与 API 调用 pip install requests # 结构化输出校验(可选) pip install pydantic # Agent 测试的 Mock 工具(按实际框架选择) pip install unittest-mock # 或者使用内置 unittest.mock如果项目里已经用了某一款 Agent 框架,比如 OpenAI SDK、LangChain、Claude Agent SDK 等,把对应 SDK 也装上。本文的示例代码不绑定具体 Agent 框架,会以 pytest + requests 作为基础演示。
3.3 环境变量配置
模型 API 的 Key 不要写死在代码里。建议统一放在.env文件,并用环境变量读取。
# .env 示例 LLM_API_KEY=your_llm_api_key LLM_BASE_URL=https://api.example.com/v1 LVM_API_KEY=your_lvm_api_key LVM_BASE_URL=https://api.example.com/v1在测试代码中加载:
import os from dotenv import load_dotenv load_dotenv() LLM_API_KEY = os.getenv("LLM_API_KEY") LLM_BASE_URL = os.getenv("LLM_BASE_URL")这样做的好处是:CI 里不用改代码,只需要注入环境变量。
3.4 项目目录结构
推荐按下面的结构组织项目,能让测试文件和被测代码清晰分离。
skill_project/ ├── skills/ # Skill 核心实现 │ ├── __init__.py │ ├── image_skill.py # 示例:图像信息提取 Skill │ └── text_skill.py # 示例:文本摘要 Skill ├── tests/ # 测试文件目录 │ ├── __init__.py │ ├── conftest.py # pytest 全局配置 │ ├── test_image_skill_unit.py # 方案一:单元测试 │ ├── test_text_skill_unit.py │ ├── eval_llm.py # 方案二:LLM/LVM 评价 │ ├── eval_lvm.py │ ├── test_agent_integration.py # 方案三:Agent 集成测试 │ └── data/ # 测试输入素材 │ ├── sample_image.jpg │ └── sample_text.txt ├── .env ├── requirements.txt └── pyproject.toml4. Skill 测试项目搭建:从一个示例 Skill 开始
为了把三套方案讲清楚,这里先构造一个可运行的示例 Skill。它能调用多模态模型,从一张商品图片中提取信息,并返回结构化 JSON。这个 Skill 同时涉及 LLM 和 LVM,用来演示测试方案非常合适。
4.1 Skill 实现示例
# skills/image_skill.py """ 图像信息提取 Skill。 输入:图片路径 输出:结构化商品信息,包括名称、颜色、品牌、标签 """ import json from typing import Dict, Any import requests class ImageInfoSkill: def __init__(self, base_url: str, api_key: str): self.base_url = base_url self.api_key = api_key def extract_info(self, image_path: str, prompt: str = "") -> Dict[str, Any]: """ 调用 LVM 模型,从图片中提取结构化信息。 """ if not image_path: raise ValueError("image_path cannot be empty") # 实际项目中这里换成对应 LVM SDK 的调用方式 response = requests.post( f"{self.base_url}/v1/vision/extract", headers={"Authorization": f"Bearer {self.api_key}"}, json={ "image_path": image_path, "prompt": prompt, }, timeout=60, ) response.raise_for_status() result = response.json() # 对返回内容做基础格式校验 return self._normalize_output(result) @staticmethod def _normalize_output(raw: Dict[str, Any]) -> Dict[str, Any]: """ 统一返回 JSON 结构,保证键名和类型一致。 """ required_keys = ["name", "color", "brand", "tags"] normalized = { "name": raw.get("name", ""), "color": raw.get("color", ""), "brand": raw.get("brand", ""), "tags": raw.get("tags", []), } # tags 必须是列表 if not isinstance(normalized["tags"], list): normalized["tags"] = [] return normalized def validate_result(self, data: Dict[str, Any]) -> bool: """ 校验结构化输出是否满足要求。 """ if not isinstance(data, dict): return False if not data.get("name"): return False if not isinstance(data.get("tags"), list): return False return True这个 Skill 的输入输出协议很清晰:输入图片路径,输出{name, color, brand, tags}四个字段。接下来三套测试都围绕它展开。
5. 方案一:Pytest 单元测试 Skill 的确定性逻辑
5.1 测试目标
单元测试的目标是“不依赖真实模型,验证代码本身的正确性”。我们把 Skill 拆成两个部分来测:
- 对空输入、非法输入的异常处理。
- 对模型返回结果的标准化和校验逻辑。
真实 HTTP 调用必须被 Mock 掉,这样才能保证测试速度快、可重复。
5.2 编写 Mock 测试
使用 pytest + unittest.mock 实现:
# tests/test_image_skill_unit.py import json import pytest from unittest.mock import patch, Mock from skills.image_skill import ImageInfoSkill @pytest.fixture def skill(): """构造不带真实 API Key 的 Skill 实例。""" return ImageInfoSkill( base_url="http://mock.example.com", api_key="test-key", ) def test_extract_info_success(skill): """测试正常流程:模型返回合法数据。""" mock_response = Mock() mock_response.json.return_value = { "name": "无线蓝牙耳机", "color": "白色", "brand": "TestBrand", "tags": ["耳机", "蓝牙", "白色"], } mock_response.raise_for_status.return_value = None with patch("skills.image_skill.requests.post", return_value=mock_response): result = skill.extract_info("data/sample_image.jpg") assert result["name"] == "无线蓝牙耳机" assert result["color"] == "白色" assert isinstance(result["tags"], list) assert len(result["tags"]) == 3 def test_extract_info_empty_path(skill): """测试异常分支:图片路径为空。""" with pytest.raises(ValueError): skill.extract_info("") def test_normalize_output_missing_keys(skill): """测试模型返回字段缺失时,normalize 能补齐默认值。""" raw = { "name": "杯子", "color": "蓝色", } result = skill._normalize_output(raw) assert result["brand"] == "" assert result["tags"] == [] def test_validate_result_reject_invalid(skill): """测试校验函数能识别非法输出。""" invalid_data = {"name": "", "tags": "not-a-list"} assert skill.validate_result(invalid_data) is False5.3 运行方式
pytest tests/test_image_skill_unit.py -v --cov=skills预期输出类似:
tests/test_image_skill_unit.py::test_extract_info_success PASSED tests/test_image_skill_unit.py::test_extract_info_empty_path PASSED tests/test_image_skill_unit.py::test_normalize_output_missing_keys PASSED tests/test_image_skill_unit.py::test_validate_result_reject_invalid PASSED5.4 方案一成功标准
- 全部用例通过。
- 代码覆盖率报告里
skills/image_skill.py的关键分支都被覆盖。 - 不发起真实网络请求,执行时间在几秒内。
这一层跑通,说明 Skill 的纯逻辑是稳的。但单元测试有个盲区:它只验证了“模型返回合法数据时,我们的处理逻辑正确”,没验证“真实模型会不会返回合法数据”。这就是方案二要解决的问题。
6. 方案二:LLM/LVM 自动评价 Skill 输出质量
6.1 测试目标
方案二不再 Mock 模型,而是真实调用 LLM 或 LVM,让大模型对 Skill 的输出进行评价。这里的关键是:设计一套让模型能“客观”打分的评测标准。
理论上,我们完全可以用人工去看几十张图片的提取效果。但批量任务几百上千条的时候,人工不现实,必须用模型当自动阅卷老师。
6.2 设计评测 prompt
这里有一个实战经验:评测 prompt 必须比业务 prompt 更严格。要求大模型输出 JSON 结构,并强制给出“通过/不通过”和“原因”。
# tests/eval_lvm.py import json import requests def build_eval_prompt(image_path: str, extracted_result: dict) -> list: """ 构造 LVM 评测 prompt。 参数说明: image_path: 原始测试图片路径 extracted_result: Skill 从图片中提取出的结构化信息 """ system_prompt = ( "你是一个严谨的视觉信息评测员。" "你需要查看原始图片,并判断给定的结构化信息是否准确。" "只输出 JSON,不要输出多余文字。" ) user_prompt = { "task": "请对比以下从商品图片中提取的信息是否准确。", "image_path": image_path, "extracted_info": extracted_result, "eval_rules": [ "如果名称、颜色、品牌、标签任一字段明显错误,结论为不通过。", "如果标签缺失关键属性,结论为不通过。", "如果信息和图片完全一致,结论为通过。", ], "output_format": { "passed": "boolean", "reason": "string, 不超过50字" } } return [ {"role": "system", "content": system_prompt}, {"role": "user", "content": json.dumps(user_prompt, ensure_ascii=False)}, ] def call_lvm_eval(base_url: str, api_key: str, image_path: str, extracted_result: dict) -> dict: """ 调用 LVM 模型执行评测。 """ messages = build_eval_prompt(image_path, extracted_result) response = requests.post( f"{base_url}/v1/vision/eval", headers={"Authorization": f"Bearer {api_key}"}, json={ "model": "lvm-eval-model", "messages": messages, }, timeout=120, ) response.raise_for_status() return response.json()6.3 批量评测脚本
实际项目中,方案二通常不在 pytest 里跑,而是单独作为一个评测脚本,因为要控制 API 成本和耗时。
# tests/eval_llm.py """ 批量评测 Skill 输出,并输出统计报告。 """ import json import os from typing import List, Dict import requests from skills.image_skill import ImageInfoSkill def load_test_cases(test_file: str) -> List[Dict]: """ 读取测试用例。测试用例文件格式为 JSON List。 """ with open(test_file, "r", encoding="utf-8") as f: return json.load(f) def run_evaluation(): base_url = os.getenv("LVM_BASE_URL") api_key = os.getenv("LVM_API_KEY") skill = ImageInfoSkill(base_url=base_url, api_key=api_key) # 测试用例:实际项目中每个用例包含图片路径和期望字段 test_cases = [ {"image_path": "data/sample_image.jpg", "expected_tags": ["耳机", "蓝牙"]}, {"image_path": "data/sample_cup.jpg", "expected_tags": ["杯子"]}, ] results = [] for case in test_cases: extracted = skill.extract_info(case["image_path"]) eval_result = call_lvm_eval(base_url, api_key, case["image_path"], extracted) results.append({ "case": case, "extracted": extracted, "eval": eval_result, }) # 统计通过率 passed = sum(1 for r in results if r["eval"].get("passed") is True) total = len(results) print(f"评测完成:通过 {passed}/{total},通过率 {passed / total * 100:.1f}%") # 输出详细结果到文件 with open("eval_report.json", "w", encoding="utf-8") as f: json.dump(results, f, ensure_ascii=False, indent=2) if __name__ == "__main__": run_evaluation()6.4 方案二成功标准
- 批量评测脚本可稳定运行,默认测试集通过率不低于团队设定的阈值。
- 评测报告能明确指出哪些图片提取失败、失败原因是什么。
- 同一批数据多次评测,结果波动在可接受范围内(如果波动过大,优先检查评测 prompt 是否写清楚了)。
这里要特别提醒:LLM/LVM 评测结果天然有随机性。同一个输出,用不同模型、不同温度参数,评分结果可能不一样。建议在评测请求里固定temperature=0,尽可能降低随机性。
7. 方案三:Agent 集成测试验证完整调用链
7.1 测试目标
前面两套方案验证了“Skill 本身正确”和“Skill 输出质量合格”。但真正上线时,Skill 是在 Agent 的完整调用链中运行的,流程是:
用户输入 -> Agent 理解意图 -> 选择 Skill -> 传参调用 -> 拿结果回填 -> 生成最终回复这个链路中经常出现的问题有:
- Agent 没有在应该调用 Skill 的时候调用它。
- 调用 Skill 时传参错误,比如把图片路径传成了文本。
- Skill 返回了异常,但 Agent 没有兜底处理,直接把错误暴露给用户。
方案三要验证的正是这些链路问题。
7.2 集成测试代码示例
这里不绑定具体 Agent 框架,用一个简化版的自定义 Agent 引擎来演示。实际项目中,把agent.run()换成你们自己的入口即可。
# tests/test_agent_integration.py """ Agent 集成测试:验证 Agent 能否正确调用 ImageInfoSkill。 """ import os import pytest from unittest.mock import patch, Mock from skills.image_skill import ImageInfoSkill class SimpleAgent: """ 简化版 Agent 引擎。 真实项目中这里是你们的 Agent 执行流程, 支持意图识别、Skill 选择、参数注入、结果回填。 """ def __init__(self, skill: ImageInfoSkill): self.skill = skill def handle_request(self, user_input: str, image_path: str = None): """ 处理用户请求。 模拟: 1. 如果输入中包含“提取”关键词,调用 image skill。 2. 如果调用失败,返回兜底话术。 """ if "提取" in user_input and image_path: try: result = self.skill.extract_info(image_path) return {"success": True, "data": result} except Exception as e: return {"success": False, "error": str(e)} return {"success": False, "error": "no skill matched"} @pytest.fixture def agent(): skill = ImageInfoSkill( base_url="http://mock.example.com", api_key="test-key", ) return SimpleAgent(skill) def test_agent_calls_skill_when_input_matches(agent): """ 验证:用户输入包含关键词时,Agent 应该触发 Skill。 """ mock_response = Mock() mock_response.json.return_value = { "name": "无线蓝牙耳机", "color": "白色", "brand": "TestBrand", "tags": ["耳机", "蓝牙"], } mock_response.raise_for_status.return_value = None with patch("skills.image_skill.requests.post", return_value=mock_response): result = agent.handle_request("帮我提取图片信息", "data/sample_image.jpg") assert result["success"] is True assert result["data"]["name"] == "无线蓝牙耳机" def test_agent_does_not_call_skill_when_input_not_match(agent): """ 验证:用户输入不包含触发词时,Agent 不应调用 Skill。 """ result = agent.handle_request("你好", "data/sample_image.jpg") assert result["success"] is False assert result["error"] == "no skill matched" def test_agent_handles_skill_exception(agent): """ 验证:Skill 抛异常时,Agent 能返回兜底错误,而不是直接崩溃。 """ with patch( "skills.image_skill.requests.post", side_effect=Exception("network error"), ): result = agent.handle_request("帮我提取图片信息", "data/sample_image.jpg") assert result["success"] is False assert "network error" in result["error"]7.3 真实环境集成测试
Mock 版集成测试通过后,还需要跑一轮“真实环境不 Mock”的集成测试。方法很简单:单独建一个 pytest 标记,只有显式指定时才运行。
pytest tests/test_agent_integration.py -m real_api标记的定义放在pyproject.toml或pytest.ini里:
# pytest.ini [pytest] markers = real_api: 标记需要真实调用模型 API 的集成测试测试代码中加装饰器:
import pytest @pytest.mark.real_api def test_agent_with_real_model(): # 这里是真实调用 LVM 的用例 pass7.4 方案三成功标准
- 所有 Mock 集成测试通过,说明 Agent 的调用逻辑正确。
- 真实 API 集成测试通过,说明模型返回结果能被 Agent 正确消化。
- 人为制造异常(比如断网、超时)时,Agent 能返回兜底信息,不会挂死。
8. 把三套方案接进 CI 与批量任务
8.1 分层执行策略
真实项目里,三套方案的执行频率不一样:
| 触发时机 | 执行内容 | 原因 |
|---|---|---|
| 每次提交代码 | 方案一 Pytest 单元测试 | 速度最快,能第一时间发现代码逻辑问题 |
| 每天定时任务 | 方案二 LLM/LVM 评测 | 消耗 API 额度,不需要每次提交都跑 |
| 发版前 / 每轮迭代 | 方案三 Agent 集成测试 | 链路完整但耗时较长,适合在发版前统一跑 |
8.2 GitHub Actions 示例
# .github/workflows/skill-test.yml name: Skill Test Pipeline on: push: branches: [main] pull_request: branches: [main] jobs: unit-test: runs-on: ubuntu-latest steps: - name: 拉取代码 uses: actions/checkout@v4 - name: 安装 Python uses: actions/setup-python@v5 with: python-version: "3.11" - name: 安装依赖 run: | pip install pytest pytest-asyncio pytest-cov requests python-dotenv - name: 运行单元测试 run: | pytest tests/test_*_unit.py -v --cov=skills eval-test: runs-on: ubuntu-latest needs: unit-test if: github.event_name == 'schedule' env: LVM_API_KEY: ${{ secrets.LVM_API_KEY }} LVM_BASE_URL: ${{ secrets.LVM_BASE_URL }} steps: - name: 拉取代码 uses: actions/checkout@v4 - name: 安装 Python uses: actions/setup-python@v5 with: python-version: "3.11" - name: 安装依赖 run: | pip install pytest requests python-dotenv - name: 运行 LVM 评测 run: | python tests/eval_lvm.py这套流水线把“快速校验”和“深度评测”分开,不会因为等模型 API 响应而拖慢开发流程。
8.3 批量任务设计建议
如果测试用例数量达到几百上千条,批量评测任务要关注以下几点:
- 控制并发:LLM/LVM API 都有速率限制,评测脚本要支持限流。可以用
threading.Semaphore或者信号量控制同时发出的请求数。 - 失败重试:网络抖动导致单条评测失败时,不要直接判失败,先重试 2 到 3 次。
- 结果落盘:每条评测结果都要写入 JSONL 或数据库,方便追溯失败样本。
- 进度条显示:用
tqdm显示批量任务进度,避免任务看起来像卡死。
# 批量评测时控制并发和重试的示例 import time from concurrent.futures import ThreadPoolExecutor, as_completed import threading semaphore = threading.Semaphore(5) # 最多 5 个并发请求 def safe_eval(item): with semaphore: for attempt in range(3): try: return call_lvm_eval( base_url, api_key, item["image_path"], item["extracted"] ) except Exception as e: if attempt == 2: return {"passed": False, "reason": f"retry failed: {e}"} time.sleep(2)9. Skill 测试资源占用与性能观察
9.1 各方案耗时对比
从实际工程角度看,三套方案的耗时差异很大:
| 方案 | 耗时量级 | 主要消耗资源 |
|---|---|---|
| Pytest 单元测试 | 秒级 | CPU,几乎无内存压力 |
| LLM/LVM 评测 | 分钟到小时级 | 网络带宽、API 额度、少量 CPU |
| Agent 集成测试 | 分钟级 | CPU、内存,真实模型时消耗 API 额度 |
9.2 如何观察性能瓶颈
- 单元测试阶段:用
pytest --durations=10查看最慢的 10 个用例。如果某个用例耗时异常,优先检查是否有真实网络请求被误放行。 - LLM/LVM 评测阶段:主要观察 API 响应时间和重试率。如果响应时间很长,考虑缩小评测 prompt,或者换更快的小模型先做一轮初筛。
- Agent 集成测试阶段:重点观察显存和内存。如果 Agent 里加载了本地 LVM 模型,显存占用会明显上升。建议用
nvidia-smi -l 5实时监控显存变化。
关于显存占用,这里不做具体数值断言,因为不同的 LVM 模型参数量差异很大。一个可靠的做法是:在自己机器上跑一轮真实 API 集成测试,用nvidia-smi记录峰值显存,再根据这个基准给 CI 机器评估是否够用。
10. Skill 测试常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
pytest 报错找不到模块skills | PYTHONPATH 未包含项目根目录 | 检查 pytest 运行路径和项目结构 | 在pyproject.toml中配置pythonpath = ["."],或使用python -m pytest运行 |
| Mock 不生效,测试发了真实请求 | patch 的目标路径写错 | 确认被测试代码里 import 的方式 | patch 应作用于skills.image_skill.requests.post,不是requests.post |
| LVM 评测结果不稳定 | 评测 prompt 不明确或 temperature 过高 | 检查评测配置,对比多次结果 | 固定 temperature=0,在 prompt 中明确列出判定规则 |
| Agent 没有触发 Skill | 意图识别规则没匹配上 | 查看 Agent 日志,确认用户输入 | 调整触发条件,增加同义词和相似表达 |
| 批量评测任务卡住 | 并发过高触发限流,或单条请求超时未处理 | 查看日志中超时记录 | 设置请求超时时间,增加失败重试,降低并发数 |
| 单元测试通过但线上效果差 | 测试数据与实际数据分布不一致 | 检查测试集是否过于理想化 | 增加边界样本、噪声样本、异常格式样本 |
| 模型返回字段缺失 | 模型输出不符合预期结构 | 打印原始返回 JSON | 在 Skill 中增加_normalize_output做兜底,测试中覆盖缺失字段场景 |
| 显存不足导致集成测试崩溃 | 本地 LVM 模型占用过高 | 用 nvidia-smi 查看显存 | 换更小模型,或改成调用远端模型 API |
11. Skill 测试最佳实践与工程建议
11.1 先把 Skill 做成“纯函数”
这是最核心的一条建议。Skill 内部不要直接依赖全局变量、Session 状态或外部服务。把所有外部依赖通过构造函数传进去,这样单元测试才能方便 Mock。
# 推荐:外部依赖通过参数注入 class ImageInfoSkill: def __init__(self, base_url: str, api_key: str): ... # 不推荐:在 Skill 内部直接创建 Session class ImageInfoSkillBad: def __init__(self): self.session = requests.Session() self.api_key = "hardcoded-key"11.2 固定评测集
方案二和方案三的测试集要作为“黄金数据集”维护。每次调整 Skill、更换模型、修改 prompt 后,都要用同一份测试集回评,才能横向对比效果。测试集不要频繁变动,新增测试用例时要记录原因。
11.3 分层报告
三套方案的结果要汇总成一份分层报告:第一层是单元测试通过率,第二层是模型评测通过率,第三层是集成测试通过率。哪个环节掉链子,就聚焦哪个环节排查。这样不会在“模型输出不好”和“代码有 bug”之间来回扯皮。
11.4 测试数据脱敏
所有测试素材必须先做脱敏。商品图片如果包含人脸、车牌、地址等信息,要打码或替换成公开可商用的图片素材。尤其在多人协作团队中,测试数据会传播到 CI 日志、评测报告、模型服务端,风险面很大。
11.5 合规红线
凡是涉及人脸、声音、版权素材的 Skill,测试用例必须确认授权。不要为了“提高覆盖率”就随意引入他人作品作为测试样本。建议在项目 README 中单独写一节“测试素材授权清单”,明确每份测试素材的来源和授权情况。
12. 总结与下一步
Skill 测试不是可有可无的锦上添花,而是 Agent 工程化的必答题。三套方案的落地顺序很清晰:
- 第一步:把 Skill 的纯逻辑接到 Pytest 单元测试里,先保证最快反馈。
- 第二步:引入 LLM/LVM 作为自动评测器,建立固定评测集和评测报告。
- 第三步:在 Agent 完整链路里跑集成测试,覆盖触发条件、参数传递、异常兜底。
建议第一次接入时,先拿一个业务价值最高的 Skill 做试点,三套方案都跑通后,再横向推广到其他 Skill。最容易踩的坑有两个:一是 Mock 目标路径写错导致测试变成真实请求,二是评测集没有固定导致不同版本之间无法横向对比。这两点提前避开,后面会顺畅很多。
下一步可以延伸的方向是:把方案二中的 LLM/LVM 评测结果接入可视化看板,按时间维度展示 Skill 质量趋势;或者把批量评测脚本包装成内部工具,让非测试岗位的同事也能提交评测任务。Skill 测试做扎实之后,你会发现 Agent 上线的信心会明显不一样。