diff --git a/apps/web/.env.example b/apps/web/.env.example index e2d571cee8..342da5366d 100644 --- a/apps/web/.env.example +++ b/apps/web/.env.example @@ -92,6 +92,11 @@ LOG_ZOD_ERRORS=true # ECONOMY_LLM_MODEL=llama-3.1-8b-instant # GROQ_API_KEY= +# --- Ollama (Local LLM) --- +# DEFAULT_LLM_PROVIDER=ollama +# OLLAMA_MODEL=llama3 +# OLLAMA_BASE_URL=http://localhost:11434/api + # ============================================================================= # Everything below is optional # ============================================================================= diff --git a/apps/web/env.ts b/apps/web/env.ts index d81c590990..296f90a4e6 100644 --- a/apps/web/env.ts +++ b/apps/web/env.ts @@ -59,6 +59,7 @@ export const env = createEnv({ OPENROUTER_API_KEY: z.string().optional(), AI_GATEWAY_API_KEY: z.string().optional(), OLLAMA_BASE_URL: z.string().optional(), + OLLAMA_MODEL: z.string().optional(), OPENAI_ZERO_DATA_RETENTION: z.coerce.boolean().optional().default(false), @@ -178,7 +179,6 @@ export const env = createEnv({ if (!value) return; return value.split(","); }), - NEXT_PUBLIC_OLLAMA_MODEL: z.string().optional(), NEXT_PUBLIC_DUB_REFER_DOMAIN: z.string().optional(), NEXT_PUBLIC_DISABLE_REFERRAL_SIGNATURE: z.coerce .boolean() @@ -236,7 +236,6 @@ export const env = createEnv({ NEXT_PUBLIC_AXIOM_DATASET: process.env.NEXT_PUBLIC_AXIOM_DATASET, NEXT_PUBLIC_AXIOM_TOKEN: process.env.NEXT_PUBLIC_AXIOM_TOKEN, NEXT_PUBLIC_LOG_SCOPES: process.env.NEXT_PUBLIC_LOG_SCOPES, - NEXT_PUBLIC_OLLAMA_MODEL: process.env.NEXT_PUBLIC_OLLAMA_MODEL, NEXT_PUBLIC_DUB_REFER_DOMAIN: process.env.NEXT_PUBLIC_DUB_REFER_DOMAIN, NEXT_PUBLIC_DISABLE_REFERRAL_SIGNATURE: process.env.NEXT_PUBLIC_DISABLE_REFERRAL_SIGNATURE, diff --git a/apps/web/package.json b/apps/web/package.json index d17f8f5987..7729c5ee32 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -133,7 +133,7 @@ "next-themes": "0.4.6", "nodemailer": "7.0.11", "nuqs": "2.8.2", - "ollama-ai-provider": "1.2.0", + "ollama-ai-provider-v2": "1.5.5", "openai": "6.9.1", "p-queue": "9.0.1", "p-retry": "7.1.0", diff --git a/apps/web/utils/actions/settings.validation.ts b/apps/web/utils/actions/settings.validation.ts index 013ce49952..32bc26635b 100644 --- a/apps/web/utils/actions/settings.validation.ts +++ b/apps/web/utils/actions/settings.validation.ts @@ -32,7 +32,6 @@ export const saveAiSettingsBody = z Provider.GOOGLE, Provider.GROQ, Provider.OPENROUTER, - ...(Provider.OLLAMA ? [Provider.OLLAMA] : []), ]), aiModel: z.string(), aiApiKey: z.string().optional(), diff --git a/apps/web/utils/llms/config.ts b/apps/web/utils/llms/config.ts index e4264c4a66..bdf6a50cbf 100644 --- a/apps/web/utils/llms/config.ts +++ b/apps/web/utils/llms/config.ts @@ -1,7 +1,5 @@ import { env } from "@/env"; -export const supportsOllama = !!env.NEXT_PUBLIC_OLLAMA_MODEL; - export const DEFAULT_PROVIDER = "DEFAULT"; export const Provider = { @@ -12,7 +10,7 @@ export const Provider = { GROQ: "groq", OPENROUTER: "openrouter", AI_GATEWAY: "aigateway", - ...(supportsOllama ? { OLLAMA: "ollama" } : {}), + ...(env.OLLAMA_MODEL ? { OLLAMA: "ollama" } : {}), }; export const providerOptions: { label: string; value: string }[] = [ @@ -23,7 +21,4 @@ export const providerOptions: { label: string; value: string }[] = [ { label: "Groq", value: Provider.GROQ }, { label: "OpenRouter", value: Provider.OPENROUTER }, { label: "AI Gateway", value: Provider.AI_GATEWAY }, - ...(supportsOllama && Provider.OLLAMA - ? [{ label: "Ollama", value: Provider.OLLAMA }] - : []), ]; diff --git a/apps/web/utils/llms/model.test.ts b/apps/web/utils/llms/model.test.ts index 65d854ba88..abbfff4b12 100644 --- a/apps/web/utils/llms/model.test.ts +++ b/apps/web/utils/llms/model.test.ts @@ -31,7 +31,7 @@ vi.mock("@openrouter/ai-sdk-provider", () => ({ })), })); -vi.mock("ollama-ai-provider", () => ({ +vi.mock("ollama-ai-provider-v2", () => ({ createOllama: vi.fn(() => (model: string) => ({ model })), })); @@ -50,8 +50,8 @@ vi.mock("@/env", () => ({ ANTHROPIC_API_KEY: "test-anthropic-key", GROQ_API_KEY: "test-groq-key", OPENROUTER_API_KEY: "test-openrouter-key", - OLLAMA_BASE_URL: "http://localhost:11434", - NEXT_PUBLIC_OLLAMA_MODEL: "llama3", + OLLAMA_BASE_URL: "http://localhost:11434/api", + OLLAMA_MODEL: "llama3", BEDROCK_REGION: "us-west-2", BEDROCK_ACCESS_KEY: "", BEDROCK_SECRET_KEY: "", @@ -153,18 +153,23 @@ describe("Models", () => { expect(result.model).toBeDefined(); }); - // it("should configure Ollama model correctly", () => { - // const userAi: UserAIFields = { - // aiApiKey: "user-api-key", - // aiProvider: Provider.OLLAMA!, - // aiModel: "llama3", - // }; + it("should configure Ollama model correctly via env vars", () => { + const userAi: UserAIFields = { + aiApiKey: null, + aiProvider: null, + aiModel: null, + }; - // const result = getModel(userAi); - // expect(result.provider).toBe(Provider.OLLAMA); - // expect(result.modelName).toBe("llama3"); - // expect(result.model).toBeDefined(); - // }); + vi.mocked(env).DEFAULT_LLM_PROVIDER = "ollama"; + vi.mocked(env).OLLAMA_MODEL = "llama3"; + vi.mocked(env).OLLAMA_BASE_URL = "http://localhost:11434/api"; + + const result = getModel(userAi); + expect(result.provider).toBe(Provider.OLLAMA); + expect(result.modelName).toBe("llama3"); + expect(result.model).toBeDefined(); + expect(result.backupModel).toBeNull(); // No backup for local Ollama + }); it("should configure Anthropic model correctly without Bedrock credentials", () => { const userAi: UserAIFields = { diff --git a/apps/web/utils/llms/model.ts b/apps/web/utils/llms/model.ts index d27072ec45..4b180578a8 100644 --- a/apps/web/utils/llms/model.ts +++ b/apps/web/utils/llms/model.ts @@ -6,7 +6,7 @@ import { createGoogleGenerativeAI } from "@ai-sdk/google"; import { createGroq } from "@ai-sdk/groq"; import { createOpenRouter } from "@openrouter/ai-sdk-provider"; import { createGateway } from "@ai-sdk/gateway"; -// import { createOllama } from "ollama-ai-provider"; +import { createOllama } from "ollama-ai-provider-v2"; import { env } from "@/env"; import { Provider } from "@/utils/llms/config"; import type { UserAIFields } from "@/utils/llms/types"; @@ -139,16 +139,17 @@ function selectModel( }; } case Provider.OLLAMA: { - throw new Error( - "Ollama is not supported. Revert to version v1.7.28 or older to use it.", - ); - // const modelName = aiModel || env.NEXT_PUBLIC_OLLAMA_MODEL; - // if (!modelName) throw new Error("Ollama model is not set"); - // return { - // provider: Provider.OLLAMA!, - // modelName, - // model: createOllama({ baseURL: env.OLLAMA_BASE_URL })(model), - // }; + const modelName = env.OLLAMA_MODEL; + const provider = Provider.OLLAMA; + if (!modelName) + throw new Error("OLLAMA_MODEL environment variable is not set"); + if (!provider) throw new Error("Provider.OLLAMA is not defined"); + return { + provider, + modelName, + model: createOllama({ baseURL: env.OLLAMA_BASE_URL })(modelName), + backupModel: null, + }; } case Provider.BEDROCK: { @@ -343,6 +344,7 @@ function getProviderApiKey(provider: string) { [Provider.GROQ]: env.GROQ_API_KEY, [Provider.OPENROUTER]: env.OPENROUTER_API_KEY, [Provider.AI_GATEWAY]: env.AI_GATEWAY_API_KEY, + ...(Provider.OLLAMA ? { [Provider.OLLAMA]: "ollama-local" } : {}), }; return providerApiKeys[provider]; diff --git a/docs/hosting/environment-variables.md b/docs/hosting/environment-variables.md index 2442db3033..66a4cb2aa0 100644 --- a/docs/hosting/environment-variables.md +++ b/docs/hosting/environment-variables.md @@ -60,7 +60,7 @@ cp apps/web/.env.example apps/web/.env | `BEDROCK_REGION` | No | AWS region for Bedrock | `us-west-2` | | **Ollama (Local LLM)** |||| | `OLLAMA_BASE_URL` | No | Ollama API endpoint (e.g., `http://localhost:11434/api`) | — | -| `NEXT_PUBLIC_OLLAMA_MODEL` | No | Model to use with Ollama | — | +| `OLLAMA_MODEL` | No | Model to use with Ollama (e.g., `llama3`) | — | | **Background Jobs (QStash)** |||| | `QSTASH_TOKEN` | No | QStash API token | — | | `QSTASH_CURRENT_SIGNING_KEY` | No | Current signing key for webhooks | — | diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 20288354e6..08a313d953 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -451,6 +451,9 @@ importers: ollama-ai-provider: specifier: 1.2.0 version: 1.2.0(zod@3.25.46) + ollama-ai-provider-v2: + specifier: 1.5.5 + version: 1.5.5(zod@3.25.46) openai: specifier: 6.9.1 version: 6.9.1(ws@8.18.3)(zod@3.25.46) @@ -9683,6 +9686,12 @@ packages: ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + ollama-ai-provider-v2@1.5.5: + resolution: {integrity: sha512-1YwTFdPjhPNHny/DrOHO+s8oVGGIE5Jib61/KnnjPRNWQhVVimrJJdaAX3e6nNRRDXrY5zbb9cfm2+yVvgsrqw==} + engines: {node: '>=18'} + peerDependencies: + zod: ^4.0.16 + ollama-ai-provider@1.2.0: resolution: {integrity: sha512-jTNFruwe3O/ruJeppI/quoOUxG7NA6blG3ZyQj3lei4+NnJo7bi3eIRWqlVpRlu/mbzbFXeJSBuYQWF6pzGKww==} engines: {node: '>=18'} @@ -23096,6 +23105,12 @@ snapshots: ohash@2.0.11: {} + ollama-ai-provider-v2@1.5.5(zod@3.25.46): + dependencies: + '@ai-sdk/provider': 2.0.0 + '@ai-sdk/provider-utils': 3.0.18(zod@3.25.46) + zod: 3.25.46 + ollama-ai-provider@1.2.0(zod@3.25.46): dependencies: '@ai-sdk/provider': 1.1.3 diff --git a/turbo.json b/turbo.json index 0019228daf..989333442e 100644 --- a/turbo.json +++ b/turbo.json @@ -42,6 +42,7 @@ "OPENROUTER_API_KEY", "AI_GATEWAY_API_KEY", "OLLAMA_BASE_URL", + "OLLAMA_MODEL", "UPSTASH_REDIS_URL", "UPSTASH_REDIS_TOKEN", @@ -126,7 +127,6 @@ "NEXT_PUBLIC_WELCOME_UPGRADE_ENABLED", "NEXT_PUBLIC_AXIOM_DATASET", "NEXT_PUBLIC_AXIOM_TOKEN", - "NEXT_PUBLIC_OLLAMA_MODEL", "NEXT_PUBLIC_DUB_REFER_DOMAIN", "NEXT_PUBLIC_USE_AEONIK_FONT" ], diff --git a/version.txt b/version.txt index 649e8a7fbb..749fa055c4 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -v2.21.59 \ No newline at end of file +v2.21.60