nostr-dvms

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Nostr Data Vending Machines (NIP-90)

Nostr数据自动售货机(NIP-90)

Overview

概述

Build AI and compute services on Nostr using the Data Vending Machine protocol. DVMs follow a simple pattern: customers publish job requests (kind 5000-5999), service providers process them and return results (kind 6000-6999), with optional feedback events (kind 7000) for status updates and payment negotiation.
使用数据自动售货机协议在Nostr上构建AI和计算服务。DVM遵循简单的运行模式:用户发布任务请求(5000-5999类),服务提供商处理请求并返回结果(6000-6999类),同时支持可选的反馈事件(7000类)用于状态更新和支付协商。

When to Use

适用场景

  • Building a DVM service provider that processes job requests
  • Creating a client that publishes DVM job requests and handles results
  • Implementing payment flows for DVM services (bid, amount, bolt11)
  • Chaining multiple DVM jobs (output of one feeds into another)
  • Adding encrypted parameters to DVM requests for privacy
  • Publishing NIP-89 handler announcements for DVM discoverability
  • Handling DVM feedback states (processing, payment-required, error, partial)
Do NOT use when:
  • Building general Nostr events (use nostr-event-builder)
  • Implementing relay WebSocket logic
  • Working with Lightning payments outside the DVM context
  • 构建处理任务请求的DVM服务提供商
  • 开发可发布DVM任务请求并处理结果的客户端
  • 实现DVM服务的支付流程(出价、金额、bolt11)
  • 串联多个DVM任务(前一个任务的输出作为后一个的输入)
  • 为DVM请求添加加密参数保障隐私
  • 发布NIP-89处理程序公告提升DVM可发现性
  • 处理DVM反馈状态(处理中、需支付、错误、部分完成)
禁止使用场景:
  • 构建通用Nostr事件(请使用nostr-event-builder)
  • 实现中继WebSocket逻辑
  • 处理DVM场景外的闪电网络支付

Workflow

工作流

1. Determine Your Role

1. 确定你的角色

Ask: "Am I building a service provider, a client, or both?"
RolePublishesSubscribes To
Service Providerkind:6xxx resultskind:5xxx requests
kind:7000 feedbackkind:5 cancellations
kind:31990 NIP-89 info
Customer/Clientkind:5xxx requestskind:6xxx results
kind:5 cancellationskind:7000 feedback
先明确:“我要构建的是服务提供商、客户端,还是两者都有?”
角色发布内容订阅内容
服务提供商kind:6xxx 结果kind:5xxx 请求
kind:7000 反馈kind:5 取消请求
kind:31990 NIP-89 信息
消费者/客户端kind:5xxx 请求kind:6xxx 结果
kind:5 取消请求kind:7000 反馈

2. Choose the Job Kind

2. 选择任务类型编号

Each DVM operation has a specific kind number. The result kind is always request kind + 1000.
Request KindResult KindDescription
50006000Text extraction
50016001Summarization
50026002Translation
50036003Text generation
50056005Content discovery/recommendation
50506050Text-to-speech
51006100Image generation
52506250Event publishing
See references/dvm-kinds.md for the full registry.
每个DVM操作都有对应的类型编号,结果类型编号始终为请求类型编号+1000。
请求类型编号结果类型编号描述
50006000文本提取
50016001内容摘要
50026002翻译
50036003文本生成
50056005内容发现/推荐
50506050语音合成
51006100图像生成
52506250事件发布
完整注册表请查看 references/dvm-kinds.md

3. Build the Job Request (Customer Side)

3. 构建任务请求(消费者端)

Construct a kind 5000-5999 event:
json
{
  "kind": 5001,
  "content": "",
  "tags": [
    ["i", "<data>", "<input-type>", "<relay>", "<marker>"],
    ["output", "<mime-type>"],
    ["relays", "wss://relay.example.com"],
    ["bid", "<msat-amount>"],
    ["t", "<topic-tag>"],
    ["p", "<preferred-service-provider-pubkey>"]
  ]
}
Input types — the second element of the
i
tag:
TypeMeaningExample
text
Raw text data, no resolution needed
["i", "Hello world", "text"]
url
URL to fetch content from
["i", "https://example.com/doc.pdf", "url"]
event
Reference to a Nostr event by ID
["i", "<event-id>", "event", "wss://relay"]
job
Output of a previous DVM job (for chaining)
["i", "<job-event-id>", "job"]
Optional tags:
  • output
    — requested MIME type for the result (e.g.,
    text/plain
    )
  • param
    — key/value parameters:
    ["param", "lang", "es"]
  • bid
    — max millisats the customer will pay
  • relays
    — where service providers should publish responses
  • p
    — preferred service provider pubkey (others MAY still respond)
  • t
    — topic tags for categorization
