Providers
Pass a model name as a string and loomflow picks the adapter from the name’s prefix. This page lists every prefix it recognises, the environment variable each provider needs, and how to configure each adapter when the defaults aren’t enough.
String dispatch by prefix
| String prefix | Resolves to | Required env var |
|---|---|---|
claude-* (incl. claude-opus-*, claude-sonnet-*, claude-haiku-*) | AnthropicModel | ANTHROPIC_API_KEY |
gpt-*, o1, o3, o1-*, o3-*, o4-* | OpenAIModel | OPENAI_API_KEY |
deepseek-* | OpenAIModel at https://api.deepseek.com | DEEPSEEK_API_KEY |
mistral-* | LiteLLMModel | provider-specific |
command-* (Cohere) | LiteLLMModel | COHERE_API_KEY |
bedrock/* | LiteLLMModel | AWS creds |
vertex_ai/* | LiteLLMModel | GCP creds |
gemini/* | LiteLLMModel | GEMINI_API_KEY |
together_ai/* | LiteLLMModel | TOGETHER_API_KEY |
replicate/* | LiteLLMModel | REPLICATE_API_KEY |
azure/* | LiteLLMModel | Azure creds |
ollama/* | LiteLLMModel | local Ollama running |
groq/* | LiteLLMModel | GROQ_API_KEY |
litellm/* | LiteLLMModel (explicit opt-in; strips the prefix) | provider-specific |
echo | EchoModel | none |
Anything that doesn’t match one of these prefixes raises ConfigError
rather than guessing, so mixtral-* or a bare provider name
won’t resolve. For those, prefix with litellm/ (e.g.
litellm/mistral/mixtral-8x7b) to route explicitly through LiteLLM.
from loomflow import Agent
Agent("...", model="claude-opus-4-7")
Agent("...", model="claude-haiku-4-5")
Agent("...", model="gpt-4o")
Agent("...", model="o3-mini")
Agent("...", model="deepseek-chat")
Agent("...", model="mistral-large")
Agent("...", model="command-r-plus")
Agent("...", model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0")
Agent("...", model="vertex_ai/gemini-pro")
Agent("...", model="ollama/llama3")
Agent("...", model="groq/llama-3.1-70b")
Agent("...", model="litellm/claude-3-haiku") # explicit opt-inAnthropic
By string
from loomflow import Agent
agent = Agent("...", model="claude-opus-4-7")
# ANTHROPIC_API_KEY must be set.Install: pip install 'loomflow[anthropic]'.
The adapter wraps the official anthropic SDK’s AsyncAnthropic
client: messages.create(...) for agent.run() and
messages.stream(...) for agent.stream(). Tool calls serialize to
Anthropic’s content-block format under the hood.
Structured output. With output_schema=, Claude 4.5 and later
models get the schema as native JSON outputs (output_config.format),
so the run’s tools stay callable. Older models (Opus 4 / 4.1, Sonnet
4, Sonnet 3.7, Claude 3.x), and schemas JSON outputs can’t express
(recursive models, free-form dict fields), get a synthetic
__output__ tool instead: forced when it’s the only tool, required
among the tools when there are others, and left to the model when
thinking is on, since a forced tool choice can’t be combined with
thinking. Either way the agent loop validates the reply against the
schema.
Neither adapter takes a temperature= option. Temperature is a
per-call argument of the adapter’s complete() and stream()
methods; the agent loop doesn’t set it, so agents use the provider
default. The Anthropic adapter sends temperature only when you set
one other than 1.0, so it works on anthropic SDK 1.x, whose
messages.create no longer takes the argument (loomflow passes it in
the request body there).
OpenAI
By string
from loomflow import Agent
agent = Agent("...", model="gpt-4o")
# OPENAI_API_KEY must be set.Install: pip install 'loomflow[openai]'.
OpenAI-compatible endpoints (Together, Fireworks, Anyscale, vLLM)
work via base_url=, either on OpenAIModel itself or on the client
you pass in.
GPT-6 and tools. The adapter calls Chat Completions, where some
GPT-6 models restrict tool calling. GPT-6 Astra and GPT-6.1 Sol can’t
call tools there at all: a request that offers tools raises
PermanentModelError (use them without tools). GPT-6 Sol and GPT-6
Luna take tools only with reasoning_effort="none", which the adapter
sends on those requests in place of your
effort, warning once if you set one.
Gateways (OpenRouter)
Gateways that name models vendor/model work with both adapters.
loomflow reads the vendor’s model out of the id
(anthropic/claude-haiku-5.5 is Claude Haiku 5.5, openai/gpt-5-mini
is GPT-5 mini), so native structured output, effort, the right output
token parameter, tokenizer, context window and price all match the
vendor’s model; the id is sent to the gateway unchanged. A :free
variant costs nothing. Effort needs the adapter that speaks the
vendor’s format: a Claude id on OpenAIModel drops it with a warning,
so use AnthropicModel (below) for Claude thinking. (Before 0.17.1,
gateway ids were priced at $0, so max_cost_usd budgets never
tripped; before 0.17.2 they also lost the vendor model’s capabilities.)
Through OpenRouter’s OpenAI-compatible API, with OpenAIModel:
from loomflow import Agent
from loomflow.model.openai import OpenAIModel
model = OpenAIModel(
"anthropic/claude-haiku-5.5", # or "openai/gpt-5-mini", ...
api_key=OPENROUTER_API_KEY,
base_url="https://openrouter.ai/api/v1",
)
agent = Agent("...", model=model)Through OpenRouter’s Anthropic-compatible Messages API, with the
native AnthropicModel (Claude’s own request format: content blocks,
output_config, thinking):
import anthropic
from loomflow import Agent
from loomflow.model.anthropic import AnthropicModel
client = anthropic.AsyncAnthropic(
base_url="https://openrouter.ai/api",
auth_token=OPENROUTER_API_KEY, # sent as a Bearer token
api_key=None,
max_retries=0, # loomflow's RetryPolicy retries
)
agent = Agent("...", model=AnthropicModel("anthropic/claude-haiku-5.5", client=client))Both setups were checked against the live API for loomflow 0.17.2: plain runs, tool calls, structured output, streaming and effort.
DeepSeek
DeepSeek speaks the OpenAI wire format, so deepseek-* specs resolve
to OpenAIModel pointed at https://api.deepseek.com, keyed by
DEEPSEEK_API_KEY (Secrets backend or env var). A missing key raises
ConfigError loudly rather than falling back to OPENAI_API_KEY.
Agent("...", model="deepseek-chat") # DEEPSEEK_API_KEY must be setThe V4 models (deepseek-v4-pro, deepseek-flash) are priced at
DeepSeek’s peak-hour rate, an upper bound: off-peak is half, so
cost_usd can overstate an off-peak bill but budget caps never
under-count.
Using DeepSeek through a reseller / gateway? Construct the adapter
directly with the gateway’s endpoint, and pass its actual rates so
cost_usd and budget caps stay honest. The third element is the
cached-input rate:
from loomflow.model.openai import OpenAIModel
model = OpenAIModel(
"deepseek-v4-flash",
api_key=GATEWAY_KEY,
base_url="https://gateway.example.com/v1",
cost_per_mtoken=(0.75, 3.00, 0.008), # (input, output, cached input) USD/MTok
)On failover gateways, also set prompt_caching={"enabled": True, "cache_key": ...}. See Prompt caching
for why the key is load-bearing there.
LiteLLM (~100 providers behind one adapter)
from loomflow import Agent
agent = Agent("...", model="mistral-large")
agent = Agent("...", model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0")
agent = Agent("...", model="ollama/llama3")Install: pip install 'loomflow[litellm]'.
Provider auth is whatever litellm expects. Typically env vars
matching the provider’s official client (AWS_PROFILE,
GOOGLE_APPLICATION_CREDENTIALS, MISTRAL_API_KEY, COHERE_API_KEY,
etc.). See LiteLLM’s docs for the full matrix.
Echo
agent = Agent("...", model="echo")
result = await agent.run("hi")
print(result.output) # "Echo: hi"EchoModel echoes the prompt as the assistant’s reply. No tool calls,
no streaming meaningful tokens. For smoke-testing the agent loop
without burning real tokens.
Scripted
from loomflow import Agent, ScriptedModel, ScriptedTurn
model = ScriptedModel(turns=[
ScriptedTurn(text="The answer is 42."),
ScriptedTurn(tool_calls=[...]),
ScriptedTurn(text="Done."),
])
agent = Agent("...", model=model)For tests with deterministic multi-turn scenarios. Each ScriptedTurn
becomes one model response; the framework consumes them in order.
Secrets handling
API keys resolve in this order inside every adapter:
- Explicit
api_key=argument on the adapter. secrets.lookup_sync(<ENV_VAR_NAME>)if aSecretsbackend is wired onAgent(tuning=Tuning(secrets=...)).os.environ[<ENV_VAR_NAME>]as the bare fallback.
For Vault / AWS Secrets Manager / 1Password, write a custom Secrets
adapter. See Secrets resolution.
Per-tenant model selection. Different tenants can use different
models. Pass an instance per call by constructing a different
Agent per tenant, or set up a router architecture where the
classifier picks a specialist with the right model.