Spaces:
Configuration error
Configuration error
Implementation Plan: Gemini API Migration
Feature Branch: 006-gemini-api-migration
Created: 2025-12-14
Status: Ready for Implementation
Technical Context
| Aspect | Status | Details |
|---|---|---|
| Framework | Resolved | FastAPI (existing) |
| New SDK | Resolved | google-genai (new unified SDK) |
| Chat Model | Resolved | gemini-2.0-flash-exp |
| Embedding Model | Resolved | text-embedding-004 |
| Async Pattern | Resolved | client.aio.models.* for async operations |
| API Key | Resolved | GEMINI_API_KEY environment variable |
Constitution Compliance
| Principle | Status | Implementation |
|---|---|---|
| Technical accuracy | Pass | Gemini API with proper SDK usage |
| Python code examples | Pass | FastAPI/Python implementation |
| Clear documentation | Pass | Research, quickstart, and plan documentation |
| Smallest viable change | Pass | Direct replacement maintaining interfaces |
Architecture Overview
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Client β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββΌββββββββββββββββββββ
β β β
βΌ βΌ βΌ
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β /api/chat β β /api/translate β β /api/personalizeβ
β (chat.py) β β (translate.py) β β (personalize.py)β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β β β
βββββββββββββββββββββΌββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β GeminiService β
β (app/services/gemini_service.py) β
β β
β Methods: β
β - get_chat_response(prompt, history) β str β
β - translate_to_urdu(content) β str β
β - personalize_content(...) β dict β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Google Gemini API β
β β
β Models: β
β - gemini-2.0-flash-exp (chat, translation, personalization) β
β - text-embedding-004 (embeddings) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Implementation Components
1. Configuration Updates
File: app/config.py
from pydantic_settings import BaseSettings
import os
class Settings(BaseSettings):
GEMINI_API_KEY: str
DATABASE_URL: str = os.getenv("DATABASE_URL", "")
NEON_DATABASE_URL: str = os.getenv("NEON_DATABASE_URL", "")
QDRANT_URL: str = os.getenv("QDRANT_URL", "http://localhost:6333")
QDRANT_API_KEY: str = os.getenv("QDRANT_API_KEY", "")
GEMINI_MODEL_CHAT: str = "gemini-2.0-flash-exp"
GEMINI_MODEL_EMBEDDING: str = "text-embedding-004"
# JWT Authentication settings
JWT_SECRET_KEY: str = os.getenv("JWT_SECRET_KEY", "your-secret-key-change-in-production")
JWT_ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
class Config:
env_file = ".env"
extra = "ignore"
settings = Settings()
2. GeminiService (New Service)
File: app/services/gemini_service.py
from google import genai
from google.genai import types
from app.config import settings
from typing import List
import json
class GeminiService:
def __init__(self):
self.client = genai.Client(api_key=settings.GEMINI_API_KEY)
self.model = settings.GEMINI_MODEL_CHAT
async def get_chat_response(self, prompt: str, history: List[dict] = None) -> str:
"""Generate chat response using Gemini."""
contents = []
if history:
for msg in history:
role = "model" if msg["role"] == "assistant" else msg["role"]
if role == "system":
continue # Handle system messages separately
contents.append(
types.Content(
role=role,
parts=[types.Part(text=msg["content"])]
)
)
contents.append(
types.Content(
role="user",
parts=[types.Part(text=prompt)]
)
)
response = await self.client.aio.models.generate_content(
model=self.model,
contents=contents
)
return response.text
async def translate_to_urdu(self, content: str) -> str:
"""Translate English content to Urdu using Gemini."""
system_instruction = "You are a professional translator. Translate the following English text to Urdu. Provide only the Urdu translation without any explanation or additional text."
response = await self.client.aio.models.generate_content(
model=self.model,
contents=content,
config=types.GenerateContentConfig(
system_instruction=system_instruction
)
)
return response.text
async def personalize_content(
self,
content: str,
software_level: str,
hardware_level: str,
learning_goals: str
) -> dict:
"""Personalize content based on user's background."""
system_instruction = f"""You are an expert educational content adapter. Your task is to personalize the following content based on the user's background.
USER PROFILE:
- Software/Programming Level: {software_level}
- Hardware/Electronics Level: {hardware_level}
- Learning Goals: {learning_goals if learning_goals else 'Not specified'}
PERSONALIZATION RULES:
For Software Level:
- beginner: Add detailed explanations, use simpler terminology, break down complex concepts, provide examples
- intermediate: Maintain moderate complexity, brief explanations for advanced concepts only
- advanced: Add technical depth, skip basic explanations, use precise technical terminology
For Hardware Level:
- none: Explain all hardware concepts from scratch, use analogies
- basic: Brief hardware explanations, define technical terms
- experienced: Use technical hardware terminology without explanation
If learning goals are specified, emphasize and connect content to those objectives.
OUTPUT FORMAT:
Respond with a JSON object containing exactly two fields:
1. "personalized_content": The adapted content
2. "adjustments_made": A brief description of what changes were made
Example response format:
{{"personalized_content": "...", "adjustments_made": "..."}}"""
response = await self.client.aio.models.generate_content(
model=self.model,
contents=content,
config=types.GenerateContentConfig(
system_instruction=system_instruction,
response_mime_type="application/json"
)
)
result = json.loads(response.text)
return result
3. EmbeddingsService (Updated)
File: app/services/embeddings_service.py
from google import genai
from google.genai import types
from app.config import settings
class EmbeddingsService:
def __init__(self):
self.client = genai.Client(api_key=settings.GEMINI_API_KEY)
self.model = settings.GEMINI_MODEL_EMBEDDING
async def create_embedding(self, text: str):
"""Generate embedding for text using Gemini."""
text = text.replace("\n", " ")
result = await self.client.aio.models.embed_content(
model=self.model,
contents=text,
config=types.EmbedContentConfig(
task_type="RETRIEVAL_DOCUMENT"
)
)
return result.embeddings[0].values
4. Route Updates
File: app/routes/chat.py - Update imports
# Change from:
from app.services.openai_service import OpenAIService
# To:
from app.services.gemini_service import GeminiService
And update instantiation:
# Change from:
openai_service = OpenAIService()
# To:
gemini_service = GeminiService()
File: app/routes/translate.py - Update imports
# Change from:
from app.services.openai_service import OpenAIService
# To:
from app.services.gemini_service import GeminiService
File: app/routes/personalize.py - Update imports
# Change from:
from app.services.openai_service import OpenAIService
# To:
from app.services.gemini_service import GeminiService
File: app/services/rag_service.py - Update imports
# Change from:
from app.services.openai_service import OpenAIService
# To:
from app.services.gemini_service import GeminiService
5. Dependencies Update
File: requirements.txt
# Remove:
# openai==1.35.13
# Add:
google-genai>=0.3.0
6. Environment Update
File: .env
# Remove or comment out:
# OPENAI_API_KEY=sk-proj-xxxxx
# Add:
GEMINI_API_KEY=your-gemini-api-key-here
Implementation Order
| Step | Task | Dependencies | Files |
|---|---|---|---|
| 1 | Update config.py | None | app/config.py |
| 2 | Create gemini_service.py | Step 1 | app/services/gemini_service.py |
| 3 | Update embeddings_service.py | Step 1 | app/services/embeddings_service.py |
| 4 | Update rag_service.py imports | Steps 2-3 | app/services/rag_service.py |
| 5 | Update chat.py imports | Step 2 | app/routes/chat.py |
| 6 | Update translate.py imports | Step 2 | app/routes/translate.py |
| 7 | Update personalize.py imports | Step 2 | app/routes/personalize.py |
| 8 | Update requirements.txt | None | requirements.txt |
| 9 | Update .env | None | .env |
| 10 | Delete openai_service.py | Steps 4-7 | app/services/openai_service.py |
Error Handling Strategy
| Scenario | HTTP Code | Handling |
|---|---|---|
| Invalid API key | 500 | Catch google.api_core.exceptions.InvalidArgument |
| Rate limit exceeded | 429 | Catch google.api_core.exceptions.ResourceExhausted |
| Service unavailable | 503 | Catch google.api_core.exceptions.ServiceUnavailable |
| Invalid JSON response | 500 | Catch JSON parse errors |
| General API error | 500 | Catch google.api_core.exceptions.GoogleAPIError |
Testing Strategy
Unit Tests
- GeminiService initialization with API key
- Async method signatures match OpenAIService
- JSON response parsing for personalization
Integration Tests
- Chat endpoint with Gemini backend
- Translation endpoint with Gemini backend
- Personalization endpoint with Gemini backend
- Embedding generation with Gemini backend
Manual Testing
- curl commands (see quickstart.md)
- Verify response quality comparable to OpenAI
- Verify error handling for invalid API key
Migration Checklist
- Config updated with GEMINI_API_KEY
- gemini_service.py created with all methods
- embeddings_service.py updated for Gemini
- All route files import gemini_service
- rag_service.py imports updated
- requirements.txt updated (openai removed, google-genai added)
- .env updated with GEMINI_API_KEY
- openai_service.py deleted
- All endpoints tested and working
- No OpenAI references remain in codebase
Related Artifacts
| Artifact | Path |
|---|---|
| Specification | specs/006-gemini-api-migration/spec.md |
| Research | specs/006-gemini-api-migration/research.md |
| Data Model | specs/006-gemini-api-migration/data-model.md |
| Quickstart | specs/006-gemini-api-migration/quickstart.md |
Risk Mitigation
| Risk | Mitigation |
|---|---|
| Embedding dimension mismatch | Documented in spec as out-of-scope; existing Qdrant data may need re-indexing |
| Model availability (experimental) | Monitor for stable release; can switch to gemini-2.0-flash when available |
| Response quality differences | Manual testing to verify comparable quality |
Next Steps
Run /sp.tasks to generate implementation tasks from this plan.