Skip to main content

LLM providers

Wuwe normalizes provider configuration, requests, responses, streaming events, tool calls, usage, retries, and errors behind llm_client.

llm_request::max_output_tokens is the common output limit. Built-in OpenAI-compatible, Anthropic, Gemini, and Ollama clients translate it to their protocol-specific request field. llm_usage distinguishes prompt, completion, cached-prompt, and reasoning tokens. calculate_llm_cost() applies explicit per-million-token pricing and falls back to the normal input/output rate when a separate cache or reasoning rate is not supplied.

Common generation controls include stop_sequences, deterministic seed, json_schema_output, and cache_mode. llm_client::capabilities() and the provider registry declare which controls the configured adapter supports. Unsupported controls return llm_error_code::unsupported_capability before network dispatch; they are never silently dropped. Malformed schemas, empty stop strings, conflicting legacy/structured response formats, and invalid output limits return llm_error_code::invalid_request.

OpenAI, Gemini, and Ollama presets declare JSON Schema output and deterministic seed support. Anthropic declares stop-sequence support. Generic OpenAICompatible metadata is deliberately conservative; a custom endpoint can set llm_client_config::capabilities_override only for features it actually implements. Explicit cache enable/disable currently fails closed unless a custom adapter declares and implements cache control.

The base llm_client reports an undeclared capability contract so existing custom clients remain usable after recompilation. Wuwe still applies request-structure validation, but feature rejection is enforced only when an adapter returns a declared contract. Custom production clients should override capabilities(); built-in and scripted clients always return declared contracts.

llm_request::context_budget optionally constrains the full request before each agent model call. See Context budget. Direct low-level llm_client::complete() calls do not rewrite the request; hosts using that boundary can call context_budget_manager::fit() explicitly.

Built-in providers

Provider IDProtocolDefault credential
OpenAIOpenAI-compatible chat completionsOPENAI_API_KEY
OpenAICompatibleConfigurable OpenAI-compatible endpointOPENAI_API_KEY
OpenRouterOpenAI-compatibleOPENROUTER_API_KEY, then OPENAI_API_KEY
AnthropicAnthropic MessagesANTHROPIC_API_KEY
GeminiGemini generateContentGEMINI_API_KEY, then GOOGLE_API_KEY
OllamaOllama chatNo API key required by default
DeepSeekOpenAI-compatibleDEEPSEEK_API_KEY, then OPENAI_API_KEY
DashScopeOpenAI-compatibleDASHSCOPE_API_KEY, QWEN_API_KEY, then OPENAI_API_KEY
QwenOpenAI-compatibleQWEN_API_KEY, DASHSCOPE_API_KEY, then OPENAI_API_KEY
ZhipuOpenAI-compatibleZHIPU_API_KEY, then BIGMODEL_API_KEY

OpenAICompatible requires a base_url. Other presets supply a default endpoint that can still be overridden.

Create a client

wuwe::llm_config config {
.model = "gpt-4.1-mini",
.timeout = 30000,
};

auto client = wuwe::make_llm_client("OpenAI", std::move(config));
const auto response = client->complete("Summarize the input.");

By default, normalization fills the provider endpoint and loads the first available credential from the provider's environment-variable list. Set load_api_key_from_environment = false when the host supplies credentials through another secret-management path.

For a custom compatible endpoint:

wuwe::llm_config config {
.base_url = "https://llm.example.com",
.api_key = token,
.model = "company-model",
};

auto client = wuwe::make_llm_client(
"OpenAICompatible", std::move(config));

Registry and capabilities

Use list_llm_providers(), find_llm_provider(), and make_default_llm_config() to build configuration UIs or validate deployment settings. llm_provider_info reports the protocol, endpoint defaults, credential names, and declared support for streaming, tools, tool choice, JSON output/schema, stop sequences, deterministic seeds, explicit cache control, reasoning summaries, multimodal input, and local runtimes.

Capabilities describe the Wuwe adapter and protocol path. A specific model or upstream account can impose narrower limits, so applications should still handle provider errors and unsupported parameters.

llm_provider_registry is the built-in provider catalog. It is intentionally separate from agent::llm::llm_client_registry, which owns configured runtime client instances. A dispatching_llm_client selects a runtime binding from llm_request::provider, a configured default, or an unambiguous single binding. Runtime binding IDs are case-sensitive and should be stable deployment identifiers, not credentials or endpoint URLs. The selected provider's own capability contract is checked before it receives the request. See Resource-aware routing for model-and-provider selection. Dispatcher graphs may be composed, but request lineage rejects recursive graphs even when a wrapper forwards the request through a worker thread. Independent requests started by callbacks are not treated as recursion. Exceptions raised by stream consumers remain consumer failures and do not produce a false backend-failure telemetry event. Dispatch observers may start independent model requests without recursively observing the events produced by those requests.

Configuration

llm_client_config includes:

  • base URL and chat path;
  • API key policy and environment loading;
  • model;
  • request timeout;
  • total, connect, first-event, and idle streaming timeouts;
  • retry count, bounded exponential backoff, jitter, and Retry-After policy;
  • optional OpenRouter referer and application title.

See Resource-aware routing, Streaming, Typed tools, and HTTP backends.

Provider resilience

resilient_llm_client wraps one or more llm_client backends with a fixed-window local rate limit, bounded retry policy, circuit breaker, and ordered model fallback. Fallback is limited to retryable provider failures; authentication, invalid requests, cancellation, and other non-recovery errors are returned directly. Streaming requests may retry or fall back only before any user-visible content, reasoning, or tool-call delta has been emitted.

Built-in clients already have a local retry setting. When they are placed behind resilient_llm_client, set the underlying max_retries to zero so the wrapper owns retry accounting and avoids multiplicative retries. Use the resilience observer for backend, retry, circuit, and fallback telemetry.

scripted_llm_client is a public, thread-safe deterministic test client. Each scripted step can validate the request, return a response or stream events, and records consumed requests for assertions.