do lots of things

This commit is contained in:
Hossein 2025-11-16 18:30:04 +03:30
parent b33fa28c68
commit 207a4ae834
10 changed files with 832 additions and 170 deletions

188
.gitignore vendored
View File

@ -1,181 +1,29 @@
# Byte-compiled / optimized / DLL files
# Python
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
env/
ENV/
env.bak/
venv.bak/
.venv
# Spyder project settings
.spyderproject
.spyproject
# Environment variables
.env
# Rope project settings
.ropeproject
# IDE
.vscode/
.idea/
*.swp
*.swo
# mkdocs documentation
/site
# OS
.DS_Store
Thumbs.db
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
# Cursor
# Cursor is an AI-powered code editor.`.cursorignore` specifies files/directories to
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
# refer to https://docs.cursor.com/context/ignore-files
.cursorignore
.cursorindexingignore
# Project specific
*.png
*.jpg
*.jpeg
!*.example.*

42
Dockerfile Normal file
View File

@ -0,0 +1,42 @@
# Use Python 3.11 slim image as base
FROM python:3.11-slim
# ---- System setup ----
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
FFMPEG_BIN=/usr/bin/ffmpeg \
FFPROBE_BIN=/usr/bin/ffprobe
# Set working directory
WORKDIR /app
# Install system dependencies (ffmpeg + curl for healthcheck, build tools for pip)
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ffmpeg \
curl \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements first for better caching
COPY requirements.txt ./requirements.txt
# Install Python dependencies
RUN python -m pip install --upgrade pip \
&& pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Expose port
EXPOSE 8000
# Health check (uses curl we installed above)
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
CMD curl -fsS http://localhost:8000/health || exit 1
# Run the application
# For dev: keep --reload + volume mount
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]

105
POSTMAN_SETUP.md Normal file
View File

@ -0,0 +1,105 @@
# Postman Setup Guide - Step by Step
## Common Error: "Field required" for outfit_photo
This error means Postman isn't sending the file correctly. Follow these exact steps:
## ✅ Correct Setup in Postman
### Step 1: Create the Request
1. Click **New** → **HTTP Request**
2. Set method to **POST**
3. Enter URL: `http://localhost:8000/api/try-on/`
### Step 2: Configure Body (CRITICAL)
1. Click the **Body** tab
2. Select **form-data** (NOT raw, NOT x-www-form-urlencoded)
3. You should see a table with columns: Key | Value | Description
### Step 3: Add First File Field
1. In the **Key** column, type exactly: `user_photo`
2. Click the dropdown on the right side of that row (it will say "Text" by default)
3. Change it to **File** (NOT Text!)
4. Click **Select Files** button that appears
5. Choose your user photo image file
### Step 4: Add Second File Field
1. Click **Add Row** or just click in a new row
2. In the **Key** column, type exactly: `outfit_photo`
3. Click the dropdown on the right side (change from "Text" to **File**)
4. Click **Select Files** button
5. Choose your outfit photo image file
### Step 5: Verify Setup
Your form-data should look like this:
```
Key | Type | Value
-------------|------|----------
user_photo | File | [your file name]
outfit_photo | File | [your file name]
```
### Step 6: Send Request
- Click **Send**
- You should receive a PNG image as the response
## ❌ Common Mistakes
1. **Using "Text" instead of "File"** - This is the #1 mistake!
- Wrong: Key = `outfit_photo`, Type = Text, Value = file path
- Right: Key = `outfit_photo`, Type = File, Value = [Select Files button]
2. **Using wrong body type**
- Wrong: Body = raw (JSON)
- Wrong: Body = x-www-form-urlencoded
- Right: Body = form-data
3. **Wrong field names**
- Must be exactly: `user_photo` and `outfit_photo` (case-sensitive)
4. **Missing trailing slash**
- Wrong: `http://localhost:8000/api/try-on`
- Right: `http://localhost:8000/api/try-on/`
## Quick Test: Health Check First
Before testing try-on, verify the service is running:
1. Create a GET request
2. URL: `http://localhost:8000/health`
3. Click Send
4. Should return: `{"status": "ok"}`
If this fails, your service isn't running!
## Visual Checklist
When you look at your Postman request, you should see:
- ✅ Method: POST
- ✅ URL: `http://localhost:8000/api/try-on/`
- ✅ Body tab selected
- ✅ "form-data" radio button selected (not raw, not x-www-form-urlencoded)
- ✅ Two rows in the form-data table
- ✅ First row: Key = `user_photo`, Type = **File** (dropdown shows "File")
- ✅ Second row: Key = `outfit_photo`, Type = **File** (dropdown shows "File")
- ✅ Both rows have file names in the Value column
## Still Having Issues?
1. **Check service is running:**
```bash
# If using Docker:
docker-compose ps
# If using Python directly:
# Make sure uvicorn is running
```
2. **Check the service logs** for more details
3. **Try the FastAPI docs:**
- Open browser: `http://localhost:8000/docs`
- Use the interactive Swagger UI to test the endpoint
- This will show you exactly what the API expects

