Observed since 20 Oct, 23:29 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
A lightweight Python library for evaluating AI agents and RAG pipelines.
A lightweight Python library for evaluating AI agents and RAG pipelines. No magic, no vendor lock-in — just explicit, readable evaluation logic.
Dataset, Task, Evaluation, Pipeline.fn(prompt: str) -> str as a judge. No default provider.fn(dataset_row: dict) -> float. No base classes, no decorators.Pipeline and get topology, model, tools, and system prompt extracted automatically.DiskCache and MemoryCache keyed on judge + prompt + agent fingerprint.assert_passing(), failures(), and JSON save/load for pytest regression tests.pip install agentstax-eval
Requires: Python 3.9+. Zero core dependencies.
Optional extras for built-in provider functions:
| Extra | Install command | Adds |
|---|---|---|
openai | pip install openai | openai SDK |
anthropic | pip install anthropic | anthropic SDK |
google | pip install google | google-genai SDK |
All the eval frameworks I have used feel more complicated than they should. They are either too heavy, too complicated, or require vendor lock-in.
I often find myself creating a simple script to test what I want, which works great in the beginning but shows its weakness later on.
I built agentstax-eval to fix that. It's simple when you need it, but flexible enough to grow with your agent architecture.
It works with whatever you're already using — you can pass your LangGraph, ADK, OpenAI Agents, CrewAI, LlamaIndex, or MSAF agent directly to Pipeline. It will walk the hierarchy, extract the topology, and fingerprint it. When your architecture changes, the cache invalidates and the monitor flags the drift.
Pair it with agentstax-eval-monitor and you get a live dashboard that shows regression detection, metric trends, and agent network visualization.
from openai import OpenAI
from agentstax_eval import Pipeline, Dataset, Task, Evaluation
from agentstax_eval.metrics import llm_correctness
from agentstax_eval.providers import openai_provider
client = OpenAI()
dataset = Dataset([
{"question": "What is the capital of France?", "expected_answer": "Paris"},
{"question": "Who wrote Hamlet?", "expected_answer": "Shakespeare"},
{"question": "What is 2 + 2?", "expected_answer": "4"},
])
def get_answer(dataset_row: dict) -> dict:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": dataset_row["question"]}],
)
return {"answer": response.choices[0].message.content}
pipeline = Pipeline(
dataset=dataset,
tasks=[Task(get_answer)],
evaluation=Evaluation(metrics=[llm_correctness(llm=openai_provider())]),
)
results = pipeline.run()
results.save(directory="results", base_filename="eval")
print(results)
Pass your agent object directly to unlock automatic metadata extraction, topology mapping, and smart caching:
from agentstax_eval import Pipeline, Dataset, Task, Evaluation, DiskCache
from agentstax_eval.metrics import llm_correctness
from agentstax_eval.providers import openai_provider
pipeline = Pipeline(
dataset=dataset,
tasks=[Task(get_answer)],
evaluation=Evaluation(metrics=[llm_correctness(llm=openai_provider())]),
agent=my_langgraph_agent, # auto-extracts topology, model, tools
cache=DiskCache(".cache"), # caches judge responses, invalidates on topology change
)
results = pipeline.run()
results.save(directory="results", base_filename="my_agent_eval")
The saved JSON now includes a topology field with the full agent graph, fingerprint, and per-node metadata — which the monitoring dashboard uses for network visualization and change detection.
from agentstax_eval.metrics import exact_match, contains_answer, json_valid
| Metric | What it checks | Source | LLM required |
|---|---|---|---|
exact_match | answer.strip().lower() == expected_answer.strip().lower() | source | No |
contains_answer | expected_answer appears in answer | source | No |
json_valid | answer parses as valid JSON | source | No |
Factory functions that take an llm callable and return a metric. The llm can be synchronous (fn(prompt: str) -> str) or asynchronous (async def).
from agentstax_eval.metrics import llm_correctness, llm_relevance, llm_faithfulness, llm_completeness, llm_rubric
| Metric | Reads | What it scores | Prompt |
|---|---|---|---|
llm_correctness(llm=) | question, expected_answer, answer | Factual correctness against reference | prompt |
llm_relevance(llm=) | question, answer | Whether answer addresses the question (reference-free) | prompt |
llm_faithfulness(llm=) | answer, context | Whether claims are supported by context (RAG) | prompt |
llm_completeness(llm=) | question, expected_answer, answer | Whether all key points are covered | prompt |
llm_rubric(llm=, criteria=) | question, answer | User-defined criteria in plain English | prompt |
All use a six-point scale (1.0, 0.8, 0.6, 0.4, 0.2, 0.0) with chain-of-thought reasoning.
Any function with the signature fn(dataset_row: dict) -> float works as a metric. The function receives the full row dict — including question, expected_answer, answer, and any extra fields your tasks added (like context). Return a float between 0.0 and 1.0. The function's __name__ is used as the column name in results.
# Deterministic metric — no LLM needed
def answer_is_concise(dataset_row: dict) -> float:
return 1.0 if len(dataset_row["answer"].split()) <= 20 else 0.0
# Custom LLM metric — you control the prompt and parsing
def my_judge_metric(dataset_row: dict) -> float:
prompt = f"Is this answer polite? Answer 1 or 0.\nAnswer: {dataset_row['answer']}"
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
)
return float(response.choices[0].message.content.strip())
evaluation = Evaluation(metrics=[exact_match, answer_is_concise, my_judge_metric])
When using factory functions or lambdas, set __name__ to control the result column name:
concise = llm_rubric(llm=judge, criteria="Two sentences or fewer.")
concise.__name__ = "rubric_concise"
Built-in fn(prompt: str) -> str wrappers that handle client setup and set judge_model metadata automatically:
from agentstax_eval.providers import openai_provider, anthropic_provider, google_provider
judge = openai_provider() # default: gpt-4.1, reads OPENAI_API_KEY
judge = anthropic_provider(model="claude-opus-4-6") # reads ANTHROPIC_API_KEY
judge = google_provider(model="gemini-2.5-pro") # reads GEMINI_API_KEY
A provider is any fn(prompt: str) -> str. To get automatic judge_model tracking in result metadata, set the attribute on the function:
from openai import OpenAI
client = OpenAI()
def my_judge(prompt: str) -> str:
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
)
return response.choices[0].message.content
my_judge.judge_model = "openai/gpt-4o"
The judge_model string flows into metadata.scoring.<metric_name>.judge_model in saved results. If omitted, scoring metadata will simply not include it.
agentstax-eval-monitor is a companion real-time dashboard that watches a directory of agentstax-eval result files and provides:
# Install
cd agentstax-eval-monitor
bun install
# Point at your results directory
bun run index.ts ./results
Opens a dashboard at http://localhost:3000.
Wraps a list of dicts. Each row must have a question. The expected_answer field is optional at the dataset level — metrics that need it raise MissingFieldError at scoring time.
dataset = Dataset([
{"question": "What year did WWII end?", "expected_answer": "1945"},
])
Wraps a callable fn(dataset_row: dict) -> dict. The returned dict is merged into each row.
task = Task(get_answer)
answered_dataset = task.run(dataset) # returns new Dataset, original not mutated
Holds a list of metrics and scores a dataset. Metrics are fn(dataset_row: dict) -> float.
evaluation = Evaluation(metrics=[exact_match, llm_correctness(llm=my_judge)])
results = evaluation.run(answered_dataset, metadata={"model": "gpt-4o"})
Convenience wrapper: runs tasks sequentially, then scores. Accepts agent for auto-extraction and cache for LLM judge caching.
pipeline = Pipeline(
dataset=dataset,
tasks=[Task(get_answer)],
evaluation=Evaluation(metrics=[llm_correctness(llm=my_judge)]),
agent=my_agent, # optional: auto-extract topology
cache=DiskCache(".cache"), # optional: cache judge responses (requires agent)
metadata={"experiment": "v2"}, # optional: merged into results (overrides extracted values)
)
results = pipeline.run()
| Parameter | Type | Description |
|---|---|---|
dataset | Dataset | Input rows to process |
tasks | list[Task] | Ordered task list, run sequentially |
evaluation | Evaluation | Scores completed rows |
metadata | dict | None | Merged into result metadata; overrides extracted values |
agent | object | None | Agent object for auto-extraction |
cache | Cache | None | LLM judge cache; requires agent |
results.metadata # dict with timestamp_utc, topology, scoring, etc.
results.rows # list[ResultRow] — each has .data (dict) and .scores (dict)
results.assert_passing() # True if all thresholded metrics pass on every row
results.failures() # list[Failure] — each row/metric pair below threshold
# Save and load
path = results.save(directory="results", base_filename="eval")
latest = EvaluationResults.load(directory="results", base_filename="eval")
Pass an agent object from a supported framework and Pipeline automatically extracts its metadata and topology — no manual metadata dict needed.
Supported frameworks:
| Framework | Install |
|---|---|
| LangGraph | pip install langgraph |
| Google ADK | pip install google-adk |
| OpenAI Agents SDK | pip install openai-agents |
| CrewAI | pip install crewai |
| LlamaIndex | pip install llama-index |
| Microsoft Agent Framework | pip install msaf |
pipeline = Pipeline(
dataset=dataset,
tasks=[Task(get_answer)],
evaluation=Evaluation(metrics=[llm_correctness(llm=judge)]),
agent=my_agent, # just pass your agent
)
Cache LLM judge responses to avoid redundant API calls. Cache keys include judge model, prompt, and agent fingerprint — the cache auto-invalidates when agent topology changes.
from agentstax_eval import DiskCache, MemoryCache
# Persistent (JSON file, atomic writes, thread-safe)
cache = DiskCache(directory=".cache")
# In-process (lost when process exits, thread-safe)
cache = MemoryCache()
Pass to Pipeline with agent for automatic fingerprint extraction:
pipeline = Pipeline(
dataset=dataset,
tasks=[Task(get_answer)],
evaluation=Evaluation(metrics=[llm_correctness(llm=judge)]),
agent=my_agent,
cache=DiskCache(".cache"),
)
results = pipeline.run()
cache.stats() # {"hits": 0, "misses": 3, "size": 3}
# Second run with same agent topology: all hits
results = pipeline.run()
cache.stats() # {"hits": 3, "misses": 3, "size": 3}
All three core objects support run_async(concurrency=N) for concurrent row processing:
import asyncio
from agentstax_eval import Pipeline, Task, Evaluation
from agentstax_eval.metrics import exact_match
async def call_agent(dataset_row: dict) -> dict:
response = await async_client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": dataset_row["question"]}],
)
return {"answer": response.choices[0].message.content}
pipeline = Pipeline(
dataset=dataset,
tasks=[Task(call_agent)],
evaluation=Evaluation(metrics=[exact_match]),
agent=my_agent,
)
results = asyncio.run(pipeline.run_async(concurrency=5))
Task.run_async() and Evaluation.run_async() are also available for step-by-step use. Default concurrency is 10.
# test_agent_quality.py
from agentstax_eval import Pipeline, Dataset, Task, Evaluation
from agentstax_eval.metrics import llm_correctness
from agentstax_eval.providers import openai_provider
def test_agent_passes_threshold():
results = Pipeline(
dataset=Dataset([
{"question": "Capital of France?", "expected_answer": "Paris"},
{"question": "What is 2 + 2?", "expected_answer": "4"},
]),
tasks=[Task(call_my_agent)],
evaluation=Evaluation(metrics=[llm_correctness(llm=openai_provider())]),
agent=my_agent,
).run()
assert results.assert_passing(), "\n".join(str(f) for f in results.failures())
Save results for trend tracking — the monitoring dashboard can watch the same directory for live regression visibility.
Contributions are welcome. Please open an issue to discuss before submitting a PR.
git clone https://github.com/your-org/agentstax-eval.git && cd agentstax-eval
uv sync && uv run pytest --ignore=tests/e2e -q
MIT — see LICENSE for details.
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.1.0 | 22 Mar 2026 | 3 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 23:29 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:7dab9fb227674447797b3697857932514d80b45c1ab922d49023069b6d6120dd