Setting the file. One moment.
Anthropic Model Recommendation · Agent Advisor · aws/agent-toolkit-for-aws · Skills Docs
ContentsBack to the top of the page Add Capabilities
81
Creating Amazon Aurora Db Cluster With Instances
104
Routing Traffic With Route53 And CloudFront
Resilience Program Design
Creating API Gateway Stage
def _feature_assessment
— line 376
This file
Number 23.56
Position 56 of 81
Type Python
Size 28 KB
Lines 776 scripts/ anthropic_model_recommendation.py
Python · 775 lines · 28 KB
"mantle_openai_responses"
,
16 "runtime_converse" ,
17 "runtime_invoke" ,
18 }
19 ANTHROPIC_PATHS = (
20 "mantle_messages" ,
21 "runtime_converse" ,
22 "runtime_invoke" ,
23 )
24 # Sources this module can genuinely reason about. `none` and `unknown` belong here: a source with no
25 # detected provider is treated as Bedrock-native and gets the Anthropic pool. Any OTHER provider
26 # routed here (azure_openai, google_genai, bedrock) still gets a recommendation, but carries a
27 # `provider_module_pending` [BLOCKS] finding so it stays provisional until that provider's own module
28 # exists. The orchestrator sends everything non-OpenAI here, so this set is the real classification.
29 ANTHROPIC_POOL = frozenset ({ "anthropic" , "none" , "unknown" })
30
31 _PRIORITY_ORDER = {
32 "quality" : [ "claude_opus_4_8" , "claude_sonnet_5" , "claude_haiku_4_5" ],
33 "balanced" : [ "claude_sonnet_5" , "claude_opus_4_8" , "claude_haiku_4_5" ],
34 "speed" : [ "claude_haiku_4_5" , "claude_sonnet_5" , "claude_opus_4_8" ],
35 "cost" : [ "claude_haiku_4_5" , "claude_sonnet_5" , "claude_opus_4_8" ],
36 "unknown" : [ "claude_sonnet_5" , "claude_opus_4_8" , "claude_haiku_4_5" ],
37 }
38
39 _FEATURE_ORDER = {
40 "agentic" : [
41 "claude_sonnet_5" ,
42 "claude_opus_4_8" ,
43 "claude_haiku_4_5" ,
44 ],
45 }
46
47 _CAPABILITY_ALIASES = { "multimodal" : "vision" }
48 _MODEL_CAPABILITIES = {
49 "extended_thinking" ,
50 "long_context" ,
51 "tool_use" ,
52 "vision" ,
53 }
54 _VERSION_SCAN_FEATURES = {
55 "assistant_prefill" ,
56 "budget_tokens" ,
57 "max_tokens_headroom" ,
58 "refusal_handling" ,
59 "sampling_parameters" ,
60 "tokenizer_rebaseline" ,
61 }
62 _REARCHITECTURE_FEATURES = {
63 "agent_infra" ,
64 "conversation_state" ,
65 "fallbacks" ,
66 "files_api" ,
67 "message_batches" ,
68 "server_tools" ,
69 "url_sources" ,
70 }
71
72 _BLOCK_FINDINGS = {
73 "budget_tokens" : (
74 "budget_tokens_removed" ,
75 "`budget_tokens` is rejected by current Claude targets." ,
76 "Replace it with adaptive thinking and an explicit effort setting." ,
77 ),
78 "sampling_parameters" : (
79 "sampling_parameters_removed" ,
80 "Legacy temperature/top_p/top_k controls are rejected by current Claude targets." ,
81 "Remove them and calibrate behavior with the golden set." ,
82 ),
83 "assistant_prefill" : (
84 "assistant_prefill_removed" ,
85 "Assistant-prefill structured output is rejected by current Claude targets." ,
86 "Use a forced tool without strict and validate the resulting schema." ,
87 ),
88 "server_tools" : (
89 "server_tools_not_portable" ,
90 "First-party server tools are not provided by the selected Bedrock path." ,
91 "Implement an application-owned tool loop or select an external service." ,
92 ),
93 "files_api" : (
94 "files_api_not_portable" ,
95 "The first-party Files API is not available on Bedrock." ,
96 "Inline supported content within the 20 MB request cap or add object retrieval." ,
97 ),
98 "url_sources" : (
99 "url_sources_not_portable" ,
100 "First-party URL image and document sources are not available on Bedrock." ,
101 "Fetch, validate, and inline the content from the application." ,
102 ),
103 "message_batches" : (
104 "message_batches_not_portable" ,
105 "Anthropic Message Batches do not port directly to Bedrock." ,
106 "Use CreateModelInvocationJob and redesign job submission and result handling." ,
107 ),
108 "models_api" : (
109 "models_api_not_portable" ,
110 "The first-party Models API is not available on Bedrock." ,
111 "Use Bedrock model discovery and keep runtime invocability as a separate probe." ,
112 ),
113 "fallbacks" : (
114 "fallbacks_not_portable" ,
115 "Server-side model fallbacks are not available on the selected Bedrock path." ,
116 "Implement explicit client-side routing, retry limits, and fallback observability." ,
117 ),
118 "conversation_state" : (
119 "conversation_state_not_portable" ,
120 "Server-side conversation state is not available on the selected Bedrock path." ,
121 "Persist and resend conversation history from an application-owned store." ,
122 ),
123 "agent_infra" : (
124 "agent_infra_rearchitecture" ,
125 "First-party Anthropic agent infrastructure is not a portable inference-call feature." ,
126 "Redesign Skills, MCP connectors, and managed-agent dependencies on AWS." ,
127 ),
128 }
129
130 _TUNE_FINDINGS = {
131 "refusal_handling" : (
132 "refusal_handling" ,
133 "Current Claude targets can return stop_reason=refusal." ,
134 "Add explicit refusal handling and include refusal cases in evaluation." ,
135 ),
136 "tokenizer_rebaseline" : (
137 "tokenizer_rebaseline" ,
138 "Token counts change across Claude versions and Bedrock paths." ,
139 "Re-baseline context sizing, usage accounting, and truncation alerts." ,
140 ),
141 "max_tokens_headroom" : (
142 "max_tokens_headroom" ,
143 "Thinking tokens share max_tokens and framework defaults can truncate output." ,
144 "Preserve source intent and tune from output distributions and thinking headroom." ,
145 ),
146 "prompt_caching" : (
147 "prompt_cache_validation" ,
148 "Prompt caching is supported but cache-hit behavior must not be assumed." ,
149 "Verify writes, reads, TTL, minimum-token thresholds, and hit telemetry." ,
150 ),
151 }
152
153
154 def _finding (code, tag, message, remediation):
155 return {
156 "code" : code,
157 "tag" : tag,
158 "message" : message,
159 "remediation" : remediation,
160 }
161
162
163 def _delta (code, category, description):
164 return { "code" : code, "category" : category, "description" : description}
165
166
167 def _version_tuple (value):
168 if not value:
169 return None
170 match = re.search( r " (\d + ) \. (\d + ) " , value)
171 return tuple ( map ( int , match.groups())) if match else None
172
173
174 def _source_version (source):
175 patterns = (
176 r "claude- (?: opus- | sonnet- | haiku- ) ? (\d + )[ .- ](\d + ) " ,
177 r "claude- (\d + )[ .- ](\d + ) - (?: opus | sonnet | haiku ) " ,
178 )
179 for model_id in source.get( "model_ids" , []):
180 normalized = model_id.lower().replace( "_" , "-" )
181 for pattern in patterns:
182 match = re.search(pattern, normalized)
183 if match:
184 return f " { int (match.group( 1 )) } . { int (match.group( 2 )) } "
185 return None
186
187
188 def _candidate_order (requirements):
189 features = requirements.get( "critical_features" , [])
190 for feature in ( "agentic" ,):
191 if feature in features:
192 return _FEATURE_ORDER [feature], feature
193 priority = requirements.get( "priority" , "unknown" )
194 return _PRIORITY_ORDER .get(priority, _PRIORITY_ORDER [ "unknown" ]), priority
195
196
197 def _required_capabilities (requirements):
198 capabilities = {
199 _CAPABILITY_ALIASES .get(feature, feature)
200 for feature in requirements.get( "critical_features" , [])
201 if _CAPABILITY_ALIASES .get(feature, feature) in _MODEL_CAPABILITIES
202 }
203 if requirements.get( "thinking_enabled" ):
204 capabilities.add( "extended_thinking" )
205 return capabilities
206
207
208 def _path_constraints (workload):
209 source = workload[ "source" ]
210 requirements = workload[ "requirements" ]
211 preferred = requirements.get( "preferred_api_path" )
212 if preferred and preferred not in SUPPORTED_PATHS :
213 raise ValueError ( f "unsupported preferred_api_path: { preferred } " )
214
215 runtime_required = bool (requirements.get( "governance" )) or requirements.get(
216 "multi_model_converse" , False
217 )
218 native_required = requirements.get( "requires_native_payload" , False )
219 messages_required = requirements.get( "preserve_messages_api" , False ) or requirements.get(
220 "newest_anthropic_betas" , False
221 )
222 source_messages = (
223 source[ "provider" ] == "anthropic"
224 and source.get( "api_surface" ) == "messages"
225 and requirements.get( "preserve_messages_api" ) is not False
226 )
227
228 runtime_path = "runtime_invoke" if native_required else "runtime_converse"
229 conflicts = []
230 if messages_required and (runtime_required or native_required):
231 conflicts.append( "messages_vs_runtime" )
232 if preferred:
233 if preferred.startswith( "mantle_" ) and (runtime_required or native_required):
234 conflicts.append( "preferred_path_vs_runtime" )
235 if preferred.startswith( "runtime_" ) and messages_required:
236 conflicts.append( "preferred_path_vs_messages" )
237 if native_required and preferred != "runtime_invoke" :
238 conflicts.append( "preferred_path_vs_native_payload" )
239
240 if conflicts:
241 return {
242 "paths" : [],
243 "conflicts" : sorted ( set (conflicts)),
244 "option_paths" : [ "mantle_messages" , runtime_path],
245 "rationale" : [
246 "Messages continuity or beta requirements conflict with required Bedrock runtime capabilities."
247 ],
248 }
249 if preferred:
250 return {
251 "paths" : [preferred],
252 "conflicts" : [],
253 "option_paths" : [],
254 "rationale" : [ f "User selected the { preferred } API path." ],
255 }
256 if native_required:
257 return {
258 "paths" : [ "runtime_invoke" ],
259 "conflicts" : [],
260 "option_paths" : [],
261 "rationale" : [ "InvokeModel is required for the provider-native Bedrock body." ],
262 }
263 if runtime_required:
264 return {
265 "paths" : [ "runtime_converse" ],
266 "conflicts" : [],
267 "option_paths" : [],
268 "rationale" : [
269 "Converse is required by governance, logging, or multi-model requirements."
270 ],
271 }
272 if messages_required:
273 return {
274 "paths" : [ "mantle_messages" ],
275 "conflicts" : [],
276 "option_paths" : [],
277 "rationale" : [
278 "Mantle is required to preserve Messages semantics or newest beta features."
279 ],
280 }
281 if source_messages:
282 return {
283 "paths" : list ( ANTHROPIC_PATHS ),
284 "conflicts" : [],
285 "option_paths" : [],
286 "rationale" : [
287 "Mantle is preferred for an existing first-party Messages API call."
288 ],
289 }
290 return {
291 "paths" : [ "runtime_converse" , "runtime_invoke" , "mantle_messages" ],
292 "conflicts" : [],
293 "option_paths" : [],
294 "rationale" : [ "Converse is the default for a new governance-ready Bedrock workload." ],
295 }
296
297
298 def _build_candidates (catalog, paths, requirements):
299 model_order, driver = _candidate_order(requirements)
300 required_capabilities = _required_capabilities(requirements)
301 min_context = requirements.get( "min_context_tokens" , 0 )
302 expected_output = requirements.get( "expected_output_tokens" , 0 )
303 candidates = []
304 for path_rank, path in enumerate (paths):
305 for model_rank, model_key in enumerate (model_order):
306 model = catalog[ "models" ][model_key]
307 path_config = model[ "paths" ].get(path, {})
308 if path_config.get( "available" ) is not True :
309 continue
310 if not required_capabilities.issubset( set (model[ "capabilities" ])):
311 continue
312 if model[ "context_window" ] < min_context:
313 continue
314 if model[ "output_token_ceiling" ] < expected_output:
315 continue
316 candidates.append(
317 {
318 "model_key" : model_key,
319 "model" : model,
320 "path" : path,
321 "path_config" : path_config,
322 "rank" : (path_rank, model_rank),
323 "driver" : driver,
324 }
325 )
326 return sorted (candidates, key =lambda item: item[ "rank" ])
327
328
329 def _candidate_summary (candidate, requirements, reason):
330 model = candidate[ "model" ]
331 path_config = candidate[ "path_config" ]
332 invocation_model_id = _resolve_invocation_model_id(
333 path_config[ "model_id" ], path_config[ "requires_cris" ], requirements
334 )
335 return {
336 "model_key" : candidate[ "model_key" ],
337 "model" : path_config[ "model_id" ],
338 "api_path" : candidate[ "path" ],
339 "invocation_model_id" : invocation_model_id,
340 "requires_cris" : path_config[ "requires_cris" ],
341 "reason" : reason,
342 }
343
344
345 def _resolve_invocation_model_id (model_id, requires_cris, requirements):
346 if not requires_cris:
347 return model_id
348 explicit = requirements.get( "inference_profile_id" )
349 if explicit:
350 return explicit
351 residency = requirements.get( "data_residency" , "unknown" )
352 if residency == "global_allowed" :
353 return f "global. { model_id } "
354 if residency == "geo_required" and requirements.get( "cris_geography" ):
355 return f " { requirements[ 'cris_geography' ] } . { model_id } "
356 return None
357
358
359 def _decision_options (catalog, workload, option_paths):
360 options = []
361 for path in dict .fromkeys(option_paths):
362 candidates = _build_candidates(catalog, [path], workload[ "requirements" ])
363 if not candidates:
364 continue
365 tradeoff = (
366 "Preserves Messages code and beta access but gives up runtime-only governance."
367 if path == "mantle_messages"
368 else "Provides runtime governance but requires rewriting the Messages integration."
369 )
370 options.append(
371 _candidate_summary(candidates[ 0 ], workload[ "requirements" ], tradeoff)
372 )
373 return options
374
375
376 def _feature_assessment (workload, source_version, target_version):
377 detected = set (workload.get( "detected_features" , []))
378 statuses = dict (workload.get( "feature_status" , {}))
379 for feature in detected:
380 statuses[feature] = "detected"
381 source_tuple = _version_tuple(source_version)
382 target_tuple = _version_tuple(target_version)
383 if source_tuple and target_tuple and source_tuple < target_tuple and target_tuple >= ( 4 , 7 ):
384 for feature in _VERSION_SCAN_FEATURES :
385 statuses.setdefault(feature, "unknown" )
386 return dict ( sorted (statuses.items()))
387
388
389 def _source_analysis (source, target_version):
390 source_version = _source_version(source)
391 source_tuple = _version_tuple(source_version)
392 target_tuple = _version_tuple(target_version)
393 changed = (
394 source_tuple != target_tuple
395 if source_tuple is not None and target_tuple is not None
396 else None
397 )
398 return {
399 "detected_version" : source_version,
400 "target_version" : target_version,
401 "version_changed" : changed,
402 }
403
404
405 def _migration_deltas (source, source_analysis, path, feature_status, requirements):
406 if source[ "provider" ] != "anthropic" :
407 return []
408 deltas = [
409 _delta(
410 "sdk_and_auth" ,
411 "path" ,
412 "Authentication and client construction change for the selected Bedrock path." ,
413 ),
414 _delta(
415 "model_id_shape" ,
416 "path" ,
417 "Mantle uses a clean path ID; runtime requires a verified CRIS profile." ,
418 ),
419 _delta(
420 "iam_action" ,
421 "platform" ,
422 "Mantle and runtime use different IAM actions and resource conditions." ,
423 ),
424 _delta(
425 "version_header" ,
426 "path" ,
427 "Mantle uses the HTTP version header; runtime uses the Bedrock body field." ,
428 ),
429 _delta(
430 "error_and_quota_surface" ,
431 "platform" ,
432 "Messages 400s, runtime ValidationException, and quota pools differ by path." ,
433 ),
434 ]
435 if source_analysis[ "version_changed" ]:
436 deltas.append(
437 _delta(
438 "claude_version_hop" ,
439 "version" ,
440 f "Claude { source_analysis[ 'detected_version' ] } to "
441 f " { source_analysis[ 'target_version' ] } requires version migration checks." ,
442 )
443 )
444 detected = {
445 feature for feature, status in feature_status.items() if status == "detected"
446 }
447 if "structured_output" in detected:
448 deltas.append(
449 _delta(
450 "structured_output" ,
451 "feature" ,
452 "Use a forced tool without strict as the portable default; native fields "
453 "are model, path, and region dependent." ,
454 )
455 )
456 if path == "runtime_converse" and (
457 "extended_thinking" in requirements.get( "critical_features" , [])
458 or "budget_tokens" in detected
459 ):
460 deltas.append(
461 _delta(
462 "additional_model_request_fields" ,
463 "path" ,
464 "Converse carries Anthropic thinking and effort fields through "
465 "additionalModelRequestFields." ,
466 )
467 )
468 if "prompt_caching" in detected:
469 deltas.append(
470 _delta(
471 "prompt_cache_shape" ,
472 "path" ,
473 "Mantle cache_control and Converse cachePoint use different request shapes." ,
474 )
475 )
476 return deltas
477
478
479 def _compatibility (feature_status, requirements, path):
480 detected = {
481 feature for feature, status in feature_status.items() if status == "detected"
482 }
483 native = {
484 feature
485 for feature in detected
486 if feature in { "citations" , "streaming" , "tool_use" , "vision" }
487 }
488 if "prompt_caching" in detected and path == "mantle_messages" :
489 native.add( "prompt_caching" )
490 portable = detected.intersection( _VERSION_SCAN_FEATURES )
491 portable.update(detected.intersection({ "models_api" , "structured_output" }))
492 if "prompt_caching" in detected and path != "mantle_messages" :
493 portable.add( "prompt_caching" )
494 rearchitecture = detected.intersection( _REARCHITECTURE_FEATURES )
495 critical = set (requirements.get( "critical_features" , []))
496 native.update(critical.intersection({ "tool_use" , "vision" , "multimodal" }))
497 return {
498 "native" : sorted (native),
499 "portable" : sorted (portable - rearchitecture),
500 "rearchitecture" : sorted (rearchitecture),
501 "unsupported" : [],
502 }
503
504
505 def _architecture_impacts (feature_status):
506 detected = {
507 feature for feature, status in feature_status.items() if status == "detected"
508 }
509 impacts = []
510 for feature in sorted (detected.intersection( _REARCHITECTURE_FEATURES )):
511 _, message, remediation = _BLOCK_FINDINGS [feature]
512 impacts.append(
513 {
514 "feature" : feature,
515 "impact" : message,
516 "recommendation" : remediation,
517 }
518 )
519 return impacts
520
521
522 def _evaluation_requirements (workload, feature_status):
523 detected = {
524 feature for feature, status in feature_status.items() if status == "detected"
525 }
526 critical = set (workload[ "requirements" ].get( "critical_features" , []))
527 trajectory = bool (
528 critical.intersection({ "agentic" , "tool_use" })
529 or detected.intersection({ "agent_infra" , "server_tools" })
530 )
531 gates = [
532 "Compare representative source and target outputs against a versioned golden set." ,
533 "Fail on refusal mishandling, truncation, or invalid structured output." ,
534 ]
535 if trajectory:
536 gates.extend(
537 [
538 "Verify the correct tool is called with valid arguments." ,
539 "Verify the agent loop terminates and guardrails remain effective." ,
540 ]
541 )
542 return { "mode" : "trajectory" if trajectory else "prompt" , "gates" : gates}
543
544
545 def _base_findings (feature_status, source_analysis):
546 detected = {
547 feature for feature, status in feature_status.items() if status == "detected"
548 }
549 blocks = []
550 tuning = []
551 for feature in sorted (detected):
552 if feature in _BLOCK_FINDINGS :
553 code, message, remediation = _BLOCK_FINDINGS [feature]
554 blocks.append(_finding(code, "[BLOCKS]" , message, remediation))
555 if feature in _TUNE_FINDINGS :
556 code, message, remediation = _TUNE_FINDINGS [feature]
557 tuning.append(_finding(code, "[TUNE]" , message, remediation))
558 unknown_version_features = sorted (
559 feature
560 for feature in _VERSION_SCAN_FEATURES
561 if feature_status.get(feature) == "unknown"
562 )
563 if source_analysis[ "version_changed" ] and unknown_version_features:
564 blocks.append(
565 _finding(
566 "version_scan_incomplete" ,
567 "[BLOCKS]" ,
568 "The source-to-target Claude version hop has unverified breaking-change surfaces: "
569 + ", " .join(unknown_version_features),
570 "Scan the recorded source paths and mark every feature detected or absent." ,
571 )
572 )
573 if "structured_output" in detected:
574 blocks.append(
575 _finding(
576 "structured_output_portable_pattern" ,
577 "[BLOCKS]" ,
578 "Native structured-output controls vary by model, path, and region." ,
579 "Use a forced tool without strict; validate the schema subset and do not "
580 "combine structured output with citations." ,
581 )
582 )
583 if "structured_output" in detected and "citations" in detected:
584 blocks.append(
585 _finding(
586 "structured_output_citations_conflict" ,
587 "[BLOCKS]" ,
588 "Structured output and citations are incompatible in the reference snapshot." ,
589 "Choose one output contract per call site and verify it live." ,
590 )
591 )
592 return blocks, tuning
593
594
595 def _verification (candidate, region, catalog, invocation_model_id):
596 path = candidate[ "path" ]
597 checks = [
598 "Probe the selected model through the selected API path in the target account and region." ,
599 "Verify path-specific IAM before code rewrite or POC generation." ,
600 ]
601 if candidate[ "path_config" ][ "requires_cris" ]:
602 checks.insert(
603 1 ,
604 "Resolve and probe a Global or geography-scoped CRIS inference profile." ,
605 )
606 return {
607 "region" : region,
608 "catalog_verified_at" : catalog[ "verified_at" ],
609 "verified_at" : None ,
610 "probe_status" : "not_run" ,
611 "availability_claim" : "provisional" ,
612 "invocation_model_id" : invocation_model_id,
613 "required_checks" : checks,
614 }
615
616
617 def _decision_required (workload, region, catalog, constraints):
618 feature_status = _feature_assessment(workload, _source_version(workload[ "source" ]), None )
619 decision_options = _decision_options(
620 catalog, workload, constraints[ "option_paths" ]
621 )
622 if len (decision_options) != len ( set (constraints[ "option_paths" ])):
623 raise ValueError (
624 "catalog has no candidate for every conflicting model/path option "
625 f "for workload { workload[ 'workload_id' ] } "
626 )
627 blocks = [
628 _finding(
629 "model_path_decision_required" ,
630 "[BLOCKS]" ,
631 "Messages continuity conflicts with required Bedrock runtime capabilities." ,
632 "Choose the Mantle continuity option or the runtime governance option, "
633 "update requirements, and rerun Model Recommend." ,
634 )
635 ]
636 return {
637 "workload_id" : workload[ "workload_id" ],
638 "provider_module" : "anthropic" ,
639 "decision_status" : "decision_required" ,
640 "source" : workload[ "source" ],
641 "source_analysis" : {
642 "detected_version" : _source_version(workload[ "source" ]),
643 "target_version" : None ,
644 "version_changed" : None ,
645 },
646 "feature_assessment" : feature_status,
647 "primary_model" : None ,
648 "model_identity" : None ,
649 "api_path" : None ,
650 "invocation_model_id" : None ,
651 "decision_options" : decision_options,
652 "alternatives" : [],
653 "rationale" : constraints[ "rationale" ],
654 "blocks" : blocks,
655 "tuning" : [],
656 "compatibility" : _compatibility(feature_status, workload[ "requirements" ], "" ),
657 "architecture_impacts" : _architecture_impacts(feature_status),
658 "migration_deltas" : [],
659 "evaluation" : _evaluation_requirements(workload, feature_status),
660 "rollout" : {
661 "strategy" : "decision_required" ,
662 "gate" : "Resolve the model/path conflict before implementation." ,
663 },
664 "verification" : {
665 "region" : region,
666 "catalog_verified_at" : catalog[ "verified_at" ],
667 "verified_at" : None ,
668 "probe_status" : "not_applicable" ,
669 "availability_claim" : "not_selected" ,
670 "invocation_model_id" : None ,
671 "required_checks" : [
672 "Resolve the model/path decision before running an availability probe."
673 ],
674 },
675 }
676
677
678 def recommend_anthropic_workload (workload, region, catalog):
679 constraints = _path_constraints(workload)
680 if constraints[ "conflicts" ]:
681 return _decision_required(workload, region, catalog, constraints)
682
683 candidates = _build_candidates(
684 catalog, constraints[ "paths" ], workload[ "requirements" ]
685 )
686 if not candidates:
687 raise ValueError (
688 f "catalog has no model/path candidate satisfying workload "
689 f " { workload[ 'workload_id' ] } requirements"
690 )
691 chosen = candidates[ 0 ]
692 model = chosen[ "model" ]
693 path = chosen[ "path" ]
694 path_config = chosen[ "path_config" ]
695 source_analysis = _source_analysis(workload[ "source" ], model[ "version" ])
696 feature_status = _feature_assessment(
697 workload,
698 source_analysis[ "detected_version" ],
699 source_analysis[ "target_version" ],
700 )
701 blocks, tuning = _base_findings(feature_status, source_analysis)
702 provider = workload[ "source" ][ "provider" ]
703 provider_module = "anthropic" if provider in ANTHROPIC_POOL else "generic"
704 if provider_module == "generic" :
705 blocks.append(
706 _finding(
707 "provider_module_pending" ,
708 "[BLOCKS]" ,
709 f "The { provider } to Bedrock compatibility module is not implemented yet." ,
710 "Keep this recommendation provisional until its provider module runs." ,
711 )
712 )
713 invocation_model_id = _resolve_invocation_model_id(
714 path_config[ "model_id" ],
715 path_config[ "requires_cris" ],
716 workload[ "requirements" ],
717 )
718 rationale = list (constraints[ "rationale" ])
719 rationale.append(
720 f " { model[ 'display_name' ] } is the highest-ranked { chosen[ 'driver' ] } model "
721 f "that satisfies the { path } constraints."
722 )
723 alternatives = [
724 _candidate_summary(
725 candidate,
726 workload[ "requirements" ],
727 "Next compatible model/path candidate after hard-constraint filtering." ,
728 )
729 for candidate in candidates[ 1 : 4 ]
730 ]
731 return {
732 "workload_id" : workload[ "workload_id" ],
733 "provider_module" : provider_module,
734 "decision_status" : "recommended" ,
735 "source" : workload[ "source" ],
736 "source_analysis" : source_analysis,
737 "feature_assessment" : feature_status,
738 "primary_model" : path_config[ "model_id" ],
739 "model_identity" : {
740 "model_key" : chosen[ "model_key" ],
741 "display_name" : model[ "display_name" ],
742 "family" : model[ "family" ],
743 "version" : model[ "version" ],
744 "context_window" : model[ "context_window" ],
745 "output_token_ceiling" : model[ "output_token_ceiling" ],
746 "path_model_id" : path_config[ "model_id" ],
747 "requires_cris" : path_config[ "requires_cris" ],
748 },
749 "api_path" : path,
750 "invocation_model_id" : invocation_model_id,
751 "decision_options" : [],
752 "alternatives" : alternatives,
753 "rationale" : rationale,
754 "blocks" : blocks,
755 "tuning" : tuning,
756 "compatibility" : _compatibility(
757 feature_status, workload[ "requirements" ], path
758 ),
759 "architecture_impacts" : _architecture_impacts(feature_status),
760 "migration_deltas" : _migration_deltas(
761 workload[ "source" ],
762 source_analysis,
763 path,
764 feature_status,
765 workload[ "requirements" ],
766 ),
767 "evaluation" : _evaluation_requirements(workload, feature_status),
768 "rollout" : {
769 "strategy" : "canary" ,
770 "gate" : "Compare source and target on the golden set before percentage rollout." ,
771 },
772 "verification" : _verification(
773 chosen, region, catalog, invocation_model_id
774 ),
775 }
776