Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 87 additions & 27 deletions api/app.py
Original file line number Diff line number Diff line change
@@ -1,73 +1,133 @@
# Import required FastAPI components for building the API
from fastapi import FastAPI, HTTPException
from fastapi import FastAPI, HTTPException, Body
from fastapi.responses import StreamingResponse
from fastapi.middleware.cors import CORSMiddleware
# Import Pydantic for data validation and settings management
from pydantic import BaseModel
# Import OpenAI client for interacting with OpenAI's API
from openai import OpenAI
import os
from typing import Optional
from typing import Optional, List
from datetime import datetime
import json

# Initialize FastAPI application with a title
app = FastAPI(title="OpenAI Chat API")

@app.get("/health")
def health():
return {"status": "ok"}

# Configure CORS (Cross-Origin Resource Sharing) middleware
# This allows the API to be accessed from different domains/origins
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Allows requests from any origin
allow_credentials=True, # Allows cookies to be included in requests
allow_methods=["*"], # Allows all HTTP methods (GET, POST, etc.)
allow_headers=["*"], # Allows all headers in requests
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

# Define the data model for chat requests using Pydantic
# This ensures incoming request data is properly validated
# ---------- Models ----------
class ChatRequest(BaseModel):
developer_message: str # Message from the developer/system
user_message: str # Message from the user
model: Optional[str] = "gpt-4.1-mini" # Optional model selection with default
api_key: str # OpenAI API key for authentication
developer_message: str # system/developer guidance
user_message: str # user prompt
model: Optional[str] = "gpt-4.1-mini"
api_key: str # OpenAI API key for authentication

# For the coffee application example (response shape)
class CoffeePlace(BaseModel):
name: str
neighborhood: Optional[str] = None
why: str
signature_drink: Optional[str] = None
notes: Optional[str] = None

# Define the main chat endpoint that handles POST requests
class CoffeeResponse(BaseModel):
city: str
generated_at: str
results: List[CoffeePlace]
raw_text: Optional[str] = None # fallback if JSON parsing fails

# ---------- Streaming chat (keep this exactly as your general chat endpoint) ----------
@app.post("/api/chat")
async def chat(request: ChatRequest):
try:
# Initialize OpenAI client with the provided API key
client = OpenAI(api_key=request.api_key)

# Create an async generator function for streaming responses

async def generate():
# Create a streaming chat completion request
stream = client.chat.completions.create(
model=request.model,
messages=[
{"role": "developer", "content": request.developer_message},
{"role": "system", "content": request.developer_message}, # use valid role
{"role": "user", "content": request.user_message}
],
stream=True # Enable streaming response
stream=True
)

# Yield each chunk of the response as it becomes available
for chunk in stream:
if chunk.choices[0].delta.content is not None:
yield chunk.choices[0].delta.content

# Return a streaming response to the client
return StreamingResponse(generate(), media_type="text/plain")

except Exception as e:
# Handle any errors that occur during processing
raise HTTPException(status_code=500, detail=str(e))

# Define a health check endpoint to verify API status
@app.get("/api/health")
async def health_check():
return {"status": "ok"}

# ---------- Coffee application example (non-streaming, returns structured JSON) ----------
@app.post("/api/coffee", response_model=CoffeeResponse)
async def coffee_recommendations(
city: str = Body(..., embed=True),
api_key: str = Body(...),
model: str = Body("gpt-4.1-mini")
):
"""
Application Example: Recommend top coffee places for a city (e.g., 'San Diego').
Returns structured JSON; if parsing fails, returns raw_text with the model’s answer.
"""
try:
client = OpenAI(api_key=api_key)

system_prompt = (
"You are a coffee expert. Return ONLY JSON (no markdown). "
"Schema: [{\"name\":\"...\",\"neighborhood\":\"...\",\"why\":\"...\","
"\"signature_drink\":\"...\",\"notes\":\"...\"}] "
"Provide 5–7 entries. Keep 'why' short and practical."
)
user_prompt = f"Best coffee places in {city}. Include well-known local favorites."

completion = client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
)

content = completion.choices[0].message.content.strip()

try:
data = json.loads(content)
places = [CoffeePlace(**item) for item in data]
return CoffeeResponse(
city=city,
generated_at=datetime.utcnow().isoformat() + "Z",
results=places
)
except Exception:
return CoffeeResponse(
city=city,
generated_at=datetime.utcnow().isoformat() + "Z",
results=[],
raw_text=content
)

except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

# Entry point for running the application directly
if __name__ == "__main__":
import uvicorn
# Start the server on all network interfaces (0.0.0.0) on port 8000
uvicorn.run(app, host="0.0.0.0", port=8000)