@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,277 @@
|
|
|
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
|
+
declare class MLflowApiClient {
|
|
8
|
+
private baseUrl;
|
|
9
|
+
constructor(baseUrl?: string);
|
|
10
|
+
/**
|
|
11
|
+
* Generic HTTP request wrapper
|
|
12
|
+
*/
|
|
13
|
+
private request;
|
|
14
|
+
/**
|
|
15
|
+
* Create a new experiment
|
|
16
|
+
*/
|
|
17
|
+
createExperiment(params: {
|
|
18
|
+
name: string;
|
|
19
|
+
artifact_location?: string;
|
|
20
|
+
tags?: Array<{
|
|
21
|
+
key: string;
|
|
22
|
+
value: string;
|
|
23
|
+
}>;
|
|
24
|
+
}): Promise<{
|
|
25
|
+
experiment_id: string;
|
|
26
|
+
}>;
|
|
27
|
+
/**
|
|
28
|
+
* Get experiment by ID
|
|
29
|
+
*/
|
|
30
|
+
getExperiment(experimentId: string): Promise<{
|
|
31
|
+
experiment: {
|
|
32
|
+
experiment_id: string;
|
|
33
|
+
name: string;
|
|
34
|
+
artifact_location: string;
|
|
35
|
+
lifecycle_stage: string;
|
|
36
|
+
tags?: Array<{
|
|
37
|
+
key: string;
|
|
38
|
+
value: string;
|
|
39
|
+
}>;
|
|
40
|
+
};
|
|
41
|
+
}>;
|
|
42
|
+
/**
|
|
43
|
+
* Search experiments
|
|
44
|
+
*/
|
|
45
|
+
searchExperiments(params?: {
|
|
46
|
+
max_results?: number;
|
|
47
|
+
filter?: string;
|
|
48
|
+
order_by?: string[];
|
|
49
|
+
}): Promise<{
|
|
50
|
+
experiments: Array<{
|
|
51
|
+
experiment_id: string;
|
|
52
|
+
name: string;
|
|
53
|
+
artifact_location: string;
|
|
54
|
+
lifecycle_stage: string;
|
|
55
|
+
tags?: Array<{
|
|
56
|
+
key: string;
|
|
57
|
+
value: string;
|
|
58
|
+
}>;
|
|
59
|
+
}>;
|
|
60
|
+
}>;
|
|
61
|
+
/**
|
|
62
|
+
* Delete experiment
|
|
63
|
+
*/
|
|
64
|
+
deleteExperiment(experimentId: string): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Create a new run
|
|
67
|
+
*/
|
|
68
|
+
createRun(params: {
|
|
69
|
+
experiment_id: string;
|
|
70
|
+
start_time?: number;
|
|
71
|
+
tags?: Array<{
|
|
72
|
+
key: string;
|
|
73
|
+
value: string;
|
|
74
|
+
}>;
|
|
75
|
+
}): Promise<{
|
|
76
|
+
run: {
|
|
77
|
+
info: {
|
|
78
|
+
run_id: string;
|
|
79
|
+
experiment_id: string;
|
|
80
|
+
status: string;
|
|
81
|
+
start_time: number;
|
|
82
|
+
artifact_uri: string;
|
|
83
|
+
};
|
|
84
|
+
data: any;
|
|
85
|
+
};
|
|
86
|
+
}>;
|
|
87
|
+
/**
|
|
88
|
+
* Get run by ID
|
|
89
|
+
*/
|
|
90
|
+
getRun(runId: string): Promise<{
|
|
91
|
+
run: {
|
|
92
|
+
info: {
|
|
93
|
+
run_id: string;
|
|
94
|
+
experiment_id: string;
|
|
95
|
+
status: string;
|
|
96
|
+
start_time: number;
|
|
97
|
+
end_time?: number;
|
|
98
|
+
artifact_uri: string;
|
|
99
|
+
};
|
|
100
|
+
data: {
|
|
101
|
+
metrics: Array<{
|
|
102
|
+
key: string;
|
|
103
|
+
value: number;
|
|
104
|
+
timestamp: number;
|
|
105
|
+
step: number;
|
|
106
|
+
}>;
|
|
107
|
+
params: Array<{
|
|
108
|
+
key: string;
|
|
109
|
+
value: string;
|
|
110
|
+
}>;
|
|
111
|
+
tags: Array<{
|
|
112
|
+
key: string;
|
|
113
|
+
value: string;
|
|
114
|
+
}>;
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
}>;
|
|
118
|
+
/**
|
|
119
|
+
* Search runs
|
|
120
|
+
*/
|
|
121
|
+
searchRuns(params: {
|
|
122
|
+
experiment_ids: string[];
|
|
123
|
+
filter?: string;
|
|
124
|
+
max_results?: number;
|
|
125
|
+
order_by?: string[];
|
|
126
|
+
}): Promise<{
|
|
127
|
+
runs: Array<{
|
|
128
|
+
info: {
|
|
129
|
+
run_id: string;
|
|
130
|
+
experiment_id: string;
|
|
131
|
+
status: string;
|
|
132
|
+
start_time: number;
|
|
133
|
+
end_time?: number;
|
|
134
|
+
artifact_uri: string;
|
|
135
|
+
};
|
|
136
|
+
data: any;
|
|
137
|
+
}>;
|
|
138
|
+
}>;
|
|
139
|
+
/**
|
|
140
|
+
* Log metric for a run
|
|
141
|
+
*/
|
|
142
|
+
logMetric(params: {
|
|
143
|
+
run_id: string;
|
|
144
|
+
key: string;
|
|
145
|
+
value: number;
|
|
146
|
+
timestamp?: number;
|
|
147
|
+
step?: number;
|
|
148
|
+
}): Promise<void>;
|
|
149
|
+
/**
|
|
150
|
+
* Log parameter for a run
|
|
151
|
+
*/
|
|
152
|
+
logParam(params: {
|
|
153
|
+
run_id: string;
|
|
154
|
+
key: string;
|
|
155
|
+
value: string;
|
|
156
|
+
}): Promise<void>;
|
|
157
|
+
/**
|
|
158
|
+
* Update run status
|
|
159
|
+
*/
|
|
160
|
+
updateRun(params: {
|
|
161
|
+
run_id: string;
|
|
162
|
+
status?: 'RUNNING' | 'SCHEDULED' | 'FINISHED' | 'FAILED' | 'KILLED';
|
|
163
|
+
end_time?: number;
|
|
164
|
+
}): Promise<void>;
|
|
165
|
+
/**
|
|
166
|
+
* Create registered model
|
|
167
|
+
*/
|
|
168
|
+
createRegisteredModel(params: {
|
|
169
|
+
name: string;
|
|
170
|
+
tags?: Array<{
|
|
171
|
+
key: string;
|
|
172
|
+
value: string;
|
|
173
|
+
}>;
|
|
174
|
+
description?: string;
|
|
175
|
+
}): Promise<{
|
|
176
|
+
registered_model: {
|
|
177
|
+
name: string;
|
|
178
|
+
creation_timestamp: number;
|
|
179
|
+
last_updated_timestamp: number;
|
|
180
|
+
};
|
|
181
|
+
}>;
|
|
182
|
+
/**
|
|
183
|
+
* Get registered model
|
|
184
|
+
*/
|
|
185
|
+
getRegisteredModel(name: string): Promise<{
|
|
186
|
+
registered_model: {
|
|
187
|
+
name: string;
|
|
188
|
+
creation_timestamp: number;
|
|
189
|
+
last_updated_timestamp: number;
|
|
190
|
+
description?: string;
|
|
191
|
+
tags?: Array<{
|
|
192
|
+
key: string;
|
|
193
|
+
value: string;
|
|
194
|
+
}>;
|
|
195
|
+
latest_versions?: any[];
|
|
196
|
+
};
|
|
197
|
+
}>;
|
|
198
|
+
/**
|
|
199
|
+
* Search registered models
|
|
200
|
+
*/
|
|
201
|
+
searchRegisteredModels(params?: {
|
|
202
|
+
filter?: string;
|
|
203
|
+
max_results?: number;
|
|
204
|
+
order_by?: string[];
|
|
205
|
+
}): Promise<{
|
|
206
|
+
registered_models: Array<{
|
|
207
|
+
name: string;
|
|
208
|
+
creation_timestamp: number;
|
|
209
|
+
last_updated_timestamp: number;
|
|
210
|
+
description?: string;
|
|
211
|
+
latest_versions?: any[];
|
|
212
|
+
}>;
|
|
213
|
+
}>;
|
|
214
|
+
/**
|
|
215
|
+
* Create model version
|
|
216
|
+
*/
|
|
217
|
+
createModelVersion(params: {
|
|
218
|
+
name: string;
|
|
219
|
+
source: string;
|
|
220
|
+
run_id?: string;
|
|
221
|
+
tags?: Array<{
|
|
222
|
+
key: string;
|
|
223
|
+
value: string;
|
|
224
|
+
}>;
|
|
225
|
+
description?: string;
|
|
226
|
+
}): Promise<{
|
|
227
|
+
model_version: {
|
|
228
|
+
name: string;
|
|
229
|
+
version: string;
|
|
230
|
+
creation_timestamp: number;
|
|
231
|
+
last_updated_timestamp: number;
|
|
232
|
+
source: string;
|
|
233
|
+
status: string;
|
|
234
|
+
};
|
|
235
|
+
}>;
|
|
236
|
+
/**
|
|
237
|
+
* Get model version
|
|
238
|
+
*/
|
|
239
|
+
getModelVersion(params: {
|
|
240
|
+
name: string;
|
|
241
|
+
version: string;
|
|
242
|
+
}): Promise<{
|
|
243
|
+
model_version: {
|
|
244
|
+
name: string;
|
|
245
|
+
version: string;
|
|
246
|
+
creation_timestamp: number;
|
|
247
|
+
current_stage: string;
|
|
248
|
+
source: string;
|
|
249
|
+
status: string;
|
|
250
|
+
run_id?: string;
|
|
251
|
+
};
|
|
252
|
+
}>;
|
|
253
|
+
/**
|
|
254
|
+
* Transition model version stage
|
|
255
|
+
*/
|
|
256
|
+
transitionModelVersionStage(params: {
|
|
257
|
+
name: string;
|
|
258
|
+
version: string;
|
|
259
|
+
stage: 'None' | 'Staging' | 'Production' | 'Archived';
|
|
260
|
+
archive_existing_versions?: boolean;
|
|
261
|
+
}): Promise<{
|
|
262
|
+
model_version: {
|
|
263
|
+
name: string;
|
|
264
|
+
version: string;
|
|
265
|
+
current_stage: string;
|
|
266
|
+
};
|
|
267
|
+
}>;
|
|
268
|
+
/**
|
|
269
|
+
* Delete model version
|
|
270
|
+
*/
|
|
271
|
+
deleteModelVersion(params: {
|
|
272
|
+
name: string;
|
|
273
|
+
version: string;
|
|
274
|
+
}): Promise<void>;
|
|
275
|
+
}
|
|
276
|
+
export declare const mlflowApi: MLflowApiClient;
|
|
277
|
+
export { MLflowApiClient };
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* MLflow REST API Client
|
|
4
|
+
*
|
|
5
|
+
* Thin wrapper around MLflow REST API for Things-Factory integration
|
|
6
|
+
* Reference: https://mlflow.org/docs/latest/rest-api.html
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.MLflowApiClient = exports.mlflowApi = void 0;
|
|
10
|
+
const env_1 = require("@things-factory/env");
|
|
11
|
+
const MLFLOW_TRACKING_URI = env_1.config.get('mlflowTrackingUri', process.env.MLFLOW_TRACKING_URI || 'http://localhost:5000');
|
|
12
|
+
class MLflowApiClient {
|
|
13
|
+
constructor(baseUrl = MLFLOW_TRACKING_URI) {
|
|
14
|
+
this.baseUrl = baseUrl.replace(/\/$/, ''); // Remove trailing slash
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Generic HTTP request wrapper
|
|
18
|
+
*/
|
|
19
|
+
async request(endpoint, options = {}) {
|
|
20
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
21
|
+
const defaultHeaders = {
|
|
22
|
+
'Content-Type': 'application/json'
|
|
23
|
+
};
|
|
24
|
+
try {
|
|
25
|
+
const response = await fetch(url, {
|
|
26
|
+
...options,
|
|
27
|
+
headers: {
|
|
28
|
+
...defaultHeaders,
|
|
29
|
+
...options.headers
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
const errorText = await response.text();
|
|
34
|
+
throw new Error(`MLflow API error: ${response.status} - ${errorText}`);
|
|
35
|
+
}
|
|
36
|
+
return await response.json();
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
console.error(`MLflow API request failed: ${endpoint}`, error);
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// ========================================================================
|
|
44
|
+
// Experiments API
|
|
45
|
+
// ========================================================================
|
|
46
|
+
/**
|
|
47
|
+
* Create a new experiment
|
|
48
|
+
*/
|
|
49
|
+
async createExperiment(params) {
|
|
50
|
+
return this.request('/api/2.0/mlflow/experiments/create', {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
body: JSON.stringify(params)
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get experiment by ID
|
|
57
|
+
*/
|
|
58
|
+
async getExperiment(experimentId) {
|
|
59
|
+
return this.request(`/api/2.0/mlflow/experiments/get?experiment_id=${experimentId}`);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Search experiments
|
|
63
|
+
*/
|
|
64
|
+
async searchExperiments(params) {
|
|
65
|
+
const queryParams = new URLSearchParams();
|
|
66
|
+
if (params?.max_results)
|
|
67
|
+
queryParams.append('max_results', String(params.max_results));
|
|
68
|
+
if (params?.filter)
|
|
69
|
+
queryParams.append('filter', params.filter);
|
|
70
|
+
return this.request(`/api/2.0/mlflow/experiments/search?${queryParams}`);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Delete experiment
|
|
74
|
+
*/
|
|
75
|
+
async deleteExperiment(experimentId) {
|
|
76
|
+
return this.request('/api/2.0/mlflow/experiments/delete', {
|
|
77
|
+
method: 'POST',
|
|
78
|
+
body: JSON.stringify({ experiment_id: experimentId })
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
// ========================================================================
|
|
82
|
+
// Runs API
|
|
83
|
+
// ========================================================================
|
|
84
|
+
/**
|
|
85
|
+
* Create a new run
|
|
86
|
+
*/
|
|
87
|
+
async createRun(params) {
|
|
88
|
+
return this.request('/api/2.0/mlflow/runs/create', {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
body: JSON.stringify(params)
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get run by ID
|
|
95
|
+
*/
|
|
96
|
+
async getRun(runId) {
|
|
97
|
+
return this.request(`/api/2.0/mlflow/runs/get?run_id=${runId}`);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Search runs
|
|
101
|
+
*/
|
|
102
|
+
async searchRuns(params) {
|
|
103
|
+
return this.request('/api/2.0/mlflow/runs/search', {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
body: JSON.stringify(params)
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Log metric for a run
|
|
110
|
+
*/
|
|
111
|
+
async logMetric(params) {
|
|
112
|
+
return this.request('/api/2.0/mlflow/runs/log-metric', {
|
|
113
|
+
method: 'POST',
|
|
114
|
+
body: JSON.stringify(params)
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Log parameter for a run
|
|
119
|
+
*/
|
|
120
|
+
async logParam(params) {
|
|
121
|
+
return this.request('/api/2.0/mlflow/runs/log-parameter', {
|
|
122
|
+
method: 'POST',
|
|
123
|
+
body: JSON.stringify(params)
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Update run status
|
|
128
|
+
*/
|
|
129
|
+
async updateRun(params) {
|
|
130
|
+
return this.request('/api/2.0/mlflow/runs/update', {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
body: JSON.stringify(params)
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
// ========================================================================
|
|
136
|
+
// Model Registry API
|
|
137
|
+
// ========================================================================
|
|
138
|
+
/**
|
|
139
|
+
* Create registered model
|
|
140
|
+
*/
|
|
141
|
+
async createRegisteredModel(params) {
|
|
142
|
+
return this.request('/api/2.0/mlflow/registered-models/create', {
|
|
143
|
+
method: 'POST',
|
|
144
|
+
body: JSON.stringify(params)
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Get registered model
|
|
149
|
+
*/
|
|
150
|
+
async getRegisteredModel(name) {
|
|
151
|
+
return this.request(`/api/2.0/mlflow/registered-models/get?name=${encodeURIComponent(name)}`);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Search registered models
|
|
155
|
+
*/
|
|
156
|
+
async searchRegisteredModels(params) {
|
|
157
|
+
const queryParams = new URLSearchParams();
|
|
158
|
+
if (params?.filter)
|
|
159
|
+
queryParams.append('filter', params.filter);
|
|
160
|
+
if (params?.max_results)
|
|
161
|
+
queryParams.append('max_results', String(params.max_results));
|
|
162
|
+
return this.request(`/api/2.0/mlflow/registered-models/search?${queryParams}`);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Create model version
|
|
166
|
+
*/
|
|
167
|
+
async createModelVersion(params) {
|
|
168
|
+
return this.request('/api/2.0/mlflow/model-versions/create', {
|
|
169
|
+
method: 'POST',
|
|
170
|
+
body: JSON.stringify(params)
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Get model version
|
|
175
|
+
*/
|
|
176
|
+
async getModelVersion(params) {
|
|
177
|
+
return this.request(`/api/2.0/mlflow/model-versions/get?name=${encodeURIComponent(params.name)}&version=${params.version}`);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Transition model version stage
|
|
181
|
+
*/
|
|
182
|
+
async transitionModelVersionStage(params) {
|
|
183
|
+
return this.request('/api/2.0/mlflow/model-versions/transition-stage', {
|
|
184
|
+
method: 'POST',
|
|
185
|
+
body: JSON.stringify(params)
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Delete model version
|
|
190
|
+
*/
|
|
191
|
+
async deleteModelVersion(params) {
|
|
192
|
+
return this.request('/api/2.0/mlflow/model-versions/delete', {
|
|
193
|
+
method: 'DELETE',
|
|
194
|
+
body: JSON.stringify(params)
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
exports.MLflowApiClient = MLflowApiClient;
|
|
199
|
+
// Export singleton instance
|
|
200
|
+
exports.mlflowApi = new MLflowApiClient();
|
|
201
|
+
//# sourceMappingURL=mlflow-api-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mlflow-api-client.js","sourceRoot":"","sources":["../../server/utils/mlflow-api-client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * MLflow REST API Client\n *\n * Thin wrapper around MLflow REST API for Things-Factory integration\n * Reference: https://mlflow.org/docs/latest/rest-api.html\n */\n\nimport { config } from '@things-factory/env'\n\nconst MLFLOW_TRACKING_URI = config.get('mlflowTrackingUri', process.env.MLFLOW_TRACKING_URI || 'http://localhost:5000')\n\ninterface MLflowResponse<T> {\n data?: T\n error?: string\n}\n\nclass MLflowApiClient {\n private baseUrl: string\n\n constructor(baseUrl: string = MLFLOW_TRACKING_URI) {\n this.baseUrl = baseUrl.replace(/\\/$/, '') // Remove trailing slash\n }\n\n /**\n * Generic HTTP request wrapper\n */\n private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\n const url = `${this.baseUrl}${endpoint}`\n\n const defaultHeaders = {\n 'Content-Type': 'application/json'\n }\n\n try {\n const response = await fetch(url, {\n ...options,\n headers: {\n ...defaultHeaders,\n ...options.headers\n }\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n throw new Error(`MLflow API error: ${response.status} - ${errorText}`)\n }\n\n return await response.json()\n } catch (error) {\n console.error(`MLflow API request failed: ${endpoint}`, error)\n throw error\n }\n }\n\n // ========================================================================\n // Experiments API\n // ========================================================================\n\n /**\n * Create a new experiment\n */\n async createExperiment(params: {\n name: string\n artifact_location?: string\n tags?: Array<{ key: string; value: string }>\n }): Promise<{ experiment_id: string }> {\n return this.request('/api/2.0/mlflow/experiments/create', {\n method: 'POST',\n body: JSON.stringify(params)\n })\n }\n\n /**\n * Get experiment by ID\n */\n async getExperiment(experimentId: string): Promise<{\n experiment: {\n experiment_id: string\n name: string\n artifact_location: string\n lifecycle_stage: string\n tags?: Array<{ key: string; value: string }>\n }\n }> {\n return this.request(`/api/2.0/mlflow/experiments/get?experiment_id=${experimentId}`)\n }\n\n /**\n * Search experiments\n */\n async searchExperiments(params?: { max_results?: number; filter?: string; order_by?: string[] }): Promise<{\n experiments: Array<{\n experiment_id: string\n name: string\n artifact_location: string\n lifecycle_stage: string\n tags?: Array<{ key: string; value: string }>\n }>\n }> {\n const queryParams = new URLSearchParams()\n if (params?.max_results) queryParams.append('max_results', String(params.max_results))\n if (params?.filter) queryParams.append('filter', params.filter)\n\n return this.request(`/api/2.0/mlflow/experiments/search?${queryParams}`)\n }\n\n /**\n * Delete experiment\n */\n async deleteExperiment(experimentId: string): Promise<void> {\n return this.request('/api/2.0/mlflow/experiments/delete', {\n method: 'POST',\n body: JSON.stringify({ experiment_id: experimentId })\n })\n }\n\n // ========================================================================\n // Runs API\n // ========================================================================\n\n /**\n * Create a new run\n */\n async createRun(params: {\n experiment_id: string\n start_time?: number\n tags?: Array<{ key: string; value: string }>\n }): Promise<{\n run: {\n info: {\n run_id: string\n experiment_id: string\n status: string\n start_time: number\n artifact_uri: string\n }\n data: any\n }\n }> {\n return this.request('/api/2.0/mlflow/runs/create', {\n method: 'POST',\n body: JSON.stringify(params)\n })\n }\n\n /**\n * Get run by ID\n */\n async getRun(runId: string): Promise<{\n run: {\n info: {\n run_id: string\n experiment_id: string\n status: string\n start_time: number\n end_time?: number\n artifact_uri: string\n }\n data: {\n metrics: Array<{ key: string; value: number; timestamp: number; step: number }>\n params: Array<{ key: string; value: string }>\n tags: Array<{ key: string; value: string }>\n }\n }\n }> {\n return this.request(`/api/2.0/mlflow/runs/get?run_id=${runId}`)\n }\n\n /**\n * Search runs\n */\n async searchRuns(params: {\n experiment_ids: string[]\n filter?: string\n max_results?: number\n order_by?: string[]\n }): Promise<{\n runs: Array<{\n info: {\n run_id: string\n experiment_id: string\n status: string\n start_time: number\n end_time?: number\n artifact_uri: string\n }\n data: any\n }>\n }> {\n return this.request('/api/2.0/mlflow/runs/search', {\n method: 'POST',\n body: JSON.stringify(params)\n })\n }\n\n /**\n * Log metric for a run\n */\n async logMetric(params: {\n run_id: string\n key: string\n value: number\n timestamp?: number\n step?: number\n }): Promise<void> {\n return this.request('/api/2.0/mlflow/runs/log-metric', {\n method: 'POST',\n body: JSON.stringify(params)\n })\n }\n\n /**\n * Log parameter for a run\n */\n async logParam(params: { run_id: string; key: string; value: string }): Promise<void> {\n return this.request('/api/2.0/mlflow/runs/log-parameter', {\n method: 'POST',\n body: JSON.stringify(params)\n })\n }\n\n /**\n * Update run status\n */\n async updateRun(params: {\n run_id: string\n status?: 'RUNNING' | 'SCHEDULED' | 'FINISHED' | 'FAILED' | 'KILLED'\n end_time?: number\n }): Promise<void> {\n return this.request('/api/2.0/mlflow/runs/update', {\n method: 'POST',\n body: JSON.stringify(params)\n })\n }\n\n // ========================================================================\n // Model Registry API\n // ========================================================================\n\n /**\n * Create registered model\n */\n async createRegisteredModel(params: {\n name: string\n tags?: Array<{ key: string; value: string }>\n description?: string\n }): Promise<{\n registered_model: {\n name: string\n creation_timestamp: number\n last_updated_timestamp: number\n }\n }> {\n return this.request('/api/2.0/mlflow/registered-models/create', {\n method: 'POST',\n body: JSON.stringify(params)\n })\n }\n\n /**\n * Get registered model\n */\n async getRegisteredModel(name: string): Promise<{\n registered_model: {\n name: string\n creation_timestamp: number\n last_updated_timestamp: number\n description?: string\n tags?: Array<{ key: string; value: string }>\n latest_versions?: any[]\n }\n }> {\n return this.request(`/api/2.0/mlflow/registered-models/get?name=${encodeURIComponent(name)}`)\n }\n\n /**\n * Search registered models\n */\n async searchRegisteredModels(params?: { filter?: string; max_results?: number; order_by?: string[] }): Promise<{\n registered_models: Array<{\n name: string\n creation_timestamp: number\n last_updated_timestamp: number\n description?: string\n latest_versions?: any[]\n }>\n }> {\n const queryParams = new URLSearchParams()\n if (params?.filter) queryParams.append('filter', params.filter)\n if (params?.max_results) queryParams.append('max_results', String(params.max_results))\n\n return this.request(`/api/2.0/mlflow/registered-models/search?${queryParams}`)\n }\n\n /**\n * Create model version\n */\n async createModelVersion(params: {\n name: string\n source: string\n run_id?: string\n tags?: Array<{ key: string; value: string }>\n description?: string\n }): Promise<{\n model_version: {\n name: string\n version: string\n creation_timestamp: number\n last_updated_timestamp: number\n source: string\n status: string\n }\n }> {\n return this.request('/api/2.0/mlflow/model-versions/create', {\n method: 'POST',\n body: JSON.stringify(params)\n })\n }\n\n /**\n * Get model version\n */\n async getModelVersion(params: { name: string; version: string }): Promise<{\n model_version: {\n name: string\n version: string\n creation_timestamp: number\n current_stage: string\n source: string\n status: string\n run_id?: string\n }\n }> {\n return this.request(\n `/api/2.0/mlflow/model-versions/get?name=${encodeURIComponent(params.name)}&version=${params.version}`\n )\n }\n\n /**\n * Transition model version stage\n */\n async transitionModelVersionStage(params: {\n name: string\n version: string\n stage: 'None' | 'Staging' | 'Production' | 'Archived'\n archive_existing_versions?: boolean\n }): Promise<{\n model_version: {\n name: string\n version: string\n current_stage: string\n }\n }> {\n return this.request('/api/2.0/mlflow/model-versions/transition-stage', {\n method: 'POST',\n body: JSON.stringify(params)\n })\n }\n\n /**\n * Delete model version\n */\n async deleteModelVersion(params: { name: string; version: string }): Promise<void> {\n return this.request('/api/2.0/mlflow/model-versions/delete', {\n method: 'DELETE',\n body: JSON.stringify(params)\n })\n }\n}\n\n// Export singleton instance\nexport const mlflowApi = new MLflowApiClient()\n\n// Export class for testing or custom instances\nexport { MLflowApiClient }\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@things-factory/integration-mlflow",
|
|
3
|
+
"version": "9.1.19",
|
|
4
|
+
"main": "dist-server/index.js",
|
|
5
|
+
"browser": "dist-client/index.js",
|
|
6
|
+
"things-factory": true,
|
|
7
|
+
"author": "Things-Factory Team",
|
|
8
|
+
"description": "Integration module for MLflow - experiment tracking and model registry",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public",
|
|
12
|
+
"@things-factory:registry": "https://registry.npmjs.org"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/hatiolab/things-factory.git",
|
|
17
|
+
"directory": "packages/integration-mlflow"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "npm run build:server && npm run build:client",
|
|
21
|
+
"copy:files": "copyfiles -e \"./client/**/*.{ts,js,json}\" -u 1 \"./client/**/*\" dist-client",
|
|
22
|
+
"build:client": "npm run copy:files && npx tsc --p ./client/tsconfig.json",
|
|
23
|
+
"build:server": "npx tsc --p ./server/tsconfig.json",
|
|
24
|
+
"clean:client": "npx rimraf dist-client",
|
|
25
|
+
"clean:server": "npx rimraf dist-server",
|
|
26
|
+
"clean": "npm run clean:server && npm run clean:client"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@operato/graphql": "^9.0.0",
|
|
30
|
+
"@operato/layout": "^9.0.0",
|
|
31
|
+
"@things-factory/auth-base": "^9.1.19",
|
|
32
|
+
"@things-factory/env": "^9.1.0",
|
|
33
|
+
"@things-factory/menu-base": "^9.1.19",
|
|
34
|
+
"@things-factory/shell": "^9.1.19"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"things-factory",
|
|
38
|
+
"mlflow",
|
|
39
|
+
"integration",
|
|
40
|
+
"experiment-tracking",
|
|
41
|
+
"model-registry",
|
|
42
|
+
"machine-learning"
|
|
43
|
+
],
|
|
44
|
+
"gitHead": "078438034dbe19915108e89ff24024f7044a85a9"
|
|
45
|
+
}
|
package/server/index.ts
ADDED
package/server/route.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MLflow Integration Routes
|
|
3
|
+
*
|
|
4
|
+
* Register additional routes if needed (beyond GraphQL)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Example: Proxy to MLflow UI, webhooks, etc.
|
|
8
|
+
// import Koa from 'koa'
|
|
9
|
+
// import Router from 'koa-router'
|
|
10
|
+
|
|
11
|
+
// process.on('bootstrap-module-domain-private-route', (app: Koa, router: Router) => {
|
|
12
|
+
// // Add custom routes here
|
|
13
|
+
// })
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Resolver, Query, Mutation, Arg, Ctx, Directive } from 'type-graphql'
|
|
2
|
+
import { mlflowApi } from '../../utils/mlflow-api-client'
|
|
3
|
+
import {
|
|
4
|
+
MLflowExperiment,
|
|
5
|
+
CreateExperimentInput
|
|
6
|
+
} from '../../types/mlflow-types'
|
|
7
|
+
|
|
8
|
+
@Resolver()
|
|
9
|
+
export class ExperimentService {
|
|
10
|
+
/**
|
|
11
|
+
* Get all experiments
|
|
12
|
+
*/
|
|
13
|
+
@Query(returns => [MLflowExperiment], {
|
|
14
|
+
description: 'Get all MLflow experiments'
|
|
15
|
+
})
|
|
16
|
+
@Directive('@privilege(category: "mlflow", privilege: "query")')
|
|
17
|
+
async mlflowExperiments(@Ctx() context: ResolverContext): Promise<MLflowExperiment[]> {
|
|
18
|
+
try {
|
|
19
|
+
const response = await mlflowApi.searchExperiments()
|
|
20
|
+
|
|
21
|
+
return response.experiments.map(exp => ({
|
|
22
|
+
experimentId: exp.experiment_id,
|
|
23
|
+
name: exp.name,
|
|
24
|
+
artifactLocation: exp.artifact_location,
|
|
25
|
+
lifecycleStage: exp.lifecycle_stage,
|
|
26
|
+
tags: exp.tags ? JSON.stringify(exp.tags) : undefined
|
|
27
|
+
}))
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error('Failed to fetch MLflow experiments:', error)
|
|
30
|
+
throw new Error(`Failed to fetch experiments: ${error.message}`)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get experiment by ID
|
|
36
|
+
*/
|
|
37
|
+
@Query(returns => MLflowExperiment, {
|
|
38
|
+
description: 'Get MLflow experiment by ID',
|
|
39
|
+
nullable: true
|
|
40
|
+
})
|
|
41
|
+
@Directive('@privilege(category: "mlflow", privilege: "query")')
|
|
42
|
+
async mlflowExperiment(
|
|
43
|
+
@Arg('experimentId') experimentId: string,
|
|
44
|
+
@Ctx() context: ResolverContext
|
|
45
|
+
): Promise<MLflowExperiment | null> {
|
|
46
|
+
try {
|
|
47
|
+
const response = await mlflowApi.getExperiment(experimentId)
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
experimentId: response.experiment.experiment_id,
|
|
51
|
+
name: response.experiment.name,
|
|
52
|
+
artifactLocation: response.experiment.artifact_location,
|
|
53
|
+
lifecycleStage: response.experiment.lifecycle_stage,
|
|
54
|
+
tags: response.experiment.tags ? JSON.stringify(response.experiment.tags) : undefined
|
|
55
|
+
}
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error(`Failed to fetch experiment ${experimentId}:`, error)
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Create new experiment
|
|
64
|
+
*/
|
|
65
|
+
@Mutation(returns => MLflowExperiment, {
|
|
66
|
+
description: 'Create new MLflow experiment'
|
|
67
|
+
})
|
|
68
|
+
@Directive('@privilege(category: "mlflow", privilege: "mutation")')
|
|
69
|
+
async createMLflowExperiment(
|
|
70
|
+
@Arg('input') input: CreateExperimentInput,
|
|
71
|
+
@Ctx() context: ResolverContext
|
|
72
|
+
): Promise<MLflowExperiment> {
|
|
73
|
+
try {
|
|
74
|
+
const tags = input.tags ? JSON.parse(input.tags) : undefined
|
|
75
|
+
|
|
76
|
+
const response = await mlflowApi.createExperiment({
|
|
77
|
+
name: input.name,
|
|
78
|
+
artifact_location: input.artifactLocation,
|
|
79
|
+
tags
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
// Fetch created experiment
|
|
83
|
+
const experiment = await mlflowApi.getExperiment(response.experiment_id)
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
experimentId: experiment.experiment.experiment_id,
|
|
87
|
+
name: experiment.experiment.name,
|
|
88
|
+
artifactLocation: experiment.experiment.artifact_location,
|
|
89
|
+
lifecycleStage: experiment.experiment.lifecycle_stage,
|
|
90
|
+
tags: experiment.experiment.tags ? JSON.stringify(experiment.experiment.tags) : undefined
|
|
91
|
+
}
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error('Failed to create MLflow experiment:', error)
|
|
94
|
+
throw new Error(`Failed to create experiment: ${error.message}`)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Delete experiment
|
|
100
|
+
*/
|
|
101
|
+
@Mutation(returns => Boolean, {
|
|
102
|
+
description: 'Delete MLflow experiment'
|
|
103
|
+
})
|
|
104
|
+
@Directive('@privilege(category: "mlflow", privilege: "mutation")')
|
|
105
|
+
async deleteMLflowExperiment(
|
|
106
|
+
@Arg('experimentId') experimentId: string,
|
|
107
|
+
@Ctx() context: ResolverContext
|
|
108
|
+
): Promise<boolean> {
|
|
109
|
+
try {
|
|
110
|
+
await mlflowApi.deleteExperiment(experimentId)
|
|
111
|
+
return true
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error(`Failed to delete experiment ${experimentId}:`, error)
|
|
114
|
+
throw new Error(`Failed to delete experiment: ${error.message}`)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|