Spaces:
Sleeping
Sleeping
Download graders/llm_grader.py from prathvi9607/SupportOps: direct link, hf CLI and curl.
- Browser
- Download file 6.38 kB
-
https://huggingface.co/spaces/prathvi9607/SupportOps/resolve/main/graders/llm_grader.py
- Command line
-
hf download hf://spaces/prathvi9607/SupportOps/graders/llm_grader.py
-
curl -L -o llm_grader.py https://huggingface.co/spaces/prathvi9607/SupportOps/resolve/main/graders/llm_grader.py
6.38 kB
| """ | |
| LLM-based grader for evaluating agent rationales and reasoning quality. | |
| Uses OpenAI-compatible API to grade soft criteria. | |
| """ | |
| import os | |
| import json | |
| import logging | |
| from typing import Any | |
| from pydantic import BaseModel, Field | |
| from openai import AsyncOpenAI | |
| from env.state import EnvState | |
| logger = logging.getLogger(__name__) | |
| class LLMGradeResult(BaseModel): | |
| """Result from LLM grading.""" | |
| diagnostic_coherence: float = Field(ge=0.0, le=1.0) | |
| evidence_sufficiency: float = Field(ge=0.0, le=1.0) | |
| root_cause_narration: float = Field(ge=0.0, le=1.0) | |
| overall: float = Field(ge=0.0, le=1.0) | |
| reasoning: str | |
| model_config = {"frozen": False} | |
| class LLMGrader: | |
| """ | |
| LLM judge using OpenAI-compatible API. | |
| Evaluates agent reasoning and rationales. | |
| """ | |
| SYSTEM_PROMPT = """You are an expert IT operations grader evaluating an AI agent's performance on an incident triage task. | |
| You will be given: | |
| 1. The hidden root cause (ground truth) | |
| 2. The agent's action history with observation updates | |
| 3. The final outcome (resolved/escalated/closed) | |
| Grade the following dimensions on a scale of 0.0 to 1.0: | |
| - diagnostic_coherence: Are the agent's actions logically consistent with the observations? Did it follow a coherent diagnostic strategy? | |
| - evidence_sufficiency: Did the agent gather enough evidence before committing to a fix or escalation? | |
| - root_cause_narration: Did the agent's actions demonstrate understanding of the actual root cause? | |
| Return ONLY a valid JSON object with these exact keys: | |
| { | |
| "diagnostic_coherence": <float 0.0-1.0>, | |
| "evidence_sufficiency": <float 0.0-1.0>, | |
| "root_cause_narration": <float 0.0-1.0>, | |
| "overall": <float 0.0-1.0>, | |
| "reasoning": "<brief explanation>" | |
| } | |
| Do not include any other text or markdown formatting.""" | |
| def __init__(self): | |
| """Initialize LLM grader with API configuration.""" | |
| self.api_base_url = os.environ.get("API_BASE_URL", "https://api.openai.com/v1") | |
| self.model_name = os.environ.get("MODEL_NAME", "gpt-4o-mini") | |
| self.api_key = os.environ.get("OPENAI_API_KEY", "") | |
| if not self.api_key: | |
| logger.warning("OPENAI_API_KEY not set. LLM grading will use fallback scores.") | |
| self.client = AsyncOpenAI( | |
| api_key=self.api_key, | |
| base_url=self.api_base_url | |
| ) | |
| async def grade(self, state: EnvState) -> LLMGradeResult: | |
| """ | |
| Grade episode using LLM judge. | |
| Args: | |
| state: Complete environment state after episode | |
| Returns: | |
| LLMGradeResult with scores and reasoning | |
| """ | |
| if not self.api_key: | |
| logger.warning("No API key available, returning fallback scores") | |
| return self._fallback_grade() | |
| try: | |
| # Build user prompt | |
| user_prompt = self._build_prompt(state) | |
| # Call LLM API | |
| response = await self.client.chat.completions.create( | |
| model=self.model_name, | |
| messages=[ | |
| {"role": "system", "content": self.SYSTEM_PROMPT}, | |
| {"role": "user", "content": user_prompt} | |
| ], | |
| temperature=0.3, | |
| max_tokens=500 | |
| ) | |
| # Parse response | |
| content = response.choices[0].message.content | |
| if not content: | |
| logger.error("Empty response from LLM") | |
| return self._fallback_grade() | |
| # Try to parse JSON | |
| try: | |
| result_dict = json.loads(content) | |
| except json.JSONDecodeError: | |
| # Try to extract JSON from markdown code blocks | |
| if "```json" in content: | |
| content = content.split("```json")[1].split("```")[0].strip() | |
| elif "```" in content: | |
| content = content.split("```")[1].split("```")[0].strip() | |
| result_dict = json.loads(content) | |
| return LLMGradeResult(**result_dict) | |
| except Exception as e: | |
| logger.error(f"LLM grading failed: {e}", exc_info=True) | |
| return self._fallback_grade() | |
| def _build_prompt(self, state: EnvState) -> str: | |
| """Build grading prompt from episode state.""" | |
| # Extract key information | |
| root_cause = state.hidden.root_cause | |
| category = state.hidden.root_cause_category | |
| severity = state.hidden.severity | |
| # Summarize action sequence | |
| action_summary = [] | |
| for i, action in enumerate(state.action_log, 1): | |
| action_summary.append( | |
| f"{i}. {action.action_name} (reward: {action.reward_received:.2f})" | |
| ) | |
| actions_text = "\n".join(action_summary) | |
| # Summarize observations | |
| final_obs = state.observation | |
| prompt = f"""## Episode Summary | |
| **Ground Truth Root Cause:** | |
| - Category: {category} | |
| - Description: {root_cause} | |
| - Severity: {severity} | |
| - Affected Users: {state.hidden.affected_users_count} | |
| **Correct Remediation:** | |
| - Primary: {state.hidden.correct_remediation} | |
| - Alternatives: {', '.join(state.hidden.correct_remediation_alts) if state.hidden.correct_remediation_alts else 'None'} | |
| **Agent's Action Sequence:** | |
| {actions_text} | |
| **Final State:** | |
| - Steps taken: {final_obs.step_count} | |
| - Episode terminated: {state.done} | |
| - Diagnostic steps: {state.diagnostic_steps_taken} | |
| - Escalation allowed: {final_obs.escalation_allowed} | |
| **Observations:** | |
| - Network Status: {final_obs.network_status.value} | |
| - VPN Status: {final_obs.vpn_status.value} | |
| - Auth Status: {final_obs.auth_status.value} | |
| - Service Health: {dict(final_obs.service_health)} | |
| **Performance:** | |
| - Cumulative Reward: {state.cumulative_reward:.2f} | |
| - Task Level: {state.task_level.value} | |
| Evaluate how well the agent diagnosed and resolved this incident.""" | |
| return prompt | |
| def _fallback_grade(self) -> LLMGradeResult: | |
| """Return neutral fallback scores when LLM unavailable.""" | |
| return LLMGradeResult( | |
| diagnostic_coherence=0.5, | |
| evidence_sufficiency=0.5, | |
| root_cause_narration=0.5, | |
| overall=0.5, | |
| reasoning="LLM grading unavailable, using fallback scores" | |
| ) | |