NVIDIA-NeMo / NVIDIA-NeMo/Guardrails
bug: GenerationLog.print_summary() crashes with stats.total_duration or activated_rail.duration None values
@christinaexyou is already working on this.
Since Aug 24, 2026.
- Dominant language
- Python
- Stars
- 7.2k
- Forks
- 842
- Avg merge
- 3d 1h
- Merged PRs (30d)
- 25
Description
Did you check docs and existing issues?
- I have read all the NeMo-Guardrails docs
- I have updated the package to the latest version before submitting this issue
- (optional) I have used the develop branch
- I have searched the existing issues of NeMo-Guardrails
Python version (python --version)
Python 3.13.2
Operating system/version
MacOS 26.5.2
NeMo-Guardrails version (if you must use a specific version and not the latest
0.24
Describe the bug
The stats.total_duration and activated_rail.duration fields are Optional[float] fields.
The GenerationLog.print_summary() doesn't have None-guards for all these fields, which will throw errors when it tries to print None with a float format.
Steps To Reproduce
Create
Expected Behavior
GenerationLog.print_summary() should correctly guard None values rather than crashing. With the option of OTEL tracing, the value of print_summary() is debatable
Actual Behavior
Create the script below in print_summary.py, and run it using uv run --locked python print_summary.py
"""Reproduce TypeError in GenerationLog.print_summary() on None duration fields.
print_summary() (nemoguardrails/rails/llm/options.py) formats several
Optional[float] duration fields with ``:.2f`` without guarding against None, so
any log carrying a None duration raises:
TypeError: unsupported format string passed to NoneType.__format__
There are two independent, unguarded crash points:
1. GenerationStats.total_duration (options.py ~line 318)
This field DEFAULTS to None, so a freshly built GenerationLog whose stats
were never populated crashes on the very first print, before any rail is
inspected.
2. ActivatedRail.duration (options.py ~line 370, the "Detailed stats" loop)
ActivatedRail.duration is Optional[float] and tool rails come through with
duration=None, so they crash the per-rail loop even when the stats block is
fully populated.
Both are reproduced below; each is caught so the script runs to completion and
prints a full traceback pointing at the exact failing line.
Run: python print_summary.py
"""
import sys
import traceback
import nemoguardrails
from nemoguardrails.rails.llm.options import (
ActivatedRail,
ExecutedAction,
GenerationLog,
GenerationStats,
)
def crash_1_total_duration() -> None:
"""GenerationStats.total_duration defaults to None and crashes line ~318.
The activated rail here carries a valid duration; only stats is left at its
default, isolating total_duration as the field that crashes.
"""
log = GenerationLog(
activated_rails=[
ActivatedRail(type="tool_output", name="tool output check", duration=0.5),
],
)
log.print_summary()
def crash_2_activated_rail_duration() -> None:
"""A tool rail with duration=None crashes the detailed-stats loop, line ~370.
stats.total_duration is populated so execution gets past crash point #1 and
reaches the per-rail loop, isolating ActivatedRail.duration as the field
that crashes.
"""
log = GenerationLog(
activated_rails=[
ActivatedRail(
type="tool_output",
name="tool output check",
executed_actions=[ExecutedAction(action_name="tool_output_check")],
duration=None,
),
],
stats=GenerationStats(total_duration=0.5),
)
log.print_summary()
def run(label: str, fn) -> None:
print(f"\n{'=' * 72}\n{label}\n{'=' * 72}")
try:
fn()
except TypeError:
print("REPRODUCED - TypeError raised:\n")
traceback.print_exc()
else:
print("No error raised - bug NOT reproduced.")
if __name__ == "__main__":
print(f"Python: {sys.version.split()[0]}")
print(f"nemoguardrails: {getattr(nemoguardrails, '__version__', 'unknown')}")
run(
"Crash 1: GenerationStats.total_duration is None (options.py ~line 318)",
crash_1_total_duration,
)
run(
"Crash 2: ActivatedRail.duration is None (options.py ~line 370)",
crash_2_activated_rail_duration,
)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.