Stored outputs¶
A run stores the output of every attempt, together with the inputs, the expected output and the metadata of each case. This page describes what evaltrack stores of a value, and how to make a large value smaller.
Private fields¶
evaltrack stores a dataclass without its private fields, the fields whose name starts with _. A
library often keeps its internal state in private fields, and this state can be large.
For example, a pydantic-ai AgentRunResult keeps the message history and the JSON schema of every
tool in private fields. evaltrack stores it as its final output only:
{ "output": "The sum is 5." }
The evaluators still see the full object, because they run before evaltrack stores the output.
Converters¶
A converter changes how evaltrack stores the values of one type. Register it in a conftest.py:
import evaltrack.converters
evaltrack.converters.register(
"my-framework",
native_types=("my_framework.results.RunResult",),
convert=lambda result: {"answer": result.answer},
)
native_types are dotted paths, compared as text, so the registration imports nothing. A subclass
of a registered type is converted too. A converter applies to each value evaltrack stores from an
eval: the output of each attempt, the inputs, expected output and metadata of each case, and the
details a runner records. It applies to a whole value, and does not look inside a value for a
registered type.
If a converter raises, evaltrack stores the value as it is and logs a warning. A converter never fails an eval.
A converter for pydantic-ai tool calls¶
This converter stores an agent run as its final output and the tool calls it made, in order:
from typing import Any
import evaltrack.converters
from pydantic_ai.messages import ModelResponse, ToolCallPart
def record_agent_run(result: Any) -> dict[str, Any]:
tool_calls = [
{"tool": part.tool_name, "args": part.args_as_dict()}
for message in result.all_messages()
if isinstance(message, ModelResponse)
for part in message.parts
if isinstance(part, ToolCallPart)
]
return {"output": result.output, "tool_calls": tool_calls}
evaltrack.converters.register(
"pydantic-ai",
native_types=("pydantic_ai.run.AgentRunResult",),
convert=record_agent_run,
)
This is often not necessary. An evaluator that checks a tool call already records its verdict and its reason for each attempt.
Large outputs¶
If each output is hundreds of kilobytes, a run of a few hundred attempts is slow to save, push and open. The dashboard gets a large value only when a pane needs it, so it can open such a run. Saving and pushing stay slow.
At the end of the pytest session, evaltrack reports each attempt whose stored output is larger than 256 KB. The report names the test and the case. To make the outputs smaller, do one of these:
- Return only what the evaluators need from the task.
- Register a converter for the type of the output.
If your eval runner records traces, use them for the full data. A translator can record the trace
IDs in the details of an attempt. The pydantic-evals translator does this when tracing is
configured, for example with Logfire.
Related: Translators · Eval runners