@aws/ml-container-creator 0.10.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/LICENSE-THIRD-PARTY +9304 -0
  2. package/bin/cli.js +2 -0
  3. package/config/bootstrap-e2e-stack.json +341 -0
  4. package/config/bootstrap-stack.json +40 -3
  5. package/config/parameter-schema-v2.json +33 -22
  6. package/config/tune-catalog.json +1781 -0
  7. package/infra/ci-harness/buildspec.yml +1 -0
  8. package/infra/ci-harness/lambda/path-prover/brain.ts +306 -0
  9. package/infra/ci-harness/lambda/path-prover/write-results.ts +152 -0
  10. package/infra/ci-harness/lib/ci-harness-stack.ts +851 -7
  11. package/infra/ci-harness/state-machines/path-prover.asl.json +496 -0
  12. package/package.json +53 -67
  13. package/servers/base-image-picker/index.js +121 -121
  14. package/servers/e2e-status/index.js +297 -0
  15. package/servers/e2e-status/manifest.json +14 -0
  16. package/servers/e2e-status/package.json +15 -0
  17. package/servers/endpoint-picker/LICENSE +202 -0
  18. package/servers/endpoint-picker/index.js +536 -0
  19. package/servers/endpoint-picker/manifest.json +14 -0
  20. package/servers/endpoint-picker/package.json +18 -0
  21. package/servers/hyperpod-cluster-picker/index.js +125 -125
  22. package/servers/instance-sizer/index.js +166 -153
  23. package/servers/instance-sizer/lib/instance-ranker.js +120 -76
  24. package/servers/instance-sizer/lib/model-resolver.js +61 -61
  25. package/servers/instance-sizer/lib/quota-resolver.js +113 -113
  26. package/servers/instance-sizer/lib/vram-estimator.js +31 -31
  27. package/servers/lib/bedrock-client.js +38 -38
  28. package/servers/lib/catalogs/instances.json +27 -0
  29. package/servers/lib/catalogs/model-servers.json +201 -3
  30. package/servers/lib/custom-validators.js +13 -13
  31. package/servers/lib/dynamic-resolver.js +4 -4
  32. package/servers/marketplace-picker/index.js +342 -0
  33. package/servers/marketplace-picker/manifest.json +14 -0
  34. package/servers/marketplace-picker/package.json +18 -0
  35. package/servers/model-picker/index.js +382 -382
  36. package/servers/region-picker/index.js +56 -56
  37. package/servers/workload-picker/LICENSE +202 -0
  38. package/servers/workload-picker/catalogs/workload-profiles.json +67 -0
  39. package/servers/workload-picker/index.js +171 -0
  40. package/servers/workload-picker/manifest.json +16 -0
  41. package/servers/workload-picker/package.json +16 -0
  42. package/src/app.js +12 -3
  43. package/src/lib/bootstrap-command-handler.js +609 -15
  44. package/src/lib/bootstrap-config.js +36 -0
  45. package/src/lib/bootstrap-profile-manager.js +48 -41
  46. package/src/lib/ci-register-helpers.js +74 -0
  47. package/src/lib/config-loader.js +3 -0
  48. package/src/lib/config-manager.js +7 -0
  49. package/src/lib/config-validator.js +1 -1
  50. package/src/lib/cuda-resolver.js +17 -8
  51. package/src/lib/generated/cli-options.js +319 -314
  52. package/src/lib/generated/parameter-matrix.js +672 -661
  53. package/src/lib/generated/validation-rules.js +76 -72
  54. package/src/lib/path-prover-brain.js +664 -0
  55. package/src/lib/prompts/infrastructure-prompts.js +2 -2
  56. package/src/lib/prompts/model-prompts.js +6 -0
  57. package/src/lib/prompts/project-prompts.js +12 -0
  58. package/src/lib/secrets-prompt-runner.js +4 -0
  59. package/src/lib/template-manager.js +1 -1
  60. package/src/lib/template-variable-resolver.js +87 -1
  61. package/src/lib/tune-catalog-validator.js +37 -4
  62. package/templates/Dockerfile +9 -0
  63. package/templates/code/adapter_sidecar.py +444 -0
  64. package/templates/code/serve +6 -0
  65. package/templates/code/serve.d/vllm.ejs +1 -1
  66. package/templates/do/.benchmark_writer.py +1476 -0
  67. package/templates/do/.tune_helper.py +982 -57
  68. package/templates/do/__pycache__/.benchmark_writer.cpython-312.pyc +0 -0
  69. package/templates/do/adapter +154 -0
  70. package/templates/do/benchmark +639 -85
  71. package/templates/do/build +5 -0
  72. package/templates/do/clean.d/async-inference.ejs +5 -0
  73. package/templates/do/clean.d/batch-transform.ejs +5 -0
  74. package/templates/do/clean.d/hyperpod-eks.ejs +5 -0
  75. package/templates/do/clean.d/managed-inference.ejs +5 -0
  76. package/templates/do/config +115 -45
  77. package/templates/do/deploy.d/async-inference.ejs +30 -3
  78. package/templates/do/deploy.d/batch-transform.ejs +29 -3
  79. package/templates/do/deploy.d/hyperpod-eks.ejs +4 -0
  80. package/templates/do/deploy.d/managed-inference.ejs +216 -14
  81. package/templates/do/lib/endpoint-config.sh +1 -1
  82. package/templates/do/lib/profile.sh +44 -0
  83. package/templates/do/optimize +106 -37
  84. package/templates/do/push +5 -0
  85. package/templates/do/register +94 -0
  86. package/templates/do/stage +567 -0
  87. package/templates/do/submit +7 -0
  88. package/templates/do/test +14 -0
  89. package/templates/do/tune +382 -59
  90. package/templates/do/validate +44 -4
