LlmCore.LLM.Native.Router (llm_core v0.8.1)

Copy Markdown View Source

Config-driven provider resolution for the Native agentic loop.

Resolves a provider name (or nil for cascade) into a fully-configured {module, model, provider_opts} tuple. Provider definitions are loaded from TOML at startup and stored in LlmCore.Config.Store.

Resolution Logic

  1. If the caller reports the requested model is loaded on the local Appliance (appliance_has_model: true) and an Appliance-module provider is listed in the cascade (or is the fallback), that provider is used. With no Appliance provider configured the flag has no effect.
  2. Explicit provider (llm_provider: "my_provider") → look up Definition by name/alias
  3. Model routing (model matches a pattern) → look up Definition for matched provider; an unusable target falls through to the cascade
  4. Cascade → walk the configured [native]cascade list, find the first usable provider Definition. With no cascade configured, the :fallback option (the routing default, passed by the caller) is the only member.
  5. Nothing matches → {:error, :no_provider}

A cascade member is usable only if its Definition is enabled, can run the native loop (provider_kind: :module, not a CLI provider), and — when it declares an auth credential — that credential resolves. Anything else is skipped, and skipped/2 reports why. llm_core ships no cascade of its own; see LlmCore.LLM.Native.Config.

Adding a new provider

Add a [providers.my_provider] section to llm_core.toml. Zero code changes.

Summary

Functions

The alias of the first usable cascade member (or fallback) backed by the local LlmCore.LLM.Appliance module, or nil.

Returns an ordered list of candidates — primary first, then cascade fallbacks.

Walk the cascade and return the first available provider.

Returns the default model for a provider alias from config.

Resolve a provider for the given model string.

Resolve a provider by explicit name (e.g. "my_provider" from native:my_provider syntax).

Match a lowercased model string against routing patterns. First match wins.

Explains which cascade members are not usable and why.

Types

config()

@type config() :: map()

provider_opts()

@type provider_opts() :: keyword()

resolution()

@type resolution() ::
  {:ok, {module(), String.t(), provider_opts()}} | {:error, :no_provider}

Functions

appliance_alias(config, opts \\ [])

@spec appliance_alias(
  config(),
  keyword()
) :: String.t() | nil

The alias of the first usable cascade member (or fallback) backed by the local LlmCore.LLM.Appliance module, or nil.

Callers use it to decide whether probing the appliance for a loaded model is worthwhile at all: with no Appliance provider configured, nothing is probed.

candidates(model, config, opts \\ [])

@spec candidates(String.t() | nil, config(), keyword()) :: [
  {module(), String.t(), provider_opts()}
]

Returns an ordered list of candidates — primary first, then cascade fallbacks.

The primary is what resolve/3 would pick. Fallbacks are the remaining cascade providers with their own default models (NOT the originally requested model — a model id one provider serves usually can't run on another).

The primary module is never duplicated in the tail. Callers walk the list and attempt each candidate in order, falling through on runtime failure.

Returns [] when nothing resolves.

cascade_pick(config, model, providers)

@spec cascade_pick(config(), String.t() | nil, map()) :: resolution()

Walk the cascade and return the first available provider.

get_default_model(alias, config)

@spec get_default_model(String.t(), config()) :: String.t() | nil

Returns the default model for a provider alias from config.

resolve(model, config, opts \\ [])

@spec resolve(String.t() | nil, config(), keyword()) :: resolution()

Resolve a provider for the given model string.

Returns {:ok, {module, model, provider_opts}} or {:error, :no_provider}.

The appliance_has_model flag lets the caller indicate whether the model is available locally (checked externally via Appliance discovery).

resolve_provider(provider_name)

@spec resolve_provider(String.t()) :: resolution()

Resolve a provider by explicit name (e.g. "my_provider" from native:my_provider syntax).

This is the direct routing path — no cascade, no model routing. Returns {:ok, {module, model, provider_opts}} or {:error, :no_provider}.

route_model(lower, config)

@spec route_model(String.t(), config()) :: {:ok, String.t()} | :no_match

Match a lowercased model string against routing patterns. First match wins.

skipped(config, opts \\ [])

@spec skipped(
  config(),
  keyword()
) :: [%{provider: String.t(), reason: atom()}]

Explains which cascade members are not usable and why.

Accepts the same :fallback option as candidates/3. Reasons: :no_provider (no such definition), :disabled, :not_native (a CLI provider cannot run the native loop), :no_credentials (the definition's auth credential is unset).