Arazzo 1.1:有什么新内容,今天能运行什么
2026 年 5 月 19 日,OpenAPI Initiative 发布了 Arazzo 1.1.0。 Arazzo 是 OpenAPI Initiative 用来描述一连串 API 调用的格式:先登录,再创建订单,然后付款,每一步把值传给下一步。OpenAPI 描述一个端点接受什么、返回什么;Arazzo 描述要调用哪些端点、按什么顺序,以及如何判断每次调用是否成功。
1.1 是自 2024 年的 1.0 以来第一个带来新功能的版本。我们针对自己的 API 写了一个 1.1 的工作流,用 10 月 9 日发布的 Redocly CLI 2.62.1 运行。runner 接受 1.1 文档,一个简单的增删改查工作流也能通过。但有两项新功能虽然能通过 linter,在真正运行时却被忽略了。 这篇文章介绍有哪些变化,提供一个可以下载运行的工作流,并指出目前最好避开的两项功能。
Arazzo 文件做什么
Arazzo 文档指向一个或多个 API 描述(通常是 OpenAPI 文件),并列出工作流。每个工作流是一串步骤。每一步通过 operationId 指定一个操作,填好参数和请求体,说明怎样算成功,并从响应中保存值供后续步骤使用。
这一个文件在三个地方都有用:
- 测试。runner 针对真实服务器执行这些步骤,任何成功条件不满足就失败。下文就是这么用的。
- 文档。读者能看到完成一项任务所需的调用顺序,这是 OpenAPI 文件表达不了的。
- AI 智能体。读过 OpenAPI 的智能体知道每个端点做什么,但得自己猜怎么把它们组合起来。Arazzo 工作流会告诉它。
Arazzo 1.1 有哪些变化
最大的亮点是支持 AsyncAPI,这样一个工作流可以把 HTTP 调用和消息通道混在一起。其他大多数变化都让 HTTP 工作流更容易编写。全部变化见发布公告:
| 变化 | 作用 |
|---|---|
type: asyncapi 来源 | 来源描述现在除了 openapi 和 arazzo,还可以是 AsyncAPI 文档。 |
channelPath、action、correlationId、timeout | 发送或接收消息的步骤。action 是 send 或 receive,correlationId 把回复和请求对应起来,timeout 是以毫秒为单位的上限。 |
dependsOn | 一个步骤可以列出必须在它开始前完成的其他步骤。列出某个步骤并不会运行它。 |
动作上的 parameters | 调用另一个工作流的成功或失败动作可以给它传入参数,比如先刷新令牌再重试。 |
| Selector Object | 用 JSONPath、XPath 或 JSON Pointer 从响应或输入中取值。可用于 outputs、参数值和请求体。 |
| Expression Type Object | 固定查询语言的版本,比如 RFC 9535 定义的 JSONPath 或 XPath 3.1。取代 1.0 的 Criterion Expression Type Object。 |
in: querystring | 把一个参数作为整个查询字符串发送,与 OpenAPI 3.2 的同一新增内容对应。 |
$self | 标识文档的 URI,这样对它的引用就不取决于文件恰好存放在哪里。 |
规范还为 $steps.login.outputs.token 这样的运行时表达式补上了完整的语法,规定了解析来源描述的顺序,以及条件何时算为真的确切规则。这些没有增加字段,但解决了两个 runner 可能得出不同结论的情况。对 gRPC、GraphQL、MCP 和循环的支持在后续版本的路线图中,1.1 里没有。
一个可以在我们的 API 上运行的工作流
这个工作流在 api.testingapis.com 上创建一个商品,读回来,修改价格,删除它,再确认它已经不在了。它只用免费端点,所以不需要账号或 API 密钥就能运行。
arazzo: 1.1.0
$self: https://testingapis.com/downloads/arazzo/products.arazzo.yaml
info:
title: Product lifecycle on testingapis.com
summary: Create a product, read it back, change its price, delete it and check that it's gone.
version: 1.0.0
sourceDescriptions:
- name: testingapis
url: ./testingapis-products.openapi.json
type: openapi
workflows:
- workflowId: product-lifecycle
inputs:
type: object
properties:
title: { type: string, default: Arazzo test product }
price: { type: number, default: 12.5 }
newPrice: { type: number, default: 9.99 }
steps:
- stepId: createProduct
operationId: Products_Create
requestBody:
contentType: application/json
payload:
title: $inputs.title
price: $inputs.price
category: electronics
successCriteria:
- condition: $statusCode == 201
outputs:
productId: $response.body#/data/id
- stepId: readProduct
operationId: Products_GetById
parameters:
- name: id
in: path
value: $steps.createProduct.outputs.productId
successCriteria:
- condition: $statusCode == 200
- context: $response.body
condition: $[?@.category == 'electronics' && @.inStock == false]
type:
type: jsonpath
version: rfc9535
onFailure:
- name: cleanUp
type: goto
stepId: deleteProduct
- stepId: changePrice
operationId: Products_Update
parameters:
- name: id
in: path
value: $steps.createProduct.outputs.productId
requestBody:
contentType: application/json
payload:
title: $inputs.title
price: $inputs.newPrice
category: electronics
successCriteria:
- condition: $statusCode == 200
- condition: $response.body#/data/price == $inputs.newPrice
onFailure:
- name: cleanUp
type: goto
stepId: deleteProduct
- stepId: deleteProduct
operationId: Products_Delete
parameters:
- name: id
in: path
value: $steps.createProduct.outputs.productId
successCriteria:
- condition: $statusCode == 200
- stepId: confirmGone
operationId: Products_GetById
parameters:
- name: id
in: path
value: $steps.createProduct.outputs.productId
successCriteria:
- condition: $statusCode == 404
outputs:
productId: $steps.createProduct.outputs.productId把它和它指向的 OpenAPI 文件一起下载,放在同一个文件夹里运行。npx 会自动获取 runner,所以有 Node 22.12 或更高版本就够了:
- products.arazzo.yaml
- testingapis-products.openapi.json(我们线上的 OpenAPI 文档,精简到商品和 echo 端点,并带有
servers条目,让 runner 知道把请求发到哪里)
$ npx @redocly/cli@2.62.1 respect products.arazzo.yamlRunning workflow products.arazzo.yaml / product-lifecycle
✓ POST /api/Products - step createProduct
✓ success criteria check - $statusCode == 201
✓ status code check - $statusCode in [200, 201, 400, 500]
✓ GET /api/Products/{id} - step readProduct
✓ success criteria check - $statusCode == 200
✓ success criteria check - $[?@.category == 'electronics' && @.inStock == fal...
✓ status code check - $statusCode in [200, 404, 500]
✓ PUT /api/Products/{id} - step changePrice
✓ success criteria check - $statusCode == 200
✓ success criteria check - $response.body#/data/price == $inputs.newPrice
✓ status code check - $statusCode in [200, 404, 500]
✓ DELETE /api/Products/{id} - step deleteProduct
✓ success criteria check - $statusCode == 200
✓ status code check - $statusCode in [200, 404, 500]
✓ GET /api/Products/{id} - step confirmGone
✓ success criteria check - $statusCode == 404
✓ status code check - $statusCode in [200, 404, 500]
Workflows: 1 passed, 1 total
Steps: 5 passed, 5 total
Checks: 12 passed, 12 total
Time: 922ms文件里有几处做的事比看上去多:
$response.body#/data/id是一个运行时表达式,#后面跟着 JSON Pointer。我们的创建端点把新商品包在{ message, data }里,所以 ID 在/data/id。后面的每一步都以$steps.createProduct.outputs.productId读取它。readProduct中的条件用新的 Expression Type Object 声明查询是 RFC 9535 的 JSONPath。只要过滤器匹配到至少一个节点, JSONPath 条件就通过,所以$[?@.category == 'electronics' && @.inStock == false]一次检查了两个字段。我们还把期望值反过来,确认检查在该失败时会失败,结果确实失败了。- 每一步的第三项检查
status code check并不在 Arazzo 文件里。它是 Redocly 根据 OpenAPI 文件列出的响应加上的,所以 API 没有记录的状态码会让该步骤失败,即使你自己的条件都通过了。
步骤失败时,工作流会收尾
在我们 API 上创建的商品是共享的:在被删除之前,任何列出商品的人都能看到你的商品。中途停下的测试会留下垃圾,所以 readProduct 和 changePrice 都有一个跳到 deleteProduct 的 onFailure 动作。为了验证,我们把价格检查改成了不可能通过:
✗ PUT /api/Products/{id} - step changePrice
✓ success criteria check - $statusCode == 200
✗ success criteria check - $response.body#/data/price == $inputs.price
✓ status code check - $statusCode in [200, 404, 500]
Running failure action cleanUp for the step changePrice
✓ DELETE /api/Products/{id} - step deleteProduct
✓ success criteria check - $statusCode == 200
✓ GET /api/Products/{id} - step confirmGone
✓ success criteria check - $statusCode == 404工作流仍然报告失败,但商品会先被删除。任何在共享环境中创建数据的工作流都应该这样做。
Selector Object 能过 lint,但不会执行
Selector Object 是从响应中取值的新方式。我们用它通过 JSON Pointer 读取商品的评分,并发送到我们的 echo 端点,该端点会原样返回收到的内容:
- stepId: getProduct
operationId: Products_GetById
parameters:
- { name: id, in: path, value: 1 }
outputs:
rating:
context: $response.body
selector: /rating/rate
type: jsonpointer
- stepId: echoIt
operationId: Echo_Echo_POST
requestBody:
contentType: application/json
payload:
rating: $steps.getProduct.outputs.ratingredocly lint 认为这个文件没有问题。echo 端点本应收到 {"rating": 4.3}。它实际收到的是:
{
"rating": {
"context": {
"id": 1,
"title": "Wireless Noise-Cancelling Headphones",
"price": 249.99,
...
"rating": { "rate": 4.3, "count": 127 },
"inStock": true,
"createdAt": "2025-06-12T10:30:00Z"
},
"selector": "/rating/rate",
"type": "jsonpointer"
}
}runner 把 context 解析成了整个响应体,然后没有应用选择器,而是把 Selector Object 本身发了出去。输出里没有任何警告。 这一步之所以失败,只是因为我们检查了服务器收到的内容;一个只检查 200 的工作流会在发送错误数据的同时照样通过。
在 runner 跟上之前,请使用 1.0 的写法。对 JSON Pointer 来说,它反而更短:
outputs:
rating: $response.body#/rating/rate传给工作流的参数丢失了
公告里给 parameters 举的例子是:一个失败动作调用刷新令牌的工作流,然后重试该步骤。我们的免费端点不签发令牌,所以我们用一个不存在的商品测试了同样的机制: 失败动作调用一个 report 工作流,它把输入 reason 发送到 echo 端点。
- stepId: getMissing
operationId: Products_GetById
parameters:
- { name: id, in: path, value: 99999 }
successCriteria:
- condition: $statusCode == 200
onFailure:
- name: reportAndRetry
type: retry
retryLimit: 1
workflowId: report
parameters:
- name: reason
value: not foundrunner 确实按规范描述的顺序调用了 report 工作流,然后重试了该步骤。但 echo 端点收到的是一个空对象:
"contentType": "application/json",
"contentLength": 2,
"body": "{}",
"json": {}reason 始终没有到达。单独用 --input reason=... 运行 report 是正常的,所以问题出在从动作传递 parameters 这一环。放到刷新令牌的场景,就意味着刷新请求发出去时没带 refresh token。
Redocly CLI 2.62.1 中能运行的功能
| Arazzo 1.1 功能 | Lint | 运行 |
|---|---|---|
arazzo: 1.1.0 | 接受 | 能运行 |
$self | 接受 | 接受;运行时没有可观察的效果 |
| Expression Type Object | 接受 | 能运行,该失败时会失败 |
dependsOn | 接受 | 接受;反正步骤本来就按顺序运行 |
| Selector Object | 接受 | 未生效:对象被当作数据发送 |
动作上的 parameters | 接受 | 工作流会运行,但输入为空 |
| AsyncAPI 步骤 | 未测试 | 未测试 |
我们没有在 Redocly 的 changelog 里找到声称支持 Arazzo 1.1 的条目,所以这些可能只是尚未实现的功能。Arazzo 仓库里列出的其他工具可能表现不同,我们只测试了 Redocly。
现在编写 1.1 工作流
- 使用
arazzo: 1.1.0和能运行的功能:Expression Type Object、$self和dependsOn。 - 用
$response.body#/data/id这样的运行时表达式读取响应中的值,而不是 Selector Object。 - 通过在某个步骤中重新登录来处理令牌刷新,而不是靠失败动作上的
parameters。 - 检查服务器收到了什么,而不只是状态码。通过的 lint 和
200都没发现这篇文章里的问题,我们的 echo 端点发现了。 - 像我们用
@redocly/cli@2.62.1那样固定 runner 的版本,这样 runner 更新就不会在没有提交的情况下改变你的结果。
如果你的工作流测试的是增删改查的完整流程,我们的 CRUD 测试课程讲了每一步需要哪些检查。读懂 OpenAPI 规范(英文)解释了工作流引用的 operationId,商品 API 文档列出了上面工作流涉及的每个字段。