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.

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 for what each provider does and how to confirm hits.

Run the provider example

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.

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 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.