停止API付费,我构建了本地AI栈
推动我走向本地AI的那一刻,平淡得令人痛苦。
我正在测试一个文档摘要的小功能。没什么革命性的。没有自主智能体发射火箭。只是一个开发者反复修改提示词、运行应用、检查输出、再试一次。
每次实验都触发一次API请求。
单独来看,请求很便宜。API费用就是这样掏空你的钱包的。它很少戴着滑雪面具出现。它通过测试、重试、嵌入、后台任务、被遗忘的脚本以及开发者经典的"我以后再优化"策略悄悄积累。
最终,我问了一个更有用的问题:
为什么我要为我自己电脑就能处理的工作负载租用智能?
所以我停止为大多数开发和内部工具使用付费AI API。我构建了一个本地技术栈。
它并非在所有方面都神奇地更好。
但它更便宜、更容易控制、更私密,而且出乎意料地实用。
1、我实际需要的技术栈
本地AI技术栈可以变得极其复杂。
你可以安装六个编排框架、三个数据库、一个Kubernetes集群、一个可观测性平台,以及足够吓跑DevOps工程师的YAML。
或者你可以从四个组件开始:
- 一个本地模型运行器
- 一个小型应用层
- 一个检索系统
- 一个简单界面
我的技术栈是这样的:
用户界面
↓
FastAPI应用
↓
通过Ollama运行的本地模型
↓
Qdrant用于文档检索
↓
本地文件和应用数据
我使用Ollama,因为它提供了一种直接的方式来下载、运行和本地调用模型。
需要更多推理控制、模型格式或硬件优化的开发者可以使用llama.cpp。它的目标是在广泛的硬件上高效运行模型推理,而不需要庞大的服务平台。
对于检索,我使用了Qdrant。它可以在本地运行,并为基于文档的应用提供向量相似性搜索。
我用Docker Compose打包支持服务,因为手动启动五个终端不是架构,是在求救。
2、在本地运行模型
安装Ollama后,我可以用以下命令下载并运行兼容模型:
ollama pull MODEL_NAME
ollama run MODEL_NAME
具体模型取决于你的硬件和用例。
不要立即下载你的机器技术上能打开的最大模型。一个消耗所有可用内存且回复慢得令人痛苦的模型不是"强大"。它是家具。
从小开始。
用真实任务测试它:
- 它能分类你的支持消息吗?
- 它能提取结构化信息吗?
- 它能准确摘要你的文档吗?
- 它能遵循你要求的输出格式吗?
- 它的响应速度足够实际用户使用吗?
基准测试有用,但你的应用不是在基准图表上运行的。你自己的测试用例更重要。
3、替换远程API调用
Ollama暴露一个本地HTTP API,通常通过localhost。这意味着应用可以像调用托管服务一样调用本地模型。
这是一个最小的Python示例:
import requests
def ask_local_model(prompt: str) -> str:
response = requests.post(
"http://localhost:11434/api/generate",
json={
"model": "MODEL_NAME",
"prompt": prompt,
"stream": False,
},
timeout=120,
)
response.raise_for_status()
return response.json()["response"]
print(ask_local_model("用三个要点解释Docker卷。"))
这个小小的变化很重要。
我的提示词实验不再需要远程请求。内部文档不需要离开机器。我可以反复测试,而不需要考虑每个略有不同的提示词是否值得再花一笔钱。
心理上的差异比预期的更大。
当每次实验都附带一个计时器时,开发者会变得保守。本地推理让实验重新感觉像正常的软件开发。
4、要求模型知道一切
本地AI最大的错误之一是期望较小的模型表现得像庞大的托管系统。
那是错误的比较。
本地模型不需要知道一切。它需要在正确的时刻获取正确的信息。
这就是检索增强生成(RAG)变得有用的地方。
过程很直接:
文档
↓
分割成有用的块
↓
创建嵌入
↓
存储带元数据的向量
↓
检索相关块
↓
将那些块发送给模型
假设我在为一个软件团队构建内部助手。
与其问模型:
我们的部署流程是怎样的?
我从部署文档中检索最相关的部分,并构建这样的提示词:
仅使用提供的上下文来回答。
上下文:
- 生产部署需要已批准的pull request。
- 数据库迁移必须单独审查。
- 回滚使用之前的容器镜像。
问题:
我应该如何部署数据库更改?
现在模型不是在试图回忆它从未学过的私有公司知识。它在解释检索到的证据。这是一个现实得多的工作。
5、让输出无聊且可预测
聊天界面能做令人印象深刻的演示,但生产系统通常需要结构化的结果。
我不希望模型在提取发票信息时变得诗意。我要JSON。
使用以下结构返回有效的JSON:
{
"invoice_number": "string or null",
"supplier": "string or null",
"total": "number or null",
"currency": "string or null"
}
不要添加解释。
然后在代码中验证响应:
import json
from pydantic import BaseModel, ValidationError
class InvoiceData(BaseModel):
invoice_number: str | None
supplier: str | None
total: float | None
currency: str | None
def parse_invoice_response(raw_response: str) -> InvoiceData:
try:
data = json.loads(raw_response)
return InvoiceData.model_validate(data)
except (json.JSONDecodeError, ValidationError) as error:
raise ValueError("模型返回了无效的发票数据。") from error
这不如构建一个"多智能体认知工作流"令人兴奋。但它更有用。
可靠的AI应用通常是由约束、验证、重试、日志记录和良好的失败处理构建的——而不是告诉模型它是世界级专家的激励性提示词。
6、真正的好处不仅仅是经济上的
我的开发循环变快了
我可以调整提示词、测试边缘情况、重新运行评估,而不需要跟踪每个请求。
敏感数据保持在我的控制下
本地处理减少了将内部文档、客户消息、源代码或研究笔记发送到外部服务的需要。
但这并不会自动使系统安全。配置不当的本地服务器仍然可以暴露数据。例如,数据库和模型端点通常应绑定到本地或私有接口,而不是随意暴露到互联网。
"本地"是一个基础设施决策,不是一个神奇的隐私贴纸。
我可以在没有互联网的情况下工作
模型、检索数据库和文档在连接问题期间仍然可用。
我获得了架构自由
我可以在不重建整个产品的情况下更换模型。应用拥有提示词、检索逻辑、验证和业务规则。模型是一个可替换的组件。
这应该是它的样子。
7、现实:本地AI不是免费的
我停止了按请求付费。我没有逃脱成本。
本地AI将账单从API使用转移到硬件、电力、存储、维护和工程时间。
你还可能面临:
- 更慢的推理
- 复杂任务上更低的输出质量
- 模型兼容性问题
- 内存限制
- 更大的应用安装包
- 安全补丁责任
- 更困难的团队部署
还有一些场景下托管API仍然是合理的选择。强大的云模型可能更适合困难推理、大上下文窗口、重度多模态工作负载、突发流量激增,或者维护推理基础设施成本高于API的产品。
这不是宗教信仰。目标不是替换每个云模型。目标是当工作负载不需要时,停止默认使用云模型。
8、我选择本地还是云的规则
我使用本地模型当:
- 任务是狭窄且可重复的
- 数据隐私很重要
- 应用必须离线工作
- 请求量使API定价变得令人不舒服
- 较小的模型产生可接受的结果
- 我在开发期间需要无限的实验
我考虑托管模型当:
- 任务需要更强的推理
- 输出质量比基础设施成本更重要
- 流量不可预测
- 团队无法维护本地推理
- 所需模型无法在可用硬件上有效运行
混合系统通常是最明智的答案。在本地运行常规分类、提取、检索和摘要。只将真正困难的请求升级到更强的远程模型——经过用户同意和适当的数据控制。
这种方法在不假装笔记本电脑是数据中心的情况下降低了成本。
9、一个实用的迁移计划
不要在一个周末重写你的整个AI产品。
选择一个现有功能。测量它当前的:
- 请求量
- API成本
- 响应时间
- 失败率
- 输出质量
- 隐私要求
然后对固定评估集测试本地模型。
使用30到100个真实示例。用相同标准对两个系统评分。不要因为本地模型的答案"看起来不错"就选择它。开发者用这种"科学方法"已经发布了足够多的bug了。
只有当质量、速度和运营成本都可接受时,才将该功能移到本地。
然后重复。
10、我学到了什么
最大的教训不是本地模型比托管模型更好。
它们并不总是更好。
教训是我把付费API当成了自动的起点。它们很方便,所以我停止质疑它们是否匹配问题。
一旦我在本地构建,AI停止感觉像一个神秘的外部智能服务。它又变成了软件。
一个在机器上运行的进程。一个我可以替换的端点。一个我可以基准测试的组件。一个需要验证、安全、日志、测试和合理边界的系统。
少一些魔法。多一些工程。说实话,这就是我需要的升级。
原文链接:I Stopped Paying for AI APIs and Built My Own Local AI Stack
汇智网翻译整理,转载请标明出处