Skip to content

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_name and store_tone are injected into the system prompt, enabling per-tenant voice without a code deploy

Consequences

  • LlmClientContract exposes a single chat(array $messages, array $options = []): AgentResponseDTO method
  • OpenRouterLlmClient reads base_url, api_key, and model from the tenant's agent_config at 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