Skip to main content

space-evals

A Python framework for testing and validating agentic AI chat experiences. Define conversation tests in YAML, run them against any LLM, and get structured results with pass/fail grading.

Why space-evals?

Existing eval platforms give you scores and metrics, but when a test fails, you're left reading raw JSON to figure out what went wrong. space-evals is built for multi-turn conversations from the ground up:

  • Scripted tests for rigid, turn-by-turn validation of deterministic flows
  • Task-based tests where an LLM plays the customer and talks to your target until a goal is reached
  • Event-driven architecture so a future UI can render conversations in real-time as tests execute
  • Plugin system for LLM providers, graders, and runners so the core stays lightweight and extensible

Installation

pip install space-evals

Then install the client plugin(s) for your LLM provider:

pip install space-evals-client-openai
pip install space-evals-client-anthropic

Quick Start

1. Create a config file

Create space-evals.yaml in your project:

clients:
  target:
    provider: openai
    model: gpt-4o

  customer:                  # needed for task-based tests
    provider: anthropic
    model: claude-sonnet-4-20250514

runner:
  max_concurrency: 10

output:
  dir: ./results

2. Write tests

Scripted -- every turn is predefined with per-turn grading:

id: greeting-flow
type: scripted
scenario: Validate greeting and handoff

turns:
  - user_message: Hi there
    graders:
      - type: llm_judge
        assertions:
          - type: Response is a friendly greeting
          - type: Response asks how it can help

  - user_message: I need help with my order
    graders:
      - type: tool_calls
        expected_calls:
          - tool_name: lookup_order
            arguments:
              source: conversation

Task-based -- an LLM simulates a customer working toward a goal:

id: order-status-task
type: task_based
scenario: User checks order status
goal: Find out the delivery date for order #12345
initial_user_message: Can you check on my order #12345?
max_turns: 5
end_conditions:
  - type: keyword_match
    keyword: delivery date
transcript_graders:
  - type: llm_judge
    assertions:
      - type: The bot provided the delivery date

3. Run

space-evals run ./tests/

Or programmatically:

import asyncio
from space_evals.engine import run_tests
from space_evals.events import EventBus
from space_evals.reporters.console import ConsoleReporter

event_bus = EventBus()
event_bus.subscribe(ConsoleReporter())

result = asyncio.run(run_tests(
    path="./tests/",
    target_client=my_target,
    customer_client=my_customer,
    event_bus=event_bus,
))

print(f"{result.passed}/{result.total} passed")

Test Spec Schema

Common fields

Field Type Required Description
id string yes Unique test identifier
type "scripted" or "task_based" yes Determines which runner executes the test
scenario string yes Human-readable description

Scripted

Field Type Required Description
turns list yes Ordered conversation turns
turns[].user_message string yes Message sent to the target
turns[].graders list no Graders for this turn's response

Task-based

Field Type Required Description
goal string yes What the simulated customer is trying to accomplish
initial_user_message string yes First message to the target
max_turns integer yes Max turns before stopping
end_conditions list no Conditions that stop the conversation early
transcript_graders list no Graders run on the full transcript after the conversation

Built-in Graders

exact_match

- type: exact_match
  expected_response: "Hello! How can I help?"

llm_judge

- type: llm_judge
  assertions:
    - type: Response is helpful and on-topic
    - type: Response does not hallucinate

Requires a judge client configured in space-evals.yaml.

tool_calls

- type: tool_calls
  expected_calls:
    - tool_name: get_weather
      arguments:
        location: "New York"

End Conditions (task-based)

tool_calls

Stops when the target makes a specific tool call.

keyword_match

Stops when a keyword appears in the target's response.

The conversation also stops if the customer LLM signals [GOAL_COMPLETE].

Configuration

Field Type Default Description
clients.target object -- LLM being tested (required)
clients.customer object -- Simulated user for task-based tests
clients.judge object -- LLM for llm_judge graders
clients.*.provider string -- Matches an installed client plugin
clients.*.model string -- Model identifier
clients.*.api_key_env string provider default Env var for API key
clients.*.params object {} Extra provider parameters
runner.max_concurrency integer 10 Max parallel tests
output.dir string ./results Result output directory

Plugins

space-evals uses a plugin architecture. The core has no LLM provider dependencies -- you install only what you need.

Official client plugins:

Package Provider Requires
space-evals-client-openai openai OPENAI_API_KEY
space-evals-client-anthropic anthropic ANTHROPIC_API_KEY

Plugins are discovered automatically via Python entry points. Install a plugin, use its provider name in your config, done.

Building your own plugin

Client plugin:

from space_evals.clients.base import BaseClient, ClientResponse, Message, register_client

@register_client("my_provider")
class MyClient(BaseClient):
    def __init__(self, model: str, api_key_env: str | None = None, **params):
        ...

    async def send(self, messages: list[Message], system_prompt: str = "") -> ClientResponse:
        ...
# pyproject.toml
[project.entry-points."space_evals.clients"]
my_provider = "my_package:MyClient"

Grader plugin:

from space_evals.graders.base import BaseGrader, register_grader
from space_evals.models.results import GraderResult

@register_grader("my_grader")
class MyGrader(BaseGrader):
    async def grade(self, turn, spec):
        ...
[project.entry-points."space_evals.graders"]
my_grader = "my_package:MyGrader"

Events

The framework emits events during execution for progress reporting and future UI integration:

Event When
RUN_STARTED Test suite begins
TEST_STARTED Individual test begins
TURN_STARTED Conversation turn begins
RESPONSE_RECEIVED LLM responds
GRADER_COMPLETED Grader finishes
TEST_COMPLETED Individual test finishes
RUN_COMPLETED All tests complete
from space_evals.events import EventBus, Event

class MyListener:
    def on_event(self, event: Event) -> None:
        print(f"{event.event_type.value}: {event.data}")

bus = EventBus()
bus.subscribe(MyListener())

Result Persistence

Results are saved as JSON when output.dir is configured:

results/<run_id>/<test_id>/result.json

Each file contains the full transcript with per-turn grader results and timing data.

License

MIT

Metadata

Release files for space-evals 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for space-evals 0.2.0
File Size Uploaded
space_evals-0.2.0.tar.gz 96.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for space-evals 0.2.0
File Interpreter ABI Platform
space_evals-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 176.5 kB

Release files / space_evals-0.2.0.tar.gz

Download URL space_evals-0.2.0.tar.gz
Size 96.7 kB
Tags Source
SHA-256 checksum
How to use checksums
b717c49f8c18ad0dfedc28c2a7f0ec0f7dd20f770400f51166de9f00b2d0917d
BLAKE2b-256 checksum
How to use checksums
32a4155aae7b6d45f76d879d957b81545404cbbc9b2d32e70ce7d391b7b7cc43
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 11, 2026.

Transparency log

Release files / space_evals-0.2.0-py3-none-any.whl

Download URL space_evals-0.2.0-py3-none-any.whl
Size 79.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d8f821aa91915f63ae7e28e56b95e2087ba4a0d81d7cb9f20d62011b3cba80de
BLAKE2b-256 checksum
How to use checksums
a51f9763adbb4e88149d1aae733005a89e6260c51b416243c2649dcc28a65341
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 11, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page