Windows Server+Caddy+FastAPI+llama.cpp+Qwen实现AI聊天机器人技术架构(1)
摘要:基于低配Windows VPS(4GB内存/2核CPU)的AI聊天机器人技术架构方案。该架构采用Android App作为客户端,通过HTTPS REST API访问远程服务,服务端由Caddy反向代理、FastAPI业务层和llama.cpp模型推理层组成,加载Qwen2.5小量化模型(推荐0.5B Q4_K_M版本)。设计重点包括:1)严格资源控制(输入限制800字/输出256token);2)无上下文处理降低内存压力;3)流式响应改善用户体验;4)单并发避免过载;5)极简架构(无数据库/无复杂框架)。文章详细说明了各组件配置参数、API设计原则和错误处理机制,特别强调在低配环境下通过流式输出、量化模型和严格限流来保证服务稳定性,最终实现一个轻量级但可用的AI对话服务。
整体目标是:
Android App 作为用户客户端,通过公网 HTTPS REST API 访问远程 VPS 上的聊天机器人服务。服务端是 Windows Server + Caddy + FastAPI + llama.cpp + Qwen。聊天业务不保留上下文,每次提问独立处理。
服务器配置较低:4G 内存、40GB 存储、2 核 CPU。
这个场景的关键不是做复杂系统,而是:
Android 端体验尽量顺滑 服务端压力尽量小 接口设计简单 严格限制输入和输出 支持流式返回 不保存上下文 不做高并发场景是:
Android App 客户端 通过公网 HTTPS REST API 访问远程 VPS 上的聊天机器人服务 服务器环境: Windows Server 公网 IP + 域名 4GB 内存 40GB 存储 2 核 CPU 服务端技术栈: Caddy + FastAPI + llama.cpp + Qwen GGUF 小模型 业务特点: 聊天机器人 不保留上下文 每次请求独立处理 低并发 服务器性能较弱一、服务器端总体架构
推荐服务器端架构如下:
公网用户 / Android App ↓ HTTPS 域名访问 ↓ Caddy ↓ FastAPI 后端服务 ↓ llama.cpp llama-server ↓ Qwen GGUF 小模型更具体:
┌────────────────────────────────────┐ │ Android App │ │ POST https://chat.example.com/api/chat/stream └─────────────────┬──────────────────┘ │ HTTPS ↓ ┌────────────────────────────────────┐ │ Caddy │ │ - 监听 80 / 443 │ │ - 自动 HTTPS │ │ - 反向代理 │ │ - 请求体大小限制 │ │ - 流式转发 │ └─────────────────┬──────────────────┘ │ HTTP │ 127.0.0.1:8000 ↓ ┌────────────────────────────────────┐ │ FastAPI │ │ - REST API │ │ - Bearer Token 鉴权 │ │ - 输入长度限制 │ │ - 单并发控制 │ │ - 不保存上下文 │ │ - 调用 llama-server │ │ - 流式返回结果 │ └─────────────────┬──────────────────┘ │ HTTP │ 127.0.0.1:8080 ↓ ┌────────────────────────────────────┐ │ llama.cpp llama-server │ │ - 加载 Qwen GGUF 量化模型 │ │ - CPU 推理 │ │ - OpenAI-compatible API │ │ - 单并发推理 │ └─────────────────┬──────────────────┘ ↓ ┌────────────────────────────────────┐ │ Qwen2.5-0.5B / 1.5B Instruct GGUF │ │ - 小模型 │ │ - 4bit 量化 │ │ - 适合低配 VPS │ └────────────────────────────────────┘二、核心设计原则
因为服务器只有:
内存:4GB CPU:2 核 存储:40GB所以后端架构要尽量克制。
核心原则是:
1. 不保存上下文 2. 不使用数据库 3. 不使用向量数据库 4. 不使用复杂 Agent 框架 5. 不使用 LangChain 全家桶 6. 不支持高并发 7. 不直接暴露模型服务 8. 使用小模型 9. 使用流式输出 10. 严格限制输入和输出长度 11. FastAPI 层做鉴权、限流、排队 12. Caddy 只作为公网 HTTPS 入口一句话:
这个后端应该是一个“极简、无状态、低并发、流式输出”的轻量聊天服务。
三、服务器端组件职责划分
1. Caddy:公网入口层
Caddy 负责:
1. 监听公网 80/443 端口 2. 自动申请 HTTPS 证书 3. 自动续期 HTTPS 证书 4. 把请求转发给 FastAPI 5. 限制请求体大小 6. 支持流式响应转发Caddy 不负责:
不负责聊天业务 不负责模型推理 不负责用户上下文 不负责数据库它只是服务器入口。
2. FastAPI:业务后端层
FastAPI 是后端核心业务层,负责:
1. 提供 REST API 2. 校验 Authorization Bearer Token 3. 校验请求参数 4. 限制用户输入长度 5. 生成 prompt 6. 调用 llama-server 7. 接收 llama-server 流式输出 8. 转发给 Android App 9. 控制并发 10. 返回错误码FastAPI 不负责:
不直接加载模型 不做模型推理 不保存聊天上下文 不保存聊天记录 不连接数据库3. llama.cpp llama-server:模型推理层
llama-server 负责:
1. 加载 Qwen GGUF 模型 2. 执行 CPU 推理 3. 提供 OpenAI-compatible API 4. 返回模型生成内容它只监听本机:
127.0.0.1:8080绝对不要暴露到公网。
4. Qwen GGUF:模型层
推荐模型:
优先:Qwen2.5-0.5B-Instruct-GGUF Q4_K_M 可选:Qwen2.5-1.5B-Instruct-GGUF Q4_K_M对于配置:
4GB 内存 2 核 CPU Windows Server最稳的是:
Qwen2.5-0.5B-Instruct Q4_K_M如果希望回答质量更好,可以尝试:
Qwen2.5-1.5B-Instruct Q4_K_M但速度和内存压力会增加。
四、推荐后端部署拓扑
服务器上建议这样监听端口:
Caddy: 0.0.0.0:80 0.0.0.0:443 FastAPI: 127.0.0.1:8000 llama-server: 127.0.0.1:8080也就是:
公网只暴露 Caddy FastAPI 只允许本机访问 llama-server 只允许本机访问请求链路:
Android App ↓ https://chat.example.com/api/chat/stream ↓ Caddy 443 ↓ http://127.0.0.1:8000/api/chat/stream ↓ FastAPI ↓ http://127.0.0.1:8080/v1/chat/completions ↓ llama-server ↓ Qwen 模型五、API 设计
建议后端只提供少量接口。
1. 健康检查接口
GET /api/health用途:
App 检查服务是否在线 运维检查服务状态响应示例:
{ "status": "ok", "service": "qwen-chat", "model": "qwen2.5-0.5b-instruct", "context_enabled": false }2. 流式聊天接口,推荐主接口
POST /api/chat/stream请求头:
Authorization: Bearer your_api_token Content-Type: application/json请求体:
{ "message": "请用简单的话解释一下量化模型是什么" }响应:
量化模型可以理解为把模型参数压缩得更小...或者 SSE 格式:
data: 量化模型可以理解为 data: 把模型参数压缩得更小 data: [DONE]3. 非流式聊天接口,可选
POST /api/chat请求:
{ "message": "你好,请介绍一下你自己" }响应:
{ "reply": "你好,我是一个运行在远程服务器上的中文聊天助手。" }这个接口适合测试,但正式聊天建议用流式接口。
六、为什么推荐流式接口?
服务器性能比较弱,如果用普通接口:
用户发送问题 服务器完整生成 生成完一次性返回用户会感觉:
等待很久 页面像卡住了 体验不好如果用流式接口:
模型生成一点 服务器返回一点 App 显示一点用户会感觉:
响应更快 正在实时输出 体验更丝滑所以在 2 核 4GB 的服务器上,流式输出非常重要。
七、无上下文设计
业务明确要求:
不保留聊天上下文。
那么 FastAPI 每次请求只发送:
system prompt + 当前用户 message不要发送:
历史用户问题 历史助手回答 session_id conversation_id chat history每次请求都是独立的。
Prompt 结构
FastAPI 调用 llama-server 时,构造 messages:
[ { "role": "system", "content": "你是一个友好、简洁、可靠的中文聊天助手。请用简单易懂的话回答用户问题。如果不知道答案,请直接说明不知道,不要编造。" }, { "role": "user", "content": "用户当前输入的问题" } ]这就是全部上下文。
无上下文的好处
1. 内存压力低 2. Prompt 短 3. 推理速度更快 4. 后端不用数据库 5. 不涉及历史隐私存储 6. 服务更容易扩展和维护无上下文的缺点
用户不能这样问:
第一句:详细介绍一下全球AI发展形势? 第二句:它上半年怎么样?因为服务器不知道“它”是谁。
需要用户每次说完整:
请介绍一下xxx 2026 年上半年的经营情况。八、服务端资源控制设计
这是整个后端架构的重点。
1. 输入长度限制
建议限制:
单次输入最多 500~800 个中文字符比如:
最大输入长度:800 字超过直接返回:
{ "error": "输入内容太长,请控制在 800 字以内。" }2. 输出长度限制
建议限制:
max_tokens: 128~256推荐先用:
max_tokens = 256如果服务器慢,降到:
max_tokens = 1283. 上下文长度限制
llama-server 参数建议:
-c 1024不要一开始用:
-c 2048 -c 4096因为上下文越长,内存和推理压力越大。
4. 并发限制
服务端只有 2 核 CPU,建议:
同时只允许 1 个推理请求FastAPI 层使用全局信号量:
semaphore = asyncio.Semaphore(1)llama-server 也设置:
--parallel 1双重限制,防止请求把服务器打爆。
5. 排队策略
建议两种方式选一种。
方式一:简单等待
适合自用或少量用户:
第二个请求等待第一个完成优点:
用户请求不会直接失败缺点:
排队时可能等很久方式二:繁忙直接拒绝
适合多人使用:
如果当前已有请求在推理,新请求直接返回 429响应:
{ "error": "服务器繁忙,请稍后再试。" }对低配服务器,我更推荐:
单用户自用:等待排队。多人使用:429 拒绝。
九、推荐 llama.cpp 启动参数
稳定优先:Qwen 0.5B
.\llama-server.exe ` -m C:\llm-chat\models\qwen2.5-0.5b-instruct-q4_k_m.gguf ` --host 127.0.0.1 ` --port 8080 ` -c 1024 ` -t 2 ` -b 64 ` --ubatch-size 32 ` --parallel 1质量稍好:Qwen 1.5B
.\llama-server.exe ` -m C:\llm-chat\models\qwen2.5-1.5b-instruct-q4_k_m.gguf ` --host 127.0.0.1 ` --port 8080 ` -c 1024 ` -t 2 ` -b 64 ` --ubatch-size 32 ` --parallel 1参数说明
--host 127.0.0.1 只允许本机访问,不能公网访问。 --port 8080 模型服务端口。 -c 1024 上下文长度,低配机器建议 1024。 -t 2 使用 2 个 CPU 线程。 -b 64 batch size,低配机器小一点更稳。 --ubatch-size 32 降低瞬时内存压力。 --parallel 1 只允许一个并发推理。十、FastAPI 后端模块设计
推荐 FastAPI 代码按模块拆分:
server/ ├── main.py # FastAPI 入口 ├── config.py # 配置项 ├── schemas.py # 请求/响应模型 ├── auth.py # Token 鉴权 ├── rate_limit.py # 并发控制/限流 ├── llm_client.py # 调用 llama-server ├── routers/ │ ├── health.py # 健康检查接口 │ └── chat.py # 聊天接口 └── utils/ └── logger.py # 日志如果想极简,也可以先全部写在一个app.py里。
但从可维护性看,建议分层:
API 层:接收请求 Auth 层:鉴权 Service 层:业务逻辑 LLM Client 层:调用 llama-server十一、FastAPI 核心逻辑
FastAPI 的核心流程:
1. Android App 请求 /api/chat/stream 2. FastAPI 校验 Authorization 3. 检查 message 是否为空 4. 检查 message 是否超过长度 5. 获取推理锁 6. 构造 prompt 7. 调用 llama-server /v1/chat/completions 8. 接收流式内容 9. 逐段返回给 Android App 10. 请求结束释放锁十二、FastAPI 示例代码,流式版本
下面是一份适合你这个架构的核心代码示例。
import os import json import asyncio import httpx from fastapi import FastAPI, Header, HTTPException from fastapi.responses import StreamingResponse from pydantic import BaseModel app = FastAPI(title="Qwen Chat Backend") LLAMA_API = "http://127.0.0.1:8080/v1/chat/completions" API_TOKEN = os.getenv("API_TOKEN", "change-this-token") MAX_INPUT_CHARS = 800 MAX_OUTPUT_TOKENS = 256 # 低配服务器:同时只允许一个推理任务 inference_semaphore = asyncio.Semaphore(1) SYSTEM_PROMPT = """ 你是一个友好、简洁、可靠的中文聊天助手。 请用简单易懂的话回答用户问题。 如果不知道答案,请直接说不知道,不要编造。 回答尽量简洁,不要输出过长内容。 """.strip() class ChatRequest(BaseModel): message: str @app.get("/api/health") async def health(): return { "status": "ok", "service": "qwen-chat", "context_enabled": False, "max_input_chars": MAX_INPUT_CHARS, "max_output_tokens": MAX_OUTPUT_TOKENS } def check_auth(authorization: str | None): if not authorization: raise HTTPException(status_code=401, detail="Missing Authorization header") prefix = "Bearer " if not authorization.startswith(prefix): raise HTTPException(status_code=401, detail="Invalid Authorization format") token = authorization[len(prefix):].strip() if token != API_TOKEN: raise HTTPException(status_code=401, detail="Invalid token") @app.post("/api/chat/stream") async def chat_stream( req: ChatRequest, authorization: str | None = Header(default=None) ): check_auth(authorization) user_message = req.message.strip() if not user_message: raise HTTPException(status_code=400, detail="message 不能为空") if len(user_message) > MAX_INPUT_CHARS: raise HTTPException( status_code=400, detail=f"输入内容太长,请控制在 {MAX_INPUT_CHARS} 字以内" ) async def generate(): async with inference_semaphore: messages = [ { "role": "system", "content": SYSTEM_PROMPT }, { "role": "user", "content": user_message } ] payload = { "model": "qwen", "messages": messages, "temperature": 0.7, "top_p": 0.9, "max_tokens": MAX_OUTPUT_TOKENS, "stream": True } try: async with httpx.AsyncClient(timeout=None) as client: async with client.stream("POST", LLAMA_API, json=payload) as response: if response.status_code != 200: yield f"[模型服务异常:HTTP {response.status_code}]" return async for line in response.aiter_lines(): if not line: continue if line.startswith("data: "): data = line[len("data: "):].strip() if data == "[DONE]": break try: obj = json.loads(data) delta = obj["choices"][0].get("delta", {}) content = delta.get("content", "") if content: yield content except Exception: continue except asyncio.CancelledError: # 客户端断开时触发 raise except Exception as e: yield f"\n[服务异常:{str(e)}]" return StreamingResponse( generate(), media_type="text/plain; charset=utf-8" )十三、是否需要 SSE?
可以有两种流式返回格式。
方案一:普通文本流
响应内容:
你好,我是一个聊天助手...Android 端直接读取 ResponseBody。
优点:
简单 开销小 Android 端好处理推荐你先用这个。
方案二:SSE
响应内容:
data: 你好 data: 我是一个聊天助手 data: [DONE]优点:
协议更规范 适合浏览器 EventSource缺点:
Android 端处理稍复杂一点场景是 Android App 调 API,所以:
优先使用普通文本流即可。
十四、Caddy 配置
假设域名是:
chat.example.comCaddyfil
chat.example.com { encode gzip reverse_proxy 127.0.0.1:8000 { flush_interval -1 } request_body { max_size 2MB } }说明:
encode gzip 启用压缩。 reverse_proxy 127.0.0.1:8000 把请求转发给 FastAPI。 flush_interval -1 尽量及时转发流式响应。 request_body max_size 2MB 限制请求体大小,防止恶意大请求。如果只给自己用,也可以在 Caddy 层再加 Basic Auth。
但如果是 Android App 调用,建议主要用:
FastAPI Bearer Token十五、鉴权设计
建议使用:
Authorization: Bearer your_api_tokenFastAPI 校验这个 token。
不要裸奔开放接口。
原因:
1. 服务器配置低,很容易被刷爆 2. 模型接口生成成本高 3. 公网服务容易被扫描 4. 一旦被滥用,CPU 长期 100%Token 配置方式
建议用环境变量:
setx API_TOKEN "your-strong-token"然后重启 FastAPI 服务。
FastAPI 中读取:
API_TOKEN = os.getenv("API_TOKEN")十六、限流设计
除了单并发,还建议加简单限流。
例如:
同一个 token 每分钟最多 10 次请求低配服务器可以更保守:
每分钟 3~5 次如果是自用,可以先不做复杂限流,但至少要有:
1. Token 鉴权 2. 单并发锁 3. 输入长度限制 4. 输出 token 限制这四个必须有。
十七、错误码设计
建议统一错误码。
场景 | HTTP 状态码 | 说明 |
|---|---|---|
服务正常 | 200 | 正常流式返回 |
参数错误 | 400 | message 为空或太长 |
未鉴权 | 401 | Token 缺失或错误 |
服务器繁忙 | 429 | 当前已有请求在推理 |
模型服务异常 | 502 | llama-server 异常 |
后端异常 | 500 | FastAPI 异常 |
如果采用“等待排队”,可以不返回 429。
如果采用“繁忙拒绝”,就返回 429。
十八、日志设计
服务器存储只有 40GB,不要写太多日志。
建议记录:
请求时间 接口路径 请求耗时 是否成功 错误信息 输入长度 输出长度不建议记录完整用户问题和完整模型回答,除非明确需要。
原因:
1. 节省磁盘 2. 降低隐私风险 3. 避免日志膨胀日志轮转建议:
单文件最大 5MB~10MB 保留 3~5 个文件十九、Windows Server 后台服务设计
建议用 NSSM 管理三个进程:
1. LlamaServer 2. QwenChatAPI 3. Caddy1. LlamaServer 服务
程序:
C:\llm-chat\llama\llama-server.exe参数:
-m C:\llm-chat\models\qwen2.5-0.5b-instruct-q4_k_m.gguf --host 127.0.0.1 --port 8080 -c 1024 -t 2 -b 64 --ubatch-size 32 --parallel 12. QwenChatAPI 服务
如果用 Uvicorn:
C:\llm-chat\server\venv\Scripts\uvicorn.exe参数:
main:app --host 127.0.0.1 --port 80003. Caddy 服务
程序:
C:\llm-chat\caddy\caddy.exe参数:
run --config C:\llm-chat\caddy\Caddyfile二十、推荐目录结构
C:\llm-chat ├── caddy │ ├── caddy.exe │ └── Caddyfile │ ├── llama │ ├── llama-server.exe │ ├── llama.dll │ └── ggml.dll │ ├── models │ └── qwen2.5-0.5b-instruct-q4_k_m.gguf │ ├── server │ ├── main.py │ ├── config.py │ ├── llm_client.py │ ├── auth.py │ ├── schemas.py │ ├── requirements.txt │ └── venv │ └── logs ├── api.log ├── caddy.log └── llama.log二十一、是否需要数据库?
当前不需要。
因为要求:
不保留上下文 聊天机器人 简单文本对话 低配 VPS所以不要上:
MySQL PostgreSQL MongoDB Redis SQLite 向量数据库除非后续要做:
用户系统 调用统计 聊天记录 知识库问答 计费系统否则数据库会增加复杂度和资源占用。
二十二、是否需要 Redis 队列?
当前不需要。
因为:
服务器只有 2 核 模型只能单并发 业务简单直接用:
asyncio.Semaphore(1)就够了。
不要引入:
Celery Redis Queue RabbitMQ Kafka这些过重。
二十三、是否需要 Docker?
Windows Server + 4GB 内存下,不建议优先 Docker。
原因:
1. Docker Desktop / 容器环境额外占用资源 2. Windows 上配置复杂度更高 3. 4GB 内存比较紧张 4. 你的服务本身很简单推荐直接裸机部署:
NSSM + 原生进程更轻量、更直接。
二十四、推荐最终参数
模型
Qwen2.5-0.5B-Instruct-GGUF Q4_K_Mllama-server
-c 1024 -t 2 -b 64 --ubatch-size 32 --parallel 1FastAPI
max_input_chars = 800 max_output_tokens = 256 并发 = 1如果卡顿:
max_output_tokens = 128 max_input_chars = 500 -c = 768二十五、最终推荐架构总结
服务器端后端建议设计成:
Caddy 作为唯一公网入口,负责 HTTPS 和反向代理。 FastAPI 作为轻量业务 API 层,负责鉴权、参数校验、输入限制、单并发控制、不保存上下文、调用模型服务、流式返回。 llama.cpp llama-server 作为本地模型推理服务,只监听 127.0.0.1,加载 Qwen GGUF 小模型。 Qwen2.5-0.5B Instruct GGUF 作为实际对话模型,优先选择 4bit 量化版本。完整链路:
Android App ↓ POST https://chat.example.com/api/chat/stream ↓ Caddy 443 ↓ FastAPI 127.0.0.1:8000 ↓ llama-server 127.0.0.1:8080 ↓ Qwen GGUF 小模型二十六、结论
在 4GB 内存、2 核 CPU 的 Windows Server VPS 上,后端架构应该尽量简单:Caddy 做 HTTPS 入口,FastAPI 做无状态 REST/流式 API,llama.cpp 做本地 Qwen 小模型推理。不要保存上下文,不要上数据库,不要支持高并发,严格限制输入输出,并使用单并发流式返回,才能让 Android App 访问时保持相对稳定和顺滑。