109
README.md Normal file
View File

@ -0,0 +1,109 @@
# AIFit Try-On Service
A FastAPI microservice that uses Google's Gemini API to generate virtual try-on images. Takes a user mirror selfie and an outfit reference image, then returns an edited image with the outfit applied.
## Setup
1. **Install dependencies** (preferably in a virtual environment):
```bash
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install -r requirements.txt
```
2. **Set up environment variables**:
- Create a `.env` file in the project root (same directory as `main.py`)
- Add your Gemini API key with **NO spaces** around the `=` sign:
```
GEMINI_API_KEY=YOUR_REAL_KEY_HERE
```
- **Important**:
- No quotes around the value
- No spaces: `GEMINI_API_KEY=key` ✅ (correct)
- Wrong: `GEMINI_API_KEY = key` ❌ (has spaces)
- Wrong: `GEMINI_API_KEY="key"` ❌ (has quotes)
- The `.env` file should be in the same directory as `docker-compose.yml`
## Running the Service
### Option 1: Direct Python
Start the FastAPI server:
```bash
uvicorn main:app --reload
```
### Option 2: Docker Compose
Build and run with Docker:
```bash
docker-compose up --build
```
The API will be available at:
- API: http://localhost:8000
- Interactive docs: http://localhost:8000/docs
- Alternative docs: http://localhost:8000/redoc
- Health check: http://localhost:8000/health
## API Endpoints
### GET `/health`
Health check endpoint for monitoring and Docker healthchecks.
**Response:**
```json
{"status": "ok"}
```
### POST `/api/try-on/`
Generate a try-on image by combining a user photo with an outfit reference.
**Parameters:**
- `user_photo` (file): User mirror/body photo (JPEG or PNG)
- `outfit_photo` (file): Collection/outfit photo (JPEG or PNG)
**Response:**
- Returns the generated image as `image/png`
**Example using curl:**
```bash
curl -X POST "http://localhost:8000/api/try-on/" \
-F "user_photo=@user_selfie.jpg" \
-F "outfit_photo=@outfit_reference.jpg" \
--output result.png
```
**Testing in Postman:**
1. **Health Check:**
- Method: `GET`
- URL: `http://localhost:8000/health`
- No body or headers needed
- Expected response: `{"status": "ok"}`
2. **Try-On Endpoint:**
- Method: `POST`
- URL: `http://localhost:8000/api/try-on/`
- Body tab → Select `form-data`
- Add two fields:
- `user_photo` (type: **File**) → Select your user mirror/body photo
- `outfit_photo` (type: **File**) → Select your collection/outfit photo
- Click **Send**
- Response will be an image (PNG format)
- To save: Click "Save Response" → "Save to a file"
**Import Postman Collection:**
You can import `postman_collection.json` into Postman for pre-configured requests.
## Notes
- The service uses `gemini-2.5-flash-image` model
- CORS is enabled for all origins (tighten in production)
- Images are validated before processing
- The service handles errors gracefully with appropriate HTTP status codes
- Docker healthcheck is configured to use the `/health` endpoint
- For production, remove `--reload` from the Dockerfile CMD and remove volume mounts from docker-compose.yml

135
TROUBLESHOOTING.md Normal file
View File