构造一个5000-5999类的事件:
json
{
  "kind": 5001,
  "content": "",
  "tags": [
    ["i", "<data>", "<input-type>", "<relay>", "<marker>"],
    ["output", "<mime-type>"],
    ["relays", "wss://relay.example.com"],
    ["bid", "<msat-amount>"],
    ["t", "<topic-tag>"],
    ["p", "<preferred-service-provider-pubkey>"]
  ]
}
输入类型 ——
i
标签的第二个元素:
类型含义示例
text
原始文本数据,无需解析
["i", "Hello world", "text"]
url
可获取内容的URL
["i", "https://example.com/doc.pdf", "url"]
event
通过ID引用Nostr事件
["i", "<event-id>", "event", "wss://relay"]
job
前一个DVM任务的输出(用于任务串联)
["i", "<job-event-id>", "job"]
可选标签:
  • output
    —— 期望的结果MIME类型(例如
    text/plain
  • param
    —— 键值对参数:
    ["param", "lang", "es"]
  • bid
    —— 消费者愿意支付的最大毫秒聪金额
  • relays
    —— 服务提供商发布响应的中继地址
  • p
    —— 首选服务提供商的公钥(其他服务商仍可响应)
  • t
    —— 用于分类的主题标签

4. Handle Job Feedback (Kind 7000)

4. 处理任务反馈(7000类)

Service providers send feedback events to communicate status:
json
{
  "kind": 7000,
  "content": "",
  "tags": [
    ["status", "<status>", "<extra-info>"],
    ["amount", "<msat>", "<bolt11>"],
    ["e", "<job-request-id>", "<relay-hint>"],
    ["p", "<customer-pubkey>"]
  ]
}
Status values:
StatusMeaningAction Required
payment-required
SP requires payment before continuingCustomer must pay
processing
SP is actively working on the jobWait for result
error
SP could not process the jobCheck extra-info for why
success
SP completed the jobResult incoming
partial
SP has partial results (content may have samples)More results coming
Critical:
payment-required
is a hard gate — the SP will NOT proceed until paid. Other statuses are informational.
The
content
field MAY contain partial results (e.g., a sample of processed output) for any feedback status.
服务提供商通过反馈事件同步状态:
json
{
  "kind": 7000,
  "content": "",
  "tags": [
    ["status", "<status>", "<extra-info>"],
    ["amount", "<msat>", "<bolt11>"],
    ["e", "<job-request-id>", "<relay-hint>"],
    ["p", "<customer-pubkey>"]
  ]
}
状态值说明:
状态含义所需操作
payment-required
服务提供商需要先收到付款才能继续处理消费者完成支付
processing
服务提供商正在处理任务等待结果返回
error
服务提供商无法处理该任务查看额外信息了解错误原因
success
服务提供商已完成任务即将返回结果
partial
服务提供商已生成部分结果(content可能包含样本)后续会返回更多结果
关键说明:
payment-required
是硬性门槛,未收到付款前服务提供商不会继续处理。其他状态仅为信息通知。
任意反馈状态的
content
字段都可能包含部分结果(例如处理输出的样本)。

5. Publish Job Results (Service Provider Side)

5. 发布任务结果(服务提供商端)

Result kind = request kind + 1000 (e.g., 5001 → 6001):
json
{
  "kind": 6001,
  "content": "<result-payload>",
  "tags": [
    ["request", "<stringified-original-job-request-event>"],
    ["e", "<job-request-id>", "<relay-hint>"],
    ["i", "<original-input-data>"],
    ["p", "<customer-pubkey>"],
    ["amount", "<msat>", "<optional-bolt11>"]
  ]
}
Required tags:
  • request
    — the FULL original job request event as a stringified JSON string
  • e
    — references the job request event ID
  • p
    — the customer's pubkey (so they can find the result)
Important: The
request
tag value is the entire job request event serialized as a JSON string, not just the event ID.
结果类型编号 = 请求类型编号 + 1000(例如5001 → 6001):
json
{
  "kind": 6001,
  "content": "<result-payload>",
  "tags": [
    ["request", "<stringified-original-job-request-event>"],
    ["e", "<job-request-id>", "<relay-hint>"],
    ["i", "<original-input-data>"],
    ["p", "<customer-pubkey>"],
    ["amount", "<msat>", "<optional-bolt11>"]
  ]
}
必填标签:
  • request
    —— 完整的原始任务请求事件序列化后的JSON字符串
  • e
    —— 关联任务请求的事件ID
  • p
    —— 消费者的公钥(方便消费者检索到结果)
重要提示:
request
标签的值是完整序列化后的任务请求事件,而不仅仅是事件ID。

6. Handle Payments

6. 处理支付

The payment model is flexible by design:
  1. Customer bids: Include
    ["bid", "<msat>"]
    in the request
  2. SP quotes: Include
    ["amount", "<msat>", "<bolt11>"]
    in feedback/result
  3. Customer pays: Either pay the bolt11 invoice OR zap the result event
Customer                    Service Provider
   |                              |
   |-- kind:5001 (bid: 5000) ---->|
   |                              |
   |<-- kind:7000 ----------------|  status: payment-required
   |    amount: 3000, bolt11:...  |
   |                              |
   |-- pay bolt11 or zap -------->|
   |                              |
   |<-- kind:7000 ----------------|  status: processing
   |                              |
   |<-- kind:6001 ----------------|  result + amount tag
SPs MUST use
payment-required
feedback to block until paid. They SHOULD NOT silently wait for payment without signaling.
支付模型在设计上具备灵活性:
  1. 消费者出价: 在请求中添加
    ["bid", "<msat>"]
    标签
  2. 服务商报价: 在反馈/结果中添加
    ["amount", "<msat>", "<bolt11>"]
    标签
  3. 消费者支付: 支付bolt11发票,或者给结果事件发送zap
消费者                         服务提供商
   |                              |
   |-- kind:5001 (出价: 5000) --->|
   |                              |
   |<-- kind:7000 ----------------|  状态: payment-required
   |    金额: 3000, bolt11:...    |
   |                              |
   |-- 支付bolt11或发送zap ------>|
   |                              |
   |<-- kind:7000 ----------------|  状态: processing
   |                              |
   |<-- kind:6001 ----------------|  结果 + 金额标签
服务提供商必须使用
payment-required
反馈来暂停流程直到收到付款,不应在未通知的情况下静默等待付款。

7. Implement Job Chaining

7. 实现任务串联

Chain jobs by using the
job
input type — the output of one job becomes the input of the next:
json
{
  "kind": 5001,
  "content": "",
  "tags": [
    ["i", "<translation-job-event-id>", "job"],
    ["param", "lang", "en"]
  ]
}
The service provider for job #2 watches for the result of job #1, then processes it. Payment timing is at the SP's discretion — they may wait for the customer to zap job #1's result before starting job #2.
Chaining example — translate then summarize:
Step 1: Publish kind:5002 (translation)
  ["i", "https://article.com/post", "url"]
  ["param", "lang", "en"]

Step 2: Publish kind:5001 (summarization)
  ["i", "<step-1-event-id>", "job"]
通过
job
输入类型实现任务串联,前一个任务的输出作为后一个任务的输入:
json
{
  "kind": 5001,
  "content": "",
  "tags": [
    ["i", "<translation-job-event-id>", "job"],
    ["param", "lang", "en"]
  ]
}
第二个任务的服务提供商会监听第一个任务的结果,获取后进行处理。支付时机由服务提供商自行决定,他们可以选择等待消费者支付完第一个任务的结果后再开始处理第二个任务。
串联示例 —— 翻译后摘要:
步骤1: 发布kind:5002(翻译任务)
  ["i", "https://article.com/post", "url"]
  ["param", "lang", "en"]

步骤2: 发布kind:5001(摘要任务)
  ["i", "<step-1-event-id>", "job"]

8. Add Encrypted Parameters (Optional)

8. 添加加密参数(可选)

For privacy, encrypt
i
and
param
tags using NIP-04 with the service provider's pubkey:
  1. Collect all
    i
    and
    param
    tags into a JSON array
  2. Encrypt with NIP-04 (customer's private key + SP's public key)
  3. Put encrypted payload in
    content
    field
  4. Add
    ["encrypted"]
    tag and
    ["p", "<sp-pubkey>"]
    tag
  5. Remove plaintext
    i
    and
    param
    tags from the event
json
{
  "kind": 5001,
  "content": "<nip04-encrypted-payload>",
  "tags": [
    ["p", "<service-provider-pubkey>"],
    ["encrypted"],
    ["output", "text/plain"],
    ["relays", "wss://relay.example.com"]
  ]
}
The SP decrypts the content to recover the input parameters. If the request was encrypted, the result MUST also be encrypted and tagged
["encrypted"]
.
为了保障隐私,可以使用NIP-04协议,用服务提供商的公钥加密
i
param
标签:
  1. 将所有
    i
    param
    标签整理为JSON数组
  2. 使用NIP-04加密(消费者私钥 + 服务提供商公钥)
  3. 将加密后的 payload 放入
    content
    字段
  4. 添加
    ["encrypted"]
    标签和
    ["p", "<sp-pubkey>"]
    标签
  5. 移除事件中明文的
    i
    param
    标签
json
{
  "kind": 5001,
  "content": "<nip04-encrypted-payload>",
  "tags": [
    ["p", "<service-provider-pubkey>"],
    ["encrypted"],
    ["output", "text/plain"],
    ["relays", "wss://relay.example.com"]
  ]
}
服务提供商会解密内容获取输入参数。如果请求是加密的,返回的结果也必须加密,并且添加
["encrypted"]
标签。

9. Publish Service Provider Discovery (NIP-89)

9. 发布服务提供商发现信息(NIP-89)

Advertise DVM capabilities with a kind:31990 handler announcement:
json
{
  "kind": 31990,
  "content": "{\"name\":\"My Summarizer DVM\",\"about\":\"AI-powered text summarization\"}",
  "tags": [
    ["d", "<unique-identifier>"],
    ["k", "5001"],
    ["t", "summarization"],
    ["t", "ai"]
  ]
}
  • k
    tag: the job request kind this DVM handles (e.g.,
    5001
    )
  • t
    tags: topic tags for discoverability
  • content
    : JSON with
    name
    and
    about
    fields (like kind:0 metadata)
通过kind:31990处理程序公告来宣传DVM的能力:
json
{
  "kind": 31990,
  "content": "{\"name\":\"我的摘要DVM\",\"about\":\"AI驱动的文本摘要服务\"}",
  "tags": [
    ["d", "<unique-identifier>"],
    ["k", "5001"],
    ["t", "summarization"],
    ["t", "ai"]
  ]
}
  • k
    标签:该DVM支持的任务请求类型编号(例如
    5001
  • t
    标签:用于提升可发现性的主题标签
  • content
    :包含
    name
    about
    字段的JSON(类似kind:0元数据)

10. Handle Cancellation

10. 处理任务取消

Customers cancel jobs by publishing a kind:5 deletion request:
json
{
  "kind": 5,
  "tags": [
    ["e", "<job-request-event-id>"],
    ["k", "5001"]
  ],
  "content": "No longer needed"
}
Service providers SHOULD monitor for kind:5 events tagging their active jobs and stop processing if a cancellation is received.
消费者可以通过发布kind:5删除请求来取消任务:
json
{
  "kind": 5,
  "tags": [
    ["e", "<job-request-event-id>"],
    ["k", "5001"]
  ],
  "content": "不再需要该任务"
}
服务提供商应监听标记了其活跃任务的kind:5事件,收到取消请求后停止处理对应的任务。

Checklist

检查清单

  • Job request uses correct kind (5000-5999) for the operation type
  • Input
    i
    tags use valid input-type (
    text
    ,
    url
    ,
    event
    ,
    job
    )
  • Job result kind = request kind + 1000
  • Result includes
    request
    tag with full stringified job request event
  • Result includes
    e
    tag referencing the job request event ID
  • Result includes
    p
    tag with customer's pubkey
  • Feedback events use kind:7000 with valid status values
  • payment-required
    feedback includes
    amount
    tag with bolt11
  • Encrypted requests have
    ["encrypted"]
    tag and encrypted content
  • Encrypted results also use
    ["encrypted"]
    tag
  • Job chains use
    ["i", "<event-id>", "job"]
    input type
  • NIP-89 discovery uses kind:31990 with
    k
    tag for supported job kind
  • Cancellation uses kind:5 with
    e
    tag referencing the job request
  • 任务请求使用了对应操作类型的正确编号(5000-5999)
  • 输入
    i
    标签使用了有效的输入类型(
    text
    url
    event
    job
  • 任务结果类型编号 = 请求类型编号 + 1000
  • 结果包含
    request
    标签,值为完整序列化的任务请求事件
  • 结果包含
    e
    标签,关联任务请求的事件ID
  • 结果包含
    p
    标签,值为消费者的公钥
  • 反馈事件使用kind:7000,并且状态值合法
  • payment-required
    反馈包含带bolt11的
    amount
    标签
  • 加密请求带有
    ["encrypted"]
    标签且内容已加密
  • 加密结果也带有
    ["encrypted"]
    标签
  • 任务串联使用
    ["i", "<event-id>", "job"]
    输入类型
  • NIP-89发现信息使用kind:31990,带有
    k
    标签说明支持的任务类型
  • 取消请求使用kind:5,带有
    e
    标签关联任务请求

Example: Complete Summarization DVM Service Provider

示例:完整的摘要DVM服务提供商

Scenario: Build a service provider that handles kind:5001 summarization requests.
场景: 构建一个处理kind:5001摘要请求的服务提供商。

Subscribe to job requests

订阅任务请求

typescript
const sub = relay.subscribe([{ kinds: [5001] }]);
typescript
const sub = relay.subscribe([{ kinds: [5001] }]);

Process a request

处理请求

typescript
async function handleJobRequest(event: NostrEvent) {
  const customerPubkey = event.pubkey;
  const jobId = event.id;

  // 1. Send processing feedback
  await publishEvent({
    kind: 7000,
    content: "",
    tags: [
      ["status", "processing", "Starting summarization"],
      ["e", jobId, "wss://relay.example.com"],
      ["p", customerPubkey],
    ],
  });

  // 2. Extract input data
  const inputTag = event.tags.find((t) => t[0] === "i");
  const inputType = inputTag[2]; // "text", "url", "event", "job"
  let inputData: string;

  if (inputType === "text") {
    inputData = inputTag[1];
  } else if (inputType === "url") {
    inputData = await fetch(inputTag[1]).then((r) => r.text());
  } else if (inputType === "event") {
    inputData = await fetchNostrEvent(inputTag[1], inputTag[3]);
  }

  // 3. Process the job
  const summary = await summarize(inputData);

  // 4. Publish result (kind = 5001 + 1000 = 6001)
  await publishEvent({
    kind: 6001,
    content: summary,
    tags: [
      ["request", JSON.stringify(event)],
      ["e", jobId, "wss://relay.example.com"],
      ["i", inputTag[1], inputTag[2]],
      ["p", customerPubkey],
      ["amount", "1000", generateBolt11(1000)],
    ],
  });
}
typescript
async function handleJobRequest(event: NostrEvent) {
  const customerPubkey = event.pubkey;
  const jobId = event.id;

  // 1. 发送处理中反馈
  await publishEvent({
    kind: 7000,
    content: "",
    tags: [
      ["status", "processing", "开始生成摘要"],
      ["e", jobId, "wss://relay.example.com"],
      ["p", customerPubkey],
    ],
  });

  // 2. 提取输入数据
  const inputTag = event.tags.find((t) => t[0] === "i");
  const inputType = inputTag[2]; // "text", "url", "event", "job"
  let inputData: string;

  if (inputType === "text") {
    inputData = inputTag[1];
  } else if (inputType === "url") {
    inputData = await fetch(inputTag[1]).then((r) => r.text());
  } else if (inputType === "event") {
    inputData = await fetchNostrEvent(inputTag[1], inputTag[3]);
  }

  // 3. 处理任务
  const summary = await summarize(inputData);

  // 4. 发布结果(类型 = 5001 + 1000 = 6001)
  await publishEvent({
    kind: 6001,
    content: summary,
    tags: [
      ["request", JSON.stringify(event)],
      ["e", jobId, "wss://relay.example.com"],
      ["i", inputTag[1], inputTag[2]],
      ["p", customerPubkey],
      ["amount", "1000", generateBolt11(1000)],
    ],
  });
}

Common Mistakes

常见错误

MistakeWhy It BreaksFix
Result kind doesn't match request kind + 1000Clients can't correlate results to requestskind:5001 → kind:6001, always add 1000
Missing
request
tag in result
Clients can't verify the result matches their requestInclude full stringified job request event
request
tag contains event ID instead of full event
Spec requires the complete event JSON as a stringUse
JSON.stringify(originalEvent)
Using
payment-required
without
amount
tag
Customer has no way to payAlways include
["amount", "<msat>", "<bolt11>"]
Forgetting
p
tag in result/feedback
Customer can't find the result via subscriptionAlways tag the customer's pubkey
Encrypting request but not resultLeaks the output even though input was privateIf request has
["encrypted"]
, result must too
Using wrong input-type in
i
tag
SP can't resolve the input data
text
=raw,
url
=fetch,
event
=nostr lookup,
job
=chain
Chaining with
event
type instead of
job
SP treats it as a static event, not a job outputUse
["i", "<id>", "job"]
for chaining
Not monitoring for kind:5 cancellationsWastes compute on cancelled jobsSubscribe to kind:5 events tagging active jobs
NIP-89 announcement missing
k
tag
Clients can't discover which kinds the DVM supportsInclude
["k", "5001"]
for each supported kind
错误造成的问题修复方案
结果类型编号不等于请求类型编号+1000客户端无法关联结果和对应的请求kind:5001 → kind:6001,始终加1000
结果中缺少
request
标签
客户端无法验证结果是否匹配自己的请求包含完整序列化后的任务请求事件
request
标签只存了事件ID而非完整事件
规范要求值为完整的事件JSON字符串使用
JSON.stringify(originalEvent)
使用
payment-required
状态但没有
amount
标签
消费者无法完成支付始终添加
["amount", "<msat>", "<bolt11>"]
结果/反馈中忘记加
p
标签
消费者无法通过订阅找到对应的结果始终标记消费者的公钥
加密了请求但没有加密结果即使输入是私密的,输出仍然会泄露如果请求带有
["encrypted"]
标签,结果也必须加密
i
标签使用了错误的输入类型
服务提供商无法解析输入数据
text
=原始文本、
url
=抓取内容、
event
=Nostr查询、
job
=任务串联
串联任务时使用
event
类型而非
job
类型
服务提供商会将其视为静态事件而非任务输出串联时使用
["i", "<id>", "job"]
未监听kind:5取消请求在已取消的任务上浪费计算资源订阅标记了活跃任务的kind:5事件
NIP-89公告缺少
k
标签
客户端无法发现DVM支持的任务类型为每个支持的任务类型添加
["k", "5001"]
标签

Key Principles

核心原则

  1. Result kind = request kind + 1000 — This is the fundamental mapping. kind:5001 always produces kind:6001. No exceptions.
  2. The
    request
    tag carries the full event
    — Not just the ID. The entire original job request event must be stringified and included so clients can verify the result matches their request without additional lookups.
  3. Payment is flexible, signaling is not — SPs can choose when to require payment, but they MUST use
    payment-required
    feedback to signal it. Silent blocking creates a broken UX.
  4. Encrypted in = encrypted out — If the job request uses encrypted params, the result MUST also be encrypted. Partial encryption leaks data.
  5. Job chaining uses the
    job
    input type
    — Not
    event
    . The
    job
    type tells the SP to wait for and use the output of a previous DVM job, not just read a static event.
  1. 结果类型编号 = 请求类型编号 + 1000 —— 这是基础映射规则,kind:5001永远返回kind:6001,没有例外。
  2. request
    标签包含完整事件
    —— 而不只是ID。必须包含完整的原始任务请求事件序列化字符串,这样客户端无需额外查询就能验证结果是否匹配自己的请求。
  3. 支付方式灵活,但通知规则固定 —— 服务提供商可以选择何时要求付款,但必须使用
    payment-required
    反馈来通知,静默阻塞会导致极差的用户体验。
  4. 加密入 → 加密出 —— 如果任务请求使用了加密参数,返回结果也必须加密,部分加密会导致数据泄露。
  5. 任务串联使用
    job
    输入类型
    —— 而非
    event
    job
    类型会告知服务提供商需要等待并使用前一个DVM任务的输出,而不是读取静态事件。