veval-sdk
Python SDK for Veval — trace, evaluate, and test AI agents.
Install
pip install veval-sdk
Quick start
Wrap your agent with run_async to send a trace to the Veval dashboard:
import asyncio
from veval import VevalSdk, VevalOptions, VevalExecutionContext
sdk = VevalSdk(VevalOptions(api_key="veval-..."))
async def my_agent(ctx: VevalExecutionContext) -> str:
result = await ctx.track_step_async(
name="call-llm",
input=ctx.input,
step=lambda: call_my_llm(ctx.input),
)
return result
output = asyncio.run(sdk.run_async("my-agent", my_agent, input="Hello"))
Every call to track_step_async records the step name, input, output, timing, and any metadata you attach.
Tracking steps
Use the StepHandle to attach token counts, cost, and model name:
async def agent(ctx: VevalExecutionContext) -> str:
async def llm_call(handle):
response = await call_my_llm(ctx.input)
handle.set_meta("tokens_in", response.usage.input_tokens)
handle.set_meta("tokens_out", response.usage.output_tokens)
handle.set_meta("cost_usd", response.usage.cost)
handle.set_meta("type", "llm")
handle.set_meta("model", "claude-opus-4-6")
return response.text
return await ctx.track_step_async("call-llm", ctx.input, llm_call)
Assertions
Use TraceAssert to validate behaviour at the trace level:
from veval import TraceAssert
assertions = [
TraceAssert.no_errors(),
TraceAssert.max_steps(10),
TraceAssert.step_exists("call-llm"),
TraceAssert.max_cost(0.05),
TraceAssert.max_duration(30_000),
TraceAssert.output_contains("success"),
TraceAssert.tool_called("web-search"),
]
Scenarios
Run a named scenario against a list of test items and assertions:
from veval import ScenarioItem
items = [
ScenarioItem(name="basic greeting", input="Hello"),
ScenarioItem(name="edge case", input=""),
]
result = asyncio.run(
sdk.run_scenario_async("smoke-test", my_agent, assertions, items=items)
)
print(f"Passed: {result.pass_count}/{len(result.results)}")
for r in result.results:
if not r.passed:
print(f" FAIL {r.item.name}: {r.failures}")
A trace-backed item (ScenarioItem(trace_id=...)) replays live: steps recorded with set_meta("type", "llm") call the model with your current prompt, so a prompt regression fails the item, and every other step returns its recorded output, so tools never reach a real system. Set mock_model_steps=True on the item, or pass mock_model_steps=True to run_scenario_async for every item (including ones loaded from the dashboard), for a free, fully mocked replay that checks code changes but can't see prompt changes. VevalTestSdk always replays trace-backed items mocked. ReplayOptions(live_model_steps=True) does the same for replay_async.
You can also pull items from the Veval dashboard by omitting items:
result = asyncio.run(sdk.run_scenario_async("smoke-test", my_agent, assertions))
Replay and snapshot testing
Load a previously recorded trace and replay it with mocked LLM responses — no real calls, deterministic results:
trace = asyncio.run(sdk.get_trace_async("tr_abc123"))
replay = asyncio.run(sdk.replay_async(
trace,
my_agent,
ReplayOptions(mock_llm_responses=True, assertions=assertions),
))
print("Passed" if replay.passed else replay.failures)
Each call gets the recorded step with the same name and an identical input, or else the next unused one in recorded order, reported as diverged in replay.fidelity. A step with no recording raises instead of calling live, and a recorded step the agent never calls fails the replay.
Store a known-good run — every step with its input and output — and detect when a new run drifts from it: a changed prompt or tool argument, a step added, dropped, repeated, or reordered:
# Save a baseline from a recorded trace. The trace is pinned, so retention never deletes it.
asyncio.run(sdk.save_snapshot_async("my-baseline", "tr_abc123"))
# In tests: an assertion like any other. A missing baseline fails; it never passes silently.
replay = asyncio.run(sdk.replay_async(trace, my_agent, ReplayOptions(
mock_llm_responses=True,
assertions=[TraceAssert.matches_snapshot(sdk, "my-baseline")],
compare_with_recording=SnapshotOptions(), # also fail if steps/inputs drift from the trace
)))
# Or compare directly, and record the result in the dashboard.
baseline = asyncio.run(sdk.get_snapshot_async("my-baseline"))
diff = asyncio.run(sdk.compare_snapshot_async("my-baseline", baseline, ctx))
if diff.has_changes:
print(diff.summary()) # every change, with a line diff of changed inputs
Test SDK
VevalTestSdk is a drop-in replacement that blocks real LLM calls, making it safe to use in unit tests. It still reports to the dashboard.
from veval import VevalTestSdk
sdk = VevalTestSdk(options).with_replay(trace)
output = asyncio.run(sdk.run_async("my-agent", my_agent, input="test input"))
API reference
VevalOptions
| Parameter | Type | Default | Description |
|---|---|---|---|
api_key |
str |
"" |
Your Veval API key — it also determines the workspace |
project_id |
str |
"" |
Deprecated and ignored; will be removed |
flush_interval_ms |
int |
5000 |
Batch flush interval |
flush_batch_size |
int |
50 |
Max traces per flush |
raise_on_send_failure |
bool |
False |
Raise VevalSendError when a trace or scenario run can't be stored. Off by default: the failure is logged as a warning on the veval logger with the HTTP status. Turn it on in tests and CI |
VevalSdk
| Method | Description |
|---|---|
run_async(name, callback, input) |
Run agent and send trace |
get_trace_async(trace_id) |
Fetch a recorded trace |
save_snapshot_async(name, trace_id_or_ctx) |
Store a named baseline (pins the trace) |
get_snapshot_async(name) |
Load the latest stored baseline |
load_snapshot_async(trace_id) |
Build a snapshot from a trace (not pinned) |
compare_snapshot_async(name, snapshot, ctx, options) |
Diff a run against a baseline and record it |
replay_async(trace, callback, options) |
Replay trace with mocked outputs |
run_scenario_async(name, agent, assertions, items) |
Run a test scenario |
TraceAssert built-ins
| Assertion | Description |
|---|---|
no_errors() |
All steps must succeed |
max_steps(n) |
Total step count must not exceed n |
step_exists(name) |
A step with this name must appear |
max_cost(usd) |
Total cost must not exceed usd |
max_duration(ms) |
Total step duration must not exceed ms |
output_contains(text) |
At least one step output must contain text |
tool_called(name) |
A tool step with this name must appear |
Custom assertions implement async ITraceAssertion.evaluate_async(ctx) -> Optional[str] — return None to pass or an error string to fail.
Requirements
- Python 3.10+
- No third-party dependencies
Metadata
Release files for veval-sdk 1.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| veval_sdk-1.3.0.tar.gz | 31.1 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| veval_sdk-1.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 60.6 kB
Release files / veval_sdk-1.3.0.tar.gz
| Download URL | veval_sdk-1.3.0.tar.gz |
|---|---|
| Size | 31.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
535d6cfbd64e450139a0bd3ed41db495f3c70592fc98706bc9c8fe22f5b351d0
|
|
BLAKE2b-256 checksum How to use checksums |
c892e3445676eb0c6367a861cef7ede5887466637adf92b878947c553d2a2d71
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Oct 16, 2026.
Transparency logRelease files / veval_sdk-1.3.0-py3-none-any.whl
| Download URL | veval_sdk-1.3.0-py3-none-any.whl |
|---|---|
| Size | 29.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b7b6ee62d01dd1ebd57b5547a6203b77a519da4f05ac6c0c935f0943a30bc1f6
|
|
BLAKE2b-256 checksum How to use checksums |
94ff20be7350beb1c61c761b7d7f6e9c190b949e13d1187bfdf2a473d11871ea
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Oct 16, 2026.
Transparency logSecurity for veval-sdk 1.3.0
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.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for veval_sdk-1.3.0.tar.gz
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 Oct 16, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.