Settings, models and sign-in

Three places, one order

FunctAI.configure!(lm = "gpt-6-luna", temperature = 0)          # the session (the `!`: it changes something)

with_settings(lm = "claude-haiku-4-5", log_calls = true) do      # a block, and every task it starts
    mood.(reviews)
end

@ai lm = "gemini:gemini-3.8-flash" function mood(review::String)::Mood … end   # the function's own
fast = configure(mood; lm = "groq:openai/gpt-oss-120b")            # a copy with other settings

A function's own settings win over with_settings, which wins over configure!, which wins over the defaults. A setting given as nothing goes back to what the next layer says. An unknown setting is refused with the names that exist:

try
    configure(mood; modle = "gpt-6-luna")
catch err
    showerror(stdout, err)
end
ArgumentError: unknown setting modle. Settings: lm, router, temperature, max_tokens, top_p, stop, seed, config, adapter, template, reasoning, include_name, capabilities, retries, api_retries, max_steps, tool_errors, log_calls, log_content, observers, journal, program_observers, caller, concurrency, progress, cache_replies, replicate, approve, plugins, program_plugins, escalate_to, escalate_below

The settings a function sets itself:

settings(configure(mood; temperature = 0, retries = 2))
(retries = 2, temperature = 0)

The settings

SettingWhat it does
lmthe model: "gpt-6-luna", "claude-haiku-4-5", "gemini:gemini-3.8-flash", "groq:openai/gpt-oss-120b", …
temperature, max_tokens, top_p, stop, seedsampling, sent to the provider
configany other lm15 Config field: config = (reasoning = LM15.Reasoning(effort = "off"),)
adapterthe layout: :xml (default, any model), :chat (DSPy's sections), :json (a schema the provider enforces)
templatea chat template: [:system => "…", :turns, :user => "Review: {review}"]
reasoningtrue: the model writes its reasoning before the answer (Python's module = "cot")
include_namefalse: leave the function's name out of the instruction
capabilitiesfacts about the model that replace the table's: Dict("native_reasoning" => false)
retriesre-asks after an unreadable reply (default 1)
api_retriesre-sends after a transient provider error (default 3)
max_stepsmodel requests per tool loop (default 8)
tool_errors:report (the model sees a tool's error, default) or :raise
concurrencycalls in flight over a column (default 8)
log_callsthe call log: a folder, true (the default folder) or false
log_contentfalse: log sizes, times and tokens, never values
callerwho is calling, added to the log: Dict("kind" => "notebook")
routerthe lm15 router calls go through (a gateway, a fake in tests)

Models and keys

A model name is provider:model or a name lm15 recognizes ("gpt-6-luna", "claude-haiku-4-5"). The key comes from the environment (OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY, …). With no model named, FunctAI uses the first provider it finds a key for.

Subscriptions and saved keys go through lm15's sign-in, shared by every lm15 and FunctAI language:

FunctAI.login("claude")                 # a browser, or a link over SSH
FunctAI.login("openai"; key = "sk-…")   # save a key
FunctAI.logins()                        # what this machine is signed in to
@ai lm = "claude:claude-sonnet-5" function …

What a model can do

What a model can do (native tool calls, reasoning, stop sequences, enforced JSON) is declared in the contract's table, never guessed, and decides how the layout is written:

model_capabilities("anthropic", "claude-haiku-4-5")
(assistant_prefill = false, instruct = true, native_function_calling = true, native_reasoning = true, native_structured_output = true, stop_sequences = true)

A setting a model doesn't take (a temperature of 0 on a reasoning model that runs only at 1) is left out of its requests, with one warning per provider, rather than failing every call.

Layouts and templates

The same function, three ways of writing it for the model:

FunctAI.prompt(configure(mood; adapter = :chat), "It broke.")
model: gpt-4.1-mini
system
Function: mood

How does the customer feel about what they bought?

Respond with the corresponding output fields, each under its header, then end with [[ ## completed ## ]]:

[[ ## result ## ]]
one of: happy, unhappy, mixed

[[ ## completed ## ]]
user
[[ ## review ## ]]
It broke.


A template is your own wording; {name} places an input:

FunctAI.prompt(configure(mood; template = [:system => "You read reviews.", :user => "Review: {review}"]), "It broke.")
model: gpt-4.1-mini
system
You read reviews.
user
Review: It broke.