Packages
nous
0.17.0
0.17.0
0.16.6
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.8
0.15.7
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.17
0.12.16
0.12.15
0.12.14
0.12.13
0.12.12
0.12.11
0.12.9
0.12.7
0.12.6
0.12.5
0.12.3
0.12.2
0.12.0
0.11.3
0.11.0
0.10.1
0.10.0
0.9.0
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.5.0
AI agent framework for Elixir with multi-provider LLM support
Current section
Files
Jump to
Current section
Files
lib/nous/eval/reporter.ex
defmodule Nous.Eval.Reporter do
@moduledoc """
Report generation for evaluation results.
## Example
{:ok, result} = Nous.Eval.run(suite)
# Print to console
Nous.Eval.Reporter.print(result)
# Generate JSON
json = Nous.Eval.Reporter.to_json(result)
# Write to file
Nous.Eval.Reporter.to_file(result, "report.json")
"""
alias Nous.Eval.SuiteResult
@doc """
Print results to console.
"""
@spec print(SuiteResult.t(), keyword()) :: :ok
defdelegate print(result, opts \\ []), to: Nous.Eval.Reporter.Console
@doc """
Print detailed results (including failures) to console.
"""
@spec print_detailed(SuiteResult.t(), keyword()) :: :ok
defdelegate print_detailed(result, opts \\ []), to: Nous.Eval.Reporter.Console
@doc """
Generate JSON report.
"""
@spec to_json(SuiteResult.t()) :: String.t()
defdelegate to_json(result), to: Nous.Eval.Reporter.Json
@doc """
Write report to JSON file.
"""
@spec to_file(SuiteResult.t(), String.t()) :: :ok | {:error, term()}
defdelegate to_file(result, path), to: Nous.Eval.Reporter.Json
@doc """
Generate markdown report.
"""
@spec to_markdown(SuiteResult.t()) :: String.t()
def to_markdown(%SuiteResult{} = result) do
"""
# Evaluation Report: #{result.suite_name}
**Date:** #{DateTime.to_iso8601(result.completed_at)}
**Duration:** #{result.duration_ms}ms
## Summary
| Metric | Value |
|--------|-------|
| Total Tests | #{result.total_count} |
| Passed | #{result.pass_count} |
| Failed | #{result.fail_count} |
| Errors | #{result.error_count} |
| Pass Rate | #{Float.round(result.pass_rate * 100, 1)}% |
| Average Score | #{Float.round(result.aggregate_score, 3)} |
#{metrics_section(result)}
## Test Results
#{test_results_table(result)}
#{failures_section(result)}
"""
end
defp metrics_section(%{metrics_summary: nil}), do: ""
defp metrics_section(%{metrics_summary: metrics}) do
"""
## Metrics
| Metric | Value |
|--------|-------|
| Total Tokens | #{metrics.total_tokens} |
| Mean Tokens | #{Float.round(metrics.mean_tokens, 1)} |
| P50 Latency | #{metrics.p50_latency_ms}ms |
| P95 Latency | #{metrics.p95_latency_ms}ms |
| Tool Calls | #{metrics.total_tool_calls} |
#{if metrics.total_estimated_cost, do: "| Estimated Cost | $#{Float.round(metrics.total_estimated_cost, 4)} |", else: ""}
"""
end
defp test_results_table(%{results: results}) do
header = "| Test | Status | Score | Duration |\n|------|--------|-------|----------|\n"
rows =
Enum.map(results, fn r ->
status = if r.passed, do: "PASS", else: "FAIL"
duration = "#{r.duration_ms}ms"
"| #{r.test_case_name} | #{status} | #{Float.round(r.score, 2)} | #{duration} |"
end)
|> Enum.join("\n")
header <> rows
end
defp failures_section(%{results: results}) do
failures = Enum.reject(results, & &1.passed)
if failures == [] do
""
else
"""
## Failures
#{Enum.map_join(failures, "\n\n", &format_failure/1)}
"""
end
end
defp format_failure(result) do
reason =
case result.evaluation_details[:reason] || result.error do
nil -> "Unknown"
r -> inspect(r)
end
"""
### #{result.test_case_name}
**Reason:** #{reason}
**Expected:** #{inspect(result.expected_output)}
**Actual:** #{inspect(result.actual_output)}
"""
end
end