Current section

Files

Jump to
nous lib nous eval config.ex
Raw

lib/nous/eval/config.ex

defmodule Nous.Eval.Config do
@moduledoc """
Configuration for the evaluation framework.
Configuration can be set via:
- Application environment
- Environment variables
- Explicit options passed to functions
## Application Configuration
config :nous, Nous.Eval,
default_model: "lmstudio:ministral-3-14b-reasoning",
default_timeout: 30_000,
parallelism: 4,
results_path: "priv/eval_results"
## Environment Variables
NOUS_EVAL_DEFAULT_MODEL=lmstudio:model
NOUS_EVAL_DEFAULT_TIMEOUT=60000
## Cost Configuration
Provider pricing for cost estimation (per 1K tokens):
config :nous, Nous.Eval,
cost_config: %{
"lmstudio" => %{input: 0.0, output: 0.0},
"openai" => %{input: 0.01, output: 0.03},
"anthropic" => %{input: 0.015, output: 0.075}
}
"""
@type t :: %__MODULE__{
default_model: String.t() | nil,
default_timeout: non_neg_integer(),
default_instructions: String.t() | nil,
parallelism: non_neg_integer(),
store_results: boolean(),
results_path: String.t(),
cost_config: map()
}
defstruct default_model: nil,
default_timeout: 60_000,
default_instructions: nil,
parallelism: 1,
store_results: true,
results_path: "priv/eval_results",
cost_config: %{
# Local inference is free
"lmstudio" => %{input: 0.0, output: 0.0},
"ollama" => %{input: 0.0, output: 0.0},
"vllm" => %{input: 0.0, output: 0.0},
"sglang" => %{input: 0.0, output: 0.0},
# Cloud providers (per 1K tokens, approximate)
"openai" => %{input: 0.01, output: 0.03},
"anthropic" => %{input: 0.015, output: 0.075},
"groq" => %{input: 0.0005, output: 0.001},
"gemini" => %{input: 0.0005, output: 0.0015},
"mistral" => %{input: 0.002, output: 0.006},
"openrouter" => %{input: 0.01, output: 0.03},
"together" => %{input: 0.005, output: 0.015}
}
@doc """
Get configuration from application environment and env vars.
Merges configuration from:
1. Defaults
2. Application config
3. Environment variables
4. Explicit options
"""
@spec get(keyword()) :: t()
def get(opts \\ []) do
app_config = Application.get_env(:nous, Nous.Eval, [])
%__MODULE__{
default_model:
opts[:default_model] ||
env_string("NOUS_EVAL_DEFAULT_MODEL") ||
app_config[:default_model],
default_timeout:
opts[:default_timeout] ||
env_integer("NOUS_EVAL_DEFAULT_TIMEOUT") ||
app_config[:default_timeout] ||
60_000,
default_instructions:
opts[:default_instructions] ||
app_config[:default_instructions],
parallelism:
opts[:parallelism] ||
env_integer("NOUS_EVAL_PARALLELISM") ||
app_config[:parallelism] ||
1,
store_results:
opts[:store_results] ||
app_config[:store_results] ||
true,
results_path:
opts[:results_path] ||
env_string("NOUS_EVAL_RESULTS_PATH") ||
app_config[:results_path] ||
"priv/eval_results",
cost_config: merge_cost_config(app_config[:cost_config])
}
end
@doc """
Get LM Studio configuration from environment.
"""
@spec lmstudio_config() :: keyword()
def lmstudio_config do
[
base_url:
System.get_env("LMSTUDIO_BASE_URL") ||
System.get_env("LMSTUDIO_URL") ||
"http://localhost:1234/v1"
]
end
@doc """
Get model for a test, with fallback chain.
"""
@spec get_model(Nous.Eval.TestCase.t() | nil, Nous.Eval.Suite.t() | nil, keyword()) ::
String.t() | nil
def get_model(test_case, suite, opts \\ []) do
config = get(opts)
cond do
opts[:model] -> opts[:model]
test_case && test_case.agent_config[:model] -> test_case.agent_config[:model]
suite && suite.default_model -> suite.default_model
config.default_model -> config.default_model
true -> nil
end
end
@doc """
Estimate cost for token usage.
Returns cost in USD.
"""
@spec estimate_cost(String.t(), non_neg_integer(), non_neg_integer()) :: float()
def estimate_cost(provider, input_tokens, output_tokens) do
config = get()
provider_key = extract_provider(provider)
rates = Map.get(config.cost_config, provider_key, %{input: 0.0, output: 0.0})
input_cost = input_tokens / 1000 * rates.input
output_cost = output_tokens / 1000 * rates.output
Float.round(input_cost + output_cost, 6)
end
# Private helpers
defp env_string(key), do: System.get_env(key)
defp env_integer(key) do
case System.get_env(key) do
nil -> nil
val -> String.to_integer(val)
end
end
defp merge_cost_config(nil), do: %__MODULE__{}.cost_config
defp merge_cost_config(custom) do
Map.merge(%__MODULE__{}.cost_config, custom)
end
defp extract_provider(model_string) when is_binary(model_string) do
case String.split(model_string, ":", parts: 2) do
[provider | _] -> provider
_ -> "unknown"
end
end
defp extract_provider(_), do: "unknown"
end