mycomesh-consumer 0.9.6 → 0.9.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/consumer.mjs +20 -3
- package/src/protocol.mjs +39 -0
package/package.json
CHANGED
package/src/consumer.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { dirname, resolve } from "node:path";
|
|
|
6
6
|
import { rootCertificates } from "node:tls";
|
|
7
7
|
import { addressOf, encodeCall } from "./eip712.mjs";
|
|
8
8
|
import { rpcCall } from "./chain.mjs";
|
|
9
|
-
import { openDelta, openResponse, outputText, prepareRequest, verifiedProvider } from "./protocol.mjs";
|
|
9
|
+
import { openDelta, openResponse, outputText, planRequest, prepareRequest, verifiedProvider } from "./protocol.mjs";
|
|
10
10
|
import { recordRequest } from "./disputes.mjs";
|
|
11
11
|
import { chatStream, responseStream } from "./sse.mjs";
|
|
12
12
|
import { consoleRoutes, localRequestAllowed } from "./console.mjs";
|
|
@@ -248,6 +248,7 @@ export class Consumer {
|
|
|
248
248
|
|
|
249
249
|
async attempt({ endpoint, model, content, maxOutputTokens = 4096, options = {}, provider, onDelta }) {
|
|
250
250
|
let lastError;
|
|
251
|
+
let raisedFee = 0;
|
|
251
252
|
for (const relay of await this.relays()) {
|
|
252
253
|
let candidates;
|
|
253
254
|
try {
|
|
@@ -267,10 +268,15 @@ export class Consumer {
|
|
|
267
268
|
const keyed = candidates.map((descriptor) => [key(descriptor), descriptor]);
|
|
268
269
|
keyed.sort((a, b) => compareRank(a[0], b[0]));
|
|
269
270
|
candidates = keyed.map(([, descriptor]) => descriptor);
|
|
270
|
-
|
|
271
|
+
let retried = false;
|
|
272
|
+
for (let index = 0; index < candidates.length; index += 1) {
|
|
273
|
+
const descriptor = candidates[index];
|
|
274
|
+
// Authorize what this request can really cost, not the Consumer's whole per-request cap.
|
|
275
|
+
const plan = planRequest(descriptor, endpoint, content, maxOutputTokens, this.maxFee);
|
|
276
|
+
const maxFee = Math.min(Number(this.maxFee), Math.max(raisedFee || 0, plan.maxFee));
|
|
271
277
|
const prepared = prepareRequest({
|
|
272
278
|
descriptor, deployment: this.deployment, keyPrivate: this.keyPrivate, relaySigner: relay.signer,
|
|
273
|
-
endpoint, model, content, maxOutputTokens
|
|
279
|
+
endpoint, model, content, maxOutputTokens: plan.maxOutputTokens, maxFee, options, now: this.now(),
|
|
274
280
|
});
|
|
275
281
|
let reply;
|
|
276
282
|
const streamed = [];
|
|
@@ -312,6 +318,17 @@ export class Consumer {
|
|
|
312
318
|
}
|
|
313
319
|
lastError = Object.assign(new Error(reply.body?.error || `Relay returned ${reply.status}`),
|
|
314
320
|
{ status: reply.status, dispatched: reply.body?.dispatched });
|
|
321
|
+
// A Provider that found the max fee too low for this request says what it needs; ask again with that.
|
|
322
|
+
const needed = /worst-case price (\d+)/.exec(String(reply.body?.error || ""));
|
|
323
|
+
if (needed && reply.body?.dispatched === false) {
|
|
324
|
+
const required = Number(needed[1]);
|
|
325
|
+
if (required > Number(this.maxFee)) {
|
|
326
|
+
throw Object.assign(new Error(`this request may cost up to ${required}, above the per-request limit ${this.maxFee}`), { status: 402 });
|
|
327
|
+
}
|
|
328
|
+
raisedFee = Math.ceil(required * 1.2);
|
|
329
|
+
if (!retried) { retried = true; index -= 1; } // the same Provider, once, with the fee it asked for
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
315
332
|
if (reply.body?.dispatched !== false || ![502, 503].includes(reply.status)) throw lastError;
|
|
316
333
|
}
|
|
317
334
|
}
|
package/src/protocol.mjs
CHANGED
|
@@ -111,3 +111,42 @@ export function openResponse(prepared, result, deployment, now = Math.floor(Date
|
|
|
111
111
|
}
|
|
112
112
|
return { response, receipt: signed, responsePlaintext: opened.payload };
|
|
113
113
|
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* (input, output) tokens a request can cost at most, estimated exactly as the Provider does
|
|
117
|
+
* (mycomesh/protocol.py worst_case_tokens): content UTF-8 length / 3, plus the backend's own input, plus
|
|
118
|
+
* the full max_output_tokens.
|
|
119
|
+
*/
|
|
120
|
+
export function worstCaseTokens(endpoint, content, maxOutputTokens, inputOverheadTokens = 0) {
|
|
121
|
+
const bytes = Buffer.byteLength(JSON.stringify(content ?? null), "utf8");
|
|
122
|
+
return [Math.ceil(bytes / 3) + Number(inputOverheadTokens || 0), Number(maxOutputTokens)];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The max fee to authorize for one request, and the output it may use: its worst-case price at the Provider's
|
|
127
|
+
* advertised network price with 50% headroom (a day's price step, estimation error), never above the
|
|
128
|
+
* Consumer's own cap. Relays reserve this amount while the request runs, so a realistic figure is what lets
|
|
129
|
+
* many requests share one deposit and one Provider's daily capacity at once. When even the worst case does
|
|
130
|
+
* not fit the cap, the output is trimmed to what the cap pays for, rather than having the Provider refuse.
|
|
131
|
+
*/
|
|
132
|
+
export function planRequest(descriptor, endpoint, content, maxOutputTokens, cap) {
|
|
133
|
+
const prices = descriptor.prices || {};
|
|
134
|
+
const inPrice = Number(prices.input_per_1k || 0);
|
|
135
|
+
const outPrice = Number(prices.output_per_1k || 0);
|
|
136
|
+
const floor = Math.max(Number(prices.minimum_fee || 0), 1);
|
|
137
|
+
const [input] = worstCaseTokens(endpoint, content, 0, descriptor.input_overhead_tokens);
|
|
138
|
+
const worst = (output) => Math.max(Math.ceil((input * inPrice + output * outPrice) / 1000), floor);
|
|
139
|
+
let output = Number(maxOutputTokens);
|
|
140
|
+
if (worst(output) > Number(cap) && outPrice > 0) {
|
|
141
|
+
// What the cap pays for, keeping 10% for a price step between authorization and settlement.
|
|
142
|
+
output = Math.floor(((Number(cap) / 1.1) * 1000 - input * inPrice) / outPrice);
|
|
143
|
+
if (output < 16) {
|
|
144
|
+
throw Object.assign(new Error(`the per-request limit ${cap} cannot pay for this request at today's price`), { status: 402 });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return { maxFee: Math.min(Number(cap), Math.ceil(worst(output) * 1.5)), maxOutputTokens: output };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function pricedMaxFee(descriptor, endpoint, content, maxOutputTokens, cap) {
|
|
151
|
+
return planRequest(descriptor, endpoint, content, maxOutputTokens, cap).maxFee;
|
|
152
|
+
}
|