Skip to content

SOP-003: Eval Creation

Document Control

PropertyValue
SOP ID003
TitleEval Creation
Version1.0
StatusActive
ComplexityMedium

Purpose

Create evaluations with four complementary graders that balance deterministic checks with semantic judgment.

Prerequisites

The Four Graders

┌─────────────────────────────────────────────────────────────────────┐
│                        GRADER ARCHITECTURE                           │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  ┌──────────────────────────────────────────────────────────────┐   │
│  │                    DETERMINISTIC GRADERS                      │   │
│  │  ┌────────────────────┐    ┌────────────────────┐            │   │
│  │  │ chemical_name      │    │ word_length        │            │   │
│  │  │ _grader            │    │ _deviation_grader  │            │   │
│  │  │                    │    │                    │            │   │
│  │  │ Type: python       │    │ Type: python       │            │   │
│  │  │ Threshold: 0.8     │    │ Threshold: 0.85    │            │   │
│  │  │ Purpose: Entity    │    │ Purpose: Length    │            │   │
│  │  │ preservation       │    │ discipline         │            │   │
│  │  └────────────────────┘    └────────────────────┘            │   │
│  └──────────────────────────────────────────────────────────────┘   │
│                                                                      │
│  ┌──────────────────────────────────────────────────────────────┐   │
│  │                     SEMANTIC GRADERS                          │   │
│  │  ┌────────────────────┐    ┌────────────────────┐            │   │
│  │  │ cosine_similarity  │    │ llm_as_judge       │            │   │
│  │  │                    │    │                    │            │   │
│  │  │ Type: text_        │    │ Type: score_model  │            │   │
│  │  │ similarity         │    │ Model: gpt-4.1     │            │   │
│  │  │ Threshold: 0.85    │    │ Threshold: 0.85    │            │   │
│  │  │ Purpose: Semantic  │    │ Purpose: Holistic  │            │   │
│  │  │ anchoring          │    │ quality check      │            │   │
│  │  └────────────────────┘    └────────────────────┘            │   │
│  └──────────────────────────────────────────────────────────────┘   │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

Grader Summary Table

GraderTypePass ThresholdWhat It ChecksWhy
chemical_name_graderpython0.8Exact chemical names in summaryPreserves critical domain entities
word_length_deviation_graderpython0.85Deviation from 100-word targetKeeps summaries concise and comparable
cosine_similaritytext_similarity0.85Cosine similarity to sourceAnchors summary to source content
llm_as_judgescore_model0.85Rubric-driven quality scoreCaptures nuanced quality signals

Step-by-Step Procedure

Step 1: Import Dependencies

python
import os
from openai import OpenAI
from dotenv import load_dotenv

load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

Step 2: Define Data Source Configuration

python
data_source_config = {
    "type": "custom",
    "item_schema": {
        "type": "object",
        "properties": {
            "section": {"type": "string"},
            "summary": {"type": "string"}
        },
        "required": ["section", "summary"],
    },
    "include_sample_schema": False,
}

Step 3: Create Chemical Name Grader

This Python grader checks if chemical names from the source appear in the summary:

python
chemical_name_grader = {
    "type": "python",
    "name": "chemical_name_grader",
    "image_tag": "2025-05-08",
    "pass_threshold": 0.8,
    "source": r"""def grade(sample: dict, item: dict) -> float:
    section = item["section"]
    summary = item["summary"]

    CHEMICALS_MASTER = [
        "[1-13C]Pyruvic acid", "[1-13C]Pyruvate",
        "Sodium [1-13C]pyruvate", "AH111501 sodium salt",
        "TRIS", "NaOH", "Na2EDTA", "EDTA",
        "Hyperpolarized Pyruvate (13C) Injection",
        # Add your domain-specific terms here
    ]

    # Find chemicals present in the section
    present = [chem for chem in CHEMICALS_MASTER if chem in section]

    # If no chemicals present, consider it satisfied
    if not present:
        return 1.0

    # Count how many appear in the summary
    correct = sum(1 for chem in present if chem in summary)

    return correct / len(present)
""",
}

Step 4: Create Word Length Grader

This Python grader penalizes summaries that deviate from the target length:

python
word_length_grader = {
    "type": "python",
    "name": "word_length_deviation_grader",
    "image_tag": "2025-05-08",
    "pass_threshold": 0.85,
    "source": r"""
def grade(sample: dict, item: dict) -> float:
    summary = item["summary"]
    word_count = len(summary.split())

    expected_summary_length = 100
    tolerance = 0.2  # 20% band around target

    # Calculate relative deviation
    deviation = abs(word_count - expected_summary_length) / expected_summary_length

    # If within tolerance band, full score
    if deviation <= tolerance:
        return 1.0

    # Outside band: score decays linearly, capped at 0
    score = 1.0 - (deviation - tolerance)
    return max(0.0, score)
""",
}

Step 5: Create Cosine Similarity Grader

Built-in grader for semantic similarity:

