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.