mcpuniverse / docs /custom-evaluators-guide.md
haochengsama's picture
Add files using upload-large-folder tool
86acc45 verified
|
Raw
History Blame Contribute Delete
8.66 kB
# Custom Evaluators Implementation Guide
This guide provides comprehensive documentation for implementing custom evaluators in MCPUniverse. Evaluators are essential components that assess agent performance against specific criteria and validation rules.
## Table of Contents
1. [Evaluator System Overview](#evaluator-system-overview)
2. [Architecture and Interface](#architecture-and-interface)
3. [Function Types and Decorators](#function-types-and-decorators)
4. [Implementation Steps](#implementation-steps)
5. [Evaluation Functions](#evaluation-functions)
6. [Comparison Functions](#comparison-functions)
## Evaluator System Overview
The evaluator system is designed to validate agent outputs against predefined criteria. It consists of two main function types:
1. **Evaluation Functions**: Transform and extract data from agent responses
2. **Comparison Functions**: Compare processed data against expected values
### Key Components
- **Evaluator Class**: Main evaluation orchestrator
- **EvaluatorConfig**: Configuration specification for evaluation rules
- **EvaluationResult**: Output containing evaluation results and reasoning
- **FunctionResult**: Wrapper for function outputs in the evaluation pipeline
### System Architecture
```
Agent Output → Evaluation Functions → Comparison Functions → EvaluationResult
↓ ↓ ↓ ↓
Raw JSON Data Extraction Value Validation Pass/Fail + Reason
```
## Architecture and Interface
### Core Classes
#### EvaluatorConfig
```python
class EvaluatorConfig(BaseModel):
func: str # Function chain, e.g., "json -> get(key) -> len"
op: str = "" # Comparison operator, e.g., "=", "<", "contains"
value: Any = None # Expected value for comparison
op_args: Any = None # Additional arguments for comparison
desc: str = "" # Description for reporting
```
#### EvaluationResult
```python
class EvaluationResult(BaseModel):
config: EvaluatorConfig # Original configuration
response: str | Dict # Agent response being evaluated
passed: bool # Whether evaluation passed
reason: str = "" # Failure reason if applicable
error: str = "" # Error message if execution failed
```
#### FunctionResult
```python
class FunctionResult(BaseModel):
result: Any # The actual result data
```
### Evaluation Flow
1. **Configuration Parsing**: Parse function chain from config
2. **Function Execution**: Execute evaluation functions sequentially
3. **Comparison**: Apply comparison operator with expected value
4. **Result Generation**: Create EvaluationResult with pass/fail status
## Function Types and Decorators
### Evaluation Function Decorator
Use `@eval_func(name="function_name")` to register evaluation functions:
```python
from mcpuniverse.evaluator.functions import eval_func, FunctionResult
@eval_func(name="my_custom_func")
async def my_custom_function(x: FunctionResult, *args, **kwargs) -> FunctionResult:
"""Custom evaluation function."""
# Process the input and return FunctionResult
processed_data = process_data(x.result)
return FunctionResult(result=processed_data)
```
### Comparison Function Decorator
Use `@compare_func(name="comparison_name")` to register comparison functions:
```python
from mcpuniverse.evaluator.functions import compare_func
@compare_func(name="my_custom_comparison")
async def my_custom_comparison(a: Any, b: Any, *args, **kwargs) -> tuple[bool, str]:
"""Custom comparison function."""
# Compare values and return (success, reason)
if custom_condition(a, b):
return True, ""
return False, "Custom validation failed"
```
## Implementation Steps
### Step 1: Create Module Structure
Create a new evaluator module in the appropriate domain:
```bash
mkdir mcpuniverse/evaluator/my_domain
touch mcpuniverse/evaluator/my_domain/__init__.py
touch mcpuniverse/evaluator/my_domain/functions.py
```
### Step 2: Implement Evaluation Functions
```python
# mcpuniverse/evaluator/my_domain/functions.py
"""
Evaluation functions for my custom domain
"""
import json
from typing import Any
from mcpuniverse.evaluator.functions import eval_func, compare_func, FunctionResult
@eval_func(name="extract_score")
async def extract_score(x: FunctionResult, *args, **kwargs) -> FunctionResult:
"""Extract numerical score from response."""
if isinstance(x, FunctionResult):
data = x.result
if isinstance(data, dict) and 'score' in data:
return FunctionResult(result=float(data['score']))
elif isinstance(data, str):
# Try to extract number from string
import re
match = re.search(r'\d+\.?\d*', data)
if match:
return FunctionResult(result=float(match.group()))
raise ValueError("Could not extract score from input")
@eval_func(name="normalize_text")
async def normalize_text(x: FunctionResult, *args, **kwargs) -> FunctionResult:
"""Normalize text for comparison."""
if isinstance(x, FunctionResult):
text = str(x.result).lower().strip()
# Remove extra whitespace
normalized = ' '.join(text.split())
return FunctionResult(result=normalized)
raise ValueError("Could not normalize text")
```
### Step 3: Implement Comparison Functions
```python
@compare_func(name="score_threshold")
async def score_threshold(a: Any, b: Any, *args, **kwargs) -> tuple[bool, str]:
"""Check if score meets threshold."""
if isinstance(a, FunctionResult):
a = a.result
if isinstance(b, FunctionResult):
b = b.result
threshold = float(b)
score = float(a)
if score >= threshold:
return True, ""
return False, f"Score {score} below threshold {threshold}"
@compare_func(name="text_similarity")
async def text_similarity(a: Any, b: Any, *args, **kwargs) -> tuple[bool, str]:
"""Check text similarity using fuzzy matching."""
from difflib import SequenceMatcher
if isinstance(a, FunctionResult):
a = a.result
if isinstance(b, FunctionResult):
b = b.result
similarity = SequenceMatcher(None, str(a), str(b)).ratio()
threshold = 0.8 # Default threshold
if len(args) > 2 and args[2]: # op_args provided
threshold = float(args[2].get('threshold', 0.8))
if similarity >= threshold:
return True, ""
return False, f"Text similarity {similarity:.2f} below threshold {threshold}"
```
### Step 4: Register Functions in Module
Update the main evaluator `__init__.py`:
```python
# mcpuniverse/evaluator/__init__.py
from .functions import *
from .my_domain.functions import * # Add your module
__all__ = [
"Evaluator",
"EvaluationResult",
"EvaluatorConfig"
]
```
## Built-in Evaluation Functions
| Function | Purpose | Usage Example |
|----------|---------|---------------|
| `json` | Parse JSON string | `"json"` |
| `get(key)` | Extract dictionary value | `"json -> get(city)"` |
| `len` | Get array/string length | `"json -> get(items) -> len"` |
| `foreach` | Iterate over arrays | `"json -> get(routes) -> foreach -> get(name)"` |
| `raw` | Pass through data unchanged | `"raw"` |
## Built-in Comparison Functions
| Function | Purpose | Usage Example |
|----------|---------|---------------|
| `=` | Exact equality | `"op": "=", "value": "expected"` |
| `<`, `>`, `<=`, `>=` | Numerical comparison | `"op": ">", "value": 100` |
| `in` | Membership test | `"op": "in", "value": ["a", "b", "c"]` |
| `contain` | Contains test | `"op": "contain", "value": "substring"` |
## Task Configuration Example
```json
{
"category": "ecommerce",
"question": "Calculate the final price for a shopping cart with discount",
"mcp_servers": [{"name": "ecommerce-api"}],
"output_format": {
"original_price": "<original total>",
"discount_percentage": "<discount %age>",
"discounted_price": "<final price>",
"savings": "<amount saved>"
},
"evaluators": [
{
"func": "json",
"op": "validate_discount",
"op_args": {
"discount_percentage": 15,
"tolerance": 0.5
}
},
{
"func": "json -> extract_order_total",
"op": ">",
"value": 0
}
]
}
```
This guide provides a complete framework for implementing custom evaluators in MCPUniverse. Follow these patterns and best practices to create robust, maintainable evaluation functions that accurately assess agent performance in your specific domain.