DS DevShelfHub Projects · AI tools
Articles / Build and Deploy a Python AI Agent in 20 Minutes: FastAPI + LangGraph + Vercel

AI Engineering

Deploy a Python AI Agent in 20 Minutes: FastAPI + LangGraph

By DevShelfHub

A complete walkthrough for shipping a real AI agent to a public URL in under twenty minutes. FastAPI backend, LangChain + LangGraph + OpenAI for the agent and tool calling, a tiny Jinja2 + JavaScript chat UI, and Vercel CLI for one-command deploys. Plus the .vercelignore trick that keeps your API key out of the deployment bundle, how to add env vars safely, and the read-only filesystem gotcha that bites every first deploy.

Deploy a Python AI Agent in 20 Minutes: FastAPI + LangGraph

Introduction

Building an AI agent in Python is the easy part. Getting it in front of other people without a server, a Kubernetes cluster, or three days of DevOps — that’s where most weekend projects die. This guide ships a working AI agent with a chat UI, a real backend, and a public URL you can send to someone, all in roughly twenty minutes.

The stack: FastAPI for the backend, LangChain + LangGraph + OpenAI for the agent, Jinja2 + a tiny HTML template for the frontend, and Vercel CLI for the deploy. No GitHub repo required, no Docker, no infrastructure. Just vc deploy.

📚 Table of contents

  • The architecture in one diagram
  • Install Node and the Vercel CLI
  • Scaffold a FastAPI project with vc init
  • Define tools the agent can call
  • Wire up the LangGraph agent
  • Add the FastAPI routes
  • The tiny HTML chat UI
  • Local test with uvicorn
  • Deploy with one command
  • Add environment variables the safe way
  • Watch out for the read-only filesystem on Vercel
  • Common mistakes
  • FAQs

The architecture in one diagram

Browser → calls POST /agent on FastAPI with a prompt → FastAPI passes the prompt to the LangGraph agent → agent invokes the OpenAI model, optionally calls tools, returns the result → FastAPI sends JSON back to the browser → the chat UI renders the response.

Everything ships as one FastAPI app. Vercel runs it as a serverless Python function.

Install Node and the Vercel CLI

Vercel’s CLI is a Node package. Install Node from nodejs.org, then:

Bash
npm i -g vercel
vercel    # first run prompts you to sign in via browser

Sign up at vercel.com (free tier is generous), authorize the CLI when prompted, then Ctrl+C out.

Scaffold a FastAPI project with vc init

Bash
mkdir deploy-ai-agent && cd deploy-ai-agent
vc init fastapi

Move the scaffolded files up one directory so everything sits at the root, then update requirements.txt:

requirements.txt
fastapi
uvicorn
jinja2
langchain
langchain-openai
langgraph
python-dotenv

Define tools the agent can call

Create agent.py. Start with two demonstration tools — read a note, write a note:

agent.py
from dotenv import load_dotenv
load_dotenv()

from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langgraph.prebuilt import create_react_agent

@tool
def read_note() -> str:
    """Read the contents of the user's notes file and return them."""
    try:
        with open("note.txt", "r") as f:
            return f.read()
    except FileNotFoundError:
        return "No note exists yet."

@tool
def write_note(content: str) -> str:
    """Append a new note to the user's notes file. Pass the content to add."""
    with open("note.txt", "a") as f:
        f.write(content + "\n")
    return "Note saved."

Three things matter when defining tools:

  • Function name — the LLM uses it to decide when to call.
  • Docstring — the LLM reads this to understand what the tool does. Be specific.
  • Type annotations — the LLM uses these to format arguments correctly.

The @tool decorator is the magic; LangChain handles the rest.

Wire up the LangGraph agent

agent.py (continued)
tools = [read_note, write_note]
system_message = (
    "You are a friendly assistant who can read and write personal notes "
    "on behalf of the user. Use the tools provided when relevant."
)

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
agent = create_react_agent(llm, tools=tools, prompt=system_message)

def run_agent(user_input: str) -> str:
    try:
        result = agent.invoke({"messages": [{"role": "user", "content": user_input}]})
        return result["messages"][-1].content
    except Exception as e:
        return f"Agent error: {e}"

Add .env with your OpenAI key:

.env
OPENAI_API_KEY=sk-...

Get the key from platform.openai.com/api-keys — requires a card on file but inference for this demo costs cents.

Add the FastAPI routes

Two routes: home (serves the HTML), agent (handles chat messages). In main.py:

main.py
from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel

from agent import run_agent

app = FastAPI()
templates = Jinja2Templates(directory="templates")

class AgentRequest(BaseModel):
    prompt: str

class AgentResponse(BaseModel):
    reply: str

@app.get("/")
async def home(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

@app.post("/agent", response_model=AgentResponse)
async def agent_endpoint(req: AgentRequest):
    return AgentResponse(reply=run_agent(req.prompt))

The tiny HTML chat UI

Create templates/index.html:

templates/index.html
<!doctype html>
<html>
  <head>
    <title>My AI Agent</title>
    <style>
      body { font-family: system-ui; max-width: 640px; margin: 2rem auto; padding: 0 1rem; }
      textarea { width: 100%; height: 100px; }
      .reply { margin-top: 1rem; padding: 1rem; background: #f4f4f5; border-radius: 8px; }
    </style>
  </head>
  <body>
    <h1>Ask my agent</h1>
    <form id="f">
      <textarea id="prompt" placeholder="Ask anything..."></textarea>
      <button type="submit">Send</button>
    </form>
    <div id="reply" class="reply"></div>
    <script>
      const f = document.getElementById("f");
      const reply = document.getElementById("reply");
      f.addEventListener("submit", async (e) => {
        e.preventDefault();
        const prompt = document.getElementById("prompt").value;
        reply.textContent = "Thinking...";
        const r = await fetch("/agent", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ prompt }),
        });
        const data = await r.json();
        reply.textContent = data.reply;
      });
    </script>
  </body>
