The AI system uses LangChain for multi-provider support with a modular agent architecture.

Multi-LLM Support

The AI system routes every chat and embedding request through one server-only LlmClient. Set AI_LLM_TRANSPORT=direct to call OpenAI, Anthropic, and Google with their native credentials, or openrouter to use one OpenRouter key and the canonical gateway model mappings in config/ai.ts.

File Structure

config/
└── ai.ts                 # Models, providers, pricing configuration

lib/ai/
├── index.ts              # Main exports
├── llm-client.ts         # Sole external AI boundary (direct + OpenRouter)
├── circuit-breaker.ts    # Upstash/in-memory closed-open-half-open route state
├── providers/
│   └── index.ts          # Model/provider discovery only; no external calls
├── agents/
│   ├── index.ts          # Agent registry
│   ├── types.ts          # Agent interfaces
│   ├── base-agent.ts     # Base class for all agents
│   ├── chat/index.ts     # Chat agent
│   ├── code-assistant/   # Code assistant agent
│   ├── translator/       # Translator agent
│   └── writer/           # Writer agent
├── cache/
│   └── index.ts          # Prompt caching utilities — applied to the Anthropic system prompt in base-agent.ts (default `5m`, optional `1h` TTL via `AnthropicCacheTTL`)
└── langgraph.ts          # Message building utilities

app/api/ai/
└── stream/route.ts       # SSE streaming endpoint

Supported Providers

Provider Models Environment Variable
OpenAI GPT-5.6 Sol, GPT-5.6 Terra, GPT-5.6 Luna OPENAI_API_KEY
Anthropic Claude Opus 5, Sonnet 5, Haiku 4.5 (Fable 5 excluded because it is not ZDR-eligible) ANTHROPIC_API_KEY
Google Gemini 3.6 Flash, 3.5 Flash-Lite, 3.1 Pro Preview/Flash-Lite, and supported 2.5 Pro/Flash/Flash-Lite GOOGLE_AI_API_KEY
OpenRouter gateway Configured OpenAI, Anthropic, and Google mappings with ZDR-capable routing AI_LLM_TRANSPORT=openrouter + OPENROUTER_API_KEY

Model Configuration

Models are defined in config/ai.ts with current direct/OpenRouter pricing, capabilities, sampling compatibility, and a canonical openRouterModelId. The curated text catalogue was last reviewed on 2026-08-18 against official vendor documentation and OpenRouter’s live model endpoint. Retired entries and modality-specific image-generation endpoints are not selectable chat models. Update this single catalogue when adding a route; do not create a second gateway-only model list.

Unified LLM boundary and ZDR

lib/ai/llm-client.ts is the only module allowed to construct provider SDKs or call chat/embedding endpoints. In OpenRouter mode every request carries provider.zdr=true, data_collection="deny", require_parameters=true, and attribution headers. The fixture used by pnpm run qa:local:private rejects a request if any of those fields is absent. This routing control is not a substitute for reviewing OpenRouter and the selected upstream provider in your DPA.

Direct mode cannot manufacture account-level ZDR by code. Direct OpenAI requests set store=false and the LangChain ZDR flag, but the operator must obtain and verify the provider's organization/project retention controls. Anthropic and Google retention likewise follows the configured API account and contract.

Circuits, retries, and model fallback

The centralized client uses the existing Upstash connection for shared production circuit state. Five transient failures in 30 seconds open one transport/model route for 30 seconds; after cooldown, one atomically leased half-open probe decides whether to close or reopen it. Store errors are redacted, rate-limited, and fail open so Redis cannot become a global AI availability dependency. Development and deterministic tests use the same state machine in process memory.

Direct vendors receive one cancellation-aware jittered retry, while OpenRouter receives no application retry because the gateway already routes among eligible providers for the same model. Before any content is emitted, an open circuit or transient failure may move once to the configured cross-provider fallback. Agent allow-lists still apply. Any partial output makes the failure terminal, and embeddings never cross models because mixing vector spaces corrupts similarity search. ai_requests.metadata.routing stores only bounded model/route/circuit outcome summaries; prompts, responses, identities, secrets, and raw provider bodies are excluded.

Built-in Agents

Five agents ship out of the box — Chat (default), Code Assistant, Translator, Writer, and Knowledge Base (RAG) — each defined in lib/ai/agents/ and registered in lib/ai/agents/index.ts. All extend BaseAgent; specialized agents may declare an allowedModels list that also constrains fallback. Credits are charged uniformly across agents — 1 credit per LLM token (input + output), deducted once post-stream against provider-reported usage from the model that actually completed the answer.

For the full architecture, registry pattern, custom-agent recipe, and advanced override hooks (like the RAG agent's prepareRAGContext() pre-fetch), see Built-in Agents.

Chat Interface

A full-featured AI conversation page lives at /private-dashboard/chat — Server Component shell with a Client Component (components/private/chat-interface.tsx) for streaming, agent switching, and session history. Data reads come from core/chat/queries.ts; writes go through core/chat/mutations.ts. Sessions are persisted in chat_sessions, messages in chat_messages, both RLS-scoped by membership.

For the file structure, hook API (useChat), session management rules, SSE handling, and pagination details, see Chat Interface.

RAG Document Chat

The Knowledge Base agent lets users upload TXT/MD/PDF files at /private-dashboard/documents, chunks and embeds them through the same selected LlmClient transport, and answers questions by retrieving relevant chunks from a pgvector HNSW index. Similarity search uses a membership-enforced match_document_chunks_text SECURITY DEFINER RPC; the match cutoff is configurable via embeddingConfig.ragMatchThreshold (default 0.1 in config/ai.ts). Credits are deducted 1:1 with the embedding tokens consumed.

For the embedding-model table, credit math, RPC details, API routes, and the Knowledge Base page walkthrough, see RAG & Documents.

Config KeyDefaultDescription
embeddingConfig.defaultModeltext-embedding-3-smallDefault embedding model for new documents
embeddingConfig.ragMatchThreshold0.1Cosine-distance cutoff for RAG chunk retrieval (pgvector <=>). Single source of truth — both core/documents/ reads and the rag agent must read this instead of inlining a literal.
aiConfig.minCreditsRequired100Minimum balance for the pre-flight check before any LLM call (chat or RAG)