Skip to content

Commit 4b741a2

Browse files
committed
Merge branch 'feature/custom-models-region-visibility' into 'develop'
feat(ui): hide Custom Models nav when deployed outside us-east-1 See merge request genaiic-reusable-assets/engagement-artifacts/genaiic-idp-accelerator!619
2 parents b74c6ae + 172378e commit 4b741a2

8 files changed

Lines changed: 219 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ SPDX-License-Identifier: MIT-0
2222

2323
### Fixed
2424

25+
- **Fixed** Nova 2.0 fine-tuning failure (`Invalid input error: Nova 2.0 doesn't support validation set`) — Nova 2.x models do not support validation datasets in the Bedrock `CreateModelCustomizationJob` API. The `finetuning_job_creator` Lambda and `ModelFinetuningService` now detect Nova 2.x models and skip the `validationDataConfig` parameter. Validation data is still generated and stored in S3 for internal metrics, but is no longer passed to the Bedrock API for these models. Added `model_capabilities` section to `finetuning_models.yaml` for config-driven control.
26+
- **Fixed** agentic extraction crash (`TypeError: unsupported format string passed to NoneType.__format__`) when table parsing stats contain `None` values for `avg_confidence` or `parse_success_rate`.
27+
- **Fixed** agentic extraction `map_table_to_schema` producing phantom empty rows from non-matching tables (e.g. account_summary rows prepended to transaction_details), causing list item ordering to be shifted by several positions.
2528
- **Error Analyzer model selection** — The agent was using the Chat Companion's model instead of its own configured model.
2629
- **Error Analyzer log processing** — Fixed early termination that stopped searching after the first Lambda function with errors; now searches all relevant log groups.
2730
- **Error Analyzer log truncation** — Fixed handling of long log messages to trim them rather than skip them entirely.

config_library/finetuning_models.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,27 @@ model_mappings:
5454
"amazon.nova-lite-v1:0": "amazon.nova-lite-v1:0:300k"
5555
"amazon.nova-pro-v1:0": "amazon.nova-pro-v1:0:300k"
5656

57+
# ---------------------------------------------------------------------------
58+
# Model Capabilities
59+
# ---------------------------------------------------------------------------
60+
# Some models have restrictions on what features they support during
61+
# fine-tuning. This section documents those restrictions.
62+
#
63+
# supports_validation: Whether the model supports a validation dataset
64+
# during fine-tuning. Nova 2.x models do NOT support validation sets.
65+
# When false, the validation data will still be generated for internal
66+
# metrics but will NOT be passed to the Bedrock CreateModelCustomizationJob API.
67+
model_capabilities:
68+
"amazon.nova-2-lite-v1:0":
69+
supports_validation: false
70+
"amazon.nova-2-pro-v1:0":
71+
supports_validation: false
72+
# Nova 1.x models support validation sets
73+
"amazon.nova-lite-v1:0":
74+
supports_validation: true
75+
"amazon.nova-pro-v1:0":
76+
supports_validation: true
77+
5778
# ---------------------------------------------------------------------------
5879
# Default Hyperparameters
5980
# ---------------------------------------------------------------------------

docs/custom-model-finetuning.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Custom Model Fine-tuning is available to **Admin** and **Author** roles. The Gra
3838
3939
## Prerequisites
4040

41+
- **Deployed in `us-east-1` region**: Custom Model Fine-Tuning is only available when the IDP Accelerator is deployed to the `us-east-1` AWS region. The Custom Models navigation link is automatically hidden in deployments to other regions.
4142
- An existing Test Set with:
4243
- At least 10 documents
4344
- At least 2 document classes

docs/nova-finetuning.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ This guide provides comprehensive step-by-step instructions for fine-tuning Amaz
2424

2525
## Prerequisites
2626

