# Model providers

Choose Anthropic, OpenAI, Azure OpenAI, Gemini, or OpenRouter.

Agent() uses Anthropic by default. Select a provider and an explicit model ID through AgentConfig; the tool and result APIs remain the same.

## Provider configuration

| Provider | Dependency | Environment variable |
| --- | --- | --- |
| anthropic | Included | ANTHROPIC_API_KEY |
| openai | harnessx[openai] | OPENAI_API_KEY |
| gemini | harnessx[gemini] | GEMINI_API_KEY |
| openrouter | harnessx[openrouter] | OPENROUTER_API_KEY |
| azure | harnessx[azure] | AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_API_KEY, OPENAI_API_VERSION |

Choose a model available to your account. The SDK does not maintain a live provider model catalog.

~~~python
import os
from harnessx import Agent, AgentConfig

agent = Agent(config=AgentConfig(
    provider="openrouter",
    model=os.environ["AGENT_MODEL"],
    system_prompt="You are a concise data-analysis assistant.",
))
~~~

The default config uses anthropic and claude-sonnet-4-6. Changing the provider does not automatically choose a compatible model; set both.

On Azure the `model` is the deployment name, and an endpoint plus API version are required. `AzureOpenAIProvider` also accepts `azure_endpoint`, `api_key`, `api_version`, and `azure_deployment` for explicit construction and injection. `"azure"` is the only Azure provider name; `make_provider("azure-openai")` was removed in 0.4.

## Transient failures and caching

Every provider retries transient failures (429, 5xx, timeouts, connection loss) according to `AgentConfig.retry`, a `RetryPolicy`: `attempts` (default 2; `1` disables retry), `backoff_seconds` (default 0.5, doubled per attempt), and `call_timeout_seconds` (default: sized to the reply budget, never below 300 s). One engine-level retry loop, bounded by `RetryPolicy.attempts`, classifies transient failures for model and tool calls alike. A retried streaming call may re-send text the caller has already shown; the run stream signals that with `ATTEMPT_RESET`. The 0.3 `llm_max_attempts`, `llm_retry_backoff_seconds`, and `model_timeout_seconds` fields still work but warn and are removed in 0.5.

Every provider also receives a prompt-cache hint for the stable prefix and translates it into its vendor's mechanism. See [Prompt caching](../prompt-caching/) for what each provider does and how to confirm hits.

## Run the provider example

~~~bash
uv run python -m examples.provider_chat \
  --provider openrouter \
  --model YOUR_MODEL_ID \
  --prompt "What is 40 * 10?"
~~~

Add --no-stream to use ordinary runs. Omit --prompt for a multi-turn conversation. The example includes a real calculator tool.

## Inject a provider

Agent accepts a provider implementing `LLMProvider` for custom adapters or scripted tests. Only two *different built-in* names contradict each other: `Agent(provider=OpenAIProvider(), config=AgentConfig(provider="anthropic"))` is rejected. A custom provider with any `name` is accepted with any config label. Non-Anthropic providers require explicit configuration and a model.

`Agent(client=...)` was removed in 0.4. Wrap the SDK client in a provider instead: `Agent(provider=AnthropicProvider(client=client))`.

A custom provider implements `create()`, optionally `stream()`, and `count_tokens(*, model, messages, system: str | None, tools)`; accept `system=None`, matching `create()`. The prompt-cache hint is only offered to a provider whose `create()` declares a `cache` parameter; a `**kwargs` signature does not count.

## Register a provider name

`register_provider(name, factory, *, replace=False)` adds a name that `AgentConfig(provider=name)` accepts and `make_provider(name)` builds by calling `factory(**kwargs)`. Built-in names cannot be replaced; registering an existing custom name again requires `replace=True`. `registered_providers()` lists the custom names and `unregister_provider(name)` removes one.

~~~python
from harnessx import Agent, AgentConfig, register_provider
from myapp.providers import ScriptedProvider  # your LLMProvider implementation

register_provider("scripted", ScriptedProvider)  # factory(**kwargs) -> LLMProvider

agent = Agent(config=AgentConfig(provider="scripted", model="fixture"))
~~~

Registration is process-local. A process that resumes a persisted session must register the same names before loading it, exactly as it must bind the same tools.

## Resource ownership

The agent closes a provider it constructs. An injected provider stays caller-owned; close it at the application boundary. Use async with agent to release resources reliably.

## Diagnose failures

A key being present does not establish that a service is reachable or a model is enabled for that account. Inspect RunResult.error and provider responses for authentication, rate, model availability, or unsupported parameter failures. Keep temperature unset unless the selected model accepts it.

## Add a decision provider

[Decisions with Jev](../decisions/) exposes a separate `DecisionProvider` contract for typed Choice, Score, and Noul assessments. Use `JevDecisionProvider` from `harnessx.decisions` alongside your agent, with the `jev` extra and `TYPESAFE_API_KEY` for live calls. It does not implement conversational `LLMProvider`, and `jev` is not an `AgentConfig.provider` value.

An application can use a decision to select an agent before a run or review its answer afterward. Decision token counts and costs remain separate from generative-model usage.
