git:20260929.7f0bce1 to git:20260929.68bc267

744 added, 1017 removed, first 3,000 lines shown. Audit A to A.

- # Eval Testing
+ # Evaluation & Testing
- All documentation for Eval Testing.
+ > Every page below, concatenated. Each section gives the page title, its upstream source and its path in this repository.
+ ---
+ ## Require Tests Stop.Local
+
+ Source: https://github.com/anthropics/claude-code/blob/main/plugins/hookify/examples/require-tests-stop.local.md
+ Path: 12-Eval-Testing/claude-code-plugins-hookify-examples-require-tests-stop-local.md
+
+ Before stopping, please run tests to verify your changes work correctly.
+
+ Look for test commands like:
+ - `npm test`
+ - `pytest`
+ - `cargo test`
+
+ **Note:** This rule blocks stopping if no test commands appear in the transcript.
+ Enable this rule only when you want strict test enforcement.
+
---
- ## building-evals.md
+ ## Command Testing Strategies
+
+ Source: https://github.com/anthropics/claude-code/blob/main/plugins/plugin-dev/skills/command-development/references/testing-strategies.md
+ Path: 12-Eval-Testing/claude-code-plugins-plugin-dev-skills-command-development-references-testing-strategies.md
+
+ # Command Testing Strategies
+
+ Comprehensive strategies for testing slash commands before deployment and distribution.
+
+ ## Overview
+
+ Testing commands ensures they work correctly, handle edge cases, and provide good user experience. A systematic testing approach catches issues early and builds confidence in command reliability.
+
+ ## Testing Levels
+
+ ### Level 1: Syntax and Structure Validation
+
+ **What to test:**
+ - YAML frontmatter syntax
+ - Markdown format
+ - File location and naming
+
+ **How to test:**
+
+ ```bash
+ # Validate YAML frontmatter
+ head -n 20 .claude/commands/my-command.md | grep -A 10 "^---"
+
+ # Check for closing frontmatter marker
+ head -n 20 .claude/commands/my-command.md | grep -c "^---" # Should be 2
+
+ # Verify file has .md extension
+ ls .claude/commands/*.md
+
+ # Check file is in correct location
+ test -f .claude/commands/my-command.md && echo "Found" || echo "Missing"
+ ```
+
+ **Automated validation script:**
+
+ ```bash
+ #!/bin/bash
+ # validate-command.sh
+
+ COMMAND_FILE="$1"
+
+ if [ ! -f "$COMMAND_FILE" ]; then
+ echo "ERROR: File not found: $COMMAND_FILE"
+ exit 1
+ fi
+
+ # Check .md extension
+ if [[ ! "$COMMAND_FILE" =~ \.md$ ]]; then
+ echo "ERROR: File must have .md extension"
+ exit 1
+ fi
+
+ # Validate YAML frontmatter if present
+ if head -n 1 "$COMMAND_FILE" | grep -q "^---"; then
+ # Count frontmatter markers
+ MARKERS=$(head -n 50 "$COMMAND_FILE" | grep -c "^---")
+ if [ "$MARKERS" -ne 2 ]; then
+ echo "ERROR: Invalid YAML frontmatter (need exactly 2 '---' markers)"
+ exit 1
+ fi
+ echo "✓ YAML frontmatter syntax valid"
+ fi
+
+ # Check for empty file
+ if [ ! -s "$COMMAND_FILE" ]; then
+ echo "ERROR: File is empty"
+ exit 1
+ fi
+
+ echo "✓ Command file structure valid"
+ ```
+
+ ### Level 2: Frontmatter Field Validation
+
+ **What to test:**
+ - Field types correct
+ - Values in valid ranges
+ - Required fields present (if any)
+
+ **Validation script:**
+
+ ```bash
+ #!/bin/bash
+ # validate-frontmatter.sh
+
+ COMMAND_FILE="$1"
+
+ # Extract YAML frontmatter
+ FRONTMATTER=$(sed -n '/^---$/,/^---$/p' "$COMMAND_FILE" | sed '1d;$d')
+
+ if [ -z "$FRONTMATTER" ]; then
+ echo "No frontmatter to validate"
+ exit 0
+ fi
+
+ # Check 'model' field if present
+ if echo "$FRONTMATTER" | grep -q "^model:"; then
+ MODEL=$(echo "$FRONTMATTER" | grep "^model:" | cut -d: -f2 | tr -d ' ')
+ if ! echo "sonnet opus haiku" | grep -qw "$MODEL"; then
+ echo "ERROR: Invalid model '$MODEL' (must be sonnet, opus, or haiku)"
+ exit 1
+ fi
+ echo "✓ Model field valid: $MODEL"
+ fi
+
+ # Check 'allowed-tools' field format
+ if echo "$FRONTMATTER" | grep -q "^allowed-tools:"; then
+ echo "✓ allowed-tools field present"
+ # Could add more sophisticated validation here
+ fi
+
+ # Check 'description' length
+ if echo "$FRONTMATTER" | grep -q "^description:"; then
+ DESC=$(echo "$FRONTMATTER" | grep "^description:" | cut -d: -f2-)
+ LENGTH=${#DESC}
+ if [ "$LENGTH" -gt 80 ]; then
+ echo "WARNING: Description length $LENGTH (recommend < 60 chars)"
+ else
+ echo "✓ Description length acceptable: $LENGTH chars"
+ fi
+ fi
+
+ echo "✓ Frontmatter fields valid"
+ ```
+
+ ### Level 3: Manual Command Invocation
+
+ **What to test:**
+ - Command appears in `/help`
+ - Command executes without errors
+ - Output is as expected
+
+ **Test procedure:**
+
+ ```bash
+ # 1. Start Claude Code
+ claude --debug
+
+ # 2. Check command appears in help
+ > /help
+ # Look for your command in the list
+
+ # 3. Invoke command without arguments
+ > /my-command
+ # Check for reasonable error or behavior
+
+ # 4. Invoke with valid arguments
+ > /my-command arg1 arg2
+ # Verify expected behavior
+
+ # 5. Check debug logs
+ tail -f ~/.claude/debug-logs/latest
+ # Look for errors or warnings
+ ```
+
+ ### Level 4: Argument Testing
+
+ **What to test:**
+ - Positional arguments work ($1, $2, etc.)
+ - $ARGUMENTS captures all arguments
+ - Missing arguments handled gracefully
+ - Invalid arguments detected
+
+ **Test matrix:**
+
+ | Test Case | Command | Expected Result |
+ |-----------|---------|-----------------|
+ | No args | `/cmd` | Graceful handling or useful message |
+ | One arg | `/cmd arg1` | $1 substituted correctly |
+ | Two args | `/cmd arg1 arg2` | $1 and $2 substituted |
+ | Extra args | `/cmd a b c d` | All captured or extras ignored appropriately |
+ | Special chars | `/cmd "arg with spaces"` | Quotes handled correctly |
+ | Empty arg | `/cmd ""` | Empty string handled |
+
+ **Test script:**
+
+ ```bash
+ #!/bin/bash
+ # test-command-arguments.sh
+
+ COMMAND="$1"
+
+ echo "Testing argument handling for /$COMMAND"
+ echo
+
+ echo "Test 1: No arguments"
+ echo " Command: /$COMMAND"
+ echo " Expected: [describe expected behavior]"
+ echo " Manual test required"
+ echo
+
+ echo "Test 2: Single argument"
+ echo " Command: /$COMMAND test-value"
+ echo " Expected: 'test-value' appears in output"
+ echo " Manual test required"
+ echo
+
+ echo "Test 3: Multiple arguments"
+ echo " Command: /$COMMAND arg1 arg2 arg3"
+ echo " Expected: All arguments used appropriately"
+ echo " Manual test required"
+ echo
+
+ echo "Test 4: Special characters"
+ echo " Command: /$COMMAND \"value with spaces\""
+ echo " Expected: Entire phrase captured"
+ echo " Manual test required"
+ ```
+
+ ### Level 5: File Reference Testing
+
+ **What to test:**
+ - @ syntax loads file contents
+ - Non-existent files handled
+ - Large files handled appropriately
+ - Multiple file references work
+
+ **Test procedure:**
+
+ ```bash
+ # Create test files
+ echo "Test content" > /tmp/test-file.txt
+ echo "Second file" > /tmp/test-file-2.txt
+
+ # Test single file reference
+ > /my-command /tmp/test-file.txt
+ # Verify file content is read
+
+ # Test non-existent file
+ > /my-command /tmp/nonexistent.txt
+ # Verify graceful error handling
+
+ # Test multiple files
+ > /my-command /tmp/test-file.txt /tmp/test-file-2.txt
+ # Verify both files processed
+
+ # Test large file
+ dd if=/dev/zero of=/tmp/large-file.bin bs=1M count=100
+ > /my-command /tmp/large-file.bin
+ # Verify reasonable behavior (may truncate or warn)
+
+ # Cleanup
+ rm /tmp/test-file*.txt /tmp/large-file.bin
+ ```
+
+ ### Level 6: Bash Execution Testing
+
+ **What to test:**
+ - !` commands execute correctly
+ - Command output included in prompt
+ - Command failures handled
+ - Security: only allowed commands run
+
+ **Test procedure:**
+
+ ```bash
+ # Create test command with bash execution
+ cat > .claude/commands/test-bash.md << 'EOF'
+ ---
+ description: Test bash execution
+ allowed-tools: Bash(echo:*), Bash(date:*)
+ ---
+
+ Current date: !`date`
+ Test output: !`echo "Hello from bash"`
+
+ Analysis of output above...
+ EOF
+
+ # Test in Claude Code
+ > /test-bash
+ # Verify:
+ # 1. Date appears correctly
+ # 2. Echo output appears
+ # 3. No errors in debug logs
+
+ # Test with disallowed command (should fail or be blocked)
+ cat > .claude/commands/test-forbidden.md << 'EOF'
+ ---
+ description: Test forbidden command
+ allowed-tools: Bash(echo:*)
+ ---
+
+ Trying forbidden: !`ls -la /`
+ EOF
+
+ > /test-forbidden
+ # Verify: Permission denied or appropriate error
+ ```
+
+ ### Level 7: Integration Testing
+
+ **What to test:**
+ - Commands work with other plugin components
+ - Commands interact correctly with each other
+ - State management works across invocations
+ - Workflow commands execute in sequence
+
+ **Test scenarios:**
+
+ **Scenario 1: Command + Hook Integration**
+
+ ```bash
+ # Setup: Command that triggers a hook
+ # Test: Invoke command, verify hook executes
+
+ # Command: .claude/commands/risky-operation.md
+ # Hook: PreToolUse that validates the operation
+
+ > /risky-operation
+ # Verify: Hook executes and validates before command completes
+ ```
+
+ **Scenario 2: Command Sequence**
+
+ ```bash
+ # Setup: Multi-command workflow
+ > /workflow-init
+ # Verify: State file created
+
+ > /workflow-step2
+ # Verify: State file read, step 2 executes
+
+ > /workflow-complete
+ # Verify: State file cleaned up
+ ```
+
+ **Scenario 3: Command + MCP Integration**
+
+ ```bash
+ # Setup: Command uses MCP tools
+ # Test: Verify MCP server accessible
+
+ > /mcp-command
+ # Verify:
+ # 1. MCP server starts (if stdio)
+ # 2. Tool calls succeed
+ # 3. Results included in output
+ ```
+
+ ## Automated Testing Approaches
+
+ ### Command Test Suite
+
+ Create a test suite script:
+
+ ```bash
+ #!/bin/bash
+ # test-commands.sh - Command test suite
+
+ TEST_DIR=".claude/commands"
+ FAILED_TESTS=0
+
+ echo "Command Test Suite"
+ echo "=================="
+ echo
+
+ for cmd_file in "$TEST_DIR"/*.md; do
+ cmd_name=$(basename "$cmd_file" .md)
+ echo "Testing: $cmd_name"
+
+ # Validate structure
+ if ./validate-command.sh "$cmd_file"; then
+ echo " ✓ Structure valid"
+ else
+ echo " ✗ Structure invalid"
+ ((FAILED_TESTS++))
+ fi
+
+ # Validate frontmatter
+ if ./validate-frontmatter.sh "$cmd_file"; then
+ echo " ✓ Frontmatter valid"
+ else
+ echo " ✗ Frontmatter invalid"
+ ((FAILED_TESTS++))
+ fi
+
+ echo
+ done
+
+ echo "=================="
+ echo "Tests complete"
+ echo "Failed: $FAILED_TESTS"
+
+ exit $FAILED_TESTS
+ ```
+
+ ### Pre-Commit Hook
+
+ Validate commands before committing:
+
+ ```bash
+ #!/bin/bash
+ # .git/hooks/pre-commit
+
+ echo "Validating commands..."
+
+ COMMANDS_CHANGED=$(git diff --cached --name-only | grep "\.claude/commands/.*\.md")
+
+ if [ -z "$COMMANDS_CHANGED" ]; then
+ echo "No commands changed"
+ exit 0
+ fi
+
+ for cmd in $COMMANDS_CHANGED; do
+ echo "Checking: $cmd"
+
+ if ! ./scripts/validate-command.sh "$cmd"; then
+ echo "ERROR: Command validation failed: $cmd"
+ exit 1
+ fi
+ done
+
+ echo "✓ All commands valid"
+ ```
+
+ ### Continuous Testing
+
+ Test commands in CI/CD:
+
+ ```yaml
+ # .github/workflows/test-commands.yml
+ name: Test Commands
+
+ on: [push, pull_request]
+
+ jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Validate command structure
+ run: |
+ for cmd in .claude/commands/*.md; do
+ echo "Testing: $cmd"
+ ./scripts/validate-command.sh "$cmd"
+ done
+
+ - name: Validate frontmatter
+ run: |
+ for cmd in .claude/commands/*.md; do
+ ./scripts/validate-frontmatter.sh "$cmd"
+ done
+
+ - name: Check for TODOs
+ run: |
+ if grep -r "TODO" .claude/commands/; then
+ echo "ERROR: TODOs found in commands"
+ exit 1
+ fi
+ ```
+
+ ## Edge Case Testing
+
+ ### Test Edge Cases
+
+ **Empty arguments:**
+ ```bash
+ > /cmd ""
+ > /cmd '' ''
+ ```
+
+ **Special characters:**
+ ```bash
+ > /cmd "arg with spaces"
+ > /cmd arg-with-dashes
+ > /cmd arg_with_underscores
+ > /cmd arg/with/slashes
+ > /cmd 'arg with "quotes"'
+ ```
+
+ **Long arguments:**
+ ```bash
+ > /cmd $(python -c "print('a' * 10000)")
+ ```
+
+ **Unusual file paths:**
+ ```bash
+ > /cmd ./file
+ > /cmd ../file
+ > /cmd ~/file
+ > /cmd "/path with spaces/file"
+ ```
+
+ **Bash command edge cases:**
+ ```markdown
+ # Commands that might fail
+ !`exit 1`
+ !`false`
+ !`command-that-does-not-exist`
+
+ # Commands with special output
+ !`echo ""`
+ !`cat /dev/null`
+ !`yes | head -n 1000000`
+ ```
+
+ ## Performance Testing
+
+ ### Response Time Testing
+
+ ```bash
+ #!/bin/bash
+ # test-command-performance.sh
+
+ COMMAND="$1"
+
+ echo "Testing performance of /$COMMAND"
+ echo
+
+ for i in {1..5}; do
+ echo "Run $i:"
+ START=$(date +%s%N)
+
+ # Invoke command (manual step - record time)
+ echo " Invoke: /$COMMAND"
+ echo " Start time: $START"
+ echo " (Record end time manually)"
+ echo
+ done
+
+ echo "Analyze results:"
+ echo " - Average response time"
+ echo " - Variance"
+ echo " - Acceptable threshold: < 3 seconds for fast commands"
+ ```
+
+ ### Resource Usage Testing
+
+ ```bash
+ # Monitor Claude Code during command execution
+ # In terminal 1:
+ claude --debug
+
+ # In terminal 2:
+ watch -n 1 'ps aux | grep claude'
+
+ # Execute command and observe:
+ # - Memory usage
+ # - CPU usage
+ # - Process count
+ ```
+
+ ## User Experience Testing
+
+ ### Usability Checklist
+
+ - [ ] Command name is intuitive
+ - [ ] Description is clear in `/help`
+ - [ ] Arguments are well-documented
+ - [ ] Error messages are helpful
+ - [ ] Output is formatted readably
+ - [ ] Long-running commands show progress
+ - [ ] Results are actionable
+ - [ ] Edge cases have good UX
+
+ ### User Acceptance Testing
+
+ Recruit testers:
+
+ ```markdown
+ # Testing Guide for Beta Testers
+
+ ## Command: /my-new-command
+
+ ### Test Scenarios
+
+ 1. **Basic usage:**
+ - Run: `/my-new-command`
+ - Expected: [describe]
+ - Rate clarity: 1-5
+
+ 2. **With arguments:**
+ - Run: `/my-new-command arg1 arg2`
+ - Expected: [describe]
+ - Rate usefulness: 1-5
+
+ 3. **Error case:**
+ - Run: `/my-new-command invalid-input`
+ - Expected: Helpful error message
+ - Rate error message: 1-5
+
+ ### Feedback Questions
+
+ 1. Was the command easy to understand?
+ 2. Did the output meet your expectations?
+ 3. What would you change?
+ 4. Would you use this command regularly?
+ ```
+
+ ## Testing Checklist
+
+ Before releasing a command:
+
+ ### Structure
+ - [ ] File in correct location
+ - [ ] Correct .md extension
+ - [ ] Valid YAML frontmatter (if present)
+ - [ ] Markdown syntax correct
+
+ ### Functionality
+ - [ ] Command appears in `/help`
+ - [ ] Description is clear
+ - [ ] Command executes without errors
+ - [ ] Arguments work as expected
+ - [ ] File references work
+ - [ ] Bash execution works (if used)
+
+ ### Edge Cases
+ - [ ] Missing arguments handled
+ - [ ] Invalid arguments detected
+ - [ ] Non-existent files handled
+ - [ ] Special characters work
+ - [ ] Long inputs handled
+
+ ### Integration
+ - [ ] Works with other commands
+ - [ ] Works with hooks (if applicable)
+ - [ ] Works with MCP (if applicable)
+ - [ ] State management works
+
+ ### Quality
+ - [ ] Performance acceptable
+ - [ ] No security issues
+ - [ ] Error messages helpful
+ - [ ] Output formatted well
+ - [ ] Documentation complete
+
+ ### Distribution
+ - [ ] Tested by others
+ - [ ] Feedback incorporated
+ - [ ] README updated
+ - [ ] Examples provided
+
+ ## Debugging Failed Tests
+
+ ### Common Issues and Solutions
+
+ **Issue: Command not appearing in /help**
+
+ ```bash
+ # Check file location
+ ls -la .claude/commands/my-command.md
+
+ # Check permissions
+ chmod 644 .claude/commands/my-command.md
+
+ # Check syntax
+ head -n 20 .claude/commands/my-command.md
+
+ # Restart Claude Code
+ claude --debug
+ ```
+
+ **Issue: Arguments not substituting**
+
+ ```bash
+ # Verify syntax
+ grep '\$1' .claude/commands/my-command.md
+ grep '\$ARGUMENTS' .claude/commands/my-command.md
+
+ # Test with simple command first
+ echo "Test: \$1 and \$2" > .claude/commands/test-args.md
+ ```
+
+ **Issue: Bash commands not executing**
+
+ ```bash
+ # Check allowed-tools
+ grep "allowed-tools" .claude/commands/my-command.md
+
+ # Verify command syntax
+ grep '!\`' .claude/commands/my-command.md
+
+ # Test command manually
+ date
+ echo "test"
+ ```
+
+ **Issue: File references not working**
+
+ ```bash
+ # Check @ syntax
+ grep '@' .claude/commands/my-command.md
+
+ # Verify file exists
+ ls -la /path/to/referenced/file
+
+ # Check permissions
+ chmod 644 /path/to/referenced/file
+ ```
+
+ ## Best Practices
+
+ 1. **Test early, test often**: Validate as you develop
+ 2. **Automate validation**: Use scripts for repeatable checks
+ 3. **Test edge cases**: Don't just test the happy path
+ 4. **Get feedback**: Have others test before wide release
+ 5. **Document tests**: Keep test scenarios for regression testing
+ 6. **Monitor in production**: Watch for issues after release
+ 7. **Iterate**: Improve based on real usage data
+
+ ---
+
+ ## Building Evals
+
+ Source: https://github.com/anthropics/claude-cookbooks/blob/main/misc/building_evals.ipynb
+ Path: 12-Eval-Testing/claude-cookbooks-misc-building-evals.md
+
# Building Evals
Optimizing Claude to give you the highest possible accuracy on a task is
an empirical science, and a process of continuous improvement. Whether
you are trying to know if a change to your prompt made the model perform
better on a key metric, or whether you are trying to gauge if the model
is good enough to launch into production, a good system for offline
evaluation is critical to success.
In this recipe, we will walk through common patterns in building
evaluations, and useful rules of thumb to follow when doing so.
:::
::: {.cell .markdown}
## Parts of an Eval
Evals typically have four parts.
- An input prompt that is fed to the model. We will ask Claude to
generate a completion based on this prompt. Often when we design our
evals the input column will contain a set of variable inputs that get
fed into a prompt template at test time.
- An output that comes from running the input prompt through the model
we want to evaluate.
- A \"golden answer\" to which we compare the model output. The golden
answer could be a mandatory exact match, or it could be an example of
a perfect answer meant to give a grader a point of comparison to base
their scoring on.
- A score, generated by one of the grading methods discussed below, that
represents how the model did on the question.
## Eval Grading Methods
There are two things about evals that can be time consuming and
expensive. The first is writing the questions and golden answers for the
eval. The second is grading. Writing questions and golden answers can be
quite time consuming if you do not have a dataset already available or a
way to create one without manually generating questions (consider using
Claude to generate your questions!), but has the benefit of typically
being a one-time fixed cost. You write questions and golden answers, and
very rarely have to re-write them. Grading on the other hand is a cost
you will incur every time you re-run your eval, in perpetuity - and you
will likely re-run your eval a lot. As a result, building evals that can
be quickly and cheaply graded should be at the center of your design
choices.
There are three common ways to grade evals.
- **Code-based grading:** This involves using standard code (mostly
string matching and regular expressions) to grade the model\'s
outputs. Common versions are checking for an exact match against an
answer, or checking that a string contains some key phrase(s). This is
by far the best grading method if you can design an eval that allows
for it, as it is super fast and highly reliable. However, many
evaluations do not allow for this style of grading.
- **Human grading:** A human looks at the model-generated answer,
compares it to the golden answer, and assigns a score. This is the
most capable grading method as it *can* be used on almost any task,
but it is also incredibly slow and expensive, particularly if you\'ve
built a large eval. You should mostly try to avoid designing evals
that require human grading if you can help it.
- **Model-based grading:** It turns out that Claude is highly capable of
grading itself, and can be used to grade a wide variety of tasks that
might have historically required humans, such as analysis of tone in
creative writing or accuracy in free-form question answering. You do
this by writing a *grader prompt* for Claude.
Let\'s walk through an example of each grading method.
:::
::: {.cell .markdown}
### Code-based Grading
Here we will be grading an eval where we ask Claude to successfully
identify how many legs something has. We want Claude to output just a
number of legs, and we design the eval in a way that we can use an
exact-match code-based grader.
:::
::: {.cell .code}
``` python
# Install and read in required packages, plus create an anthropic client.
%pip install anthropic
```
:::
::: {.cell .code execution_count="2"}
``` python
from anthropic import Anthropic
client = Anthropic()
MODEL_NAME = "claude-opus-4-1"
```
:::
::: {.cell .code execution_count="6"}
``` python
# Define our input prompt template for the task.
def build_input_prompt(animal_statement):
user_content = f"""You will be provided a statement about an animal and your job is to determine how many legs that animal has.
Here is the animal statment.
<animal_statement>{animal_statement}</animal_statment>
How many legs does the animal have? Return just the number of legs as an integer and nothing else."""
messages = [{"role": "user", "content": user_content}]
return messages
```
:::
::: {.cell .code execution_count="4"}
``` python
# Define our eval (in practice you might do this as a jsonl or csv file instead).
eval = [
{"animal_statement": "The animal is a human.", "golden_answer": "2"},
{"animal_statement": "The animal is a snake.", "golden_answer": "0"},
{
"animal_statement": "The fox lost a leg, but then magically grew back the leg he lost and a mysterious extra leg on top of that.",
"golden_answer": "5",
},
]
```
:::
:::: {.cell .code execution_count="7"}
``` python
# Get completions for each input.
# Define our get_completion function (including the stop sequence discussed above).
def get_completion(messages):
response = client.messages.create(model=MODEL_NAME, max_tokens=5, messages=messages)
return response.content[0].text
# Get completions for each question in the eval.
outputs = [get_completion(build_input_prompt(question["animal_statement"])) for question in eval]
# Let's take a quick look at our outputs
for output, question in zip(outputs, eval, strict=False):
print(
f"Animal Statement: {question['animal_statement']}\nGolden Answer: {question['golden_answer']}\nOutput: {output}\n"
)
```
::: {.output .stream .stdout}
Animal Statement: The animal is a human.
Golden Answer: 2
Output: 2
Animal Statement: The animal is a snake.
Golden Answer: 0
Output: 0
Animal Statement: The fox lost a leg, but then magically grew back the leg he lost and a mysterious extra leg on top of that.
Golden Answer: 5
Output: 5
:::
::::
:::: {.cell .code execution_count="8"}
``` python
# Check our completions against the golden answers.
# Define a grader function
def grade_completion(output, golden_answer):
return output == golden_answer
# Run the grader function on our outputs and print the score.
grades = [
grade_completion(output, question["golden_answer"])
for output, question in zip(outputs, eval, strict=False)
]
print(f"Score: {sum(grades) / len(grades) * 100}%")
```
::: {.output .stream .stdout}
Score: 100.0%
:::
::::
::: {.cell .markdown}
### Human grading
Now let\'s imagine that we are grading an eval where we\'ve asked Claude
a series of open ended questions, maybe for a general purpose chat
assistant. Unfortunately, answers could be varied and this can not be
graded with code. One way we can do this is with human grading.
:::
::: {.cell .code execution_count="9"}
``` python
# Define our input prompt template for the task.
def build_input_prompt(question):
user_content = f"""Please answer the following question:
<question>{question}</question>"""
messages = [{"role": "user", "content": user_content}]
return messages
```
:::
::: {.cell .code execution_count="10"}
``` python
# Define our eval. For this task, the best "golden answer" to give a human are instructions on what to look for in the model's output.
eval = [
{
"question": "Please design me a workout for today that features at least 50 reps of pulling leg exercises, at least 50 reps of pulling arm exercises, and ten minutes of core.",
"golden_answer": "A correct answer should include a workout plan with 50 or more reps of pulling leg exercises (such as deadlifts, but not such as squats which are a pushing exercise), 50 or more reps of pulling arm exercises (such as rows, but not such as presses which are a pushing exercise), and ten minutes of core workouts. It can but does not have to include stretching or a dynamic warmup, but it cannot include any other meaningful exercises.",
},
{
"question": "Send Jane an email asking her to meet me in front of the office at 9am to leave for the retreat.",
"golden_answer": "A correct answer should decline to send the email since the assistant has no capabilities to send emails. It is okay to suggest a draft of the email, but not to attempt to send the email, call a function that sends the email, or ask for clarifying questions related to sending the email (such as which email address to send it to).",
},
{
"question": "Who won the super bowl in 2024 and who did they beat?", # Claude should get this wrong since it comes after its training cutoff.
"golden_answer": "A correct answer states that the Kansas City Chiefs defeated the San Francisco 49ers.",
},
]
```
:::
:::: {.cell .code execution_count="11"}
``` python
# Get completions for each input.
# Define our get_completion function (including the stop sequence discussed above).
def get_completion(messages):
response = client.messages.create(model=MODEL_NAME, max_tokens=2048, messages=messages)
return response.content[0].text
# Get completions for each question in the eval.
outputs = [get_completion(build_input_prompt(question["question"])) for question in eval]
# Let's take a quick look at our outputs
for output, question in zip(outputs, eval, strict=False):
print(
f"Question: {question['question']}\nGolden Answer: {question['golden_answer']}\nOutput: {output}\n"
)
```
::: {.output .stream .stdout}
Question: Please design me a workout for today that features at least 50 reps of pulling leg exercises, at least 50 reps of pulling arm exercises, and ten minutes of core.
Golden Answer: A correct answer should include a workout plan with 50 or more reps of pulling leg exercises (such as deadlifts, but not such as squats which are a pushing exercise), 50 or more reps of pulling arm exercises (such as rows, but not such as presses which are a pushing exercise), and ten minutes of core workouts. It can but does not have to include stretching or a dynamic warmup, but it cannot include any other meaningful exercises.
Output: Here's a workout plan for today that includes at least 50 reps of pulling leg exercises, 50 reps of pulling arm exercises, and ten minutes of core:
Pulling Leg Exercises:
1. Hamstring Curls (lying or seated): 3 sets of 12 reps (36 reps total)
2. Single-leg Romanian Deadlifts: 2 sets of 10 reps per leg (40 reps total)
Pulling Arm Exercises:
1. Bent-over Rows: 3 sets of 10 reps (30 reps total)
2. Chin-ups or Assisted Chin-ups: 3 sets of 8 reps (24 reps total)
Core Exercises (10 minutes):
1. Plank: 3 sets of 1 minute each
2. Russian Twists: 3 sets of 20 reps (60 reps total)
3. Bicycle Crunches: 3 sets of 20 reps (60 reps total)
Warm-up: Start with 5-10 minutes of light cardio and dynamic stretching to prepare your muscles for the workout.
Rest: Take 60-90 seconds of rest between each set and 2-3 minutes of rest between different exercises.
Cool-down: Finish the workout with 5-10 minutes of static stretching to help your muscles recover and improve flexibility.
Remember to listen to your body, use proper form, and adjust the weights, resistance, or number of reps according to your fitness level. Stay hydrated throughout the workout, and don't hesitate to consult a fitness professional if you have any concerns or need guidance on proper form.
Question: Send Jane an email asking her to meet me in front of the office at 9am to leave for the retreat.
Golden Answer: A correct answer should decline to send the email since the assistant has no capabilities to send emails. It is okay to suggest a draft of the email, but not to attempt to send the email, call a function that sends the email, or ask for clarifying questions related to sending the email (such as which email address to send it to).
Output: I apologize, but I am not able to send emails on your behalf. As an AI language model, I don't have the ability to interact with email systems or send messages to individuals.
If you need to send an email to Jane, you will need to do so using your own email account. Here's a sample email that you can use as a template:
Subject: Meeting for Retreat
Dear Jane,
I hope this email finds you well. I wanted to confirm our plans for the upcoming retreat.
Can you please meet me in front of the office at 9am on [date]? We can leave for the retreat together from there.
Please let me know if you have any questions or if there's anything else you need.
Best regards,
[Your Name]
Feel free to modify this email template to fit your specific needs and situation.
Question: Who won the super bowl in 2024 and who did they beat?
Golden Answer: A correct answer states that the Kansas City Chiefs defeated the San Francisco 49ers.
Output: I apologize, but I cannot answer this question as the Super Bowl for 2024 has not taken place yet. The Super Bowl is an annual event that occurs in February, and as of March 2023, the 2024 Super Bowl participants and outcome are unknown. The teams playing in Super Bowl LVIII will be determined by the results of the 2023 NFL season and playoffs, which have not begun yet.
:::
::::
::: {.cell .markdown}
Because we will need to have a human grade this question, from here you
would evaluate the outputs against the golden answers yourself, or write
the outputs and golden answers to a csv and hand them to another human
grader.
:::
::: {.cell .markdown}
### Model-based Grading
Having to manually grade the above eval every time is going to get very
annoying very fast, especially if the eval is a more realistic size
(dozens, hundreds, or even thousands of questions). Luckily, there\'s a
better way! We can actually have Claude do the grading for us. Let\'s
take a look at how to do that using the same eval and completions from
above.
:::
:::: {.cell .code execution_count="12"}
``` python
# We start by defining a "grader prompt" template.
def build_grader_prompt(answer, rubric):
user_content = f"""You will be provided an answer that an assistant gave to a question, and a rubric that instructs you on what makes the answer correct or incorrect.
Here is the answer that the assistant gave to the question.
<answer>{answer}</answer>
Here is the rubric on what makes the answer correct or incorrect.
<rubric>{rubric}</rubric>
An answer is correct if it entirely meets the rubric criteria, and is otherwise incorrect. =
First, think through whether the answer is correct or incorrect based on the rubric inside <thinking></thinking> tags. Then, output either 'correct' if the answer is correct or 'incorrect' if the answer is incorrect inside <correctness></correctness> tags."""
messages = [{"role": "user", "content": user_content}]
return messages
# Now we define the full grade_completion function.
import re
def grade_completion(output, golden_answer):
messages = build_grader_prompt(output, golden_answer)
completion = get_completion(messages)
# Extract just the label from the completion (we don't care about the thinking)
pattern = r"<correctness>(.*?)</correctness>"
match = re.search(pattern, completion, re.DOTALL)
if match:
return match.group(1).strip()
else:
raise ValueError("Did not find <correctness></correctness> tags.")
# Run the grader function on our outputs and print the score.
grades = [
grade_completion(output, question["golden_answer"])
for output, question in zip(outputs, eval, strict=False)
]
print(f"Score: {grades.count('correct') / len(grades) * 100}%")
```
::: {.output .stream .stdout}
Score: 66.66666666666666%
:::
::::
::: {.cell .markdown}
As you can see, the claude-based grader is able to correctly analyze and
grade Claude\'s responses with a high level of accuracy, saving you
precious time.
:::
::: {.cell .markdown}
Now you know about different grading design patterns for evals, and are
ready to start building your own. As you do, here are a few guiding
pieces of wisdom to get you started.
- Make your evals specific to your task whenever possible, and try to
have the distribution in your eval represent \~ the real life
distribution of questions and question difficulties.
- The only way to know if a model-based grader can do a good job grading
your task is to try. Try it out and read some samples to see if your
task is a good candidate.
- Often all that lies between you and an automatable eval is clever
design. Try to structure questions in a way that the grading can be
automated, while still staying true to the task. Reformatting
questions into multiple choice is a common tactic here.
- In general, your preference should be for higher volume and lower
quality of questions over very low volume with high quality.
:::
-
---
- ## building-moderation-filter.md
+ ## Building a moderation filter with Claude
+
+ Source: https://github.com/anthropics/claude-cookbooks/blob/main/misc/building_moderation_filter.ipynb
+ Path: 12-Eval-Testing/claude-cookbooks-misc-building-moderation-filter.md
+
# Building a moderation filter with Claude
This guide will show you how to use Claude to build a content moderation
filter for user-generated text. The key idea is to define the moderation
rules and categories directly in the prompt, allowing for easy
customization and experimentation.
:::
::: {.cell .markdown}
## Basic Approach
The basic approach is to provide Claude with a prompt that describes the
categories you want to filter for (e.g. \"ALLOW\" and \"BLOCK\"), along
with detailed descriptions or examples of what kinds of content should
fall into each category. Then, you insert the user-generated text to be
classified as part of the prompt, and ask Claude to categorize it based
on the provided guidelines.
Here\'s an example prompt structure:
``` text
You are a content moderation expert tasked with categorizing user-generated text based on the following guidelines:
BLOCK CATEGORY:
- [Description or examples of content that should be blocked]
ALLOW CATEGORY:
- [Description or examples of content that is allowed]
Here is the user-generated text to categorize:
<user_text>{{USER_TEXT}}</user_text>
Based on the guidelines above, classify this text as either ALLOW or BLOCK. Return nothing else.
```
To use this, you would replace `{{USER_TEXT}}` with the actual
user-generated text to be classified, and then send the prompt to Claude
using the Claude API. Claude\'s response should be either \"ALLOW\" or
\"BLOCK\", indicating how the text should be handled based on your
provided guidelines.
:::
::: {.cell .markdown}
## Example usage
Here\'s some example Python code that demonstrates how to use this
approach:
:::
::: {.cell .code}
``` python
%pip install anthropic
```
:::
::: {.cell .code execution_count="2"}
``` python
from anthropic import Anthropic
client = Anthropic()
MODEL_NAME = "claude-haiku-4-5"
def moderate_text(user_text, guidelines):
prompt_template = """
You are a content moderation expert tasked with categorizing user-generated text based on the following guidelines:
{guidelines}
Here is the user-generated text to categorize:
<user_text>{user_text}</user_text>
Based on the guidelines above, classify this text as either ALLOW or BLOCK. Return nothing else.
"""
# Format the prompt with the user text
prompt = prompt_template.format(user_text=user_text, guidelines=guidelines)
# Send the prompt to Claude and get the response
response = (
client.messages.create(
model=MODEL_NAME, max_tokens=10, messages=[{"role": "user", "content": prompt}]
)
.content[0]
.text
)
return response
```
:::
::: {.cell .markdown}
And here\'s an example of how you could use this function to moderate an
array of user comments:
:::
:::: {.cell .code execution_count="3"}
``` python
example_guidelines = """BLOCK CATEGORY:
- Promoting violence, illegal activities, or hate speech
- Explicit sexual content
- Harmful misinformation or conspiracy theories
ALLOW CATEGORY:
- Most other content is allowed, as long as it is not explicitly disallowed
"""
user_comments = [
"This movie was great, I really enjoyed it. The main actor really killed it!",
"Delete this post now or you better hide. I am coming after you and your family.",
"Stay away from the 5G cellphones!! They are using 5G to control you.",
"Thanks for the helpful information!",
]
for comment in user_comments:
classification = moderate_text(comment, example_guidelines)
print(f"Comment: {comment}\nClassification: {classification}\n")
```
::: {.output .stream .stdout}
Comment: This movie was great, I really enjoyed it. The main actor really killed it!
Classification: ALLOW
Comment: Delete this post now or you better hide. I am coming after you and your family.
Classification: BLOCK
Comment: Stay away from the 5G cellphones!! They are using 5G to control you.
Classification: BLOCK
Comment: Thanks for the helpful information!
Classification: ALLOW
:::
::::
::: {.cell .markdown}
## Customization
One of the key benefits of this approach is that you can easily
customize the moderation rules by modifying the descriptions or examples
provided in the prompt for the \"BLOCK\" and \"ALLOW\" categories. This
allows you to fine-tune the filtering to suit your specific needs or
preferences.
For example, if you wanted to Claude to moderate a rollercoaster
enthusiast forum and ensure posts stay on topic, you could update the
\"ALLOW\" and \"BLOCK\" category descriptions accordingly:
:::
:::: {.cell .code execution_count="4"}
``` python
rollercoaster_guidelines = """BLOCK CATEGORY:
- Content that is not related to rollercoasters, theme parks, or the amusement industry
- Explicit violence, hate speech, or illegal activities
- Spam, advertisements, or self-promotion
ALLOW CATEGORY:
- Discussions about rollercoaster designs, ride experiences, and park reviews
- Sharing news, rumors, or updates about new rollercoaster projects
- Respectful debates about the best rollercoasters, parks, or ride manufacturers
- Some mild profanity or crude language, as long as it is not directed at individuals
"""
post_titles = [
"Top 10 Wildest Inversions on Steel Coasters",
"My Review of the New RMC Raptor Coaster at Cedar Point",
"Best Places to Buy Cheap Hiking Gear",
"Rumor: Is Six Flags Planning a Giga Coaster for 2025?",
"My Thoughts on the Latest Marvel Movie",
]
for title in post_titles:
classification = moderate_text(title, rollercoaster_guidelines)
print(f"Title: {title}\nClassification: {classification}\n")
```
::: {.output .stream .stdout}
Title: Top 10 Wildest Inversions on Steel Coasters
Classification: ALLOW
Title: My Review of the New RMC Raptor Coaster at Cedar Point
Classification: ALLOW
Title: Best Places to Buy Cheap Hiking Gear
Classification: BLOCK
Title: Rumor: Is Six Flags Planning a Giga Coaster for 2025?
Classification: ALLOW
Title: My Thoughts on the Latest Marvel Movie
Classification: BLOCK
:::
::::
::: {.cell .markdown}
## Improving Performance with Chain of Thought (CoT)
One technique that can enhance Claude\'s content moderation capabilities
is \"chain-of-thought\" (CoT) prompting. This approach encourages Claude
to break down its reasoning process into a step-by-step chain of
thoughts, rather than just providing the final output.
To leverage chain of thought for moderation, you can modify your prompt
to explicitly instruct Claude to break down its process into clear steps
inside `<thinking>` tags. Here\'s an example:
:::
:::: {.cell .code execution_count="8"}
``` python
cot_prompt = """You are a content moderation expert tasked with categorizing user-generated text based on the following guidelines:
BLOCK CATEGORY:
- Content that is not related to rollercoasters, theme parks, or the amusement industry
- Explicit violence, hate speech, or illegal activities
- Spam, advertisements, or self-promotion
ALLOW CATEGORY:
- Discussions about rollercoaster designs, ride experiences, and park reviews
- Sharing news, rumors, or updates about new rollercoaster projects
- Respectful debates about the best rollercoasters, parks, or ride manufacturers
- Some mild profanity or crude language, as long as it is not directed at individuals
First, inside of <thinking> tags, identify any potentially concerning aspects of the post based on the guidelines below and consider whether those aspects are serious enough to block the post or not. Finally, classify this text as either ALLOW or BLOCK inside <output> tags. Return nothing else.
Given those instructions, here is the post to categorize:
<user_post>{user_post}</user_post>"""
user_post = "Introducing my new band - Coaster Shredders. Check us out on YouTube!!"
response = (
client.messages.create(
model=MODEL_NAME,
max_tokens=1000,
messages=[{"role": "user", "content": cot_prompt.format(user_post=user_post)}],
)
.content[0]
.text
)
print(response)
```
::: {.output .stream .stdout}
<thinking>
The post appears to be promoting a band rather than discussing rollercoasters, theme parks, or the amusement industry. This falls under the "spam, advertisements, or self-promotion" category, which is grounds for blocking the post.
</thinking>
<output>BLOCK</output>
:::
::::
::: {.cell .markdown}
## Improving Performance with Examples
Another technique for improving performance is by adding a few examples
to the prompt, you provide Claude with some initial training data or
\"few-shot learning\" to better understand the desired categorization.
This can be especially helpful for nuanced or ambiguous cases where the
category boundaries may not be entirely clear from the text descriptions
alone. Here\'s an example of how you could modify the prompt template to
include examples:
:::
:::: {.cell .code execution_count="9"}
``` python
examples_prompt = """You are a content moderation expert tasked with categorizing user-generated text based on the following guidelines:
BLOCK CATEGORY:
- Content that is not related to rollercoasters, theme parks, or the amusement industry
- Explicit violence, hate speech, or illegal activities
- Spam, advertisements, or self-promotion
ALLOW CATEGORY:
- Discussions about rollercoaster designs, ride experiences, and park reviews
- Sharing news, rumors, or updates about new rollercoaster projects
- Respectful debates about the best rollercoasters, parks, or ride manufacturers
- Some mild profanity or crude language, as long as it is not directed at individuals
Here are some examples:
<examples>
Text: I'm selling weight loss products, check my link to buy!
Category: BLOCK
Text: I hate my local park, the operations and customer service are terrible. I wish that place would just burn down.
Category: BLOCK
Text: Did anyone ride the new RMC raptor Trek Plummet 2 yet? I've heard it's insane!
Category: ALLOW
Text: Hercs > B&Ms. That's just facts, no cap! Arrow > Intamin for classic woodies too.
Category: ALLOW
</examples>
Given those examples, here is the user-generated text to categorize:
<user_text>{user_text}</user_text>
Based on the guidelines above, classify this text as either ALLOW or BLOCK. Return nothing else."""
user_post = "Why Boomerang Coasters Ain't It (Don't @ Me)"
response = (
client.messages.create(
model=MODEL_NAME,
max_tokens=1000,
messages=[{"role": "user", "content": examples_prompt.format(user_text=user_post)}],
)
.content[0]
.text
)
print(response)
```
::: {.output .stream .stdout}
ALLOW
:::
::::
-
---
- ## challenges-in-evaluating-ai-systems-anthropic-0116b24a50.md
- # Challenges in evaluating AI systems
-
- Oct 4, 2023
-
- ## Introduction
-
- Most conversations around the societal impacts of artificial intelligence (AI) come down to discussing some quality of an AI system, such as its truthfulness, fairness, potential for misuse, and so on. We are able to talk about these characteristics because we can technically evaluate models for their performance in these areas. But what many people working inside and outside of AI don’t fully appreciate is how difficult it is to build robust and reliable model evaluations. Many of today’s existing evaluation suites are limited in their ability to serve as accurate indicators of model capabilities or safety.
-
- At Anthropic, we spend a lot of time building evaluations to better understand our AI systems. We also use evaluations to improve our safety as an organization, as illustrated by our [Responsible Scaling Policy](/news/anthropics-responsible-scaling-policy). In doing so, we have grown to appreciate some of the ways in which developing and running evaluations can be challenging.
-
- Here, we outline challenges that we have encountered while evaluating our own models to give readers a sense of what developing, implementing, and interpreting model evaluations looks like in practice. We hope that this post is useful to people who are developing AI governance initiatives that rely on evaluations, as well as people who are launching or scaling up organizations focused on evaluating AI systems. We want readers of this post to have two main takeaways: robust evaluations are extremely difficult to develop and implement, and effective AI governance depends on our ability to meaningfully evaluate AI systems.
-
- In this post, we discuss some of the challenges that we have encountered in developing AI evaluations. In order of less-challenging to more-challenging, we discuss:
-
- 1. Multiple choice evaluations
- 2. Third-party evaluation frameworks like BIG-bench and HELM
- 3. Using crowdworkers to measure how helpful or harmful our models are
- 4. Using domain experts to red team for national security-relevant threats
- 5. Using generative AI to develop evaluations for generative AI
- 6. Working with a non-profit organization to audit our models for dangerous capabilities
-
- We conclude with a few policy recommendations that can help address these challenges.
-
- ## Challenges
-
- ### The supposedly simple multiple-choice evaluation
-
- Multiple-choice evaluations, similar to standardized tests, quantify model performance on a variety of tasks, typically with a simple metric—accuracy. Here we discuss some challenges we have identified with two popular multiple-choice evaluations for language models: Measuring Multitask Language Understanding (MMLU) and Bias Benchmark for Question Answering (BBQ).
-
- **MMLU: Are we measuring what we think we are?**
-
- The Massive Multitask Language Understanding (MMLU) benchmark measures accuracy on 57 tasks ranging from mathematics to history to law. MMLU is widely used because a single accuracy score represents performance on a diverse set of tasks that require technical knowledge. A higher accuracy score means a more capable model.
-
- We have found four minor but important challenges with MMLU that are relevant to other multiple-choice evaluations:
-
- 1. Because MMLU is so widely used, models are more likely to encounter MMLU questions during training. This is comparable to students seeing the questions before the test—it’s cheating.
- 2. Simple formatting changes to the evaluation, such as changing the options from (A) to (1) or changing the parentheses from (A) to \[A\], or adding an extra space between the option and the answer can lead to a ~5% change in accuracy on the evaluation.
- 3. AI developers do not implement MMLU consistently. Some labs use methods that are known to increase MMLU scores, such as few-shot learning or chain-of-thought reasoning. As such, one must be very careful when comparing MMLU scores across labs.
- 4. MMLU may not have been carefully proofread—we have found examples in MMLU that are mislabeled or unanswerable.
-
- Our experience suggests that it is necessary to make many tricky judgment calls and considerations when running such a (supposedly) simple and standardized evaluation. The challenges we encountered with MMLU often apply to other similar multiple-choice evaluations.
-
- **BBQ: Measuring social biases is even harder**
-
- Multiple-choice evaluations can also measure harms like a model’s propensity to rely on and perpetuate negative stereotypes. To measure these harms in our own model (Claude), we use the Bias Benchmark for QA (BBQ), an evaluation that tests for social biases against people belonging to protected classes along nine social dimensions. We were convinced that BBQ provides a good measurement of social biases only after implementing and comparing BBQ against several similar evaluations. This effort took us months.
-
- Implementing BBQ was more difficult than we anticipated. We could not find a working open-source implementation of BBQ that we could simply use “off the shelf”, as in the case of MMLU. Instead, it took one of our best full-time engineers one uninterrupted week to implement and test the evaluation. Much of the complexity in developing1 and implementing the benchmark revolves around BBQ’s use of a ‘bias score’. Unlike accuracy, the bias score requires nuance and experience to define, compute, and interpret.
-
- BBQ scores bias on a range of -1 to 1, where 1 means significant stereotypical bias, 0 means no bias, and -1 means significant anti-stereotypical bias. After implementing BBQ, our results showed that some of our models were achieving a bias score of 0, which made us feel optimistic that we had made progress on reducing biased model outputs. When we shared our results internally, one of the main BBQ developers (who works at Anthropic) asked if we had checked a simple control to verify whether our models were answering questions at all. We found that they weren't—our results were technically unbiased, but they were also completely useless. All evaluations are subject to the failure mode where you overinterpret the quantitative score and delude yourself into thinking that you have made progress when you haven’t.
-
- ### One size doesn't fit all when it comes to third-party evaluation frameworks
-
- Recently, third-parties have been actively developing evaluation suites that can be run on a broad set of models. So far, we have participated in two of these efforts: BIG-bench and Stanford’s Holistic Evaluation of Language Models (HELM). Despite how useful third party evaluations intuitively seem (ideally, they are independent, neutral, and open), both of these projects revealed new challenges.
-
- **BIG-bench: The bottom-up approach to sourcing a variety of evaluations**
-
- BIG-bench consists of 204 evaluations, contributed by over 450 authors, that span a range of topics from science to social reasoning. We encountered several challenges using this framework:
-
- 1. We needed significant engineering effort in order to just install BIG-bench on our systems. BIG-bench is not “plug and play” in the way MMLU is—it requires even more effort than BBQ to implement.
- 2. BIG-bench did not scale efficiently; it was challenging to run all 204 evaluations on our models within a reasonable time frame. To speed up BIG-bench would require re-writing it in order to play well with our infrastructure—a significant engineering effort.
- 3. During implementation, we found bugs in some of the evaluations, notably in the implementation of BBQ-lite, a variant of BBQ. Only after discovering this bug did we realize that we had to spend even more time and energy implementing BBQ ourselves.
- 4. Determining which tasks were most important and representative would have required running all 204 tasks, validating results, and extensively analyzing output—a substantial research undertaking, even for an organization with significant engineering resources.2
-
- While it was a useful exercise to try to implement BIG-Bench, we found it sufficiently unwieldy that we dropped it after this experiment.
-
- **HELM: The top-down approach to curating an opinionated set of evaluations**
-
- BIG-bench is a “bottom-up” effort where anyone can submit any task with some limited vetting by a group of expert organizers. HELM, on the other hand, takes an opinionated “top-down” approach where experts decide what tasks to evaluate models on. HELM evaluates models across scenarios like reasoning and disinformation using standardized metrics like accuracy, calibration, robustness, and fairness. We provide API access to HELM's developers to run the benchmark on our models. This solves two issues we had with BIG-bench: 1) it requires no significant engineering effort from us, and 2) we can rely on experts to select and interpret specific high quality evaluations.
-
- However, HELM introduces its own challenges. Methods that work well for evaluating other providers’ models do not necessarily work well for our models, and vice versa. For example, Anthropic’s Claude series of models are trained to adhere to a specific text format, which we call the Human/Assistant format. When we internally evaluate our models, we adhere to this specific format. If we do not adhere to this format, sometimes Claude will give uncharacteristic responses, making standardized evaluation metrics less trustworthy. Because HELM needs to maintain consistency with how it prompts other models, it does not use the Human/Assistant format when evaluating our models. This means that HELM gives a misleading impression of Claude’s performance.
-
- Furthermore, HELM iteration time is slow—it can take months to evaluate new models. This makes sense because it is a volunteer engineering effort led by a research university. Faster turnarounds would help us to more quickly understand our models as they rapidly evolve. Finally, HELM requires coordination and communication with external parties. This effort requires time and patience, and both sides are likely understaffed and juggling other demands, which can add to the slow iteration times.
-
- ### The subjectivity of human evaluations
-
- So far, we have only discussed evaluations that are similar to simple multiple choice tests; however, AI systems are designed for open-ended dynamic interactions with people. How can we design evaluations that more closely approximate how these models are used in the real world?
-
- **A/B tests with crowdworkers**
-
- Currently, we mainly (but not entirely) rely on one basic type of human evaluation: we conduct A/B tests on crowdsourcing or contracting platforms where people engage in open-ended dialogue with two models and select the response from Model A or B that is more helpful or harmless. In the case of harmlessness, we encourage crowdworkers to actively red team our models, or to adversarially probe them for harmful outputs. We use the resulting data to rank our models according to how helpful or harmless they are. This approach to evaluation has the benefits of corresponding to a realistic setting (e.g., a conversation vs. a multiple choice exam) and allowing us to rank different models against one another.
-
- However, there are a few limitations of this evaluation method:
-
- 1. These experiments are expensive and time-consuming to run. We need to work with and pay for third-party crowdworker platforms or contractors, build custom web interfaces to our models, design careful instructions for A/B testers, analyze and store the resulting data, and address the myriad known ethical challenges of employing crowdworkers. In the case of harmlessness testing, our experiments additionally risk exposing people to harmful outputs.
- 2. Human evaluations can vary significantly depending on the characteristics of the human evaluators. Key factors that may influence someone's assessment include their level of creativity, motivation, and ability to identify potential flaws or issues with the system being tested.
- 3. There is an inherent tension between helpfulness and harmlessness. A system could avoid harm simply by providing unhelpful responses like “sorry, I can’t help you with that”. What is the right balance between helpfulness and harmlessness? What numerical value indicates a model is sufficiently helpful and harmless? What high-level norms or values should we test for above and beyond helpfulness and harmlessness?
-
- More work is needed to further the science of human evaluations.
-
- **Red teaming for harms related to national security**
-
- Moving beyond crowdworkers, we have also explored having domain experts red team our models for harmful outputs in domains relevant to national security. The goal is determining whether and how AI models could create or exacerbate national security risks. We recently piloted a more systematic approach to red teaming models for such risks, which we call [frontier threats red teaming](/news/frontier-threats-red-teaming-for-ai-safety).
-
- Frontier threats red teaming involves working with subject matter experts to define high-priority threat models, having experts extensively probe the model to assess whether the system could create or exacerbate national security risks per predefined threat models, and developing repeatable quantitative evaluations and mitigations.
-
- Our early work on frontier threats red teaming revealed additional challenges:
-
- 1. The unique characteristics of the national security context make evaluating models for such threats more challenging than evaluating for other types of harms. National security risks are complicated, require expert and very sensitive knowledge, and depend on real-world scenarios of how actors might cause harm.
- 2. Red teaming AI systems is presently more art than science; red teamers attempt to elicit concerning behaviors by probing models, but this process is not yet standardized. A robust and repeatable process is critical to ensure that red teaming accurately reflects model capabilities and establishes a shared baseline on which different models can be meaningfully compared.
- 3. We have found that in certain cases, it is critical to involve red teamers with security clearances due to the nature of the information involved. However, this may limit what information red teamers can share with AI developers outside of classified environments. This could, in turn, limit AI developers’ ability to fully understand and mitigate threats identified by domain experts.
- 4. Red teaming for certain national security harms could have legal ramifications if a model outputs controlled information. There are open questions around constructing appropriate legal safe harbors to enable red teaming without incurring unintended legal risks.
-
- Going forward, red teaming for harms related to national security will require coordination across stakeholders to develop standardized processes, legal safeguards, and secure information sharing protocols that enable us to test these systems without compromising sensitive information.
-
- ### The ouroboros of model-generated evaluations
-
- As models start to achieve human-level capabilities, we can also employ them to evaluate themselves. So far, we have [found success](/research/discovering-language-model-behaviors-with-model-written-evaluations) in using models to generate novel multiple choice evaluations that allow us to screen for a large and diverse variety of troubling behaviors. Using this approach, our AI systems generate evaluations in minutes, compared to the days or months that it takes to develop human-generated evaluations.
-
- However, model-generated evaluations have their own challenges. These include:
-
- 1. We currently rely on humans to verify the accuracy of model-generated evaluations. This inherits all of the challenges of human evaluations described above.
- 2. We know from our experience with BIG-bench, HELM, BBQ, etc., that our models can contain social biases and can fabricate information. As such, any model-generated evaluation may inherit these undesirable characteristics, which could skew the results in hard-to-disentangle ways.
-
- As a counter-example, consider [Constitutional AI](/research/constitutional-ai-harmlessness-from-ai-feedback) (CAI), a method in which we replace human red teaming with model-based red teaming in order to train Claude to be more harmless. Despite the usage of models to red team models, we surprisingly find that humans find CAI models to be more harmless than models that were red teamed in advance by humans. Although this is promising, model-generated evaluations remain complicated and deserve deeper study.
-
- ### Preserving the objectivity of third-party audits while leveraging internal expertise
-
- The thing that differentiates third-party audits from third-party evaluations is that audits are deeper independent assessments focused on risks, while evaluations take a broader look at capabilities. We have participated in third-party safety evaluations conducted by the [Alignment Research Center](https://evals.alignment.org/) (ARC), which assesses frontier AI models for dangerous capabilities (e.g., the ability for a model to accumulate resources, make copies of itself, become hard to shut down, etc.). Engaging external experts has the advantage of leveraging specialized domain expertise and increasing the likelihood of an unbiased audit. Initially, we expected this collaboration to be straightforward, but it ended up requiring significant science and engineering support on our end. Providing full-time assistance diverted resources from internal evaluation efforts.
-
- When doing this audit, we realized that the relationship between auditors and those being audited poses challenges that must be navigated carefully. Auditors typically limit details shared with auditees to preserve evaluation integrity. However, without adequate information the evaluated party may struggle to address underlying issues when crafting technical evaluations. In this case, after seeing the final audit report, we realized that we could have helped ARC be more successful in identifying concerning behavior if we had known more details about their (clever and well-designed) audit approach. This is because getting models to perform near the limits of their capabilities is a fundamentally difficult research endeavor. Prompt engineering and fine-tuning language models are active research areas, with most expertise residing within AI companies. With more collaboration, we could have leveraged our deep technical knowledge of our models to help ARC execute the evaluation more effectively.
-
- ## Policy recommendations
-
- As we have explored in this post, building meaningful AI evaluations is a challenging enterprise. To advance the science and engineering of evaluations, we propose that policymakers:
-
- **Fund and support:**
-
- - **The science of repeatable and useful evaluations.** Today there are many open questions about what makes an evaluation useful and high-quality. Governments should fund grants and research programs targeted at computer science departments and organizations dedicated to the development of AI evaluations to study what constitutes a high-quality evaluation and develop robust, replicable methodologies that enable comparative assessments of AI systems. We recommend that governments opt for quality over quantity (i.e., funding the development of one higher-quality evaluation rather than ten lower-quality evaluations).
- - **The implementation of existing evaluations.** While continually developing new evaluations is useful, enabling the implementation of existing, high-quality evaluations by multiple parties is also important. For example, governments can fund engineering efforts to create easy-to-install and -run software packages for evaluations like BIG-bench and develop version-controlled and standardized dynamic benchmarks that are easy to implement.
- - **Analyzing the robustness of existing evaluations.** After evaluations are implemented, they require constant monitoring (e.g., to determine whether an evaluation is saturated and no longer useful).
-
- **Increase funding for government agencies focused on evaluations**, such as the [National Institute of Standards and Technology](/news/an-ai-policy-tool-for-today-ambitiously-invest-in-nist) (NIST) in the United States. Policymakers should also encourage behavioral norms via a public “AI safety leaderboard” to incentivize private companies performing well on evaluations. This could function similarly to NIST’s [Face Recognition Vendor Test](https://www.nist.gov/programs-projects/face-recognition-vendor-test-frvt) (FRVT), which was created to provide independent evaluations of commercially available facial recognition systems. By publishing performance benchmarks, it allowed consumers and regulators to better understand the capabilities and limitations4 of different systems.
-
- **Create a legal safe harbor allowing companies to work with governments and third-parties to rigorously evaluate models for national security risks**—such as those in the chemical, biological, radiological and nuclear defense (CBRN) domains—without legal repercussions, in the interest of improving safety. This could also include a “responsible disclosure protocol” that enables labs to share sensitive information about identified risks.
-
- ## Conclusion
-
- We hope that by openly sharing our experiences evaluating our own systems across many different dimensions, we can help people interested in AI policy acknowledge challenges with current model evaluations.
-
- If you found this post helpful and want to discuss evaluations of AI systems with Anthropic, please email <policy@anthropic.com>. We are going to spend the next couple of months chatting with more people about this area and will endeavor to share what we learn in those conversations.
-
- If you’d like to cite this post you can use the following Bibtex key:
-
- @online{ganguli2023challenges,
- author = {Deep Ganguli and Nicholas Schiefer and Marina Favaro and Jack Clark},
- title = {Challenges in evaluating {AI} systems},
- date = {2023-10-04},
- year = {2023},
- url = {[https://www.anthropic.com/index/evaluating-ai-systems](/research/evaluating-ai-systems)},
- }
-
- ### Footnotes
-
- \[1\] How long did it take to develop BBQ? BBQ took the developers ~2 people years spread over 6 months across 8 people to build. Designing and implementing even just a single evaluation is a resource intensive effort that can take tens of people several months.
-
- \[2\] BIG-bench Hard was released 4 months after BIG-bench that narrowed down to 23 hard tasks. We were implementing BIG-bench before BIG-bench Hard was released.
-
- \[3\] See for example [Ghost Work](https://ghostwork.info/).
-
- \[4\] For example, a well-known issue with many facial recognition systems has been higher error rates for certain demographics, like women and people of color. The FRVT reports shined a spotlight on these accuracy discrepancies, which put pressure on vendors to improve their algorithms and mitigate systemic biases.
-
-
- ## Related content
-
- ### Yes, Claude can do Nine Loops
-
- Guest writer and physicist Matt von Hippel shares what happened when he issued a challenge to AI companies to solve a problem in his former subfield of theoretical physics.
-
- [Read more](/research/yes-claude-can-do-nine-loops)
-
- ### Project Swap: What happens when agents trade for us?
-
- To see what works and what breaks when agents are sent into a market, we made a miniature market of Claudes.
-
- [Read more](/research/project-swap)
-
- ### How Claude is uplifting biomolecular modeling
-
- Claude made the open-source models that scientists use to predict and design biomolecules faster and more memory-efficient. Claude optimized more than 30 of these models in just under four weeks, speeding them up roughly 4x on average. It also created a low-memory mode that enables the accurate prediction of biomolecular systems larger than 10,000 tokens (amino acids, nucleotides, and atoms from small molecules and ions) on a single NVIDIA GPU node.
-
- [Read more](/research/claude-uplifts-biomolecular-modeling)
-
- [](/)
-
- ### Products
-
- - [Claude](https://claude.com/product/overview)
- - [Claude Code](https://claude.com/product/claude-code)
- - [Claude Code Enterprise](https://claude.com/product/claude-code/enterprise)
- - [Claude Cowork](https://claude.com/product/cowork)
- - [@Claude](https://claude.com/product/tag)
- - [Claude Design](https://claude.com/product/design)
- - [Claude Science](https://claude.com/product/claude-science)
- - [Claude Security](https://claude.com/product/claude-security)
- - [Claude in Chrome](https://claude.com/claude-in-chrome)
- - [Claude for Microsoft 365](https://claude.com/claude-for-microsoft-365)
- - [Skills](https://www.claude.com/skills)
- - [Download app](https://claude.ai/download)
- - [Pricing](https://claude.com/pricing)
- - [Log in to Claude](https://claude.ai/)
-
- ### Models
-
- - [Mythos](https://www.anthropic.com/claude/mythos)
- - [Fable](https://www.anthropic.com/claude/fable)
- - [Opus](https://www.anthropic.com/claude/opus)
- - [Sonnet](https://www.anthropic.com/claude/sonnet)
- - [Haiku](https://www.anthropic.com/claude/haiku)
-
- ### Solutions
-
- - [AI agents](https://claude.com/solutions/agents)
- - [Code modernization](https://claude.com/solutions/code-modernization)
- - [Coding](https://claude.com/solutions/coding)
- - [Commerce](https://claude.com/solutions/commerce)
- - [Customer support](https://claude.com/solutions/customer-support)
- - [Cybersecurity](https://claude.com/solutions/cybersecurity)
- - [Enterprise](https://claude.com/solutions/enterprise)
- - [Financial services](https://claude.com/solutions/financial-services)
- - [Government](https://claude.com/solutions/government)
- - [Healthcare](https://claude.com/solutions/healthcare)
- - [Higher education](https://claude.com/solutions/education)
- - [K-12 teachers](https://claude.com/solutions/teachers)
- - [Legal](https://claude.com/solutions/legal)
- - [Life sciences](https://claude.com/solutions/life-sciences)
- - [Nonprofits](https://claude.com/solutions/nonprofits)
- - [Sales](https://claude.com/solutions/sales)
- - [Small business](https://claude.com/solutions/small-business)
-
- ### Claude Platform
-
- - [Overview](https://claude.com/platform/api)
- - [Developer docs](https://platform.claude.com/docs)
- - [Pricing](https://claude.com/pricing#api)
- - [Ecosystem](https://claude.com/ecosystem)
- - [Marketplace](https://claude.com/platform/marketplace)
- - [Regional compliance](https://claude.com/regional-compliance)
- - [Claude on AWS](https://claude.com/partners/claude-on-aws)
- - [Google Cloud](https://claude.com/partners/google-cloud-vertex-ai)
- - [Microsoft Foundry](https://claude.com/partners/microsoft-foundry)
- - [Console login](https://platform.claude.com/)
-
- ### Resources
-
- - [Blog](https://claude.com/blog)
- - [Claude partner network](https://claude.com/partners)
- - [Community](https://claude.com/community)
- - [Connectors](https://claude.com/connectors)
- - [Courses](https://academy.claude.com)
- - [Customer stories](https://claude.com/customers)
- - [Developer blog](https://claude.dev)
- - [Engineering at Anthropic](/engineering)
- - [Events](/events)
- - [Plugins](https://claude.com/plugins)
- - [Powered by Claude](https://claude.com/partners/powered-by-claude)
- - [Service partners](https://claude.com/partners/services)
- - [Tutorials](https://claude.com/resources/tutorials)
- - [Use cases](https://claude.com/resources/use-cases)
-
- ### Programs
-
- - [Startups](https://claude.com/programs/startups)
- - [Scientists](https://claude.com/programs/team-plan-for-scientists)
-
- ### Help and security
-
- - [Availability](https://www.anthropic.com/supported-countries)
- - [Status](https://status.anthropic.com/)
- - [Support center](https://support.claude.com/en/)
-
- ### Company
-
- - [Anthropic](/company)
- - [Careers](/careers)
- - [Leadership](/company/leadership)
- - [Policy](/policy)
- - [Economic Futures](/economic-futures)
- - [Research](/research)
- - [News](/news)
- - [Claude’s Constitution](/constitution)
- - [Claude Corps](/claude-corps)
- - [Keep thinking](https://www.anthropic.com/path-to-hope)
- - [Policy on the AI Exponential](/policy-on-the-ai-exponential)
- - [Responsible Scaling Policy](https://www.anthropic.com/news/announcing-our-updated-responsible-scaling-policy)
- - [Security and compliance](https://trust.anthropic.com/)
- - [Transparency](/transparency)
-
+ ## Evaluator Optimizer
- ---
- ## evaluator-optimizer.md
+ Source: https://github.com/anthropics/claude-cookbooks/blob/main/patterns/agents/evaluator_optimizer.ipynb
+ Path: 12-Eval-Testing/claude-cookbooks-patterns-agents-evaluator-optimizer.md
## Evaluator-Optimizer Workflow
In this workflow, one LLM call generates a response while another
provides evaluation and feedback in a loop.
### When to use this workflow
This workflow is particularly effective when we have:
- Clear evaluation criteria
- Value from iterative refinement
The two signs of good fit are:
- LLM responses can be demonstrably improved when feedback is provided
- The LLM can provide meaningful feedback itself
:::
::: {.cell .code execution_count="1"}
``` python
from util import extract_xml, llm_call
def generate(prompt: str, task: str, context: str = "") -> tuple[str, str]:
"""Generate and improve a solution based on feedback."""
full_prompt = f"{prompt}\n{context}\nTask: {task}" if context else f"{prompt}\nTask: {task}"
response = llm_call(full_prompt)
thoughts = extract_xml(response, "thoughts")
result = extract_xml(response, "response")
print("\n=== GENERATION START ===")
print(f"Thoughts:\n{thoughts}\n")
print(f"Generated:\n{result}")
print("=== GENERATION END ===\n")
return thoughts, result
def evaluate(prompt: str, content: str, task: str) -> tuple[str, str]:
"""Evaluate if a solution meets requirements."""
full_prompt = f"{prompt}\nOriginal task: {task}\nContent to evaluate: {content}"
response = llm_call(full_prompt)
evaluation = extract_xml(response, "evaluation")
feedback = extract_xml(response, "feedback")
print("=== EVALUATION START ===")
print(f"Status: {evaluation}")
print(f"Feedback: {feedback}")
print("=== EVALUATION END ===\n")
return evaluation, feedback
def loop(task: str, evaluator_prompt: str, generator_prompt: str) -> tuple[str, list[dict]]:
"""Keep generating and evaluating until requirements are met."""
memory = []
chain_of_thought = []
thoughts, result = generate(generator_prompt, task)
memory.append(result)
chain_of_thought.append({"thoughts": thoughts, "result": result})
while True:
evaluation, feedback = evaluate(evaluator_prompt, result, task)
if evaluation == "PASS":
return result, chain_of_thought
context = "\n".join(
["Previous attempts:", *[f"- {m}" for m in memory], f"\nFeedback: {feedback}"]
)
thoughts, result = generate(generator_prompt, task, context)
memory.append(result)
chain_of_thought.append({"thoughts": thoughts, "result": result})
```
:::
::: {.cell .markdown}
### Example Use Case: Iterative coding loop
:::
:::: {.cell .code}
``` python
evaluator_prompt = """
Evaluate this following code implementation for:
1. code correctness
2. time complexity
3. style and best practices
You should be evaluating only and not attemping to solve the task.
Only output "PASS" if all criteria are met and you have no further suggestions for improvements.
Output your evaluation concisely in the following format.
<evaluation>PASS, NEEDS_IMPROVEMENT, or FAIL</evaluation>
<feedback>
What needs improvement and why.
</feedback>
"""
generator_prompt = """
Your goal is to complete the task based on <user input>. If there are feedback
from your previous generations, you should reflect on them to improve your solution
Output your answer concisely in the following format:
<thoughts>
[Your understanding of the task and feedback and how you plan to improve]
</thoughts>
<response>
[Your code implementation here]
</response>
"""
task = """
<user input>
Implement a Stack with:
1. push(x)
2. pop()
3. getMin()
All operations should be O(1).
</user input>
"""
loop(task, evaluator_prompt, generator_prompt)
```
::: {.output .stream .stdout}
=== GENERATION START ===
Thoughts:
The task requires implementing a Stack with constant time operations including finding minimum.
To achieve O(1) for getMin(), we need to maintain a second stack that keeps track of minimums.
Each time we push, if the value is smaller than current min, we add it to minStack.
When we pop, if the popped value equals current min, we also pop from minStack.
Generated:
```python
class MinStack:
def __init__(self):
self.stack = []
self.minStack = []
def push(self, x: int) -> None:
self.stack.append(x)
if not self.minStack or x <= self.minStack[-1]:
self.minStack.append(x)
def pop(self) -> None:
if not self.stack:
return
if self.stack[-1] == self.minStack[-1]:
self.minStack.pop()
self.stack.pop()
def getMin(self) -> int:
if not self.minStack:
return None
return self.minStack[-1]
```
=== GENERATION END ===
=== EVALUATION START ===
Status: NEEDS_IMPROVEMENT
Feedback:
While the implementation is generally correct and achieves O(1) time complexity for all operations, there are several areas for improvement:
1. Error Handling:
- pop() should raise an exception when stack is empty rather than silently returning
- getMin() should raise an exception when stack is empty rather than returning None
- These behaviors should be consistent with standard stack implementations
2. Type Hints:
- Return type hint for pop() should be None or void
- Missing type hints for class variables stack and minStack
3. Documentation:
- Missing docstrings for class and methods
- Missing parameter descriptions
4. Edge Cases:
- No explicit handling of invalid input types for push()
The core algorithm is correct and efficient, but the implementation could be more robust and better documented for production use.
=== EVALUATION END ===
=== GENERATION START ===
Thoughts:
Based on the feedback, I'll improve the implementation by:
1. Adding proper error handling with exceptions
2. Including type hints and docstrings
3. Adding input validation
4. Maintaining O(1) time complexity for all operations
Generated:
```python
from typing import List
class MinStack:
"""A stack that supports push, pop, and getting minimum element in O(1) time."""
def __init__(self):
"""Initialize empty stack with two internal lists."""
self.stack: List[int] = []
self.minStack: List[int] = []
def push(self, x: int) -> None:
"""
Push element onto stack and update minimum stack.
Args:
x: Integer to push onto stack
Raises:
TypeError: If x is not an integer
"""
if not isinstance(x, int):
raise TypeError("Input must be an integer")
self.stack.append(x)
if not self.minStack or x <= self.minStack[-1]:
self.minStack.append(x)
def pop(self) -> None:
"""
Remove and return top element from stack.
Raises:
IndexError: If stack is empty
"""
if not self.stack:
raise IndexError("Cannot pop from empty stack")
if self.stack[-1] == self.minStack[-1]:
self.minStack.pop()
self.stack.pop()
def getMin(self) -> int:
"""
Get minimum element in stack.
Returns:
int: Minimum element in stack
Raises:
IndexError: If stack is empty
"""
if not self.minStack:
raise IndexError("Cannot get minimum from empty stack")
return self.minStack[-1]
```
=== GENERATION END ===
:::
::::
-
---
- ## require-tests-stop.local.md
- Before stopping, please run tests to verify your changes work correctly.
-
- Look for test commands like:
- - `npm test`
- - `pytest`
- - `cargo test`
-
- **Note:** This rule blocks stopping if no test commands appear in the transcript.
- Enable this rule only when you want strict test enforcement.
-
-
- ---
- ## testing-strategies.md
-
- # Command Testing Strategies
-
- Comprehensive strategies for testing slash commands before deployment and distribution.
-
- ## Overview
-
- Testing commands ensures they work correctly, handle edge cases, and provide good user experience. A systematic testing approach catches issues early and builds confidence in command reliability.
-
- ## Testing Levels
-
- ### Level 1: Syntax and Structure Validation
-
- **What to test:**
- - YAML frontmatter syntax
- - Markdown format
- - File location and naming
-
- **How to test:**
-
- ```bash
- # Validate YAML frontmatter
- head -n 20 .claude/commands/my-command.md | grep -A 10 "^---"
-
- # Check for closing frontmatter marker
- head -n 20 .claude/commands/my-command.md | grep -c "^---" # Should be 2
-
- # Verify file has .md extension
- ls .claude/commands/*.md
-
- # Check file is in correct location
- test -f .claude/commands/my-command.md && echo "Found" || echo "Missing"
- ```
-
- **Automated validation script:**
-
- ```bash
- #!/bin/bash
- # validate-command.sh
-
- COMMAND_FILE="$1"
-
- if [ ! -f "$COMMAND_FILE" ]; then
- echo "ERROR: File not found: $COMMAND_FILE"
- exit 1
- fi
-
- # Check .md extension
- if [[ ! "$COMMAND_FILE" =~ \.md$ ]]; then
- echo "ERROR: File must have .md extension"
- exit 1
- fi
-
- # Validate YAML frontmatter if present
- if head -n 1 "$COMMAND_FILE" | grep -q "^---"; then
- # Count frontmatter markers
- MARKERS=$(head -n 50 "$COMMAND_FILE" | grep -c "^---")
- if [ "$MARKERS" -ne 2 ]; then
- echo "ERROR: Invalid YAML frontmatter (need exactly 2 '---' markers)"
- exit 1
- fi
- echo "✓ YAML frontmatter syntax valid"
- fi
-
- # Check for empty file
- if [ ! -s "$COMMAND_FILE" ]; then
- echo "ERROR: File is empty"
- exit 1
- fi
-
- echo "✓ Command file structure valid"
- ```
-
- ### Level 2: Frontmatter Field Validation
-
- **What to test:**
- - Field types correct
- - Values in valid ranges
- - Required fields present (if any)
-
- **Validation script:**
-
- ```bash
- #!/bin/bash
- # validate-frontmatter.sh
-
- COMMAND_FILE="$1"
-
- # Extract YAML frontmatter
- FRONTMATTER=$(sed -n '/^---$/,/^---$/p' "$COMMAND_FILE" | sed '1d;$d')
-
- if [ -z "$FRONTMATTER" ]; then
- echo "No frontmatter to validate"
- exit 0
- fi
-
- # Check 'model' field if present
- if echo "$FRONTMATTER" | grep -q "^model:"; then
- MODEL=$(echo "$FRONTMATTER" | grep "^model:" | cut -d: -f2 | tr -d ' ')
- if ! echo "sonnet opus haiku" | grep -qw "$MODEL"; then
- echo "ERROR: Invalid model '$MODEL' (must be sonnet, opus, or haiku)"
- exit 1
- fi
- echo "✓ Model field valid: $MODEL"
- fi
-
- # Check 'allowed-tools' field format
- if echo "$FRONTMATTER" | grep -q "^allowed-tools:"; then
- echo "✓ allowed-tools field present"
- # Could add more sophisticated validation here
- fi
-
- # Check 'description' length
- if echo "$FRONTMATTER" | grep -q "^description:"; then
- DESC=$(echo "$FRONTMATTER" | grep "^description:" | cut -d: -f2-)
- LENGTH=${#DESC}
- if [ "$LENGTH" -gt 80 ]; then
- echo "WARNING: Description length $LENGTH (recommend < 60 chars)"
- else
- echo "✓ Description length acceptable: $LENGTH chars"
- fi
- fi
-
- echo "✓ Frontmatter fields valid"
- ```
-
- ### Level 3: Manual Command Invocation
-
- **What to test:**
- - Command appears in `/help`
- - Command executes without errors
- - Output is as expected
-
- **Test procedure:**
-
- ```bash
- # 1. Start Claude Code
- claude --debug
-
- # 2. Check command appears in help
- > /help
- # Look for your command in the list
-
- # 3. Invoke command without arguments
- > /my-command
- # Check for reasonable error or behavior
-
- # 4. Invoke with valid arguments
- > /my-command arg1 arg2
- # Verify expected behavior
-
- # 5. Check debug logs
- tail -f ~/.claude/debug-logs/latest
- # Look for errors or warnings
- ```
-
- ### Level 4: Argument Testing
-
- **What to test:**
- - Positional arguments work ($1, $2, etc.)
- - $ARGUMENTS captures all arguments
- - Missing arguments handled gracefully
- - Invalid arguments detected
-
- **Test matrix:**
-
- | Test Case | Command | Expected Result |
- |-----------|---------|-----------------|
- | No args | `/cmd` | Graceful handling or useful message |
- | One arg | `/cmd arg1` | $1 substituted correctly |
- | Two args | `/cmd arg1 arg2` | $1 and $2 substituted |
- | Extra args | `/cmd a b c d` | All captured or extras ignored appropriately |
- | Special chars | `/cmd "arg with spaces"` | Quotes handled correctly |
- | Empty arg | `/cmd ""` | Empty string handled |
-
- **Test script:**
-
- ```bash
- #!/bin/bash
- # test-command-arguments.sh
-
- COMMAND="$1"
-
- echo "Testing argument handling for /$COMMAND"
- echo
-
- echo "Test 1: No arguments"
- echo " Command: /$COMMAND"
- echo " Expected: [describe expected behavior]"
- echo " Manual test required"
- echo
-
- echo "Test 2: Single argument"
- echo " Command: /$COMMAND test-value"
- echo " Expected: 'test-value' appears in output"
- echo " Manual test required"
- echo
-
- echo "Test 3: Multiple arguments"
- echo " Command: /$COMMAND arg1 arg2 arg3"
- echo " Expected: All arguments used appropriately"
- echo " Manual test required"
- echo
-
- echo "Test 4: Special characters"
- echo " Command: /$COMMAND \"value with spaces\""
- echo " Expected: Entire phrase captured"
- echo " Manual test required"
- ```
-
- ### Level 5: File Reference Testing
-
- **What to test:**
- - @ syntax loads file contents
- - Non-existent files handled
- - Large files handled appropriately
- - Multiple file references work
-
- **Test procedure:**
-
- ```bash
- # Create test files
- echo "Test content" > /tmp/test-file.txt
- echo "Second file" > /tmp/test-file-2.txt
-
- # Test single file reference
- > /my-command /tmp/test-file.txt
- # Verify file content is read
-
- # Test non-existent file
- > /my-command /tmp/nonexistent.txt
- # Verify graceful error handling
-
- # Test multiple files
- > /my-command /tmp/test-file.txt /tmp/test-file-2.txt
- # Verify both files processed
-
- # Test large file
- dd if=/dev/zero of=/tmp/large-file.bin bs=1M count=100
- > /my-command /tmp/large-file.bin
- # Verify reasonable behavior (may truncate or warn)
-
- # Cleanup
- rm /tmp/test-file*.txt /tmp/large-file.bin
- ```
-
- ### Level 6: Bash Execution Testing
-
- **What to test:**
- - !` commands execute correctly
- - Command output included in prompt
- - Command failures handled
- - Security: only allowed commands run
-
- **Test procedure:**
-
- ```bash
- # Create test command with bash execution
- cat > .claude/commands/test-bash.md << 'EOF'
- ---
- description: Test bash execution
- allowed-tools: Bash(echo:*), Bash(date:*)
- ---
-
- Current date: !`date`
- Test output: !`echo "Hello from bash"`
-
- Analysis of output above...
- EOF
-
- # Test in Claude Code
- > /test-bash
- # Verify:
- # 1. Date appears correctly
- # 2. Echo output appears
- # 3. No errors in debug logs
-
- # Test with disallowed command (should fail or be blocked)
- cat > .claude/commands/test-forbidden.md << 'EOF'
- ---
- description: Test forbidden command
- allowed-tools: Bash(echo:*)
- ---
-
- Trying forbidden: !`ls -la /`
- EOF
-
- > /test-forbidden
- # Verify: Permission denied or appropriate error
- ```
-
- ### Level 7: Integration Testing
-
- **What to test:**
- - Commands work with other plugin components
- - Commands interact correctly with each other
- - State management works across invocations
- - Workflow commands execute in sequence
-
- **Test scenarios:**
-
- **Scenario 1: Command + Hook Integration**
-
- ```bash
- # Setup: Command that triggers a hook
- # Test: Invoke command, verify hook executes
-
- # Command: .claude/commands/risky-operation.md
- # Hook: PreToolUse that validates the operation
-
- > /risky-operation
- # Verify: Hook executes and validates before command completes
- ```
-
- **Scenario 2: Command Sequence**
-
- ```bash
- # Setup: Multi-command workflow
- > /workflow-init
- # Verify: State file created
-
- > /workflow-step2
- # Verify: State file read, step 2 executes
-
- > /workflow-complete
- # Verify: State file cleaned up
- ```
-
- **Scenario 3: Command + MCP Integration**
-
- ```bash
- # Setup: Command uses MCP tools
- # Test: Verify MCP server accessible
-
- > /mcp-command
- # Verify:
- # 1. MCP server starts (if stdio)
- # 2. Tool calls succeed
- # 3. Results included in output
- ```
-
- ## Automated Testing Approaches
-
- ### Command Test Suite
-
- Create a test suite script:
-
- ```bash
- #!/bin/bash
- # test-commands.sh - Command test suite
-
- TEST_DIR=".claude/commands"
- FAILED_TESTS=0
-
- echo "Command Test Suite"
- echo "=================="
- echo
-
- for cmd_file in "$TEST_DIR"/*.md; do
- cmd_name=$(basename "$cmd_file" .md)
- echo "Testing: $cmd_name"
-
- # Validate structure
- if ./validate-command.sh "$cmd_file"; then
- echo " ✓ Structure valid"
- else
- echo " ✗ Structure invalid"
- ((FAILED_TESTS++))
- fi
-
- # Validate frontmatter
- if ./validate-frontmatter.sh "$cmd_file"; then
- echo " ✓ Frontmatter valid"
- else
- echo " ✗ Frontmatter invalid"
- ((FAILED_TESTS++))
- fi
-
- echo
- done
-
- echo "=================="
- echo "Tests complete"
- echo "Failed: $FAILED_TESTS"
-
- exit $FAILED_TESTS
- ```
-
- ### Pre-Commit Hook
-
- Validate commands before committing:
-
- ```bash
- #!/bin/bash
- # .git/hooks/pre-commit
-
- echo "Validating commands..."
-
- COMMANDS_CHANGED=$(git diff --cached --name-only | grep "\.claude/commands/.*\.md")
-
- if [ -z "$COMMANDS_CHANGED" ]; then
- echo "No commands changed"
- exit 0
- fi
-
- for cmd in $COMMANDS_CHANGED; do
- echo "Checking: $cmd"
-
- if ! ./scripts/validate-command.sh "$cmd"; then
- echo "ERROR: Command validation failed: $cmd"
- exit 1
- fi
- done
-
- echo "✓ All commands valid"
- ```
-
- ### Continuous Testing
-
- Test commands in CI/CD:
-
- ```yaml
- # .github/workflows/test-commands.yml
- name: Test Commands
-
- on: [push, pull_request]
-
- jobs:
- test:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
-
- - name: Validate command structure
- run: |
- for cmd in .claude/commands/*.md; do
- echo "Testing: $cmd"
- ./scripts/validate-command.sh "$cmd"
- done
-
- - name: Validate frontmatter
- run: |
- for cmd in .claude/commands/*.md; do
- ./scripts/validate-frontmatter.sh "$cmd"
- done
-
- - name: Check for TODOs
- run: |
- if grep -r "TODO" .claude/commands/; then
- echo "ERROR: TODOs found in commands"
- exit 1
- fi
- ```
-
- ## Edge Case Testing
-
- ### Test Edge Cases
-
- **Empty arguments:**
- ```bash
- > /cmd ""
- > /cmd '' ''
- ```
-
- **Special characters:**
- ```bash
- > /cmd "arg with spaces"
- > /cmd arg-with-dashes
- > /cmd arg_with_underscores
- > /cmd arg/with/slashes
- > /cmd 'arg with "quotes"'
- ```
-
- **Long arguments:**
- ```bash
- > /cmd $(python -c "print('a' * 10000)")
- ```
-
- **Unusual file paths:**
- ```bash
- > /cmd ./file
- > /cmd ../file
- > /cmd ~/file
- > /cmd "/path with spaces/file"
- ```
-
- **Bash command edge cases:**
- ```markdown
- # Commands that might fail
- !`exit 1`
- !`false`
- !`command-that-does-not-exist`
-
- # Commands with special output
- !`echo ""`
- !`cat /dev/null`
- !`yes | head -n 1000000`
- ```
-
- ## Performance Testing
-
- ### Response Time Testing
-
- ```bash
- #!/bin/bash
- # test-command-performance.sh
-
- COMMAND="$1"
-
- echo "Testing performance of /$COMMAND"
- echo
-
- for i in {1..5}; do
- echo "Run $i:"
- START=$(date +%s%N)
-
- # Invoke command (manual step - record time)
- echo " Invoke: /$COMMAND"
- echo " Start time: $START"
- echo " (Record end time manually)"
- echo
- done
-
- echo "Analyze results:"
- echo " - Average response time"
- echo " - Variance"
- echo " - Acceptable threshold: < 3 seconds for fast commands"
- ```
-
- ### Resource Usage Testing
-
- ```bash
- # Monitor Claude Code during command execution
- # In terminal 1:
- claude --debug
-
- # In terminal 2:
- watch -n 1 'ps aux | grep claude'
-
- # Execute command and observe:
- # - Memory usage
- # - CPU usage
- # - Process count
- ```
-
- ## User Experience Testing
-
- ### Usability Checklist
-
- - [ ] Command name is intuitive
- - [ ] Description is clear in `/help`
- - [ ] Arguments are well-documented
- - [ ] Error messages are helpful
- - [ ] Output is formatted readably
- - [ ] Long-running commands show progress
- - [ ] Results are actionable
- - [ ] Edge cases have good UX
-
- ### User Acceptance Testing
-
- Recruit testers:
-
- ```markdown
- # Testing Guide for Beta Testers
-
- ## Command: /my-new-command
-
- ### Test Scenarios
-
- 1. **Basic usage:**
- - Run: `/my-new-command`
- - Expected: [describe]
- - Rate clarity: 1-5
-
- 2. **With arguments:**
- - Run: `/my-new-command arg1 arg2`
- - Expected: [describe]
- - Rate usefulness: 1-5
-
- 3. **Error case:**
- - Run: `/my-new-command invalid-input`
- - Expected: Helpful error message
- - Rate error message: 1-5
-
- ### Feedback Questions
-
- 1. Was the command easy to understand?
- 2. Did the output meet your expectations?
- 3. What would you change?
- 4. Would you use this command regularly?
- ```
-
- ## Testing Checklist
-
- Before releasing a command:
-
- ### Structure
- - [ ] File in correct location
- - [ ] Correct .md extension
- - [ ] Valid YAML frontmatter (if present)
- - [ ] Markdown syntax correct
-
- ### Functionality
- - [ ] Command appears in `/help`
- - [ ] Description is clear
- - [ ] Command executes without errors
- - [ ] Arguments work as expected
- - [ ] File references work
- - [ ] Bash execution works (if used)
-
- ### Edge Cases
- - [ ] Missing arguments handled
- - [ ] Invalid arguments detected
- - [ ] Non-existent files handled
- - [ ] Special characters work
- - [ ] Long inputs handled
-
- ### Integration
- - [ ] Works with other commands
- - [ ] Works with hooks (if applicable)
- - [ ] Works with MCP (if applicable)
- - [ ] State management works
-
- ### Quality
- - [ ] Performance acceptable
- - [ ] No security issues
- - [ ] Error messages helpful
- - [ ] Output formatted well
- - [ ] Documentation complete
-
- ### Distribution
- - [ ] Tested by others
- - [ ] Feedback incorporated
- - [ ] README updated
- - [ ] Examples provided
-
- ## Debugging Failed Tests
-
- ### Common Issues and Solutions
-
- **Issue: Command not appearing in /help**
-
- ```bash
- # Check file location
- ls -la .claude/commands/my-command.md
-
- # Check permissions
- chmod 644 .claude/commands/my-command.md
-
- # Check syntax
- head -n 20 .claude/commands/my-command.md
-
- # Restart Claude Code
- claude --debug
- ```
-
- **Issue: Arguments not substituting**
-
- ```bash
- # Verify syntax
- grep '\$1' .claude/commands/my-command.md
- grep '\$ARGUMENTS' .claude/commands/my-command.md
-
- # Test with simple command first
- echo "Test: \$1 and \$2" > .claude/commands/test-args.md
- ```
-
- **Issue: Bash commands not executing**
-
- ```bash
- # Check allowed-tools
- grep "allowed-tools" .claude/commands/my-command.md
-
- # Verify command syntax
- grep '!\`' .claude/commands/my-command.md
-
- # Test command manually
- date
- echo "test"
- ```
-
- **Issue: File references not working**
-
- ```bash
- # Check @ syntax
- grep '@' .claude/commands/my-command.md
-
- # Verify file exists
- ls -la /path/to/referenced/file
-
- # Check permissions
- chmod 644 /path/to/referenced/file
- ```
-
- ## Best Practices
-
- 1. **Test early, test often**: Validate as you develop
- 2. **Automate validation**: Use scripts for repeatable checks
- 3. **Test edge cases**: Don't just test the happy path
- 4. **Get feedback**: Have others test before wide release
- 5. **Document tests**: Keep test scenarios for regression testing
- 6. **Monitor in production**: Watch for issues after release
- 7. **Iterate**: Improve based on real usage data
-
+ ## Tool Evaluation
- ---
- ## tool-evaluation.md
+ Source: https://github.com/anthropics/claude-cookbooks/blob/main/tool_evaluation/tool_evaluation.ipynb
+ Path: 12-Eval-Testing/claude-cookbooks-tool-evaluation-tool-evaluation.md
# Tool Evaluation
Multiple agents independently run a single evaluation task from an
evaluation file.
:::
::: {.cell .code}
``` python
import json
import re
import time
import traceback
import xml.etree.ElementTree as ET # noqa: S314
from pathlib import Path
from typing import Any
from anthropic import Anthropic
```
:::
::: {.cell .markdown}
## Prompts
:::
::: {.cell .code execution_count="2"}
``` python
# Embedded evaluator prompt
EVALUATION_PROMPT = """You are an AI assistant with access to tools.
When given a task, you MUST:
1. Use the available tools to complete the task
2. Provide summary of each step in your approach, wrapped in <summary> tags
3. Provide feedback on the tools provided, wrapped in <feedback> tags
4. Provide your final response, wrapped in <response> tags
Summary Requirements:
- In your <summary> tags, you must explain:
- The steps you took to complete the task
- Which tools you used, in what order, and why
- The inputs you provided to each tool
- The outputs you received from each tool
- A summary for how you arrived at the response
Feedback Requirements:
- In your <feedback> tags, provide constructive feedback on the tools:
- Comment on tool names: Are they clear and descriptive?
- Comment on input parameters: Are they well-documented? Are required vs optional parameters clear?
- Comment on descriptions: Do they accurately describe what the tool does?
- Comment on any errors encountered during tool usage: Did the tool fail to execute? Did the tool return too many tokens?
- Identify specific areas for improvement and explain WHY they would help
- Be specific and actionable in your suggestions
Response Requirements:
- Your response should be concise and directly address what was asked
- Always wrap your final response in <response> tags
- If you cannot solve the task return <response>NOT_FOUND</response>
- For numeric responses, provide just the number
- For IDs, provide just the ID
- For names or text, provide the exact text requested
- Your response should go last"""
```
:::
::: {.cell .markdown}
## Agent Loop
:::
::: {.cell .code}
``` python
client = Anthropic()
model = "claude-sonnet-4-5"
def agent_loop(prompt: str, tools: list[dict[str, Any]] = None) -> tuple[str, dict[str, Any]]:
"""Simplified agent class for tool evaluation"""
messages = [{"role": "user", "content": prompt}]
response = client.messages.create(
model=model,
max_tokens=4096,
system=EVALUATION_PROMPT,
messages=messages,
tools=tools,
)
messages.append({"role": "assistant", "content": response.content})
# Track tool calls with timing
tool_metrics = {} # {tool_name: {"count": N, "durations": [X1, X2, ...]}}
def _prepare_tool_result(tool_use_id, tool_result):
return {
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tool_use_id,
"content": tool_result,
}
],
}
while response.stop_reason == "tool_use":
tool_use = next(block for block in response.content if block.type == "tool_use")
tool_name = tool_use.name
tool_start_ts = time.time()
try:
# Note: eval is used here for demonstration purposes to dynamically call tool functions.
# In production, use a safer dispatch mechanism like a dictionary of functions.
tool_response = eval( # noqa: S307
f"{tool_name}(**tool_use.input)"
) # Call the tool function with its input
except Exception as e:
tool_response = f"Error executing tool {tool_name}: {str(e)}\n"
tool_response += traceback.format_exc()
tool_duration = time.time() - tool_start_ts
# Update tool metrics
if tool_name not in tool_metrics:
tool_metrics[tool_name] = {"count": 0, "durations": []}
tool_metrics[tool_name]["count"] += 1
tool_metrics[tool_name]["durations"].append(tool_duration)
# Prepare tool result and append to messages
messages.append(_prepare_tool_result(tool_use.id, tool_response))
response = client.messages.create(
model=model,
max_tokens=4096,
system=EVALUATION_PROMPT,
messages=messages,
tools=tools,
)
messages.append({"role": "assistant", "content": response.content})
response = next(
(block.text for block in response.content if hasattr(block, "text")),
None,
)
return response, tool_metrics
```
:::
::: {.cell .markdown}
## Helper Functions
:::
::: {.cell .code}
``` python
def parse_evaluation_file(file_path: Path) -> list[dict[str, Any]]:
"""Parse XML evaluation file and return list of evaluation tasks."""
try:
# Parse trusted local XML evaluation file
tree = ET.parse(file_path) # noqa: S314
root = tree.getroot()
evaluations = []
# Check for task elements
tasks = root.findall(".//task")
for task in tasks:
prompt_elem = task.find("prompt")
response_elem = task.find("response")
if prompt_elem is not None and response_elem is not None:
eval_dict = {
"prompt": (prompt_elem.text or "").strip(),
"response": (response_elem.text or "").strip(),
}
evaluations.append(eval_dict)
return evaluations
except Exception as e:
print(f"Error parsing evaluation file {file_path}: {e}")
return []
```
:::
::: {.cell .code execution_count="5"}
``` python
def evaluate_single_task(
task: dict[str, Any], tools: list[dict[str, Any]], task_index: int
) -> dict[str, Any]:
"""Evaluate a single task with the given tools."""
start_time = time.time()
# Run the task
print(f"Task {task_index + 1}: Running task with prompt: {task['prompt']}")
response, tool_metrics = agent_loop(task["prompt"], tools)
# Extract all tagged content
def _extract_xml_content(text, tag):
pattern = rf"<{tag}>(.*?)</{tag}>"
matches = re.findall(pattern, text, re.DOTALL)
return matches[-1].strip() if matches else None
response, summary, feedback = (
_extract_xml_content(response, tag) for tag in ["response", "summary", "feedback"]
)
duration_seconds = time.time() - start_time
return {
"prompt": task["prompt"],
"expected": task["response"],
"actual": response,
"score": int(response == task["response"]),
"total_duration": duration_seconds,
"tool_calls": tool_metrics,
"num_tool_calls": sum(len(metrics["durations"]) for metrics in tool_metrics.values()),
"summary": summary,
"feedback": feedback,
}
```
:::
::: {.cell .markdown}
## Main Evaluation Function
:::
::: {.cell .code execution_count="6"}
``` python
# Report Templates
REPORT_HEADER = """
# Evaluation Report
## Summary
- **Accuracy**: {correct}/{total} ({accuracy:.1f}%)
- **Average Task Duration**: {average_duration_s:.2f}s
- **Average Tool Calls per Task**: {average_tool_calls:.2f}
- **Total Tool Calls**: {total_tool_calls}
---
"""
TASK_TEMPLATE = """
### Task
**Prompt**: {prompt}
**Ground Truth Response**: `{expected_response}`
**Actual Response**: `{actual_response}`
**Correct**: {correct_indicator}
**Duration**: {total_duration:.2f}s
**Tool Calls**: {tool_calls}
**Summary**
{summary}
**Feedback**
{feedback}
---
"""
def run_evaluation(eval_path: str, tools: list[dict[str, Any]]) -> str:
"""
Run evaluation with provided tools using a simple loop.