@aws/ml-container-creator 0.13.4 → 0.15.0

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 (43) hide show
  1. package/README.md +23 -5
  2. package/config/parameter-schema-v2.json +32 -4
  3. package/infra/ci-harness/lib/ci-harness-stack.ts +13 -5
  4. package/infra/ci-harness/package-lock.json +122 -116
  5. package/infra/ci-harness/package.json +1 -1
  6. package/package.json +5 -3
  7. package/pyproject.toml +21 -0
  8. package/requirements.txt +19 -0
  9. package/servers/instance-sizer/index.js +72 -4
  10. package/servers/instance-sizer/lib/model-resolver.js +28 -2
  11. package/src/app.js +17 -0
  12. package/src/lib/bootstrap-command-handler.js +33 -23
  13. package/src/lib/config-loader.js +18 -0
  14. package/src/lib/config-manager.js +6 -1
  15. package/src/lib/dataset-slug.js +152 -0
  16. package/src/lib/generated/cli-options.js +9 -3
  17. package/src/lib/generated/parameter-matrix.js +14 -3
  18. package/src/lib/generated/validation-rules.js +1 -1
  19. package/src/lib/mcp-query-runner.js +6 -0
  20. package/src/lib/prompt-runner.js +5 -0
  21. package/src/lib/prompts/feature-prompts.js +1 -1
  22. package/src/lib/template-manager.js +0 -7
  23. package/src/lib/template-variable-resolver.js +51 -1
  24. package/src/lib/tune-config-state.js +14 -1
  25. package/templates/do/.adapter_helper.py +451 -0
  26. package/templates/do/.benchmark_writer.py +22 -0
  27. package/templates/do/.register_helper.py +1163 -0
  28. package/templates/do/.stage_helper.py +419 -0
  29. package/templates/do/.tune_helper.py +379 -65
  30. package/templates/do/__pycache__/.adapter_helper.cpython-312.pyc +0 -0
  31. package/templates/do/__pycache__/.benchmark_writer.cpython-312.pyc +0 -0
  32. package/templates/do/__pycache__/.register_helper.cpython-312.pyc +0 -0
  33. package/templates/do/__pycache__/.tune_helper.cpython-312.pyc +0 -0
  34. package/templates/do/adapter +427 -27
  35. package/templates/do/add-ic +85 -3
  36. package/templates/do/benchmark +173 -15
  37. package/templates/do/config +24 -0
  38. package/templates/do/lib/inference-component.sh +56 -3
  39. package/templates/do/lib/profile.sh +5 -0
  40. package/templates/do/register +552 -6
  41. package/templates/do/stage +91 -272
  42. package/templates/do/test +12 -2
  43. package/templates/do/tune +264 -12
@@ -35,6 +35,7 @@ _usage() {
35
35
  echo " add <name> --weights <s3-uri> Add a new LoRA adapter from S3"
36
36
  echo " add <name> --from-hub <hf-repo-id> Add a new LoRA adapter from HuggingFace Hub"
37
37
  echo " add <name> --from-tune [technique] Add adapter from do/tune output"
38
+ echo " add <name> --from-registry [arn] Add adapter from model registry"
38
39
  echo " list List all adapters on the endpoint"
39
40
  echo " remove <name> Remove an adapter"
40
41
  echo " update <name> --weights <new-s3-uri> Update adapter weights from S3"
@@ -43,12 +44,18 @@ _usage() {
43
44
  echo ""
44
45
  echo "Options:"
45
46
  echo " --help, -h Show this help message"
47
+ echo " --local Use local aws s3 cp instead of Processing Job (--from-tune)"
48
+ echo " --no-wait Submit Processing Job and return immediately (--from-tune)"
46
49
  echo ""
47
50
  echo "Examples:"
48
51
  echo " ./do/adapter add ectsum --weights s3://my-bucket/adapters/ectsum/adapter.tar.gz"
49
52
  echo " ./do/adapter add ectsum --from-hub predibase/llama-3.1-8b-ectsum"
50
53
  echo " ./do/adapter add tuned-sft --from-tune"
51
54
  echo " ./do/adapter add tuned-sft --from-tune sft"
55
+ echo " ./do/adapter add tuned-sft --from-tune --local"
56
+ echo " ./do/adapter add tuned-sft --from-tune --no-wait"
57
+ echo " ./do/adapter add my-adapter --from-registry"
58
+ echo " ./do/adapter add my-adapter --from-registry arn:aws:sagemaker:...:model-package/proj/2"
52
59
  echo " ./do/adapter list"
53
60
  echo " ./do/adapter remove ectsum"
54
61
  echo " ./do/adapter update ectsum --weights s3://my-bucket/adapters/ectsum-v2/adapter.tar.gz"
@@ -56,7 +63,7 @@ _usage() {
56
63
  echo ""
57
64
  echo "Adapter metadata is stored in do/adapters/<name>.conf"
58
65
  echo ""
59
- echo "Note: --weights, --from-hub, and --from-tune are mutually exclusive."
66
+ echo "Note: --weights, --from-hub, --from-tune, and --from-registry are mutually exclusive."
60
67
  }
61
68
 
62
69
  # ── Validate LoRA is enabled ──────────────────────────────────────────────────
@@ -367,6 +374,10 @@ _adapter_add() {
367
374
  local from_hub=""
368
375
  local from_tune=""
369
376
  local from_tune_technique=""
377
+ local from_registry=""
378
+ local registry_arn=""
379
+ local use_local=""
380
+ local no_wait=""
370
381
 
371
382
  # Parse add arguments
372
383
  shift # remove 'add' from args
@@ -400,10 +411,29 @@ _adapter_add() {
400
411
  shift
401
412
  fi
402
413
  ;;
414
+ --from-registry)
415
+ from_registry="true"
416
+ # Check if next argument is an ARN (starts with arn:)
417
+ if [ -n "${2:-}" ] && [[ "${2}" == arn:* ]]; then
418
+ registry_arn="$2"
419
+ shift 2
420
+ else
421
+ shift
422
+ fi
423
+ ;;
424
+ --local)
425
+ use_local="true"
426
+ shift
427
+ ;;
428
+ --no-wait)
429
+ no_wait="true"
430
+ shift
431
+ ;;
403
432
  --help|-h)