@ -0,0 +1,135 @@
# Troubleshooting Guide
## API Key Not Loading from .env
### Step 1: Verify .env File Exists and Location
The `.env` file must be in the **project root** (same directory as `docker-compose.yml` and `main.py`).
Check if it exists:
```bash
# Windows PowerShell
Test-Path .env
# Should return: True
```
### Step 2: Check .env File Format
Your `.env` file should look exactly like this:
```
GEMINI_API_KEY=AIzaSyYourActualKeyHere123456789
```
**Common mistakes:**
- ❌ `GEMINI_API_KEY = value` (spaces around =)
- ❌ `GEMINI_API_KEY="value"` (quotes)
- ❌ `GEMINI_API_KEY='value'` (single quotes)
- ❌ `GEMINI_API_KEY= value` (space after =)
- ✅ `GEMINI_API_KEY=value` (correct!)
### Step 3: Test if API Key is Loaded
1. **If using Docker:**
```bash
docker-compose restart
docker-compose logs aifit-fastapi
```
Look for: `✓ GEMINI_API_KEY loaded: AIzaSy...`
2. **Use the debug endpoint:**
- Open browser: `http://localhost:8000/debug/api-key-status`
- Or in Postman: GET `http://localhost:8000/debug/api-key-status`
- Should return:
```json
{
"status": "ok",
"message": "API key is loaded",
"key_length": 39,
"key_preview": "AIzaSy...",
"key_ends_with": "xyz"
}
```
### Step 4: If Using Docker - Restart After Creating .env
If you created the `.env` file after starting Docker:
1. Stop the container:
```bash
docker-compose down
```
2. Start again:
```bash
docker-compose up --build
```
Docker needs to be restarted to pick up new `.env` files.
### Step 5: Alternative - Set Environment Variable Directly
If `.env` still doesn't work, you can set it directly in `docker-compose.yml`:
```yaml
environment:
- GEMINI_API_KEY=your_actual_key_here
# ... other vars
```
**⚠️ Warning:** Don't commit this to git! Use `.env` file instead.
### Step 6: Verify API Key is Valid
1. Check the key starts with `AIza` (most Gemini keys do)
2. Make sure there are no extra characters or line breaks
3. Try regenerating the key from Google AI Studio if needed
### Step 7: Check Docker Logs
```bash
docker-compose logs aifit-fastapi
```
Look for:
- `✓ GEMINI_API_KEY loaded: ...` = Good!
- `⚠ API key seems too short` = Key might be truncated
- `GEMINI_API_KEY not set` = File not found or wrong format
## Quick Fix Checklist
- [ ] `.env` file exists in project root
- [ ] `.env` file has exactly: `GEMINI_API_KEY=your_key` (no spaces, no quotes)
- [ ] Restarted Docker after creating/editing `.env`
- [ ] Checked `/debug/api-key-status` endpoint
- [ ] Verified API key is valid (starts with `AIza` typically)
- [ ] Checked Docker logs for loading confirmation
## Still Not Working?
1. **Test without Docker:**
```bash
# Activate venv
python -m venv venv
venv\Scripts\activate # Windows
# Install deps
pip install -r requirements.txt
# Run directly (will use .env)
uvicorn main:app --reload
```
2. **Check if python-dotenv is installed:**
```bash
pip list | findstr dotenv
```
3. **Manually set environment variable:**
```bash
# Windows PowerShell
$env:GEMINI_API_KEY="your_key_here"
uvicorn main:app --reload
```

28
docker-compose.yml Normal file
View File

@ -0,0 +1,28 @@
version: '3.8'
services:
aifit-fastapi:
build:
context: .
dockerfile: Dockerfile
container_name: aifit-fastapi
ports:
- "8000:8000"
volumes:
- .:/app # hot-reload for dev; remove in prod
env_file:
- .env # must contain GEMINI_API_KEY=...
environment:
- PYTHONPATH=/app
- FFMPEG_BIN=/usr/bin/ffmpeg
- FFPROBE_BIN=/usr/bin/ffprobe
# Explicitly pass GEMINI_API_KEY from .env (fallback if env_file doesn't work)
- GEMINI_API_KEY=${GEMINI_API_KEY}
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s

261
main.py Normal file
View File

