Skip to content

Commit

Permalink
Merge pull request #365 from elie222/openrouter
Browse files Browse the repository at this point in the history
Add support for OpenRouter
  • Loading branch information
elie222 authored Mar 1, 2025
2 parents 69f29e9 + 72d0a21 commit b6f3788
Show file tree
Hide file tree
Showing 10 changed files with 75 additions and 13 deletions.
27 changes: 20 additions & 7 deletions apps/web/app/(app)/settings/ModelSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,10 @@ function ModelSectionForm(props: {
const aiModel = watch("aiModel");

// if model not part of provider then switch to default model for provider
if (!modelOptions[aiProvider].find((o) => o.value === aiModel)) {
if (
modelOptions[aiProvider].length &&
!modelOptions[aiProvider].find((o) => o.value === aiModel)
) {
setValue("aiModel", getDefaultModel(aiProvider));
}
}, [aiProvider, setValue, watch]);
Expand Down Expand Up @@ -133,12 +136,22 @@ function ModelSectionForm(props: {
error={errors.aiProvider}
/>

<Select
label="Model"
options={modelSelectOptions}
{...register("aiModel")}
error={errors.aiModel}
/>
{modelSelectOptions.length ? (
<Select
label="Model"
options={modelSelectOptions}
{...register("aiModel")}
error={errors.aiModel}
/>
) : (
<Input
type="text"
name="aiModel"
label="Model"
registerProps={register("aiModel")}
error={errors.aiModel}
/>
)}

<Input
type="password"
Expand Down
7 changes: 7 additions & 0 deletions apps/web/app/api/user/settings/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ async function saveAISettings(options: SaveSettingsBody) {
function getModel() {
switch (aiProvider) {
case Provider.OPEN_AI:
if (!options.aiApiKey)
throw new SafeError("OpenAI API key is required");

return options.aiModel;
case Provider.ANTHROPIC:
if (options.aiApiKey) {
Expand All @@ -32,6 +35,10 @@ async function saveAISettings(options: SaveSettingsBody) {
return options.aiModel || Model.GEMINI_2_0_FLASH;
case Provider.GROQ:
return options.aiModel || Model.GROQ_LLAMA_3_3_70B;
case Provider.OPENROUTER:
if (!options.aiApiKey)
throw new SafeError("OpenRouter API key is required");
return options.aiModel;
case Provider.OLLAMA:
return Model.OLLAMA;
default:
Expand Down
1 change: 1 addition & 0 deletions apps/web/app/api/user/settings/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const saveSettingsBody = z
Provider.OPEN_AI,
Provider.GOOGLE,
Provider.GROQ,
Provider.OPENROUTER,
...(Provider.OLLAMA ? [Provider.OLLAMA] : []),
]),
aiModel: z.string(),
Expand Down
1 change: 1 addition & 0 deletions apps/web/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const env = createEnv({
BEDROCK_REGION: z.string().default("us-west-2"),
GOOGLE_API_KEY: z.string().optional(),
GROQ_API_KEY: z.string().optional(),
OPENROUTER_API_KEY: z.string().optional(),
UPSTASH_REDIS_URL: z.string().optional(),
UPSTASH_REDIS_TOKEN: z.string().optional(),
OLLAMA_BASE_URL: z.string().optional(),
Expand Down
1 change: 1 addition & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@mdx-js/react": "^3.1.0",
"@next/mdx": "^14.2.15",
"@next/third-parties": "^14.2.15",
"@openrouter/ai-sdk-provider": "0.4.2",
"@portabletext/react": "^3.2.0",
"@prisma/client": "6.3.1",
"@radix-ui/react-avatar": "^1.1.2",
Expand Down
10 changes: 4 additions & 6 deletions apps/web/utils/cold-email/is-cold-email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@ import { hasPreviousEmailsFromSenderOrDomain } from "@/utils/gmail/message";

const logger = createScopedLogger("ai-cold-email");

const aiResponseSchema = z.object({
coldEmail: z.boolean().nullish(),
reason: z.string().nullish(),
});

type ColdEmailBlockerReason = "hasPreviousEmail" | "ai" | "ai-already-labeled";

export async function isColdEmail({
Expand Down Expand Up @@ -144,7 +139,10 @@ ${stringifyEmail(email, 500)}
userAi: user,
system,
prompt,
schema: aiResponseSchema,
schema: z.object({
coldEmail: z.boolean(),
reason: z.string(),
}),
userEmail: user.email || "",
usageLabel: "Cold email check",
});
Expand Down
3 changes: 3 additions & 0 deletions apps/web/utils/llms/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const Provider = {
ANTHROPIC: "anthropic",
GOOGLE: "google",
GROQ: "groq",
OPENROUTER: "openrouter",
...(supportsOllama ? { OLLAMA: "ollama" } : {}),
};

Expand All @@ -30,6 +31,7 @@ export const providerOptions: { label: string; value: string }[] = [
{ label: "OpenAI", value: Provider.OPEN_AI },
{ label: "Google", value: Provider.GOOGLE },
{ label: "Groq", value: Provider.GROQ },
{ label: "OpenRouter", value: Provider.OPENROUTER },
...(supportsOllama && Provider.OLLAMA
? [{ label: "Ollama", value: Provider.OLLAMA }]
: []),
Expand Down Expand Up @@ -71,6 +73,7 @@ export const modelOptions: Record<string, { label: string; value: string }[]> =
value: Model.GROQ_LLAMA_3_3_70B,
},
],
[Provider.OPENROUTER]: [],
...(Provider.OLLAMA && Model.OLLAMA
? {
[Provider.OLLAMA]: [{ label: "Ollama", value: Model.OLLAMA }],
Expand Down
20 changes: 20 additions & 0 deletions apps/web/utils/llms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { createAnthropic } from "@ai-sdk/anthropic";
import { createAmazonBedrock } from "@ai-sdk/amazon-bedrock";
import { createGoogleGenerativeAI } from "@ai-sdk/google";
import { createGroq } from "@ai-sdk/groq";
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
import { createOllama } from "ollama-ai-provider";
import { env } from "@/env";
import { saveAiUsage } from "@/utils/usage";
Expand All @@ -34,6 +35,7 @@ function getDefaultProvider(): string {
if (env.BEDROCK_ACCESS_KEY) return Provider.ANTHROPIC;
if (env.ANTHROPIC_API_KEY) return Provider.ANTHROPIC;
if (env.OPENAI_API_KEY) return Provider.OPEN_AI;
if (env.OPENROUTER_API_KEY) return Provider.OPENROUTER;
if (env.GOOGLE_API_KEY) return Provider.GOOGLE;
if (env.GROQ_API_KEY) return Provider.GROQ;
if (supportsOllama && env.OLLAMA_BASE_URL) return Provider.OLLAMA!;
Expand Down Expand Up @@ -107,6 +109,24 @@ function getModel({ aiProvider, aiModel, aiApiKey }: UserAIFields) {
};
}

if (provider === Provider.OPENROUTER) {
if (!aiApiKey && !env.OPENROUTER_API_KEY)
throw new Error("OpenRouter API key is not set");
if (!aiModel) throw new Error("OpenRouter model is not set");

const openrouter = createOpenRouter({
apiKey: aiApiKey || env.OPENROUTER_API_KEY,
});

const chatModel = openrouter.chat(aiModel);

return {
provider: Provider.OPENROUTER,
model: aiModel,
llmModel: chatModel,
};
}

if (provider === Provider.OLLAMA && env.NEXT_PUBLIC_OLLAMA_MODEL) {
return {
provider: Provider.OLLAMA,
Expand Down
15 changes: 15 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
"BEDROCK_ACCESS_KEY",
"BEDROCK_SECRET_KEY",
"BEDROCK_REGION",
"GOOGLE_API_KEY",
"GROQ_API_KEY",
"OPENROUTER_API_KEY",
"OLLAMA_BASE_URL",
"UPSTASH_REDIS_URL",
"UPSTASH_REDIS_TOKEN",
Expand Down

1 comment on commit b6f3788

@vercel
Copy link

@vercel vercel bot commented on b6f3788 Mar 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.