0g-orbit 0.2.2 → 0.2.3
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/CHANGELOG.md +45 -0
- package/LICENSE +21 -0
- package/README.md +151 -0
- package/dist/cli/cli.js +1 -1
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +23 -1
- package/dist/storage.js.map +1 -1
- package/package.json +28 -4
- package/examples/ai-chatbot/index.ts +0 -74
- package/examples/model-registry/index.ts +0 -137
- package/examples/quick-start/index.ts +0 -65
- package/packages/cli/package.json +0 -30
- package/packages/cli/src/cli.ts +0 -69
- package/packages/cli/src/commands/account.ts +0 -29
- package/packages/cli/src/commands/inference.ts +0 -103
- package/packages/cli/src/commands/init.ts +0 -71
- package/packages/cli/src/commands/storage.ts +0 -91
- package/packages/cli/src/utils.ts +0 -21
- package/packages/cli/tsconfig.json +0 -8
- package/packages/core/package.json +0 -35
- package/packages/core/src/errors.test.ts +0 -99
- package/packages/core/src/errors.ts +0 -79
- package/packages/core/src/index.ts +0 -37
- package/packages/core/src/inference.ts +0 -256
- package/packages/core/src/networks.test.ts +0 -62
- package/packages/core/src/networks.ts +0 -62
- package/packages/core/src/orbit.test.ts +0 -153
- package/packages/core/src/orbit.ts +0 -159
- package/packages/core/src/retry.test.ts +0 -99
- package/packages/core/src/retry.ts +0 -99
- package/packages/core/src/storage.test.ts +0 -199
- package/packages/core/src/storage.ts +0 -158
- package/packages/core/src/types.ts +0 -85
- package/packages/core/tsconfig.json +0 -8
- package/packages/core/vitest.config.ts +0 -7
- package/src/cli/cli.ts +0 -95
- package/src/cli/commands/account.ts +0 -29
- package/src/cli/commands/fine-tuning.ts +0 -169
- package/src/cli/commands/inference.ts +0 -103
- package/src/cli/commands/init.ts +0 -71
- package/src/cli/commands/storage.ts +0 -91
- package/src/cli/utils.ts +0 -21
- package/src/errors.test.ts +0 -99
- package/src/errors.ts +0 -90
- package/src/fine-tuning.test.ts +0 -299
- package/src/fine-tuning.ts +0 -330
- package/src/index.ts +0 -45
- package/src/inference.ts +0 -256
- package/src/networks.test.ts +0 -62
- package/src/networks.ts +0 -62
- package/src/orbit.test.ts +0 -153
- package/src/orbit.ts +0 -204
- package/src/retry.test.ts +0 -99
- package/src/retry.ts +0 -99
- package/src/storage.test.ts +0 -199
- package/src/storage.ts +0 -158
- package/src/types.ts +0 -157
- package/tsconfig.json +0 -20
- package/vitest.config.ts +0 -7
package/src/fine-tuning.ts
DELETED
|
@@ -1,330 +0,0 @@
|
|
|
1
|
-
import { Wallet } from 'ethers'
|
|
2
|
-
import {
|
|
3
|
-
createZGComputeNetworkBroker,
|
|
4
|
-
type ZGComputeNetworkBroker,
|
|
5
|
-
} from '@0glabs/0g-serving-broker'
|
|
6
|
-
import type { NetworkConfig } from './networks.js'
|
|
7
|
-
import type {
|
|
8
|
-
DatasetUploadResult,
|
|
9
|
-
CreateTaskOptions,
|
|
10
|
-
FineTuneTask,
|
|
11
|
-
FineTuneModel,
|
|
12
|
-
FineTuneProvider,
|
|
13
|
-
FineTuneStatus,
|
|
14
|
-
} from './types.js'
|
|
15
|
-
import { FineTuningError } from './errors.js'
|
|
16
|
-
import { withRetry } from './retry.js'
|
|
17
|
-
import { StorageClient } from './storage.js'
|
|
18
|
-
|
|
19
|
-
const DEFAULT_TRAINING_PARAMS = {
|
|
20
|
-
nEpochs: 3,
|
|
21
|
-
batchSize: 4,
|
|
22
|
-
learningRate: 5e-5,
|
|
23
|
-
loraRank: 8,
|
|
24
|
-
loraAlpha: 16,
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export class FineTuningClient {
|
|
28
|
-
private broker: ZGComputeNetworkBroker | null = null
|
|
29
|
-
private initialized = false
|
|
30
|
-
private wallet: Wallet
|
|
31
|
-
private network: NetworkConfig
|
|
32
|
-
private storageClient: StorageClient
|
|
33
|
-
|
|
34
|
-
constructor(network: NetworkConfig, wallet: Wallet, storageClient: StorageClient) {
|
|
35
|
-
this.network = network
|
|
36
|
-
this.wallet = wallet
|
|
37
|
-
this.storageClient = storageClient
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
private async ensureBroker(): Promise<ZGComputeNetworkBroker> {
|
|
41
|
-
if (this.broker && this.initialized) return this.broker
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
this.broker = await createZGComputeNetworkBroker(
|
|
45
|
-
this.wallet as any,
|
|
46
|
-
this.network.ledgerContractAddress,
|
|
47
|
-
this.network.inferenceContractAddress,
|
|
48
|
-
this.network.fineTuningContractAddress
|
|
49
|
-
)
|
|
50
|
-
this.initialized = true
|
|
51
|
-
return this.broker
|
|
52
|
-
} catch (err: unknown) {
|
|
53
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
54
|
-
throw new FineTuningError(
|
|
55
|
-
`Failed to initialize fine-tuning broker: ${msg}`,
|
|
56
|
-
'Check your network connection and wallet private key. The compute contracts may be temporarily unavailable.'
|
|
57
|
-
)
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// --- Dataset ---
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Upload a dataset to 0G Storage for fine-tuning.
|
|
65
|
-
* Uses the existing StorageClient for upload, returning the root hash
|
|
66
|
-
* needed to create a fine-tuning task.
|
|
67
|
-
*/
|
|
68
|
-
async uploadDataset(filePath: string): Promise<DatasetUploadResult> {
|
|
69
|
-
return withRetry(
|
|
70
|
-
async () => {
|
|
71
|
-
try {
|
|
72
|
-
const result = await this.storageClient.store(filePath)
|
|
73
|
-
return {
|
|
74
|
-
root: result.root,
|
|
75
|
-
txHash: result.txHash,
|
|
76
|
-
}
|
|
77
|
-
} catch (err: unknown) {
|
|
78
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
79
|
-
throw new FineTuningError(
|
|
80
|
-
`Failed to upload dataset: ${msg}`,
|
|
81
|
-
'Check that the dataset file exists, is valid JSONL, and you have sufficient OG balance.'
|
|
82
|
-
)
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
{ maxAttempts: 3 }
|
|
86
|
-
)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// --- Tasks ---
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Create a fine-tuning task. Requires a dataset already uploaded to 0G Storage.
|
|
93
|
-
*/
|
|
94
|
-
async createTask(options: CreateTaskOptions): Promise<FineTuneTask> {
|
|
95
|
-
const broker = await this.ensureBroker()
|
|
96
|
-
|
|
97
|
-
if (!broker.fineTuning) {
|
|
98
|
-
throw new FineTuningError(
|
|
99
|
-
'Fine-tuning broker not available.',
|
|
100
|
-
'Fine-tuning requires a Wallet signer (not JsonRpcSigner).'
|
|
101
|
-
)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const params = { ...DEFAULT_TRAINING_PARAMS, ...options.trainingParams }
|
|
105
|
-
|
|
106
|
-
return withRetry(
|
|
107
|
-
async () => {
|
|
108
|
-
try {
|
|
109
|
-
// Acknowledge the provider signer (required before task creation)
|
|
110
|
-
await broker.fineTuning!.acknowledgeProviderSigner(
|
|
111
|
-
options.providerAddress
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
// Write training params as JSON string (broker expects a file path,
|
|
115
|
-
// but the underlying createTask reads the file content — we'll pass
|
|
116
|
-
// the JSON string directly via a temp file approach)
|
|
117
|
-
const { writeFileSync, unlinkSync } = await import('node:fs')
|
|
118
|
-
const { tmpdir } = await import('node:os')
|
|
119
|
-
const { join } = await import('node:path')
|
|
120
|
-
const { randomBytes } = await import('node:crypto')
|
|
121
|
-
|
|
122
|
-
const tempPath = join(tmpdir(), `orbit-params-${randomBytes(8).toString('hex')}.json`)
|
|
123
|
-
writeFileSync(tempPath, JSON.stringify(params))
|
|
124
|
-
|
|
125
|
-
let taskId: string
|
|
126
|
-
try {
|
|
127
|
-
taskId = await broker.fineTuning!.createTask(
|
|
128
|
-
options.providerAddress,
|
|
129
|
-
options.model,
|
|
130
|
-
options.dataset,
|
|
131
|
-
tempPath
|
|
132
|
-
)
|
|
133
|
-
} finally {
|
|
134
|
-
try { unlinkSync(tempPath) } catch { /* ignore cleanup errors */ }
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return {
|
|
138
|
-
id: taskId,
|
|
139
|
-
model: options.model,
|
|
140
|
-
dataset: options.dataset,
|
|
141
|
-
provider: options.providerAddress,
|
|
142
|
-
status: 'init' as FineTuneStatus,
|
|
143
|
-
}
|
|
144
|
-
} catch (err: unknown) {
|
|
145
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
146
|
-
if (msg.includes('User opted not to continue')) {
|
|
147
|
-
throw new FineTuningError(msg, 'The provider has pending tasks in queue. Try again later or use a different provider.')
|
|
148
|
-
}
|
|
149
|
-
throw new FineTuningError(
|
|
150
|
-
`Failed to create task: ${msg}`,
|
|
151
|
-
'Verify the model name, dataset hash, and provider address. Run orbit.listModels() to see available options.'
|
|
152
|
-
)
|
|
153
|
-
}
|
|
154
|
-
},
|
|
155
|
-
{ maxAttempts: 2 }
|
|
156
|
-
)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Get the status of a fine-tuning task.
|
|
161
|
-
*/
|
|
162
|
-
async getTask(providerAddress: string, taskId: string): Promise<FineTuneTask> {
|
|
163
|
-
const broker = await this.ensureBroker()
|
|
164
|
-
|
|
165
|
-
if (!broker.fineTuning) {
|
|
166
|
-
throw new FineTuningError('Fine-tuning broker not available.')
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
try {
|
|
170
|
-
const task = await broker.fineTuning.getTask(providerAddress, taskId)
|
|
171
|
-
return this.mapTask(task, providerAddress)
|
|
172
|
-
} catch (err: unknown) {
|
|
173
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
174
|
-
if (msg.includes('No task found') || msg.includes('not found')) {
|
|
175
|
-
throw new FineTuningError(
|
|
176
|
-
`Task "${taskId}" not found for provider ${providerAddress}.`,
|
|
177
|
-
'Check the task ID and provider address. Run orbit.listTasks() to see your tasks.'
|
|
178
|
-
)
|
|
179
|
-
}
|
|
180
|
-
throw new FineTuningError(`Failed to get task: ${msg}`)
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Get the training log for a fine-tuning task.
|
|
186
|
-
*/
|
|
187
|
-
async getTaskLog(providerAddress: string, taskId: string): Promise<string> {
|
|
188
|
-
const broker = await this.ensureBroker()
|
|
189
|
-
|
|
190
|
-
if (!broker.fineTuning) {
|
|
191
|
-
throw new FineTuningError('Fine-tuning broker not available.')
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
try {
|
|
195
|
-
return await broker.fineTuning.getLog(providerAddress, taskId)
|
|
196
|
-
} catch (err: unknown) {
|
|
197
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
198
|
-
throw new FineTuningError(`Failed to get task log: ${msg}`)
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* List all fine-tuning tasks for a given provider.
|
|
204
|
-
*/
|
|
205
|
-
async listTasks(providerAddress: string): Promise<FineTuneTask[]> {
|
|
206
|
-
const broker = await this.ensureBroker()
|
|
207
|
-
|
|
208
|
-
if (!broker.fineTuning) {
|
|
209
|
-
throw new FineTuningError('Fine-tuning broker not available.')
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
try {
|
|
213
|
-
const tasks = await broker.fineTuning.listTask(providerAddress)
|
|
214
|
-
return tasks.map((t) => this.mapTask(t, providerAddress))
|
|
215
|
-
} catch (err: unknown) {
|
|
216
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
217
|
-
throw new FineTuningError(`Failed to list tasks: ${msg}`)
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// --- Models ---
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Download a fine-tuned model. Combines acknowledge + download.
|
|
225
|
-
* The task must be in 'delivered' status.
|
|
226
|
-
*/
|
|
227
|
-
async downloadModel(
|
|
228
|
-
providerAddress: string,
|
|
229
|
-
taskId: string,
|
|
230
|
-
outputPath: string
|
|
231
|
-
): Promise<void> {
|
|
232
|
-
const broker = await this.ensureBroker()
|
|
233
|
-
|
|
234
|
-
if (!broker.fineTuning) {
|
|
235
|
-
throw new FineTuningError('Fine-tuning broker not available.')
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return withRetry(
|
|
239
|
-
async () => {
|
|
240
|
-
try {
|
|
241
|
-
await broker.fineTuning!.acknowledgeModel(
|
|
242
|
-
providerAddress,
|
|
243
|
-
taskId,
|
|
244
|
-
outputPath
|
|
245
|
-
)
|
|
246
|
-
} catch (err: unknown) {
|
|
247
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
248
|
-
if (msg.includes('No deliverable found')) {
|
|
249
|
-
throw new FineTuningError(
|
|
250
|
-
`Model not ready for task "${taskId}". The task may still be training.`,
|
|
251
|
-
'Check task status with orbit.getFineTuneTask(). The task must be in "delivered" status.'
|
|
252
|
-
)
|
|
253
|
-
}
|
|
254
|
-
throw new FineTuningError(
|
|
255
|
-
`Failed to download model: ${msg}`,
|
|
256
|
-
'Ensure the task is in "delivered" status and the output path is writable.'
|
|
257
|
-
)
|
|
258
|
-
}
|
|
259
|
-
},
|
|
260
|
-
{ maxAttempts: 2 }
|
|
261
|
-
)
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* List available base models for fine-tuning.
|
|
266
|
-
*/
|
|
267
|
-
async listModels(): Promise<FineTuneModel[]> {
|
|
268
|
-
const broker = await this.ensureBroker()
|
|
269
|
-
|
|
270
|
-
if (!broker.fineTuning) {
|
|
271
|
-
throw new FineTuningError('Fine-tuning broker not available.')
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
try {
|
|
275
|
-
const [standardModels, customizedModels] = await broker.fineTuning.listModel()
|
|
276
|
-
|
|
277
|
-
const models: FineTuneModel[] = []
|
|
278
|
-
|
|
279
|
-
for (const [name, config] of standardModels) {
|
|
280
|
-
models.push({ name, config })
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
for (const [name, config] of customizedModels) {
|
|
284
|
-
models.push({ name, config })
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return models
|
|
288
|
-
} catch (err: unknown) {
|
|
289
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
290
|
-
throw new FineTuningError(`Failed to list models: ${msg}`)
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* List fine-tuning service providers.
|
|
296
|
-
*/
|
|
297
|
-
async listProviders(): Promise<FineTuneProvider[]> {
|
|
298
|
-
const broker = await this.ensureBroker()
|
|
299
|
-
|
|
300
|
-
if (!broker.fineTuning) {
|
|
301
|
-
throw new FineTuningError('Fine-tuning broker not available.')
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
try {
|
|
305
|
-
const services = await broker.fineTuning.listService()
|
|
306
|
-
return services.map((s: any) => ({
|
|
307
|
-
address: s.provider ?? '',
|
|
308
|
-
url: s.url ?? '',
|
|
309
|
-
models: s.models ?? [],
|
|
310
|
-
}))
|
|
311
|
-
} catch (err: unknown) {
|
|
312
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
313
|
-
throw new FineTuningError(`Failed to list providers: ${msg}`)
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// --- Helpers ---
|
|
318
|
-
|
|
319
|
-
private mapTask(task: any, providerAddress: string): FineTuneTask {
|
|
320
|
-
return {
|
|
321
|
-
id: task.id ?? '',
|
|
322
|
-
model: task.preTrainedModelHash ?? '',
|
|
323
|
-
dataset: task.datasetHash ?? '',
|
|
324
|
-
provider: providerAddress,
|
|
325
|
-
status: (task.progress?.toLowerCase() ?? 'init') as FineTuneStatus,
|
|
326
|
-
createdAt: task.createdAt,
|
|
327
|
-
updatedAt: task.updatedAt,
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
// Main entry point
|
|
2
|
-
export { Orbit } from './orbit.js'
|
|
3
|
-
|
|
4
|
-
// Clients (for direct access)
|
|
5
|
-
export { StorageClient } from './storage.js'
|
|
6
|
-
export { InferenceClient } from './inference.js'
|
|
7
|
-
export { FineTuningClient } from './fine-tuning.js'
|
|
8
|
-
|
|
9
|
-
// Types
|
|
10
|
-
export type {
|
|
11
|
-
OrbitConfig,
|
|
12
|
-
StoreResult,
|
|
13
|
-
StoreOptions,
|
|
14
|
-
RetrieveOptions,
|
|
15
|
-
InferResult,
|
|
16
|
-
InferOptions,
|
|
17
|
-
ServiceInfo,
|
|
18
|
-
AccountStatus,
|
|
19
|
-
DatasetUploadResult,
|
|
20
|
-
CreateTaskOptions,
|
|
21
|
-
FineTuneTask,
|
|
22
|
-
FineTuneStatus,
|
|
23
|
-
FineTuneModel,
|
|
24
|
-
FineTuneProvider,
|
|
25
|
-
} from './types.js'
|
|
26
|
-
|
|
27
|
-
// Network config
|
|
28
|
-
export type { NetworkName, NetworkConfig } from './networks.js'
|
|
29
|
-
export { NETWORKS, getNetwork } from './networks.js'
|
|
30
|
-
|
|
31
|
-
// Retry utilities
|
|
32
|
-
export { withRetry, isTransientError } from './retry.js'
|
|
33
|
-
export type { RetryOptions } from './retry.js'
|
|
34
|
-
|
|
35
|
-
// Errors
|
|
36
|
-
export {
|
|
37
|
-
OrbitError,
|
|
38
|
-
ConnectionError,
|
|
39
|
-
StorageError,
|
|
40
|
-
InferenceError,
|
|
41
|
-
InsufficientBalanceError,
|
|
42
|
-
ProviderNotFoundError,
|
|
43
|
-
TimeoutError,
|
|
44
|
-
FineTuningError,
|
|
45
|
-
} from './errors.js'
|
package/src/inference.ts
DELETED
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
import { Wallet } from 'ethers'
|
|
2
|
-
import {
|
|
3
|
-
createZGComputeNetworkBroker,
|
|
4
|
-
type ZGComputeNetworkBroker,
|
|
5
|
-
} from '@0glabs/0g-serving-broker'
|
|
6
|
-
import type { NetworkConfig } from './networks.js'
|
|
7
|
-
import type { InferResult, InferOptions, ServiceInfo } from './types.js'
|
|
8
|
-
import { InferenceError, ProviderNotFoundError, TimeoutError } from './errors.js'
|
|
9
|
-
import { withRetry } from './retry.js'
|
|
10
|
-
|
|
11
|
-
const DEFAULT_LEDGER_DEPOSIT = 0.1 // 0.1 OG initial deposit
|
|
12
|
-
const AUTO_FUND_INTERVAL = 30_000 // 30 seconds
|
|
13
|
-
const DEFAULT_TIMEOUT = 30_000 // 30 seconds
|
|
14
|
-
|
|
15
|
-
interface ChatCompletionResponse {
|
|
16
|
-
id?: string
|
|
17
|
-
model?: string
|
|
18
|
-
choices?: Array<{ message?: { content?: string } }>
|
|
19
|
-
usage?: {
|
|
20
|
-
prompt_tokens?: number
|
|
21
|
-
completion_tokens?: number
|
|
22
|
-
total_tokens?: number
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export class InferenceClient {
|
|
27
|
-
private broker: ZGComputeNetworkBroker | null = null
|
|
28
|
-
private wallet: Wallet
|
|
29
|
-
private network: NetworkConfig
|
|
30
|
-
private initialized = false
|
|
31
|
-
|
|
32
|
-
constructor(network: NetworkConfig, wallet: Wallet) {
|
|
33
|
-
this.network = network
|
|
34
|
-
this.wallet = wallet
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
private async ensureBroker(): Promise<ZGComputeNetworkBroker> {
|
|
38
|
-
if (this.broker && this.initialized) return this.broker
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
// Cast wallet to avoid ESM/CJS ethers type mismatch
|
|
42
|
-
this.broker = await createZGComputeNetworkBroker(
|
|
43
|
-
this.wallet as any,
|
|
44
|
-
this.network.ledgerContractAddress,
|
|
45
|
-
this.network.inferenceContractAddress,
|
|
46
|
-
this.network.fineTuningContractAddress
|
|
47
|
-
)
|
|
48
|
-
this.initialized = true
|
|
49
|
-
return this.broker
|
|
50
|
-
} catch (err: unknown) {
|
|
51
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
52
|
-
throw new InferenceError(
|
|
53
|
-
`Failed to initialize compute broker: ${msg}`,
|
|
54
|
-
'Check your network connection and wallet private key. The compute contracts may be temporarily unavailable.'
|
|
55
|
-
)
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async listServices(): Promise<ServiceInfo[]> {
|
|
60
|
-
const broker = await this.ensureBroker()
|
|
61
|
-
return withRetry(
|
|
62
|
-
async () => {
|
|
63
|
-
try {
|
|
64
|
-
const services = await broker.inference.listService()
|
|
65
|
-
return services.map((s: any) => ({
|
|
66
|
-
provider: s.provider ?? s.address ?? '',
|
|
67
|
-
model: s.model ?? '',
|
|
68
|
-
url: s.url ?? '',
|
|
69
|
-
inputPrice: BigInt(s.inputPrice ?? 0),
|
|
70
|
-
outputPrice: BigInt(s.outputPrice ?? 0),
|
|
71
|
-
verifiable: Boolean(s.verifiability ?? s.verifiable),
|
|
72
|
-
}))
|
|
73
|
-
} catch (err: unknown) {
|
|
74
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
75
|
-
throw new InferenceError(
|
|
76
|
-
`Failed to list services: ${msg}`,
|
|
77
|
-
'The inference contract may be temporarily unavailable. Check your network connection.'
|
|
78
|
-
)
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
{ maxAttempts: 3 }
|
|
82
|
-
)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async infer(
|
|
86
|
-
model: string,
|
|
87
|
-
options: InferOptions
|
|
88
|
-
): Promise<InferResult> {
|
|
89
|
-
const broker = await this.ensureBroker()
|
|
90
|
-
|
|
91
|
-
// Find a provider for the requested model
|
|
92
|
-
const providerAddress = options.provider ?? await this.findProvider(model)
|
|
93
|
-
const timeout = options.timeout ?? DEFAULT_TIMEOUT
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
// Ensure ledger exists and has funds
|
|
97
|
-
await this.ensureLedgerFunded(broker)
|
|
98
|
-
|
|
99
|
-
// Start auto-funding for this provider
|
|
100
|
-
await broker.inference.startAutoFunding(providerAddress, {
|
|
101
|
-
interval: AUTO_FUND_INTERVAL,
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
// Get service metadata (endpoint + model name)
|
|
105
|
-
const { endpoint, model: providerModel } =
|
|
106
|
-
await broker.inference.getServiceMetadata(providerAddress)
|
|
107
|
-
|
|
108
|
-
// Build the request content for billing calculation
|
|
109
|
-
const content = options.messages.map((m) => m.content).join('\n')
|
|
110
|
-
|
|
111
|
-
// Get authenticated headers
|
|
112
|
-
const headers = await broker.inference.getRequestHeaders(
|
|
113
|
-
providerAddress,
|
|
114
|
-
content
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
// Make the OpenAI-compatible request with timeout + retry
|
|
118
|
-
const data = await withRetry(
|
|
119
|
-
() => this.fetchCompletion(endpoint, providerModel, options, headers, timeout),
|
|
120
|
-
{
|
|
121
|
-
maxAttempts: 2,
|
|
122
|
-
baseDelay: 2000,
|
|
123
|
-
isRetryable: (err) => {
|
|
124
|
-
// Don't retry timeouts (user already waited long enough)
|
|
125
|
-
if (err instanceof TimeoutError) return false
|
|
126
|
-
// Retry transient provider errors
|
|
127
|
-
if (err instanceof InferenceError) {
|
|
128
|
-
const msg = err.message
|
|
129
|
-
return msg.includes('502') || msg.includes('503') || msg.includes('504')
|
|
130
|
-
}
|
|
131
|
-
return false
|
|
132
|
-
},
|
|
133
|
-
}
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
// Extract chatID for TEE verification
|
|
137
|
-
const chatID = data._chatID
|
|
138
|
-
|
|
139
|
-
// Process response (caches fees + verifies TEE signature)
|
|
140
|
-
let verified: boolean | null = null
|
|
141
|
-
try {
|
|
142
|
-
verified = await broker.inference.processResponse(
|
|
143
|
-
providerAddress,
|
|
144
|
-
chatID,
|
|
145
|
-
data.usage ? JSON.stringify(data.usage) : undefined
|
|
146
|
-
)
|
|
147
|
-
} catch {
|
|
148
|
-
// Verification failure is non-fatal
|
|
149
|
-
verified = null
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Stop auto-funding
|
|
153
|
-
broker.inference.stopAutoFunding(providerAddress)
|
|
154
|
-
|
|
155
|
-
return {
|
|
156
|
-
content: data.choices?.[0]?.message?.content ?? '',
|
|
157
|
-
model: data.model ?? providerModel,
|
|
158
|
-
usage: data.usage
|
|
159
|
-
? {
|
|
160
|
-
promptTokens: data.usage.prompt_tokens ?? 0,
|
|
161
|
-
completionTokens: data.usage.completion_tokens ?? 0,
|
|
162
|
-
totalTokens: data.usage.total_tokens ?? 0,
|
|
163
|
-
}
|
|
164
|
-
: undefined,
|
|
165
|
-
verified,
|
|
166
|
-
}
|
|
167
|
-
} catch (err) {
|
|
168
|
-
// Clean up auto-funding on error
|
|
169
|
-
broker.inference.stopAutoFunding(providerAddress)
|
|
170
|
-
|
|
171
|
-
if (err instanceof InferenceError || err instanceof TimeoutError || err instanceof ProviderNotFoundError) throw err
|
|
172
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
173
|
-
throw new InferenceError(`Inference failed: ${msg}`)
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
private async fetchCompletion(
|
|
178
|
-
endpoint: string,
|
|
179
|
-
providerModel: string,
|
|
180
|
-
options: InferOptions,
|
|
181
|
-
headers: Record<string, string> | object,
|
|
182
|
-
timeout: number
|
|
183
|
-
): Promise<ChatCompletionResponse & { _chatID?: string }> {
|
|
184
|
-
const controller = new AbortController()
|
|
185
|
-
const timer = setTimeout(() => controller.abort(), timeout)
|
|
186
|
-
|
|
187
|
-
let response: Response
|
|
188
|
-
try {
|
|
189
|
-
response = await fetch(`${endpoint}/chat/completions`, {
|
|
190
|
-
method: 'POST',
|
|
191
|
-
headers: {
|
|
192
|
-
'Content-Type': 'application/json',
|
|
193
|
-
...headers,
|
|
194
|
-
},
|
|
195
|
-
body: JSON.stringify({
|
|
196
|
-
model: providerModel,
|
|
197
|
-
messages: options.messages,
|
|
198
|
-
temperature: options.temperature,
|
|
199
|
-
max_tokens: options.maxTokens,
|
|
200
|
-
}),
|
|
201
|
-
signal: controller.signal,
|
|
202
|
-
})
|
|
203
|
-
} catch (err) {
|
|
204
|
-
if (err instanceof Error && err.name === 'AbortError') {
|
|
205
|
-
throw new TimeoutError(
|
|
206
|
-
`Inference request timed out after ${timeout / 1000}s.`
|
|
207
|
-
)
|
|
208
|
-
}
|
|
209
|
-
throw err
|
|
210
|
-
} finally {
|
|
211
|
-
clearTimeout(timer)
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
if (!response.ok) {
|
|
215
|
-
const body = await response.text().catch(() => '')
|
|
216
|
-
throw new InferenceError(
|
|
217
|
-
`Provider returned ${response.status}: ${body}`,
|
|
218
|
-
response.status === 429
|
|
219
|
-
? 'Provider is rate-limited. Wait a moment and retry, or try a different provider.'
|
|
220
|
-
: response.status >= 500
|
|
221
|
-
? 'The provider is experiencing issues. Try a different provider with the provider option.'
|
|
222
|
-
: 'Check the model name and request parameters.'
|
|
223
|
-
)
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const data = (await response.json()) as ChatCompletionResponse
|
|
227
|
-
const chatID = response.headers.get('ZG-Res-Key') ?? data.id ?? undefined
|
|
228
|
-
|
|
229
|
-
return { ...data, _chatID: chatID }
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
private async findProvider(model: string): Promise<string> {
|
|
233
|
-
const services = await this.listServices()
|
|
234
|
-
const match = services.find(
|
|
235
|
-
(s) => s.model.toLowerCase() === model.toLowerCase()
|
|
236
|
-
)
|
|
237
|
-
if (!match) {
|
|
238
|
-
const available = [...new Set(services.map((s) => s.model))].join(', ') || 'none'
|
|
239
|
-
throw new ProviderNotFoundError(
|
|
240
|
-
`No provider found for model "${model}". Available models: ${available}`
|
|
241
|
-
)
|
|
242
|
-
}
|
|
243
|
-
return match.provider
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
private async ensureLedgerFunded(
|
|
247
|
-
broker: ZGComputeNetworkBroker
|
|
248
|
-
): Promise<void> {
|
|
249
|
-
try {
|
|
250
|
-
await broker.ledger.getLedger()
|
|
251
|
-
} catch {
|
|
252
|
-
// Ledger doesn't exist yet — create it with initial deposit
|
|
253
|
-
await broker.ledger.addLedger(DEFAULT_LEDGER_DEPOSIT)
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
package/src/networks.test.ts
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest'
|
|
2
|
-
import { getNetwork, NETWORKS } from './networks.js'
|
|
3
|
-
|
|
4
|
-
describe('NETWORKS', () => {
|
|
5
|
-
it('testnet has correct chain ID', () => {
|
|
6
|
-
expect(NETWORKS.testnet.chainId).toBe(16602n)
|
|
7
|
-
})
|
|
8
|
-
|
|
9
|
-
it('mainnet has correct chain ID', () => {
|
|
10
|
-
expect(NETWORKS.mainnet.chainId).toBe(16661n)
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
it('testnet has multiple fallback RPC URLs', () => {
|
|
14
|
-
expect(NETWORKS.testnet.rpcUrls.length).toBeGreaterThanOrEqual(2)
|
|
15
|
-
expect(NETWORKS.testnet.rpcUrls[0]).toBe(NETWORKS.testnet.rpcUrl)
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
it('mainnet has multiple fallback RPC URLs', () => {
|
|
19
|
-
expect(NETWORKS.mainnet.rpcUrls.length).toBeGreaterThanOrEqual(2)
|
|
20
|
-
expect(NETWORKS.mainnet.rpcUrls[0]).toBe(NETWORKS.mainnet.rpcUrl)
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
it('all contract addresses are non-empty hex strings', () => {
|
|
24
|
-
for (const net of Object.values(NETWORKS)) {
|
|
25
|
-
expect(net.flowContractAddress).toMatch(/^0x[0-9a-fA-F]+$/)
|
|
26
|
-
expect(net.ledgerContractAddress).toMatch(/^0x[0-9a-fA-F]+$/)
|
|
27
|
-
expect(net.inferenceContractAddress).toMatch(/^0x[0-9a-fA-F]+$/)
|
|
28
|
-
expect(net.fineTuningContractAddress).toMatch(/^0x[0-9a-fA-F]+$/)
|
|
29
|
-
}
|
|
30
|
-
})
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
describe('getNetwork', () => {
|
|
34
|
-
it('resolves by name', () => {
|
|
35
|
-
const net = getNetwork('testnet')
|
|
36
|
-
expect(net.name).toBe('testnet')
|
|
37
|
-
expect(net.chainId).toBe(16602n)
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it('resolves by chain ID', () => {
|
|
41
|
-
const net = getNetwork(16661n)
|
|
42
|
-
expect(net.name).toBe('mainnet')
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it('throws on unknown name', () => {
|
|
46
|
-
expect(() => getNetwork('devnet' as any)).toThrow('Unknown network')
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it('throws on unknown chain ID', () => {
|
|
50
|
-
expect(() => getNetwork(99999n)).toThrow('Unknown chain ID')
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it('returns a copy — mutations do not affect originals', () => {
|
|
54
|
-
const net = getNetwork('testnet')
|
|
55
|
-
net.rpcUrl = 'https://mutated.example.com'
|
|
56
|
-
net.rpcUrls.push('https://extra.example.com')
|
|
57
|
-
|
|
58
|
-
const fresh = getNetwork('testnet')
|
|
59
|
-
expect(fresh.rpcUrl).not.toBe('https://mutated.example.com')
|
|
60
|
-
expect(fresh.rpcUrls).not.toContain('https://extra.example.com')
|
|
61
|
-
})
|
|
62
|
-
})
|