404
433
  echo "Usage: ./do/adapter add <name> --weights <s3-uri>"
405
434
  echo " ./do/adapter add <name> --from-hub <hf-repo-id>"
406
435
  echo " ./do/adapter add <name> --from-tune [technique]"
436
+ echo " ./do/adapter add <name> --from-registry [version-arn]"
407
437
  echo ""
408
438
  echo "Add a new LoRA adapter to the endpoint."
409
439
  echo ""
@@ -414,14 +444,23 @@ _adapter_add() {
414
444
  echo " --from-tune [technique] Use adapter output from do/tune"
415
445
  echo " Without technique: uses latest tune output"
416
446
  echo " With technique (e.g., sft, dpo): uses technique-specific output"
447
+ echo " --from-registry [arn] Add adapter from model registry"
448
+ echo " Without ARN: presents interactive selection"
449
+ echo " With ARN: adds directly using specified version ARN"
450
+ echo " --local Use local aws s3 cp instead of Processing Job (--from-tune only)"
451
+ echo " --no-wait Submit Processing Job and return immediately (--from-tune only)"
417
452
  echo ""
418
- echo "Note: --weights, --from-hub, and --from-tune are mutually exclusive."
453
+ echo "Note: --weights, --from-hub, --from-tune, and --from-registry are mutually exclusive."
419
454
  echo ""
420
455
  echo "Examples:"
421
456
  echo " ./do/adapter add ectsum --weights s3://bucket/adapters/ectsum/adapter.tar.gz"
422
457
  echo " ./do/adapter add ectsum --from-hub predibase/llama-3.1-8b-ectsum"
423
458
  echo " ./do/adapter add tuned-sft --from-tune"
424
459
  echo " ./do/adapter add tuned-sft --from-tune sft"
460
+ echo " ./do/adapter add tuned-sft --from-tune --local"
461
+ echo " ./do/adapter add tuned-sft --from-tune --no-wait"
462
+ echo " ./do/adapter add my-adapter --from-registry"
463
+ echo " ./do/adapter add my-adapter --from-registry arn:aws:sagemaker:...:model-package/proj/2"
425
464
  exit 0
426
465
  ;;
427
466
  -*)
@@ -429,6 +468,7 @@ _adapter_add() {
429
468
  echo " Usage: ./do/adapter add <name> --weights <s3-uri>"
430
469
  echo " ./do/adapter add <name> --from-hub <hf-repo-id>"
431
470
  echo " ./do/adapter add <name> --from-tune [technique]"
471
+ echo " ./do/adapter add <name> --from-registry [version-arn]"
432
472
  exit 1
433
473
  ;;
434
474
  *)
