@aws/ml-container-creator 0.9.1 → 0.10.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.
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 +2049 -0
  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 +837 -7
  11. package/infra/ci-harness/state-machines/path-prover.asl.json +496 -0
  12. package/package.json +53 -68
  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 +138 -138
  23. package/servers/instance-sizer/lib/instance-ranker.js +76 -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/jumpstart-public.json +101 -16
  29. package/servers/lib/catalogs/model-servers.json +201 -3
  30. package/servers/lib/catalogs/models.json +182 -26
  31. package/servers/lib/custom-validators.js +13 -13
  32. package/servers/lib/dynamic-resolver.js +4 -4
  33. package/servers/marketplace-picker/index.js +342 -0
  34. package/servers/marketplace-picker/manifest.json +14 -0
  35. package/servers/marketplace-picker/package.json +18 -0
  36. package/servers/model-picker/index.js +382 -382
  37. package/servers/region-picker/index.js +56 -56
  38. package/servers/workload-picker/LICENSE +202 -0
  39. package/servers/workload-picker/catalogs/workload-profiles.json +67 -0
  40. package/servers/workload-picker/index.js +171 -0
  41. package/servers/workload-picker/manifest.json +16 -0
  42. package/servers/workload-picker/package.json +16 -0
  43. package/src/app.js +4 -390
  44. package/src/lib/bootstrap-command-handler.js +710 -1148
  45. package/src/lib/bootstrap-config.js +36 -0
  46. package/src/lib/bootstrap-profile-manager.js +641 -0
  47. package/src/lib/bootstrap-provisioners.js +421 -0
  48. package/src/lib/ci-register-helpers.js +74 -0
  49. package/src/lib/config-loader.js +408 -0
  50. package/src/lib/config-manager.js +66 -1685
  51. package/src/lib/config-mcp-client.js +118 -0
  52. package/src/lib/config-validator.js +634 -0
  53. package/src/lib/cuda-resolver.js +149 -0
  54. package/src/lib/e2e-catalog-validator.js +251 -3
  55. package/src/lib/e2e-ci-recorder.js +103 -0
  56. package/src/lib/generated/cli-options.js +315 -311
  57. package/src/lib/generated/parameter-matrix.js +671 -0
  58. package/src/lib/generated/validation-rules.js +71 -71
  59. package/src/lib/marketplace-flow.js +276 -0
  60. package/src/lib/mcp-query-runner.js +768 -0
  61. package/src/lib/parameter-schema-validator.js +62 -18
  62. package/src/lib/path-prover-brain.js +607 -0
  63. package/src/lib/prompt-runner.js +41 -1504
  64. package/src/lib/prompts/feature-prompts.js +172 -0
  65. package/src/lib/prompts/index.js +48 -0
  66. package/src/lib/prompts/infrastructure-prompts.js +690 -0
  67. package/src/lib/prompts/model-prompts.js +552 -0
  68. package/src/lib/prompts/project-prompts.js +82 -0
  69. package/src/lib/prompts.js +2 -1446
  70. package/src/lib/registry-command-handler.js +135 -3
  71. package/src/lib/secrets-prompt-runner.js +251 -0
  72. package/src/lib/template-variable-resolver.js +422 -0
  73. package/src/lib/tune-catalog-validator.js +37 -4
  74. package/templates/Dockerfile +9 -0
  75. package/templates/code/adapter_sidecar.py +444 -0
  76. package/templates/code/serve +6 -0
  77. package/templates/code/serve.d/vllm.ejs +1 -1
  78. package/templates/do/.benchmark_writer.py +1476 -0
  79. package/templates/do/.tune_helper.py +982 -57
  80. package/templates/do/__pycache__/.benchmark_writer.cpython-312.pyc +0 -0
  81. package/templates/do/adapter +149 -0
  82. package/templates/do/benchmark +639 -85
  83. package/templates/do/config +108 -5
  84. package/templates/do/deploy.d/managed-inference.ejs +192 -11
  85. package/templates/do/optimize +106 -37
  86. package/templates/do/register +89 -0
  87. package/templates/do/test +13 -0
  88. package/templates/do/tune +378 -59
  89. package/templates/do/validate +44 -4
  90. package/config/parameter-schema.json +0 -88
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "@amzn/ml-container-creator-workload-picker",
3
+ "version": "1.0.0",
4
+ "description": "MCP server that provides named benchmark workload profiles for do/benchmark.",
5
+ "modes": {
6
+ "static": true,
7
+ "smart": false,
8
+ "discover": false
9
+ },
10
+ "catalogs": {
11
+ "workload-profiles": "./catalogs/workload-profiles.json"
12
+ },
13
+ "tool": {
14
+ "name": "get_workload_profile"
15
+ }
16
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "@amzn/ml-container-creator-workload-picker",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "description": "MCP server that provides named benchmark workload profiles for ML Container Creator. Defines token distributions, concurrency levels, and streaming modes for standardized benchmarking.",
6
+ "type": "module",
7
+ "main": "index.js",
8
+ "license": "Apache-2.0",
9
+ "scripts": {
10
+ "test": "node test.js"
11
+ },
12
+ "dependencies": {
13
+ "@modelcontextprotocol/sdk": "^1.12.0",
14
+ "zod": "^3.22.0"
15
+ }
16
+ }
package/src/app.js CHANGED
@@ -16,7 +16,7 @@ import CommentGenerator from './lib/comment-generator.js';
16
16
  import ConfigurationManager from './lib/configuration-manager.js';
