Build AI Apps with Python: A Practical Stack Guide
Building AI apps with Python means wiring together a language model API, a web framework to expose your logic, and enough structure to handle streaming, errors, and state without the whole thing turning into a pile of scripts. Python is the default choice here because every major LLM provider ships a Python SDK first, the async ecosystem (FastAPI, httpx, asyncio) handles streaming responses well, and the data/ML tooling you'll eventually need — pandas, numpy, pydantic — is already native to the language.
This guide walks through the actual stack: what to install, how to structure a minimal AI app, and where things typically break (streaming, rate limits, tool calling, cost tracking) so you don't have to discover them the hard way.
The minimal stack
You don't need much to get a working AI app. The core pieces are:
- HTTP client —
httpxfor async requests, or the provider's official SDK - Web framework — FastAPI if you're building an API or backend service; Streamlit or Gradio if you want a quick UI
- Validation —
pydanticfor request/response schemas, which pairs naturally with FastAPI - Environment management —
python-dotenvfor API keys,uvorpoetryfor dependencies
A bare-bones request loop looks like this:
import os
import httpx
API_KEY = os.environ["SUBTOAPI_KEY"]
async def ask(prompt: str) -> str:
async with httpx.AsyncClient() as client:
resp = await client.post(
"https://api.subtoapi.app/v1/messages",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"model": "claude-sonnet-4",
"max_tokens": 1024,
"messages": [{"role": "user", "content": prompt}],
},
)
resp.raise_for_status()
data = resp.json()
return data["content"][0]["text"]
That's a complete, working call. Everything else you add — streaming, tools, retries, caching — is layered on top of this base pattern.
Structuring the app, not just the API call
Once you go past a single script, the useful separation is: a thin API layer, a service layer that talks to the model, and a persistence layer if you need conversation history or usage tracking.
app/
api/ # FastAPI routes
services/ # LLM calls, tool execution, business logic
models/ # pydantic schemas
storage/ # database or file-based history
FastAPI routes should stay small — parse the request, call a service function, return the response. Put retry logic, prompt construction, and error handling in the service layer so it's testable without spinning up a server.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class ChatRequest(BaseModel):
message: str
@app.post("/chat")
async def chat(req: ChatRequest):
reply = await ask(req.message)
return {"reply": reply}
This is enough to deploy behind a load balancer, add auth middleware, or expose to a frontend.
Streaming responses without blocking
If your app has any kind of chat interface, streaming matters — users wait less and perceive the app as faster. In Python this means using Server-Sent Events or a chunked response, consumed with an async generator.
import json
import httpx
async def stream_reply(prompt: str):
async with httpx.AsyncClient() as client:
async with client.stream(
"POST",
"https://api.subtoapi.app/v1/messages",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"model": "claude-sonnet-4",
"max_tokens": 1024,
"stream": True,
"messages": [{"role": "user", "content": prompt}],
},
) as resp:
async for line in resp.aiter_lines():
if line.startswith("data:"):
yield line[5:].strip()
FastAPI can wrap this in a StreamingResponse, and on the frontend you consume it with EventSource or fetch with a readable stream. Details on the event format are in the streaming docs if you're integrating this against SubToAPI directly.
Adding tool use
Most real AI apps eventually need the model to call functions — look up a record, hit an internal API, run a calculation. The pattern is: define tool schemas, send them with the request, and when the model returns a tool call, execute it in your own code and send the result back.
tools = [
{
"name": "get_order_status",
"description": "Look up the status of an order by ID",
"input_schema": {
"type": "object",
"properties": {"order_id": {"type": "string"}},
"required": ["order_id"],
},
}
]
The model decides when to call the tool; your Python code stays in control of what actually executes, which matters for anything touching a database or external system. See /docs/tools for the exact request/response shape.
Where SubToAPI fits
If you already have Claude access through a subscription rather than a pay-per-token API account, wiring that into a Python app directly is awkward — there's no clean HTTPS API meant for application traffic. SubToAPI turns that subscription into a proper API: you get sub_live_... application keys, standard REST endpoints for messages, streaming and tool use, and usage metadata per key so you can see what each app or teammate is consuming. It's a drop-in replacement for the httpx calls above — same JSON shape, same streaming format — which means you can build the app once and not rewrite it if you change how you're billed for model access.
Setup is a signup and one API key: check /docs/quickstart for the five-minute version, or /docs/messages for the full request reference. Plans start at Solo (€9), with Team (€19/seat) and Scale (€49/seat) for multi-key setups — see /pricing. There's a free trial at /signup if you want to test the stack above against it before committing.
Testing and deploying
Keep model calls behind an interface you can mock in tests — don't hit a live API in your test suite. pytest with respx (for mocking httpx) covers this well. For deployment, a FastAPI app behind uvicorn/gunicorn on any container platform works fine; there's nothing AI-specific about the deployment story once the API layer is built correctly.
questions
Do I need a GPU to build AI apps with Python? No. If you're calling a hosted model API (Claude, or a SubToAPI-backed endpoint), all inference happens on the provider's infrastructure. You only need a GPU if you're running or fine-tuning open-weight models locally.
Which Python framework should I use: FastAPI or Flask? FastAPI is the better default for AI apps because of native async support and built-in request validation via pydantic, which matters when you're streaming responses or defining tool schemas.
How do I avoid hardcoding API keys in a Python AI app? Load them from environment variables with python-dotenv locally and your platform's secrets manager in production. Never commit keys to source control, and rotate them if you're granting per-teammate access.