@@ -0,0 +1,342 @@
1
+ #!/usr/bin/env node
2
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
+ // SPDX-License-Identifier: Apache-2.0
4
+
5
+ /**
6
+ * Marketplace Picker MCP Server
7
+ *
8
+ * A bundled MCP server that discovers active AWS Marketplace model package
9
+ * subscriptions for deployment via the ml-container-creator generator.
10
+ *
11
+ * Uses ListModelPackages with ModelPackageType='Marketplace' to find subscribed
12
+ * packages, then DescribeModelPackage for each to extract InferenceSpecification
13
+ * details (supported instance types, content types).
14
+ *
15
+ * Tool: get_marketplace_subscriptions
16
+ * Accepts: { region?: string, limit?: number }
17
+ * Returns: { subscriptions: [...], message: string }
18
+ *
19
+ * Environment variables:
20
+ * AWS_REGION - AWS region for SageMaker API calls (default: us-east-1)
21
+ * AWS_PROFILE - AWS profile to use for credentials
22
+ */
23
+
24
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
25
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
26
+ import { z } from 'zod';
27
+ import { fileURLToPath } from 'node:url';
28
+ import { resolve } from 'node:path';
29
+ import { readFileSync } from 'node:fs';
30
+ import { homedir } from 'node:os';
31
+
32
+ /**
33
+ * Log to stderr so it doesn't interfere with MCP stdio protocol on stdout.
34
+ */
35
+ function log(message) {
36
+ process.stderr.write(`[marketplace-picker] ${message}\n`);
37
+ }
38
+
39
+ // ── AWS SDK lazy loading ─────────────────────────────────────────────────────
40
+
41
+ let _SageMakerClient = null;
42
+ let _ListModelPackagesCommand = null;
43
+ let _DescribeModelPackageCommand = null;
44
+ let _fromIni = null;
45
+
46
+ /**
47
+ * Lazily load the AWS SDK SageMaker client classes.
48
+ */
49
+ async function _ensureSdkLoaded() {
50
+ if (_SageMakerClient) return;
51
+ const sdk = await import('@aws-sdk/client-sagemaker');
52
+ _SageMakerClient = sdk.SageMakerClient;
53
+ _ListModelPackagesCommand = sdk.ListModelPackagesCommand;
54
+ _DescribeModelPackageCommand = sdk.DescribeModelPackageCommand;
55
+ try {
56
+ const credentialProviders = await import('@aws-sdk/credential-providers');
57
+ _fromIni = credentialProviders.fromIni;
58
+ } catch {
59
+ // credential-providers not available — profile-based fallback won't work
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Create a SageMaker client for the given region using default credential chain.
65
+ */
66
+ function _createClient(region) {
67
+ return new _SageMakerClient({ region });
68
+ }
69
+
70
+ /**
71
+ * Create a SageMaker client using a named AWS profile via fromIni.
72
+ */
73
+ function _createClientWithProfile(region, profile) {
74
+ if (!_fromIni) {
75
+ throw new Error('Cannot use profile-based credentials: @aws-sdk/credential-providers not available');
76
+ }
77
+ return new _SageMakerClient({
78
+ region,
79
+ credentials: _fromIni({ profile })
80
+ });
81
+ }
82
+
83
+ /**
84
+ * Detect available AWS profile names from ~/.aws/credentials and ~/.aws/config.
85
+ */
86
+ function _detectAwsProfiles() {
87
+ const profiles = new Set();
88
+ try {
89
+ const credsPath = resolve(homedir(), '.aws/credentials');
90
+ const creds = readFileSync(credsPath, 'utf8');
91
+ for (const match of creds.matchAll(/^\[(.+)\]$/gm)) {
92
+ profiles.add(match[1]);
93
+ }
94
+ } catch { /* no credentials file */ }
95
+ try {
96
+ const configPath = resolve(homedir(), '.aws/config');
97
+ const config = readFileSync(configPath, 'utf8');
98
+ for (const match of config.matchAll(/^\[profile\s+(.+)\]$/gm)) {
99
+ profiles.add(match[1]);
100
+ }
101
+ } catch { /* no config file */ }
102
+ return [...profiles];
103
+ }
104
+
105
+ // ── Core logic ───────────────────────────────────────────────────────────────
106
+
107
+ /**
108
+ * Fetch marketplace model package subscriptions from SageMaker.
109
+ *
110
+ * Lists model packages with ModelPackageType='Marketplace', then describes
111
+ * each to extract InferenceSpecification details.
112
+ *
113
+ * @param {object} client - SageMaker client instance
114
+ * @param {object} options - { limit }
115
+ * @returns {Promise<Array<object>>} Array of subscription info objects
116
+ */
117
+ async function fetchMarketplaceSubscriptions(client, { limit = 20 } = {}) {
118
+ const subscriptions = [];
119
+ let nextToken;
120
+
121
+ // Paginate ListModelPackages — Marketplace type only
122
+ const collectedArns = [];
123
+ do {
124
+ const params = {
125
+ ModelPackageType: 'Marketplace',
126
+ MaxResults: Math.min(limit, 100)
127
+ };
128
+ if (nextToken) params.NextToken = nextToken;
129
+
130
+ const command = new _ListModelPackagesCommand(params);
131
+ const response = await client.send(command);
132
+
133
+ const summaries = response.ModelPackageSummaryList || [];
134
+ for (const summary of summaries) {
135
+ collectedArns.push(summary.ModelPackageArn);
136
+ if (collectedArns.length >= limit) break;
137
+ }
138
+
139
+ nextToken = response.NextToken;
140
+ } while (nextToken && collectedArns.length < limit);
141
+
142
+ // Describe each model package to get InferenceSpecification details
143
+ for (const arn of collectedArns) {
144
+ try {
145
+ const describeCmd = new _DescribeModelPackageCommand({
146
+ ModelPackageName: arn
147
+ });
148
+ const detail = await client.send(describeCmd);
149
+
150
+ const inferenceSpec = detail.InferenceSpecification || {};
151
+ const supportedInstanceTypes = inferenceSpec.SupportedRealtimeInferenceInstanceTypes || [];
152
+ const supportedContentTypes = inferenceSpec.SupportedContentTypes || [];
153
+
154
+ // Extract model name from the ARN (last segment before version)
155
+ const arnParts = arn.split('/');
156
+ const modelName = arnParts.length >= 2 ? arnParts[arnParts.length - 2] : arn;
157
+
158
+ // Extract vendor from model package description or source
159
+ const vendor = detail.ModelPackageDescription
160
+ ? detail.ModelPackageDescription.split(' ')[0]
161
+ : 'Unknown';
162
+
163
+ subscriptions.push({
164
+ arn,
165
+ modelName,
166
+ vendor,
167
+ supportedInstanceTypes,
168
+ supportedContentTypes,
169
+ status: detail.ModelPackageStatus || 'Unknown'
170
+ });
171
+ } catch (err) {
172
+ if (err.name === 'AccessDeniedException' || err.Code === 'AccessDeniedException') {
173
+ log(`AccessDeniedException for package "${arn}" — skipping`);
174
+ continue;
175
+ }
176
+ log(`Warning: could not describe package "${arn}": ${err.message}`);
177
+ }
178
+ }
179
+
180
+ return subscriptions;
181
+ }
182
+
183
+ /**
184
+ * Build the MCP response from a list of discovered subscriptions.
185
+ *
186
+ * @param {Array} subscriptions - Array of subscription objects from fetchMarketplaceSubscriptions
187
+ * @returns {{ subscriptions: Array, message: string }}
188
+ */
189
+ function buildResponse(subscriptions) {
190
+ if (!subscriptions || subscriptions.length === 0) {
191
+ return {
192
+ subscriptions: [],
193
+ message: 'No active AWS Marketplace model package subscriptions found in this region. Subscribe to models at https://aws.amazon.com/marketplace/solutions/machine-learning'
194
+ };
195
+ }
196
+
197
+ return {
198
+ subscriptions,
199
+ message: `Found ${subscriptions.length} Marketplace model package subscription(s).`
200
+ };
201
+ }
202
+
203
+ // ── MCP Server ───────────────────────────────────────────────────────────────
204
+
205
+ const server = new McpServer({
206
+ name: 'marketplace-picker',
207
+ version: '1.0.0'
208
+ });
209
+
210
+ // Register the get_marketplace_subscriptions tool
211
+ server.tool(
212
+ 'get_marketplace_subscriptions',
213
+ 'Discovers active AWS Marketplace model package subscriptions with supported instance types and content types',
214
+ {
215
+ region: z.string().optional().describe('AWS region to query (defaults to AWS_REGION env var or us-east-1)'),
216
+ limit: z.number().int().positive().default(20).describe('Maximum number of subscriptions to return')
217
+ },
218
+ async ({ region, limit }) => {
219
+ const effectiveRegion = region || process.env.AWS_REGION || 'us-east-1';
220
+ const profile = process.env.AWS_PROFILE || null;
221
+ log(`Querying Marketplace subscriptions in region: ${effectiveRegion}${profile ? ` (profile: ${profile})` : ''}`);
222
+
223
+ try {
224
+ await _ensureSdkLoaded();
225
+
226
+ let subscriptions = null;
227
+ let lastError = null;
228
+
229
+ // Strategy 1: If a specific profile was requested, use it directly
230
+ if (profile) {
231
+ try {
232
+ log(`Trying explicit profile: ${profile}`);
233
+ const client = _createClientWithProfile(effectiveRegion, profile);
234
+ subscriptions = await fetchMarketplaceSubscriptions(client, { limit });
235
+ } catch (err) {
236
+ log(`Profile "${profile}" failed: ${err.message}`);
237
+ lastError = err;
238
+ }
239
+ }
240
+
241
+ // Strategy 2: Try the default credential chain
242
+ if (!subscriptions) {
243
+ try {
244
+ log('Trying default credential chain');
245
+ const client = _createClient(effectiveRegion);
246
+ subscriptions = await fetchMarketplaceSubscriptions(client, { limit });
247
+ } catch (err) {
248
+ log(`Default credential chain failed: ${err.message}`);
249
+ lastError = err;
250
+ }
251
+ }
252
+
253
+ // Strategy 3: Detect available AWS profiles and try each
254
+ if (!subscriptions && _fromIni) {
255
+ const profiles = _detectAwsProfiles();
256
+ if (profiles.length > 0) {
257
+ log(`Default credentials failed, trying ${profiles.length} detected profile(s): ${profiles.join(', ')}`);
258
+ for (const p of profiles) {
259
+ try {
260
+ const client = _createClientWithProfile(effectiveRegion, p);
261
+ subscriptions = await fetchMarketplaceSubscriptions(client, { limit });
262
+ log(`Profile "${p}" succeeded`);
263
+ break;
264
+ } catch (err) {
265
+ log(`Profile "${p}" failed: ${err.message}`);
266
+ lastError = err;
267
+ }
268
+ }
269
+ }
270
+ }
271
+
272
+ // If all strategies failed, throw the last error
273
+ if (!subscriptions) {
274
+ throw lastError || new Error('No AWS credentials available');
275
+ }
276
+
277
+ const result = buildResponse(subscriptions);
278
+
279
+ if (subscriptions.length > 0) {
280
+ log(`Found ${subscriptions.length} Marketplace subscription(s)`);
281
+ } else {
282
+ log('No Marketplace subscriptions found');
283
+ }
284
+
285
+ return {
286
+ content: [{
287
+ type: 'text',
288
+ text: JSON.stringify(result)
289
+ }]
290
+ };
291
+ } catch (err) {
292
+ log(`Error querying Marketplace subscriptions: ${err.message}`);
293
+
294
+ // Handle AccessDeniedException gracefully
295
+ if (err.name === 'AccessDeniedException' || err.Code === 'AccessDeniedException') {
296
+ log('AccessDeniedException — returning empty result');
297
+ return {
298
+ content: [{
299
+ type: 'text',
300
+ text: JSON.stringify({
301
+ subscriptions: [],
302
+ message: 'Access denied when querying Marketplace subscriptions. Check IAM permissions for sagemaker:ListModelPackages and sagemaker:DescribeModelPackage.'
303
+ })
304
+ }]
305
+ };
306
+ }
307
+
308
+ const errorResult = {
309
+ subscriptions: [],
310
+ error: err.message,
311
+ message: `Failed to query Marketplace subscriptions: ${err.message}`
312
+ };
313
+ return {
314
+ content: [{
315
+ type: 'text',
316
+ text: JSON.stringify(errorResult)
317
+ }]
318
+ };
319
+ }
320
+ }
321
+ );
322
+
323
+ // Export for testing
324
+ export {
325
+ fetchMarketplaceSubscriptions,
326
+ buildResponse,
327
+ _ensureSdkLoaded,
328
+ _createClient,
329
+ _createClientWithProfile,
330
+ _detectAwsProfiles
331
+ };
332
+
333
+ // Guard MCP transport — only connect when run as main module
334
+ const __filename = fileURLToPath(import.meta.url);
335
+ const isMain = process.argv[1] && resolve(process.argv[1]) === __filename;
336
+
337
+ if (isMain) {
338
+ log('Starting Marketplace Picker MCP server');
339
+ await _ensureSdkLoaded();
340
+ const transport = new StdioServerTransport();
341
+ await server.connect(transport);
342
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "@amzn/ml-container-creator-marketplace-picker",
3
+ "version": "1.0.0",
4
+ "description": "Discovers active AWS Marketplace model package subscriptions for deployment.",
5
+ "modes": {
6
+ "static": false,
7
+ "smart": false,
8
+ "discover": true
9
+ },
10
+ "catalogs": {},
11
+ "tool": {
12
+ "name": "get_marketplace_subscriptions"
13
+ }
14
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "@amzn/ml-container-creator-marketplace-picker",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "description": "MCP server that discovers active AWS Marketplace model package subscriptions.",
6
+ "type": "module",
7
+ "main": "index.js",
8
+ "license": "Apache-2.0",
9
+ "scripts": {
10
+ "test": "node test.js"
11
+ },
12
+ "dependencies": {
13
+ "@aws-sdk/client-sagemaker": "^3.700.0",
14
+ "@aws-sdk/credential-providers": "^3.700.0",
15
+ "@modelcontextprotocol/sdk": "^1.0.0",
16
+ "zod": "^3.22.0"
17
+ }
18
+ }