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:
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
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:
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:
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
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:
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:
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:
<!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
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
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:
.env
Then add the variable to Vercel directly — encrypted at rest, not in the deployment bundle:
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
.envfile 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=0for 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 minutes — run LLMs locally with Ollama vs Docker Model Runner — building AI agents for production: Day 1