lightnode-sdk 0.6.0 → 0.6.1
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/README.md +78 -0
- package/dist/cli.js +137 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -205,6 +205,84 @@ for await (const event of handle.events) {
|
|
|
205
205
|
}
|
|
206
206
|
```
|
|
207
207
|
|
|
208
|
+
### Batch runner (new in 0.6.0)
|
|
209
|
+
|
|
210
|
+
Fan out many prompts as parallel encrypted inferences. Capped concurrency, stable result order, per-slot errors so one stalled worker does not kill the batch.
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
import { runInferenceBatch } from "lightnode-sdk";
|
|
214
|
+
|
|
215
|
+
const results = await runInferenceBatch({
|
|
216
|
+
network: "testnet",
|
|
217
|
+
privateKey: process.env.PRIVATE_KEY!,
|
|
218
|
+
model: "llama3-8b",
|
|
219
|
+
system: "Reply in one short sentence.",
|
|
220
|
+
concurrency: 4,
|
|
221
|
+
prompts: [
|
|
222
|
+
"one-line fact about the ocean",
|
|
223
|
+
"one-line fact about the moon",
|
|
224
|
+
"one-line fact about coffee",
|
|
225
|
+
],
|
|
226
|
+
onSlotComplete: ({ index, result, error }) => {
|
|
227
|
+
console.log(`#${index}`, error?.message ?? result?.answer);
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
for (const r of results) {
|
|
232
|
+
if (r.error) console.warn(`slot ${r.index} failed:`, r.error.message);
|
|
233
|
+
else console.log(r.result.answer);
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Fits: batch evals, content scoring, RAG re-ranking, parallel rewrites. Pass `{signal}` (`AbortSignal`) to cancel queued slots mid-run.
|
|
238
|
+
|
|
239
|
+
### Agent class (new in 0.6.0)
|
|
240
|
+
|
|
241
|
+
ReAct-style tool calling on top of `runInferenceWithKey`. The model emits `<tool>name {"k":"v"}</tool>` or `<answer>...</answer>`; the SDK parses, runs the handler, threads the observation back. Works on small open models (llama3-8b) without native function calling.
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
import { Agent } from "lightnode-sdk";
|
|
245
|
+
|
|
246
|
+
const agent = new Agent({
|
|
247
|
+
network: "testnet",
|
|
248
|
+
privateKey: process.env.PRIVATE_KEY!,
|
|
249
|
+
model: "llama3-8b",
|
|
250
|
+
system: "You are a careful research assistant.",
|
|
251
|
+
tools: [
|
|
252
|
+
{
|
|
253
|
+
name: "add",
|
|
254
|
+
description: "Add two integers and return the sum.",
|
|
255
|
+
args: { a: "first integer", b: "second integer" },
|
|
256
|
+
handler: ({ a, b }) => Number(a) + Number(b),
|
|
257
|
+
},
|
|
258
|
+
],
|
|
259
|
+
maxIterations: 3,
|
|
260
|
+
onStep: (step) => console.log(step.kind, step),
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const { answer, steps, hitLimit } = await agent.run("What is 17 + 25?");
|
|
264
|
+
console.log(answer); // "42"
|
|
265
|
+
console.log(steps); // [{ kind: "tool_call", ... }, { kind: "answer", text: "42" }]
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Each iteration is one inference (one on-chain `submitJob`); cap `maxIterations` to keep wall-clock + cost bounded. Tool handlers are plain functions that may be async; return JSON-serializable data so the model can read the observation.
|
|
269
|
+
|
|
270
|
+
### Cancellation (new in 0.6.0)
|
|
271
|
+
|
|
272
|
+
`runInference` and `runInferenceWithKey` accept an `AbortSignal`. In-flight on-chain transactions still settle (the protocol is the source of truth); the SDK just stops awaiting and rejects with `Error("aborted")`.
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
const controller = new AbortController();
|
|
276
|
+
setTimeout(() => controller.abort(), 15_000);
|
|
277
|
+
|
|
278
|
+
await runInferenceWithKey({
|
|
279
|
+
network: "testnet",
|
|
280
|
+
privateKey: process.env.PRIVATE_KEY!,
|
|
281
|
+
prompt: "short answer please",
|
|
282
|
+
signal: controller.signal,
|
|
283
|
+
});
|
|
284
|
+
```
|
|
285
|
+
|
|
208
286
|
### Typed errors
|
|
209
287
|
|
|
210
288
|
```ts
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { LightNode, modelStatsCsv, workerStatsCsv, workerJobsCsv, runInferenceWithKey, isStalledWorker, workerPreflight, workerWatch, BRIDGE_ROUTE, DAO, DAO_ADDRESSES } from "./index.js";
|
|
2
|
+
import { LightNode, modelStatsCsv, workerStatsCsv, workerJobsCsv, runInferenceWithKey, runInferenceBatch, Agent, isStalledWorker, workerPreflight, workerWatch, BRIDGE_ROUTE, DAO, DAO_ADDRESSES } from "./index.js";
|
|
3
3
|
import { addInference, addAnalyticsDashboard, addNftMint, addChat, addAgent } from "./add.js";
|
|
4
4
|
import { createPublicClient, http, parseEther } from "viem";
|
|
5
5
|
import { privateKeyToAccount, generatePrivateKey } from "viem/accounts";
|
|
@@ -22,6 +22,10 @@ const HELP = `lightnode <command> [--net mainnet|testnet]
|
|
|
22
22
|
Run one inference (needs PRIVATE_KEY in env):
|
|
23
23
|
chat <prompt> stream one encrypted inference answer to stdout
|
|
24
24
|
([--model llama3-8b] [--key 0x...])
|
|
25
|
+
batch <prompts.json> run N prompts in parallel, JSON line per result
|
|
26
|
+
([--model] [--concurrency 4])
|
|
27
|
+
agent <task> ReAct-style agent with built-in add + now tools
|
|
28
|
+
([--model] [--max-iter 4])
|
|
25
29
|
|
|
26
30
|
Wallet helpers:
|
|
27
31
|
wallet new generate a fresh testnet key, print it
|
|
@@ -392,8 +396,140 @@ async function main() {
|
|
|
392
396
|
}
|
|
393
397
|
break;
|
|
394
398
|
}
|
|
399
|
+
case "batch": {
|
|
400
|
+
// `lightnode batch <prompts.json>` or `lightnode batch -` (stdin).
|
|
401
|
+
// Input shape: ["prompt one","prompt two"] OR
|
|
402
|
+
// { "model": "llama3-8b", "system": "...", "prompts": ["...", "..."] }
|
|
403
|
+
// Output: one JSON object per line (index, answer or error). Composes
|
|
404
|
+
// with `jq` for downstream processing.
|
|
405
|
+
const arg = positionals[1] ?? "";
|
|
406
|
+
if (!arg)
|
|
407
|
+
die("usage: lightnode batch <prompts.json> (or `lightnode batch -` to read stdin)");
|
|
408
|
+
const raw = await (arg === "-" ? readStdin() : readFile(arg));
|
|
409
|
+
let parsed;
|
|
410
|
+
try {
|
|
411
|
+
parsed = JSON.parse(raw);
|
|
412
|
+
}
|
|
413
|
+
catch (e) {
|
|
414
|
+
die("invalid JSON: " + e.message);
|
|
415
|
+
}
|
|
416
|
+
const cfg = normalizeBatchInput(parsed);
|
|
417
|
+
const model = flag("--model") ?? cfg.model ?? "llama3-8b";
|
|
418
|
+
const concurrency = Number(flag("--concurrency") ?? "4") || 4;
|
|
419
|
+
const privateKey = pickKey();
|
|
420
|
+
process.stderr.write(`Running ${cfg.prompts.length} prompts on ${net} via ${model} (concurrency=${concurrency})\n`);
|
|
421
|
+
const results = await runInferenceBatch({
|
|
422
|
+
network: net,
|
|
423
|
+
privateKey,
|
|
424
|
+
model,
|
|
425
|
+
system: cfg.system,
|
|
426
|
+
concurrency,
|
|
427
|
+
prompts: cfg.prompts,
|
|
428
|
+
onSlotComplete: ({ index, result, error }) => {
|
|
429
|
+
process.stdout.write(JSON.stringify({
|
|
430
|
+
index,
|
|
431
|
+
ok: error == null,
|
|
432
|
+
answer: result?.answer ?? null,
|
|
433
|
+
error: error?.message ?? null,
|
|
434
|
+
jobId: result?.jobId.toString() ?? error?.jobId ?? null,
|
|
435
|
+
}) + "\n");
|
|
436
|
+
},
|
|
437
|
+
});
|
|
438
|
+
const okCount = results.filter((r) => r.error == null).length;
|
|
439
|
+
process.stderr.write(`Done: ${okCount}/${results.length} succeeded\n`);
|
|
440
|
+
break;
|
|
441
|
+
}
|
|
442
|
+
case "agent": {
|
|
443
|
+
// Quick one-shot agent demo: built-in `add` + `now` tools. Bring the
|
|
444
|
+
// model your prompt as positional args (or pipe via stdin) and watch
|
|
445
|
+
// the step trace on stderr while the answer streams to stdout.
|
|
446
|
+
const inline = positionals.slice(1).join(" ").trim();
|
|
447
|
+
const task = inline || (await readStdin()).trim();
|
|
448
|
+
if (!task)
|
|
449
|
+
die("usage: lightnode agent <task> (or pipe the task to stdin)");
|
|
450
|
+
const model = flag("--model") ?? "llama3-8b";
|
|
451
|
+
const maxIter = Number(flag("--max-iter") ?? "4") || 4;
|
|
452
|
+
const privateKey = pickKey();
|
|
453
|
+
// A tiny built-in toolset so the command is runnable without writing
|
|
454
|
+
// a wrapper. For real tools, import Agent from the SDK and pass your own.
|
|
455
|
+
const tools = [
|
|
456
|
+
{
|
|
457
|
+
name: "add",
|
|
458
|
+
description: "Add two integers and return the sum.",
|
|
459
|
+
args: { a: "first integer", b: "second integer" },
|
|
460
|
+
handler: ({ a, b }) => Number(a) + Number(b),
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
name: "now",
|
|
464
|
+
description: "Return the current ISO timestamp.",
|
|
465
|
+
args: {},
|
|
466
|
+
handler: () => new Date().toISOString(),
|
|
467
|
+
},
|
|
468
|
+
];
|
|
469
|
+
const agent = new Agent({
|
|
470
|
+
network: net,
|
|
471
|
+
privateKey,
|
|
472
|
+
model,
|
|
473
|
+
system: "You are a careful assistant. Use tools when they help; otherwise answer directly.",
|
|
474
|
+
tools,
|
|
475
|
+
maxIterations: maxIter,
|
|
476
|
+
onStep: (step) => {
|
|
477
|
+
if (step.kind === "tool_call") {
|
|
478
|
+
process.stderr.write(`[tool] ${step.name}(${JSON.stringify(step.args)}) -> ${JSON.stringify(step.result)}\n`);
|
|
479
|
+
}
|
|
480
|
+
else if (step.kind === "tool_error") {
|
|
481
|
+
process.stderr.write(`[tool-error] ${step.name}: ${step.error}\n`);
|
|
482
|
+
}
|
|
483
|
+
else if (step.kind === "thought") {
|
|
484
|
+
process.stderr.write(`[think] ${step.text.slice(0, 200)}\n`);
|
|
485
|
+
}
|
|
486
|
+
},
|
|
487
|
+
});
|
|
488
|
+
try {
|
|
489
|
+
const { answer, steps, iterations, hitLimit } = await agent.run(task);
|
|
490
|
+
process.stdout.write(answer + "\n");
|
|
491
|
+
process.stderr.write(JSON.stringify({ iterations, steps: steps.length, hitLimit }) + "\n");
|
|
492
|
+
}
|
|
493
|
+
catch (e) {
|
|
494
|
+
die("agent failed: " + e.message);
|
|
495
|
+
}
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
395
498
|
default:
|
|
396
499
|
console.log(HELP);
|
|
397
500
|
}
|
|
398
501
|
}
|
|
502
|
+
async function readStdin() {
|
|
503
|
+
return new Promise((resolve) => {
|
|
504
|
+
let buf = "";
|
|
505
|
+
process.stdin.setEncoding("utf8");
|
|
506
|
+
process.stdin.on("data", (d) => (buf += d));
|
|
507
|
+
process.stdin.on("end", () => resolve(buf));
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
async function readFile(path) {
|
|
511
|
+
const fs = await import("node:fs/promises");
|
|
512
|
+
return fs.readFile(path, "utf8");
|
|
513
|
+
}
|
|
514
|
+
function normalizeBatchInput(parsed) {
|
|
515
|
+
if (Array.isArray(parsed)) {
|
|
516
|
+
const prompts = parsed.filter((p) => typeof p === "string");
|
|
517
|
+
if (!prompts.length)
|
|
518
|
+
die("batch input: array must contain at least one string prompt");
|
|
519
|
+
return { prompts };
|
|
520
|
+
}
|
|
521
|
+
if (parsed && typeof parsed === "object") {
|
|
522
|
+
const obj = parsed;
|
|
523
|
+
const prompts = Array.isArray(obj.prompts) ? obj.prompts.filter((p) => typeof p === "string") : [];
|
|
524
|
+
if (!prompts.length)
|
|
525
|
+
die("batch input: object must have a `prompts` array of strings");
|
|
526
|
+
return {
|
|
527
|
+
prompts,
|
|
528
|
+
system: typeof obj.system === "string" ? obj.system : undefined,
|
|
529
|
+
model: typeof obj.model === "string" ? obj.model : undefined,
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
die("batch input: expected JSON array of strings OR object { prompts, system?, model? }");
|
|
533
|
+
return { prompts: [] };
|
|
534
|
+
}
|
|
399
535
|
main().catch((e) => die(String(e?.message ?? e)));
|
package/dist/index.d.ts
CHANGED
|
@@ -90,7 +90,7 @@ export declare class LightNode {
|
|
|
90
90
|
* (especially in registry-proxy environments like StackBlitz where lockfiles
|
|
91
91
|
* may pin an older minor than the local install command suggests).
|
|
92
92
|
*/
|
|
93
|
-
export declare const SDK_VERSION = "0.6.
|
|
93
|
+
export declare const SDK_VERSION = "0.6.1";
|
|
94
94
|
export { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS, aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv, fromWei, computeModelId as modelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, consumerGatewayHost, GatewayClient, GatewayHttpError, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, crypto, runInference, runInferenceWithKey, runInferenceStream, Conversation, chat, runInferenceBatch, Agent, parseAgentOutput, workerPreflight, workerWatch, Bridge, BRIDGE_ROUTE, HYPERLANE_ROUTER_ABI, ERC20_ABI, addressToBytes32, quoteBridgeFee, bridgeableBalance, bridgeAllowance, approveBridge, bridgeTransfer, DAO, DAO_ADDRESSES, ProposalState, PROPOSAL_STATE_LABEL, VoteSupport, GOVERNOR_ABI, VOTES_ABI, OnchainModelRegistry, AIVM_MODEL_REGISTRY_ABI, BENCHMARK_REGISTRY_ABI, ModelStatus, MODEL_STATUS_LABEL, StalledWorkerError, OnChainRevertError, RelayTokenTimeoutError, GatewayAuthError, isStalledWorker, };
|
|
95
95
|
export type { BearerSource, GatewayClientOptions, SelectSessionResult, PrepareSessionResult, UploadBlobResult, SessionTokenResult } from "./gateway.js";
|
|
96
96
|
export type { SessionPreparation, RunInferenceArgs, RunInferenceResult, RunInferenceWithKeyArgs, RunInferenceStreamResult } from "./inference.js";
|
package/dist/index.js
CHANGED
|
@@ -145,7 +145,7 @@ export class LightNode {
|
|
|
145
145
|
* (especially in registry-proxy environments like StackBlitz where lockfiles
|
|
146
146
|
* may pin an older minor than the local install command suggests).
|
|
147
147
|
*/
|
|
148
|
-
export const SDK_VERSION = "0.6.
|
|
148
|
+
export const SDK_VERSION = "0.6.1";
|
|
149
149
|
export { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS, aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv, fromWei, computeModelId as modelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, consumerGatewayHost,
|
|
150
150
|
// v0.3 inference-submit surface (BETA - see README "Submitting inference").
|
|
151
151
|
GatewayClient, GatewayHttpError, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, crypto,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lightnode-sdk",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Read-only TypeScript client for LightChain AI: workers, jobs, models, on-chain registration, and per-model network analytics. Independent, community-built (not an official LightChain package).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|