Skip to content

Commit 1c8b515

Browse files
authored
Merge pull request #58 from donvito/feature/gemini-support
feature/gemini-support
2 parents 788704c + 3f2d1b4 commit 1c8b515

File tree

12 files changed

+216
-11
lines changed

12 files changed

+216
-11
lines changed

README.md

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ More to come...check swagger docs for updated endpoints.
4949
| [OpenRouter](https://openrouter.ai/) | Open source and private models | Available |
5050
| [Vercel AI Gateway](https://vercel.com/ai-gateway) | Open source and private models | Available |
5151
| [LlamaCpp](https://github.com/ggml-org/llama.cpp) | Local models via llama.cpp server (self-hosted) | Available |
52-
| [Google](https://ai.google.dev/) | Gemini models | In Progress |
52+
| [Google Gemini](https://ai.google.dev/) | Gemini models via OpenAI-compatible interface | Available |
5353

5454

5555
## Run the project
@@ -93,6 +93,7 @@ DEFAULT_ACCESS_TOKEN=your-secret-api-key
9393
OPENAI_API_KEY=your-openai-api-key
9494
ANTHROPIC_API_KEY=your-anthropic-api-key
9595
OPENROUTER_API_KEY=your-openrouter-api-key
96+
GOOGLE_AI_API_KEY=your-google-ai-api-key
9697
```
9798
You need to configure at least one provider api key. Otherwise, the app will not start.
9899

@@ -144,6 +145,10 @@ OPENAI_API_KEY=your-openai-api-key
144145
# Anthropic Configuration
145146
ANTHROPIC_API_KEY=your-anthropic-api-key
146147
148+
# Google Gemini Configuration
149+
GOOGLE_AI_API_KEY=your-google-ai-api-key
150+
GEMINI_MODEL=gemini-2.5-flash-lite
151+
147152
# Ollama Configuration
148153
OLLAMA_ENABLED=true
149154
OLLAMA_BASE_URL=http://localhost:11434
@@ -161,8 +166,27 @@ LMSTUDIO_ENABLED=true
161166
LMSTUDIO_BASE_URL=http://localhost:1234
162167
163168
# You can change LMSTUDIO_BASE_URL to use a remote LM Studio instance
169+
170+
# OpenRouter Configuration
171+
OPENROUTER_API_KEY=your-openrouter-api-key
164172
```
165173

174+
### Google Gemini Setup
175+
176+
To use Google Gemini models:
177+
178+
1. Get your API key from [Google AI Studio](https://makersuite.google.com/app/apikey)
179+
2. Set `GOOGLE_AI_API_KEY` in your `.env` file
180+
3. Optionally configure `GEMINI_MODEL` (defaults to `gemini-2.5-flash-lite`)
181+
182+
Available Gemini models:
183+
- `gemini-2.5-flash-lite` (default)
184+
- `gemini-2.5-flash`
185+
- `gemini-2.5-pro`
186+
- `gemini-pro-vision`
187+
188+
**Note**: The Gemini provider uses Google's OpenAI-compatible interface to maintain compatibility with AI SDK v4.
189+
166190
**Important:** Make sure to add `.env` to your `.gitignore` file to avoid committing sensitive information to version control.
167191

168192

@@ -196,7 +220,26 @@ You can access demos at http://localhost:3000/api/demos
196220
## Provider and Model Selection
197221
You need to send the service and model name in the request body. See examples in the swagger docs.
198222

199-
For example, to summarize text using qwen2.5-coder model with Ollama as provider, you can use the following curl command:
223+
For example, to summarize text using Gemini model with Google as provider, you can use the following curl command:
224+
225+
```curl
226+
curl --location 'http://localhost:3000/api/v1/summarize' \
227+
--header 'Content-Type: application/json' \
228+
--header 'Accept: application/json' \
229+
--data '{
230+
"payload": {
231+
"text": "Text to summarize",
232+
"maxLength": 100
233+
},
234+
"config": {
235+
"provider": "google",
236+
"model": "gemini-2.5-flash-lite",
237+
"temperature": 0
238+
}
239+
}'
240+
```
241+
242+
Or with Ollama:
200243

201244
```curl
202245
curl --location 'http://localhost:3000/api/v1/summarize' \

bun.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,14 @@ export async function configureAuth(app: OpenAPIHono): Promise<void> {
136136

137137
export async function checkLLMProvidersAvailability() {
138138

139-
// Check if OpenAI, Anthropic, or OpenRouter are available via API keys
139+
// Check if OpenAI, Anthropic, OpenRouter, or Google are available via API keys
140140
const openaiApiKey = process.env.OPENAI_API_KEY;
141141
const anthropicApiKey = process.env.ANTHROPIC_API_KEY;
142142
const openrouterApiKey = process.env.OPENROUTER_API_KEY;
143143
const aigatewayApiKey = process.env.AI_GATEWAY_API_KEY;
144+
const geminiApiKey = process.env.GOOGLE_AI_API_KEY;
144145

145-
if (!openaiApiKey && !anthropicApiKey && !openrouterApiKey && !aigatewayApiKey) {
146+
if (!openaiApiKey && !anthropicApiKey && !openrouterApiKey && !aigatewayApiKey && !geminiApiKey) {
146147
throw new Error('No API keys found for external LLM providers');
147148
}
148149

@@ -151,6 +152,7 @@ export async function checkLLMProvidersAvailability() {
151152
if (anthropicApiKey) availableProviders.push('Anthropic');
152153
if (openrouterApiKey) availableProviders.push('OpenRouter');
153154
if (aigatewayApiKey) availableProviders.push('AIGateway');
155+
if (geminiApiKey) availableProviders.push('Google');
154156

155157
return availableProviders;
156158
}

src/config/models.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
{ "name": "openai/gpt-4o-mini", "capabilities": ["summarize", "pdf-summarizer", "rewrite", "compose", "planning", "keywords", "sentiment", "askText", "emailReply", "translate", "meetingNotes", "outline"], "notes": "OpenRouter proxy to GPT-4o mini." },
4343
{ "name": "google/gemini-2.0-flash-lite-001", "capabilities": ["summarize", "pdf-summarizer", "rewrite", "compose", "planning", "keywords", "sentiment", "askText", "emailReply", "translate", "meetingNotes", "outline"], "notes": "Google Gemini 2.0 Flash Lite via OpenRouter with Q&A capabilities." },
4444
{ "name": "google/gemini-2.5-flash-lite", "capabilities": ["summarize", "pdf-summarizer", "rewrite", "compose", "planning", "keywords", "sentiment", "askText", "emailReply", "translate", "meetingNotes", "outline"], "notes": "Google Gemini 2.5 Flash Lite via OpenRouter with 1M token context window for large document Q&A." },
45+
{ "name": "google/gemini-2.5-flash", "capabilities": ["summarize", "pdf-summarizer", "rewrite", "compose", "planning", "keywords", "sentiment", "askText", "emailReply", "translate", "meetingNotes", "outline"], "notes": "Google Gemini 2.5 Flash via OpenRouter with advanced capabilities." },
46+
{ "name": "google/gemini-2.5-pro", "capabilities": ["summarize", "pdf-summarizer", "rewrite", "compose", "planning", "keywords", "sentiment", "askText", "emailReply", "translate", "meetingNotes", "outline"], "notes": "Google Gemini 2.5 Pro via OpenRouter with enhanced reasoning." },
4547
{ "name": "openai/gpt-oss-20b", "capabilities": ["summarize", "pdf-summarizer", "rewrite", "compose", "planning", "keywords", "sentiment", "askText", "emailReply", "translate", "meetingNotes", "outline"], "notes": "OpenRouter proxy to GPT OSS 20B" },
4648
{ "name": "openai/gpt-oss-120b", "capabilities": ["summarize", "pdf-summarizer", "rewrite", "compose", "planning", "keywords", "sentiment", "askText", "emailReply", "translate", "meetingNotes", "outline"], "notes": "OpenRouter proxy to GPT OSS 120B" },
4749
{ "name": "nvidia/nemotron-nano-9b-v2", "capabilities": ["summarize", "pdf-summarizer", "rewrite", "compose", "planning", "keywords", "sentiment", "askText", "emailReply", "translate", "meetingNotes", "outline"], "notes": "NVIDIA Nemotron Nano 9B v2; compact high-performance model." }
@@ -63,6 +65,14 @@
6365

6466
]
6567
},
68+
"google": {
69+
"enabled": true,
70+
"models": [
71+
{ "name": "gemini-2.5-flash-lite", "capabilities": ["summarize", "pdf-summarizer", "rewrite", "compose", "planning", "keywords", "sentiment", "vision", "askText", "emailReply", "translate", "meetingNotes", "outline"], "notes": "Google Gemini 2.5 Flash Lite with fast processing and vision capabilities." },
72+
{ "name": "gemini-2.5-flash", "capabilities": ["summarize", "pdf-summarizer", "rewrite", "compose", "planning", "keywords", "sentiment", "vision", "askText", "emailReply", "translate", "meetingNotes", "outline"], "notes": "Google Gemini 2.5 Flash with advanced multimodal capabilities." },
73+
{ "name": "gemini-2.5-pro", "capabilities": ["summarize", "pdf-summarizer", "rewrite", "compose", "planning", "keywords", "sentiment", "vision", "askText", "emailReply", "translate", "meetingNotes", "outline"], "notes": "Google Gemini 2.5 Pro with enhanced reasoning and vision support." }
74+
]
75+
},
6676
"llamacpp": {
6777
"enabled": true,
6878
"models": [

src/config/models.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as path from 'path'
33

44
export type ModelCapability =
55
| 'summarize'
6+
| 'pdf-summarizer'
67
| 'rewrite'
78
| 'compose'
89
| 'keywords'

src/config/services.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ export interface LlamaCppConfig extends ServiceConfig {
5151
timeout?: number;
5252
}
5353

54+
export interface GoogleConfig extends ServiceConfig {
55+
apiKey: string;
56+
model: string;
57+
}
58+
5459
// OpenAI Configuration
5560
export const openaiConfig: OpenAIConfig = {
5661
name: 'OpenAI',
@@ -123,8 +128,17 @@ export const llamacppConfig: LlamaCppConfig = {
123128
timeout: parseInt(process.env.LLAMACPP_TIMEOUT || '30000'),
124129
};
125130

131+
// Google Gemini Configuration
132+
export const googleConfig: GoogleConfig = {
133+
name: 'Google',
134+
enabled: !!process.env.GOOGLE_AI_API_KEY,
135+
priority: 8,
136+
apiKey: process.env.GOOGLE_AI_API_KEY || '',
137+
model: process.env.GEMINI_MODEL || 'gemini-2.5-flash-lite',
138+
};
139+
126140
// Available services
127-
export const availableServices = [openaiConfig, anthropicConfig, ollamaConfig, openrouterConfig, lmstudioConfig, aigatewayConfig, llamacppConfig];
141+
export const availableServices = [openaiConfig, anthropicConfig, ollamaConfig, openrouterConfig, lmstudioConfig, aigatewayConfig, llamacppConfig, googleConfig];
128142

129143
// Get the primary service (highest priority enabled service)
130144
export function getPrimaryService(): ServiceConfig | null {

src/routes/v1/services.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ const serviceStatusSchema = z.object({
5050
model: z.string(),
5151
chatModel: z.string(),
5252
})
53+
}),
54+
google: z.object({
55+
enabled: z.boolean(),
56+
available: z.boolean(),
57+
config: z.object({
58+
model: z.string(),
59+
hasApiKey: z.boolean(),
60+
})
5361
})
5462
}),
5563
primary: z.string().nullable(),
@@ -62,7 +70,7 @@ const modelsSchema = z.object({
6270
available: z.boolean()
6371
})
6472

65-
const capabilityEnum = z.enum(['summarize', 'rewrite', 'compose', 'keywords', 'sentiment', 'emailReply', 'vision', 'askText', 'translate', 'meetingNotes', 'planning', 'outline'])
73+
const capabilityEnum = z.enum(['summarize', 'pdf-summarizer', 'rewrite', 'compose', 'keywords', 'sentiment', 'emailReply', 'vision', 'askText', 'translate', 'meetingNotes', 'planning', 'outline'])
6674
const byProviderSchema = z.record(z.array(z.string()))
6775
const providerViewSchema = z.record(z.array(z.object({
6876
name: z.string(),
@@ -73,6 +81,7 @@ const providerViewSchema = z.record(z.array(z.object({
7381
source: z.literal('config'),
7482
byCapability: z.object({
7583
summarize: byProviderSchema,
84+
'pdf-summarizer': byProviderSchema,
7685
rewrite: byProviderSchema,
7786
compose: byProviderSchema,
7887
keywords: byProviderSchema,
@@ -105,6 +114,7 @@ async function handleGetModels(c: Context) {
105114
if (source === 'config') {
106115
const byCapability = {
107116
summarize: getModelsByCapability('summarize'),
117+
'pdf-summarizer': getModelsByCapability('pdf-summarizer'),
108118
rewrite: getModelsByCapability('rewrite'),
109119
compose: getModelsByCapability('compose'),
110120
keywords: getModelsByCapability('keywords'),
@@ -128,7 +138,8 @@ async function handleGetModels(c: Context) {
128138
getAvailableModels('ollama'),
129139
getAvailableModels('openrouter'),
130140
getAvailableModels('anthropic'),
131-
getAvailableModels('lmstudio')
141+
getAvailableModels('lmstudio'),
142+
getAvailableModels('google')
132143
])
133144
const response = {
134145
openai: {
@@ -155,6 +166,11 @@ async function handleGetModels(c: Context) {
155166
service: 'lmstudio',
156167
models: results[4].status === 'fulfilled' ? results[4].value : [],
157168
available: await checkServiceAvailability('lmstudio')
169+
},
170+
google: {
171+
service: 'google',
172+
models: results[5].status === 'fulfilled' ? results[5].value : [],
173+
available: await checkServiceAvailability('google')
158174
}
159175
}
160176
return c.json(response, 200)
@@ -175,8 +191,8 @@ async function handleGetModels(c: Context) {
175191
async function handleServiceHealth(c: Context) {
176192
try {
177193
const service = c.req.param('service')
178-
if (!service || !['openai', 'ollama', 'openrouter', 'anthropic', 'lmstudio'].includes(service)) {
179-
return c.json({ error: 'Invalid service. Must be one of: openai, ollama, openrouter, anthropic, lmstudio' }, 400)
194+
if (!service || !['openai', 'ollama', 'openrouter', 'anthropic', 'lmstudio', 'google'].includes(service)) {
195+
return c.json({ error: 'Invalid service. Must be one of: openai, ollama, openrouter, anthropic, lmstudio, google' }, 400)
180196
}
181197
const available = await checkServiceAvailability(service as any)
182198
return c.json({
@@ -236,6 +252,7 @@ router.openapi(
236252
openrouter: modelsSchema,
237253
anthropic: modelsSchema,
238254
lmstudio: modelsSchema,
255+
google: modelsSchema,
239256
}),
240257
modelsGuidanceSchema.extend({ byProvider: providerViewSchema }).partial({ byCapability: true, byProvider: true })
241258
])

src/services/ai.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
lmstudioConfig,
88
aigatewayConfig,
99
llamacppConfig,
10+
googleConfig,
1011
isServiceEnabled
1112
} from "../config/services";
1213
import { llmRequestSchema } from "../schemas/v1/llm";
@@ -21,6 +22,7 @@ enum Provider {
2122
lmstudio = 'lmstudio',
2223
aigateway = 'aigateway',
2324
llamacpp = 'llamacpp',
25+
google = 'google',
2426
}
2527

2628
// Service types
@@ -68,6 +70,8 @@ export async function checkServiceAvailability(service: AIService): Promise<bool
6870
return isServiceEnabled('AIGateway');
6971
case Provider.llamacpp:
7072
return isServiceEnabled('LlamaCpp');
73+
case Provider.google:
74+
return isServiceEnabled('Google');
7175
default:
7276
return false;
7377
}
@@ -179,6 +183,14 @@ export async function getServiceStatus() {
179183
config: {
180184
baseURL: llamacppConfig.baseURL,
181185
}
186+
},
187+
google: {
188+
enabled: isServiceEnabled('Google'),
189+
available: await checkServiceAvailability(Provider.google),
190+
config: {
191+
model: googleConfig.model,
192+
hasApiKey: !!googleConfig.apiKey,
193+
}
182194
}
183195
};
184196

0 commit comments

Comments
 (0)