Hermes Web Chat MVP in Action: Building Your Own AI Web Chat System from Scratch (Full Version)
Table of Contents
Chapter 1 Why Build Hermes Web Chat
Chapter 2 Overall Architecture Design
Chapter 3 Project Directory
Chapter 4 Frontend Page Development
Chapter 5 FastAPI Backend
Chapter 6 DeepSeek Integration
Chapter 7 API Design
Chapter 8 OpenResty Proxy Configuration
Chapter 9 HTTPS Deployment
Chapter 10 Systemd Deployment
Chapter 11 Common Troubleshooting
Chapter 12 Performance Optimization
Chapter 13 Next Upgrade Directions
Appendix A Full Source Code
Appendix B Complete nginx Configuration
Appendix C Complete systemd Configuration
Chapter 1 Why Build Hermes Web Chat
Previously, our Hermes Agent could already:
✅ WeCom (Enterprise WeChat)
✅ Redis Queue
✅ Worker
✅ DeepSeek
But there was one problem.
All entry points were through WeCom.
Debugging was very cumbersome.
So we decided to first build:
Browser → Hermes → DeepSeek
This is the lightest layer of the entire Hermes stack.
The overall structure is as follows:
Browser
│
▼
index.html
│
fetch()
│
▼
FastAPI
│
DeepSeek SDK
│
▼
DeepSeek API
│
Return answer
│
▼
Browser
The whole chain is very simple.
No Redis.
No Worker.
No WeCom.
Just get the AI capability working first.
Chapter 2 Creating the Project
First, set up the project.
mkdir hermes-web-chat-mvp
cd hermes-web-chat-mvp
Create the directory structure:
hermes-web-chat-mvp/
├── frontend/
│ └── index.html
│
├── backend/
│ ├── app/
│ │
│ ├── main.py
│ │
│ ├── chat.py
│ │
│ └── requirements.txt
│
└── README.md
This is the final directory structure.
Chapter 3 Python Environment
Create a virtual environment.
cd backend
python3 -m venv venv
Activate it:
source venv/bin/activate
Install dependencies.
requirements.txt:
fastapi
uvicorn
requests
python-dotenv
Install:
pip install -r requirements.txt
Check:
pip list
You should see:
fastapi
uvicorn
requests
Chapter 4 Writing the Backend
main.py:
from fastapi import FastAPI
from pydantic import BaseModel
import requests
app = FastAPI()
API_KEY="your DeepSeek Key"
class ChatRequest(BaseModel):
message:str
@app.post("/chat")
def chat(req:ChatRequest):
headers={
"Authorization":f"Bearer {API_KEY}"
}
data={
"model":"deepseek-chat",
"messages":[
{
"role":"user",
"content":req.message
}
]
}
r=requests.post(
"https://api.deepseek.com/chat/completions",
headers=headers,
json=data
)
result=r.json()
reply=result["choices"][0]["message"]["content"]
return {"reply":reply}
Start the server:
uvicorn main:app --host 0.0.0.0 --port 8001
Open in browser:
http://<server-IP>:8001/docs
You should see the Swagger UI.
Chapter 5 Writing the Frontend
frontend/index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hermes Chat</title>
</head>
<body>
<h2>Hermes Chat</h2>
<input id="msg">
<button onclick="send()">
Send
</button>
<div id="reply"></div>
<script>
async function send(){
const message=document.getElementById("msg").value;
const res=await fetch("/api/chat",{
method:"POST",
headers:{
"Content-Type":"application/json"
},
body:JSON.stringify({
message:message
})
});
const data=await res.json();
document.getElementById("reply").innerHTML=data.reply;
}
</script>
</body>
</html>
Start the frontend server:
python3 -m http.server 8080
Visit in browser:
http://<server-IP>:8080
Chapter 6 OpenResty
Because the browser cannot directly access port 8001.
So we configure Nginx.
server {
listen 443 ssl;
server_name hermes.xxx.com;
location / {
root /opt/hermes/frontend;
index index.html;
}
location /api/ {
proxy_pass http://127.0.0.1:8001/;
}
}
Note here:
location /api/
maps to
proxy_pass http://127.0.0.1:8001/
The trailing
/
must not be omitted.
Chapter 7 Frontend Fetch
This is also the most common pitfall.
Many people write:
fetch("/chat")
and get a 404.
Because the actual proxy endpoint is
/api/chat
So you must use:
fetch("/api/chat")
Chapter 8 Pitfalls We Encountered
This is the most important chapter of the entire project.
1 Backend not started
At the time, we ran:
ps aux | grep uvicorn
and found
no 8001
So
curl
127.0.0.1:8001/chat
failed directly.
2 OpenResty proxy misconfiguration
Initially we configured:
proxy_pass http://127.0.0.1:8001;
which resulted in:
/api/chat
↓
8001/api/chat
The backend had no
/api/chat
endpoint, hence a 404.
Later we changed to:
proxy_pass http://127.0.0.1:8001/;
and it worked.
3 Returning “No response”
This was the biggest pitfall at the time.
{"reply":"No response"}
The cause was not the frontend.
Rather:
The DeepSeek API returned no choices.
So
result["choices"]
was empty.
Finally, we added:
print(r.text)
and located the real error.
4 CORS issues
If the frontend is not on the same origin.
For example:
8080
↓
8001
The browser will report:
CORS blocked
We needed:
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"]
)
5 HTTPS
If the web page is:
https://
But the backend is:
http://
The browser will complain about:
Mixed Content
So finally we unified:
Browser
↓
HTTPS
↓
OpenResty
↓
HTTP
↓
FastAPI
The browser always uses HTTPS.
Chapter 9 Systemd Deployment
Create:
/etc/systemd/system/hermes-web.service
Content:
[Unit]
Description=Hermes Web Chat
After=network.target
[Service]
WorkingDirectory=/opt/ai/hermes-web-chat-mvp/backend
ExecStart=/opt/ai/hermes-web-chat-mvp/backend/venv/bin/uvicorn main:app --host 0.0.0.0 --port 8001
Restart=always
User=root
[Install]
WantedBy=multi-user.target
Start it:
systemctl daemon-reload
systemctl enable hermes-web
systemctl start hermes-web
View logs:
journalctl -u hermes-web -f
Chapter 10 Next Upgrade Directions
Currently, this MVP has completed basic verification. It can gradually evolve into a true enterprise‑grade AI platform in the future:
- Add streaming output to achieve character‑by‑character display like ChatGPT.
- Introduce multi‑turn conversations and context management to support continuous dialogues.
- Add Markdown rendering, syntax highlighting, and mathematical formula display.
- Integrate user login, permission control, and conversation history.
- Incorporate Redis queues to improve handling under high concurrency.
- Add file upload, knowledge base retrieval (RAG), and tool calling capabilities.
- Reuse the same AI services for the WeCom assistant, unifying the backend for both web and WeCom clients.
Conclusion
This Hermes Web Chat MVP practice not only delivered a working AI web chat system, but more importantly verified the complete call chain from the browser to FastAPI and then to DeepSeek. It lays the foundation for future integration with WeCom, Redis, Workers, knowledge bases, and multi‑agent architectures.
Compared to debugging within a complex architecture, a lightweight Web Chat MVP allows rapid validation of model invocation, API design, reverse proxy, and deployment processes. It serves as the first cornerstone in building the Hermes enterprise AI platform. All advanced features can be incrementally built upon this MVP without needing to start over.