mirror of
https://github.com/zadam/trilium.git
synced 2026-05-07 19:06:18 +02:00
feat(llm): basic support for Google Gemini
This commit is contained in:
@@ -20,7 +20,8 @@ export interface ProviderType {
|
||||
|
||||
export const PROVIDER_TYPES: ProviderType[] = [
|
||||
{ id: "anthropic", name: "Anthropic" },
|
||||
{ id: "openai", name: "OpenAI" }
|
||||
{ id: "openai", name: "OpenAI" },
|
||||
{ id: "google", name: "Google Gemini" }
|
||||
];
|
||||
|
||||
interface AddProviderModalProps {
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "^2.0.0",
|
||||
"@ai-sdk/google": "^2.0.64",
|
||||
"@ai-sdk/openai": "2.0.101",
|
||||
"ai": "^5.0.0",
|
||||
"better-sqlite3": "12.8.0",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { LlmProvider, ModelInfo } from "./types.js";
|
||||
import { AnthropicProvider } from "./providers/anthropic.js";
|
||||
import { GoogleProvider } from "./providers/google.js";
|
||||
import { OpenAiProvider } from "./providers/openai.js";
|
||||
import optionService from "../options.js";
|
||||
import log from "../log.js";
|
||||
@@ -18,7 +19,8 @@ export interface LlmProviderSetup {
|
||||
/** Factory functions for creating provider instances */
|
||||
const providerFactories: Record<string, (apiKey: string) => LlmProvider> = {
|
||||
anthropic: (apiKey) => new AnthropicProvider(apiKey),
|
||||
openai: (apiKey) => new OpenAiProvider(apiKey)
|
||||
openai: (apiKey) => new OpenAiProvider(apiKey),
|
||||
google: (apiKey) => new GoogleProvider(apiKey)
|
||||
};
|
||||
|
||||
/** Cache of instantiated providers by their config ID */
|
||||
|
||||
102
apps/server/src/services/llm/providers/google.ts
Normal file
102
apps/server/src/services/llm/providers/google.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { createGoogleGenerativeAI, type GoogleGenerativeAIProvider } from "@ai-sdk/google";
|
||||
import { streamText, stepCountIs, type ToolSet } from "ai";
|
||||
import type { LlmMessage } from "@triliumnext/commons";
|
||||
|
||||
import type { LlmProviderConfig, StreamResult } from "../types.js";
|
||||
import { BaseProvider, buildModelList } from "./base_provider.js";
|
||||
|
||||
/**
|
||||
* Available Google Gemini models with pricing (USD per million tokens).
|
||||
* Source: https://ai.google.dev/gemini-api/docs/pricing
|
||||
*/
|
||||
const { models: AVAILABLE_MODELS, pricing: MODEL_PRICING } = buildModelList([
|
||||
// ===== Current Models =====
|
||||
{
|
||||
id: "gemini-2.5-pro",
|
||||
name: "Gemini 2.5 Pro",
|
||||
pricing: { input: 1.25, output: 10 },
|
||||
contextWindow: 1048576,
|
||||
isDefault: true
|
||||
},
|
||||
{
|
||||
id: "gemini-2.5-flash",
|
||||
name: "Gemini 2.5 Flash",
|
||||
pricing: { input: 0.3, output: 2.5 },
|
||||
contextWindow: 1048576
|
||||
},
|
||||
{
|
||||
id: "gemini-2.5-flash-lite",
|
||||
name: "Gemini 2.5 Flash-Lite",
|
||||
pricing: { input: 0.1, output: 0.4 },
|
||||
contextWindow: 1048576
|
||||
},
|
||||
{
|
||||
id: "gemini-2.0-flash",
|
||||
name: "Gemini 2.0 Flash",
|
||||
pricing: { input: 0.1, output: 0.4 },
|
||||
contextWindow: 1048576,
|
||||
isLegacy: true
|
||||
}
|
||||
]);
|
||||
|
||||
export class GoogleProvider extends BaseProvider {
|
||||
name = "google";
|
||||
protected defaultModel = "gemini-2.5-flash";
|
||||
protected titleModel = "gemini-2.5-flash-lite";
|
||||
protected availableModels = AVAILABLE_MODELS;
|
||||
protected modelPricing = MODEL_PRICING;
|
||||
|
||||
private google: GoogleGenerativeAIProvider;
|
||||
|
||||
constructor(apiKey: string) {
|
||||
super();
|
||||
if (!apiKey) {
|
||||
throw new Error("API key is required for Google provider");
|
||||
}
|
||||
this.google = createGoogleGenerativeAI({ apiKey });
|
||||
}
|
||||
|
||||
protected createModel(modelId: string) {
|
||||
return this.google(modelId);
|
||||
}
|
||||
|
||||
protected override addWebSearchTool(tools: ToolSet): void {
|
||||
tools.google_search = this.google.tools.googleSearch({});
|
||||
}
|
||||
|
||||
/**
|
||||
* Override chat to add Google-specific extended thinking support.
|
||||
* Gemini 2.5 uses thinkingBudget, Gemini 3.x uses thinkingLevel.
|
||||
*/
|
||||
override chat(messages: LlmMessage[], config: LlmProviderConfig): StreamResult {
|
||||
if (!config.enableExtendedThinking) {
|
||||
return super.chat(messages, config);
|
||||
}
|
||||
|
||||
const systemPrompt = this.buildSystemPrompt(messages, config);
|
||||
const chatMessages = messages.filter(m => m.role !== "system");
|
||||
const coreMessages = this.buildMessages(chatMessages, systemPrompt);
|
||||
|
||||
const streamOptions: Parameters<typeof streamText>[0] = {
|
||||
model: this.createModel(config.model || this.defaultModel),
|
||||
messages: coreMessages,
|
||||
maxOutputTokens: config.maxTokens || 8096,
|
||||
providerOptions: {
|
||||
google: {
|
||||
thinkingConfig: {
|
||||
thinkingBudget: config.thinkingBudget || 10000
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const tools = this.buildTools(config);
|
||||
if (Object.keys(tools).length > 0) {
|
||||
streamOptions.tools = tools;
|
||||
streamOptions.stopWhen = stepCountIs(5);
|
||||
streamOptions.toolChoice = "auto";
|
||||
}
|
||||
|
||||
return streamText(streamOptions);
|
||||
}
|
||||
}
|
||||
25
pnpm-lock.yaml
generated
25
pnpm-lock.yaml
generated
@@ -559,6 +559,9 @@ importers:
|
||||
'@ai-sdk/anthropic':
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.71(zod@4.3.6)
|
||||
'@ai-sdk/google':
|
||||
specifier: ^2.0.64
|
||||
version: 2.0.64(zod@4.3.6)
|
||||
'@ai-sdk/openai':
|
||||
specifier: 2.0.101
|
||||
version: 2.0.101(zod@4.3.6)
|
||||
@@ -1551,6 +1554,12 @@ packages:
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4.1.8
|
||||
|
||||
'@ai-sdk/google@2.0.64':
|
||||
resolution: {integrity: sha512-FUVSkdpC+j2o3anRHabJ5UXXPfnqs8uRkv5zh5x4u8p1e7C4y+YtTxeTD2aSSMGV+8ef+VNEAp5gponXpwKk0g==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.25.76 || ^4.1.8
|
||||
|
||||
'@ai-sdk/openai@2.0.101':
|
||||
resolution: {integrity: sha512-kQ52HLV45T3bQbRzWExXW6+pkg3Nvq4dUnZHUPJXWgkUUsAhZjxHrXqPOc/0yfn/4+Dn2uLmIgAkP9IfzMMcNg==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -16049,6 +16058,12 @@ snapshots:
|
||||
'@vercel/oidc': 3.1.0
|
||||
zod: 4.3.6
|
||||
|
||||
'@ai-sdk/google@2.0.64(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 2.0.1
|
||||
'@ai-sdk/provider-utils': 3.0.22(zod@4.3.6)
|
||||
zod: 4.3.6
|
||||
|
||||
'@ai-sdk/openai@2.0.101(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 2.0.1
|
||||
@@ -17408,8 +17423,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-ui': 47.6.1
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-horizontal-line@47.6.1':
|
||||
dependencies:
|
||||
@@ -17419,8 +17432,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
'@ckeditor/ckeditor5-widget': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-html-embed@47.6.1':
|
||||
dependencies:
|
||||
@@ -17430,8 +17441,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
'@ckeditor/ckeditor5-widget': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-html-support@47.6.1':
|
||||
dependencies:
|
||||
@@ -17447,8 +17456,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-widget': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
es-toolkit: 1.39.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-icons@47.6.1': {}
|
||||
|
||||
@@ -17489,8 +17496,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-ui': 47.6.1
|
||||
'@ckeditor/ckeditor5-utils': 47.6.1
|
||||
ckeditor5: 47.6.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-inspector@5.0.0': {}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user