27+
> **Region Availability**: Nova fine-tuning is only available in the `us-east-1` AWS region. Ensure your IDP Accelerator stack is deployed to `us-east-1` to use this feature. The Custom Models UI is automatically hidden in deployments to other regions.
28+
2729
### AWS Setup
2830
Set up AWS CLI and credentials:
2931
```bash

lib/idp_common_pkg/idp_common/model_finetuning/service.py

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@
3434
{"id": "us.amazon.nova-2-pro-v1:0", "name": "Nova 2 Pro", "provider": "Amazon"},
3535
]
3636

37+
# Nova 2.x models do NOT support validation datasets during fine-tuning.
38+
# Passing a validation set to the Bedrock API for these models results in:
39+
# "Invalid input error: Nova 2.0 doesn't support validation set"
40+
_NOVA_2_MODEL_PREFIXES = ("amazon.nova-2-",)
41+
3742
# Module-level cache for supported models
3843
_supported_models_cache: Optional[List[Dict[str, str]]] = None
3944

@@ -355,6 +360,29 @@ def create_finetuning_job(
355360

356361
return result
357362

363+
@staticmethod
364+
def _is_nova_2_model(model_id: str) -> bool:
365+
"""
366+
Check if a model identifier refers to a Nova 2.x model.
367+
368+
Nova 2.x models have specific restrictions, such as not supporting
369+
validation datasets during fine-tuning.
370+
371+
Args:
372+
model_id: Model identifier (may include cross-region prefix or context window suffix)
373+
374+
Returns:
375+
True if the model is a Nova 2.x model
376+
"""
377+
# Strip cross-region prefix (e.g., "us.amazon.nova-2-lite-v1:0" -> "amazon.nova-2-lite-v1:0")
378+
normalized = model_id
379+
if "." in normalized and not normalized.startswith("arn:"):
380+
parts = normalized.split(".", 1)
381+
if len(parts[0]) <= 3 and parts[0].isalpha():
382+
normalized = parts[1]
383+
384+
return any(normalized.startswith(prefix) for prefix in _NOVA_2_MODEL_PREFIXES)
385+
358386
def _create_nova_job_params(
359387
self, config: FinetuningJobConfig, data_uris: Dict[str, str]
360388
) -> Dict[str, Any]:
@@ -389,10 +417,19 @@ def _create_nova_job_params(
389417
if config.output_uri:
390418
job_params["outputDataConfig"] = {"s3Uri": config.output_uri}
391419

420+
# Add validation data only if the model supports it.
421+
# Nova 2.x models do NOT support validation sets — passing one causes:
422+
# "Invalid input error: Nova 2.0 doesn't support validation set"
392423
if "validation_data_uri" in data_uris:
393-
job_params["validationDataConfig"] = {
394-
"validators": [{"s3Uri": data_uris["validation_data_uri"]}]
395-
}
424+
if self._is_nova_2_model(config.base_model):
425+
logger.info(
426+
f"Skipping validation data for Nova 2.x model '{config.base_model}' "
427+
"— model does not support validation sets"
428+
)
429+
else:
430+
job_params["validationDataConfig"] = {
431+
"validators": [{"s3Uri": data_uris["validation_data_uri"]}]
432+
}
396433

397434
if config.client_request_token:
398435
job_params["clientRequestToken"] = config.client_request_token

lib/idp_common_pkg/tests/unit/test_model_finetuning.py

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ def test_validate_nova_hyperparameters_invalid_learning_rate(self, service):
372372

373373
@pytest.mark.unit
374374
def test_create_nova_job_params(self, service):
375-
"""Test Nova job parameters creation."""
375+
"""Test Nova v1 job parameters creation includes validation data."""
376376
config = FinetuningJobConfig(
377377
base_model="us.amazon.nova-lite-v1:0",
378378
training_data_uri="s3://bucket/training.jsonl",
@@ -401,6 +401,72 @@ def test_create_nova_job_params(self, service):
401401
== "s3://bucket/validation.jsonl"
402402
)
403403

404+
@pytest.mark.unit
405+
def test_create_nova_2_job_params_skips_validation(self, service):
406+
"""Test Nova 2.x job parameters skip validation data.
407+
408+
Nova 2.0 does not support validation sets. Passing one causes:
409+
'Invalid input error: Nova 2.0 doesn't support validation set'
410+
"""
411+
config = FinetuningJobConfig(
412+
base_model="us.amazon.nova-2-lite-v1:0",
413+
training_data_uri="s3://bucket/training.jsonl",
414+
role_arn="arn:aws:iam::123456789012:role/BedrockRole",
415+
job_name="test-job",
416+
model_name="test-model",
417+
hyperparameters={"epochCount": "2"},
418+
)
419+
420+
data_uris = {
421+
"training_data_uri": "s3://bucket/training.jsonl",
422+
"validation_data_uri": "s3://bucket/validation.jsonl",
423+
}
424+
425+
params = service._create_nova_job_params(config, data_uris)
426+
427+
assert params["customizationType"] == "FINE_TUNING"
428+
assert params["baseModelIdentifier"] == "us.amazon.nova-2-lite-v1:0"
429+
assert params["trainingDataConfig"]["s3Uri"] == "s3://bucket/training.jsonl"
430+
# Validation data should NOT be included for Nova 2.x models
431+
assert "validationDataConfig" not in params
432+
433+
@pytest.mark.unit
434+
def test_create_nova_2_pro_job_params_skips_validation(self, service):
435+
"""Test Nova 2 Pro job parameters also skip validation data."""
436+
config = FinetuningJobConfig(
437+
base_model="amazon.nova-2-pro-v1:0",
438+
training_data_uri="s3://bucket/training.jsonl",
439+
role_arn="arn:aws:iam::123456789012:role/BedrockRole",
440+
job_name="test-job",
441+
model_name="test-model",
442+
)
443+
444+
data_uris = {
445+
"training_data_uri": "s3://bucket/training.jsonl",
446+
"validation_data_uri": "s3://bucket/validation.jsonl",
447+
}
448+
449+
params = service._create_nova_job_params(config, data_uris)
450+
451+
# Validation data should NOT be included for Nova 2.x models
452+
assert "validationDataConfig" not in params
453+
454+
@pytest.mark.unit
455+
def test_is_nova_2_model(self, service):
456+
"""Test _is_nova_2_model correctly identifies Nova 2.x models."""
457+
# Nova 2.x models (should return True)
458+
assert service._is_nova_2_model("amazon.nova-2-lite-v1:0") is True
459+
assert service._is_nova_2_model("amazon.nova-2-pro-v1:0") is True
460+
assert service._is_nova_2_model("us.amazon.nova-2-lite-v1:0") is True
461+
assert service._is_nova_2_model("us.amazon.nova-2-pro-v1:0") is True
462+
assert service._is_nova_2_model("amazon.nova-2-lite-v1:0:256k") is True
463+
464+
# Nova 1.x models (should return False)
465+
assert service._is_nova_2_model("amazon.nova-lite-v1:0") is False
466+
assert service._is_nova_2_model("amazon.nova-pro-v1:0") is False
467+
assert service._is_nova_2_model("us.amazon.nova-lite-v1:0") is False
468+
assert service._is_nova_2_model("us.amazon.nova-pro-v1:0") is False
469+
404470
@pytest.mark.unit
405471
def test_create_finetuning_job(self, service, mock_bedrock_client):
406472
"""Test creating a fine-tuning job."""

src/lambda/finetuning_job_creator/index.py

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def _load_finetuning_config() -> Dict[str, Any]:
4848
The configuration is cached after first load to avoid repeated S3 calls.
4949
5050
Returns:
51-
Dictionary containing model_mappings and default_hyperparameters
51+
Dictionary containing model_mappings, default_hyperparameters, and model_capabilities
5252
"""
5353
global _config_cache
5454

@@ -69,6 +69,12 @@ def _load_finetuning_config() -> Dict[str, Any]:
6969
"learningRate": "0.00001",
7070
"batchSize": "1",
7171
},
72+
"model_capabilities": {
73+
"amazon.nova-2-lite-v1:0": {"supports_validation": False},
74+
"amazon.nova-2-pro-v1:0": {"supports_validation": False},
75+
"amazon.nova-lite-v1:0": {"supports_validation": True},
76+
"amazon.nova-pro-v1:0": {"supports_validation": True},
77+
},
7278
}
7379

7480
# Try to load from S3
@@ -89,6 +95,9 @@ def _load_finetuning_config() -> Dict[str, Any]:
8995
"default_hyperparameters": config.get(
9096
"default_hyperparameters", default_config["default_hyperparameters"]
9197
),
98+
"model_capabilities": config.get(
99+
"model_capabilities", default_config["model_capabilities"]
100+
),
92101
}
93102
logger.info(f"Loaded fine-tuning config with {len(_config_cache['model_mappings'])} model mappings")
94103
return _config_cache
@@ -116,6 +125,51 @@ def _get_default_hyperparameters() -> Dict[str, str]:
116125
return config.get("default_hyperparameters", {})
117126

118127

128+
def _model_supports_validation(model_id: str) -> bool:
129+
"""
130+
Check if a model supports validation datasets during fine-tuning.
131+
132+
Nova 2.x models do NOT support validation sets. Passing a validation
133+
dataset to the Bedrock API for these models results in:
134+
"Invalid input error: Nova 2.0 doesn't support validation set"
135+
136+
Args:
137+
model_id: The model identifier (can be cross-region profile, standard ID, or mapped ID)
138+
139+
Returns:
140+
True if the model supports validation datasets, False otherwise.
141+
Defaults to True for unknown models (backward compatible).
142+
"""
143+
config = _load_finetuning_config()
144+
model_capabilities = config.get("model_capabilities", {})
145+
146+
# Strip cross-region prefix for lookup (e.g., "us.amazon.nova-2-lite-v1:0" -> "amazon.nova-2-lite-v1:0")
147+
normalized = model_id
148+
if "." in normalized and not normalized.startswith("arn:"):
149+
parts = normalized.split(".", 1)
150+
if len(parts[0]) <= 3 and parts[0].isalpha():
151+
normalized = parts[1]
152+
153+
# Strip context window suffix for lookup (e.g., "amazon.nova-2-lite-v1:0:256k" -> "amazon.nova-2-lite-v1:0")
154+
# Model capabilities are keyed by base model ID without context window suffix
155+
base_id = normalized
156+
# Count colons - base IDs have format "amazon.nova-2-lite-v1:0", mapped IDs have "amazon.nova-2-lite-v1:0:256k"
157+
colon_parts = base_id.split(":")
158+
if len(colon_parts) > 2:
159+
# Has context window suffix, strip it (keep only first two parts: "amazon.nova-2-lite-v1:0")
160+
base_id = ":".join(colon_parts[:2])
161+
162+
# Look up capabilities
163+
capabilities = model_capabilities.get(base_id, {})
164+
supports_validation = capabilities.get("supports_validation", True)
165+
166+
logger.info(
167+
f"Model '{model_id}' (base_id='{base_id}') supports_validation={supports_validation}"
168+
)
169+
170+
return supports_validation
171+
172+
119173
def _normalize_model_identifier(model_id: str) -> str:
120174
"""
121175
Normalize model identifier for Bedrock fine-tuning API.
@@ -212,13 +266,20 @@ def handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
212266
"hyperParameters": _get_default_hyperparameters(),
213267
}
214268

215-
# Add validation data if provided
216-
if validation_data_uri:
269+
# Add validation data if provided AND the model supports it.
270+
# Nova 2.x models do NOT support validation sets — passing one causes:
271+
# "Invalid input error: Nova 2.0 doesn't support validation set"
272+
if validation_data_uri and _model_supports_validation(base_model):
217273
job_params["validationDataConfig"] = {
218274
"validators": [
219275
{"s3Uri": validation_data_uri}
220276
]
221277
}
278+
elif validation_data_uri:
279+
logger.info(
280+
f"Skipping validation data for model '{base_model}' — "
281+
"model does not support validation sets"
282+
)
222283

223284
# Add output data config
224285
output_uri = f"s3://{FINETUNING_BUCKET}/finetuning/{job_id}/output/"

src/ui/src/components/genaiidp-layout/navigation.tsx

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,9 @@ const Navigation = ({
187187
return viewerNavItems;
188188
}, [items, isAdmin, isAuthor, isViewerOnly, isReviewerOnly]);
189189

190-
// Filter out Capacity Planning link if pattern is not Pattern-2
190+
// Filter out navigation items based on deployment context:
191+
// - Capacity Planning: hidden if pattern is not Pattern-2 or Unified
192+
// - Custom Models: hidden if deployed region is not us-east-1
191193
const filteredItems = useMemo(() => {
192194
const pattern = (settings?.IDPPattern as string | undefined)?.toLowerCase();
193195

@@ -201,29 +203,42 @@ const Navigation = ({
201203
pattern.includes('unified') ||
202204
/pattern[\s\-_]?2/.test(pattern); // Regex: "pattern" followed by optional separator, then "2"
203205

206+
// Custom Models is only available in us-east-1 (Nova fine-tuning region requirement)
207+
const deployedRegion = import.meta.env.VITE_AWS_REGION as string | undefined;
208+
const isCustomModelsSupported = deployedRegion === 'us-east-1';
209+
204210
// Debug logging (remove after testing)
205211
if (pattern) {
206212
console.log('[Navigation] IDPPattern detected:', settings.IDPPattern, '| Capacity Planning supported:', isCapacityPlanningSupported);
207213
}
214+
console.log('[Navigation] Region:', deployedRegion, '| Custom Models supported:', isCustomModelsSupported);
215+
216+
// Build list of items to hide from the Configuration section
217+
const hiddenConfigItems = new Set<string>();
218+
if (!isCapacityPlanningSupported) {
219+
hiddenConfigItems.add('Capacity Planning');
220+
}
221+
if (!isCustomModelsSupported) {
222+
hiddenConfigItems.add('Custom Models');
223+
}
208224

209-
if (isCapacityPlanningSupported) {
210-
// Show Capacity Planning for Pattern-2, Unified, or if pattern is unknown
225+
if (hiddenConfigItems.size === 0) {
211226
return baseItems;
212227
}
213228

214-
// Filter out Capacity Planning for Pattern 1 and Pattern 3
229+
// Filter out hidden items from Configuration section and top-level
215230
return baseItems
216231
.map((item) => {
217232
if (item.type === 'section' && item.text === 'Configuration') {
218233
const section = item as SideNavigationProps.Section;
219234
return {
220235
...item,
221-
items: section.items.filter((subItem) => (subItem as { text?: string }).text !== 'Capacity Planning'),
236+
items: section.items.filter((subItem) => !hiddenConfigItems.has((subItem as { text?: string }).text ?? '')),
222237
};
223238
}
224239
return item;
225240
})
226-
.filter((item) => (item as { text?: string }).text !== 'Capacity Planning'); // Also filter top-level if it exists
241+
.filter((item) => !hiddenConfigItems.has((item as { text?: string }).text ?? ''));
227242
}, [baseItems, settings?.IDPPattern]);
228243

229244
// Determine active link based on current path

0 commit comments

Comments
 (0)