| | """ |
| | Agent Output Validation |
| | ======================== |
| | |
| | JSON schemas for validating LLM agent outputs. |
| | Ensures data integrity between pipeline stages. |
| | """ |
| |
|
| | from typing import Any, Optional |
| |
|
| | try: |
| | from jsonschema import validate, ValidationError |
| |
|
| | HAS_JSONSCHEMA = True |
| | except ImportError: |
| | HAS_JSONSCHEMA = False |
| |
|
| | from core.logging import get_logger |
| |
|
| | logger = get_logger("validation") |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | BRAND_IDENTIFICATION_SCHEMA = { |
| | "type": "object", |
| | "properties": { |
| | "brand_primary": {"type": ["string", "null"]}, |
| | "brand_secondary": {"type": ["string", "null"]}, |
| | "brand_accent": {"type": ["string", "null"]}, |
| | "palette_strategy": {"type": "string"}, |
| | "cohesion_score": {"type": ["number", "integer"]}, |
| | "cohesion_notes": {"type": "string"}, |
| | "semantic_names": {"type": "object"}, |
| | "self_evaluation": {"type": "object"}, |
| | }, |
| | "required": ["brand_primary", "palette_strategy"], |
| | } |
| |
|
| | BENCHMARK_ADVICE_SCHEMA = { |
| | "type": "object", |
| | "properties": { |
| | "recommended_benchmark": {"type": "string"}, |
| | "recommended_benchmark_name": {"type": "string"}, |
| | "reasoning": {"type": "string"}, |
| | "alignment_changes": {"type": "array"}, |
| | "pros_of_alignment": {"type": "array"}, |
| | "cons_of_alignment": {"type": "array"}, |
| | "alternative_benchmarks": {"type": "array"}, |
| | "self_evaluation": {"type": "object"}, |
| | }, |
| | "required": ["recommended_benchmark", "reasoning"], |
| | } |
| |
|
| | BEST_PRACTICES_SCHEMA = { |
| | "type": "object", |
| | "properties": { |
| | "overall_score": {"type": ["number", "integer"]}, |
| | "checks": {"type": "array"}, |
| | "priority_fixes": {"type": "array"}, |
| | "passing_practices": {"type": "array"}, |
| | "failing_practices": {"type": "array"}, |
| | "self_evaluation": {"type": "object"}, |
| | }, |
| | "required": ["overall_score", "priority_fixes"], |
| | } |
| |
|
| | HEAD_SYNTHESIS_SCHEMA = { |
| | "type": "object", |
| | "properties": { |
| | "executive_summary": {"type": "string"}, |
| | "scores": {"type": "object"}, |
| | "benchmark_fit": {"type": "object"}, |
| | "brand_analysis": {"type": "object"}, |
| | "top_3_actions": {"type": "array"}, |
| | "color_recommendations": {"type": "array"}, |
| | "type_scale_recommendation": {"type": "object"}, |
| | "spacing_recommendation": {"type": "object"}, |
| | "self_evaluation": {"type": "object"}, |
| | }, |
| | "required": ["executive_summary", "top_3_actions"], |
| | } |
| |
|
| | |
| | AGENT_SCHEMAS = { |
| | "aurora": BRAND_IDENTIFICATION_SCHEMA, |
| | "brand_identifier": BRAND_IDENTIFICATION_SCHEMA, |
| | "atlas": BENCHMARK_ADVICE_SCHEMA, |
| | "benchmark_advisor": BENCHMARK_ADVICE_SCHEMA, |
| | "sentinel": BEST_PRACTICES_SCHEMA, |
| | "best_practices": BEST_PRACTICES_SCHEMA, |
| | "nexus": HEAD_SYNTHESIS_SCHEMA, |
| | "head_synthesizer": HEAD_SYNTHESIS_SCHEMA, |
| | } |
| |
|
| |
|
| | |
| | |
| | |
| |
|
| | def validate_agent_output(data: Any, agent_name: str) -> tuple[bool, Optional[str]]: |
| | """ |
| | Validate an agent's output against its expected schema. |
| | |
| | Args: |
| | data: The output data (dict or dataclass with to_dict()) |
| | agent_name: Name of the agent (e.g., 'aurora', 'nexus') |
| | |
| | Returns: |
| | (is_valid, error_message) tuple |
| | """ |
| | agent_key = agent_name.lower().strip() |
| | schema = AGENT_SCHEMAS.get(agent_key) |
| |
|
| | if not schema: |
| | logger.warning(f"No schema found for agent: {agent_name}") |
| | return True, None |
| |
|
| | |
| | if hasattr(data, "to_dict"): |
| | data_dict = data.to_dict() |
| | elif hasattr(data, "__dataclass_fields__"): |
| | from dataclasses import asdict |
| | data_dict = asdict(data) |
| | elif isinstance(data, dict): |
| | data_dict = data |
| | else: |
| | return False, f"Cannot validate: unexpected type {type(data)}" |
| |
|
| | if not HAS_JSONSCHEMA: |
| | |
| | return _manual_validate(data_dict, schema, agent_name) |
| |
|
| | try: |
| | validate(instance=data_dict, schema=schema) |
| | logger.debug(f"Validation passed for {agent_name}") |
| | return True, None |
| | except ValidationError as e: |
| | error_msg = f"Validation failed for {agent_name}: {e.message}" |
| | logger.warning(error_msg) |
| | return False, error_msg |
| |
|
| |
|
| | def _manual_validate(data: dict, schema: dict, agent_name: str) -> tuple[bool, Optional[str]]: |
| | """Fallback validation without jsonschema library.""" |
| | required = schema.get("required", []) |
| | missing = [field for field in required if field not in data] |
| |
|
| | if missing: |
| | error_msg = f"{agent_name} output missing required fields: {missing}" |
| | logger.warning(error_msg) |
| | return False, error_msg |
| |
|
| | return True, None |
| |
|
| |
|
| | def validate_all_agents(outputs: dict) -> dict[str, tuple[bool, Optional[str]]]: |
| | """ |
| | Validate all agent outputs at once. |
| | |
| | Args: |
| | outputs: Dict mapping agent_name → output data |
| | |
| | Returns: |
| | Dict mapping agent_name → (is_valid, error_message) |
| | """ |
| | results = {} |
| | for agent_name, data in outputs.items(): |
| | results[agent_name] = validate_agent_output(data, agent_name) |
| | return results |
| |
|