@ -0,0 +1,261 @@
import os
import json
from io import BytesIO
from pprint import pprint
from fastapi import FastAPI, UploadFile, File, Form, HTTPException
from fastapi.responses import Response
from fastapi.middleware.cors import CORSMiddleware
from dotenv import load_dotenv
from google import genai
from google.genai.types import Part
from PIL import Image
# --- Load .env (for local development) ---
load_dotenv()
# ---- Gemini SDK Setup ----
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
# Strip whitespace (common issue with .env files)
if GEMINI_API_KEY:
GEMINI_API_KEY = GEMINI_API_KEY.strip()
if not GEMINI_API_KEY:
raise RuntimeError("GEMINI_API_KEY not set")
client = genai.Client(api_key=GEMINI_API_KEY)
IMAGE_MODEL = "gemini-2.5-flash-image"
app = FastAPI()
# (Optional) CORS if you call it from frontend directly
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # tighten in production
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
def find_first_image_part(resp):
"""Return the actual output image part."""
if not getattr(resp, "candidates", None):
return None
for cand in resp.candidates:
content = getattr(cand, "content", None)
if not content or not getattr(content, "parts", None):
continue
for p in content.parts:
if hasattr(p, "inline_data") and getattr(p.inline_data, "data", None):
return p.inline_data
if hasattr(p, "blob") and getattr(p.blob, "data", None):
return p.blob
return None
@app.get("/health")
def health():
return {"status": "ok"}
@app.post("/api/try-on/", summary="Try-on (user_photo + outfit_photo + metadata)")
async def try_on(
user_photo: UploadFile = File(...),
outfit_photo: UploadFile = File(...),
metadata: str = Form("", description="JSON or text describing the outfit items"),
):
"""
metadata can be:
- free text: 'black hoodie, patterned shirt, white shorts'
- JSON: '{"outer": "hoodie", "inner": "shirt", "bottom": "shorts"}'
- any additional instructions
"""
# ---- Read files ----
user_bytes = await user_photo.read()
outfit_bytes = await outfit_photo.read()
if not user_bytes:
raise HTTPException(status_code=400, detail="user_photo is empty")
if not outfit_bytes:
raise HTTPException(status_code=400, detail="outfit_photo is empty")
# ---- Parse metadata if JSON ----
metadata_text = ""
try:
meta = json.loads(metadata)
metadata_text = json.dumps(meta, indent=2)
except:
metadata_text = metadata.strip()
# ---- Build Prompt ----
prompt = f"""
EDIT TASK FULL OUTFIT REPLACEMENT
FIRST image = user body/mirror selfie
SECOND image = outfit reference
Follow these rules:
1. Edit ONLY the body/clothing of the FIRST image.
2. Replace ALL clothing with the items visible in the SECOND image.
3. Keep the user's:
- face
- skin
- hands
- phone
- pose
- environment
EXACTLY the same.
4. IF a metadata description is given, use it to understand layers:
Metadata:
{metadata_text}
5. Ensure:
- realistic lighting
- correct fabric physics
- correct drape
- realism
- no blending of people
- no copying faces from the second image
6. Output ONE final edited image.
IMPORTANT:
- FIRST = user_photo
- SECOND = outfit_photo
"""
# ---- Wrap image files ----
user_part = Part.from_bytes(data=user_bytes, mime_type=user_photo.content_type or "image/jpeg")
outfit_part = Part.from_bytes(data=outfit_bytes, mime_type=outfit_photo.content_type or "image/jpeg")
# ---- Call Gemini ----
print(f"\n[DEBUG] Using model: {IMAGE_MODEL}")
print(f"[DEBUG] Metadata received: {metadata[:100] if metadata else '(empty)'}")
try:
response = client.models.generate_content(
model=IMAGE_MODEL,
contents=[
prompt,
user_part,
outfit_part,
"First image = user selfie. Second image = outfit reference.",
],
)
except Exception as e:
error_msg = str(e)
# Check if it's an API key error
if "API key" in error_msg or "INVALID_ARGUMENT" in error_msg or "API_KEY" in error_msg:
raise HTTPException(
status_code=401,
detail=(
f"Gemini API key error: {error_msg}. "
"Please verify your GEMINI_API_KEY in the .env file is correct."
)
)
# Log the full error for debugging
import traceback
print(f"[ERROR] Gemini API error: {error_msg}")
print(traceback.format_exc())
raise HTTPException(status_code=502, detail=f"Gemini error: {error_msg}")
# ---- Debug: Inspect response ----
print("\n[DEBUG] === Gemini Response Analysis ===")
if not getattr(response, "candidates", None):
print("[DEBUG] No candidates at all. Full response:")
pprint(response)
else:
cand = response.candidates[0]
finish_reason = getattr(cand, "finish_reason", None)
print(f"[DEBUG] Finish reason: {finish_reason}")
print(f"[DEBUG] Safety ratings: {getattr(cand, 'safety_ratings', None)}")
if hasattr(cand, "content") and getattr(cand.content, "parts", None):
print(f"[DEBUG] Parts in first candidate ({len(cand.content.parts)} total):")
for i, part in enumerate(cand.content.parts):
part_type = type(part).__name__
print(f" Part {i}: type={part_type}")
# Check for text
text = getattr(part, "text", None)
if text:
print(f" TEXT: {text[:300]}")
# Check for inline_data
if hasattr(part, "inline_data"):
inline = part.inline_data
if inline:
data_size = len(getattr(inline, "data", b""))
mime = getattr(inline, "mime_type", "unknown")
print(f" INLINE_DATA: mime={mime}, size={data_size} bytes")
# Check for blob
if hasattr(part, "blob"):
blob = part.blob
if blob:
data_size = len(getattr(blob, "data", b""))
mime = getattr(blob, "mime_type", "unknown")
print(f" BLOB: mime={mime}, size={data_size} bytes")
print("[DEBUG] === End Response Analysis ===\n")
# ---- Extract output image ----
img_part = find_first_image_part(response)
if not img_part or not getattr(img_part, "data", None):
# Try to extract any text message from Gemini
message = "No image returned from Gemini."
if getattr(response, "candidates", None) and len(response.candidates) > 0:
cand = response.candidates[0]
finish_reason = getattr(cand, "finish_reason", "unknown")
# Extract text explanation from Gemini
if hasattr(cand, "content") and getattr(cand.content, "parts", None):
for part in cand.content.parts:
txt = getattr(part, "text", None)
if txt:
message += f" Gemini says: {txt[:500]}"
break
# Check safety ratings
if hasattr(cand, "safety_ratings"):
safety_ratings = cand.safety_ratings
if safety_ratings:
blocked = [
f"{getattr(r, 'category', 'unknown')}"
for r in safety_ratings
if hasattr(r, "blocked") and getattr(r, "blocked", False)
]
if blocked:
message += f" Safety blocked: {', '.join(blocked)}"
raise HTTPException(
status_code=502,
detail=f"{message} Finish reason: {finish_reason}",
)
else:
raise HTTPException(
status_code=502,
detail="No image returned from Gemini. No candidates in response.",
)
raw = img_part.data
# Ensure valid image
try:
Image.open(BytesIO(raw))
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"Returned image is invalid: {e}. Image size: {len(raw)} bytes"
)
return Response(content=raw, media_type="image/png")

