Appearance
ADR-011 — LLM Provider: OpenRouter + BYOK
Status: Accepted
Date: 2026-05-21
Context
The support agent needs to call an LLM. Options: direct Anthropic API, direct OpenAI API, or an aggregator like OpenRouter.
The founder has a Claude Code subscription (OAuth-based), not an Anthropic API key. Buy2give.store has a ZAI API key for GLM and Kimi models.
Decision
OpenRouter as the sole LLM transport. One LlmClientContract + one OpenRouterLlmClient. No per-provider adapters.
BYOK (Bring Your Own Key): each tenant stores their OpenRouter API key in agent_config (encrypted at rest, AES-256).
Model is a plain string in agent_config — the operator picks any OpenRouter model slug. No model enum, no validation beyond "non-empty string".
json
{
"provider": "openrouter",
"model": "zhipu/glm-4-flash",
"api_key": "<encrypted>",
"history_window": 10,
"store_name": "buy2give.store",
"store_tone": "friendly, professional"
}Rationale
- OpenRouter covers ZAI GLM, Kimi, Hermes, Claude, GPT-4o via a single OpenAI-compatible API — no per-provider client code
- Model spend stays with the customer (BYOK) — Opsome never bills for tokens
- Plain string model removes a code change every time a new model is added to OpenRouter
store_nameandstore_toneare injected into the system prompt, enabling per-tenant voice without a code deploy
Consequences
LlmClientContractexposes a singlechat(array $messages, array $options = []): AgentResponseDTOmethodOpenRouterLlmClientreadsbase_url,api_key, andmodelfrom the tenant'sagent_configat call time- Model cost monitoring is the tenant's responsibility (OpenRouter dashboard)
- If a tenant's model does not support structured output / JSON mode, the client falls back to prompt-based JSON enforcement