Skip to content

Commit c0206dc

Browse files
committed
refactor(inference): modular multi-provider AI architecture
Replace monolithic inference module with provider-specific clients supporting OpenAI, Anthropic Claude, Google Gemini, and Ollama. Each provider now has native SDK integration with structured output support via JSON schema. Architecture: - Add packages/shared/inference/ with modular provider clients - Add InferenceClientFactory and EmbeddingClientFactory for provider selection - Separate inference and embedding providers (Anthropic lacks embeddings API) New providers: - OpenAI: Chat Completions + Responses API for GPT-5/o-series reasoning - Google: Gemini 2.5/3.x with JSON schema, batch embeddings - Anthropic: Claude 4.5 with structured outputs (beta), vision support Configuration: - Add INFERENCE_PROVIDER for explicit provider selection (auto-detects if unset) - Add ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL - Add GEMINI_API_KEY, GEMINI_BASE_URL - Add EMBEDDING_PROVIDER for separate embedding configuration - Add OPENAI_USE_RESPONSES_API, OPENAI_REASONING_EFFORT for GPT-5 - Add reasoning effort support for o-series models (o1, o3, o4) - Auto-detect max_completion_tokens for GPT-5/o-series models - Change default models to gpt-5-mini Breaking changes: - Remove INFERENCE_SUPPORTS_STRUCTURED_OUTPUT (use INFERENCE_OUTPUT_SCHEMA) - Remove INFERENCE_USE_MAX_COMPLETION_TOKENS (now auto-detected) Other: - Add ProviderIndicator component showing active provider/models in settings - Add @anthropic-ai/sdk and @google/genai dependencies - Upgrade zod 3.24.2 -> 3.25.0 for Anthropic SDK compatibility - Add 106 unit tests and 19 live API integration tests - Rewrite AI provider documentation with model tables and examples - Add translations for AI provider settings UI to all 31 locales
1 parent aa7a81e commit c0206dc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+4506
-594
lines changed

apps/browser-extension/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"superjson": "^2.2.1",
3939
"tailwind-merge": "^2.2.1",
4040
"tailwindcss-animate": "^1.0.7",
41-
"zod": "^3.24.2"
41+
"zod": "^3.25.0"
4242
},
4343
"devDependencies": {
4444
"@crxjs/vite-plugin": "2.2.0",

apps/mcp/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,6 @@
4141
"@karakeep/sdk": "workspace:*",
4242
"@modelcontextprotocol/sdk": "^1.9.0",
4343
"turndown": "^7.2.0",
44-
"zod": "^3.24.2"
44+
"zod": "^3.25.0"
4545
}
4646
}