@@ -439,6 +479,7 @@ _adapter_add() {
439
479
  echo " Usage: ./do/adapter add <name> --weights <s3-uri>"
440
480
  echo " ./do/adapter add <name> --from-hub <hf-repo-id>"
441
481
  echo " ./do/adapter add <name> --from-tune [technique]"
482
+ echo " ./do/adapter add <name> --from-registry [version-arn]"
442
483
  exit 1
443
484
  fi
444
485
  shift
@@ -460,46 +501,97 @@ _adapter_add() {
460
501
  [ -n "${weights_uri}" ] && source_count=$((source_count + 1))
461
502
  [ -n "${from_hub}" ] && source_count=$((source_count + 1))
462
503
  [ -n "${from_tune}" ] && source_count=$((source_count + 1))
504
+ [ -n "${from_registry}" ] && source_count=$((source_count + 1))
463
505
 
464
506
  if [ "${source_count}" -gt 1 ]; then
465
- echo "❌ --weights, --from-hub, and --from-tune are mutually exclusive"
507
+ echo "❌ --weights, --from-hub, --from-tune, and --from-registry are mutually exclusive"
466
508
  echo ""
467
509
  echo " Use one of:"
468
510
  echo " ./do/adapter add ${adapter_name} --weights <s3-uri>"
469
511
  echo " ./do/adapter add ${adapter_name} --from-hub <hf-repo-id>"
470
512
  echo " ./do/adapter add ${adapter_name} --from-tune [technique]"
513
+ echo " ./do/adapter add ${adapter_name} --from-registry [version-arn]"
471
514
  exit 1
472
515
  fi
473
516
 
474
517
  if [ "${source_count}" -eq 0 ]; then
475
- echo "❌ One of --weights, --from-hub, or --from-tune is required"
518
+ echo "❌ One of --weights, --from-hub, --from-tune, or --from-registry is required"
476
519
  echo " Usage: ./do/adapter add <name> --weights <s3-uri>"
477
520
  echo " ./do/adapter add <name> --from-hub <hf-repo-id>"
478
521
  echo " ./do/adapter add <name> --from-tune [technique]"
522
+ echo " ./do/adapter add <name> --from-registry [version-arn]"
479
523
  exit 1
480
524
  fi
481
525
 
482
526
  # ── Resolve --from-tune to weights_uri ────────────────────────────────
483
527
  if [ -n "${from_tune}" ]; then
484
528
  if [ -n "${from_tune_technique}" ]; then
485
- # Technique-specific: read TUNE_ADAPTER_PATH_<TECHNIQUE>
486
- local technique_upper
487
- technique_upper=$(echo "${from_tune_technique}" | tr '[:lower:]' '[:upper:]')
488
- local tune_var="TUNE_ADAPTER_PATH_${technique_upper}"
489
- local tune_path="${!tune_var:-}"
490
-
491
- if [ -z "${tune_path}" ]; then
492
- echo "❌ No adapter output found for technique: ${from_tune_technique}"
493
- echo ""
494
- echo " ${tune_var} is not set in do/config."
495
- echo ""
496
- echo " Run a tune job first:"
497
- echo " ./do/tune --technique ${from_tune_technique} --dataset <source>"
498
- exit 1
499
- fi
529
+ # Check if technique contains a hyphen — may be technique-dataset compound
530
+ if [[ "${from_tune_technique}" == *-* ]]; then
531
+ # Try compound key: TUNE_ADAPTER_PATH_<TECHNIQUE>_<SLUG>
532
+ local compound_technique="${from_tune_technique%%-*}"
533
+ local compound_slug="${from_tune_technique#*-}"
534
+ local compound_technique_upper
535
+ compound_technique_upper=$(echo "${compound_technique}" | tr '[:lower:]' '[:upper:]')
536
+ local compound_slug_upper
537
+ compound_slug_upper=$(echo "${compound_slug}" | tr '[:lower:]' '[:upper:]' | sed 's/-/_/g')
538
+ local compound_var="TUNE_ADAPTER_PATH_${compound_technique_upper}_${compound_slug_upper}"
539
+ local compound_path="${!compound_var:-}"
540
+
541
+ if [ -n "${compound_path}" ]; then
542
+ weights_uri="${compound_path}"
543
+ echo "📦 Using tune adapter output for technique '${compound_technique}' dataset '${compound_slug}': ${weights_uri}"
544
+ else
545
+ # Fallback: try as technique-only
546
+ local technique_upper
547
+ technique_upper=$(echo "${from_tune_technique}" | tr '[:lower:]' '[:upper:]' | sed 's/-/_/g')
548
+ local tune_var="TUNE_ADAPTER_PATH_${technique_upper}"
549
+ local tune_path="${!tune_var:-}"
550
+
551
+ if [ -n "${tune_path}" ]; then
552
+ echo "⚠️ ${compound_var} not found, falling back to ${tune_var}"
553
+ weights_uri="${tune_path}"
554
+ echo "📦 Using tune adapter output for technique '${from_tune_technique}': ${weights_uri}"
555
+ else
556
+ # Try the technique part alone as final fallback
557
+ local fallback_var="TUNE_ADAPTER_PATH_${compound_technique_upper}"
558
+ local fallback_path="${!fallback_var:-}"
559
+
560
+ if [ -n "${fallback_path}" ]; then
561
+ echo "⚠️ ${compound_var} not found, falling back to ${fallback_var}"
562
+ weights_uri="${fallback_path}"
563
+ echo "📦 Using tune adapter output for technique '${compound_technique}': ${weights_uri}"
564
+ else
565
+ echo "❌ No adapter output found for: ${from_tune_technique}"
566
+ echo ""
567
+ echo " Tried: ${compound_var}, ${fallback_var}"
568
+ echo ""
569
+ echo " Run a tune job first:"
570
+ echo " ./do/tune --technique ${compound_technique} --dataset <source>"
571
+ exit 1
572
+ fi
573
+ fi
574
+ fi
575
+ else
576
+ # Technique-only: read TUNE_ADAPTER_PATH_<TECHNIQUE>
577
+ local technique_upper
578
+ technique_upper=$(echo "${from_tune_technique}" | tr '[:lower:]' '[:upper:]')
579
+ local tune_var="TUNE_ADAPTER_PATH_${technique_upper}"
580
+ local tune_path="${!tune_var:-}"
581
+
582
+ if [ -z "${tune_path}" ]; then
583
+ echo "❌ No adapter output found for technique: ${from_tune_technique}"
584
+ echo ""
585
+ echo " ${tune_var} is not set in do/config."
586
+ echo ""
587
+ echo " Run a tune job first:"
588
+ echo " ./do/tune --technique ${from_tune_technique} --dataset <source>"
589
+ exit 1
590
+ fi
500
591
 
501
- weights_uri="${tune_path}"
502
- echo "📦 Using tune adapter output for technique '${from_tune_technique}': ${weights_uri}"
592
+ weights_uri="${tune_path}"
593
+ echo "📦 Using tune adapter output for technique '${from_tune_technique}': ${weights_uri}"
594
+ fi
503
595
  else
504
596
  # No technique: read TUNE_OUTPUT_PATH_LATEST and verify type
505
597
  if [ -z "${TUNE_OUTPUT_PATH_LATEST:-}" ]; then
@@ -529,6 +621,95 @@ _adapter_add() {
529
621
  fi
530
622
  echo ""
531
623
 
624
+ # ── Route to Processing Job helper (default) or local path ────────
625
+ if [ -z "${use_local}" ]; then
626
+ # Default: use Processing Job via .adapter_helper.py
627
+ echo "🚀 Submitting Processing Job to stage adapter..."
628
+ echo ""
629
+
630
+ # Resolve execution role
631
+ local exec_role="${EXECUTION_ROLE_ARN:-}"
632
+ if [ -z "${exec_role}" ]; then
633
+ exec_role="${ROLE_ARN:-}"
634
+ fi
635
+ if [ -z "${exec_role}" ]; then
636
+ exec_role="${SAGEMAKER_ROLE_ARN:-}"
637
+ fi
638
+ if [ -z "${exec_role}" ]; then
639
+ echo "❌ No execution role found."
640
+ echo ""
641
+ echo " Run 'ml-container-creator bootstrap' to set up your profile,"
642
+ echo " or set ROLE_ARN / EXECUTION_ROLE_ARN in do/config."
643
+ exit 1
644
+ fi
645
+
646
+ # Resolve S3 bucket
647
+ local adapter_bucket="${ADAPTER_S3_BUCKET:-}"
648
+ if [ -z "${adapter_bucket}" ]; then
649
+ local account_id
650
+ account_id=$(aws sts get-caller-identity --query Account --output text 2>/dev/null || echo "")
651
+ if [ -z "${account_id}" ]; then
652
+ echo "❌ Could not determine AWS account ID."
653
+ echo " Ensure AWS credentials are configured."
654
+ exit 4
655
+ fi
656
+ adapter_bucket="mlcc-adapters-${account_id}-${AWS_REGION}"
657
+ fi
658
+
659
+ # Build helper args
660
+ local helper_args=(
661
+ "stage-from-tune"
662
+ "--training-output-s3-uri" "${weights_uri}"
663
+ "--adapter-name" "${adapter_name}"
664
+ "--bucket" "${adapter_bucket}"
665
+ "--project" "${PROJECT_NAME}"
666
+ "--role-arn" "${exec_role}"
667
+ "--region" "${AWS_REGION}"
668
+ )
669
+ if [ -n "${no_wait}" ]; then
670
+ helper_args+=("--no-wait")
671
+ fi
672
+
673
+ # Invoke the Python helper
674
+ local helper_output
675
+ if ! helper_output=$(python3 "${SCRIPT_DIR}/.adapter_helper.py" "${helper_args[@]}" 2>/dev/null); then
676
+ echo "❌ Processing Job failed. See error above."
677
+ exit 1
678
+ fi
679
+
680
+ # Parse JSON output from helper (extract only the JSON line, skip any log noise)
681
+ local json_line
682
+ json_line=$(echo "${helper_output}" | grep -E '^\{' | tail -1)
683
+ local job_status
684
+ job_status=$(echo "${json_line}" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('status',''))" 2>/dev/null || echo "")
685
+
686
+ if [ "${job_status}" = "Completed" ] || [ "${job_status}" = "InProgress" ]; then
687
+ echo "${json_line}"
688
+ # Extract adapter_s3_uri for downstream use
689
+ local staged_adapter_uri
690
+ staged_adapter_uri=$(echo "${json_line}" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('adapter_s3_uri',''))" 2>/dev/null || echo "")
691
+
692
+ if [ -n "${no_wait}" ]; then
693
+ echo ""
694
+ echo "✅ Processing Job submitted. Check status with:"
695
+ echo " python3 ${SCRIPT_DIR}/.adapter_helper.py status --job-name <job-name>"
696
+ echo ""
697
+ echo " Once complete, re-run without --no-wait to register the adapter."
698
+ exit 0
699
+ fi
700
+
701
+ # Update weights_uri to point to the staged adapter
702
+ weights_uri="${staged_adapter_uri}"
703
+ echo ""
704
+ echo "✅ Adapter staged via Processing Job: ${weights_uri}"
705
+ else
706
+ echo "❌ Unexpected status from Processing Job helper: ${job_status}"
707
+ echo " Output: ${helper_output}"
708
+ exit 1
709
+ fi
710
+ else
711
+ # ── --local flag: Package tune artifacts locally (original behavior) ──
712
+
532
713
  # ── Package tune artifacts as tar.gz if needed ────────────────────
533
714
  # Tune output is an S3 path that may be:
534
715
  # 1. Already a tar.gz file (s3://...adapter.tar.gz) → use directly
@@ -677,6 +858,137 @@ _adapter_add() {
677
858
  weights_uri="${s3_tar_path}"
678
859
  fi
679
860
  echo ""
861
+ fi # end --local else branch
862
+ fi
863
+
864
+ # ── Resolve --from-registry to weights_uri ────────────────────────────
865
+ if [ -n "${from_registry}" ]; then
866
+ if [ -z "${registry_arn}" ]; then
867
+ # Interactive mode: query registry and present selection (AC-4.2)
868
+ # Check if stdin is a TTY (AC-4.4)
869
+ if [ ! -t 0 ]; then
870
+ echo "Error: --from-registry requires an explicit version ARN in non-interactive mode."
871
+ echo "Usage: ./do/adapter add <name> --from-registry <version-arn>"
872
+ exit 1
873
+ fi
874
+
875
+ echo "📦 Querying model registry for available adapters..."
876
+ echo ""
877
+
878
+ local adapters_json
879
+ adapters_json=$(python3 "${SCRIPT_DIR}/.register_helper.py" list-adapters \
880
+ --project-name "${PROJECT_NAME}" \
881
+ --region "${AWS_REGION}" 2>/dev/null)
882
+
883
+ # Extract JSON line (filter out any non-JSON noise)
884
+ local json_line
885
+ json_line=$(echo "${adapters_json}" | grep -E '^\{' | tail -1)
886
+
887
+ if [ -z "${json_line}" ]; then
888
+ echo "❌ Failed to query model registry."
889
+ echo " Check that your AWS credentials are configured and the project has registered adapters."
890
+ exit 1
891
+ fi
892
+
893
+ # Parse adapter count
894
+ local adapter_count
895
+ adapter_count=$(echo "${json_line}" | python3 -c "import sys,json; data=json.loads(sys.stdin.read()); print(len(data.get('adapters',[])))" 2>/dev/null || echo "0")
896
+
897
+ if [ "${adapter_count}" -eq 0 ]; then
898
+ echo "❌ No adapters found in the model registry for project: ${PROJECT_NAME}"
899
+ echo ""
900
+ echo " Register an adapter first with: ./do/register (after do/tune)"
901
+ echo " Or use --weights or --from-hub to add an adapter manually."
902
+ exit 1
903
+ fi
904
+
905
+ # Display selection menu
906
+ echo "Available adapters in registry (${adapter_count} found):"
907
+ echo ""
908
+ printf ' %-4s%-10s%-12s%-20s%s\n' "#" "VERSION" "TECHNIQUE" "CREATED" "PARENT MODEL"
909
+
910
+ local i=0
911
+ while [ "${i}" -lt "${adapter_count}" ]; do
912
+ local version technique created_at parent_arn
913
+ version=$(echo "${json_line}" | python3 -c "import sys,json; data=json.loads(sys.stdin.read()); print(data['adapters'][${i}].get('version','?'))" 2>/dev/null)
914
+ technique=$(echo "${json_line}" | python3 -c "import sys,json; data=json.loads(sys.stdin.read()); print(data['adapters'][${i}].get('tuneTechnique','?'))" 2>/dev/null)
915
+ created_at=$(echo "${json_line}" | python3 -c "import sys,json; data=json.loads(sys.stdin.read()); t=data['adapters'][${i}].get('createdAt',''); print(t[:10] if t else '?')" 2>/dev/null)
916
+ parent_arn=$(echo "${json_line}" | python3 -c "import sys,json; data=json.loads(sys.stdin.read()); a=data['adapters'][${i}].get('parentModelVersionArn',''); print(a.split('/')[-2]+'/'+a.split('/')[-1] if '/' in a else a[:40])" 2>/dev/null)
917
+
918
+ local num=$((i + 1))
919
+ printf ' %-4s%-10s%-12s%-20s%s\n' "${num}" "v${version}" "${technique}" "${created_at}" "${parent_arn}"
920
+ i=$((i + 1))
921
+ done
922
+
923
+ echo ""
924
+ echo -n "Select adapter (1-${adapter_count}): "
925
+ read -r selection
926
+
927
+ # Validate selection
928
+ if ! echo "${selection}" | grep -qE '^[0-9]+$'; then
929
+ echo "❌ Invalid selection: ${selection}"
930
+ exit 1
931
+ fi
932
+ if [ "${selection}" -lt 1 ] || [ "${selection}" -gt "${adapter_count}" ]; then
933
+ echo "❌ Selection out of range. Choose 1-${adapter_count}."
934
+ exit 1
935
+ fi
936
+
937
+ # Get the ARN of the selected adapter
938
+ local sel_idx=$((selection - 1))
939
+ registry_arn=$(echo "${json_line}" | python3 -c "import sys,json; data=json.loads(sys.stdin.read()); print(data['adapters'][${sel_idx}]['arn'])" 2>/dev/null)
940
+
941
+ if [ -z "${registry_arn}" ]; then
942
+ echo "❌ Failed to retrieve ARN for selected adapter."
943
+ exit 1
944
+ fi
945
+
946
+ echo ""
947
+ echo "✅ Selected: ${registry_arn}"
948
+ echo ""
949
+ fi
950
+
951
+ # Direct mode (AC-4.3): use the provided or selected ARN to get version details
952
+ echo "📦 Retrieving adapter details from registry: ${registry_arn}"
953
+ echo ""
954
+
955
+ local version_json
956
+ version_json=$(python3 "${SCRIPT_DIR}/.register_helper.py" get-version \
957
+ --arn "${registry_arn}" \
958
+ --region "${AWS_REGION}" 2>/dev/null)
959
+
960
+ local version_line
961
+ version_line=$(echo "${version_json}" | grep -E '^\{' | tail -1)
962
+
963
+ if [ -z "${version_line}" ]; then
964
+ echo "❌ Failed to get version details for: ${registry_arn}"
965
+ echo ""
966
+ echo " Check that the ARN is correct and you have sagemaker:DescribeModelPackage permission."
967
+ echo " Run ./do/adapter list to see available registry adapters."
968
+ exit 1
969
+ fi
970
+
971
+ # Check for error in response
972
+ local version_error
973
+ version_error=$(echo "${version_line}" | python3 -c "import sys,json; data=json.loads(sys.stdin.read()); print(data.get('error',''))" 2>/dev/null || echo "")
974
+ if [ -n "${version_error}" ]; then
975
+ echo "❌ Registry error: ${version_error}"
976
+ exit 1
977
+ fi
978
+
979
+ # Extract model data URL (weights path)
980
+ weights_uri=$(echo "${version_line}" | python3 -c "import sys,json; data=json.loads(sys.stdin.read()); print(data.get('modelDataUrl',''))" 2>/dev/null || echo "")
981
+
982
+ if [ -z "${weights_uri}" ]; then
983
+ echo "❌ No model data URL found for registry version: ${registry_arn}"
984
+ echo ""
985
+ echo " The registered adapter does not have a model data URL."
986
+ echo " Use --weights with an explicit S3 URI instead."
987
+ exit 1
988
+ fi
989
+
990
+ echo "✅ Resolved adapter weights from registry: ${weights_uri}"
991
+ echo ""
680
992
  fi
681
993
 
682
994
  # ── Validate HF repo ID format (if --from-hub) ───────────────────────
@@ -705,7 +1017,7 @@ _adapter_add() {
705
1017
  fi
706
1018
 
707
1019
  # ── Validate S3 URI format (only when --weights is explicitly used) ──
708
- if [ -n "${weights_uri}" ] && [ -z "${from_hub}" ] && [ -z "${from_tune}" ]; then
1020
+ if [ -n "${weights_uri}" ] && [ -z "${from_hub}" ] && [ -z "${from_tune}" ] && [ -z "${from_registry}" ]; then
709
1021
  if ! echo "${weights_uri}" | grep -qE '^s3://.*\.tar\.gz$'; then
710
1022
  echo "❌ Invalid S3 URI: ${weights_uri}"
711
1023
  echo ""
@@ -734,6 +1046,9 @@ _adapter_add() {
734
1046
  elif [ -n "${from_tune}" ]; then
735
1047
  echo " Source: do/tune output"
736
1048
  echo " Weights: ${weights_uri}"
1049
+ elif [ -n "${from_registry}" ]; then
1050
+ echo " Source: Model Registry (${registry_arn})"
1051
+ echo " Weights: ${weights_uri}"
737
1052
  else
738
1053
  echo " Weights: ${weights_uri}"
739
1054
  fi
@@ -834,9 +1149,24 @@ EOF
834
1149
 
835
1150
  # Add tune-specific metadata if --from-tune was used
836
1151
  if [ -n "${from_tune}" ]; then
1152
+ local tune_technique_meta="${from_tune_technique:-latest}"
1153
+ local tune_dataset_meta=""
1154
+ if [ -n "${from_tune_technique}" ] && [[ "${from_tune_technique}" == *-* ]]; then
1155
+ tune_technique_meta="${from_tune_technique%%-*}"
1156
+ tune_dataset_meta="${from_tune_technique#*-}"
1157
+ fi
837
1158
  cat >> "${SCRIPT_DIR}/adapters/${adapter_name}.conf" <<EOF
838
1159
  export ADAPTER_SOURCE="tune"
839
- export ADAPTER_TUNE_TECHNIQUE="${from_tune_technique:-latest}"
1160
+ export ADAPTER_TUNE_TECHNIQUE="${tune_technique_meta}"
1161
+ export ADAPTER_TUNE_DATASET="${tune_dataset_meta}"
1162
+ EOF
1163
+ fi
1164
+
1165
+ # Add registry-specific metadata if --from-registry was used
1166
+ if [ -n "${from_registry}" ]; then
1167
+ cat >> "${SCRIPT_DIR}/adapters/${adapter_name}.conf" <<EOF
1168
+ export ADAPTER_SOURCE="registry"
1169
+ export ADAPTER_REGISTRY_ARN="${registry_arn}"
840
1170
  EOF
841
1171
  fi
842
1172
 
@@ -851,6 +1181,8 @@ EOF
851
1181
  echo " Source: HuggingFace Hub (${from_hub})"
852
1182
  elif [ -n "${from_tune}" ]; then
853
1183
  echo " Source: do/tune (${from_tune_technique:-latest})"
1184
+ elif [ -n "${from_registry}" ]; then
1185
+ echo " Source: Model Registry (${registry_arn})"
854
1186
  fi
855
1187
  echo " Created: ${created_at}"
856
1188
  echo ""
@@ -945,7 +1277,31 @@ _adapter_list() {
945
1277
  ownership=" (external)"
946
1278
  fi
947
1279
 
948
- output_lines="${output_lines}$(printf '%-14s%-12s%s%s' "${display_name}" "${status}" "${weights_url}" "${ownership}")\n"
1280
+ # Check for tuning metadata in conf file
1281
+ local tune_info=""
1282
+ if [ -d "${SCRIPT_DIR}/adapters" ]; then
1283
+ for conf_file in "${SCRIPT_DIR}"/adapters/*.conf; do
1284
+ [ -f "${conf_file}" ] || continue
1285
+ local conf_ic
1286
+ conf_ic=$(grep "^export ADAPTER_IC_NAME=" "${conf_file}" 2>/dev/null | sed 's/^export ADAPTER_IC_NAME="//' | sed 's/"$//' || echo "")
1287
+ if [ "${conf_ic}" = "${ic_name}" ]; then
1288
+ local conf_technique
1289
+ conf_technique=$(grep "^export ADAPTER_TUNE_TECHNIQUE=" "${conf_file}" 2>/dev/null | sed 's/^export ADAPTER_TUNE_TECHNIQUE="//' | sed 's/"$//' || echo "")
1290
+ local conf_dataset
1291
+ conf_dataset=$(grep "^export ADAPTER_TUNE_DATASET=" "${conf_file}" 2>/dev/null | sed 's/^export ADAPTER_TUNE_DATASET="//' | sed 's/"$//' || echo "")
1292
+ if [ -n "${conf_technique}" ]; then
1293
+ if [ -n "${conf_dataset}" ]; then
1294
+ tune_info=" (from tune: ${conf_technique} / ${conf_dataset})"
1295
+ else
1296
+ tune_info=" (from tune: ${conf_technique})"
1297
+ fi
1298
+ fi
1299
+ break
1300
+ fi
1301
+ done
1302
+ fi
1303
+
1304
+ output_lines="${output_lines}$(printf '%-14s%-12s%s%s%s' "${display_name}" "${status}" "${weights_url}" "${ownership}" "${tune_info}")\n"
949
1305
  found_adapters=$((found_adapters + 1))
950
1306
  done
951
1307
 
@@ -953,12 +1309,56 @@ _adapter_list() {
953
1309
  echo "No adapters found on this endpoint."
954
1310
  echo ""
955
1311
  echo "Add one with: ./do/adapter add <name> --weights <s3-uri>"
1312
+ fi
1313
+
1314
+ if [ "${found_adapters}" -gt 0 ]; then
1315
+ # ── Print table ───────────────────────────────────────────────────────
1316
+ printf '%-14s%-12s%s\n' "NAME" "STATUS" "WEIGHTS"
1317
+ echo -e "${output_lines}" | sed '$ { /^$/d; }'
1318
+ fi
1319
+
1320
+ # ── Show registry adapters (isAdapter=true) alongside local ones ──────
1321
+ echo ""
1322
+ echo "📦 Registry adapters:"
1323
+ echo ""
1324
+
1325
+ local registry_json
1326
+ registry_json=$(python3 "${SCRIPT_DIR}/.register_helper.py" list-adapters \
1327
+ --project-name "${PROJECT_NAME}" \
1328
+ --region "${AWS_REGION}" 2>/dev/null || echo "")
1329
+
1330
+ local registry_line
1331
+ registry_line=$(echo "${registry_json}" | grep -E '^\{' | tail -1)
1332
+
1333
+ if [ -z "${registry_line}" ]; then
1334
+ echo " (could not query registry — check AWS credentials)"
956
1335
  return 0
957
1336
  fi
958
1337
 
959
- # ── Print table ───────────────────────────────────────────────────────
960
- printf '%-14s%-12s%s\n' "NAME" "STATUS" "WEIGHTS"
961
- echo -e "${output_lines}" | sed '$ { /^$/d; }'
1338
+ local registry_count
1339
+ registry_count=$(echo "${registry_line}" | python3 -c "import sys,json; data=json.loads(sys.stdin.read()); print(len(data.get('adapters',[])))" 2>/dev/null || echo "0")
1340
+
1341
+ if [ "${registry_count}" -eq 0 ]; then
1342
+ echo " (none found)"
1343
+ return 0
1344
+ fi
1345
+
1346
+ printf ' %-10s%-12s%-14s%s\n' "VERSION" "TECHNIQUE" "CREATED" "PARENT MODEL"
1347
+
1348
+ local ri=0
1349
+ while [ "${ri}" -lt "${registry_count}" ]; do
1350
+ local rv rt rc rp
1351
+ rv=$(echo "${registry_line}" | python3 -c "import sys,json; data=json.loads(sys.stdin.read()); print(data['adapters'][${ri}].get('version','?'))" 2>/dev/null)
1352
+ rt=$(echo "${registry_line}" | python3 -c "import sys,json; data=json.loads(sys.stdin.read()); print(data['adapters'][${ri}].get('tuneTechnique','?'))" 2>/dev/null)
1353
+ rc=$(echo "${registry_line}" | python3 -c "import sys,json; data=json.loads(sys.stdin.read()); t=data['adapters'][${ri}].get('createdAt',''); print(t[:10] if t else '?')" 2>/dev/null)
1354
+ rp=$(echo "${registry_line}" | python3 -c "import sys,json; data=json.loads(sys.stdin.read()); a=data['adapters'][${ri}].get('parentModelVersionArn',''); print(a.split('/')[-2]+'/'+a.split('/')[-1] if '/' in a else a[:40])" 2>/dev/null)
1355
+
1356
+ printf ' %-10s%-12s%-14s%s\n' "v${rv}" "${rt}" "${rc}" "${rp}"
1357
+ ri=$((ri + 1))
1358
+ done
1359
+
1360
+ echo ""
1361
+ echo "Add from registry: ./do/adapter add <name> --from-registry [version-arn]"
962
1362
  }
963
1363
 
964
1364
  _adapter_remove() {