
Evaluation
- 93 installs
- 941 repo stars
- Updated August 5, 2026
- guanyang/antigravity-skills
Build multi-dimensional evaluation frameworks for agent systems using rubrics, LLM-as-judge, and complexity-stratified test sets.
About
Explains how to evaluate non-deterministic agents by scoring outcomes across weighted dimensions rather than single metrics or fixed paths. A developer uses it when building quality gates, catching regressions, or comparing agent configurations.
- Multi-dimensional rubrics with LLM-as-judge plus human review and pass/fail thresholds
- Complexity-stratified test sets and the BrowseComp finding that token usage drives 80% of variance
Evaluation by the numbers
- 93 all-time installs (skills.sh)
- Ranked #4,706 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/guanyang/antigravity-skills --skill evaluationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 93 |
|---|---|
| repo stars | ★ 941 |
| Last updated | August 5, 2026 |
| Repository | guanyang/antigravity-skills ↗ |
What it does
Build multi-dimensional evaluation frameworks for agent systems using rubrics, LLM-as-judge, and complexity-stratified test sets.
Files
Evaluation Methods for Agent Systems
Evaluate agent systems differently from traditional software because agents make dynamic decisions, are non-deterministic between runs, and often lack single correct answers. Build evaluation frameworks that account for these characteristics, provide actionable feedback, catch regressions, and validate that context engineering choices achieve intended effects.
When to Activate
Activate this skill when:
- Testing agent performance systematically
- Validating context engineering choices
- Measuring improvements over time
- Catching regressions before deployment
- Building quality gates for agent pipelines
- Comparing different agent configurations
- Evaluating production systems continuously
Core Concepts
Focus evaluation on outcomes rather than execution paths, because agents may find alternative valid routes to goals. Judge whether the agent achieves the right outcome via a reasonable process, not whether it followed a specific sequence of steps.
Use multi-dimensional rubrics instead of single scores because one number hides critical failures in specific dimensions. Capture factual accuracy, completeness, citation accuracy, source quality, and tool efficiency as separate dimensions, then weight them for the use case.
Deploy LLM-as-judge for scalable evaluation across large test sets while supplementing with human review to catch edge cases, hallucinations, and subtle biases that automated evaluation misses.
Performance Drivers: The 95% Finding
Apply the BrowseComp research finding when designing evaluation budgets: three factors explain 95% of browsing agent performance variance.
| Factor | Variance Explained | Implication |
|---|---|---|
| Token usage | 80% | More tokens = better performance |
| Number of tool calls | ~10% | More exploration helps |
| Model choice | ~5% | Better models multiply efficiency |
Act on these implications when designing evaluations:
- Set realistic token budgets: Evaluate agents with production-realistic token limits, not unlimited resources, because token usage drives 80% of variance.
- Prioritize model upgrades over token increases: Upgrading model versions provides larger gains than doubling token budgets on previous versions because better models use tokens more efficiently.
- Validate multi-agent architectures: The finding supports distributing work across agents with separate context windows, so evaluate multi-agent setups against single-agent baselines.
Detailed Topics
Evaluation Challenges
Handle Non-Determinism and Multiple Valid Paths
Design evaluations that tolerate path variation because agents may take completely different valid paths to reach goals. One agent might search three sources while another searches ten; both may produce correct answers. Avoid checking for specific steps. Instead, define outcome criteria (correctness, completeness, quality) and score against those, treating the execution path as informational rather than evaluative.
Test Context-Dependent Failures
Evaluate across a range of complexity levels and interaction lengths because agent failures often depend on context in subtle ways. An agent might succeed on simple queries but fail on complex ones, work well with one tool set but fail with another, or degrade after extended interaction as context accumulates. Include simple, medium, complex, and very complex test cases to surface these patterns.
Score Composite Quality Dimensions Separately
Break agent quality into separate dimensions (factual accuracy, completeness, coherence, tool efficiency, process quality) and score each independently because an agent might score high on accuracy but low on efficiency, or vice versa. Then compute weighted aggregates tuned to use-case priorities. This approach reveals which dimensions need improvement rather than averaging away the signal.
Evaluation Rubric Design
Build Multi-Dimensional Rubrics
Define rubrics covering key dimensions with descriptive levels from excellent to failed. Include these core dimensions and adapt weights per use case:
- Factual accuracy: Claims match ground truth (weight heavily for knowledge tasks)
- Completeness: Output covers requested aspects (weight heavily for research tasks)
- Citation accuracy: Citations match claimed sources (weight for trust-sensitive contexts)
- Source quality: Uses appropriate primary sources (weight for authoritative outputs)
- Tool efficiency: Uses right tools a reasonable number of times (weight for cost-sensitive systems)
Convert Rubrics to Numeric Scores
Map dimension assessments to numeric scores (0.0 to 1.0), apply per-dimension weights, and calculate weighted overall scores. Set passing thresholds based on use-case requirements, typically 0.7 for general use and 0.9 for high-stakes applications. Store individual dimension scores alongside the aggregate because the breakdown drives targeted improvement.
Evaluation Methodologies
Use LLM-as-Judge for Scale
Build LLM-based evaluation prompts that include: clear task description, the agent output under test, ground truth when available, an evaluation scale with explicit level descriptions, and a request for structured judgment with reasoning. LLM judges provide consistent, scalable evaluation across large test sets. Use a different model family than the agent being evaluated to avoid self-enhancement bias.
Supplement with Human Evaluation
Route edge cases, unusual queries, and a random sample of production traffic to human reviewers because humans notice hallucinated answers, system failures, and subtle biases that automated evaluation misses. Track patterns across human reviews to identify systematic issues and feed findings back into automated evaluation criteria.
Apply End-State Evaluation for Stateful Agents
For agents that mutate persistent state (files, databases, configurations), evaluate whether the final state matches expectations rather than how the agent got there. Define expected end-state assertions and verify them programmatically after each test run.
Test Set Design
Select Representative Samples
Start with small samples (20-30 cases) during early development when changes have dramatic impacts and low-hanging fruit is abundant. Scale to 50+ cases for reliable signal as the system matures. Sample from real usage patterns, add known edge cases, and ensure coverage across complexity levels.
Stratify by Complexity
Structure test sets across complexity levels to prevent easy examples from inflating scores:
- Simple: single tool call, factual lookup
- Medium: multiple tool calls, comparison logic
- Complex: many tool calls, significant ambiguity
- Very complex: extended interaction, deep reasoning, synthesis
Report scores per stratum alongside overall scores to reveal where the agent actually struggles.
Context Engineering Evaluation
Validate Context Strategies Systematically
Run agents with different context strategies on the same test set and compare quality scores, token usage, and efficiency metrics. This isolates the effect of context engineering from other variables and prevents anecdote-driven decisions.
Run Degradation Tests
Test how context degradation affects performance by running agents at different context sizes. Identify performance cliffs where context becomes problematic and establish safe operating limits. Feed these limits back into context management strategies.
Continuous Evaluation
Build Automated Evaluation Pipelines
Integrate evaluation into the development workflow so evaluations run automatically on agent changes. Track results over time, compare versions, and block deployments that regress on key metrics.
Monitor Production Quality
Sample production interactions and evaluate them continuously. Set alerts for quality drops below warning (0.85 pass rate) and critical (0.70 pass rate) thresholds. Maintain dashboards showing trend analysis over time windows to detect gradual degradation.
Practical Guidance
Building Evaluation Frameworks
Follow this sequence to build an evaluation framework, because skipping early steps leads to measurements that do not reflect real quality:
1. Define quality dimensions relevant to the use case before writing any evaluation code, because dimensions chosen later tend to reflect what is easy to measure rather than what matters. 2. Create rubrics with clear, descriptive level definitions so evaluators (human or LLM) produce consistent scores. 3. Build test sets from real usage patterns and edge cases, stratified by complexity, with at least 50 cases for reliable signal. 4. Implement automated evaluation pipelines that run on every significant change. 5. Establish baseline metrics before making changes so improvements can be measured against a known reference. 6. Run evaluations on all significant changes and compare against the baseline. 7. Track metrics over time for trend analysis because gradual degradation is harder to notice than sudden drops. 8. Supplement automated evaluation with human review on a regular cadence.
Avoiding Evaluation Pitfalls
Guard against these common failures that undermine evaluation reliability:
- Overfitting to specific paths: Evaluate outcomes, not specific steps, because agents find novel valid paths.
- Ignoring edge cases: Include diverse test scenarios covering the full complexity spectrum.
- Single-metric obsession: Use multi-dimensional rubrics because a single score hides dimension-specific failures.
- Neglecting context effects: Test with realistic context sizes and histories rather than clean-room conditions.
- Skipping human evaluation: Automated evaluation misses subtle issues that humans catch reliably.
Examples
Example 1: Simple Evaluation
def evaluate_agent_response(response, expected):
rubric = load_rubric()
scores = {}
for dimension, config in rubric.items():
scores[dimension] = assess_dimension(response, expected, dimension)
overall = weighted_average(scores, config["weights"])
return {"passed": overall >= 0.7, "scores": scores}Example 2: Test Set Structure
Test sets should span multiple complexity levels to ensure comprehensive evaluation:
test_set = [
{
"name": "simple_lookup",
"input": "What is the capital of France?",
"expected": {"type": "fact", "answer": "Paris"},
"complexity": "simple",
"description": "Single tool call, factual lookup"
},
{
"name": "medium_query",
"input": "Compare the revenue of Apple and Microsoft last quarter",
"complexity": "medium",
"description": "Multiple tool calls, comparison logic"
},
{
"name": "multi_step_reasoning",
"input": "Analyze sales data from Q1-Q4 and create a summary report with trends",
"complexity": "complex",
"description": "Many tool calls, aggregation, analysis"
},
{
"name": "research_synthesis",
"input": "Research emerging AI technologies, evaluate their potential impact, and recommend adoption strategy",
"complexity": "very_complex",
"description": "Extended interaction, deep reasoning, synthesis"
}
]Guidelines
1. Use multi-dimensional rubrics, not single metrics 2. Evaluate outcomes, not specific execution paths 3. Cover complexity levels from simple to complex 4. Test with realistic context sizes and histories 5. Run evaluations continuously, not just before release 6. Supplement LLM evaluation with human review 7. Track metrics over time for trend detection 8. Set clear pass/fail thresholds based on use case
Gotchas
1. Overfitting evals to specific code paths: Tests pass but the agent fails on slight input variations. Write eval criteria against outcomes and semantics, not surface patterns, and rotate test inputs periodically. 2. LLM-judge self-enhancement bias: Models rate their own outputs higher than independent judges do. Use a different model family as the evaluation judge than the model being evaluated. 3. Test set contamination: Eval examples leak into training data or prompt templates, inflating scores. Keep eval sets versioned and separate from any data used in prompts or fine-tuning. 4. Metric gaming: Optimizing for the metric rather than actual quality produces agents that score well but disappoint users. Cross-validate automated metrics against human judgments regularly. 5. Single-dimension scoring: One aggregate number hides critical failures in specific dimensions. Always report per-dimension scores alongside the overall score, and fail the eval if any single dimension falls below its minimum threshold. 6. Eval set too small: Fewer than 50 examples produces unreliable signal with high variance between runs. Scale the eval set to at least 50 cases and report confidence intervals. 7. Not stratifying by difficulty: Easy examples inflate overall scores, masking failures on hard cases. Report scores per complexity stratum and weight the overall score to prevent easy-case dominance. 8. Treating eval as one-time: Evaluation must be continuous, not a launch gate. Agent quality drifts as models update, tools change, and usage patterns evolve. Run evals on every change and on a regular production cadence.
Integration
This skill connects to all other skills as a cross-cutting concern:
- context-fundamentals - Evaluating context usage
- context-degradation - Detecting degradation
- context-optimization - Measuring optimization effectiveness
- multi-agent-patterns - Evaluating coordination
- tool-design - Evaluating tool effectiveness
- memory-systems - Evaluating memory quality
References
Internal reference:
- Metrics Reference - Read when: designing specific evaluation metrics, choosing scoring scales, or implementing weighted rubric calculations
Internal skills:
- All other skills connect to evaluation for quality measurement
External resources:
- LLM evaluation benchmarks - Read when: selecting or building benchmark suites for agent comparison
- Agent evaluation research papers - Read when: adopting new evaluation methodologies or validating current approach
- Production monitoring practices - Read when: setting up alerting, dashboards, or sampling strategies for live systems
---
Skill Metadata
Created: 2025-12-20 Last Updated: 2026-03-17 Author: Agent Skills for Context Engineering Contributors Version: 1.1.0
Evaluation Reference: Metrics and Implementation
This document provides implementation details for evaluation metrics and evaluation systems.
Core Metric Definitions
Factual Accuracy
Factual accuracy measures whether claims in agent output match ground truth.
Excellent (1.0): All claims verified against ground truth, no errors
Good (0.8): Minor errors that do not affect main conclusions
Acceptable (0.6): Major claims correct, minor inaccuracies present
Poor (0.3): Significant factual errors in key claims
Failed (0.0): Fundamental factual errors that invalidate outputCalculation approach:
- Extract claims from output
- Verify each claim against ground truth
- Weight claims by importance (major claims more weight)
- Calculate weighted average of claim accuracy
Completeness
Completeness measures whether output covers all requested aspects.
Excellent (1.0): All requested aspects thoroughly covered
Good (0.8): Most aspects covered with minor gaps
Acceptable (0.6): Key aspects covered, some gaps
Poor (0.3): Major aspects missing from output
Failed (0.0): Fundamental aspects not addressedCitation Accuracy
Citation accuracy measures whether cited sources match claimed sources.
Excellent (1.0): All citations accurate and complete
Good (0.8): Minor citation formatting issues
Acceptable (0.6): Major citations accurate
Poor (0.3): Significant citation problems
Failed (0.0): Citations missing or completely incorrectSource Quality
Source quality measures whether appropriate primary sources were used.
Excellent (1.0): Primary authoritative sources
Good (0.8): Mostly primary sources with some secondary
Acceptable (0.6): Mix of primary and secondary sources
Poor (0.3): Mostly secondary or unreliable sources
Failed (0.0): No credible sources citedTool Efficiency
Tool efficiency measures whether the agent used appropriate tools a reasonable number of times.
Excellent (1.0): Optimal tool selection and call count
Good (0.8): Good tool selection with minor inefficiencies
Acceptable (0.6): Appropriate tools with some redundancy
Poor (0.3): Wrong tools or excessive call counts
Failed (0.0): Severe tool misuse or extremely excessive callsRubric Implementation
EVALUATION_DIMENSIONS = {
"factual_accuracy": {
"weight": 0.30,
"description": "Claims match ground truth",
"levels": {
"excellent": 1.0,
"good": 0.8,
"acceptable": 0.6,
"poor": 0.3,
"failed": 0.0
}
},
"completeness": {
"weight": 0.25,
"description": "All requested aspects covered",
"levels": {
"excellent": 1.0,
"good": 0.8,
"acceptable": 0.6,
"poor": 0.3,
"failed": 0.0
}
},
"citation_accuracy": {
"weight": 0.15,
"description": "Citations match sources",
"levels": {
"excellent": 1.0,
"good": 0.8,
"acceptable": 0.6,
"poor": 0.3,
"failed": 0.0
}
},
"source_quality": {
"weight": 0.10,
"description": "Appropriate primary sources used",
"levels": {
"excellent": 1.0,
"good": 0.8,
"acceptable": 0.6,
"poor": 0.3,
"failed": 0.0
}
},
"tool_efficiency": {
"weight": 0.20,
"description": "Right tools used reasonably",
"levels": {
"excellent": 1.0,
"good": 0.8,
"acceptable": 0.6,
"poor": 0.3,
"failed": 0.0
}
}
}
def calculate_overall_score(dimension_scores, rubric):
"""Calculate weighted overall score from dimension scores."""
total_weight = 0
weighted_sum = 0
for dimension, score in dimension_scores.items():
if dimension in rubric:
weight = rubric[dimension]["weight"]
weighted_sum += score * weight
total_weight += weight
return weighted_sum / total_weight if total_weight > 0 else 0Test Set Management
class TestSet:
def __init__(self, name):
self.name = name
self.tests = []
self.tags = {}
def add_test(self, test_case):
"""Add test case to test set."""
self.tests.append(test_case)
# Index by tags
for tag in test_case.get("tags", []):
if tag not in self.tags:
self.tags[tag] = []
self.tags[tag].append(len(self.tests) - 1)
def filter(self, **criteria):
"""Filter tests by criteria."""
filtered = []
for test in self.tests:
match = True
for key, value in criteria.items():
if test.get(key) != value:
match = False
break
if match:
filtered.append(test)
return filtered
def get_complexity_distribution(self):
"""Get distribution of tests by complexity."""
distribution = {}
for test in self.tests:
complexity = test.get("complexity", "medium")
distribution[complexity] = distribution.get(complexity, 0) + 1
return distributionEvaluation Runner
class EvaluationRunner:
def __init__(self, test_set, rubric, agent):
self.test_set = test_set
self.rubric = rubric
self.agent = agent
self.results = []
def run_all(self, verbose=False):
"""Run evaluation on all tests."""
self.results = []
for i, test in enumerate(self.test_set.tests):
if verbose:
print(f"Running test {i+1}/{len(self.test_set.tests)}")
result = self.run_test(test)
self.results.append(result)
return self.summarize()
def run_test(self, test):
"""Run single evaluation test."""
# Get agent output
output = self.agent.run(test["input"])
# Evaluate
evaluation = self.evaluate_output(output, test)
return {
"test": test,
"output": output,
"evaluation": evaluation
}
def evaluate_output(self, output, test):
"""Evaluate agent output against test."""
ground_truth = test.get("expected", {})
dimension_scores = {}
for dimension, config in self.rubric.items():
score = self.evaluate_dimension(
output, ground_truth, dimension, config
)
dimension_scores[dimension] = score
overall = calculate_overall_score(dimension_scores, self.rubric)
return {
"overall_score": overall,
"dimension_scores": dimension_scores,
"passed": overall >= 0.7
}
def summarize(self):
"""Summarize evaluation results."""
if not self.results:
return {"error": "No results"}
passed = sum(1 for r in self.results if r["evaluation"]["passed"])
dimension_totals = {}
for dimension in self.rubric.keys():
dimension_totals[dimension] = {
"total": 0,
"count": 0
}
for result in self.results:
for dimension, score in result["evaluation"]["dimension_scores"].items():
if dimension in dimension_totals:
dimension_totals[dimension]["total"] += score
dimension_totals[dimension]["count"] += 1
dimension_averages = {}
for dimension, data in dimension_totals.items():
if data["count"] > 0:
dimension_averages[dimension] = data["total"] / data["count"]
return {
"total_tests": len(self.results),
"passed": passed,
"failed": len(self.results) - passed,
"pass_rate": passed / len(self.results) if self.results else 0,
"dimension_averages": dimension_averages,
"failures": [
r for r in self.results
if not r["evaluation"]["passed"]
]
}Production Monitoring
class ProductionMonitor:
def __init__(self, sample_rate=0.01):
self.sample_rate = sample_rate
self.samples = []
self.alert_thresholds = {
"pass_rate_warning": 0.85,
"pass_rate_critical": 0.70
}
def sample_and_evaluate(self, query, output):
"""Sample production interaction for evaluation."""
if random.random() > self.sample_rate:
return None
evaluation = evaluate_output(output, {}, EVALUATION_RUBRIC)
sample = {
"query": query[:200],
"output_preview": output[:200],
"score": evaluation["overall_score"],
"passed": evaluation["passed"],
"timestamp": current_timestamp()
}
self.samples.append(sample)
return sample
def get_metrics(self):
"""Calculate current metrics from samples."""
if not self.samples:
return {"status": "insufficient_data"}
passed = sum(1 for s in self.samples if s["passed"])
pass_rate = passed / len(self.samples)
avg_score = sum(s["score"] for s in self.samples) / len(self.samples)
return {
"sample_count": len(self.samples),
"pass_rate": pass_rate,
"average_score": avg_score,
"status": self._get_status(pass_rate)
}
def _get_status(self, pass_rate):
"""Get status based on pass rate."""
if pass_rate < self.alert_thresholds["pass_rate_critical"]:
return "critical"
elif pass_rate < self.alert_thresholds["pass_rate_warning"]:
return "warning"
else:
return "healthy""""Agent Evaluation Framework for context-engineered agent systems.
Use when: building evaluation pipelines, scoring agent outputs against
multi-dimensional rubrics, managing test sets, or monitoring production
agent quality. Provides composable classes that can be used independently
or wired together into a full evaluation pipeline.
Typical usage::
evaluator = AgentEvaluator()
test_set = TestSet("my_tests").create_standard_tests()
runner = EvaluationRunner(evaluator, test_set)
summary = runner.run_all(verbose=True)
print(summary)
"""
from typing import Dict, List, Any, Optional
from dataclasses import dataclass
from enum import Enum
import time
__all__ = [
"ScoreLevel",
"RubricDimension",
"DEFAULT_RUBRIC",
"AgentEvaluator",
"TestSet",
"EvaluationRunner",
"ProductionMonitor",
]
class ScoreLevel(Enum):
"""Use when: mapping qualitative judgments to numeric scores."""
EXCELLENT = 1.0
GOOD = 0.8
ACCEPTABLE = 0.6
POOR = 0.3
FAILED = 0.0
@dataclass
class RubricDimension:
"""Definition of a single evaluation dimension.
Use when: defining custom rubric dimensions beyond the defaults.
"""
name: str
weight: float
description: str
levels: Dict[str, str] # level_name -> description
DEFAULT_RUBRIC: Dict[str, RubricDimension] = {
"factual_accuracy": RubricDimension(
name="factual_accuracy",
weight=0.30,
description="Claims in output match ground truth",
levels={
"excellent": "All claims verified, no errors",
"good": "Minor errors not affecting main conclusions",
"acceptable": "Major claims correct, minor inaccuracies",
"poor": "Significant factual errors",
"failed": "Fundamental factual errors",
},
),
"completeness": RubricDimension(
name="completeness",
weight=0.25,
description="Output covers all requested aspects",
levels={
"excellent": "All aspects thoroughly covered",
"good": "Most aspects covered, minor gaps",
"acceptable": "Key aspects covered, some gaps",
"poor": "Major aspects missing",
"failed": "Fundamental aspects missing",
},
),
"citation_accuracy": RubricDimension(
name="citation_accuracy",
weight=0.15,
description="Citations match claimed sources",
levels={
"excellent": "All citations accurate and complete",
"good": "Minor citation issues",
"acceptable": "Major citations accurate",
"poor": "Significant citation problems",
"failed": "Citations missing or incorrect",
},
),
"source_quality": RubricDimension(
name="source_quality",
weight=0.10,
description="Uses appropriate primary sources",
levels={
"excellent": "Primary sources, authoritative",
"good": "Mostly primary, some secondary",
"acceptable": "Mix of primary and secondary",
"poor": "Mostly secondary or unreliable",
"failed": "No credible sources",
},
),
"tool_efficiency": RubricDimension(
name="tool_efficiency",
weight=0.20,
description="Uses right tools reasonable number of times",
levels={
"excellent": "Optimal tool selection and count",
"good": "Good tool selection, minor inefficiencies",
"acceptable": "Appropriate tools, some redundancy",
"poor": "Wrong tools or excessive calls",
"failed": "Severe tool misuse",
},
),
}
# ---------------------------------------------------------------------------
# Evaluation Engine
# ---------------------------------------------------------------------------
class AgentEvaluator:
"""Main evaluation engine for agent outputs.
Use when: scoring a single agent output against a multi-dimensional rubric.
Instantiate with a custom rubric or rely on ``DEFAULT_RUBRIC``.
"""
def __init__(self, rubric: Optional[Dict[str, RubricDimension]] = None) -> None:
self.rubric: Dict[str, RubricDimension] = rubric or DEFAULT_RUBRIC
self.evaluation_history: List[Dict[str, Any]] = []
def evaluate(
self,
task: Dict[str, Any],
output: str,
ground_truth: Optional[Dict[str, Any]] = None,
tool_calls: Optional[List[Dict[str, Any]]] = None,
) -> Dict[str, Any]:
"""Evaluate agent output against task requirements.
Use when: you have a single (task, output) pair and need per-dimension
scores plus an overall pass/fail verdict.
Returns evaluation results with per-dimension scores.
"""
scores: Dict[str, Dict[str, Any]] = {}
for dimension_name, dimension in self.rubric.items():
score = self._evaluate_dimension(
dimension=dimension,
task=task,
output=output,
ground_truth=ground_truth,
tool_calls=tool_calls,
)
scores[dimension_name] = {
"score": score,
"weight": dimension.weight,
"level": self._score_to_level(score),
}
# Calculate weighted overall
overall: float = sum(
s["score"] * self.rubric[k].weight for k, s in scores.items()
)
result: Dict[str, Any] = {
"overall_score": overall,
"dimension_scores": scores,
"passed": overall >= 0.7,
"timestamp": time.time(),
}
self.evaluation_history.append(result)
return result
def _evaluate_dimension(
self,
dimension: RubricDimension,
task: Dict[str, Any],
output: str,
ground_truth: Optional[Dict[str, Any]] = None,
tool_calls: Optional[List[Dict[str, Any]]] = None,
) -> float:
"""Evaluate a single dimension.
Use when: extending the evaluator with custom dimension logic.
In production, replace heuristics with LLM judgment or human evaluation.
"""
output_lower: str = output.lower()
task_type: str = task.get("type", "")
if dimension.name == "factual_accuracy":
if ground_truth:
return self._check_factual_accuracy(output, ground_truth)
return 0.7 # Default assumption
elif dimension.name == "completeness":
required: List[str] = task.get("requirements", [])
if required:
covered = sum(1 for r in required if r.lower() in output_lower)
return covered / len(required)
return 0.8
elif dimension.name == "citation_accuracy":
if task.get("requires_citations"):
# Look for citation patterns like [1], [Author 2024], [source]
# Avoid false positives from code brackets or JSON
citation_pattern = r'\[\d+\]|\[[A-Z][a-z]+(?:\s+(?:et al\.?|&)\s+[A-Z][a-z]+)?\s*[\d,]+\]|\[(?:source|ref|cite)[^\]]*\]'
import re as _re
citations_found = _re.findall(citation_pattern, output)
if len(citations_found) >= 1:
return 1.0
elif any(marker in output_lower for marker in ["according to", "cited in", "reported by"]):
return 0.7
return 0.4
return 0.8 # Citations not required
elif dimension.name == "source_quality":
quality_markers = ["according to", "reported by", "data from", "study"]
quality_count = sum(1 for m in quality_markers if m in output_lower)
return min(1.0, 0.5 + quality_count * 0.1)
elif dimension.name == "tool_efficiency":
if tool_calls:
expected_count = self._estimate_expected_tools(task_type)
actual_count = len(tool_calls)
if actual_count <= expected_count:
return 1.0
elif actual_count <= expected_count * 1.5:
return 0.7
else:
return 0.4
return 0.8 # No tool calls needed or recorded
return 0.5 # Default
def _check_factual_accuracy(
self, output: str, ground_truth: Dict[str, Any]
) -> float:
"""Check output against ground truth.
Use when: ground truth key_claims are available for comparison.
"""
if not ground_truth:
return 0.7
key_claims: List[str] = ground_truth.get("key_claims", [])
if not key_claims:
return 0.7
output_lower: str = output.lower()
matched: int = sum(1 for claim in key_claims if claim.lower() in output_lower)
if matched == len(key_claims):
return 1.0
elif matched >= len(key_claims) * 0.7:
return 0.8
elif matched >= len(key_claims) * 0.5:
return 0.6
else:
return 0.3
def _estimate_expected_tools(self, task_type: str) -> int:
"""Estimate expected tool count for task type."""
estimates: Dict[str, int] = {
"research": 3,
"create": 2,
"analyze": 2,
"general": 1,
}
return estimates.get(task_type, 1)
def _score_to_level(self, score: float) -> str:
"""Convert numeric score to level name."""
if score >= 0.9:
return "excellent"
elif score >= 0.7:
return "good"
elif score >= 0.5:
return "acceptable"
elif score >= 0.25:
return "poor"
else:
return "failed"
# ---------------------------------------------------------------------------
# Test Set Management
# ---------------------------------------------------------------------------
class TestSet:
"""Manage evaluation test sets with tagging and complexity stratification.
Use when: building, filtering, or analyzing collections of evaluation
test cases. Supports tag-based indexing and complexity distribution
analysis.
"""
def __init__(self, name: str) -> None:
self.name: str = name
self.tests: List[Dict[str, Any]] = []
self.tags: Dict[str, List[int]] = {}
def add_test(self, test: Dict[str, Any]) -> None:
"""Add a test case to the test set.
Use when: incrementally building a test set from individual cases.
"""
self.tests.append(test)
idx: int = len(self.tests) - 1
for tag in test.get("tags", []):
if tag not in self.tags:
self.tags[tag] = []
self.tags[tag].append(idx)
def filter(self, **criteria: Any) -> List[Dict[str, Any]]:
"""Filter tests by criteria.
Use when: selecting a subset of tests matching specific field values.
"""
results: List[Dict[str, Any]] = []
for test in self.tests:
match = True
for key, value in criteria.items():
if test.get(key) != value:
match = False
break
if match:
results.append(test)
return results
def get_complexity_distribution(self) -> Dict[str, int]:
"""Get distribution of tests by complexity.
Use when: verifying test set balance across difficulty levels.
"""
distribution: Dict[str, int] = {}
for test in self.tests:
complexity: str = test.get("complexity", "medium")
distribution[complexity] = distribution.get(complexity, 0) + 1
return distribution
def create_standard_tests(self) -> "TestSet":
"""Populate with standard test cases for context engineering evaluation.
Use when: bootstrapping a test set quickly for initial development.
"""
tests: List[Dict[str, Any]] = [
{
"name": "simple_lookup",
"input": "What is the capital of France?",
"expected": {"type": "fact", "answer": "Paris"},
"complexity": "simple",
"tags": ["knowledge", "simple"],
},
{
"name": "context_retrieval",
"input": "Based on the user preferences, recommend a restaurant",
"context": {
"user_preferences": {
"cuisine": "Italian",
"price_range": "moderate",
}
},
"complexity": "medium",
"tags": ["retrieval", "reasoning"],
},
{
"name": "multi_step_reasoning",
"input": "Analyze the sales data and create a summary report",
"complexity": "complex",
"tags": ["analysis", "multi-step"],
},
]
for test in tests:
self.add_test(test)
return self
# ---------------------------------------------------------------------------
# Evaluation Runner
# ---------------------------------------------------------------------------
class EvaluationRunner:
"""Run evaluations across an entire test set and produce summaries.
Use when: executing a full evaluation pass over a test set, comparing
agent versions, or generating evaluation reports.
"""
def __init__(self, evaluator: AgentEvaluator, test_set: TestSet) -> None:
self.evaluator: AgentEvaluator = evaluator
self.test_set: TestSet = test_set
self.results: List[Dict[str, Any]] = []
def run_all(self, verbose: bool = False) -> Dict[str, Any]:
"""Run evaluation on all tests in the test set.
Use when: performing a complete evaluation pass.
"""
self.results = []
for i, test in enumerate(self.test_set.tests):
if verbose:
print(
f"Running test {i + 1}/{len(self.test_set.tests)}: {test['name']}"
)
result = self.run_test(test)
self.results.append(result)
return self.summarize()
def run_test(self, test: Dict[str, Any]) -> Dict[str, Any]:
"""Run a single evaluation test.
Use when: evaluating an individual test case outside of a full run.
In production, replace the simulated output with actual agent execution.
"""
# In production, run actual agent
# Here we simulate
output: str = f"Simulated output for: {test.get('input', '')}"
evaluation: Dict[str, Any] = self.evaluator.evaluate(
task=test,
output=output,
ground_truth=test.get("expected"),
tool_calls=[],
)
return {
"test": test,
"output": output,
"evaluation": evaluation,
"passed": evaluation["passed"],
}
def summarize(self) -> Dict[str, Any]:
"""Summarize evaluation results with per-dimension averages.
Use when: generating a report after a full evaluation run.
"""
if not self.results:
return {"error": "No results"}
passed: int = sum(1 for r in self.results if r["passed"])
# Dimension averages
dimension_totals: Dict[str, Dict[str, float]] = {}
for dim_name in self.evaluator.rubric.keys():
dimension_totals[dim_name] = {"total": 0.0, "count": 0.0}
for result in self.results:
for dim_name, score in result["evaluation"]["dimension_scores"].items():
dimension_totals[dim_name]["total"] += score["score"]
dimension_totals[dim_name]["count"] += 1
dimension_averages: Dict[str, float] = {}
for dim_name, data in dimension_totals.items():
if data["count"] > 0:
dimension_averages[dim_name] = data["total"] / data["count"]
return {
"total_tests": len(self.results),
"passed": passed,
"failed": len(self.results) - passed,
"pass_rate": passed / len(self.results) if self.results else 0,
"dimension_averages": dimension_averages,
"failures": [
{
"test": r["test"]["name"],
"score": r["evaluation"]["overall_score"],
}
for r in self.results
if not r["passed"]
],
}
# ---------------------------------------------------------------------------
# Production Monitoring
# ---------------------------------------------------------------------------
class ProductionMonitor:
"""Monitor agent performance in production via sampling.
Use when: setting up continuous quality monitoring for a deployed agent.
Samples interactions at a configurable rate and tracks pass rate, average
score, and alert status.
"""
def __init__(self, sample_rate: float = 0.01) -> None:
import random
self.sample_rate: float = sample_rate
self._rng: random.Random = random.Random()
self.samples: List[Dict[str, Any]] = []
self.alert_thresholds: Dict[str, float] = {
"pass_rate_warning": 0.85,
"pass_rate_critical": 0.70,
}
def should_sample(self) -> bool:
"""Determine if current interaction should be sampled.
Use when: deciding at request time whether to evaluate this interaction.
"""
return self._rng.random() < self.sample_rate
def record_sample(
self, query: str, output: str, evaluation: Dict[str, Any]
) -> None:
"""Record a production sample for evaluation.
Use when: storing evaluated production interactions for trend analysis.
"""
sample: Dict[str, Any] = {
"query": query[:200],
"output_preview": output[:200],
"score": evaluation.get("overall_score", 0),
"passed": evaluation.get("passed", False),
"timestamp": time.time(),
}
self.samples.append(sample)
def get_metrics(self) -> Dict[str, Any]:
"""Calculate current metrics from collected samples.
Use when: checking production health or generating monitoring reports.
"""
if not self.samples:
return {"status": "insufficient_data"}
passed: int = sum(1 for s in self.samples if s["passed"])
pass_rate: float = passed / len(self.samples)
avg_score: float = sum(s["score"] for s in self.samples) / len(self.samples)
status: str = "healthy"
if pass_rate < self.alert_thresholds["pass_rate_critical"]:
status = "critical"
elif pass_rate < self.alert_thresholds["pass_rate_warning"]:
status = "warning"
return {
"sample_count": len(self.samples),
"pass_rate": pass_rate,
"average_score": avg_score,
"status": status,
"alerts": self._generate_alerts(pass_rate, avg_score),
}
def _generate_alerts(
self, pass_rate: float, avg_score: float
) -> List[Dict[str, str]]:
"""Generate alerts based on metrics."""
alerts: List[Dict[str, str]] = []
if pass_rate < self.alert_thresholds["pass_rate_critical"]:
alerts.append(
{
"type": "critical",
"message": f"Pass rate ({pass_rate:.2f}) below critical threshold",
}
)
elif pass_rate < self.alert_thresholds["pass_rate_warning"]:
alerts.append(
{
"type": "warning",
"message": f"Pass rate ({pass_rate:.2f}) below warning threshold",
}
)
if avg_score < 0.6:
alerts.append(
{
"type": "quality",
"message": f"Average score ({avg_score:.2f}) indicates quality issues",
}
)
return alerts
# ---------------------------------------------------------------------------
# CLI entry point
# ---------------------------------------------------------------------------
if __name__ == "__main__":
print("=== Agent Evaluation Framework Demo ===\n")
# 1. Create evaluator with default rubric
evaluator = AgentEvaluator()
print(f"Rubric dimensions: {list(evaluator.rubric.keys())}\n")
# 2. Build a standard test set
test_set = TestSet("demo").create_standard_tests()
print(f"Test set: {test_set.name}")
print(f"Test count: {len(test_set.tests)}")
print(f"Complexity distribution: {test_set.get_complexity_distribution()}\n")
# 3. Run evaluation
runner = EvaluationRunner(evaluator, test_set)
summary = runner.run_all(verbose=True)
print(f"\n--- Summary ---")
print(f"Total: {summary['total_tests']}")
print(f"Passed: {summary['passed']}")
print(f"Failed: {summary['failed']}")
print(f"Pass rate: {summary['pass_rate']:.1%}")
print(f"Dimension averages: {summary['dimension_averages']}")
if summary["failures"]:
print(f"\nFailures:")
for f in summary["failures"]:
print(f" - {f['test']}: {f['score']:.2f}")