ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

基于Pytest的AI Agent Skill测试全攻略:从单元到多模态

基于Pytest的AI Agent Skill测试全攻略:从单元到多模态 在 AI Agent 项目里Skills 往往承担了“给大模型插上手和眼”的作用。无论是让 Agent 调用天气接口、执行计算还是解析一张图片能力都被封装成一段段可复用的 Skill。但随着 Skill 数量变多一个很现实的问题就出现了改了一个 Skill 的内部逻辑或者升级了底层的 LLM 模型你怎么知道其他功能没有悄悄坏掉网上关于 Skills 开发的资料不少但专门讲“Skill 测试方案”的体系化内容却很零散。本文就围绕 Pytest、Agent、Skills、LVM、LLM 这几个关键词整理一套完整可落地的 Skill 测试实战方案。无论你是刚开始接触 Agent 开发还是已经在项目中维护大量 Skill都能在这篇文章里找到能直接复用的测试思路和代码。1. 背景与核心概念1.1 什么是 Skill为什么要测试它在 Agent 开发中Skill也叫 Tool、Function Calling 能力可以理解为一个“能力包”。它把某个具体功能封装成一个函数让大模型在对话过程中通过结构化调用去执行。例如查询天气输入城市名输出天气信息。执行计算输入表达式返回计算结果。图片分析输入图片路径返回图片内容描述。数据库查询输入 SQL 参数返回查询结果。Skill 的本质是一个可被 LLM 调用的函数但它和传统函数有一点显著不同它的参数由大模型生成而不是由开发者硬编码传入。这意味着 Skill 的输入存在不确定性大模型可能传错参数、漏掉必填项甚至把字符串格式的值传给数值类型的字段。测试 Skill不只是验证函数逻辑是否正确还要验证以下几个方面Skill 函数本身的逻辑是否正确。Skill 能否被 Agent 正确发现并调用。传入参数不合法时Skill 是否有合理的异常处理。LLM 生成的参数经过浮点精度转换后结果是否在可接受误差范围内。多模态场景下LVM 的识别结果能否转化为稳定可用的结构化输出。所以Skill 测试不能只靠“写个函数、跑一下、看结果”这种传统模式它需要一套从底层函数到 Agent 交互、再到多模态输入的完整测试体系。1.2 LVM 与 LLM 的概念区分标题里同时出现了 LVM 和 LLM这两个概念在 Skill 测试中经常被混在一起先做一个区分。LLMLarge Language Model大语言模型主要处理文本它的输入输出是 token 序列。在 Agent 中LLM 负责理解用户意图、决策调用哪个 Skill、生成最终回复。例如“帮我算一下 10 除以 3”LLM 会识别出应该调用计算 Skill并把表达式参数传过去。LVMLarge Vision Model大视觉模型则负责处理图像、视频等多模态输入。它可以从图片中识别物体、提取文字、判断场景。例如用户上传一张商品图片Agent 需要调用 LVM 提取商品信息再由 LLM 组织回复。LVM 的输出往往是描述文本或结构化数据这些结果会被后续流程进一步加工。注意这里的 LVM 和 Linux 里的逻辑卷管理Logical Volume Manager同样缩写为 LVM是完全不同的概念。在 AI Agent 场景中LVM 指大视觉模型。如果你搜索资料时看到“lvm 分区”“不创建 lvm 能用吗”那是 Linux 存储管理的内容和本文无关不要混淆。1.3 三种 Skill 测试方案的整体思路本文要讲的“Skill 测试 3 大方案”是从测试粒度和集成范围来划分的方案测试对象测试级别主要解决什么问题方案一纯函数单元测试Skill 函数本身单元测试函数逻辑、边界条件、异常处理、数值精度方案二Agent 交互测试Agent Skill 组合集成测试Skill 注册、参数传递、工具选择、调用链路方案三端到端多模态测试LLM LVM Skill 全链路端到端测试图文混合输入、多模态输出、全链路稳定性三种方案有明确的分工。方案一最快适合在开发阶段频繁执行方案二能发现 Agent 编排层的问题方案三最接近真实用户场景但执行成本和稳定性成本最高。实际项目中应该把三层测试组合成金字塔结构底层单元测试数量最多端到端测试数量最少。2. 环境准备与版本说明2.1 环境要求为了方便复现本文示例采用以下环境操作系统Windows 10/11 或 macOS 均可命令差异不大。编程语言Python 3.10 或更高版本。测试框架pytest 7.x 或 8.x。图像处理Pillow 9.x用于生成测试图片仅方案三需要。LLM/LVM 服务示例代码使用 Mock 模拟不强制要求真实 API Key。版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路。如果你的项目已经使用 pytest 6.x大部分 fixture 和断言语法也是兼容的。创建虚拟环境并安装依赖python -m venv .venv source .venv/bin/activate # Windows 使用 .venv\Scripts\activate pip install pytest pillow2.2 示例项目结构为了让测试代码结构清晰建议按下面的目录组织项目skill-test-demo/ ├── agent_executor.py # 轻量 Agent 执行器示例用 ├── skills/ │ ├── __init__.py │ ├── calculator_skill.py # 计算 Skill │ ├── weather_skill.py # 天气查询 Skill模拟 │ └── image_skill.py # 图片分析 SkillLVM 模拟 ├── tests/ │ ├── __init__.py │ ├── conftest.py # pytest fixture 公共配置 │ ├── test_calculator_skill.py │ ├── test_weather_skill.py │ ├── test_agent_interaction.py │ └── test_image_skill.py └── pytest.inipytest.ini用来配置 pytest 的根目录和测试发现规则[pytest] testpaths tests python_files test_*.py python_classes Test* python_functions test_*3. 核心原理拆解Pytest、Agent 与模型精度3.1 Pytest 的核心测试能力Pytest 是 Python 生态中最流行的测试框架它适合做 Skill 测试的原因主要有三点。第一断言表达简洁。pytest 使用 Python 原生assert关键字配合 pytest 的断言增强插件失败时会自动输出上下文信息排查问题效率高。第二fixture 机制灵活。fixture 可以完成 Skill 对象创建、临时目录生成、Mock 客户端注入等准备工作并且能控制作用域和清理逻辑。第三参数化测试方便。同一个 Skill 函数可以配合多组输入输出进行批量验证非常适合测试函数边界。下面是一个最小 fixture 示例后面实战部分会在此基础上扩展# tests/conftest.py import pytest pytest.fixture def calculator_skill(): 返回计算 Skill 的调用入口。 from skills.calculator_skill import calculate return calculate3.2 Agent 执行循环要测 Agent 与 Skill 的交互先要理解 Agent 执行一个调用的大致流程用户输入文本或图片。LLM 把输入解析成意图决定是否需要调用 Skill。LLM 输出一个结构化的工具调用请求包含 Skill 名称和参数。Agent 框架根据 Skill 名称到注册表中查找对应函数。Skill 函数执行返回结果。LLM 拿到结果后生成最终回复。测试时可以在两个关键位置做文章在“LLM 输出工具调用请求”这一层使用 Mock 模拟 LLM 的输出只验证 Agent 是否正确路由到 Skill。在“Skill 函数执行”这一层直接调用 Skill验证函数逻辑。这样就能把不确定性因素控制在最小范围内。3.3 LLM 精度问题对 Skill 测试的影响热词中有一个高频短语是“llm大模型之精度问题fp16 fp32 bf16详解与实践”这个问题在 Skill 测试中确实存在。LLM 在生成数值参数时内部计算可能采用 fp16、bf16 等低精度格式。当模型输出类似0.3333333432674408这样的浮点数时如果 Skill 内部用判断结果等于1 / 3测试必然失败。这并不一定是 Skill 逻辑错误而是浮点精度问题。所以在测试数值型 Skill 时推荐使用pytest.approx()进行近似比较。例如import pytest def test_precision_compare(): result 10 / 3 assert result pytest.approx(3.3333, abs1e-3)abs1e-3表示允许最大绝对误差为 0.001。这在实际工程中非常实用尤其是 Skill 返回结果需要继续传给下游系统时可以避免因为微小误差导致整个断言失败。3.4 LVM 输出在测试中的特殊性LVM 的输出与 LLM 有本质区别。LLM 的输出是文本 token可以直接做字符串断言LVM 输出的是基于图像内容的理解结果同一张图片在不同版本的视觉模型下描述可能不同。因此LVM 相关 Skill 的测试策略不能依赖“完全相同”的文本比较而应该验证结构化字段是否完整、关键属性是否在允许集合内。例如一个“识别图片主色调”的 Skill不应该断言“返回了纯字符串红色”而应该断言“返回了 JSON其中 color 字段在 [red, green, blue] 中”。4. 完整实战案例三种方案逐一落地4.1 创建 Skill 核心代码先准备好被测试的 Skill 函数。为了贴近真实项目这里实现三个 Skill计算器、天气查询、图片主色调分析。4.1.1 计算器 Skill# skills/calculator_skill.py import ast import operator # 只允许一小部分安全操作符避免真实项目使用 eval 带来的安全风险 _ALLOWED_OPERATORS { ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv, } def _safe_eval(node): if isinstance(node, ast.Expression): return _safe_eval(node.body) if isinstance(node, ast.Constant): if isinstance(node.value, (int, float)): return node.value raise ValueError(不支持的常量类型) if isinstance(node, ast.BinOp): left _safe_eval(node.left) right _safe_eval(node.right) op _ALLOWED_OPERATORS.get(type(node.op)) if op is None: raise ValueError(不支持的操作符) return op(left, right) raise ValueError(不支持的表达式) def calculate(expression: str) - float: 计算字符串形式的四则运算表达式返回浮点数结果。 if not isinstance(expression, str) or not expression.strip(): raise ValueError(expression 必须是有效字符串) tree ast.parse(expression, modeeval) result _safe_eval(tree) return round(float(result), 4)4.1.2 天气查询 Skill# skills/weather_skill.py class WeatherError(Exception): pass def get_weather(city: str) - str: 模拟天气查询 Skill。真实项目中这里会调用天气服务 API。 if not isinstance(city, str) or not city.strip(): raise WeatherError(城市名不能为空) # 模拟不同城市的天气结果便于测试确定性 mock_data { 北京: 北京今日多云气温 18°C东北风 2 级。, 上海: 上海今日小雨气温 22°C东南风 3 级。, 广州: 广州今日晴气温 27°C南风 2 级。, } if city not in mock_data: raise WeatherError(f暂不支持城市{city}) return mock_data[city]4.1.3 图片主色调分析 SkillLVM 模拟# skills/image_skill.py import json from PIL import Image class LVMClient: 模拟大视觉模型客户端。 真实项目中这里会调用 Qwen-VL、GPT-4V 等视觉模型。 示例中用 Pillow 直接读取图片像素模拟 LVM 返回结构化结果。 def analyze_color(self, image_path: str) - dict: with Image.open(image_path) as img: # 将图片缩小到 1x1取平均色 small img.resize((1, 1)) r, g, b small.getpixel((0, 0))[:3] return { color_rgb: [r, g, b], color_name: _map_color_name(r, g, b), } def _map_color_name(r: int, g: int, b: int) - str: if r 200 and g 100 and b 100: return red if g 200 and r 100 and b 100: return green if b 200 and r 100 and g 100: return blue return unknown def analyze_image_color(image_path: str) - str: 图片分析 Skill返回主色调的 JSON 字符串。 client LVMClient() result client.analyze_color(image_path) return json.dumps(result, ensure_asciiFalse)4.2 方案一纯函数单元测试这一层测试直接针对 Skill 函数本身不关心 Agent 怎么调用。测试重点放在函数逻辑、边界条件、异常路径和数值精度上。4.2.1 计算器 Skill 的单元测试# tests/test_calculator_skill.py import pytest from skills.calculator_skill import calculate def test_basic_add(): assert calculate(1 2) 3.0 def test_div_precision(): # 10 / 3 的结果用 pytest.approx 做近似断言 assert calculate(10 / 3) pytest.approx(3.3333, abs1e-3) def test_unsupported_expression(): with pytest.raises(ValueError): calculate(1 2 * (3 4)) def test_empty_expression(): with pytest.raises(ValueError): calculate()注意test_unsupported_expression里的表达式包含括号示例计算器只实现了二元运算和常量遇到括号节点会抛出异常。这是故意设计的边界行为。你的真实 Skill 如果有能力处理括号测试预期应该相应调整。4.2.2 天气 Skill 的单元测试# tests/test_weather_skill.py import pytest from skills.weather_skill import get_weather, WeatherError def test_known_city(): result get_weather(北京) assert 北京 in result assert 多云 in result def test_unknown_city(): with pytest.raises(WeatherError): get_weather(杭州) def test_empty_city(): with pytest.raises(WeatherError): get_weather()单元测试的好处是执行速度快适合在每次改动 Skill 后快速回归。如果测试发现失败可以立刻定位到函数内部问题不需要搭建完整的 Agent 环境。4.3 方案二Agent 交互测试单元测试通过后接下来要验证 Skill 能否被 Agent 正确调用。这里实现一个极简的 Agent 执行器用来模拟“LLM 决策 Skill 名称和参数 Agent 路由执行”的流程。4.3.1 轻量 Agent 执行器# agent_executor.py class ToolRegistry: Skill 注册表负责保存和查找 Skill 函数。 def __init__(self): self._tools {} def register(self, name: str, fn): self._tools[name] fn def list_tools(self): return list(self._tools.keys()) def execute(self, name: str, **kwargs): if name not in self._tools: raise KeyError(fSkill {name} 未注册) return self._tools[name](**kwargs) class AgentExecutor: 一个简化版 Agent 执行器。 实际项目中这里会集成 LangChain、OpenAI Function Calling 或其他 Agent 框架。 本示例把 LLM 决策过程抽象成 _call_llm便于测试时替换成模拟逻辑。 def __init__(self, registry: ToolRegistry): self.registry registry def run(self, user_input: str): skill_name, params self._call_llm(user_input) return self.registry.execute(skill_name, **params) def _call_llm(self, user_input: str): # 这里用简单的关键词规则模拟 LLM 决策真实项目应替换为大模型 if 天气 in user_input: return get_weather, {city: 北京} if 计算 in user_input: return calculate, {expression: 10 / 3} raise ValueError(无法识别意图)4.3.2 Agent 交互测试代码# tests/test_agent_interaction.py import pytest from agent_executor import ToolRegistry, AgentExecutor from skills.calculator_skill import calculate from skills.weather_skill import get_weather pytest.fixture def agent_executor(): 创建注册了 Weather 和 Calculator 的 Agent 实例。 registry ToolRegistry() registry.register(get_weather, get_weather) registry.register(calculate, calculate) return AgentExecutor(registry) def test_weather_skill_registered(agent_executor): assert get_weather in agent_executor.registry.list_tools() def test_agent_calls_weather_skill(agent_executor): result agent_executor.run(北京天气怎么样) assert 北京 in result assert 多云 in result def test_agent_calls_calculate_skill(agent_executor): result agent_executor.run(帮我计算 10 / 3) assert result pytest.approx(3.3333, abs1e-3) def test_unregistered_skill_raises_error(): registry ToolRegistry() with pytest.raises(KeyError): registry.execute(not_exist_skill)Agent 交互测试的价值在于它验证了 Skill 注册表、参数透传和 Agent 路由逻辑是否正确。在这个测试中_call_llm是模拟的所以测试结果稳定可靠。真实项目中可以使用 LangChain 的Tool抽象也可以直接使用 OpenAI SDK 的tools参数测试思路一致。4.4 方案三端到端多模态测试LVM 场景第三种方案面向带图片输入的复杂场景。它模拟用户上传一张图片Agent 先通过 LVM 识别图片主色调再返回结构化结果。4.4.1 生成测试图片测试不能依赖外部图片文件所以用 Pillow 在临时目录里现场生成一张纯红色图片# tests/test_image_skill.py import json import pytest from PIL import Image from skills.image_skill import analyze_image_color pytest.fixture def red_image(tmp_path): 生成一张 100x100 的纯红色测试图片。 img_path tmp_path / red.png img Image.new(RGB, (100, 100), color(255, 0, 0)) img.save(img_path) return str(img_path) def test_analyze_image_color_structured_output(red_image): result_str analyze_image_color(red_image) result json.loads(result_str) assert color_rgb in result assert color_name in result assert result[color_rgb] [255, 0, 0] assert result[color_name] red def test_analyze_image_color_allowed_values(red_image): result_str analyze_image_color(red_image) result json.loads(result_str) allowed {red, green, blue, unknown} assert result[color_name] in allowed4.4.2 端到端链路验证方案三还可以进一步扩展把 LVM 结果和 LLM 组织回复的流程串在一起。例如def test_end_to_end_image_report(red_image): # 第一步LVM 识别 color_result json.loads(analyze_image_color(red_image)) # 第二步LLM 根据结构化结果生成文本报告 report f图片主色调是 {color_result[color_name]}RGB 值为 {tuple(color_result[color_rgb])} # 第三步断言最终报告内容 assert 红色 in report or red in report这种测试关注的是整条链路是否顺畅即使 LVM 输出稍有变化只要字段结构稳定测试就不会随便失败。4.5 运行与验证在项目根目录执行pytest -v预期你会看到类似下面的输出tests/test_calculator_skill.py::test_basic_add PASSED tests/test_calculator_skill.py::test_div_precision PASSED tests/test_calculator_skill.py::test_unsupported_expression PASSED tests/test_calculator_skill.py::test_empty_expression PASSED tests/test_weather_skill.py::test_known_city PASSED tests/test_weather_skill.py::test_unknown_city PASSED tests/test_weather_skill.py::test_empty_city PASSED tests/test_agent_interaction.py::test_weather_skill_registered PASSED tests/test_agent_interaction.py::test_agent_calls_weather_skill PASSED tests/test_agent_interaction.py::test_agent_calls_calculate_skill PASSED tests/test_agent_interaction.py::test_unregistered_skill_raises_error PASSED tests/test_image_skill.py::test_analyze_image_color_structured_output PASSED tests/test_image_skill.py::test_analyze_image_color_allowed_values PASSED tests/test_image_skill.py::test_end_to_end_image_report PASSED如果你希望只运行某一类测试可以用-k参数过滤。例如只跑图片相关测试pytest -v -k image5. 常见问题与排查思路在实际项目中Skill 测试会遇到不少问题。下面整理了一些高频场景和对应的排查建议。问题现象常见原因解决思路fixture 创建的对象在多个测试之间相互污染fixture 默认作用域是 function但有些开发者手动缓存了对象使用pytest.fixture默认作用域需要复用时可设置scopemodule或scopesessionAgent 调用真实 LLM测试经常超时网络不稳定或模型响应时间长测试中把 LLM 调用替换为 Mock或使用录播/回放机制Skill 返回浮点数时断言失败LLM 或 Python 浮点精度问题使用pytest.approx()不要直接比较相等LVM 图像识别结果不稳定不同模型版本输出描述文本不同只断言结构化字段不要断言原文完全一致测试依赖外部 API未联网时批量失败外部服务不可用使用 Mock 或 fixture 临时替换外部客户端pytest 无法发现测试文件目录结构或pytest.ini配置不对检查testpaths确保文件名以test_开头Skill 执行时抛异常但测试返回通过测试断言不完整没覆盖异常路径使用pytest.raises检查预期异常修改一个 Skill 导致多个测试连锁失败测试耦合了未 Mock 的外部依赖分离单元测试和集成测试底层逻辑不依赖外部服务Agent 测试中注册的 Skill 名拼写错误手工维护注册表容易漏注册编写测试断言注册表包含所有 Skill 名称下面针对两个最常见的细节问题做进一步说明。5.1 浮点精度导致断言失败如果你直接执行assert calculate(10 / 3) 3.3333在多数 Python 环境中会失败。因为10 / 3的实际结果是3.3333333333333335而3.3333只是一个近似值。正确写法是assert calculate(10 / 3) pytest.approx(3.3333, abs1e-3)在 LLM 输出参数时还有另一种精度问题模型可能把3.3333序列化成3.3333但 Skill 内部返回10 / 3的完整结果。此时同样建议用pytest.approx比较。5.2 Agent 执行器报“The agent execution provider did not respond in time”这个报错通常出现在 Agent 框架等待模型响应超时的情况下。表现是测试长时间卡住然后抛出一个超时异常。这可能不是 Skill 本身的问题而是模型调用环节的问题。排查顺序建议如下确认是否真的需要调用真实大模型测试环境建议替换为 Mock。检查模型接口是否有网络代理、鉴权等前置问题。查看 Agent 框架超时配置适当调大超时时间。如果使用官方 SDK确认版本与接口兼容。6. 最佳实践与工程建议6.1 按测试金字塔组织 Skill 测试不要把全部测试都堆在端到端层面。推荐的比例是单元测试方案一占比最高覆盖每个 Skill 函数的正常路径、边界路径、异常路径。集成测试方案二覆盖注册表和 Agent 路由逻辑。端到端测试方案三数量最少只覆盖核心用户链路。这样做的好处是大部分测试执行速度快、定位准确端到端测试虽然更真实但执行慢、依赖多跑太多反而降低迭代效率。6.2 用 fixture 隔离外部依赖Skill 经常会调用外部 API、数据库或模型服务。测试中应该让这些依赖变得可控。推荐做法是在conftest.py里定义统一的外部客户端 fixture测试时用 Mock 对象替换真实客户端。例如# tests/conftest.py import pytest from unittest.mock import Mock pytest.fixture def mock_llm_client(): client Mock() client.choose_skill.return_value (get_weather, {city: 北京}) return client这样测试就不依赖真实模型服务执行稳定也不会产生 API 费用。6.3 复用真实 SKill 注册表避免重复注册在多个测试文件中不要各自创建一套 Skill 注册表否则很容易出现“测试 A 注册了测试 B 忘注册”的问题。可以把注册逻辑提取成一个公共 fixture# tests/conftest.py import pytest from agent_executor import ToolRegistry from skills.calculator_skill import calculate from skills.weather_skill import get_weather pytest.fixture def registry(): reg ToolRegistry() reg.register(calculate, calculate) reg.register(get_weather, get_weather) return reg这样每个测试都使用同一份注册逻辑减少重复代码也便于统一维护。6.4 对 LVM 输出采用字段级断言多模态模型输出天然存在一定随机性。测试时不要写死整段描述文本而应该只检查关键字段是否存在。只检查枚举值是否在允许集合内。只检查数值范围是否合理。如果你确实需要验证文本内容可以通过“包含关键关键词”的方式比如assert 北京 in result而不是assert result 北京今日多云...。6.5 在 CI 中分层执行推荐在 CI 流水线中配置“差分执行”策略每次提交都跑单元测试。合并请求时跑单元测试 集成测试。发布前跑单元测试 集成测试 少量端到端测试。例如pytest tests/test_calculator_skill.py tests/test_weather_skill.py pytest tests/test_agent_interaction.py pytest tests/test_image_skill.py如果测试用例数量已经很大也可以使用 pytest 的分组插件给不同测试打标签再在 CI 中分别执行。6.6 记录测试日志方便复盘Skill 测试失败时如果只有断言信息往往不够。建议在测试用例中通过logging或print输出关键中间数据例如 Agent 选择的 Skill 名称、传入参数、返回结果。pytest 加-s参数可以看到 print 输出加--log-cli-levelDEBUG可以看到日志输出。这在实际排查中能节省大量时间。7. 总结与学习路线本文从 AI Agent 中 Skill 测试的实际痛点出发梳理了三种测试方案纯函数单元测试、Agent 交互测试、端到端多模态测试。三种方案不是互相替代的关系而是层层递进、互为补充。通过实战示例你应该已经掌握了如何用 pytest fixture 管理 Skill 对象和外部依赖。如何断言浮点型 Skill 输出避免精度问题导致的误报。如何构建一个可测试的 Agent 执行器并验证 Skill 注册和路由逻辑。如何用 Pillow 生成测试图片完成 LVM 场景下的结构化断言。下一步可以继续深入的方向包括接入真实大模型 SDK把_call_llm替换成 OpenAI Function Calling 或 LangChain 的 Tool Calling。使用pytest-mock统一管理 Mock减少手工 Mock 代码。结合覆盖率工具统计每个 Skill 函数的测试覆盖率。为 Skills 接入日志和监控在生产环境观察真实调用情况。Skill 测试的价值在于“把不确定性挡在发布之前”。大模型的输出天然不稳定但 Skill 层的函数逻辑应该稳定。通过分层测试既能让函数级逻辑保持可靠又能在模型升级、Prompt 调整时及时发现回归。建议你在当前项目里先挑一个核心 Skill按照方案一和方案二搭好测试骨架再逐步扩展到 LVM 多模态场景。多做几次就能找到最适合自己项目的测试节奏。
返回列表