FastAPI Backend for an LLM

Design

  • Keep the API key on the server. The browser talks only to your backend.
  • Use the async client (AsyncAnthropic) so one worker can serve many waiting requests.
  • Validate input size, and return clear HTTP errors when the LLM provider fails.

The App

# main.py
import anthropic
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field

app = FastAPI(title="ShopEase Assistant")
client = anthropic.AsyncAnthropic()          # reads ANTHROPIC_API_KEY from the environment
SYSTEM = "You are the ShopEase support assistant. Answer in under 80 words."

class ChatIn(BaseModel):
    message: str = Field(min_length=1, max_length=2000)
    history: list[dict] = Field(default_factory=list, max_length=20)

class ChatOut(BaseModel):
    reply: str
    input_tokens: int
    output_tokens: int

@app.post("/chat", response_model=ChatOut)
async def chat(body: ChatIn):
    try:
        resp = await client.messages.create(
            model="claude-sonnet-5-5",
            max_tokens=1000,
            system=SYSTEM,
            messages=body.history + [{"role": "user", "content": body.message}],
        )
    except anthropic.RateLimitError:
        raise HTTPException(503, "Assistant is busy, please retry in a few seconds")
    except anthropic.APIStatusError as e:
        raise HTTPException(502, f"Upstream error {e.status_code}")
    text = next((b.text for b in resp.content if b.type == "text"), "")
    return ChatOut(reply=text, input_tokens=resp.usage.input_tokens, output_tokens=resp.usage.output_tokens)

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

Call it

curl -s -X POST http://localhost:8000/chat \
  -H "Content-Type: application/json" \
  -d '{"message": "What is the return window for shoes?"}'

Example output

{"reply":"Shoes can be returned within 10 days of delivery, as long as the tags are attached. Start a return from My Orders and we will schedule a pickup.","input_tokens":38,"output_tokens":41}