Use a custom trace type when you want shared helpers or computed views over
the full interaction history (for example, a turn count), or a custom Rich
rendering for notebooks and terminals. This guide uses a small LLMTrace
subclass for chat-style [user] / [assistant] formatting.
Giskardβs Trace is not OpenTelemetry: it is the immutable conversation
history passed to checks and interaction callables.
Subclass Trace and
pass trace_type=LLMTrace on Scenario.
The scenario runner starts from an empty trace instance of that type and appends
interactions as usual.
Use a private helper (here _conversation_markdown) to build a transcript string.
Override __rich_console__ so Rich-based output (see below) can render Markdown.
Giskard does not use a _repr_prompt_ hook; Rich uses __rich_console__ / __rich__
on the trace when building reports.
Add @computed_field properties for values you want to reuse in custom checks.
from rich.console import Console, ConsoleOptions, RenderResult
from rich.markdown import Markdown
from giskard.checks import Trace
from pydantic import computed_field
classLLMTrace(Trace[str,str]):
"""Chat-oriented trace with a Markdown transcript for Rich."""
Pass trace_type=LLMTrace so execution uses your class. Optional
annotations={...} on the scenario is copied onto the initial trace (shared
metadata such as tenant or experiment id) and appears on trace.annotations for
checks and callables.
ββββββββββββββββββββββββββββββββββββββββββββββββββββ β PASSED ββββββββββββββββββββββββββββββββββββββββββββββββββββtenant_annotationPASSmin_two_turnsPASSββββββββββββββββββββββββββββββββββββββββββββββββββββββ Trace ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
[assistant]: Hi! How can I help?
user: What is 2+2?
[assistant]: 2 + 2 equals 4.
ββββββββββββββββββββββββββββββββββββββββββββββββββ 1 step in 0ms ββββββββββββββββββββββββββββββββββββββββββββββββββ
ScenarioResult validates final_trace as the base Trace type. After a run,
type(result.final_trace) is therefore the generic Trace, even when you
passed trace_type=LLMTrace. During execution, checks still receive your
subclass β the FnCheck above uses isinstance(trace, LLMTrace) and
trace.turn_count.
To print a Rich transcript (Markdown [user] / [assistant] blocks), rebuild
an LLMTrace from the final interactions and annotations, then pass it to
Console.print or use it
anywhere Rich renders objects.
print_report() uses
Rich on result.final_trace; that object uses the default per-interaction
trace layout unless you reconstruct your subclass as below.
defas_llm_trace(trace:Trace[str,str])-> LLMTrace:
"""Rebuild LLMTrace for Rich / repr after `Scenario.run()`."""
[assistant]: Hi! How can I help?
user: What is 2+2?
[assistant]: 2 + 2 equals 4.
ββββββββββββββββββββββββββββββββββββββββββββββββββββ β PASSED ββββββββββββββββββββββββββββββββββββββββββββββββββββtenant_annotationPASSmin_two_turnsPASSββββββββββββββββββββββββββββββββββββββββββββββββββββββ Trace ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
[assistant]: Hi! How can I help?
user: What is 2+2?
[assistant]: 2 + 2 equals 4.
ββββββββββββββββββββββββββββββββββββββββββββββββββ 1 step in 0ms ββββββββββββββββββββββββββββββββββββββββββββββββββ
Built-in checks still use paths like trace.last.outputs. Custom fields on a
subclass are available in Python (for example trace.turn_count); expose
data through trace.annotations or interaction metadata if you need it in
JSONPath strings. See JSONPath in checks.