1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
|
NIP-XX
======
Data Vending Machine
--------------------
`draft` `optional` `author:pablof7z`
This NIP defines the interaction between customers and Service Providers to perform on-demand computation.
## Rationale
Nostr can act as a marketplace for data processing, where users request jobs to be processed in certain ways (e.g. "speech-to-text", "summarization", etc.), but where they don't necessarily care about "who" processes the data.
This NIP is not to be confused with a 1:1 marketplace; but rather, a flow where user announces a desired output, willigness to pay, and service providers compete to fulfill the job requirement in the best way possible.
### Actors
There are two actors to the workflow described in this NIP:
* Customers (npubs who request a job)
* Service providers (npubs who fulfill jobs)
# Event Kinds
## Job request
A request to have data processed -- published by a customer
```json
{
"kind": 68001,
"content": "",
"tags": [
[ "j", "<job-type>", "<optional-model>" ],
[ "i", "<data>", "<input-type>", "<marker>" ],
[ "output", "<mime-type>" ],
[ "relays", "wss://..."],
[ "bid", "<msat-amount>", "<optional-max-price>" ],
[ "exp", "<timestamp>" ],
[ "p", "service-provider-1" ],
[ "p", "service-provider-2" ],
]
}
```
* `content` field: An optional, human-readable description of what this job is for.
* `j` tag: Job-type to be executed.
* A job request MUST have exactly one `j` tag.
* It MAY include a second value specifying the name of a model to be used when computing the result.
* `i` tag: Input data for the job.
* A job request CAN have zero or more inputs.
* Positional arguments: `["i", "<data>", "<input-type>", "<relay>", "<marker>"]`
* `<data>`: The argument for the input
* `<input-type>`: The way this argument should be interpreted, one of:
* `url`: a URL to be fetched
* `event`: a nostr event ID, include an optional relay-url extra param
* `job`: the output of a previous job with the specified event ID
* `<relay>`: if `event` or `job` input-type, the relay where the event/job was published, otherwise optional or empty string.
* `<marker>`: an optional field indicating how this input should be used.
* `output` tag: MIME type. Expected output format. Service Providers SHOULD publish the result of the job in this format.
* `bid` tag: Customer MAY specify a maximum amount (in millisats) they are willing to pay.
* `relays` tag: relays where Service Providers SHOULD publish responses to.
* `p` tags: Service Providers the customer is interested in having process this job. Other SP MIGHT still choose to process the job.
* `exp`: Optional expiration timestamp. Service Providers SHOULD not send results after this timestamp.
## Job result
The output of processing the data -- published by the Service Provider.
```json
{
"pubkey": "service-provider pubkey in hex",
"content": "<payload>",
"kind": 68002,
"tags" [
[ "request", "<68001-event>" ],
[ "e", "<id-of-68001-event>" ],
[ "p", "<Customer's pubkey>" ],
[ "status", "success", "<more-info>" ],
[ "amount", "requested-payment-amount" ]
]
}
```
## Job feedback
Both customers and service providers can give feedback about a job.
The result of the job SHOULD be included in the `content` field. If the output is not text, the `content` field SHOULD be empty and an `output` tag should be used instead as described below.
* `status` tag: Service Providers MAY indicate errors or extra info about the results by including them in the `status` tag.
* `amount`: millisats that the Service Provider is requesting to be paid.
# Protocol Flow
* Customer publishes a job request
`{ "kind": 68001, "tags": [ [ "j", "speech-to-text" ], ... ] }`
* Service Providers subsribe to the type of jobs they can perform
`{"kinds":[68001], "#j": ["speech-to-text", "image-generation", ... ]}`
* When a job comes in, the Service Providers who opt to attempt to fulfill the request begin processing it, or they can react to it with feedback for the user (e.g. _payment required_, _unprocessable entity_, etc.)
* Upon completion, the service provider publishes the result of the job with a `job-result` event.
* Upon acceptance, the user zaps the service provider, tagging the job result event.
# Payment
Customers SHOULD pay service providers whose job results they accept by zapping the Service Provider and tagging the `kind:68002` job result.
Additionally, if a service provider requests full or partial prepayment via a `kind:68003` job-feedback event, the customer SHOULD zap that event to pay the service provider.
# Cancellation
A `kind:68001` job request might be cancelled by publishing a `kind:5` delete request event tagging the job request event.
# Job chaining
A Customer MAY request multiple jobs to be processed in a chained form, so that the output of a job can be the input of the next job. (e.g. summarization of a podcast's transcription). This is done by specifying as `input` an eventID of a different job with the `job` marker.
Service Providers MAY begin processing a subsequent job the moment they see the prior job's result, but they will likely wait for a zap to be published first. This introduces a risk that Service Provider of job #1 might delay publishing the zap event in order to have an advantage. This risk is up to Service Providers to mitigate or to decide whether the service provider of job #1 tends to have good-enough results so as to not wait for a explicit zap to assume the job was accepted.
# Job Feedback
## Job request reactions
Service Providers might opt to give feedback about a job.
### E.g. Payment required
```json
{
"kind": 68003,
"content": "Please pay 7 sats for job xxxx",
"tags": [
[ "e", <job-request-id> ],
[ "status", "payment-required" ],
[ "amount", "7000" ],
]
}
```
## Job feedback
A user might choose to not accept a job result for any reason. A user can provide feedback via NIP-25 reactions.
The `content` of the `kind:7` event SHOULD include a description of how the user reacted to the job result.
## Explicitly not addressed in this NIP
### Reputation system
Service providers are at obvious risk of having their results not compensated. Mitigation of this risk is up to service providers to figure out (i.e. building reputation systems, requiring npub "balances", etc, etc).
It's out of scope (and undesirable) to have this NIP address this issue; the market should.
# Appendix 1: Examples
## Transcript of a podcast from second `900` to `930`.
### `kind:68001`: Job Request
```json
{
"id": "12345",
"pubkey": "abcdef",
"content": "",
"tags": [
[ "j", "speech-to-text" ],
[ "i", "https://bitcoin.review/episode1.mp3", "url" ],
[ "params", "range", "900", "930" ],
[ "bid", "5000", "9000" ],
[ "output", "text/plain" ]
]
}
```
### `kind:68003`: Job Feedback: request for (partial) payment
```json
{
"kind": 68003,
"content": "",
"tags": [
["e", "12345"],
["p", "abcdef"],
["status", "payment-required"],
["amount", "1000"]
]
}
```
* User zaps 1000 sats to event kind:68003.
### `kind:68002`: Job fulfillment + request for remaining payment
```json
{
"content": "blah blah blah",
"tags": [
["e", "12345"],
["p", "abcdef"],
["amount", "6000"]
]
}
```
## Summarization of a podcast
User publishes two job requests at the same time in the order they should be executed.
### `kind:68001`: Job Request #1
```json
{
"id": "12345",
"pubkey": "abcdef",
"content": "I need a transcript of Bitcoin.review from second 900 to 930",
"tags": [
[ "j", "speech-to-text" ],
[ "params", "range", "900", "930" ],
[ "i", "https://bitcoin.review/episode1.mp3", "url" ],
[ "bid", "5000", "9000" ]
]
}
```
### `kind:68001`: Job Request #2
```json
{
"id": "12346",
"pubkey": "abcdef",
"content": "",
"tags": [
[ "j", "summarization" ],
[ "params", "length", "3 paragraphs" ],
[ "i", "12346", "job" ],
[ "bid", "300", "900" ]
]
}
```
## Translation of a note
### `kind:68001`: Job Request #1
```json
{
"id": "12346",
"pubkey": "abcdef",
"content": "",
"tags": [
[ "j", "translation" ],
[ "i", "<hexid>", "event", "wss://relay.nostr.com" ]
[ "params", "language", "es_AR" ],
[ "bid", "100", "500" ]
]
}
```
### `kind:68003`: Job respomse
```json
{
"kind": 68003,
"content": "Che, que copado, boludo!",
"tags": [
["e", "12346"],
["p", "abcdef"],
["amount", "1000"]
]
}
```
## AI-image of the summarization of 2 podcasts
### `kind:68001`: Job request #1 (transcribe podcast #1)
```json
{
"id": "123",
"tags": [
[ "j", "speech-to-text" ],
[ "i", "https://example.com/episode1.mp3", "url" ],
[ "bid", "100", "500" ]
]
}
```
### `kind:68001`: Job request #2 (transcribe podcast #2)
```json
{
"id": "124",
"tags": [
[ "j", "speech-to-text" ],
[ "i", "https://example.com/episode2.mp3", "url" ],
[ "bid", "100", "500" ]
]
}
```
### `kind:68001`: Job request #3 (summarize both podcasts into one podcast)
```json
{
"id": "125",
"tags": [
[ "j", "summarize" ],
[ "param", "length", "1 paragraph" ],
[ "i", "123", "job" ],
[ "i", "124", "job" ],
[ "bid", "100", "500" ]
]
}
```
# Appendix 2: Job types
This NIP defines some example job types, Customers SHOULD specify these types for maximum compatibility with Service Providers. Other job types MAY be added to this NIP after being observed in the wild.
### `speech-to-text`
#### params
| param | req? | description
|--------------------------------|------|--------
| `range` | opt | timestamp range (in seconds) of desired text to be transcribed
| `alignment` | opt | word, segment, raw : word-level, segment-level or raw outputs
### `summarization`
| param | req? | description
|--------------------------------|------|--------
| `length` | opt | desired length
### `translation` -- Translate text to a specific language
#### params
| param | req? | description
|--------------------------------|------|--------
| `language` | req | requested language in BCP 47 format.
# Notes
* Should there be a possibility of getting the job result delivered encrypted? I don't like it but maybe it should be supported.
* Ambiguity on job acceptance, particularly for job-chaining circumstances is deliberately ambiguous: service providers could wait until explicit job result acceptance / payment to start working on the next item on the chain, or they could start working as soon as they see a result of the previous job computed.
That's up to each service provider to choose how to behave depending on the circumstances. This gives a higher level of flexibility to service providers (which sophisticated service providers would take anyway).
|