大模型这波浪潮起来之后,我身边不少朋友第一反应都是"想玩,但没卡"。本地跑个 7B 模型,光显存就得 8G 起步,想微调更是直接劝退;租云 GPU 吧,按小时计费,跑两天钱包就瘪了。于是很多人卡在"想搭个自己的 AI 助手"这一步,迟迟没动手。
其实换个思路就通了:推理这件事,完全可以不放在自己机器上。OpenRouter 这类聚合平台把市面上主流大模型的 API 统一成一个入口,注册就能拿到密钥,很多模型还带免费额度。你要做的只是写几十行调用代码,把对话界面搭起来。整条链路不需要一块独立显卡,一台普通笔记本、甚至一台云主机都能跑。这篇就把我从零搭一个"零成本大模型助手"的完整过程拆开讲,包括平台怎么选、密钥怎么管、免费模型怎么挑、代码怎么写、以及那些文档里不会写但一定会踩的坑。
1. 先想清楚:为什么"不买 GPU"反而是更聪明的起点
1.1 本地部署的真实成本,比你想的高
很多人对"本地跑大模型"有种执念,觉得数据在自己手里才安心。这个想法没错,但得算笔账。一个 7B 参数的模型,FP16 精度下光权重就要占约 14GB 显存,就算用 4-bit 量化压到 4GB 左右,加上 KV Cache 和推理框架本身的开销,实际占用往往还要往上浮。这意味着你至少需要一张 8GB 显存的卡才能比较舒服地跑起来,12GB 会更稳。
再看时间成本。装 CUDA 驱动、配 PyTorch 的 GPU 版本、处理各种版本冲突,这一套下来新手折腾一整天是常态。我见过太多人卡在"显卡驱动和框架版本对不上"这一步,最后热情耗尽。而租 GPU 呢?按量计费的实例,一张中端卡每小时几块到十几块不等,你要是想长期挂一个助手服务,一个月下来费用相当可观。
所以对绝大多数"我只是想有个能用的 AI 助手"的人来说,本地部署的性价比其实很低。除非你有明确的数据合规要求,或者就是要做模型微调研究,否则没必要一上来就啃硬件这块硬骨头。
1.2 API 聚合平台解决的到底是什么问题
传统做法是:想用 A 家的模型就注册 A 家、充值 A 家、对接 A 家的接口;想换 B 家,又得重来一遍。每家接口格式还不完全一样,有的用messages数组,有的字段名不同,切换成本很高。
OpenRouter 这类聚合平台的价值就在于把多家模型收敛到一个统一的 OpenAI 兼容接口。你只需要一个密钥、一个 base_url,就能调用平台上挂载的各种模型,切换模型只是改一个字符串参数的事。这对个人开发者和小项目来说,省掉的是大量的对接和维护工作。
提示:聚合平台本质是"中间层",请求会经过它的服务器转发。所以涉及敏感数据的场景要谨慎评估,个人学习、日常问答、内容生成这类用途则完全没问题。
1.3 免费额度能撑起一个什么样的助手
这是大家最关心的。平台上确实有一批模型提供免费调用额度,通常以:free后缀标识。这些免费模型大多是中小参数量的开源模型,比如 gemma 系列、一些 7B 到 9B 级别的模型。它们的能力边界在哪?
日常对话、文本润色、简单代码解释、翻译、信息整理这些任务,免费模型完全够用。但你要是让它做复杂推理、长文档深度分析、高质量长文创作,就会明显感觉到力不从心——要么答得浅,要么中途开始胡言乱语。所以定位要摆正:免费额度适合做个人助手、学习练手、轻量工具,别指望它替代付费的旗舰模型。
2. 平台与模型选型:别一上来就挑最贵的
2.1 注册与密钥获取的实际流程
流程本身不复杂,但有几个细节值得说。注册一般支持邮箱或第三方账号登录,登录后在账户设置里能找到 API Keys 管理页面,点创建就能生成一串以sk-or-开头的密钥。生成后一定要立刻复制保存,因为多数平台出于安全考虑,密钥只在创建时完整显示一次,关掉页面就再也看不到了,只能重新生成。
密钥的管理是个容易被忽视的点。我的建议是:
- 不要硬编码在代码里,用环境变量或
.env文件管理; - 给不同用途创建不同的密钥,比如一个给本地测试、一个给线上服务,方便出问题时单独吊销;
- 定期轮换,尤其是密钥曾经出现在截图、日志或公开仓库里的时候。
# .env 文件示例 OPENROUTER_API_KEY=sk-or-你的密钥 OPENROUTER_BASE_URL=https://openrouter.ai/api/v12.2 免费模型怎么挑:看三个指标
平台上模型列表很长,免费的那批怎么选?我一般看三个维度。
第一是上下文长度。这个直接决定你能喂多长的内容。有的免费模型上下文只有 4K 到 8K token,稍微长一点的对话历史就超了,会直接报错。有的能到 32K 甚至更高,体验就好很多。选之前一定看清楚。
第二是参数量与定位。gemma-7b 这类 7B 模型属于轻量级,响应快、成本低,适合高频简单任务;如果平台上有更大的免费模型,复杂任务优先用大的。
第三是稳定性。免费模型在高峰期经常排队或限流,返回 429 或超时是家常便饭。所以实际项目里,我通常会配置一个"主模型 + 备用模型"的降级策略,主模型挂了自动切备用。
| 维度 | 建议标准 | 踩坑提醒 |
|---|---|---|
| 上下文长度 | 至少 8K,优先 32K+ | 太短会导致长对话直接报错 |
| 参数量 | 简单任务 7B 够用 | 复杂推理别硬上小模型 |
| 稳定性 | 有备用模型兜底 | 免费模型高峰期限流频繁 |
| 响应速度 | 首 token 延迟可接受 | 大模型免费版可能很慢 |
2.3 一个容易被忽略的细节:模型名的写法
调用时模型名是字符串,但不同平台的命名规则不一样。有的带厂商前缀,比如google/gemma-7b-it,有的免费版要加:free后缀。写错模型名是最常见的 400 错误来源之一。报错信息里通常会提示"支持的模型名有哪些",照着改就行。我建议把常用模型名集中定义成常量,别散落在代码各处,改起来方便。
3. 把调用链路跑通:从一次 curl 到完整对话
3.1 先用最原始的方式验证密钥
写代码之前,先用 curl 打一发,确认密钥和网络都通。这一步能帮你排除掉一大半"到底是密钥问题还是代码问题"的纠结。
curl https://openrouter.ai/api/v1/chat/completions \ -H "Authorization: Bearer $OPENROUTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "google/gemma-7b-it:free", "messages": [ {"role": "user", "content": "用一句话解释什么是大模型"} ] }'如果返回里能看到choices[0].message.content,说明链路通了。如果返回 401,是密钥问题;返回 400,多半是模型名或参数格式问题;返回 429,就是限流了,等一会儿再试。
3.2 Python 封装:把重复逻辑收进一个类
验证通过后,用 Python 封装一个客户端。核心思路是把"发请求、处理异常、重试"这些重复逻辑收进一个类里,业务代码只管传消息、拿结果。
import os import time import requests from dotenv import load_dotenv load_dotenv() class LLMClient: def __init__(self, model="google/gemma-7b-it:free", fallback=None): self.api_key = os.getenv("OPENROUTER_API_KEY") self.base_url = os.getenv("OPENROUTER_BASE_URL") self.model = model self.fallback = fallback def chat(self, messages, temperature=0.7, max_retries=3): models = [self.model] + ([self.fallback] if self.fallback else []) for model in models: for attempt in range(max_retries): try: resp = requests.post( f"{self.base_url}/chat/completions", headers={ "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json", }, json={ "model": model, "messages": messages, "temperature": temperature, }, timeout=60, ) if resp.status_code == 200: return resp.json()["choices"][0]["message"]["content"] if resp.status_code == 429: time.sleep(2 ** attempt) # 指数退避 continue resp.raise_for_status() except requests.RequestException as e: print(f"[{model}] 第 {attempt+1} 次失败: {e}") time.sleep(1) raise RuntimeError("所有模型均调用失败")这段代码里有几个设计点值得解释。指数退避(2 ** attempt)是为了应对限流——第一次等 1 秒,第二次 2 秒,第三次 4 秒,避免疯狂重试把额度打得更死。降级策略是主模型失败后自动切备用模型,提升整体可用性。超时设置(timeout=60)很重要,免费模型偶尔会卡住不返回,没有超时的话程序会一直挂着。
3.3 多轮对话:历史消息怎么管
大模型本身是无状态的,它不记得你上一句说了什么。所谓"多轮对话",其实是每次请求都把完整的历史消息一起发过去。所以你需要维护一个messages列表,每轮把用户输入和模型回复都追加进去。
history = [{"role": "system", "content": "你是一个简洁友好的助手"}] def ask(user_input): history.append({"role": "user", "content": user_input}) reply = client.chat(history) history.append({"role": "assistant", "content": reply}) return reply这里有个坑:历史越长,token 消耗越大,而且迟早会超过模型的上下文上限。免费模型上下文本来就小,聊十几轮就可能爆。解决办法是做个简单的截断——只保留最近 N 轮,或者当总长度超阈值时,把最早的消息丢掉。更讲究的做法是做摘要压缩,但对个人助手来说,截断就够了。
4. 从脚本到"助手":界面、记忆与流式输出
4.1 用 Streamlit 十分钟搭个网页界面
命令行能跑通之后,下一步是给它一个像样的界面。Streamlit 是我最推荐的选择,纯 Python,不用写前端,几十行就能出一个能用的聊天页。
import streamlit as st from client import LLMClient st.title("我的大模型助手") client = LLMClient(model="google/gemma-7b-it:free") if "messages" not in st.session_state: st.session_state.messages = [ {"role": "system", "content": "你是一个简洁友好的助手"} ] for msg in st.session_state.messages: if msg["role"] != "system": with st.chat_message(msg["role"]): st.write(msg["content"]) if prompt := st.chat_input("说点什么..."): st.session_state.messages.append({"role": "user", "content": prompt}) with st.chat_message("user"): st.write(prompt) with st.chat_message("assistant"): reply = client.chat(st.session_state.messages) st.write(reply) st.session_state.messages.append({"role": "assistant", "content": reply})st.session_state是 Streamlit 里保存会话状态的关键,页面刷新时消息不会丢。跑起来用streamlit run app.py,浏览器自动打开,一个能对话的助手就成了。
4.2 流式输出:让等待不再难熬
非流式调用有个体验问题:模型要生成完整段落后才一次性返回,长回答时用户要盯着空白等好几秒。流式输出(streaming)能让文字像打字一样一个个蹦出来,体感快很多。
实现上,请求时加"stream": true,然后逐行读取响应。OpenAI 兼容接口返回的是 SSE 格式,每行以data:开头,遇到data: [DONE]结束。
def chat_stream(self, messages): resp = requests.post( f"{self.base_url}/chat/completions", headers={"Authorization": f"Bearer {self.api_key}"}, json={"model": self.model, "messages": messages, "stream": True}, stream=True, timeout=60, ) for line in resp.iter_lines(): if not line: continue line = line.decode("utf-8") if line.startswith("data: "): data = line[6:] if data == "[DONE]": break import json delta = json.loads(data)["choices"][0]["delta"] yield delta.get("content", "")配合 Streamlit 的st.write_stream,就能实现打字机效果。注意流式模式下错误处理会更麻烦,因为响应头返回 200 之后才可能中途出错,所以要在循环里加 try 捕获。
4.3 给助手加点"记忆"和"人设"
一个光秃秃的问答框用久了会觉得单薄。两个低成本但效果明显的增强:
系统提示词(system prompt)决定助手的性格和边界。比如"你是一个严谨的技术助手,回答尽量给出可执行的步骤,不确定的地方要明说",比默认的泛泛而谈好用得多。这个提示词放在messages列表最前面,每轮都带着。
本地记忆可以用一个简单的 JSON 文件存对话历史,重启后还能接着聊。再进一步,可以把用户偏好、常用信息存成键值对,在构造 system prompt 时动态拼进去,助手就会显得"记得你"。
注意:免费模型的上下文有限,记忆别存太多,否则每轮请求都塞一大堆历史,很快就超限了。我的经验是保留最近 10 轮左右,更早的做摘要或直接丢弃。
5. 那些文档不写但一定会踩的坑
5.1 限流与超时:免费额度的代价
免费模型最大的问题就是不稳定。高峰期请求排队,返回 429 是常态;有时候请求发出去了,几十秒没响应,最后超时。应对策略前面代码里已经体现了:指数退避重试 + 备用模型降级 + 合理超时。这三板斧基本能覆盖大部分情况。
还有个细节:不要并发猛打。免费额度通常有每分钟请求数限制,你同时发十个请求,大概率一半被拒。老老实实串行,或者加个简单的节流。
5.2 上下文超限:报错信息要会读
最常见的报错之一就是上下文超限,提示类似"maximum context length is X tokens"。这时候别慌,先算一下你的messages总长度。粗略估算:一个中文字大约 1 到 2 个 token,英文一个单词约 1.3 个 token。如果历史消息堆太多,就截断。如果单条输入本身就超长(比如贴了一整篇论文),那只能换上下文更大的模型,或者先做分段处理。
5.3 密钥泄露:一个真实的风险场景
我见过有人把带密钥的代码直接推到公开仓库,几小时后额度就被刷光了。密钥泄露的后果是实打实的经济损失(如果绑了付费)或额度被滥用。防护措施:
.env文件加进.gitignore;- 代码里永远从环境变量读,不写死;
- 定期在平台后台检查用量,发现异常立即吊销重建;
- 给密钥设置额度上限(如果平台支持)。
5.4 模型"幻觉"与输出格式不稳定
免费小模型的幻觉问题比大模型明显,尤其是问它事实性问题时,可能一本正经地编。另外,如果你要求它输出 JSON,它经常会在 JSON 外面裹一层解释文字,导致解析失败。应对办法是:在提示词里明确要求"只输出 JSON,不要任何其他文字",并且在代码里做容错解析——先尝试直接json.loads,失败就用正则把{...}抠出来再解析。
| 常见问题 | 典型表现 | 应对方案 |
|---|---|---|
| 限流 | 返回 429 | 指数退避 + 备用模型 |
| 超时 | 长时间无响应 | 设置 timeout + 重试 |
| 上下文超限 | 400 报错 | 截断历史 / 换大上下文模型 |
| 密钥泄露 | 额度异常消耗 | 环境变量管理 + 定期轮换 |
| 输出格式乱 | JSON 解析失败 | 提示词约束 + 容错解析 |
6. 成本控制与后续扩展的几个方向
6.1 免费额度用完了怎么办
免费额度不是无限的,用超了要么等重置,要么充值。充值前先想清楚用途:如果只是个人日常用,免费额度配合降级策略基本够;如果要做正式产品,那就得评估付费模型的成本,按 token 计费的话,一次对话几分钱到几毛钱不等,量大了也是笔开销。我的建议是先用免费额度把产品逻辑跑通,确认有价值再考虑付费,别一上来就充钱。
6.2 从"助手"到"工具":几个自然的扩展
跑通基础对话后,可以往几个方向延伸。一是接入知识库,把本地文档做向量化,检索后拼进提示词,让助手能回答你私有资料里的问题。二是做成 API 服务,用 FastAPI 包一层,其他程序就能调用你的助手。三是多模型路由,根据任务类型自动选模型——简单问答走免费小模型,复杂任务走付费大模型,兼顾成本和效果。
6.3 关于"要不要本地部署"的最终判断
绕了一圈回到最初的问题。我的结论是:除非有硬性的数据合规要求或微调研究需求,否则个人用户没必要折腾本地 GPU 部署。API 方案在成本、维护、模型更新上都占优。真到了需要本地的场景,再考虑租 GPU 或买卡也不迟。先用最低成本把想法验证出来,比什么都重要。
我在实际搭这套东西的过程中最大的体会是:别追求一步到位。先让 curl 通,再让脚本通,再加界面,再加记忆,每一步都能独立验证。这样出问题时你知道是哪一层的事,排查起来快得多。反过来,一上来就写个几百行的完整应用,报错了根本不知道从哪查起。另外,免费模型的脾气要摸清——哪个时段稳、哪个模型响应快,用几天就有感觉了,把这些经验固化成配置,助手就越来越顺手。