The app entry point and its API
The entry point is the one module that exposes the assistant over HTTP: a FastAPI app with a /chat endpoint that takes a message, calls the assistant and returns its reply as JSON.
Last updated: 15 Oct, 2026 · LLMOps
The A prompt library in one place lesson was the last of the app's internal pieces. Something has to tie them together and let the outside world reach them, and in the repo that is main.py, a FastAPI app with a single POST endpoint. This lesson builds the same shape for the shop assistant and tests it with no server and no key, standing the model in with a deterministic call_api.
A deterministic stand-in for the model
A production assistant would send the message to a model, which needs a key and gives a different answer each run. The stand-in keeps the same shape, a prompt in and a string out, but answers from a small handbook, the same pattern the Agent Governance Toolkit course's shop bot uses, so the endpoint can be checked every run.
from fastapi import FastAPI
from pydantic import BaseModel
HANDBOOK = {
"refund": "Refunds are issued within 30 days of delivery.",
"shipping": "Standard shipping takes three to five working days.",
}
def call_api(prompt): # the deterministic stand-in for a model
for keyword, answer in HANDBOOK.items():
if keyword in prompt.lower():
return answer
return "Please contact the shop for help with that."The FastAPI app and its endpoint
The app declares the request body with a Pydantic model and exposes one POST route. The route hands the message to the stand-in and wraps the reply in JSON, the same contract a real model-backed endpoint would keep.
app = FastAPI()
class ChatRequest(BaseModel):
message: str
@app.post("/chat")
def chat(request: ChatRequest):
# one endpoint: take a message, return the assistant's reply
return {"reply": call_api(request.message)}Testing the endpoint without a server
FastAPI's TestClient calls the app in the same process, so a request runs the real routing and validation with no server to start and no port to open. It posts a message and reads the status and the JSON back.
from fastapi.testclient import TestClient
from app import app
client = TestClient(app)
# a POST to /chat, with no server running and no model key
response = client.post("/chat", json={"message": "How do I get a refund?"})
print("status:", response.status_code)
print("json:", response.json())status: 200
json: {'reply': 'Refunds are issued within 30 days of delivery.'}What the test client exercised
- The request was validated, because the JSON matched the
ChatRequestmodel; a body without amessagewould have come back as a 422 before the route ran. - The route returned a 200 with JSON, the same response a deployed server would send, so the endpoint's contract is tested without deploying it.
- The stand-in answered deterministically, so the test asserts a known reply. Swapping in a real model changes only
call_api, not the endpoint around it.
A script against an entry point with an API
| A script you run by hand | A FastAPI entry point | |
|---|---|---|
| Reached by | A person at a terminal | An HTTP request from anywhere |
| Input | Edited in the code | A validated request body |
| Output | Printed | JSON with a status code |
| Tested | By rerunning it | With a test client, no server |
Where the entry point fits
- Serving the assistant, where a web or mobile client posts a message and reads the reply.
- A contract to test against, where the test client checks the endpoint on every change with no deployment.
- The seam a container runs, where a server imports this module and the next part deploys it.
call_api, the test stops being deterministic, which is why the stand-in stays in the test suite and the real model runs in production.Related
- Previous: A prompt library in one place
- Next: Packaging: setup.py, pyproject and requirements
- Reference: The FastAPI documentation
- Post a message about shipping and confirm the handbook's shipping answer comes back.
- Send a body with no
messagefield and read the 422 the validation returns. - Add a
GET /healthroute that returns{"status": "ok"}and call it with the test client.
Slow is fine. Stopping is the only problem.