ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Windows Server+Caddy+FastAPI+llama.cpp+Qwen实现AI聊天机器人技术架构(1)

Windows Server+Caddy+FastAPI+llama.cpp+Qwen实现AI聊天机器人技术架构(1) Windows ServerCaddyFastAPIllama.cppQwen实现AI聊天机器人技术架构1摘要基于低配Windows VPS4GB内存/2核CPU的AI聊天机器人技术架构方案。该架构采用Android App作为客户端通过HTTPS REST API访问远程服务服务端由Caddy反向代理、FastAPI业务层和llama.cpp模型推理层组成加载Qwen2.5小量化模型推荐0.5B Q4_K_M版本。设计重点包括1严格资源控制输入限制800字/输出256token2无上下文处理降低内存压力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 CPU2 核 存储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. 输入长度限制建议限制单次输入最多 500800 个中文字符比如最大输入长度800 字超过直接返回{ error: 输入内容太长请控制在 800 字以内。 }2. 输出长度限制建议限制max_tokens: 128256推荐先用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(titleQwen 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_code401, detailMissing Authorization header) prefix Bearer if not authorization.startswith(prefix): raise HTTPException(status_code401, detailInvalid Authorization format) token authorization[len(prefix):].strip() if token ! API_TOKEN: raise HTTPException(status_code401, detailInvalid token) app.post(/api/chat/stream) async def chat_stream( req: ChatRequest, authorization: str | None Header(defaultNone) ): check_auth(authorization) user_message req.message.strip() if not user_message: raise HTTPException(status_code400, detailmessage 不能为空) if len(user_message) MAX_INPUT_CHARS: raise HTTPException( status_code400, detailf输入内容太长请控制在 {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(timeoutNone) as client: async with client.stream(POST, LLAMA_API, jsonpayload) 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_typetext/plain; charsetutf-8 )十三、是否需要 SSE可以有两种流式返回格式。方案一普通文本流响应内容你好我是一个聊天助手...Android 端直接读取 ResponseBody。优点简单 开销小 Android 端好处理推荐你先用这个。方案二SSE响应内容data: 你好 data: 我是一个聊天助手 data: [DONE]优点协议更规范 适合浏览器 EventSource缺点Android 端处理稍复杂一点场景是 Android App 调 API所以优先使用普通文本流即可。十四、Caddy 配置假设域名是chat.example.comCaddyfilchat.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 次请求低配服务器可以更保守每分钟 35 次如果是自用可以先不做复杂限流但至少要有1. Token 鉴权 2. 单并发锁 3. 输入长度限制 4. 输出 token 限制这四个必须有。十七、错误码设计建议统一错误码。场景HTTP 状态码说明服务正常200正常流式返回参数错误400message 为空或太长未鉴权401Token 缺失或错误服务器繁忙429当前已有请求在推理模型服务异常502llama-server 异常后端异常500FastAPI 异常如果采用“等待排队”可以不返回 429。如果采用“繁忙拒绝”就返回 429。十八、日志设计服务器存储只有 40GB不要写太多日志。建议记录请求时间 接口路径 请求耗时 是否成功 错误信息 输入长度 输出长度不建议记录完整用户问题和完整模型回答除非明确需要。原因1. 节省磁盘 2. 降低隐私风险 3. 避免日志膨胀日志轮转建议单文件最大 5MB10MB 保留 35 个文件十九、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 服务如果用 UvicornC:\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这些过重。二十三、是否需要 DockerWindows 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 1FastAPImax_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/流式 APIllama.cpp 做本地 Qwen 小模型推理。不要保存上下文不要上数据库不要支持高并发严格限制输入输出并使用单并发流式返回才能让 Android App 访问时保持相对稳定和顺滑。
返回列表