Local ChatGPT with Gemma 3 源码实战:用 Ollama 与 Chainlit 搭建 100% 本地运行的多模态聊天助手
【免费下载链接】ai-engineering-hubIn-depth tutorials on LLMs, RAGs and real-world AI agent applications.项目地址: https://gitcode.com/GitHub_Trending/ai/ai-engineering-hub
本指南以仓库local-chatgpt with Gemma 3项目为核心,完整拆解如何基于 Google DeepMind 的 Gemma 3 开源模型与 Chainlit 框架,在本地构建一个类 ChatGPT 的多轮对话应用:模型权重完全运行在本机、无需调用任何云端 API,同时天然支持图像输入与“打字机式”消息渲染。读完你将掌握 Chainlit 的事件回调编程模型、Ollama 本地推理的接入方式、会话级上下文管理与多模态消息构造,并能在此模板上自由切换模型或扩展功能。
项目概览:一台机器上的“迷你 ChatGPT”
该项目定位非常清晰:利用 Google DeepMind 的 Gemma 3 与 Chainlit,创建一个 100% 在本机运行的 mini-ChatGPT(见 README 开篇描述)。整个技术栈只有三个核心组件:
| 组件 | 作用 | 在本项目中的角色 |
|---|---|---|
| Ollama | 本地模型运行时与推理引擎 | 负责下载权重、启动本地服务并执行gemma3:4b推理 |
| Gemma 3(4B) | Google DeepMind 的开源大语言模型 | 对话与多模态理解的“大脑”,模型参数gemma3:4b |
| Chainlit | Python 聊天应用 UI 框架 | 提供聊天界面、事件回调、会话状态与流式消息渲染 |
| app.py | 应用入口逻辑 | 串联 UI 事件与本地模型调用 |
这种“Ollama 出算力、Chainlit 出界面”的组合带来的直接收益是:对话数据不离开本机、不需要 OpenAI/Anthropic 等云端 API Key,联网需求几乎为零。从 app.py 源码可以看出,整个推理链路只有一个对ollama.chat(...)的本地调用,没有发起任何 HTTP 外部请求,是名副其实的本地部署方案。
环境准备:安装 Ollama 并拉取 Gemma 3 模型
README 的安装步骤面向 Linux 环境给出了一条可完整照做的命令链。在终端依次执行:
# 在 Linux 上安装 Ollama curl -fsSL https://ollama.com/install.sh | sh # 拉取 Gemma 3 4B 模型权重 ollama pull gemma3:4bollama pull gemma3:4b会从 Ollama 模型仓库下载约 4B 参数的 Gemma 3 权重并缓存在本地,后续推理完全离线进行。若你的机器显存充足,也可以把4b换成更大规格的标签(例如gemma3:12b),这只需修改一处模型名,详见后文“横向对照”一节。
注意一个 README 中的笔误:原文档中“pull the DeepSeek-R1 model”的注释是从姊妹项目复制遗留的描述,实际应拉取的是
gemma3:4b,请以命令本身为准。这一笔误恰好说明本仓库内多个“Local ChatGPT”变体共用同一套模板(后文会对照验证)。
如果你在 macOS 或 Windows 上使用,README 中的脚本式安装不适用,需要改用 Ollama 对应平台的桌面安装方式;安装后本机同样会常驻一个 Ollama 本地服务(默认监听 11434 端口),后续 Python 客户端默认即连接该地址。
安装 Python 依赖
项目要求Python 3.11 或更高版本,依赖仅三个:
pip install pydantic==2.10.1 chainlit ollama各依赖角色如下:
chainlit:聊天 UI 框架,提供@cl.on_chat_start、@cl.on_message等事件装饰器;ollama:Ollama 官方 Python 客户端,用于在 Python 侧发起本地模型推理;pydantic==2.10.1:被 Chainlit 依赖的数据校验库。这里锁定了精确版本(而非pydantic>=2.x),从写法上可推断是为了与所用 Chainlit 版本保持严格兼容,避免大版本升级带来的接口不兼容问题——本地复现时建议保留该固定版本。
启动应用并体验对话
依赖装好后,在项目目录下运行:
chainlit run app.py -wapp.py是要启动的应用文件;-w(watch)开启热重载,修改源码后无需手动重启,服务会自动刷新。
命令执行成功后,Chainlit 会在本地启动 Web 界面(默认监听localhost:8000),浏览器打开即可看到聊天页面。首次运行时,它会自动生成 Chainlit 所需的配置文件目录;正常启动的前提是本机 Ollama 服务处于运行状态、且已pull过gemma3:4b。
仓库还提供了实际运行效果演示:点击观看 AI 助手的 Demo 视频,可在动手前直观了解最终界面与交互形态。
app.py 逐段拆解:从聊天事件到本地推理
本项目的核心逻辑全部集中在一个仅 65 行的 app.py 文件中,堪称“小文件大模式”。它定义了三个 Chainlit 回调:
| 装饰器/函数 | 行号 | 触发时机 | 职责 |
|---|---|---|---|
@cl.on_chat_start→start_chat | app.py#L5-L25 | 每个会话开始 | 初始化 system 上下文与欢迎语 |
@cl.step(type="tool")→tool | app.py#L27-L46 | 每次收到用户输入后由main调用 | 拼装消息、调用 Gemma 3、写入上下文 |
@cl.on_message→main | app.py#L49-L65 | 收到用户消息/图片 | 解析附件并驱动推理与输出 |
下面逐段说明其工作机制。
1.start_chat:初始化会话级上下文与打字机欢迎语
@cl.on_chat_start async def start_chat(): cl.user_session.set( "interaction", [{"role": "system", "content": "You are a helpful assistant."}], ) msg = cl.Message(content="") start_message = "Hello, I'm your 100% local ChatGPT powered by Google Deepmind's Gemma 3. How can I help you today?" for token in start_message: await msg.stream_token(token) time.sleep(0.005) await msg.send()关键点:
- 会话级状态存储:
cl.user_session.set("interaction", [...])把消息列表写入 Chainlit 的用户会话存储。interaction是整个多轮对话的“记忆载体”,初始只包含一条 system 指令"You are a helpful assistant.",后续每一轮问答都会向这个列表追加内容,从而让模型具备上下文连续性(详见下文“会话级上下文”一节)。 - 打字机式欢迎语:代码先把欢迎文本逐字符通过
msg.stream_token(token)送入消息对象,每字符间隔time.sleep(0.005)(5 毫秒),最后msg.send()一次性发出,模拟出“逐字浮现”的 AI 启动效果。
2.tool:真正的本地推理函数
@cl.step(type="tool") async def tool(input_message, image=None): interaction = cl.user_session.get("interaction") if image: interaction.append({"role": "user", "content": input_message, "images": image}) else: interaction.append({"role": "user", "content": input_message}) response = ollama.chat(model="gemma3:4b", messages=interaction) interaction.append({"role": "assistant", "content": response.message.content}) return response这段是整个应用的核心:
@cl.step(type="tool")装饰器把该函数注册为一个 Chainlit “工具步骤”。调用它时,界面上会显示一个可展开的中间步骤节点,方便观察推理过程。- 多模态消息构造:如果本次输入携带图片,用户消息会被构造成带
images字段的字典({"role": "user", "content": ..., "images": image})。image实际是图片文件的本地路径列表,Ollama 客户端会读取这些路径对应的图片并随请求发送给模型——这正是 Gemma 3 视觉能力的接入点。 - 本地推理调用:
ollama.chat(model="gemma3:4b", messages=interaction)把“完整的 interaction 历史”一次性交给本地 Gemma 3,返回的response.message.content即模型生成的回答文本。 - 上下文的双向写入:调用前后分别把
user消息与assistant回复追加进interaction,保证每一轮对话都“记得”之前的全部内容。
3.main:消息入口与图片附件识别
@cl.on_message async def main(message: cl.Message): images = [file for file in message.elements if "image" in file.mime] if images: tool_res = await tool(message.content, [i.path for i in images]) else: tool_res = await tool(message.content) msg = cl.Message(content="") for token in tool_res.message.content: await msg.stream_token(token) await msg.send()- 附件识别:
message.elements是用户上传的文件列表;file.mime是文件 MIME 类型,代码通过"image" in file.mime判断是否为图片(png、jpeg、gif 等均命中)。存在图片时,提取所有i.path(本地文件路径)作为多模态输入传给tool。 - 输出渲染:模型完整生成后,
main同样采用stream_token逐字符渲染回答。注意这里与欢迎语的渲染方式一致——即先整体拿到回复、再逐字播报的“打字机效果”(下文会解释它与真正 token 流式推理的区别)。
4. 一次完整对话的数据流
整个请求链路可以用下图概括(依据 app.py 各回调的调用关系):
用户在 Web 界面输入文字 / 上传图片 │ ▼ @cl.on_message (main) │ 遍历 message.elements 过滤 "image" 类型,得到图片路径列表 ▼ @cl.step(type="tool") (tool) │ 将 user 消息(含可选 images 字段)追加到 interaction ▼ ollama.chat(model="gemma3:4b", messages=interaction) │ 本机 Ollama 推理,返回 response ▼ 将 assistant 回复追加到 interaction(记忆沉淀) │ ▼ main 用 stream_token 逐字渲染回复 → 展示在聊天界面三个值得深挖的实现机制
会话级上下文:记忆是如何“沉淀”的
interaction列表始终存放在cl.user_session中,这是 Chainlit 提供的按用户会话隔离的存储,同一会话内所有回调都能读取同一份数据。整个对话期间该列表的生长轨迹如下:
- 会话开始:
[{"role": "system", "content": "You are a helpful assistant."}] - 第一轮提问后:追加一条
user消息 - 模型回复后:追加一条
assistant消息 - 第二轮提问、回复……列表持续增长
因此模型的每一轮回答都建立在完整的“历史消息序列”之上——这与 OpenAI 等云端 API 的messages协议在结构上完全同构,也意味着你随时可以把ollama.chat替换为任何遵循相同消息协议的本地或远程推理接口。
“打字机效果”与真正的 token 流式推理
源码中所有输出都走for token in content: await msg.stream_token(token)。需要厘清的是:这里的content来自response.message.content,即模型已经完整生成的一整段文本;逐字符渲染只是在 UI 层制造打字机效果,中间time.sleep(0.005)制造了节奏感,并非逐 token 推理。
如果希望实现“边生成边输出”的真正流式体验,可以将 app.py#L40-L41 处的调用改为ollama.chat(..., stream=True),再对迭代返回的块做增量渲染——当前仓库提交的是一个更简单直接的版本,这一点从源码结构可以明确推断。
多模态链路:从拖拽图片到模型“看懂”
Gemma 3 属于支持图像输入的开放模型,而本项目让这一能力变得几乎零成本:Chainlit 聊天框天然支持文件上传 →message.elements暴露上传文件 → 按 MIME 过滤图片 → 把本地路径列表放入消息的images字段 → Ollama 读取图片并送入 Gemma 3。整个图片处理无需任何额外的视觉预处理管线,这是该实现最有价值的开箱即用特性之一。
配套 notebook:最小可复现的推理验证
仓库中的 notebook.ipynb 记录了开发过程中对ollama.chat的最小调用验证。值得注意的是,其中测试使用的是deepseek-r1模型(该 notebook 大概率由 DeepSeek 变体项目复用而来),但它验证的能力对 Gemma 3 同样成立:
import ollama response = ollama.chat(model="deepseek-r1", messages=[{"role": "user", "content": "Hello, how are you?"}]) print(response)从 notebook 输出的响应对象可以看到ollama.chat返回体的关键字段:response.message(含role与content)、created_at、done_reason、以及eval_count/eval_duration等耗时统计。app.py 正是通过response.message.content取出回答文本——理解了这一点,就抓住了整个项目“UI 与推理衔接”的接口契约。如果你想绕过 UI 单独验证模型是否就绪,这段 notebook 代码是最快的冒烟测试。
横向对照:仓库内同一模板的多种模型变体
本项目并非孤立存在。在当前仓库中,local-chatgpt、local-chatgpt with DeepSeek与local-chatgpt with Gemma 3共用几乎相同的工程模板与安装说明,差异仅在模型一行:
| 仓库目录 | 驱动模型 | 模型拉取命令 |
|---|---|---|
| local-chatgpt with Gemma 3 | gemma3:4b(本主题) | ollama pull gemma3:4b |
| local-chatgpt with DeepSeek | deepseek-r1 | ollama pull deepseek-r1 |
| local-chatgpt | llama3.2-vision | ollama pull llama3.2-vision |
例如在 local-chatgpt/app.py#L38 中,模型名换成了llama3.2-vision,其余回调结构与 Gemma 3 变体完全一致。这揭示了一个可迁移结论:这套 Chainlit 回调骨架对模型完全无感。想换模型时,只需执行ollama pull <新模型>,并把 app.py#L40 的model="gemma3:4b"改为新模型标签即可(若模型不支持视觉,则需同步去掉images字段的传参逻辑)。
常见问题与排查思路
基于对安装与源码的分析,本地复现时最常见的几类问题及对应处理建议如下:
- 模型未找到(
model not found):确认已执行ollama pull gemma3:4b,且模型名与代码中model="gemma3:4b"完全一致; - 推理无响应/连接失败:检查本机 Ollama 服务是否在运行(默认端口 11434);Linux 安装脚本执行后可通过
ollama list验证服务与模型状态; - 界面卡在欢迎语:确认
start_chat中的time.sleep与渲染逻辑没有因终端中断而异常,必要时在启动命令中保留-w以便观察热重载日志; - 上传图片无效:
main只接收 MIME 类型含"image"的文件,若上传的是非图片文件会被静默忽略。
小结与可行的扩展方向
local-chatgpt with Gemma 3用极少的代码量演示了一套完整、可离线运行的多模态对话范式:Chainlit 负责交互与状态,Ollama 负责本地推理,Gemma 3 提供文本与视觉理解能力,三者以标准messages协议串联。它既是零成本上手本地 LLM 应用开发的样板,也是理解会话记忆、多模态消息与 UI 流式渲染的绝佳教材。
如果你希望在此基础上继续实验,结合源码留出的扩展点,以下方向都只需在 app.py 上做局部改动即可实现:
- 接入真流式推理:把
ollama.chat改为stream=True并增量渲染,让回答“边算边出”; - 切换/对比更多模型:借助
ollama pull拉取gemma3:12b、deepseek-r1等并替换模型名; - 增强 system 角色设定:修改
start_chat中初始化的 system 内容,即可定制助手人设与行为边界; - 为长对话做截断/摘要:
interaction无限增长会拖慢推理,可加入消息窗口裁剪或历史摘要逻辑。
上述改动均属于读者在自己本地副本上的实践范畴,仓库本身保持只读即可运行验证。
【免费下载链接】ai-engineering-hubIn-depth tutorials on LLMs, RAGs and real-world AI agent applications.项目地址: https://gitcode.com/GitHub_Trending/ai/ai-engineering-hub
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考