17
17
  import RegistryLoader from './lib/registry-loader.js';
18
18
  import { resolvePrefixedEnvVars } from './lib/engine-prefix-resolver.js';
19
- import { isTuneSupported } from './lib/tune-catalog-validator.js';
19
+ import { _ensureTemplateVariables, _validateEnvironmentVariables } from './lib/template-variable-resolver.js';
20
20
  import ejs from 'ejs';
21
21
 
22
22
  const __filename = fileURLToPath(import.meta.url);
@@ -344,13 +344,15 @@ export async function writeProject(templateDir, destDir, answers, registryConfig
344
344
  // Exclude do/benchmark when benchmarking is not selected
345
345
  if (!answers.includeBenchmark) {
346
346
  ignorePatterns.push('**/do/benchmark');
347
+ ignorePatterns.push('**/do/.benchmark_writer.py');
347
348
  ignorePatterns.push('**/do/optimize');
348
349
  }
349
350
 
350
- // Exclude do/adapter and do/adapters/ when LoRA is not enabled
351
+ // Exclude do/adapter, do/adapters/, and adapter sidecar when LoRA is not enabled
351
352
  if (!answers.enableLora) {
352
353
  ignorePatterns.push('**/do/adapter');
353
354
  ignorePatterns.push('**/do/adapters/**');
355
+ ignorePatterns.push('**/code/adapter_sidecar.py');
354
356
  }
355
357
 
356
358
  // Exclude tune files when framework is NOT transformers OR deploymentTarget is batch-transform
@@ -634,218 +636,6 @@ function _createGeneratorAdapter(projectName, options) {
634
636
  return adapter;
635
637
  }
636
638
 
637
- /**
638
- * Ensures all template variables have proper defaults to prevent
639
- * "undefined" errors in EJS templates. Also enriches answers with
640
- * registry data (env var merging, HuggingFace data, Triton base image).
641
- *
642
- * @param {object} answers - Answers object to fill defaults into
643
- * @param {object|null} registryConfigManager - Registry configuration manager (or null)
644
- */
645
- async function _ensureTemplateVariables(answers, registryConfigManager = null) {
646
- const defaults = {
647
- chatTemplate: null,
648
- chatTemplateSource: null,
649
- hfToken: null,
650
- hfTokenArn: null,
651
- ngcApiKey: null,
652
- ngcTokenArn: null,
653
- envVars: {},
654
- inferenceAmiVersion: null,
655
- accelerator: null,
656
- frameworkVersion: null,
657
- validationLevel: 'unknown',
658
- configSources: [],
659
- recommendedInstanceTypes: [],
660
- roleArn: null,
661
- deploymentConfig: '',
662
- architecture: null,
663
- backend: null,
664
- engine: null,
665
- codebuildComputeType: null,
666
- codebuildProjectName: null,
667
- modelName: null,
668
- modelFormat: null,
669
- includeSampleModel: true,
670
- includeTesting: true,
671
- testTypes: [],
672
- buildTimestamp: new Date().toISOString(),
673
- buildTarget: 'codebuild',
674
- deploymentTarget: 'realtime-inference',
675
- hyperPodCluster: null,
676
- hyperPodNamespace: 'default',
677
- hyperPodReplicas: 1,
678
- fsxVolumeHandle: null,
679
- baseImage: null,
680
- modelSource: 'huggingface',
681
- artifactUri: '',
682
- modelLoadStrategy: 'runtime',
683
- existingEndpointName: null,
684
- enableLora: false,
685
- maxLoras: 30,
686
- maxLoraRank: 64
687
- };
688
-
689
- Object.entries(defaults).forEach(([key, value]) => {
690
- if (answers[key] === undefined) {
691
- answers[key] = value;
692
- }
693
- });
694
-
695
- // Backward compatibility: populate framework and modelServer from architecture/backend
696
- if (!answers.framework && answers.architecture) {
697
- answers.framework = answers.architecture;
698
- }
699
- if (!answers.modelServer && answers.backend) {
700
- answers.modelServer = answers.backend;
701
- }
702
-
703
- // Always include testing with all available test types
704
- answers.includeTesting = true;
705
- if (!answers.testTypes || answers.testTypes.length === 0) {
706
- if (answers.architecture === 'transformers' || answers.framework === 'transformers') {
707
- answers.testTypes = ['hosted-model-endpoint'];
708
- } else {
709
- answers.testTypes = ['local-model-cli', 'local-model-server', 'hosted-model-endpoint'];
710
- }
711
- }
712
-
713
- // Merge catalog env vars into answers.envVars with correct precedence
714
- await _mergeEnvVarsWithPrecedence(answers, registryConfigManager);
715
-
716
- // For Triton architecture, set default base image fallback
717
- if (answers.architecture === 'triton' && !answers.baseImage) {
718
- // Try to look up base image from framework registry using deployment-config key
719
- const tritonRegistryKey = answers.deploymentConfig;
720
- if (tritonRegistryKey && registryConfigManager?.frameworkRegistry) {
721
- const tritonFrameworkConfig = registryConfigManager.frameworkRegistry[tritonRegistryKey];
722
- if (tritonFrameworkConfig) {
723
- const versions = Object.keys(tritonFrameworkConfig).sort((a, b) =>
724
- b.localeCompare(a, undefined, { numeric: true })
725
- );
726
- if (versions.length > 0) {
727
- const latestConfig = tritonFrameworkConfig[versions[0]];
728
- if (latestConfig.baseImage) {
729
- answers.baseImage = latestConfig.baseImage;
730
- }
731
- if (latestConfig.inferenceAmiVersion && !answers.inferenceAmiVersion) {
732
- answers.inferenceAmiVersion = latestConfig.inferenceAmiVersion;
733
- }
734
- if (latestConfig.accelerator) {
735
- answers.accelerator = latestConfig.accelerator;
736
- }
737
- }
738
- }
739
- }
740
- // Final fallback: hardcoded default Triton base image
741
- if (!answers.baseImage) {
742
- answers.baseImage = 'nvcr.io/nvidia/tritonserver:24.08-py3';
743
- }
744
- }
745
-
746
- // For transformer models, enrich with HuggingFace data and non-envVar metadata
747
- if (answers.framework === 'transformers' && answers.modelName && registryConfigManager) {
748
- try {
749
- // Fetch HuggingFace data for model-specific info
750
- const hfData = await registryConfigManager._fetchHuggingFaceData(answers.modelName);
751
-
752
- // Merge chatTemplate if available and not already set
753
- if (hfData && hfData.chatTemplate && !answers.chatTemplate) {
754
- answers.chatTemplate = hfData.chatTemplate;
755
- answers.chatTemplateSource = 'HuggingFace_Hub_API';
756
- }
757
-
758
- // Check Model Registry for chatTemplate overrides
759
- if (registryConfigManager.modelRegistry) {
760
- const modelConfig = _findModelConfig(answers.modelName, registryConfigManager);
761
-
762
- if (modelConfig && modelConfig.chatTemplate) {
763
- answers.chatTemplate = modelConfig.chatTemplate;
764
- answers.chatTemplateSource = 'Model_Registry';
765
- }
766
- }
767
-
768
- // Set framework-level metadata (non-envVar fields)
769
- if (answers.frameworkVersion && registryConfigManager.frameworkRegistry) {
770
- const frameworkConfig = registryConfigManager.frameworkRegistry[answers.framework]?.[answers.frameworkVersion];
771
-
772
- if (frameworkConfig) {
773
- if (frameworkConfig.inferenceAmiVersion && !answers.inferenceAmiVersion) {
774
- answers.inferenceAmiVersion = frameworkConfig.inferenceAmiVersion;
775
- }
776
- if (frameworkConfig.accelerator) {
777
- answers.accelerator = frameworkConfig.accelerator;
778
- }
779
- }
780
- }
781
- } catch (error) {
782
- // Silently continue - defaults are already set
783
- }
784
- }
785
-
786
- // Populate baseImage from the catalog when still falsy (covers --skip-prompts and
787
- // cases where MCP/CLI/config did not provide a base image).
788
- // Precedence: MCP > CLI > config > catalog default (this block).
789
- if (!answers.baseImage && registryConfigManager?.frameworkRegistry) {
790
- const backendKey = answers.backend || answers.modelServer;
791
- if (backendKey) {
792
- const frameworkVersions = registryConfigManager.frameworkRegistry[backendKey];
793
- if (frameworkVersions) {
794
- let resolvedConfig = null;
795
- if (answers.frameworkVersion && frameworkVersions[answers.frameworkVersion]) {
796
- resolvedConfig = frameworkVersions[answers.frameworkVersion];
797
- } else {
798
- // Fall back to latest version
799
- const versions = Object.keys(frameworkVersions).sort((a, b) =>
800
- b.localeCompare(a, undefined, { numeric: true })
801
- );
802
- if (versions.length > 0) {
803
- resolvedConfig = frameworkVersions[versions[0]];
804
- }
805
- }
806
- if (resolvedConfig?.baseImage) {
807
- answers.baseImage = resolvedConfig.baseImage;
808
- }
809
- }
810
- }
811
- }
812
-
813
- // Populate icGpuCount from instance catalog when not explicitly set.
814
- // The deploy template uses IC_GPU_COUNT unconditionally for NumberOfAcceleratorDevicesRequired,
815
- // so it must always have a value for GPU deployments.
816
- if (answers.icGpuCount == null && answers.instanceType) {
817
- // Use gpuCount from instance-sizer recommendation if available
818
- if (answers.gpuCount) {
819
- answers.icGpuCount = answers.gpuCount;
820
- } else {
821
- // Look up from instances catalog
822
- try {
823
- const catalogPath = path.resolve(__dirname, '..', 'servers', 'lib', 'catalogs', 'instances.json');
824
- const catalogData = JSON.parse(fs.readFileSync(catalogPath, 'utf-8'));
825
- const instanceInfo = catalogData?.catalog?.[answers.instanceType];
826
- if (instanceInfo?.gpus && instanceInfo.gpus > 0) {
827
- answers.icGpuCount = instanceInfo.gpus;
828
- }
829
- } catch {
830
- // Silently continue — template fallback handles missing value
831
- }
832
- }
833
- }
834
-
835
- // Determine tune support based on model presence in the tune catalog.
836
- // Used by the do/config template to write TUNE_SUPPORTED=true|false.
837
- if (answers.tuneSupported === undefined) {
838
- try {
839
- const tuneCatalogPath = path.resolve(__dirname, '..', 'config', 'tune-catalog.json');
840
- const tuneCatalog = JSON.parse(fs.readFileSync(tuneCatalogPath, 'utf-8'));
841
- const modelId = answers.modelName || '';
842
- answers.tuneSupported = isTuneSupported(modelId, tuneCatalog);
843
- } catch {
844
- answers.tuneSupported = false;
845
- }
846
- }
847
- }
848
-
849
639
  /**
850
640
  * Orders environment variables by priority category for template rendering.
851
641
  *
@@ -894,182 +684,6 @@ function _getOrderedEnvVars(envVars) {
894
684
  return sorted.map(([key, value]) => ({ key, value }));
895
685
  }
896
686
 
897
- /**
898
- * Validates environment variables using the registry system.
899
- * Displays errors and warnings to the user.
900
- *
901
- * @param {object} answers - Configuration answers
902
- * @param {object} registryConfigManager - Registry configuration manager
903
- */
904
- async function _validateEnvironmentVariables(answers, registryConfigManager) {
905
- // Get framework configuration
906
- // For Triton configs, look up using deploymentConfig key (e.g. 'triton-fil')
907
- let frameworkConfig;
908
- if (answers.architecture === 'triton' && answers.deploymentConfig) {
909
- const tritonEntry = registryConfigManager.frameworkRegistry?.[answers.deploymentConfig];
910
- if (tritonEntry) {
911
- const versions = Object.keys(tritonEntry);
912
- if (versions.length > 0) {
913
- frameworkConfig = tritonEntry[versions[0]];
914
- }
915
- }
916
- }
917
- if (!frameworkConfig) {
918
- frameworkConfig = registryConfigManager.frameworkRegistry?.[answers.framework]?.[answers.frameworkVersion];
919
- }
920
-
921
- if (!frameworkConfig || !frameworkConfig.envVars) {
922
- return; // No env vars to validate
923
- }
924
-
925
- console.log('\n🔍 Validating environment variables...');
926
-
927
- // Validate environment variables
928
- const validationResult = registryConfigManager.validateEnvironmentVariables(
929
- frameworkConfig.envVars,
930
- frameworkConfig
931
- );
932
-
933
- // Display validation results
934
- if (validationResult.errors && validationResult.errors.length > 0) {
935
- console.log('\n❌ Environment Variable Validation Errors:');
936
- validationResult.errors.forEach(error => {
937
- console.log(` • ${error.key}: ${error.message}`);
938
- });
939
- }
940
-
941
- if (validationResult.warnings && validationResult.warnings.length > 0) {
942
- console.log('\n⚠️ Environment Variable Validation Warnings:');
943
- validationResult.warnings.forEach(warning => {
944
- console.log(` • ${warning.key ? `${warning.key}: ` : ''}${warning.message}`);
945
- });
946
- }
947
-
948
- if (validationResult.strategiesUsed && validationResult.strategiesUsed.length > 0) {
949
- console.log(`\n✅ Validation methods used: ${validationResult.strategiesUsed.join(', ')}`);
950
- }
951
-
952
- if (!validationResult.errors || validationResult.errors.length === 0) {
953
- if (!validationResult.warnings || validationResult.warnings.length === 0) {
954
- console.log(' ✅ All environment variables validated successfully');
955
- }
956
- }
957
-
958
- // In non-interactive mode (skip-prompts), throw on errors
959
- if (validationResult.errors && validationResult.errors.length > 0) {
960
- throw new Error('Environment variable validation failed. Please fix the errors and try again.');
961
- }
962
- }
963
-
964
- /**
965
- * Merges environment variables from all catalog sources with correct precedence.
966
- * Precedence (lowest → highest):
967
- * 1. catalog defaults (Image_Entry defaults.envVars)
968
- * 2. framework profile (Image_Entry profiles[selectedProfile].envVars)
969
- * 3. model entry (model catalog entry envVars)
970
- * 4. model profile (model catalog entry profiles[selectedProfile].envVars)
971
- * 5. CLI overrides (existing answers.envVars from user CLI input)
972
- *
973
- * @param {object} answers - Configuration answers
974
- * @param {object|null} registryConfigManager - Registry configuration manager
975
- */
976
- async function _mergeEnvVarsWithPrecedence(answers, registryConfigManager) {
977
- if (!registryConfigManager) return;
978
-
979
- // Capture CLI-provided env vars before merging (highest precedence)
980
- const cliEnvVars = { ...answers.envVars };
981
-
982
- // Resolve the framework config for the selected framework + version
983
- const frameworkName = answers.framework || answers.deploymentConfig;
984
- const frameworkVersion = answers.frameworkVersion;
985
- let frameworkConfig = null;
986
-
987
- if (frameworkName && registryConfigManager.frameworkRegistry) {
988
- const frameworkVersions = registryConfigManager.frameworkRegistry[frameworkName];
989
- if (frameworkVersions) {
990
- if (frameworkVersion && frameworkVersions[frameworkVersion]) {
991
- frameworkConfig = frameworkVersions[frameworkVersion];
992
- } else {
993
- // Fall back to latest version for Triton and other non-versioned lookups
994
- const versions = Object.keys(frameworkVersions).sort((a, b) =>
995
- b.localeCompare(a, undefined, { numeric: true })
996
- );
997
- if (versions.length > 0) {
998
- frameworkConfig = frameworkVersions[versions[0]];
999
- }
1000
- }
1001
- }
1002
- }
1003
-
1004
- // Resolve the model config (exact match or pattern match)
1005
- let modelConfig = null;
1006
- if (answers.modelName && registryConfigManager.modelRegistry) {
1007
- modelConfig = _findModelConfig(answers.modelName, registryConfigManager);
1008
- }
1009
-
1010
- // Layer 1: catalog defaults (Image_Entry defaults.envVars)
1011
- const catalogDefaults = frameworkConfig?.envVars || {};
1012
-
1013
- // Layer 2: framework profile envVars
1014
- let frameworkProfileEnvVars = {};
1015
- if (answers.frameworkProfile && frameworkConfig?.profiles) {
1016
- const profile = frameworkConfig.profiles[answers.frameworkProfile];
1017
- if (profile?.envVars) {
1018
- frameworkProfileEnvVars = profile.envVars;
1019
- }
1020
- }
1021
-
1022
- // Layer 3: model entry envVars
1023
- const modelEntryEnvVars = modelConfig?.envVars || {};
1024
-
1025
- // Layer 4: model profile envVars
1026
- let modelProfileEnvVars = {};
1027
- if (answers.modelProfile && modelConfig?.profiles) {
1028
- const profile = modelConfig.profiles[answers.modelProfile];
1029
- if (profile?.envVars) {
1030
- modelProfileEnvVars = profile.envVars;
1031
- }
1032
- }
1033
-
1034
- // Layer 5: CLI overrides (captured above)
1035
-
1036
- // Merge in precedence order: each layer overrides the previous
1037
- answers.envVars = {
1038
- ...catalogDefaults,
1039
- ...frameworkProfileEnvVars,
1040
- ...modelEntryEnvVars,
1041
- ...modelProfileEnvVars,
1042
- ...cliEnvVars
1043
- };
1044
- }
1045
-
1046
- /**
1047
- * Finds model configuration by exact match or glob-pattern match.
1048
- *
1049
- * @param {string} modelName - Model ID to look up
1050
- * @param {object} registryConfigManager - Registry configuration manager
1051
- * @returns {object|null} Model configuration or null
1052
- */
1053
- function _findModelConfig(modelName, registryConfigManager) {
1054
- if (!registryConfigManager?.modelRegistry) return null;
1055
-
1056
- // Exact match first
1057
- const exact = registryConfigManager.modelRegistry[modelName];
1058
- if (exact) return exact;
1059
-
1060
- // Pattern matching with glob-style wildcards
1061
- for (const [pattern, config] of Object.entries(registryConfigManager.modelRegistry)) {
1062
- if (pattern.includes('*')) {
1063
- const regex = new RegExp(`^${pattern.replace(/\*/g, '.*')}$`);
1064
- if (regex.test(modelName)) {
1065
- return config;
1066
- }
1067
- }
1068
- }
1069
-
1070
- return null;
1071
- }
1072
-
1073
687
  /**
1074
688
  * Generates Triton-specific files (Dockerfile, model repository structure).
1075
689
  *