A FastAPI REST API that analyzes free-form note text and returns structured insights: summary, key points, tone, and which LLM provider handled the request.
Models are integrated in a pluggable way (Groq, OpenAI, Gemini) through a single business flow and Pydantic validation.
| Component | Role |
|---|---|
| Python 3.10+ (recommended; 3.9 may work with dependency warnings) | Language |
| FastAPI | HTTP framework, request/response validation, OpenAPI/Swagger |
| Uvicorn | ASGI server |
| Pydantic v2 | AnalyzeRequest / AnalyzeResponse schemas |
| python-dotenv | Load variables from .env on startup |
| openai (official SDK) | OpenAI client and OpenAI API–compatible providers (e.g. Groq via base_url) |
| google-genai | Google Gemini client (developer API) |
smart-notes-api/
├── app/
│ ├── main.py # FastAPI app, .env loading
│ ├── exceptions.py # AnalysisError (business errors → HTTP)
│ ├── routers/
│ │ └── notes.py # POST /analyze
│ ├── schemas/
│ │ └── notes.py # Pydantic request/response models
│ └── services/
│ ├── notes_service.py # Wires analyze_note → analyze_text
│ └── llm_provider.py # LLM providers, prompt, fallback
├── requirements.txt
├── LICENSE.md
└── README.md
python3 -m venv .venv
source .venv/bin/activate # macOS / Linux
# .venv\Scripts\activate # Windowspip install -r requirements.txtCopy app/.env.example to app/.env (or use a .env at the repo root) and fill in the keys you plan to use.
On startup, app/main.py loads, in order:
app/.env.envat the project root
Variables not set in the shell are read from these files.
uvicorn app.main:app --reloadBase URL: http://127.0.0.1:8000
Core behavior lives in app/services/llm_provider.py.
| Variable | Values | Default |
|---|---|---|
LLM_PROVIDER |
groq, openai, gemini |
groq |
LLM_FALLBACK_ENABLED |
true / false (and equivalents 1, yes, on) |
true |
LLM_PROVIDERsets which backend is tried first.- If
LLM_FALLBACK_ENABLED=true, after a failure (API error, quota, etc.) other providers are tried in a fixed order: groq → openai → gemini, but only if an API key exists for that provider. Thellm_providerfield in the response shows which one succeeded (useful when fallback ran).
| Provider | Variables | Default model (if not overridden) |
|---|---|---|
| Groq | GROQ_API_KEY, optional GROQ_MODEL |
llama-3.3-70b-versatile |
| OpenAI | OPENAI_API_KEY, optional OPENAI_MODEL |
gpt-4o-mini |
| Gemini | GEMINI_API_KEY or GOOGLE_API_KEY, optional GEMINI_MODEL |
gemini-2.0-flash |
- Groq: key at console.groq.com/keys. The client uses the OpenAI-compatible API with a fixed
base_url:https://api.groq.com/openai/v1(you do not need to set it in.env). - OpenAI: key at platform.openai.com.
- Gemini: key at Google AI Studio; SDK:
google-genai.
If no provider has a configured key, the API returns an error indicating missing configuration.
analyze_text(text)(llm_provider.py) picks a provider fromLLM_PROVIDERand applies optional fallback.- OpenAI and Groq share the same path: the official
OpenAISDK client; Groq only differs byapi_keyandbase_url. - Gemini uses the
google.genaiclient (genai.Client),generate_contentwithresponse_mime_type="application/json"andsystem_instructionusing the same system text as the others. - The model is asked for JSON-only output with fixed fields (see prompt below). Where supported,
response_format: { "type": "json_object" }is used (OpenAI-compatible). - JSON is parsed, validated against an internal
_LLMJsonOutputmodel (summary,key_points,tone), thenAnalyzeResponseis built by addingllm_providerwith the id of the backend that responded.
Provider errors are wrapped in AnalysisError (app/exceptions.py) with status_code and message; the router maps them to HTTPException for HTTP clients.
The prompt is defined as SYSTEM_PROMPT in llm_provider.py. It asks for a single JSON object with no markdown, containing:
summary(string)key_points(array of strings)tone(string)
To change analysis behavior, edit that block (and if you change the JSON shape, update the Pydantic schemas accordingly).
Defined in app/schemas/notes.py (used in Swagger/ReDoc and validation):
| Model | Role |
|---|---|
AnalyzeRequest |
Input: text (string, minimum 1 character). |
AnalyzeResponse |
Output: summary, key_points, tone, llm_provider (backend id: e.g. groq, openai, gemini). |
The router is unchanged: it still returns AnalyzeResponse on POST /analyze.
Health check.
Example response:
{
"status": "ok",
"message": "Smart Notes API is running."
}Body:
{
"text": "Today's team meeting was productive; we set priorities for the quarter."
}Example response (current shape):
{
"summary": "…",
"key_points": ["…", "…"],
"tone": "neutral",
"llm_provider": "groq"
}Common errors: 401 / 429 / 502 / 503 / 504 depending on authentication, limits, provider failures, or missing configuration. The body usually includes detail with a readable message.
| UI | URL |
|---|---|
| Swagger UI | http://127.0.0.1:8000/docs |
| ReDoc | http://127.0.0.1:8000/redoc |
No provider guarantees unlimited free usage forever; policies change. In practice:
- Groq, Google AI (Gemini), and OpenAI often offer free or trial tiers with limits (per minute, per day, per project).
- Messages like rate limit or quota exceeded usually mean you hit the free tier cap, need to wait, or the project requires billing to raise quotas (depends on provider and account).
- For learning and integration, paying is not required; for real traffic or production, check each console: Billing, Quotas, and Usage.
This repository does not handle payments—it only calls APIs with the keys you configure.
The code is released under the MIT License. See LICENSE.md.
Render’s default Python is 3.14.x for new services. This project pins Python 3.12 via .python-version in the repo root so pip installs binary wheels for pydantic-core instead of building from source (building would require Rust and can fail on read-only build environments).
If you override the version in the Render dashboard, set PYTHON_VERSION to a full version (e.g. 3.12.8)—see Render: Python version.
Start command example:
uvicorn app.main:app --host 0.0.0.0 --port $PORTSet PORT from Render’s environment (Render injects it). Configure LLM keys under Environment in the dashboard (same variables as in .env.example).
- Authentication (your own API key, JWT, etc.)
- Persist notes in a database
- Automated tests (pytest) with mocked LLM responses
- Deployment (Docker, Railway, Fly.io, Render, etc.)