Test any Ajentify agent with simulated conversations, deterministic assertions, and LLM-powered assessments.
Project description
Ajentify Testing Framework
Test any Ajentify agent with simulated conversations, deterministic assertions, and LLM-powered assessments.
Installation
pip install ajentify-testing
Or install directly from GitHub:
pip install git+https://github.com/purpledevilai/ajentify-testing.git
Quick Start
# Scaffold a test directory in your project
cd your-project/
ajentify-test init
# Configure credentials
# Edit .env with your Ajentify API key and agent ID
# Run tests
ajentify-test
ajentify-test init creates:
your-project/
tests/
__init__.py
test_example.py # Example test to get started
.env.example # Credential template
.env # Your credentials (gitignored)
Core Concepts
SimAgent
A throwaway agent that role-plays a user scenario against your agent under test. It automatically includes an end_test tool so the conversation terminates naturally.
sim = SimAgent(
session,
persona="A first home buyer asking about a property in Richmond",
first_message="Hi, what's the price guide for 42 Smith St?",
)
TargetContext
A wrapper around your agent's Ajentify context. Exposes two families of checks:
assert_*()— Deterministic, instant, zero-cost. Inspects the conversation data directly.assess_*()— LLM-powered via inline SRE. Handles subjective/semantic checks.
target = TargetContext(
session,
agent_id=session.env("AGENT_ID"),
prompt_args={"region": "VIC"},
user_defined={"api_url": "https://..."},
)
run_conversation()
Orchestrates the multi-turn dialogue between a SimAgent and TargetContext. Ends when the sim agent calls end_test, max_turns is reached, or either side returns an empty message.
run_conversation(sim, target, max_turns=15)
Writing Tests
Each test is a Python file in tests/ with three exports:
# tests/test_property_lookup.py
from ajentify_testing import SimAgent, TargetContext, run_conversation
name = "property_lookup"
description = "Agent should look up property details when asked"
def run(session):
sim = SimAgent(session,
persona="A buyer asking about a 3-bedroom house",
first_message="What's the price for 42 Smith St in Richmond?",
)
target = TargetContext(session,
agent_id=session.env("AGENT_ID"),
prompt_args={"region": "VIC"},
)
run_conversation(sim, target, max_turns=15)
# Deterministic assertions
target.assert_called_tool("lookup_property")
target.assert_called_tool("lookup_property", with_params={"suburb": "Richmond"})
target.assert_message_contains("Hi, thank you for choosing NewLiving Realestate")
# LLM-powered assessments (raise on first failure)
target.assess_true("Gave the user a price guide")
target.assess_false("Offered a private inspection without being asked")
target.assess_score("Followed professional sales approach", min=0.7)
Resource cleanup is handled automatically by the framework. SimAgents and TargetContexts are cleaned up after each test, even if assertions fail.
Batch Assessments with assess_all
Individual assess_* calls raise on the first failure and each makes its own LLM call. assess_all evaluates every assessment in one single LLM call, collects all pass/fail results with reasoning, then raises a combined failure if anything didn't pass:
from ajentify_testing import AssessTrue, AssessFalse, AssessScore
target.assess_all([
AssessTrue("Greeted the user by name"),
AssessTrue("Provided a price guide"),
AssessFalse("Offered a private inspection without being asked"),
AssessScore("Followed professional sales approach", min=0.7),
])
The prompt lists every assertion with its type and threshold. The LLM returns a structured object per assertion with a result ("PASS" / "FAIL") and reasoning. If any fail, a single TestFailure is raised with a combined summary — every assertion runs regardless of earlier failures.
Mixed Assert + Assess with check_all
When you want to combine deterministic assertions and LLM assessments in one collected batch, use check_all. Deterministic checks run instantly; LLM assessments run in parallel. All results are collected before any failure is raised:
from ajentify_testing import (
AssertCalledTool, AssertTurnCount,
AssessTrue, AssessFalse, AssessScore,
)
target.check_all([
AssertCalledTool("lookup_property", with_params={"suburb": "Richmond"}),
AssertTurnCount(max=10),
AssessTrue("Gave the user a price guide"),
AssessFalse("Offered a private inspection without being asked"),
AssessScore("Followed professional sales approach", min=0.7),
])
Each Assert* descriptor maps to an assert_* call; each Assess* descriptor maps to an assess_* call (run in parallel via ThreadPoolExecutor).
Structured Extraction with extract
Use extract for ad-hoc structured data extraction from the conversation, powered by the inline SRE endpoint:
from ajentify_testing import Param
result = target.extract([
Param.boolean("passed", "Whether the agent completed the task"),
Param.number("score", "Quality score from 0.0 to 1.0"),
Param.string("summary", "Brief summary of what happened"),
])
if result["passed"]:
print(f"Score: {result['score']}")
You can provide a custom prompt (use {conversation} as a placeholder for the transcript):
result = target.extract(
[Param.string("bin_color", "The bin color the agent recommended")],
prompt="What bin color did the agent recommend?\n\n{conversation}",
)
Param Helper
Build parameter definitions without writing dicts by hand:
from ajentify_testing import Param
Param.string("name", "Person's name")
Param.number("score", "Quality score")
Param.boolean("passed", "Whether it passed")
Param.array("topics", "Key topics", items=Param.string("topic", "A topic"))
Param.object("person", "A person", children=[
Param.string("name", "Name"),
Param.number("age", "Age"),
])
Param.enum("sentiment", "Sentiment", values=["positive", "negative", "neutral"])
Customising Prompts
The framework uses built-in prompts for sim agents, assessors, and extraction. To customise any prompt without modifying the package:
ajentify-test init --with-prompts
This creates an ajentify_prompts.py in your project root with all the default prompts. Edit any prompt you want to change — delete lines you don't need to override:
# ajentify_prompts.py
SIM_AGENT_PREAMBLE = (
"You are a simulated customer testing our support bot. "
"Be realistic and occasionally confused...\n\n"
)
# Only override what you need — everything else uses built-in defaults
Available prompt overrides:
| Variable | Used by | Purpose |
|---|---|---|
SIM_AGENT_PREAMBLE |
SimAgent |
System prompt preamble for simulated users |
BOOLEAN_ASSESSOR_PROMPT |
assess_true(), assess_false() |
Prompt for true/false evaluation |
SCORE_ASSESSOR_PROMPT |
assess_score() |
Prompt for score evaluation |
EXTRACT_PROMPT_TEMPLATE |
extract() |
Default extraction prompt |
ASSESS_ALL_PROMPT_TEMPLATE |
assess_all() |
Batch assessment prompt |
END_TEST_TOOL_DESCRIPTION |
TestSession |
Description for the end_test tool |
END_TEST_SUMMARY_PARAM_DESCRIPTION |
TestSession |
Description for the summary parameter |
Assertion Reference
assert_* (Deterministic)
| Method | Description |
|---|---|
assert_called_tool(name) |
Agent called the named tool |
assert_called_tool(name, with_params={...}) |
Agent called the tool with matching params |
assert_not_called_tool(name) |
Agent did NOT call the named tool |
assert_message_contains(text) |
At least one agent message contains the text |
assert_message_not_contains(text) |
No agent message contains the text |
assert_turn_count(min=N, max=N) |
Conversation length is within bounds |
assess_* (LLM-Powered)
| Method | Description |
|---|---|
assess_true(statement) |
Statement is true about the conversation |
assess_false(statement) |
Statement is false about the conversation |
assess_score(criteria, min=0.7) |
Score (0.0–1.0) meets minimum threshold |
assess_all([...]) |
One LLM call evaluates all AssessTrue/False/Score checks, collects all results before failing |
check_all (Mixed)
| Method | Description |
|---|---|
check_all([...]) |
Mix of Assert* and Assess* descriptors — asserts run instantly, assessments run in parallel, all results collected before failing |
Descriptors: AssertCalledTool, AssertNotCalledTool, AssertMessageContains, AssertMessageNotContains, AssertTurnCount, AssessTrue, AssessFalse, AssessScore
extract
| Method | Description |
|---|---|
extract(parameters) |
Extract structured data from the conversation |
extract(parameters, prompt=...) |
Extract with a custom prompt (use {conversation}) |
Running Tests
# Run all tests in parallel
ajentify-test
# Run a single test by name
ajentify-test --test property_lookup
# Specify a custom tests directory
ajentify-test --tests-dir path/to/my/tests
The legacy python run_tests.py entry point still works if you're running from the framework source.
Results are printed to the console and saved as a markdown report in results/.
Environment Variables
| Variable | Required | Description |
|---|---|---|
AJENTIFY_API_KEY |
Yes | Your Ajentify API key |
AGENT_ID |
No | Default agent ID (read via session.env("AGENT_ID")) |
Access any env var in tests with session.env("MY_VAR").
Project Structure (when using pip)
your-project/
tests/ # Your test files (committed to your repo)
__init__.py
test_example.py
ajentify_prompts.py # Optional prompt overrides
.env # Credentials (gitignored)
.env.example # Credential template
requirements.txt # includes ajentify-testing
results/ # Generated markdown reports (gitignored)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ajentify_testing-0.1.0.tar.gz.
File metadata
- Download URL: ajentify_testing-0.1.0.tar.gz
- Upload date:
- Size: 25.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7d755f3b961309faad413939f74c0e6e86f27d26d7df2c9eca815cc00ca228a3
|
|
| MD5 |
7a0dc3f2cbcc3c359c8085876674e529
|
|
| BLAKE2b-256 |
83123f8317dd7221e6579ce551826d4d5d61685d3fdb2a8246b032384ec94030
|
Provenance
The following attestation bundles were made for ajentify_testing-0.1.0.tar.gz:
Publisher:
publish.yml on purpledevilai/ajentify-testing
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ajentify_testing-0.1.0.tar.gz -
Subject digest:
7d755f3b961309faad413939f74c0e6e86f27d26d7df2c9eca815cc00ca228a3 - Sigstore transparency entry: 1262279553
- Sigstore integration time:
-
Permalink:
purpledevilai/ajentify-testing@da7915acff0109a3416ba43c8f16ebc83ddff4d8 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/purpledevilai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@da7915acff0109a3416ba43c8f16ebc83ddff4d8 -
Trigger Event:
push
-
Statement type:
File details
Details for the file ajentify_testing-0.1.0-py3-none-any.whl.
File metadata
- Download URL: ajentify_testing-0.1.0-py3-none-any.whl
- Upload date:
- Size: 25.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3036800efadec544bb2443a2953ef53342c086719023f2ad3e1b1be555663201
|
|
| MD5 |
27d00269e87f29794a8c3649f400eb2f
|
|
| BLAKE2b-256 |
49d61f9c18d9bd7466b5e45b1543193a0cb2739055177045525f05dc5afdbe29
|
Provenance
The following attestation bundles were made for ajentify_testing-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on purpledevilai/ajentify-testing
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ajentify_testing-0.1.0-py3-none-any.whl -
Subject digest:
3036800efadec544bb2443a2953ef53342c086719023f2ad3e1b1be555663201 - Sigstore transparency entry: 1262279564
- Sigstore integration time:
-
Permalink:
purpledevilai/ajentify-testing@da7915acff0109a3416ba43c8f16ebc83ddff4d8 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/purpledevilai
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@da7915acff0109a3416ba43c8f16ebc83ddff4d8 -
Trigger Event:
push
-
Statement type: