diff --git a/package.json b/package.json index db70194..ef4df6d 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@langchain/anthropic": "^0.2.3", "@langchain/community": "^0.2.16", "@langchain/openai": "^0.0.25", + "@langchain/google-genai": "^0.0.23", "@xenova/transformers": "^2.17.1", "axios": "^1.6.8", "better-sqlite3": "^11.0.0", diff --git a/sample.config.toml b/sample.config.toml index dddcc03..885e608 100644 --- a/sample.config.toml +++ b/sample.config.toml @@ -1,13 +1,14 @@ -[GENERAL] -PORT = 3001 # Port to run the server on -SIMILARITY_MEASURE = "cosine" # "cosine" or "dot" -KEEP_ALIVE = "5m" # How long to keep Ollama models loaded into memory. (Instead of using -1 use "-1m") - [API_KEYS] -OPENAI = "" # OpenAI API key - sk-1234567890abcdef1234567890abcdef -GROQ = "" # Groq API key - gsk_1234567890abcdef1234567890abcdef -ANTHROPIC = "" # Anthropic API key - sk-ant-1234567890abcdef1234567890abcdef +OPENAI = "" +GROQ = "" +ANTHROPIC = "" +GEMINI = "" [API_ENDPOINTS] -SEARXNG = "http://localhost:32768" # SearxNG API URL -OLLAMA = "" # Ollama API URL - http://host.docker.internal:11434 \ No newline at end of file +OLLAMA = "" +SEARXNG = "http://localhost:32768" + +[GENERAL] +PORT = 3_001 +SIMILARITY_MEASURE = "cosine" +KEEP_ALIVE = "5m" diff --git a/src/config.ts b/src/config.ts index 8624e7f..4e7d07a 100644 --- a/src/config.ts +++ b/src/config.ts @@ -14,6 +14,7 @@ interface Config { OPENAI: string; GROQ: string; ANTHROPIC: string; + Gemini: string; }; API_ENDPOINTS: { SEARXNG: string; @@ -43,6 +44,8 @@ export const getGroqApiKey = () => loadConfig().API_KEYS.GROQ; export const getAnthropicApiKey = () => loadConfig().API_KEYS.ANTHROPIC; +export const getGeminiApiKey = () => loadConfig().API_KEYS.Gemini; + export const getSearxngApiEndpoint = () => process.env.SEARXNG_API_URL || loadConfig().API_ENDPOINTS.SEARXNG; diff --git a/src/lib/providers/gemini.ts b/src/lib/providers/gemini.ts new file mode 100644 index 0000000..0a29efe --- /dev/null +++ b/src/lib/providers/gemini.ts @@ -0,0 +1,119 @@ +import { ChatGoogleGenerativeAI } from '@langchain/google-genai'; +import { GoogleGenerativeAIEmbeddings } from '@langchain/google-genai'; +import { getGeminiApiKey } from '../../config'; +import logger from '../../utils/logger'; +import axios from 'axios'; + +interface GeminiModel { + name: string; + baseModelId: string; + version: string; + displayName: string; + description: string; + inputTokenLimit: number; + outputTokenLimit: number; + supportedGenerationMethods: string[]; + temperature: number; + maxTemperature: number; + topP: number; + topK: number; +} + +interface GeminiModelsResponse { + models: GeminiModel[]; + nextPageToken?: string; +} + +const fetchGeminiModels = async (apiKey: string): Promise => { + try { + const response = await axios.get( + `https://generativelanguage.googleapis.com/v1beta/models?key=${apiKey}`, + ); + return response.data.models; + } catch (err) { + logger.error(`Error fetching Gemini models: ${err}`); + return []; + } +}; + +export const loadGeminiChatModels = async () => { + const geminiApiKey = getGeminiApiKey(); + + if (!geminiApiKey) return {}; + + try { + const models = await fetchGeminiModels(geminiApiKey); + const chatModels: Record = {}; + + // If no models are available from the API, fallback to default models + if (!models.length) { + chatModels['gemini-pro'] = { + displayName: 'Gemini Pro', + model: new ChatGoogleGenerativeAI({ + temperature: 0.7, + apiKey: geminiApiKey, + modelName: 'gemini-pro', + }), + }; + return chatModels; + } + + for (const model of models) { + if (model.supportedGenerationMethods.includes('generateContent')) { + chatModels[model.name] = { + displayName: model.displayName, + model: new ChatGoogleGenerativeAI({ + temperature: model.temperature || 0.7, + apiKey: geminiApiKey, + modelName: model.baseModelId, + }), + }; + } + } + + return chatModels; + } catch (err) { + logger.error(`Error loading Gemini chat models: ${err}`); + return {}; + } +}; + +export const loadGeminiEmbeddingsModels = async () => { + const geminiApiKey = getGeminiApiKey(); + + if (!geminiApiKey) return {}; + + try { + const models = await fetchGeminiModels(geminiApiKey); + const embeddingsModels: Record = {}; + + // If no models are available from the API, fallback to default models + if (!models.length) { + embeddingsModels['embedding-001'] = { + displayName: 'Gemini Embedding', + model: new GoogleGenerativeAIEmbeddings({ + apiKey: geminiApiKey, + modelName: 'embedding-001', + }), + }; + return embeddingsModels; + } + + for (const model of models) { + if (model.supportedGenerationMethods.includes('embedContent')) { + embeddingsModels[model.name] = { + displayName: model.displayName, + model: new GoogleGenerativeAIEmbeddings({ + apiKey: geminiApiKey, + modelName: model.baseModelId, + }), + }; + } + } + + return embeddingsModels; + } catch (err) { + logger.error(`Error loading Gemini embeddings model: ${err}`); + return {}; + } +}; \ No newline at end of file diff --git a/src/lib/providers/index.ts b/src/lib/providers/index.ts index d919fd4..390ed3c 100644 --- a/src/lib/providers/index.ts +++ b/src/lib/providers/index.ts @@ -3,18 +3,20 @@ import { loadOllamaChatModels, loadOllamaEmbeddingsModels } from './ollama'; import { loadOpenAIChatModels, loadOpenAIEmbeddingsModels } from './openai'; import { loadAnthropicChatModels } from './anthropic'; import { loadTransformersEmbeddingsModels } from './transformers'; - +import { loadGeminiChatModels, loadGeminiEmbeddingsModels } from './gemini'; const chatModelProviders = { openai: loadOpenAIChatModels, groq: loadGroqChatModels, ollama: loadOllamaChatModels, anthropic: loadAnthropicChatModels, + gemini : loadGeminiChatModels }; const embeddingModelProviders = { openai: loadOpenAIEmbeddingsModels, local: loadTransformersEmbeddingsModels, ollama: loadOllamaEmbeddingsModels, + gemini : loadGeminiEmbeddingsModels }; export const getAvailableChatModelProviders = async () => { diff --git a/src/routes/config.ts b/src/routes/config.ts index f635e4b..4d6589a 100644 --- a/src/routes/config.ts +++ b/src/routes/config.ts @@ -7,6 +7,7 @@ import { getGroqApiKey, getOllamaApiEndpoint, getAnthropicApiKey, + getGeminiApiKey, getOpenaiApiKey, updateConfig, } from '../config'; @@ -52,7 +53,7 @@ router.get('/', async (_, res) => { config['ollamaApiUrl'] = getOllamaApiEndpoint(); config['anthropicApiKey'] = getAnthropicApiKey(); config['groqApiKey'] = getGroqApiKey(); - + config['geminiApiKey'] = getGeminiApiKey(); res.status(200).json(config); } catch (err: any) { res.status(500).json({ message: 'An error has occurred.' }); @@ -68,6 +69,7 @@ router.post('/', async (req, res) => { OPENAI: config.openaiApiKey, GROQ: config.groqApiKey, ANTHROPIC: config.anthropicApiKey, + Gemini: config.geminiApiKey, }, API_ENDPOINTS: { OLLAMA: config.ollamaApiUrl, diff --git a/ui/components/SettingsDialog.tsx b/ui/components/SettingsDialog.tsx index 716dd7d..163857b 100644 --- a/ui/components/SettingsDialog.tsx +++ b/ui/components/SettingsDialog.tsx @@ -63,6 +63,7 @@ interface SettingsType { openaiApiKey: string; groqApiKey: string; anthropicApiKey: string; + geminiApiKey: string; ollamaApiUrl: string; } @@ -476,6 +477,22 @@ const SettingsDialog = ({ } /> +
+

+ Gemini API Key +

+ + setConfig({ + ...config, + geminiApiKey: e.target.value, + }) + } + /> +
)} {isLoading && (