@things-factory/integration-mlflow 9.1.19
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/client/bootstrap.ts +41 -0
- package/client/index.ts +2 -0
- package/client/pages/mlflow-experiments-page.ts +45 -0
- package/client/pages/mlflow-models-page.ts +45 -0
- package/client/pages/mlflow-runs-page.ts +45 -0
- package/client/pages/mlflow-viewer-page.ts +74 -0
- package/client/route.ts +19 -0
- package/client/tsconfig.json +11 -0
- package/dist-client/bootstrap.d.ts +1 -0
- package/dist-client/bootstrap.js +40 -0
- package/dist-client/bootstrap.js.map +1 -0
- package/dist-client/index.d.ts +2 -0
- package/dist-client/index.js +3 -0
- package/dist-client/index.js.map +1 -0
- package/dist-client/pages/mlflow-experiments-page.d.ts +5 -0
- package/dist-client/pages/mlflow-experiments-page.js +48 -0
- package/dist-client/pages/mlflow-experiments-page.js.map +1 -0
- package/dist-client/pages/mlflow-models-page.d.ts +5 -0
- package/dist-client/pages/mlflow-models-page.js +48 -0
- package/dist-client/pages/mlflow-models-page.js.map +1 -0
- package/dist-client/pages/mlflow-runs-page.d.ts +5 -0
- package/dist-client/pages/mlflow-runs-page.js +48 -0
- package/dist-client/pages/mlflow-runs-page.js.map +1 -0
- package/dist-client/pages/mlflow-viewer-page.d.ts +12 -0
- package/dist-client/pages/mlflow-viewer-page.js +82 -0
- package/dist-client/pages/mlflow-viewer-page.js.map +1 -0
- package/dist-client/route.d.ts +1 -0
- package/dist-client/route.js +17 -0
- package/dist-client/route.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -0
- package/dist-server/index.d.ts +4 -0
- package/dist-server/index.js +8 -0
- package/dist-server/index.js.map +1 -0
- package/dist-server/route.d.ts +5 -0
- package/dist-server/route.js +12 -0
- package/dist-server/route.js.map +1 -0
- package/dist-server/service/experiment/experiment-service.d.ts +19 -0
- package/dist-server/service/experiment/experiment-service.js +135 -0
- package/dist-server/service/experiment/experiment-service.js.map +1 -0
- package/dist-server/service/index.d.ts +10 -0
- package/dist-server/service/index.js +19 -0
- package/dist-server/service/index.js.map +1 -0
- package/dist-server/service/model-registry/model-registry-service.d.ts +31 -0
- package/dist-server/service/model-registry/model-registry-service.js +255 -0
- package/dist-server/service/model-registry/model-registry-service.js.map +1 -0
- package/dist-server/service/run/run-service.d.ts +27 -0
- package/dist-server/service/run/run-service.js +214 -0
- package/dist-server/service/run/run-service.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -0
- package/dist-server/types/mlflow-types.d.ts +87 -0
- package/dist-server/types/mlflow-types.js +337 -0
- package/dist-server/types/mlflow-types.js.map +1 -0
- package/dist-server/utils/mlflow-api-client.d.ts +277 -0
- package/dist-server/utils/mlflow-api-client.js +201 -0
- package/dist-server/utils/mlflow-api-client.js.map +1 -0
- package/package.json +45 -0
- package/server/index.ts +5 -0
- package/server/route.ts +13 -0
- package/server/service/experiment/experiment-service.ts +117 -0
- package/server/service/index.ts +17 -0
- package/server/service/model-registry/model-registry-service.ts +232 -0
- package/server/service/run/run-service.ts +177 -0
- package/server/tsconfig.json +11 -0
- package/server/types/mlflow-types.ts +235 -0
- package/server/utils/mlflow-api-client.ts +375 -0
- package/things-factory.config.js +13 -0
- package/translations/en.json +28 -0
- package/translations/ko.json +28 -0
|
@@ -0,0 +1,375 @@
|
|
|
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 }
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import route from './dist-client/route'
|
|
2
|
+
import bootstrap from './dist-client/bootstrap'
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
route,
|
|
6
|
+
routes: [
|
|
7
|
+
{ tagname: 'mlflow-experiments-page', page: 'mlflow-experiments' },
|
|
8
|
+
{ tagname: 'mlflow-runs-page', page: 'mlflow-runs' },
|
|
9
|
+
{ tagname: 'mlflow-models-page', page: 'mlflow-models' },
|
|
10
|
+
{ tagname: 'mlflow-viewer-page', page: 'mlflow-viewer' }
|
|
11
|
+
],
|
|
12
|
+
bootstrap
|
|
13
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"menu": {
|
|
3
|
+
"mlflow": "MLflow",
|
|
4
|
+
"experiments": "Experiments",
|
|
5
|
+
"runs": "Runs",
|
|
6
|
+
"models": "Model Registry",
|
|
7
|
+
"mlflow-ui": "MLflow UI"
|
|
8
|
+
},
|
|
9
|
+
"label": {
|
|
10
|
+
"experiment-name": "Experiment Name",
|
|
11
|
+
"experiment-id": "Experiment ID",
|
|
12
|
+
"run-id": "Run ID",
|
|
13
|
+
"run-status": "Status",
|
|
14
|
+
"model-name": "Model Name",
|
|
15
|
+
"model-version": "Version",
|
|
16
|
+
"model-stage": "Stage",
|
|
17
|
+
"metrics": "Metrics",
|
|
18
|
+
"parameters": "Parameters",
|
|
19
|
+
"artifacts": "Artifacts"
|
|
20
|
+
},
|
|
21
|
+
"button": {
|
|
22
|
+
"create-experiment": "Create Experiment",
|
|
23
|
+
"create-run": "Create Run",
|
|
24
|
+
"register-model": "Register Model",
|
|
25
|
+
"transition-stage": "Transition Stage",
|
|
26
|
+
"view-mlflow": "Open MLflow UI"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"menu": {
|
|
3
|
+
"mlflow": "MLflow",
|
|
4
|
+
"experiments": "실험",
|
|
5
|
+
"runs": "실행",
|
|
6
|
+
"models": "모델 레지스트리",
|
|
7
|
+
"mlflow-ui": "MLflow UI"
|
|
8
|
+
},
|
|
9
|
+
"label": {
|
|
10
|
+
"experiment-name": "실험 이름",
|
|
11
|
+
"experiment-id": "실험 ID",
|
|
12
|
+
"run-id": "실행 ID",
|
|
13
|
+
"run-status": "상태",
|
|
14
|
+
"model-name": "모델 이름",
|
|
15
|
+
"model-version": "버전",
|
|
16
|
+
"model-stage": "단계",
|
|
17
|
+
"metrics": "메트릭",
|
|
18
|
+
"parameters": "파라미터",
|
|
19
|
+
"artifacts": "아티팩트"
|
|
20
|
+
},
|
|
21
|
+
"button": {
|
|
22
|
+
"create-experiment": "실험 생성",
|
|
23
|
+
"create-run": "실행 생성",
|
|
24
|
+
"register-model": "모델 등록",
|
|
25
|
+
"transition-stage": "단계 전환",
|
|
26
|
+
"view-mlflow": "MLflow UI 열기"
|
|
27
|
+
}
|
|
28
|
+
}
|