FastAPI+Ollama 部署本地AI大模型对话助手

用 Python + FastAPI + Ollama,从零搭一个完全跑在自己电脑上的 AI 对话助手:数据不出本机、不需要任何 API Key、不花一分钱。

全文包含项目介绍、环境前提、三步运行、功能清单、核心原理拆解与优势对比,所有用到的开源项目均附官方网址。

开源地址GitHub - yangran-coder/ai-chatbot: AI聊天助手 · GitHub (MIT 许可)

一、这个项目是什么?

先说结论:这是一个只有 4 个依赖、1 个 Python 文件、1 个 HTML 文件的本地 AI 聊天助手。

它长得跟 ChatGPT 网页版差不多------有对话气泡、打字机式逐字输出、多轮上下文记忆、代码块渲染、连接状态指示灯。但本质区别在于:模型跑在你自己的电脑上,请求不出内网,不需要注册任何账号,也不需要付一分钱。

三层架构如下(启动后访问 http://localhost:8000 即可看到真实界面):

复制代码
┌─────────────────────────────────────────┐
│  浏览器 · static/index.html(306 行)     │   展示界面 / 发请求 / 流式渲染
│  零构建,无需 npm                         │
└──────────────────┬──────────────────────┘
                   │  POST /api/chat  {messages}
                   │  ← SSE: data:{"content":"你"} ...
                   ▼
┌─────────────────────────────────────────┐
│  FastAPI 后端 · main.py(约 100 行)      │   历史整理 / 转发 / 错误处理
│  Uvicorn 承载,默认 8000 端口             │
└──────────────────┬──────────────────────┘
                   │  POST http://localhost:11434/api/chat
                   ▼
┌─────────────────────────────────────────┐
│  Ollama 本地大模型 · qwen2.5:3b          │   推理,数据不出本机
└─────────────────────────────────────────┘

项目名叫「小K AI」,技术栈如下:

层次 技术 作用
前端 原生 HTML + CSS + JS(单文件,306 行) 渲染界面、发送消息、流式接收
后端 FastAPI + Uvicorn 对话历史整理、请求转发、SSE 流式输出
模型层 Ollama + Qwen2.5 本地大模型推理,默认 qwen2.5:3b

整个项目零前端构建------没有 npm、没有 webpack、没有 node_modules,双击 HTML 的结构直接由后端托管。这是我刻意的设计:对于一个"想搞懂全栈链路"的学习型项目,构建工具的复杂度会掩盖真正的核心逻辑。

二、运行前需要准备什么?

2.1 硬件前提

项目 最低要求 推荐配置 说明
内存 8 GB 16 GB 及以上 qwen2.5:3b 加载后约占用 2~3 GB
硬盘 5 GB 可用 10 GB 可用 仅为模型文件预留,代码本身不到 20 KB
显卡 无要求 NVIDIA 6 GB 显存以上 没有独显也能跑(纯 CPU 推理,只是慢)

2.2 软件前提

① Python 3.9 及以上(推荐 3.10 / 3.11)

官网下载:Download Python | Python.org

Windows 用户注意:安装时务必勾选 "Add Python to PATH" ,否则后续命令行里敲 python 会提示找不到命令。

验证:

bash 复制代码
python --version
# 或
python3 --version

② Ollama(本地大模型运行时)

这是整个方案的核心,它把"下载模型、加载模型、提供 HTTP 接口"这几件事封装成了一条命令。

Windows 上双击安装包一路下一步即可;macOS / Linux 也可一行命令安装:

bash 复制代码
# macOS / Linux
curl -fsSL https://ollama.com/install.sh | sh

安装验证:

bash 复制代码
ollama --version

③ 一个模型

安装完 Ollama 后它会自动在后台常驻,监听 http://localhost:11434。首次使用需要拉一个模型下来:

bash 复制代码
ollama pull qwen2.5:3b

模型名必须与 ollama list 显示的一致,写错会导致后端报 404------这是本项目专门处理过的一个坑,见第六节。

几款常用模型对比(体积与定位):

模型 体积 特点 模型页
qwen2.5:3b ~2 GB 中文友好、轻量,本项目默认 qwen2.5
qwen2.5:latest ~4.7 GB 7B 版本,效果明显更好 qwen2.5
qwen2.5-coder ~4.7 GB 代码能力增强 qwen2.5-coder
llama3.2:latest ~2 GB 英文更顺 llama3.2

三、快速运行项目

第 1 步:拉模型

bash 复制代码
ollama pull qwen2.5:3b

(上一节已做过的可跳过。约 2 GB,视网速等待几分钟。)

第 2 步:装依赖并启动

bash 复制代码
# 进入项目目录
cd ai-chatbot
​
# 创建虚拟环境(推荐,避免污染全局环境)
python -m venv .venv
​
# 激活虚拟环境
# Windows:
.venv\Scripts\activate
# macOS / Linux:
source .venv/bin/activate
​
# 安装依赖(国内可加清华源加速)
pip install -r requirements.txt
# pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
​
# 复制配置模板(可选,不改也能跑)
cp .env.example .env      # Windows 用: copy .env.example .env
​
# 启动
uvicorn main:app --reload --port 8000

看到 Uvicorn running on http://127.0.0.1:8000 即启动成功。

第 3 步:打开浏览器

访问 http://localhost:8000

右上角状态灯显示绿色「已连接 · qwen2.5:3b」就说明三层链路全通,可以开始对话了。

浏览器打开的网页显示如下:

在浏览器打开网页后,终端显示如下:

测试一下能否使用:点击页面上给出的问题,回答如下:

说明此AI对话助手可以正常使用啦!

四、它有哪些功能?

功能 说明 实现要点
流式打字机输出 答案逐字蹦出,不用干等 后端 SSE + 前端 ReadableStream
多轮上下文记忆 能接着上一句话聊 前端维护 messages 数组全量回传
连接状态指示灯 顶部实时显示「已连接 / Ollama 未就绪 / 无法连接后端」 页面加载调用 /api/health
空状态引导卡片 首屏 4 个示例问题,点一下直接问 data-q 属性 + 事件委托
键盘操作 Enter 发送、Shift+Enter 换行 keydown 判断 e.shiftKey
输入框自适应高度 最多撑到 160px 后滚动 scrollHeight 动态回写
代码块渲染 ``包裹的内容渲染为 <pre> 按`` 切分奇偶段
错误可见化 模型名写错等问题直接在气泡里显示原因 后端显式捕获非 200 与流内 error
移动端适配 600px 断点,气泡宽度 90% CSS @media
XSS 防护 全部用 textContent 写入,不用 innerHTML 富文本渲染函数

几个值得单独说的细节:

1)代码块渲染为什么"手写"而不用 markdown 库?

