AI Hints
Home›Agentic AI Tutorial›Production
Agentic AI · Lesson 75 of 100

Serving an Agent

An API for Juno.

⏱ 2 min read▶ Video at 2:12:25🎓 Free
Watch this lesson in the Agentic AI courseWatch on YouTube ↗Full Agentic AI course

So far, Juno has lived in a script. For a website or an app to use it, it needs to be a service, with an address. Our ML Ops course goes deep into this. Here, we'll do what matters for agents.

Serving an Agent
From the video: Serving an Agent
  • Juno lives in a script
  • It needs an address
  • What matters for agents

An agent behind an API

FastAPI turns a function into a web service. Pydantic checks each request. Our endpoint would call the agent, and it returns a reply, plus a run ID for tracing. We use a test client, so there's no need for a real server. Run it. A good request gets a two hundred. A bad request is rejected automatically, with a four twenty two, before it reaches any model.

api.pypython
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel

app = FastAPI()

class Ticket(BaseModel):
    customer: str
    message: str

@app.post("/support")
def support(ticket: Ticket):
    reply = f"Hi {ticket.customer}, I'm checking that for you."
    return {"reply": reply, "run_id": "r-1042"}

client = TestClient(app)
r = client.post("/support", json={"customer": "Priya", "message": "Where is B42?"})
print(r.status_code, r.json())
print(client.post("/support", json={"customer": "Priya"}).status_code)
Output
200 {'reply': "Hi Priya, I'm checking that for you.", 'run_id': 'r-1042'}
422

Packaging it

A container packages the service, so that it runs the same way everywhere. Copy the requirements first, so that builds are faster. Run as a user who isn't root. And notice what's missing. There's no key here. Secrets are passed in when it runs, and they're never baked into the image.

Dockerfiledockerfile
FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
USER nobody
CMD ["uvicorn", "api:app", "--host", "0.0.0.0", "--port", "8000"]

Don't keep people waiting

Agents can take minutes, and web requests shouldn't. So accept the job at once, put it on a queue, and let workers handle it. Here, four tickets are accepted immediately, and two workers process them. Run it. Accepted first, and processed afterwards. The user gets a run ID straight away, and it can check back, or get a notification. In production, you'd use a real queue such as Redis or SQS.

queue_demo.pypython
import asyncio

async def worker(name, queue):
    while True:
        ticket = await queue.get()
        await asyncio.sleep(0.1)
        print(f"{name} finished {ticket}")
        queue.task_done()

async def main():
    queue = asyncio.Queue()
    workers = [asyncio.create_task(worker(f"worker-{i}", queue)) for i in (1, 2)]
    for n in range(1, 5):
        await queue.put(f"ticket-{n}")
    print("Accepted four tickets immediately")
    await queue.join()
    for w in workers:
        w.cancel()

asyncio.run(main())
Output
Accepted four tickets immediately
worker-1 finished ticket-1
worker-2 finished ticket-2
worker-1 finished ticket-3
worker-2 finished ticket-4

Limiting concurrency

Model providers have rate limits, and a burst can get you blocked. A semaphore caps how many calls run at once. Here, nine calls run, and never more than three together. Run it. They finish in three waves. Add that around all of your model calls, along with the backoff that you learned, and bursts stop being a problem.

limits.pypython
import asyncio, time

async def call_model(limit, n):
    async with limit:
        await asyncio.sleep(0.1)
        return n

async def main():
    limit = asyncio.Semaphore(3)
    start = time.time()
    done = await asyncio.gather(*[call_model(limit, n) for n in range(9)])
    print(f"{len(done)} calls, at most three at once: {time.time() - start:.1f}s")

asyncio.run(main())
Output
9 calls, at most three at once: 0.3s

Summary

An API, a container, a queue, and a rate limit. Next, the safest way to release an agent to real customers.

Key takeaways
  • FastAPI serves the agent
  • Long work goes on a queue
  • Cap concurrent model calls
Next up: Rolling Out Safely — from shadow mode to full launch