How Open SEO Configures AI/LLM Integration with OpenRouter: A Complete Technical Guide
Open SEO configures its AI/LLM integration with OpenRouter using a three-layer architecture that reads environment variables through runtime-env.ts, constructs a configured model instance via buildChatAgentModel in openrouter.ts, and consumes the model in chat agents with usage tracking and billing.
The open-source Open SEO repository (every-app/open-seo) powers its intelligent chat assistants—including the onboarding "Sam" assistant and the SAM research agent—through OpenRouter's unified API. This integration leverages the official @openrouter/ai-sdk-provider package to enable flexible model selection, zero-data-retention policies, and accurate per-request cost tracking.
Runtime Environment Configuration
The integration begins with secure secret management in src/server/lib/runtime-env.ts. This module provides typed helpers that validate required environment variables at runtime.
The getRequiredEnvValue function throws an error if the OpenRouter API key is missing, while getOptionalEnvValue allows the model identifier to fall back to defaults:
const apiKey = await getRequiredEnvValue("OPENROUTER_API_KEY");
const modelId = await getOptionalEnvValue("OPENROUTER_MODEL");
These values are declared in src/env.d.ts for TypeScript support within the Cloudflare Worker environment, ensuring type safety across both production deployments and local development using process.env.
OpenRouter Model Factory
The core configuration lives in src/server/lib/openrouter.ts, which exports buildChatAgentModel to instantiate a Vercel AI SDK-compatible LanguageModelV3 object. This factory function centralizes all OpenRouter-specific settings.
Default Model and Environment Overrides
By default, Open SEO uses the minimax/minimax-m3 model. Developers can override this by setting the optional OPENROUTER_MODEL environment variable without modifying source code:
const DEFAULT_CHAT_AGENT_MODEL = "minimax/minimax-m3";
export function buildChatAgentModel(
apiKey: string,
modelId?: string,
): LanguageModelV3 {
return createOpenRouter({ apiKey })(modelId ?? DEFAULT_CHAT_AGENT_MODEL, {
// Configuration options...
});
}
Usage Tracking and Cost Calculation
The factory enables usage: { include: true } to force OpenRouter to return the exact USD cost of each generation. This metadata is later extracted via openRouterCostUsd in src/server/lib/chatAgent.ts to debit the organization's credit pool.
Reasoning and Provider Configuration
The model configuration implements several enterprise-grade policies:
- Reasoning Channel:
reasoning: { effort: "medium" }generates chain-of-thought output that remains separate from the visible user-facing answer - Provider Fallbacks:
order: ["together", "atlas-cloud/fp8"]specifies priority providers if the primary fails - Zero-Data-Retention:
zdr: trueensures prompts are never stored on the provider side - Fallback Allowance:
allow_fallbacks: trueprevents hard outages during provider throttling
return createOpenRouter({ apiKey })(modelId ?? DEFAULT_CHAT_AGENT_MODEL, {
usage: { include: true },
reasoning: { effort: "medium" },
provider: {
order: ["together", "atlas-cloud/fp8"],
zdr: true,
allow_fallbacks: true,
},
});
Chat Agent Implementation
Both the OnboardingChatAgent and SamChatAgent consume the OpenRouter integration through getChatAgentModel. These agents in src/server/features/onboarding/OnboardingChatAgent.ts and src/server/features/sam/SamChatAgent.ts stream responses using the Vercel AI SDK's streamText function.
After each generation completes, the agents extract billing data from providerMetadata.openrouter.usage.cost and track spending:
const model = await getChatAgentModel();
const result = streamText({
model,
system: buildSystemPrompt(project.domain),
messages: await convertToModelMessages(this.messages),
maxOutputTokens: 4000,
onFinish: async (event) => {
const costUsd = event.steps.reduce(
(sum, step) => sum + openRouterCostUsd(step.providerMetadata),
0,
);
await trackUsageCreditSpend({ /* billing details */ });
},
});
Complete Implementation Example
To use the OpenRouter integration in your own feature implementation:
// Asynchronous initialization (recommended for serverless)
import { getChatAgentModel } from "@/server/lib/openrouter";
async function generateSeoAnalysis(messages: any[]) {
const model = await getChatAgentModel();
const { text } = await streamText({
model,
messages,
maxOutputTokens: 2000,
});
return text;
}
// Synchronous variant when credentials are cached
import { buildChatAgentModel } from "@/server/lib/openrouter";
const model = buildChatAgentModel(
process.env.OPENROUTER_API_KEY!,
"anthropic/claude-3.5-sonnet" // Optional override
);
Summary
Open SEO's AI/LLM integration with OpenRouter provides a production-ready foundation for conversational AI features:
- Flexible model selection via the
OPENROUTER_MODELenvironment variable without code changes - Strict privacy controls through Zero-Data-Retention (
zdr) and configurable reasoning channels - High availability via provider fallbacks across Together AI and Atlas Cloud
- Accurate billing by extracting real-time USD costs from OpenRouter's usage metadata
- Type-safe configuration using Cloudflare Worker environment types in
src/env.d.ts
Frequently Asked Questions
What environment variables are required to configure OpenRouter in Open SEO?
You must set OPENROUTER_API_KEY as a required variable in your Cloudflare Worker environment or .env file. Optionally, set OPENROUTER_MODEL to override the default minimax/minimax-m3 model with any slug supported by OpenRouter.
How does Open SEO handle OpenRouter outages or rate limiting?
The configuration in src/server/lib/openrouter.ts sets allow_fallbacks: true and defines a provider order array that falls back to together and atlas-cloud/fp8 if the primary provider fails. This ensures chat agents remain operational during individual provider outages.
How is usage billing calculated for AI-generated content?
OpenRouter returns exact USD costs in the response metadata when usage: { include: true } is configured. The openRouterCostUsd helper in src/server/lib/chatAgent.ts aggregates costs across streaming steps, and the trackUsageCreditSpend function debits the organization's credit pool accordingly.
Can I change the AI model without redeploying the application?
Yes. Because buildChatAgentModel accepts an optional modelId parameter that defaults to the OPENROUTER_MODEL environment variable, you can switch models instantly by updating the environment variable. If no override is provided, the system falls back to minimax/minimax-m3.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →