Setting the file. One moment. Validate AI Profile · Bedrock Migration Assessment · aws/agent-toolkit-for-aws · Skills Docs26.5
Schema Preferences
93
Creating Amazon Aurora Db Cluster With Instances
Shieldadvanced
Resilience Program Design
Connecting Lambda To DynamoDB
def validate_profile
— line 285
This file
- Number
- 26.8
- Position
- 8 of 8
- Type
- Python
- Size
- 16 KB
- Lines
- 331
scripts/validate-ai-profile.py
Python·331 lines·16 KB
Path(
__file__
).resolve().parent.parent
13SCHEMA_PATH = SKILL_ROOT / "references" / "contracts" / "ai-workload-profile.schema.json"
14
15# Top-level keys of the GCP-shaped profile (gcp-to-aws schema-discover-ai.md); none exist in the neutral contract.
16LEGACY_TOP_LEVEL_KEYS = ("metadata", "summary", "models", "infrastructure", "current_costs", "detection_signals")
17LEGACY_SOURCE_KEYS = ("model_id", "service", "detected_via", "capabilities_used", "usage_context")
18V11_FIELDS = ("integration", "workloads", "agentic_profile", "tool_manifest")
19ORCHESTRATION_FRAMEWORKS = {"langchain", "langgraph", "llama_index", "llamaindex", "semantic_kernel"}
20GATEWAY_FRAMEWORK_VALUES = {"llm_router": "llm_router", "api_gateway": "api_gateway", "voice_platform": "voice_platform"}
21
22_JSON_TYPES = {"object": dict, "array": list, "string": str, "boolean": bool, "number": (int, float), "integer": int}
23
24
25class ProfileRejected(Exception):
26 def __init__(self, code: str, detail: str) -> None:
27 super().__init__(f"{code}: {detail}")
28 self.code = code
29 self.detail = detail
30
31
32def _schema_errors(schema: dict, value: Any, root: dict, path: str = "$") -> list[str]:
33 if "$ref" in schema:
34 return _schema_errors(root["definitions"][schema["$ref"].rsplit("/", 1)[-1]], value, root, path)
35 if "type" in schema and (
36 not isinstance(value, _JSON_TYPES[schema["type"]])
37 or (schema["type"] in ("integer", "number") and isinstance(value, bool))
38 ):
39 return [f"{path}: expected {schema['type']}"]
40 if "minimum" in schema and isinstance(value, (int, float)) and not isinstance(value, bool) and value < schema["minimum"]:
41 return [f"{path}: below minimum {schema['minimum']}"]
42 errors: list[str] = []
43 if "const" in schema and value != schema["const"]:
44 errors.append(f"{path}: must equal {schema['const']!r}")
45 if "enum" in schema and value not in schema["enum"]:
46 errors.append(f"{path}: {value!r} not in {schema['enum']}")
47 if isinstance(value, str):
48 if "pattern" in schema and not re.search(schema["pattern"], value):
49 errors.append(f"{path}: {value!r} does not match {schema['pattern']}")
50 if len(value) < schema.get("minLength", 0):
51 errors.append(f"{path}: shorter than {schema['minLength']}")
52 if isinstance(value, dict):
53 properties = schema.get("properties", {})
54 errors += [f"{path}: missing required {key!r}" for key in schema.get("required", []) if key not in value]
55 if schema.get("additionalProperties") is False:
56 errors += [f"{path}: unexpected key {key!r}" for key in value if key not in properties]
57 for key, sub in properties.items():
58 if key in value:
59 errors += _schema_errors(sub, value[key], root, f"{path}.{key}")
60 if isinstance(value, list):
61 if len(value) < schema.get("minItems", 0):
62 errors.append(f"{path}: fewer than {schema['minItems']} items")
63 if "maxItems" in schema and len(value) > schema["maxItems"]:
64 errors.append(f"{path}: more than {schema['maxItems']} items")
65 if schema.get("uniqueItems"):
66 canonical = [json.dumps(item, sort_keys=True) for item in value]
67 if len(set(canonical)) != len(canonical):
68 errors.append(f"{path}: items are not unique")
69 if "items" in schema:
70 for index, item in enumerate(value):
71 errors += _schema_errors(schema["items"], item, root, f"{path}[{index}]")
72 if "oneOf" in schema:
73 matches = sum(1 for branch in schema["oneOf"] if not _schema_errors(branch, value, root, path))
74 if matches != 1:
75 errors.append(f"{path}: matches {matches} oneOf branches, expected exactly 1")
76 return errors
77
78
79def _legacy_field(profile: dict) -> str | None:
80 for key in LEGACY_TOP_LEVEL_KEYS:
81 if key in profile:
82 return key
83 for index, source in enumerate(_items(profile, "ai_sources")):
84 for key in LEGACY_SOURCE_KEYS:
85 if isinstance(source, dict) and key in source:
86 return f"ai_sources[{index}].{key}"
87 return None
88
89
90def _items(container: Any, key: str) -> list:
91 value = container.get(key) if isinstance(container, dict) else None
92 return value if isinstance(value, list) else []
93
94
95def _confirmed_confidence_path(profile: dict) -> str | None:
96 for i, source in enumerate(_items(profile, "ai_sources")):
97 for j, observation in enumerate(_items(source, "model_observations")):
98 for k, entry in enumerate(_items(observation, "evidence")):
99 if isinstance(entry, dict) and entry.get("confidence") == "confirmed":
100 return f"ai_sources[{i}].model_observations[{j}].evidence[{k}].confidence"
101 return None
102
103
104def _check_semantics(profile: dict) -> dict[tuple[str, str], dict]:
105 sources: dict[tuple[str, str], dict] = {}
106 for source in profile["ai_sources"]:
107 key = (source["provider"], source["source_service"])
108 if key in sources:
109 raise ProfileRejected("duplicate_ai_source", f"{key[0]}/{key[1]} appears more than once")
110 sources[key] = source
111 observed = {item["normalized_model"] for item in source["model_observations"]}
112 if len(observed) != len(source["model_observations"]) or observed != set(source["models"]):
113 raise ProfileRejected(
114 "observation_mismatch",
115 f"{key[0]}/{key[1]}: model_observations {sorted(observed)} must match models {sorted(source['models'])} one-to-one",
116 )
117 for item in source["model_observations"]:
118 if item["model_family"] not in source["model_families"]:
119 raise ProfileRejected(
120 "observation_family_mismatch",
121 f"{key[0]}/{key[1]}: {item['normalized_model']} has family {item['model_family']!r} not in model_families",
122 )
123 for entry in item["evidence"]:
124 if entry["provenance"] == "user_confirmed" and entry["confidence"] != "high":
125 raise ProfileRejected(
126 "user_confirmed_requires_high",
127 f"{key[0]}/{key[1]}: {item['normalized_model']} user_confirmed evidence must have confidence 'high'",
128 )
129
130 for gateway in profile["gateways"]:
131 resolved: set[tuple[str, str]] = set()
132 for upstream in gateway["upstreams"]:
133 if "source_service" in upstream:
134 source = sources.get((upstream["provider"], upstream["source_service"]))
135 if source is None:
136 raise ProfileRejected(
137 "unmatched_gateway_upstream",
138 f"{gateway['product']}: upstream {upstream['provider']}/{upstream['source_service']} has no ai_sources entry",
139 )
140 else:
141 candidates = [s for (provider, _), s in sources.items() if provider == upstream["provider"]]
142 if len(candidates) != 1:
143 raise ProfileRejected(
144 "unmatched_gateway_upstream",
145 f"{gateway['product']}: service-less upstream {upstream['provider']} matches {len(candidates)} ai_sources entries, expected 1",
146 )
147 source = candidates[0]
148 key = (source["provider"], source["source_service"])
149 if key in resolved:
150 raise ProfileRejected("duplicate_gateway_upstream", f"{gateway['product']}: {key[0]}/{key[1]} appears more than once")
151 resolved.add(key)
152
153 families = {f["family"] for f in upstream["model_families"]}
154 if len(families) != len(upstream["model_families"]) or families != set(source["model_families"]):
155 raise ProfileRejected(
156 "upstream_family_mismatch",
157 f"{gateway['product']}: {key[0]}/{key[1]} families {sorted(families)} must match {sorted(source['model_families'])}",
158 )
159 models_by_family: dict[str, set[str]] = {}
160 for item in source["model_observations"]:
161 models_by_family.setdefault(item["model_family"], set()).add(item["normalized_model"])
162 for family in upstream["model_families"]:
163 if family["model_evidence"] != source["model_evidence"]:
164 raise ProfileRejected(
165 "upstream_family_mismatch",
166 f"{gateway['product']}: {key[0]}/{key[1]} family {family['family']} evidence differs from the source",
167 )
168 expected = models_by_family.get(family["family"], set())
169 if set(family["models"]) != expected:
170 raise ProfileRejected(
171 "upstream_models_mismatch",
172 f"{gateway['product']}: {key[0]}/{key[1]} family {family['family']} lists {sorted(family['models'])}, "
173 f"but the source observes {sorted(expected)} in that family",
174 )
175 return sources
176
177
178def _check_v11(profile: dict, sources: dict[tuple[str, str], dict]) -> None:
179 present = [k for k in V11_FIELDS if k in profile]
180 if profile["schema_version"] == "1.0" and present:
181 raise ProfileRejected("version_mismatch", f"{', '.join(present)} requires schema_version \"1.1\"")
182 workload_ids: set[str] = set()
183 for workload in profile.get("workloads", []):
184 key = (workload["provider"], workload["source_service"])
185 if workload["workload_id"] in workload_ids:
186 raise ProfileRejected("duplicate_workload", f"workload_id {workload['workload_id']!r} appears more than once")
187 workload_ids.add(workload["workload_id"])
188 if key not in sources or workload["model"] not in sources[key]["models"]:
189 raise ProfileRejected("unmatched_workload", f"{workload['workload_id']}: {key[0]}/{key[1]} {workload['model']} is not an observed model")
190 agentic = profile.get("agentic_profile")
191 tools = profile.get("tool_manifest")
192 if tools is not None and agentic is None:
193 raise ProfileRejected("tool_manifest_without_agents", "tool_manifest is present without agentic_profile")
194 if agentic is None:
195 return
196 observed = {m for s in sources.values() for m in s["models"]}
197 agent_ids = [a["agent_id"] for a in agentic["agents"]]
198 if len(set(agent_ids)) != len(agent_ids):
199 raise ProfileRejected("duplicate_agent", "agent_id values must be unique")
200 tool_names = [tool["name"] for tool in tools or []]
201 if len(set(tool_names)) != len(tool_names):
202 raise ProfileRejected("duplicate_tool", "tool_manifest names must be unique")
203 for agent in agentic["agents"]:
204 if agent["model"] not in observed:
205 raise ProfileRejected("unmatched_agent_model", f"{agent['agent_id']}: {agent['model']} is not an observed model")
206 missing = sorted(set(agent["tools"]) - set(tool_names))
207 if missing:
208 raise ProfileRejected("unmatched_agent_tool", f"{agent['agent_id']} uses {missing}, which tool_manifest does not list")
209 for tool in tools or []:
210 unknown = sorted(set(tool["used_by_agents"]) - set(agent_ids))
211 if unknown:
212 raise ProfileRejected("unmatched_tool_agent", f"tool {tool['name']} names unknown agents {unknown}")
213
214
215def _framework_detected(profile: dict) -> list[str] | None:
216 integration = profile.get("integration")
217 if integration is None or "agentic_profile" in profile:
218 return None
219 values: set[str] = set()
220 for gateway in profile["gateways"]:
221 if gateway["type"] not in GATEWAY_FRAMEWORK_VALUES:
222 return None
223 values.add(GATEWAY_FRAMEWORK_VALUES[gateway["type"]])
224 frameworks = set(integration["frameworks"])
225 if frameworks - ORCHESTRATION_FRAMEWORKS:
226 return None
227 if frameworks:
228 values.add("framework")
229 if integration["pattern"] == "mixed" or (not values and integration["pattern"] in ("direct_sdk", "rest_api")):
230 values.add("direct")
231 return sorted(values) or None
232
233
234def _input_types_detected(profile: dict) -> str | None:
235 integration = profile.get("integration")
236 if integration is None:
237 return None
238 capabilities = integration["capabilities_summary"]
239 if capabilities["speech_to_text"]:
240 return "audio"
241 return "vision" if capabilities["vision"] else "text-only"
242
243
244def _summary(profile: dict) -> dict:
245 via: dict[tuple[str, str], list[str]] = {}
246 sources_by_provider: dict[str, list[tuple[str, str]]] = {}
247 for source in profile["ai_sources"]:
248 sources_by_provider.setdefault(source["provider"], []).append((source["provider"], source["source_service"]))
249 for gateway in profile["gateways"]:
250 for upstream in gateway["upstreams"]:
251 key = (upstream["provider"], upstream["source_service"]) if "source_service" in upstream else sources_by_provider[upstream["provider"]][0]
252 via.setdefault(key, []).append(gateway["product"])
253
254 ai_sources = []
255 all_high = bool(profile["ai_sources"])
256 for source in profile["ai_sources"]:
257 key = (source["provider"], source["source_service"])
258 models = []
259 for item in source["model_observations"]:
260 high = any(entry["confidence"] == "high" for entry in item["evidence"])
261 all_high = all_high and high
262 models.append({"model": item["normalized_model"], "raw_model": item["raw_model"], "high_confidence": high})
263 all_high = all_high and source["model_evidence"] == "observed"
264 ai_sources.append({
265 "provider": source["provider"],
266 "source_service": source["source_service"],
267 "model_evidence": source["model_evidence"],
268 "models": models,
269 "via_gateways": sorted(set(via.get(key, []))),
270 })
271 return {
272 "ok": True,
273 "schema_version": profile["schema_version"],
274 "ai_sources": ai_sources,
275 "gateways": [
276 {"product": g["product"], "type": g["type"], "upstream_evidence": g["upstream_evidence"]}
277 for g in profile["gateways"]
278 ],
279 "models_extractable": all_high,
280 "ai_framework_detected": _framework_detected(profile),
281 "ai_vision_detected": _input_types_detected(profile),
282 }
283
284
285def validate_profile(profile: Any, schema: dict | None = None) -> dict:
286 if not isinstance(profile, dict):
287 raise ProfileRejected("not_an_object", "the profile must be a JSON object")
288 legacy = _legacy_field(profile)
289 if legacy is not None:
290 raise ProfileRejected(
291 "legacy_profile_rejected",
292 f"field {legacy!r} belongs to the GCP-shaped profile; supply the neutral contract "
293 "(schema_version \"1.0\" or \"1.1\", ai_sources[], gateways[])",
294 )
295 confirmed = _confirmed_confidence_path(profile)
296 if confirmed is not None:
297 raise ProfileRejected(
298 "confirmed_confidence_rejected",
299 f"{confirmed} is 'confirmed'; record user confirmation as provenance 'user_confirmed' with confidence 'high'",
300 )
301 schema = schema if schema is not None else json.loads(SCHEMA_PATH.read_text(encoding="utf-8"))
302 errors = _schema_errors(schema, profile, schema)
303 if errors:
304 raise ProfileRejected("schema_violation", "; ".join(errors[:5]))
305 _check_v11(profile, _check_semantics(profile))
306 return _summary(profile)
307
308
309def main(argv: list[str] | None = None) -> int:
310 parser = argparse.ArgumentParser(description=__doc__)
311 parser.add_argument("profile", type=Path, help="path to ai-workload-profile.json")
312 args = parser.parse_args(argv)
313 try:
314 try:
315 profile = json.loads(args.profile.read_text(encoding="utf-8"))
316 except FileNotFoundError:
317 raise ProfileRejected("not_found", str(args.profile))
318 except (json.JSONDecodeError, UnicodeDecodeError) as exc:
319 raise ProfileRejected("invalid_json", str(exc))
320 except OSError as exc:
321 raise ProfileRejected("not_readable", str(exc))
322 result = validate_profile(profile)
323 except ProfileRejected as exc:
324 print(json.dumps({"ok": False, "error": exc.code, "detail": exc.detail}, indent=2))
325 return 1
326 print(json.dumps(result, indent=2))
327 return 0
328
329
330if __name__ == "__main__":
331 sys.exit(main())