MuhammadSaad16's picture
Upload 112 files
971b4ea verified

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.