</html>

85 lines of HTML, no framework. The browser POSTs to /agent; FastAPI does the rest.

Local test with uvicorn

Bash
pip install -r requirements.txt
uvicorn main:app --reload --port 8000
# open http://localhost:8000

Type a prompt, get a response. If it works locally, the deploy is just a CLI command away.

Deploy with one command

Bash
vc deploy

Answer the prompts: yes to setup, pick your account, name the project, skip the linking, accept defaults. Vercel uploads the code and prints a preview URL. Open it — your agent is live.

Add environment variables the safe way

By default Vercel deploys whatever’s in the directory — including your .env file if you’re not careful. Add a .vercelignore to exclude it:

.vercelignore
.env

Then add the variable to Vercel directly — encrypted at rest, not in the deployment bundle:

Bash
vc env add OPENAI_API_KEY
# Paste the key, choose all environments (production / preview / dev)
vc deploy   # redeploy now that env var is wired in

Watch out for the read-only filesystem on Vercel

The two demo tools (read_note / write_note) work locally but fail on Vercel’s free tier. Serverless functions run on an ephemeral, partially read-only filesystem — you can’t persist files between requests, and many directories aren’t writable at all.

Real tools for production deploys should use a real backing store:

  • Postgres or SQLite-on-Turso for structured data.
  • Vercel KV (Redis) or Upstash for key-value state.
  • S3-compatible object storage for files.
  • External APIs — weather, GitHub, Slack, anything HTTP — for stateless tools.

Swap the demo tools in tools = [read_note, write_note] for whatever the agent actually needs.

❌ Common mistakes

  • Deploying a .env file by accident. Always add a .vercelignore.
  • Using filesystem tools on Vercel. Use a database or external API instead.
  • No timeout on the OpenAI call. Vercel functions have a max execution time; LangGraph + a slow model can hit it on cold starts.
  • Pinning langchain/langgraph version-free in requirements.txt. They evolve fast and break compatibility between minor versions. Pin them.
  • Skipping local test before deploy. Deploy is fast but the feedback loop on errors is slower than local.
  • Burning OpenAI credits with an open-to-the-world endpoint. Add basic auth (a shared password header) or rate limiting before sharing the URL widely.

💡 Pro tips

  • Start with gpt-4o-mini or gpt-4.1-mini for cheap iteration. Upgrade to gpt-4.1 once the agent is working.
  • Set temperature=0 for tool-calling agents. Non-zero temperature makes the agent unreliable about when to call tools.
  • Use Vercel’s deployment preview URLs to demo to others — every push gets its own URL.
  • For real apps, replace the file-based tools with a Postgres-backed memory and use LangGraph’s checkpointer for conversation state.
  • If the cold-start latency on Vercel free tier is too slow, switch to Vercel Pro or deploy on Render / Fly.io which keep your function warm.

Conclusion

Twenty minutes, one CLI command, a public URL with a working AI agent behind it. This is the smallest version of the stack that scales — same FastAPI + LangGraph pattern works on AWS, Render, Fly, or your own VPS when you outgrow Vercel. The deployment workflow doesn’t change as the project grows; the tools and the agent do.

Next step: replace the file-based demo tools with real ones, add a Postgres-backed conversation history, slap basic auth on the endpoint, and you have a small production-shaped AI service that you can keep iterating on.

Related reading: Python Requests: call any API in 15 minutesrun LLMs locally with Ollama vs Docker Model Runnerbuilding AI agents for production: Day 1

Build and Deploy a Python AI Agent in 20 Minutes: FastAPI + LangGraph + Vercel FAQ

Does this work without OpenAI?

Yes — swap ChatOpenAI for any LangChain-compatible chat model. Anthropic Claude (ChatAnthropic), Google Gemini (ChatGoogleGenerativeAI), or a local Ollama endpoint via the OpenAI-compatible API. Code shape stays the same.

How much does it cost to run?

Vercel free tier handles real traffic for a small project. OpenAI costs scale with usage — gpt-4o-mini is sub-cent per chat exchange. For a demo with friends, expect under $1/month.

Can I add authentication?

Yes — FastAPI has good auth ecosystem (Auth0, Clerk, custom JWT). Add a dependency that validates a token on the /agent endpoint. For demos, even a shared secret header stops random visitors from burning your OpenAI credits.

Why Vercel and not Render / Fly / Railway?

Vercel has the best free-tier CLI deploy experience for Python serverless functions. Render and Fly are excellent alternatives for long-running services, especially when you outgrow serverless and want WebSockets or persistent state. The code in this guide ports trivially to any of them.

How do I add conversation memory?

LangGraph has a checkpointer system — pass a thread_id on each invoke, store checkpoints in Postgres or Redis. The free-tier filesystem on Vercel won’t work; you need an external store.

What about streaming responses?

FastAPI supports streaming via StreamingResponse and Server-Sent Events. LangGraph supports streaming via .astream_events(). Wire the two together for a typewriter-effect chat. Slightly more complex than this article’s scope but well-documented in LangChain.