Skip to content

Measurements API

The measurements module provides two complementary metric systems:

  • MeasurementEngine — applies registered measurement functions to a dataset after the fact (output quality, similarity, structure)
  • AgentWriter — records runtime behavioral metrics while an agent runs (latency, tokens, tool calls, outcome)

Import

from adgtk.measurements import (
    # Runtime writer
    AgentWriter,
    track_step,
    # Dataset engine
    MeasurementEngine,
    MeasurementData,
    MeasurementReport,
    # Registry
    create_measurement,
    get_measurement_factory_entry,
    get_measurements_by_tag,
    get_measurements_by_type,
    register_to_measurement_factory,
)

AgentWriter

Runtime metric writer for agentic test runs. See the full guide for examples.

writer = AgentWriter(result_folders)               # name="agent" by default
writer = AgentWriter(result_folders, name="planner")

Methods

Method Description
log_step(latency, tokens_in, tokens_out, error) Record metrics for one agent step
log_tool_call(tool, success) Record one tool invocation
log_outcome(success, goal_completion, optimal_steps) Record task result
save() Write all CSVs to folders.metrics/
step_count() Number of steps logged
tool_distribution() {tool_name: call_count} dict
summary() Latest value snapshot for all metrics

Built-in metrics written to disk

Label Logged by What it captures
latency log_step Wall-clock seconds per step
tokens_in log_step Input tokens per step
tokens_out log_step Output tokens per step
error log_step 1.0 if step errored, else 0.0
tool_call_count log_tool_call 1.0 per call (sum = total)
tool.{name} log_tool_call Per-tool: 1.0 success / 0.0 failure
success log_outcome 1.0 = pass, 0.0 = fail
goal_completion log_outcome Partial-credit score 0–1
retry_count log_outcome Failed attempts before this call
path_efficiency log_outcome min(optimal / actual steps, 1.0)
first_attempt_success log_outcome (first call) 1.0 if first attempt succeeded

Minimal example

writer = AgentWriter(result_folders)

for step_result in agent.run(task):
    writer.log_step(
        latency=step_result.elapsed,
        tokens_in=step_result.tokens_in,
        tokens_out=step_result.tokens_out,
    )
    for call in step_result.tool_calls:
        writer.log_tool_call(call.name, success=call.ok)

writer.log_outcome(success=True, goal_completion=0.9, optimal_steps=3)
writer.save()

track_step

Decorator that wraps a step function with automatic latency and error capture.

from adgtk.measurements import AgentWriter, track_step

writer = AgentWriter(result_folders)

@track_step(writer)
def agent_step(prompt: str) -> str:
    return call_llm(prompt)

# Disable error flagging (latency only):
@track_step(writer, log_errors=False)
def best_effort_step(prompt: str) -> str:
    ...

Latency is always recorded — log_step fires in the finally block even when the wrapped function raises.


MeasurementEngine

Applies one or more registered measurement functions to a dataset. Results are stored in the same MetricTracker / CSV format used by AgentWriter.

engine = MeasurementEngine()
engine = MeasurementEngine(add_by_tag="built-in")   # pre-load all built-ins
engine = MeasurementEngine(add_by_tag="comparison") # pre-load comparison functions

Methods

Method Description
add(factory_id) Register a measurement by factory ID
measure(data) Apply all measurements; iterates per-item if needed
compare(pairs) Apply comparison measurements to [(a, b), ...]
measure_dataset_distribution(dataset) Distribution measurements over the full dataset
compare_dataset_distribution(ds1, ds2) Distribution comparisons
save_data(result_folders) Write CSVs via the internal MetricTracker
export_last_val_to_dict() {label: latest_value} for each measurement
report() MeasurementReport with all labels and recorded data

Example

engine = MeasurementEngine(add_by_tag="comparison")
engine.compare(list(zip(predictions, references)))
engine.save_data(result_folders)

for label, value in engine.export_last_val_to_dict().items():
    result.set(label, value)

Built-in measurements

These ship with ADGTK and are registered automatically at import time. Use them directly via MeasurementEngine or create_measurement.

String measurements

Factory ID Tags Input Returns Description
string_length string, built-in str int Character count
exact_match string, comparison, built-in str, str float 1.0 if identical, else 0.0
token_f1 string, comparison, built-in str, str float Word-overlap F1 (case-insensitive)
json_valid string, built-in str float 1.0 if valid JSON, else 0.0

Dictionary measurements

Factory ID Tags Input Returns Description
dict_total_str_length dict, built-in dict int Total character count of all string values
key_overlap dict, comparison, built-in dict, dict float Shallow key overlap ratio
dict_schema_match dict, comparison, built-in dict, dict float Recursive key-path overlap (ignores values)
schema_key_depth dict, built-in dict int Maximum nesting depth (root = 1)

List measurements

Factory ID Tags Input Returns Description
list_item_type_consistency list, built-in list float Proportion of items sharing the dominant type

Browsing via CLI

adgtk-factory list measurement
adgtk-factory list measurement --tags built-in
adgtk-factory list measurement --tags comparison

Registry functions

# Instantiate a measurement by ID
m = create_measurement("exact_match")
score = m("the cat sat", "the cat")

# Look up an entry without instantiating
entry = get_measurement_factory_entry("token_f1")

# Filter by tag
entries = get_measurements_by_tag("comparison")

# Filter by measurement type
entries = get_measurements_by_type("direct_comparison")

register_to_measurement_factory

Decorator to register a custom measurement function or class. See Custom Measurements for the full guide.

from adgtk.measurements import register_to_measurement_factory

@register_to_measurement_factory(tags=["text", "custom"])
def my_score(a: str, b: str) -> float:
    """My custom similarity metric."""
    ...

MeasurementData / MeasurementReport

TypedDicts returned by engine.report().

class MeasurementData(TypedDict):
    label: str
    description: str
    data: list

class MeasurementReport(TypedDict):
    engine_id: str
    measurements: list[MeasurementData]

Measurement types reference

Type Signature Use case
direct_measurement f(a) -> float Score one item
direct_comparison f(a, b) -> float Diff between two items
distribution_measurement f(data) -> list[float] Characterise a dataset
distribution_comparison f(dist_a, dist_b) -> float Compare two datasets