很多教程会引入 marked.js + highlight.js 来渲染 Markdown。本项目没有这么做,原因是:引入意味着要么加 CDN(离线环境失效)、要么加 npm(违背零构建)。而聊天场景 90% 的富文本需求就是"代码要有个灰底块",所以只需按 ``````````` 切分、奇数段包进 <pre> 即可,20 行代码解决问题,且天然免疫 XSS (全程 textContent)。

2)为什么状态灯很重要?

本地部署最常见的困惑是"我到底哪一层没起来"。状态灯把三态明确区分开:已连接 · 模型名(全通)、Ollama 未就绪(后端活着但模型层挂了)、无法连接后端(后端本身没起)。故障定位时间从"翻日志十分钟"降到"看一眼"。

五、核心原理拆解

理解这张图,就理解了整个全栈链路:

复制代码
浏览器 (static/index.html)
    │  POST /api/chat   { messages: [...] }
    │  ← SSE 流式返回:data: {"content":"你"} ...
    ▼
FastAPI 后端 (main.py)
    │  POST http://localhost:11434/api/chat   { model, messages, stream:true }
    ▼
Ollama 本地大模型

为什么要中间夹一层后端,前端直接调 Ollama 不行吗?

技术上可以,但有三个实际问题:

① 浏览器直连 11434 端口会触发**跨域(CORS)**限制;

② 任何 API Key 或模型配置写在前端等于公开泄露

③ 缺少统一入口后,后续加鉴权、限流、日志、多模型路由都无从下手。后端这一层是"中枢",不是为了凑架构。

5.1 流式输出是怎么做到的

后端:用 httpx 的流式请求把 Ollama 的输出边收边转成 SSE。

python 复制代码
# main.py ------ 核心转发逻辑(节选)
async with client.stream(
    "POST",
    f"{OLLAMA_BASE_URL}/api/chat",
    json={"model": MODEL_NAME, "messages": messages, "stream": True},
) as resp:
    async for line in resp.aiter_lines():
        if not line:
            continue
        chunk = json.loads(line)
        if "message" in chunk and chunk["message"].get("content"):
            text = chunk["message"]["content"]
            # SSE 格式:以 "data: " 开头,两个换行结尾
            yield f"data: {json.dumps({'content': text}, ensure_ascii=False)}\n\n"
        if chunk.get("done"):
            yield "data: [DONE]\n\n"

前端:用 fetch + ReadableStream 逐块读取,拼进气泡。

html 复制代码
// static/index.html ------ 前端接收(节选)
const resp = await fetch("/api/chat", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ messages }),
});
const reader = resp.body.getReader();          // 拿到流读取器
const decoder = new TextDecoder();
let buf = "";
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  buf += decoder.decode(value, { stream: true });
  const parts = buf.split("\n\n"); buf = parts.pop();   // 按 SSE 帧边界切分
  for (const part of parts) {
    if (!part.startsWith("data: ")) continue;
    const data = part.slice(6).trim();
    if (data === "[DONE]") continue;
    const json = JSON.parse(data);
    acc += json.content;
    bubble.textContent = acc;                  // 逐字刷新,形成打字机效果
  }
}

SSE 协议规范可参考 MDN:使用服务器发送事件 - Web API | MDN

为什么用 SSE 而不是 WebSocket? 对话是单向流 :请求一次,服务端持续吐字,客户端不需要再发数据。SSE 基于普通 HTTP,天然穿透代理、自动重连、浏览器原生 EventSource 支持,而 WebSocket 需要额外握手与双向通道管理。用 SSE 是更轻的正确解------WebSocket 在这里属于过度设计。

5.2 AI 为什么能"记住"前面聊了什么

大语言模型本身是无状态 的:每次请求对它而言都是全新的。所谓"记忆",靠的是每次都把完整对话历史一起发过去

python 复制代码
let messages = [];   // 前端保存完整对话历史
​
messages.push({ role: "user", content: text });        // 用户发言入列
// ... 请求时整列发给后端
// 收到完整回答后:
messages.push({ role: "assistant", content: acc });    // AI 回答也入列

后端原样透传给 Ollama 的 /api/chat,模型看到完整上下文,于是能接着聊。

由此可推出两个实践结论:

  • 上下文越长,单次推理越慢、占用显存越高------这是所有长对话变慢的根本原因;

  • 刷新页面 = 清空 messages = 模型"失忆"。想持久化就得把历史存进数据库或 localStorage(见第八节扩展方向)。

5.3 一个真实的坑:Ollama 的静默 404

这是开发中实际踩到的问题,值得单独记录:当模型名不存在时,Ollama 会返回 404,且这个响应不是流式的 。如果只写 async for line in resp.aiter_lines(),循环会直接结束,前端收到一个空响应------界面上表现为"AI 没有反应",控制台没有任何报错,极难排查。

解决办法是显式检查状态码:

python 复制代码
if resp.status_code != 200:
    body = await resp.aread()
    err = json.loads(body).get("error", body.decode("utf-8", "ignore"))
    yield f"data: {json.dumps({'error': f'Ollama 返回 {resp.status_code}:{err}'}, ensure_ascii=False)}\n\n"
    yield "data: [DONE]\n\n"
    return

加上这段后,模型名写错会在气泡里直接显示 Ollama 返回 404:model "xxx" not found一句话定位问题

六、相比其他方案,优势在哪?

6.1 对比云端 API 方案(如直接调 OpenAI / 文心 / 通义 API)

维度 本项目(本地 Ollama) 云端 API
数据隐私 完全不出本机,可断网运行 需上传对话内容至服务商
费用 0 元(电费除外) 按 token 计费,长期有成本
网络依赖 首次拉模型后无需联网 强依赖网络与服务商可用性
注册门槛 无,不需要 API Key 需注册、实名、额度申请
模型能力 3B~7B 量级,够用但不顶尖 顶尖大模型,复杂推理更强
响应速度 取决于本机硬件 稳定较快
合规风险 内网场景可放心使用 涉敏数据需评估

客观结论 :如果追求最强推理能力或生产级稳定性,云端 API 仍是更优解;但如果诉求是隐私敏感场景(内部文档、代码、医疗/法务文本)、离线环境、学习研究、零成本原型验证,本地方案的性价比是压倒性的。

6.2 工程层面的其他优势

  1. 配置与代码分离OLLAMA_BASE_URLMODEL_NAME 通过 .env 注入(python-dotenv),换模型不用改代码。

  2. 自带健康检查/api/health 返回 {"ok": true, "models": [...], "model": "qwen2.5:3b"},便于接入监控或容器探针。

  3. 自动接口文档 :FastAPI 原生生成 /docs,无需手写 Swagger。

  4. 静态路由挂载顺序正确app.mount("/", ...) 放在所有 /api 路由之后,避免静态托管覆盖 API------新手常踩的顺序陷阱。

  5. MIT 许可:可自由商用、二次开发,无授权负担。

七、常见问题速查

现象 原因 解决办法
状态灯红色「Ollama 未就绪」 Ollama 未启动 启动 Ollama 应用,或执行 ollama serve
对话无反应、无报错 模型名与 ollama list 不一致 ollama list 核对后改 .envMODEL_NAME
提示 model not found 模型没拉下来 ollama pull qwen2.5:3b
首次回答特别慢 模型首次加载需载入显存/内存 正常现象,第二次起明显变快
Address already in use 8000 端口被占用 uvicorn main:app --port 8001
ModuleNotFoundError: No module named 'dotenv' 依赖未装全 pip install -r requirements.txt(含 python-dotenv
响应逐字输出但很卡 CPU 推理 + 模型偏大 qwen2.5:3b,或改用带 GPU 的机器
页面 404 / 样式丢失 未在项目根目录启动 cd 到含 main.py 的目录再执行

八、相关官方资源汇总

核心依赖

项目 官方网址 说明
Ollama https://ollama.com 本地大模型运行时(首页)
Ollama 下载 Download Ollama on macOS 各平台安装包
Ollama 模型库 Ollama 搜索全部可用模型
Ollama GitHub GitHub - ollama/ollama: Get up and running with Kimi, GLM, MiniMax, DeepSeek, gpt-oss, Qwen, Gemma and other models. · GitHub 源码与文档
Ollama API 文档 ollama/docs/api.md at main · ollama/ollama · GitHub /api/chat 接口规范
FastAPI FastAPI - FastAPI 中文官方文档
FastAPI GitHub GitHub - fastapi/fastapi: FastAPI framework, high performance, easy to learn, fast to code, ready for production · GitHub 源码
Uvicorn https://www.uvicorn.org ASGI 服务器官网
Uvicorn GitHub GitHub - Kludex/uvicorn: An ASGI web server, for Python. 🦄 · GitHub 源码
HTTPX https://www.python-httpx.org 异步 HTTP 客户端
HTTPX GitHub GitHub - encode/httpx: A next generation HTTP client for Python. 🦋 · GitHub 源码
python-dotenv GitHub - theskumar/python-dotenv: Reads key-value pairs from a .env file and can set them as environment variables. It helps in developing applications following the 12-factor principles. · GitHub .env 加载
Python [Download Python | Python.org](https://www.python.org/downloads/ "Download Python Python.org")

本项目源码GitHub - yangran-coder/ai-chatbot: AI聊天助手 · GitHub

相关推荐
Python图像识别1 小时前
10-【2027毕设】YOLO11PCB缺陷检测识别系统 - Python完整源码+PyQt5界面+训练模型+数据集
python·深度学习·yolo·毕业设计·毕设
szephyr1 小时前
用 Python 写自动化脚本:定时任务、文件批处理、自动出报表
python·自动化·pandas·脚本·定时任务
wuyk5551 小时前
Python实战项目01:简易记事本系统
开发语言·python
凤城老人1 小时前
从零手写开源私有云盘|Flask+Vue3+Electron全栈网盘,单容器一键部署
python·docker·electron·vue
lpfasd1231 小时前
配置文件格式对比 与 AI Coding 的选择逻辑
python·flask·numpy
2601_962885721 小时前
如何用 Python 把 A 股行情批量导出到 Excel/CSV?(多股票多 Sheet)
python
2601_962218612 小时前
万象生鲜系统多终端统一数据协议PC手机PDA数据实时同步
大数据·数据库·人工智能·python·算法
科技苑2 小时前
Python AI自动剪辑视频简易程序
人工智能·python
夜雪一千2 小时前
Python爬虫实战:把Bootstrap栅格Div伪表格转为原生Table表格
python