API key resolution per spec §6.4. Keys never appear on the engine.
Five-level resolution chain — the first level that yields a non-empty string wins:
opts[:api_key]— explicit per-call overrideALLM.Keys.Store— in-process Agent set viaput/2Application.get_env(:allm, :keys, %{})[provider]System.get_env(env_var_for(provider)).envfile atconfig :allm, :dotenv_path(default:File.cwd!() <> "/.env") — only consulted whenconfig :allm, load_dotenv: true.
Empty-string values at every level are treated as missing (defensive —
an unset-looking env var like OPENAI_API_KEY= must not satisfy
resolution).
get/1 and get/2 return {:ok, key, source} on hit or
{:error, :missing} on miss (spec §6.4 — Non-obvious decision #9
preserves this shape despite the project's "no atom-tuple errors" rule).
fetch!/2 raises %ALLM.Error.EngineError{reason: :missing_key} on
miss (Non-obvious decision #8 — the only function in the library that
raises).
.env parser limitations
ALLM ships a built-in .env parser (Non-obvious decision #2) to avoid
a dotenvy dependency for a level-5 fallback most users won't even
enable. The supported subset is strict: KEY=VALUE lines, # comment
lines, blank lines, export KEY=VALUE (the leading export is
stripped), and surrounding double-quote stripping. No variable
interpolation, no multi-line values, no escape sequences, no single-
quote stripping. Users with complex .env files should either
System.put_env/2 at boot or depend on dotenvy themselves.
Summary
Functions
Remove provider's key from the in-process runtime store.
Translate a provider atom to its env-var name.
Like get/2, but raises %ALLM.Error.EngineError{reason: :missing_key}
when no source yields a non-empty key.
Resolve the API key for provider via the five-level chain.
Resolve the API key for provider, honoring opts[:api_key] as the
highest-precedence source.
Install key for provider in the in-process runtime store.
Types
@type provider() :: atom()
@type source() :: :opts | :runtime | :app_config | :env | :dotenv
Functions
@spec delete(provider()) :: :ok
Remove provider's key from the in-process runtime store.
Does not touch Application env, system env, or the .env cache.
Translate a provider atom to its env-var name.
Known providers use the spec §6.4 table (:openai → "OPENAI_API_KEY",
etc.). Unknown providers fall back to
String.upcase("#{provider}") <> "_API_KEY".
Public because ALLM.Keys.Dotenv.lookup/1 delegates through it for
consistent provider→env-var translation: the .env source looks up the
same env-var name the System.get_env source does, so configuring one
(OPENAI_API_KEY=… in the shell) and the other (OPENAI_API_KEY=… in
.env) uses an identical key name. This module is the single source
of truth for that mapping.
Examples
iex> ALLM.Keys.env_var_for(:openai)
"OPENAI_API_KEY"
iex> ALLM.Keys.env_var_for(:anthropic)
"ANTHROPIC_API_KEY"
iex> ALLM.Keys.env_var_for(:some_new_provider)
"SOME_NEW_PROVIDER_API_KEY"
Like get/2, but raises %ALLM.Error.EngineError{reason: :missing_key}
when no source yields a non-empty key.
This is the ONLY function in the library that raises
ALLM.Error.EngineError rather than returning it in an {:error, _}
tuple (Non-obvious decision #8). Justified because adapters look up
keys at call time deep inside their implementation, and bubbling
{:error, _} through every with chain is untenable.
The raised error's :metadata carries :checked_sources — a list of
the source atoms that were actually consulted (:dotenv is included
only when config :allm, load_dotenv: true).
Note: :checked_sources records which levels were configured/enabled
for this call (i.e., which chain links were walked), not which levels
actually had a value supplied. An :opts entry appearing in the list
means the opts keyword was inspected — it does not distinguish
"api_key: omitted" from "api_key: present but empty/nil".
Examples
iex> ALLM.Keys.Store.clear()
iex> ALLM.Keys.put(:my_test_provider, "sk-doctest")
iex> ALLM.Keys.fetch!(:my_test_provider)
"sk-doctest"
iex> ALLM.Keys.Store.clear()
iex> try do
...> ALLM.Keys.fetch!(:my_test_provider)
...> rescue
...> e in ALLM.Error.EngineError -> e.reason
...> end
:missing_key
Resolve the API key for provider via the five-level chain.
Returns {:ok, key, source} on hit (where source identifies which
level of the chain provided the key) or {:error, :missing} on miss.
Shorthand for get(provider, []).
Examples
iex> ALLM.Keys.Store.clear()
iex> ALLM.Keys.put(:my_test_provider, "sk-doctest")
iex> ALLM.Keys.get(:my_test_provider)
{:ok, "sk-doctest", :runtime}
iex> ALLM.Keys.Store.clear()
:ok
Resolve the API key for provider, honoring opts[:api_key] as the
highest-precedence source.
See the module docs for the full chain.
Install key for provider in the in-process runtime store.
Cleared by ALLM.Keys.delete/1 or ALLM.Keys.Store.clear/0. Persists
for the lifetime of the ALLM.Keys.Store Agent.
Examples
iex> ALLM.Keys.Store.clear()
iex> ALLM.Keys.put(:my_test_provider, "sk-doctest")
:ok
iex> ALLM.Keys.get(:my_test_provider)
{:ok, "sk-doctest", :runtime}
iex> ALLM.Keys.Store.clear()
:ok