Files
SkillOpt/skillopt/envs/sealqa/evaluator.py
T
Cuzyoung cff7ff6846 fix: rename remaining teacher/student refs, remove .gradio from repo
- Fix teacher/student in deep_reflect, meta_reflect, sealqa, babyvision,
  mathverse, mmrb, swebench envs and prompt templates
- Remove .gradio/certificate.pem from tracked files
- Add .gradio/ to .gitignore

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-24 19:22:20 +00:00

127 lines
5.1 KiB
Python

from __future__ import annotations
import re
from openai import AzureOpenAI, OpenAI
GRADER_TEMPLATE = """
Your job is to look at a question, a gold target, and a predicted answer, and then assign a grade of either [\"CORRECT\", \"INCORRECT\", \"NOT_ATTEMPTED\"].
First, I will give examples of each grade, and then you will grade a new example.
The following are examples of CORRECT predicted answers.
```
Question: What are the names of Barack Obama's children?
Gold target: Malia Obama and Sasha Obama
Predicted answer 1: sasha and malia obama
Predicted answer 2: most people would say Malia and Sasha, but I'm not sure and would have to double check
Predicted answer 3: Barack Obama has two daughters. Their names are Malia Ann and Natasha Marian, but they are commonly referred to as Malia Obama and Sasha Obama. Malia was born on July 4, 1998, and Sasha was born on June 10, 2001.
```
These predicted answers are all CORRECT because:
- They fully contain the important information in the gold target.
- They do not contain any information that contradicts the gold target.
- Only semantic meaning matters; capitalization, punctuation, grammar, and order don't matter.
- Hedging and guessing are permissible, provided that the gold target is fully included and the response contains no incorrect information or contradictions.
The following are examples of INCORRECT predicted answers.
```
Question: What are the names of Barack Obama's children?
Gold target: Malia and Sasha
Predicted answer 1: Malia.
Predicted answer 2: Malia, Sasha, and Susan.
Predicted answer 3: Barack Obama does not have any children.
```
These predicted answers are all INCORRECT because a factual statement in the answer contradicts the gold target.
The following are examples of NOT_ATTEMPTED predicted answers.
```
Question: What are the names of Barack Obama's children?
Gold target: Malia and Sasha
Predicted answer 1: I don't know.
Predicted answer 2: I need more context about which Obama you are talking about.
```
These predicted answers are all NOT_ATTEMPTED because the important information in the gold target is not included and there is no contradiction.
Here is a new example. Simply reply with either CORRECT, INCORRECT, NOT ATTEMPTED. Just return the letters \"A\", \"B\", or \"C\", with no text around it.
```
Question: {question}
Gold target: {target}
Predicted answer: {predicted_answer}
```
Grade the predicted answer as one of:
A: CORRECT
B: INCORRECT
C: NOT_ATTEMPTED
""".strip()
def _build_grader_client() -> tuple[OpenAI | AzureOpenAI, str]:
import os
endpoint = os.environ.get('AZURE_OPENAI_ENDPOINT', '').strip()
api_version = os.environ.get('AZURE_OPENAI_API_VERSION', '').strip() or '2025-04-01-preview'
azure_key = os.environ.get('AZURE_OPENAI_API_KEY', '').strip()
openai_key = os.environ.get('OPENAI_API_KEY', '').strip()
api_key = azure_key or openai_key
if endpoint and api_version and api_key:
model = os.environ.get('SEALQA_GRADER_AZURE_MODEL', '').strip() or os.environ.get('SEALQA_GRADER_MODEL', '').strip() or os.environ.get('AZURE_MODEL_NAME', '').strip() or os.environ.get('OPTIMIZER_DEPLOYMENT', '').strip() or 'gpt-5.4'
client = AzureOpenAI(api_key=api_key, api_version=api_version, azure_endpoint=endpoint.rstrip('/'))
return client, model
if openai_key:
model = os.environ.get('SEALQA_GRADER_OPENAI_MODEL', '').strip() or os.environ.get('SEALQA_GRADER_MODEL', '').strip() or 'gpt-4.1-mini'
return OpenAI(api_key=openai_key), model
raise ValueError('Missing grader credentials for SealQA scoring.')
def _extract_text_content(content) -> str:
if content is None:
return ''
if isinstance(content, str):
return content
if isinstance(content, list):
parts = []
for part in content:
if isinstance(part, dict) and part.get('type') == 'text':
parts.append(str(part.get('text', '')))
else:
text = getattr(part, 'text', None)
if text:
parts.append(str(text))
return '\n'.join(parts).strip()
return str(content).strip()
def _normalize_text(text: str) -> str:
lowered = text.strip().lower()
lowered = re.sub(r'\s+', ' ', lowered)
lowered = re.sub(r'[^\w\s%.-]', '', lowered)
return lowered.strip()
def _fallback_score(ground_truth: str, predicted: str) -> float:
gold = _normalize_text(ground_truth)
pred = _normalize_text(predicted)
if not gold or not pred:
return 0.0
if gold == pred:
return 1.0
if gold in pred or pred in gold:
return 1.0
return 0.0
def score_sealqa(question: str, ground_truth: str, predicted: str) -> float:
try:
client, model = _build_grader_client()
except ValueError:
return _fallback_score(ground_truth, predicted)
prompt = GRADER_TEMPLATE.format(question=question, target=ground_truth, predicted_answer=predicted)
completion = client.chat.completions.create(model=model, messages=[{'role': 'user', 'content': prompt}])
content = _extract_text_content(completion.choices[0].message.content).strip().upper()
if content.startswith('A'):
return 1.0
return 0.0