68
postman_collection.json Normal file
View File

@ -0,0 +1,68 @@
{
"info": {
"name": "AIFit Try-On Service",
"description": "Collection for testing the AIFit FastAPI service",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Health Check",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "http://localhost:8000/health",
"protocol": "http",
"host": [
"localhost"
],
"port": "8000",
"path": [
"health"
]
},
"description": "Check if the service is running and healthy"
},
"response": []
},
{
"name": "Try-On",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "formdata",
"formdata": [
{
"key": "user_photo",
"type": "file",
"src": [],
"description": "User mirror/body photo (JPEG or PNG)"
},
{
"key": "outfit_photo",
"type": "file",
"src": [],
"description": "Collection/outfit photo (JPEG or PNG)"
}
]
},
"url": {
"raw": "http://localhost:8000/api/try-on/",
"protocol": "http",
"host": [
"localhost"
],
"port": "8000",
"path": [
"api",
"try-on",
""
]
},
"description": "User photo + outfit photo → edited image"
},
"response": []
}
]
}

7
requirements.txt Normal file
View File

@ -0,0 +1,7 @@
fastapi
uvicorn[standard]
google-genai
Pillow
python-multipart
python-dotenv

59
test_api_key.py Normal file
View File

@ -0,0 +1,59 @@
"""
Simple script to test if your Gemini API key is working.
Run this to verify your API key before using it in the FastAPI service.
"""
import os
from dotenv import load_dotenv
from google import genai
# Load .env
load_dotenv()
# Get API key
api_key = os.getenv("GEMINI_API_KEY")
if not api_key:
print("[ERROR] GEMINI_API_KEY not found in .env file or environment")
print("\nMake sure you have a .env file with:")
print("GEMINI_API_KEY=your_key_here")
exit(1)
# Strip whitespace
api_key = api_key.strip()
print(f"[OK] API Key found: {api_key[:10]}... (length: {len(api_key)})")
# Validate format
if not api_key.startswith("AIza"):
print("[WARNING] API key doesn't start with 'AIza' - this might be incorrect")
# Test the API key
print("\nTesting API key with Gemini...")
try:
client = genai.Client(api_key=api_key)
# Try a simple test call
print("Making test API call...")
response = client.models.generate_content(
model="gemini-2.5-flash-image",
contents=["Generate a simple test image of a red circle."],
)
print("\n[SUCCESS] API key is valid and working!")
print("You can now use this key in your FastAPI service.")
except Exception as e:
error_msg = str(e)
print(f"\n[ERROR] API key test failed")
print(f"Error: {error_msg}")
if "API key" in error_msg or "INVALID_ARGUMENT" in error_msg:
print("\n[TROUBLESHOOTING]")
print("1. Check that your API key is correct (no typos)")
print("2. Verify the key in your .env file has no spaces or quotes")
print("3. Make sure the key is from Google AI Studio: https://aistudio.google.com/app/apikey")
print("4. Try regenerating a new API key")
print("5. Check if the API key has expired or been revoked")
else:
print(f"\nUnexpected error: {e}")