Packages
nous
0.12.5
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/prom_ex/plugin.ex
if Code.ensure_loaded?(PromEx) do
defmodule Nous.PromEx.Plugin do
@moduledoc """
PromEx plugin for Nous AI agent metrics.
This plugin captures Prometheus metrics for agent execution, model requests,
and tool execution based on Nous's telemetry events.
## Prerequisites
This plugin requires PromEx to be available in your project. Add these
dependencies to your `mix.exs`:
{:prom_ex, "~> 1.11"},
{:plug, "~> 1.18"} # Required by PromEx
## Usage
Add this plugin to your PromEx module:
defmodule MyApp.PromEx do
use PromEx, otp_app: :my_app
@impl true
def plugins do
[
# ... other plugins
{Nous.PromEx.Plugin, []}
]
end
end
## Configuration Options
* `:otp_app` - The OTP application name (optional, defaults to PromEx module setting)
* `:metric_prefix` - Custom metric prefix (optional, defaults to `[:otp_app, :nous]`)
* `:duration_unit` - Time unit for duration metrics: `:second`, `:millisecond`,
`:microsecond`, or `:nanosecond` (default: `:millisecond`)
## Exposed Metric Groups
* `:nous_agent_event_metrics` - Agent execution metrics
* `:nous_model_event_metrics` - Model request metrics
* `:nous_tool_event_metrics` - Tool execution metrics
## Metrics
### Agent Metrics
* `nous_agent_run_duration` - Distribution of agent run durations
* `nous_agent_run_tokens_total` - Distribution of total tokens used
* `nous_agent_run_input_tokens` - Distribution of input tokens
* `nous_agent_run_output_tokens` - Distribution of output tokens
* `nous_agent_run_tool_calls` - Distribution of tool calls per run
* `nous_agent_run_iterations` - Distribution of iterations per run
* `nous_agent_run_exceptions_total` - Counter of agent exceptions
### Model Metrics
* `nous_model_request_duration` - Distribution of model request durations
* `nous_model_request_tokens_total` - Distribution of total tokens per request
* `nous_model_request_input_tokens` - Distribution of input tokens per request
* `nous_model_request_output_tokens` - Distribution of output tokens per request
* `nous_model_request_exceptions_total` - Counter of model request exceptions
* `nous_model_stream_connect_duration` - Distribution of stream connection times
* `nous_model_stream_exceptions_total` - Counter of stream exceptions
### Tool Metrics
* `nous_tool_execution_duration` - Distribution of tool execution durations
* `nous_tool_execution_attempts` - Distribution of attempts per execution
* `nous_tool_execution_exceptions_total` - Counter of tool exceptions
"""
use PromEx.Plugin
alias PromEx.MetricTypes.Event
@impl true
def event_metrics(opts) do
otp_app = Keyword.get(opts, :otp_app)
metric_prefix = Keyword.get(opts, :metric_prefix, [otp_app, :nous])
duration_unit = Keyword.get(opts, :duration_unit, :millisecond)
[
agent_metrics(metric_prefix, duration_unit),
model_metrics(metric_prefix, duration_unit),
tool_metrics(metric_prefix, duration_unit)
]
end
defp agent_metrics(metric_prefix, duration_unit) do
Event.build(
:nous_agent_event_metrics,
[
distribution(
metric_prefix ++ [:agent, :run, :duration, duration_unit],
event_name: [:nous, :agent, :run, :stop],
measurement: :duration,
description: "Duration of agent runs",
tags: [:agent_name, :model_provider, :model_name],
tag_values: &agent_tag_values/1,
unit: {:native, duration_unit},
reporter_options: [
buckets: duration_buckets(duration_unit)
]
),
distribution(
metric_prefix ++ [:agent, :run, :tokens, :total],
event_name: [:nous, :agent, :run, :stop],
measurement: :total_tokens,
description: "Total tokens used per agent run",
tags: [:agent_name, :model_provider, :model_name],
tag_values: &agent_tag_values/1,
reporter_options: [
buckets: [100, 500, 1000, 2500, 5000, 10000, 25000, 50000, 100_000]
]
),
distribution(
metric_prefix ++ [:agent, :run, :tokens, :input],
event_name: [:nous, :agent, :run, :stop],
measurement: :input_tokens,
description: "Input tokens per agent run",
tags: [:agent_name, :model_provider, :model_name],
tag_values: &agent_tag_values/1,
reporter_options: [
buckets: [100, 500, 1000, 2500, 5000, 10000, 25000, 50000]
]
),
distribution(
metric_prefix ++ [:agent, :run, :tokens, :output],
event_name: [:nous, :agent, :run, :stop],
measurement: :output_tokens,
description: "Output tokens per agent run",
tags: [:agent_name, :model_provider, :model_name],
tag_values: &agent_tag_values/1,
reporter_options: [
buckets: [50, 100, 250, 500, 1000, 2500, 5000, 10000]
]
),
distribution(
metric_prefix ++ [:agent, :run, :tool_calls],
event_name: [:nous, :agent, :run, :stop],
measurement: :tool_calls,
description: "Number of tool calls per agent run",
tags: [:agent_name, :model_provider, :model_name],
tag_values: &agent_tag_values/1,
reporter_options: [
buckets: [0, 1, 2, 5, 10, 20, 50]
]
),
distribution(
metric_prefix ++ [:agent, :run, :iterations],
event_name: [:nous, :agent, :run, :stop],
measurement: :iterations,
description: "Number of iterations per agent run",
tags: [:agent_name, :model_provider, :model_name],
tag_values: &agent_tag_values/1,
reporter_options: [
buckets: [1, 2, 3, 5, 10, 15, 20]
]
),
counter(
metric_prefix ++ [:agent, :run, :exceptions, :total],
event_name: [:nous, :agent, :run, :exception],
description: "Total number of agent run exceptions",
tags: [:agent_name, :model_provider, :error_kind],
tag_values: &agent_exception_tag_values/1
)
]
)
end
defp model_metrics(metric_prefix, duration_unit) do
Event.build(
:nous_model_event_metrics,
[
distribution(
metric_prefix ++ [:model, :request, :duration, duration_unit],
event_name: [:nous, :model, :request, :stop],
measurement: :duration,
description: "Duration of model API requests",
tags: [:provider, :model_name, :has_tool_calls],
tag_values: &model_tag_values/1,
unit: {:native, duration_unit},
reporter_options: [
buckets: duration_buckets(duration_unit)
]
),
distribution(
metric_prefix ++ [:model, :request, :tokens, :total],
event_name: [:nous, :model, :request, :stop],
measurement: :total_tokens,
description: "Total tokens per model request",
tags: [:provider, :model_name],
tag_values: &model_tag_values/1,
reporter_options: [
buckets: [100, 500, 1000, 2500, 5000, 10000, 25000]
]
),
distribution(
metric_prefix ++ [:model, :request, :tokens, :input],
event_name: [:nous, :model, :request, :stop],
measurement: :input_tokens,
description: "Input tokens per model request",
tags: [:provider, :model_name],
tag_values: &model_tag_values/1,
reporter_options: [
buckets: [100, 500, 1000, 2500, 5000, 10000]
]
),
distribution(
metric_prefix ++ [:model, :request, :tokens, :output],
event_name: [:nous, :model, :request, :stop],
measurement: :output_tokens,
description: "Output tokens per model request",
tags: [:provider, :model_name],
tag_values: &model_tag_values/1,
reporter_options: [
buckets: [50, 100, 250, 500, 1000, 2500, 5000]
]
),
counter(
metric_prefix ++ [:model, :request, :exceptions, :total],
event_name: [:nous, :model, :request, :exception],
description: "Total number of model request exceptions",
tags: [:provider, :model_name, :error_kind],
tag_values: &model_exception_tag_values/1
),
distribution(
metric_prefix ++ [:model, :stream, :connect, :duration, duration_unit],
event_name: [:nous, :model, :stream, :connected],
measurement: :duration,
description: "Time to establish streaming connection",
tags: [:provider, :model_name],
tag_values: &model_tag_values/1,
unit: {:native, duration_unit},
reporter_options: [
buckets: duration_buckets(duration_unit)
]
),
counter(
metric_prefix ++ [:model, :stream, :exceptions, :total],
event_name: [:nous, :model, :stream, :exception],
description: "Total number of stream exceptions",
tags: [:provider, :model_name, :error_kind],
tag_values: &model_exception_tag_values/1
)
]
)
end
defp tool_metrics(metric_prefix, duration_unit) do
Event.build(
:nous_tool_event_metrics,
[
distribution(
metric_prefix ++ [:tool, :execution, :duration, duration_unit],
event_name: [:nous, :tool, :execute, :stop],
measurement: :duration,
description: "Duration of tool executions",
tags: [:tool_name, :success],
tag_values: &tool_tag_values/1,
unit: {:native, duration_unit},
reporter_options: [
buckets: duration_buckets(duration_unit)
]
),
distribution(
metric_prefix ++ [:tool, :execution, :attempts],
event_name: [:nous, :tool, :execute, :stop],
measurement: :attempt,
description: "Number of attempts for tool execution",
tags: [:tool_name, :success],
tag_values: &tool_tag_values/1,
reporter_options: [
buckets: [1, 2, 3, 4, 5]
]
),
counter(
metric_prefix ++ [:tool, :execution, :exceptions, :total],
event_name: [:nous, :tool, :execute, :exception],
description: "Total number of tool execution exceptions",
tags: [:tool_name, :will_retry, :error_kind],
tag_values: &tool_exception_tag_values/1
)
]
)
end
# Tag value extractors
defp agent_tag_values(metadata) do
%{
agent_name: metadata.agent_name,
model_provider: to_string(metadata.model_provider),
model_name: metadata.model_name
}
end
defp agent_exception_tag_values(metadata) do
%{
agent_name: metadata.agent_name,
model_provider: to_string(metadata.model_provider),
error_kind: to_string(metadata.kind)
}
end
defp model_tag_values(metadata) do
base = %{
provider: to_string(metadata.provider),
model_name: metadata.model_name
}
if Map.has_key?(metadata, :has_tool_calls) do
Map.put(base, :has_tool_calls, to_string(metadata.has_tool_calls))
else
base
end
end
defp model_exception_tag_values(metadata) do
%{
provider: to_string(metadata.provider),
model_name: metadata.model_name,
error_kind: to_string(metadata.kind)
}
end
defp tool_tag_values(metadata) do
%{
tool_name: metadata.tool_name,
success: to_string(metadata.success)
}
end
defp tool_exception_tag_values(metadata) do
%{
tool_name: metadata.tool_name,
will_retry: to_string(metadata.will_retry),
error_kind: normalize_error_kind(metadata.kind)
}
end
defp normalize_error_kind(kind) when is_atom(kind), do: to_string(kind)
defp normalize_error_kind(kind), do: inspect(kind)
# Duration buckets based on time unit
defp duration_buckets(:second) do
[0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30, 60]
end
defp duration_buckets(:millisecond) do
[10, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 30000, 60000]
end
defp duration_buckets(:microsecond) do
[1000, 5000, 10000, 50000, 100_000, 500_000, 1_000_000, 5_000_000, 10_000_000]
end
defp duration_buckets(:nanosecond) do
[
1_000_000,
5_000_000,
10_000_000,
50_000_000,
100_000_000,
500_000_000,
1_000_000_000
]
end
end
else
defmodule Nous.PromEx.Plugin do
@moduledoc """
PromEx plugin for Nous AI agent metrics.
To use this plugin, add `{:prom_ex, "~> 1.11"}` and `{:plug, "~> 1.18"}` to your dependencies.
"""
def event_metrics(_opts) do
raise "PromEx is not available. Add {:prom_ex, \"~> 1.11\"} and {:plug, \"~> 1.18\"} to your dependencies."
end
end
end