apps/mobile/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
"react-native-webview": "^13.13.5",
6464
"sonner-native": "^0.22.2",
6565
"tailwind-merge": "^2.2.1",
66-
"zod": "^3.24.2",
66+
"zod": "^3.25.0",
6767
"zustand": "^5.0.5"
6868
},
6969
"devDependencies": {

apps/web/components/settings/AISettings.tsx

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import { api } from "@/lib/trpc";
4444
import { useUserSettings } from "@/lib/userSettings";
4545
import { cn } from "@/lib/utils";
4646
import { zodResolver } from "@hookform/resolvers/zod";
47-
import { Info, Plus, Save, Trash2 } from "lucide-react";
47+
import { Bot, Cpu, Image, Info, Plus, Save, Trash2 } from "lucide-react";
4848
import { Controller, useForm } from "react-hook-form";
4949
import { z } from "zod";
5050

@@ -82,6 +82,94 @@ function SettingsSection({
8282
);
8383
}
8484

85+
const providerDisplayNames: Record<string, string> = {
86+
openai: "OpenAI",
87+
anthropic: "Anthropic Claude",
88+
google: "Google Gemini",
89+
ollama: "Ollama (Local)",
90+
};
91+
92+
function ProviderInfoItem({
93+
icon,
94+
label,
95+
value,
96+
}: {
97+
icon: React.ReactNode;
98+
label: string;
99+
value: string;
100+
}) {
101+
return (
102+
<div className="flex items-center gap-3">
103+
<div
104+
className="flex h-8 w-8 items-center justify-center rounded-md bg-muted"
105+
aria-hidden="true"
106+
>
107+
{icon}
108+
</div>
109+
<div className="flex flex-col">
110+
<span className="text-xs text-muted-foreground">{label}</span>
111+
<span className="text-sm font-medium">{value}</span>
112+
</div>
113+
</div>
114+
);
115+
}
116+
117+
export function ProviderIndicator() {
118+
const { t } = useTranslation();
119+
const clientConfig = useClientConfig();
120+
121+
const { provider, textModel, imageModel, embeddingProvider, embeddingModel } =
122+
clientConfig.inference;
123+
124+
if (!provider) {
125+
return (
126+
<SettingsSection title={t("settings.ai.provider_status")}>
127+
<div className="flex items-start gap-2 rounded-md bg-amber-50 p-4 text-sm text-amber-800 dark:bg-amber-950 dark:text-amber-200">
128+
<Info className="size-4 flex-shrink-0" />
129+
<p>{t("settings.ai.no_provider_configured")}</p>
130+
</div>
131+
</SettingsSection>
132+
);
133+
}
134+
135+
const providerName = providerDisplayNames[provider] ?? provider;
136+
const embeddingProviderName = embeddingProvider
137+
? (providerDisplayNames[embeddingProvider] ?? embeddingProvider)
138+
: null;
139+
140+
return (
141+
<SettingsSection
142+
title={t("settings.ai.provider_status")}
143+
description={t("settings.ai.provider_status_description")}
144+
>
145+
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
146+
<ProviderInfoItem
147+
icon={<Bot className="size-4 text-muted-foreground" />}
148+
label={t("settings.ai.provider")}
149+
value={providerName}
150+
/>
151+
<ProviderInfoItem
152+
icon={<Cpu className="size-4 text-muted-foreground" />}
153+
label={t("settings.ai.text_model")}
154+
value={textModel}
155+
/>
156+
<ProviderInfoItem
157+
icon={<Image className="size-4 text-muted-foreground" />}
158+
label={t("settings.ai.image_model")}
159+
value={imageModel}
160+
/>
161+
{embeddingProviderName && embeddingModel && (
162+
<ProviderInfoItem
163+
icon={<Cpu className="size-4 text-muted-foreground" />}
164+
label={t("settings.ai.embeddings")}
165+
value={`${embeddingProviderName} / ${embeddingModel}`}
166+
/>
167+
)}
168+
</div>
169+
</SettingsSection>
170+
);
171+
}
172+
85173
export function AIPreferences() {
86174
const { t } = useTranslation();
87175
const clientConfig = useClientConfig();
@@ -677,6 +765,9 @@ export default function AISettings() {
677765
{t("settings.ai.ai_settings")}
678766
</h2>
679767

768+
{/* Provider Status */}
769+
<ProviderIndicator />
770+
680771
{/* AI Preferences */}
681772
<AIPreferences />
682773

apps/web/lib/clientConfig.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ export const ClientConfigCtx = createContext<ClientConfig>({
1616
inferredTagLang: "english",
1717
enableAutoTagging: false,
1818
enableAutoSummarization: false,
19+
provider: null,
20+
textModel: "",
21+
imageModel: "",
22+
embeddingProvider: null,
23+
embeddingModel: "",
1924
},
2025
serverVersion: undefined,
2126
disableNewReleaseCheck: true,

apps/web/lib/i18n/locales/ar/translation.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,14 @@
193193
"tag_style_description": "اختر كيف ينبغي تنسيق علاماتك التي تم إنشاؤها تلقائيًا.",
194194
"auto_tagging_description": "إنشاء علامات تلقائيًا لعلاماتك المرجعية باستخدام الذكاء الاصطناعي.",
195195
"camelCase": "camelCase",
196-
"auto_summarization": "التلخيص التلقائي"
196+
"auto_summarization": "التلخيص التلقائي",
197+
"provider_status": "حالة المزود",
198+
"provider_status_description": "مزود الذكاء الاصطناعي والنماذج المكونة حاليًا لهذا المثيل.",
199+
"no_provider_configured": "لم يتم تكوين مزود للذكاء الاصطناعي. اتصل بالمسؤول لإعداد استدلال الذكاء الاصطناعي.",
200+
"provider": "المزود",
201+
"text_model": "نموذج النص",
202+
"image_model": "نموذج الصورة",
203+
"embeddings": "التضمينات"
197204
},
198205
"feeds": {
199206
"rss_subscriptions": "اشتراكات RSS",

apps/web/lib/i18n/locales/cs/translation.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,14 @@
116116
"tag_style_description": "Vyber si, jakým způsobem se mají automaticky generované štítky formátovat.",
117117
"auto_tagging_description": "Automaticky generovat štítky pro tvoje záložky pomocí umělý inteligence.",
118118
"camelCase": "camelCase",
119-
"auto_summarization": "Automatický shrnutí"
119+
"auto_summarization": "Automatický shrnutí",
120+
"provider_status": "Stav poskytovatele",
121+
"provider_status_description": "Poskytovatel AI a modely aktuálně nakonfigurované pro tuto instanci.",
122+
"no_provider_configured": "Není nakonfigurován žádný poskytovatel AI. Kontaktujte administrátora pro nastavení AI inference.",
123+
"provider": "Poskytovatel",
124+
"text_model": "Textový model",
125+
"image_model": "Obrazový model",
126+
"embeddings": "Vložení"
120127
},
121128
"webhooks": {
122129
"webhooks": "Webhooky",

apps/web/lib/i18n/locales/da/translation.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,14 @@
159159
"tag_style_description": "Vælg, hvordan dine automatisk genererede tags skal formateres.",
160160
"auto_tagging_description": "Generér automatisk tags til dine bogmærker ved hjælp af AI.",
161161
"camelCase": "camelCase",
162-
"auto_summarization": "Automatisk opsummering"
162+
"auto_summarization": "Automatisk opsummering",
163+
"provider_status": "Udbyderstatus",
164+
"provider_status_description": "AI-udbyderen og modellerne, der aktuelt er konfigureret for denne instans.",
165+
"no_provider_configured": "Ingen AI-udbyder er konfigureret. Kontakt din administrator for at konfigurere AI-inferens.",
166+
"provider": "Udbyder",
167+
"text_model": "Tekstmodel",
168+
"image_model": "Billedmodel",
169+
"embeddings": "Indlejringer"
163170
},
164171
"broken_links": {
165172
"crawling_status": "Gennemsøgningsstatus",

apps/web/lib/i18n/locales/de/translation.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,14 @@
190190
"tag_style_description": "Wähle, wie deine automatisch generierten Tags formatiert werden sollen.",
191191
"auto_tagging_description": "Automatische Tag-Generierung für deine Lesezeichen mithilfe von KI.",
192192
"camelCase": "camelCase",
193-
"auto_summarization": "Automatische Zusammenfassung"
193+
"auto_summarization": "Automatische Zusammenfassung",
194+
"provider_status": "Anbieterstatus",
195+
"provider_status_description": "Der KI-Anbieter und die Modelle, die derzeit für diese Instanz konfiguriert sind.",
196+
"no_provider_configured": "Kein KI-Anbieter ist konfiguriert. Kontaktiere deinen Administrator, um KI-Inferenz einzurichten.",
197+
"provider": "Anbieter",
198+
"text_model": "Textmodell",
199+
"image_model": "Bildmodell",
200+
"embeddings": "Einbettungen"
194201
},
195202
"feeds": {
196203
"rss_subscriptions": "RSS-Abonnements",

apps/web/lib/i18n/locales/el/translation.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,14 @@
193193
"tag_style_description": "Διάλεξε πώς να μορφοποιηθούν οι αυτόματα δημιουργημένες ετικέτες σου.",
194194
"auto_tagging_description": "Δημιουργήστε αυτόματα ετικέτες για τους σελιδοδείκτες σας χρησιμοποιώντας AI.",
195195
"camelCase": "camelCase",
196-
"auto_summarization": "Αυτόματη δημιουργία περιλήψεων"
196+
"auto_summarization": "Αυτόματη δημιουργία περιλήψεων",
197+
"provider_status": "Κατάσταση παρόχου",
198+
"provider_status_description": "Ο πάροχος AI και τα μοντέλα που έχουν ρυθμιστεί για αυτήν την εγκατάσταση.",
199+
"no_provider_configured": "Δεν έχει ρυθμιστεί πάροχος AI. Επικοινωνήστε με τον διαχειριστή σας για να ρυθμίσετε την AI εξαγωγή συμπερασμάτων.",
200+
"provider": "Πάροχος",
201+
"text_model": "Μοντέλο κειμένου",
202+
"image_model": "Μοντέλο εικόνας",
203+
"embeddings": "Ενσωματώσεις"
197204
},
198205
"feeds": {
199206
"rss_subscriptions": "Συνδρομές RSS",

0 commit comments

Comments
 (0)