@things-factory/integration-mlflow 9.2.5 → 10.0.0-beta.10
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/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -8
- package/client/tsconfig.json +0 -11
- package/server/index.ts +0 -5
- package/server/route.ts +0 -13
- package/server/service/experiment/experiment-service.ts +0 -117
- package/server/service/index.ts +0 -17
- package/server/service/model-registry/model-registry-service.ts +0 -232
- package/server/service/run/run-service.ts +0 -177
- package/server/tsconfig.json +0 -11
- package/server/types/mlflow-types.ts +0 -235
- package/server/utils/mlflow-api-client.ts +0 -375
|
@@ -1,375 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MLflow REST API Client
|
|
3
|
-
*
|
|
4
|
-
* Thin wrapper around MLflow REST API for Things-Factory integration
|
|
5
|
-
* Reference: https://mlflow.org/docs/latest/rest-api.html
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { config } from '@things-factory/env'
|
|
9
|
-
|
|
10
|
-
const MLFLOW_TRACKING_URI = config.get('mlflowTrackingUri', process.env.MLFLOW_TRACKING_URI || 'http://localhost:5000')
|
|
11
|
-
|
|
12
|
-
interface MLflowResponse<T> {
|
|
13
|
-
data?: T
|
|
14
|
-
error?: string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
class MLflowApiClient {
|
|
18
|
-
private baseUrl: string
|
|
19
|
-
|
|
20
|
-
constructor(baseUrl: string = MLFLOW_TRACKING_URI) {
|
|
21
|
-
this.baseUrl = baseUrl.replace(/\/$/, '') // Remove trailing slash
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Generic HTTP request wrapper
|
|
26
|
-
*/
|
|
27
|
-
private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
|
|
28
|
-
const url = `${this.baseUrl}${endpoint}`
|
|
29
|
-
|
|
30
|
-
const defaultHeaders = {
|
|
31
|
-
'Content-Type': 'application/json'
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
const response = await fetch(url, {
|
|
36
|
-
...options,
|
|
37
|
-
headers: {
|
|
38
|
-
...defaultHeaders,
|
|
39
|
-
...options.headers
|
|
40
|
-
}
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
if (!response.ok) {
|
|
44
|
-
const errorText = await response.text()
|
|
45
|
-
throw new Error(`MLflow API error: ${response.status} - ${errorText}`)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return await response.json()
|
|
49
|
-
} catch (error) {
|
|
50
|
-
console.error(`MLflow API request failed: ${endpoint}`, error)
|
|
51
|
-
throw error
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// ========================================================================
|
|
56
|
-
// Experiments API
|
|
57
|
-
// ========================================================================
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Create a new experiment
|
|
61
|
-
*/
|
|
62
|
-
async createExperiment(params: {
|
|
63
|
-
name: string
|
|
64
|
-
artifact_location?: string
|
|
65
|
-
tags?: Array<{ key: string; value: string }>
|
|
66
|
-
}): Promise<{ experiment_id: string }> {
|
|
67
|
-
return this.request('/api/2.0/mlflow/experiments/create', {
|
|
68
|
-
method: 'POST',
|
|
69
|
-
body: JSON.stringify(params)
|
|
70
|
-
})
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Get experiment by ID
|
|
75
|
-
*/
|
|
76
|
-
async getExperiment(experimentId: string): Promise<{
|
|
77
|
-
experiment: {
|
|
78
|
-
experiment_id: string
|
|
79
|
-
name: string
|
|
80
|
-
artifact_location: string
|
|
81
|
-
lifecycle_stage: string
|
|
82
|
-
tags?: Array<{ key: string; value: string }>
|
|
83
|
-
}
|
|
84
|
-
}> {
|
|
85
|
-
return this.request(`/api/2.0/mlflow/experiments/get?experiment_id=${experimentId}`)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Search experiments
|
|
90
|
-
*/
|
|
91
|
-
async searchExperiments(params?: { max_results?: number; filter?: string; order_by?: string[] }): Promise<{
|
|
92
|
-
experiments: Array<{
|
|
93
|
-
experiment_id: string
|
|
94
|
-
name: string
|
|
95
|
-
artifact_location: string
|
|
96
|
-
lifecycle_stage: string
|
|
97
|
-
tags?: Array<{ key: string; value: string }>
|
|
98
|
-
}>
|
|
99
|
-
}> {
|
|
100
|
-
const queryParams = new URLSearchParams()
|
|
101
|
-
if (params?.max_results) queryParams.append('max_results', String(params.max_results))
|
|
102
|
-
if (params?.filter) queryParams.append('filter', params.filter)
|
|
103
|
-
|
|
104
|
-
return this.request(`/api/2.0/mlflow/experiments/search?${queryParams}`)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Delete experiment
|
|
109
|
-
*/
|
|
110
|
-
async deleteExperiment(experimentId: string): Promise<void> {
|
|
111
|
-
return this.request('/api/2.0/mlflow/experiments/delete', {
|
|
112
|
-
method: 'POST',
|
|
113
|
-
body: JSON.stringify({ experiment_id: experimentId })
|
|
114
|
-
})
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// ========================================================================
|
|
118
|
-
// Runs API
|
|
119
|
-
// ========================================================================
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Create a new run
|
|
123
|
-
*/
|
|
124
|
-
async createRun(params: {
|
|
125
|
-
experiment_id: string
|
|
126
|
-
start_time?: number
|
|
127
|
-
tags?: Array<{ key: string; value: string }>
|
|
128
|
-
}): Promise<{
|
|
129
|
-
run: {
|
|
130
|
-
info: {
|
|
131
|
-
run_id: string
|
|
132
|
-
experiment_id: string
|
|
133
|
-
status: string
|
|
134
|
-
start_time: number
|
|
135
|
-
artifact_uri: string
|
|
136
|
-
}
|
|
137
|
-
data: any
|
|
138
|
-
}
|
|
139
|
-
}> {
|
|
140
|
-
return this.request('/api/2.0/mlflow/runs/create', {
|
|
141
|
-
method: 'POST',
|
|
142
|
-
body: JSON.stringify(params)
|
|
143
|
-
})
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Get run by ID
|
|
148
|
-
*/
|
|
149
|
-
async getRun(runId: string): Promise<{
|
|
150
|
-
run: {
|
|
151
|
-
info: {
|
|
152
|
-
run_id: string
|
|
153
|
-
experiment_id: string
|
|
154
|
-
status: string
|
|
155
|
-
start_time: number
|
|
156
|
-
end_time?: number
|
|
157
|
-
artifact_uri: string
|
|
158
|
-
}
|
|
159
|
-
data: {
|
|
160
|
-
metrics: Array<{ key: string; value: number; timestamp: number; step: number }>
|
|
161
|
-
params: Array<{ key: string; value: string }>
|
|
162
|
-
tags: Array<{ key: string; value: string }>
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}> {
|
|
166
|
-
return this.request(`/api/2.0/mlflow/runs/get?run_id=${runId}`)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Search runs
|
|
171
|
-
*/
|
|
172
|
-
async searchRuns(params: {
|
|
173
|
-
experiment_ids: string[]
|
|
174
|
-
filter?: string
|
|
175
|
-
max_results?: number
|
|
176
|
-
order_by?: string[]
|
|
177
|
-
}): Promise<{
|
|
178
|
-
runs: Array<{
|
|
179
|
-
info: {
|
|
180
|
-
run_id: string
|
|
181
|
-
experiment_id: string
|
|
182
|
-
status: string
|
|
183
|
-
start_time: number
|
|
184
|
-
end_time?: number
|
|
185
|
-
artifact_uri: string
|
|
186
|
-
}
|
|
187
|
-
data: any
|
|
188
|
-
}>
|
|
189
|
-
}> {
|
|
190
|
-
return this.request('/api/2.0/mlflow/runs/search', {
|
|
191
|
-
method: 'POST',
|
|
192
|
-
body: JSON.stringify(params)
|
|
193
|
-
})
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Log metric for a run
|
|
198
|
-
*/
|
|
199
|
-
async logMetric(params: {
|
|
200
|
-
run_id: string
|
|
201
|
-
key: string
|
|
202
|
-
value: number
|
|
203
|
-
timestamp?: number
|
|
204
|
-
step?: number
|
|
205
|
-
}): Promise<void> {
|
|
206
|
-
return this.request('/api/2.0/mlflow/runs/log-metric', {
|
|
207
|
-
method: 'POST',
|
|
208
|
-
body: JSON.stringify(params)
|
|
209
|
-
})
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Log parameter for a run
|
|
214
|
-
*/
|
|
215
|
-
async logParam(params: { run_id: string; key: string; value: string }): Promise<void> {
|
|
216
|
-
return this.request('/api/2.0/mlflow/runs/log-parameter', {
|
|
217
|
-
method: 'POST',
|
|
218
|
-
body: JSON.stringify(params)
|
|
219
|
-
})
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Update run status
|
|
224
|
-
*/
|
|
225
|
-
async updateRun(params: {
|
|
226
|
-
run_id: string
|
|
227
|
-
status?: 'RUNNING' | 'SCHEDULED' | 'FINISHED' | 'FAILED' | 'KILLED'
|
|
228
|
-
end_time?: number
|
|
229
|
-
}): Promise<void> {
|
|
230
|
-
return this.request('/api/2.0/mlflow/runs/update', {
|
|
231
|
-
method: 'POST',
|
|
232
|
-
body: JSON.stringify(params)
|
|
233
|
-
})
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// ========================================================================
|
|
237
|
-
// Model Registry API
|
|
238
|
-
// ========================================================================
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Create registered model
|
|
242
|
-
*/
|
|
243
|
-
async createRegisteredModel(params: {
|
|
244
|
-
name: string
|
|
245
|
-
tags?: Array<{ key: string; value: string }>
|
|
246
|
-
description?: string
|
|
247
|
-
}): Promise<{
|
|
248
|
-
registered_model: {
|
|
249
|
-
name: string
|
|
250
|
-
creation_timestamp: number
|
|
251
|
-
last_updated_timestamp: number
|
|
252
|
-
}
|
|
253
|
-
}> {
|
|
254
|
-
return this.request('/api/2.0/mlflow/registered-models/create', {
|
|
255
|
-
method: 'POST',
|
|
256
|
-
body: JSON.stringify(params)
|
|
257
|
-
})
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Get registered model
|
|
262
|
-
*/
|
|
263
|
-
async getRegisteredModel(name: string): Promise<{
|
|
264
|
-
registered_model: {
|
|
265
|
-
name: string
|
|
266
|
-
creation_timestamp: number
|
|
267
|
-
last_updated_timestamp: number
|
|
268
|
-
description?: string
|
|
269
|
-
tags?: Array<{ key: string; value: string }>
|
|
270
|
-
latest_versions?: any[]
|
|
271
|
-
}
|
|
272
|
-
}> {
|
|
273
|
-
return this.request(`/api/2.0/mlflow/registered-models/get?name=${encodeURIComponent(name)}`)
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Search registered models
|
|
278
|
-
*/
|
|
279
|
-
async searchRegisteredModels(params?: { filter?: string; max_results?: number; order_by?: string[] }): Promise<{
|
|
280
|
-
registered_models: Array<{
|
|
281
|
-
name: string
|
|
282
|
-
creation_timestamp: number
|
|
283
|
-
last_updated_timestamp: number
|
|
284
|
-
description?: string
|
|
285
|
-
latest_versions?: any[]
|
|
286
|
-
}>
|
|
287
|
-
}> {
|
|
288
|
-
const queryParams = new URLSearchParams()
|
|
289
|
-
if (params?.filter) queryParams.append('filter', params.filter)
|
|
290
|
-
if (params?.max_results) queryParams.append('max_results', String(params.max_results))
|
|
291
|
-
|
|
292
|
-
return this.request(`/api/2.0/mlflow/registered-models/search?${queryParams}`)
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Create model version
|
|
297
|
-
*/
|
|
298
|
-
async createModelVersion(params: {
|
|
299
|
-
name: string
|
|
300
|
-
source: string
|
|
301
|
-
run_id?: string
|
|
302
|
-
tags?: Array<{ key: string; value: string }>
|
|
303
|
-
description?: string
|
|
304
|
-
}): Promise<{
|
|
305
|
-
model_version: {
|
|
306
|
-
name: string
|
|
307
|
-
version: string
|
|
308
|
-
creation_timestamp: number
|
|
309
|
-
last_updated_timestamp: number
|
|
310
|
-
source: string
|
|
311
|
-
status: string
|
|
312
|
-
}
|
|
313
|
-
}> {
|
|
314
|
-
return this.request('/api/2.0/mlflow/model-versions/create', {
|
|
315
|
-
method: 'POST',
|
|
316
|
-
body: JSON.stringify(params)
|
|
317
|
-
})
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Get model version
|
|
322
|
-
*/
|
|
323
|
-
async getModelVersion(params: { name: string; version: string }): Promise<{
|
|
324
|
-
model_version: {
|
|
325
|
-
name: string
|
|
326
|
-
version: string
|
|
327
|
-
creation_timestamp: number
|
|
328
|
-
current_stage: string
|
|
329
|
-
source: string
|
|
330
|
-
status: string
|
|
331
|
-
run_id?: string
|
|
332
|
-
}
|
|
333
|
-
}> {
|
|
334
|
-
return this.request(
|
|
335
|
-
`/api/2.0/mlflow/model-versions/get?name=${encodeURIComponent(params.name)}&version=${params.version}`
|
|
336
|
-
)
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* Transition model version stage
|
|
341
|
-
*/
|
|
342
|
-
async transitionModelVersionStage(params: {
|
|
343
|
-
name: string
|
|
344
|
-
version: string
|
|
345
|
-
stage: 'None' | 'Staging' | 'Production' | 'Archived'
|
|
346
|
-
archive_existing_versions?: boolean
|
|
347
|
-
}): Promise<{
|
|
348
|
-
model_version: {
|
|
349
|
-
name: string
|
|
350
|
-
version: string
|
|
351
|
-
current_stage: string
|
|
352
|
-
}
|
|
353
|
-
}> {
|
|
354
|
-
return this.request('/api/2.0/mlflow/model-versions/transition-stage', {
|
|
355
|
-
method: 'POST',
|
|
356
|
-
body: JSON.stringify(params)
|
|
357
|
-
})
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* Delete model version
|
|
362
|
-
*/
|
|
363
|
-
async deleteModelVersion(params: { name: string; version: string }): Promise<void> {
|
|
364
|
-
return this.request('/api/2.0/mlflow/model-versions/delete', {
|
|
365
|
-
method: 'DELETE',
|
|
366
|
-
body: JSON.stringify(params)
|
|
367
|
-
})
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// Export singleton instance
|
|
372
|
-
export const mlflowApi = new MLflowApiClient()
|
|
373
|
-
|
|
374
|
-
// Export class for testing or custom instances
|
|
375
|
-
export { MLflowApiClient }
|