D
Dev SOPKnowledge Base
Search
← All topics

Stateful Agent Evaluation: Datasets, Graders, and CI Gates

Evaluation framework for memory-capable agents — dataset formats, target modes, extractor types, rubric grading, and CI gate patterns. Covers multi-turn testing and memory block verification.

agentsevalstestingllmmemorylettaci-cd
Agent trigger phrases: agent evals · stateful agent testing · memory verification · LLM evaluation · agent grader · eval dataset · agent CI gate

Overview

Stateful agent evals differ from stateless LLM evals. Agents accumulate memory, use tools, and behave differently across multi-turn conversations. The pipeline tests whether agents remember correctly, use tools appropriately, and produce quality responses.

Pipeline: Dataset → Target → Extractor → Grader → Gate → Results

The Six Concepts

1. Dataset

A list of observations to send to the agent. Formats: CSV (single-turn) or JSONL (multi-turn).

input,ground_truth
"What is the capital of France?",Paris
"What is the capital of Japan?",Tokyo

Multi-turn (JSONL):

{"input": ["Remember that my favorite city is Paris", "What's my favorite city?"], "ground_truth": "Paris"}
{"input": ["My dog is named Biscuit", "What is my dog's name?"], "ground_truth": "Biscuit"}

2. Target

The agent being evaluated. Three modes:

| Mode | Behavior | Use Case | |------|----------|----------| | Agent file | Clones one fresh agent per observation | Reproducible regression testing | | Agent ID | Sends all observations to the same live agent | Training/learning loops | | Agent factory | Function that constructs agents programmatically | Parameterized testing |

Agent file mode gives isolated, reproducible tests. Agent ID mode mutates the agent — useful for training via repeated evaluation.

3. Extractor

Pulls the specific piece of agent output to grade.

Built-in extractors:

  • last_assistant — most recent assistant message
  • memory block (by label) — contents of a named memory block (e.g., user_preferences)
  • tool_calls — what tools the agent invoked
  • tool_returns — what the tools returned
  • Custom functions for anything else

4. Grader

Scores the extracted output. Two types:

Tool-based grading — deterministic, binary:

  • contains — does extracted text contain the ground truth?
  • Score: 1 if yes, 0 if no

Rubric-based grading — uses a second LLM as judge:

  • Define weighted criteria (e.g., accuracy 50%, completeness 30%, format 20%)
  • Judge reads rubric + extracted output + ground truth
  • Returns 0–1 score with rationale

Anything you can articulate in a rubric can be scored.

5. Gate

Pass/fail threshold for CI integration. Throws an error when score falls below threshold.

gate = EvalGate(min_score=0.67)  # 67% of observations must pass

Designed for GitHub Actions integration — fails the pipeline if agent quality degrades.

6. Results

Metrics: average score, standard deviation, error count, per-observation breakdown with scores and rationale.

Workflow: Build an Eval Suite

Step 1: Create Dataset

# Single-turn CSV
dataset = [
    {"input": "What is my name?", "ground_truth": "Alice"},
    {"input": "What city do I live in?", "ground_truth": "Seattle"},
]

# Or multi-turn: teach then test
dataset = [
    {
        "input": [
            "My name is Alice and I live in Seattle",
            "What is my name?"
        ],
        "ground_truth": "Alice"
    }
]

Step 2: Define Target

from letta_evals import AgentEvalTarget

target = AgentEvalTarget(
    agent_file="agents/memory-agent.json",  # clones per observation
    # OR
    agent_id="agent-abc123",               # same live agent
)

Step 3: Configure Extractor

from letta_evals import extractors

extractor = extractors.last_assistant()           # default
# OR
extractor = extractors.memory_block("user_info")  # specific memory block
# OR
extractor = extractors.tool_calls()               # what tools were called

Step 4: Configure Grader

from letta_evals import graders

# Simple containment check
grader = graders.contains()

# LLM judge with rubric
grader = graders.rubric(
    criteria={
        "accuracy": {"weight": 0.5, "description": "Is the answer factually correct?"},
        "completeness": {"weight": 0.3, "description": "Does it cover all required info?"},
        "format": {"weight": 0.2, "description": "Is it properly formatted?"},
    },
    model="gpt-4o",
)

Step 5: Run and Gate

from letta_evals import EvalSuite, EvalGate

suite = EvalSuite(
    dataset=dataset,
    target=target,
    extractor=extractor,
    grader=grader,
    gate=EvalGate(min_score=0.75),
)

results = suite.run()
print(f"Score: {results.average_score:.2%}")
results.to_csv("eval-results.csv")

CI/CD Integration

# .github/workflows/agent-evals.yml
- name: Run Agent Evals
  run: python run_evals.py
  env:
    LETTA_API_KEY: ${{ secrets.LETTA_API_KEY }}
    OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}

If EvalGate threshold is not met, the script exits non-zero and the workflow fails.

Memory Block Testing Pattern

Test that agents store information in the correct memory block:

dataset = [
    {
        "input": ["I prefer dark mode and always use vim keybindings", "check"],
        "ground_truth": "dark mode",  # should appear in user_preferences block
    }
]
extractor = extractors.memory_block("user_preferences")
grader = graders.contains()

Common Eval Mistakes

| Mistake | Fix | |---------|-----| | Single-turn only | Add multi-turn: teach then probe | | Ground truth too strict | Use rubric grading for fuzzy matches | | Testing on training data | Hold out 20% of examples for final eval | | No gate in CI | Always wire a min_score threshold | | Grading full response | Use extractors to isolate what you're scoring |