python
cosine_similarity_grader = {
    "name": "cosine_similarity",
    "type": "text_similarity",
    "input": "{ { item.summary } }",     # Remove spaces in actual code
    "reference": "{ { item.section } }",  # Remove spaces in actual code
    "evaluation_metric": "cosine",
    "pass_threshold": 0.85,
}

Step 6: Create LLM-as-Judge Grader

Uses GPT-4.1 to evaluate summary quality:

python
llm_as_judge_grader = {
    "name": "llm_as_judge",
    "type": "score_model",
    "model": "gpt-4.1",
    "input": [
        {
            "role": "system",
            "content": (
                "You are an expert technical summarization evaluator. "
                "Evaluate whether the summary captures and preserves the important "
                "technical facts and specific details from the section.\n\n"
                "Scoring Guidelines:\n"
                "- Return a numerical score between 0 and 1.\n"
                "- 1.0: Flawless - comprehensive, faithful, accurate\n"
                "- 0.75-0.99: Excellent - all main facts, minor omissions\n"
                "- 0.5-0.75: Good - most info retained, some details missing\n"
                "- 0.3-0.5: Significant info missing or inaccuracies\n"
                "- 0.0-0.3: Major omissions or failures\n\n"
                "Respond only with a single number between 0 and 1."
            ),
        },
        {
            "role": "user",
            "content": (
                "Section:\n{ {item.section} }\n"
                "Summary:\n{ {sample.output_text} }"
            ),
        },
    ],
    "range": [0, 1],
    "pass_threshold": 0.85,
}

Step 7: Create the Eval

python
testing_criteria = [
    chemical_name_grader,
    word_length_grader,
    cosine_similarity_grader,
    llm_as_judge_grader,
]

eval = client.evals.create(
    name="self_evolving_eval",
    data_source_config=data_source_config,
    testing_criteria=testing_criteria,
)

print(f"Created Eval: {eval.id}")

Step 8: Store Eval ID

Save the eval ID for use in the evaluation loop:

python
# Save to config
EVAL_ID = eval.id

# Or save to file
with open('eval_id.txt', 'w') as f:
    f.write(eval.id)

Complete Code

python
import os
from openai import OpenAI
from dotenv import load_dotenv

load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

# Data source configuration
data_source_config = {
    "type": "custom",
    "item_schema": {
        "type": "object",
        "properties": {
            "section": {"type": "string"},
            "summary": {"type": "string"}
        },
        "required": ["section", "summary"],
    },
    "include_sample_schema": False,
}

# All four graders
testing_criteria = [
    {
        "type": "python",
        "name": "chemical_name_grader",
        "image_tag": "2025-05-08",
        "pass_threshold": 0.8,
        "source": r"""def grade(sample: dict, item: dict) -> float:
    section = item["section"]
    summary = item["summary"]
    CHEMICALS = ["[1-13C]Pyruvic acid", "[1-13C]pyruvate", "TRIS", "NaOH"]
    present = [c for c in CHEMICALS if c in section]
    if not present: return 1.0
    correct = sum(1 for c in present if c in summary)
    return correct / len(present)
""",
    },
    {
        "type": "python",
        "name": "word_length_deviation_grader",
        "image_tag": "2025-05-08",
        "pass_threshold": 0.85,
        "source": r"""def grade(sample: dict, item: dict) -> float:
    summary = item["summary"]
    word_count = len(summary.split())
    expected = 100
    tolerance = 0.2
    deviation = abs(word_count - expected) / expected
    if deviation <= tolerance: return 1.0
    return max(0.0, 1.0 - (deviation - tolerance))
""",
    },
    {
        "name": "cosine_similarity",
        "type": "text_similarity",
        "input": "{ { item.summary } }",
        "reference": "{ { item.section } }",
        "evaluation_metric": "cosine",
        "pass_threshold": 0.85,
    },
    {
        "name": "llm_as_judge",
        "type": "score_model",
        "model": "gpt-4.1",
        "input": [
            {"role": "system", "content": "Score summary quality 0-1. Respond with only a number."},
            {"role": "user", "content": "Section:\n{ {item.section} }\nSummary:\n{ {sample.output_text} }"}
        ],
        "range": [0, 1],
        "pass_threshold": 0.85,
    },
]

# Create eval
eval = client.evals.create(
    name="self_evolving_eval",
    data_source_config=data_source_config,
    testing_criteria=testing_criteria,
)

print(f"✅ Created Eval: {eval.id}")

Verification Checklist

  • [ ] All four graders defined with correct types
  • [ ] Pass thresholds set appropriately (0.8-0.85)
  • [ ] Data source schema matches your dataset
  • [ ] Eval created successfully with valid ID
  • [ ] Eval ID stored for later use

Troubleshooting

Issue: Python grader syntax error

Solution: Ensure raw string (r""") is used and no special characters are escaped incorrectly.

Issue: LLM-as-judge returns text instead of number

Solution: Add explicit instruction: "Respond with only a single number."

Issue: Cosine similarity always returns 0

Solution: Check that input and reference templates match your item schema field names.

See Also

Based on OpenAI Cookbook - Bain & OpenAI Collaboration