Spaces:
Sleeping
Sleeping
Download agents/validate_agent.py from yania-n/ProjectCraft: direct link, hf CLI and curl.
- Browser
- Download file 4.12 kB
-
https://huggingface.co/spaces/yania-n/ProjectCraft/resolve/main/agents/validate_agent.py
- Command line
-
hf download hf://spaces/yania-n/ProjectCraft/agents/validate_agent.py
-
curl -L -o validate_agent.py https://huggingface.co/spaces/yania-n/ProjectCraft/resolve/main/agents/validate_agent.py
4.12 kB
| """Validate Agent — validates user inputs before any AI call is made. Uses Haiku for semantic coherence checks only.""" | |
| import asyncio, json, os, re | |
| import anthropic | |
| from json_repair import repair_json | |
| from pydantic import BaseModel | |
| from typing import Optional | |
| from core.sanitizer import sanitize_objective | |
| from core.date_engine import validate_date_order | |
| from core.planner import ERROR_MESSAGES | |
| class CoherenceResult(BaseModel): | |
| coherent: bool = True | |
| suggestion: Optional[str] = None | |
| # PROMPT v1.0 | |
| SYSTEM_PROMPT = """ | |
| You are validating a project brief for completeness. | |
| Return ONLY valid JSON: | |
| { "coherent": true/false, "suggestion": "string or null" } | |
| coherent = true if the objective is specific enough to generate a plan. | |
| suggestion = a short improvement tip if coherent is false, else null. | |
| """ | |
| def _check_required_fields( | |
| objective: str, | |
| teams: list, | |
| start_date: str, | |
| end_date: str, | |
| start_ai: bool, | |
| end_ai: bool, | |
| ) -> list[str]: | |
| """Pure Python field-level checks. Returns list of error strings (empty = OK).""" | |
| errors: list[str] = [] | |
| if not objective or not objective.strip(): | |
| errors.append(ERROR_MESSAGES["no_objective"]) | |
| if not teams or len(teams) < 1: | |
| errors.append(ERROR_MESSAGES["no_teams"]) | |
| start_provided = bool(start_date and start_date.strip()) | |
| end_provided = bool(end_date and end_date.strip()) | |
| if not start_provided and not start_ai and not end_provided and not end_ai: | |
| errors.append(ERROR_MESSAGES["no_dates"]) | |
| if start_provided and end_provided and not start_ai and not end_ai: | |
| if not validate_date_order(start_date, end_date): | |
| errors.append(ERROR_MESSAGES["date_order"]) | |
| return errors | |
| async def _check_coherence(objective: str) -> CoherenceResult: | |
| """Call Haiku to check if objective is semantically coherent.""" | |
| try: | |
| client = anthropic.AsyncAnthropic(api_key=os.environ.get("ANTHROPIC_API_KEY")) | |
| response = await client.messages.create( | |
| model="claude-haiku-4-5-20251001", | |
| max_tokens=256, | |
| system=SYSTEM_PROMPT, | |
| messages=[{"role": "user", "content": sanitize_objective(objective)}], | |
| ) | |
| raw_text = response.content[0].text.strip() | |
| if raw_text.startswith("```"): | |
| first_newline = raw_text.find("\n") | |
| raw_text = raw_text[first_newline + 1:] if first_newline != -1 else raw_text[3:] | |
| if raw_text.rstrip().endswith("```"): | |
| raw_text = raw_text.rstrip()[:-3].rstrip() | |
| try: | |
| data = json.loads(raw_text) | |
| except json.JSONDecodeError: | |
| data = repair_json(raw_text, return_objects=True) | |
| if not isinstance(data, dict): | |
| data = {} | |
| return CoherenceResult(**data) | |
| except anthropic.APITimeoutError: | |
| return CoherenceResult(coherent=True) | |
| except anthropic.RateLimitError: | |
| return CoherenceResult(coherent=True) | |
| except json.JSONDecodeError: | |
| return CoherenceResult(coherent=True) | |
| except Exception: | |
| return CoherenceResult(coherent=True) | |
| async def run( | |
| objective: str, | |
| teams: list, | |
| start_date: str = "", | |
| end_date: str = "", | |
| start_ai: bool = False, | |
| end_ai: bool = False, | |
| ) -> dict: | |
| """ | |
| Main entry point. Returns: | |
| {"valid": bool, "errors": list[str], "coherent": bool, "suggestion": str | None} | |
| Step 1: run pure Python field checks | |
| Step 2: if field checks pass, run coherence check | |
| Step 3: assemble result dict | |
| """ | |
| errors = _check_required_fields(objective, teams, start_date, end_date, start_ai, end_ai) | |
| coherence = CoherenceResult(coherent=True) | |
| if not errors: | |
| coherence = await _check_coherence(objective) | |
| # Coherence is advisory only — a low coherence score adds a suggestion but never blocks generation. | |
| # Hard field errors (missing objective/teams/dates) are the only blockers. | |
| return { | |
| "valid": len(errors) == 0, | |
| "errors": errors, | |
| "coherent": coherence.coherent, | |
| "suggestion": coherence.suggestion, | |
| } | |