上周五下午,CTO在群里发了一条消息:"模型服务下线,全部切API。"
没有讨论,没有会议,直接通知。
终于来了。
为什么支持切API
先交代背景。一家做企业知识库SaaS的公司,核心功能是用大模型做文档问答。去年初上线的时候,选了自建方案------租了4张A100,微调了一个7B模型,搭了推理服务。
听起来很"技术自研",对吧?
但实际运行了一年,问题越来越多:
模型效果不够好。7B模型在通用问答上还行,但遇到垂直领域的专业问题(比如法律合同、医疗报告),经常胡说八道。我们花了大量时间做数据清洗和微调,效果提升有限。
维护成本太高。4张A100每月租金约4万,加上电费、带宽、运维人力,每月硬成本约8万。而且GPU经常出问题------有一次显存泄漏导致服务挂了4个小时,客户投诉了十几条。
迭代速度太慢。每次想升级模型,都要重新微调、重新部署、重新测试,周期至少2周。而OpenAI和DeepSeek几乎每个月都有新模型发布,我们永远在追。
去年10月团队就想切API,但被高层否了。原因是------"自建模型是核心技术壁垒"。
到上个月,DeepSeek-V3的API价格降到了每百万token不到1块钱。高层算了一笔账,发现每月花8万维护的GPU集群,换成API调用,成本不到2000块。
然后就有了那条群消息。
迁移过程:RAG架构怎么搭的
切API不是简单地把模型调用地址换一下就完事了。核心场景是文档问答,需要把企业私有数据和模型结合,所以RAG(检索增强生成)架构是关键。
分享一下最终的架构,以及踩过的坑。
整体流程:用户提问 → 向量检索相关文档 → 把检索结果+用户问题一起发给大模型 → 大模型基于检索内容回答。
ini
# 核心调用逻辑(简化版)
import openai
def answer_question(question: str, top_k: int = 5):
# 1. 向量检索
docs = vector_store.search(question, top_k=top_k)
# 2. 构建prompt
context = "\n\n".join([f"文档{ i+1}:{doc.content}" for i, doc in enumerate(docs)])
prompt = f"""你是一个企业知识库助手。请根据以下文档内容回答用户问题。
如果文档中没有相关信息,请直接说"我没有找到相关信息",不要编造。
参考文档:
{context}
用户问题:{question}
请用中文回答,引用文档时标注来源编号。"""
# 3. 调用大模型
response = openai.ChatCompletion.create(
model="deepseek-chat",
messages=[{"role": "user", "content": prompt}],
temperature=0.1, # 低温度,减少幻觉
max_tokens=2000
)
return response.choices[0].message.content
这段代码看着简单,但踩了几个大坑。
坑1:chunk size怎么选?
把文档切成小段存入向量数据库,切法直接影响检索质量。一开始用的固定512 token切分,效果很差------经常把一个完整的段落切成两半,检索到的是半截内容。
后来试了好几种方案:
固定256 token:切得太碎,上下文丢失严重 固定512 token:段落经常被切断 按段落切分(推荐):用正则按换行符+标题切,保持语义完整 递归切分:先按大标题切,再按小标题切,再按段落切,每层不超过512 token
最后选了递归切分,效果最好。但不同类型的文档最优chunk size不一样------合同类文档适合按条款切,技术文档适合按章节切。目前是按文档类型自动选择切分策略。
ini
# 递归切分(简化版)
from langchain.text_splitter import RecursiveCharacterTextSplitter
def split_document(doc_text: str, doc_type: str):
# 根据文档类型选择不同的切分参数
if doc_type == "contract":
# 合同按条款切
splitter = RecursiveCharacterTextSplitter(
separators=["\n第", "\n条款", "\n\n", "\n"],
chunk_size=800,
chunk_overlap=100
)
elif doc_type == "tech_doc":
# 技术文档按章节切
splitter = RecursiveCharacterTextSplitter(
separators=["\n## ", "\n### ", "\n\n", "\n"],
chunk_size=600,
chunk_overlap=80
)
else:
# 默认按段落切
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50
)
return splitter.split_text(doc_text)
坑2:向量模型选哪个?
一开始用的OpenAI的text-embedding-ada-002,效果好但贵。后来换了阿里的text-embedding-v2,便宜了90%,但中文效果差一截。
最后折中方案:用阿里的模型做初筛(召回top 50),再用OpenAI的模型做精排(从50里选top 5)。成本降了80%,效果几乎没变。
ini
# 两阶段检索
def two_stage_search(question: str):
# 粗排:阿里向量模型,召回50条
candidates = aliyun_vector_store.search(question, top_k=50)
# 精排:OpenAI向量模型,从50条中选5条
reranked = openai_reranker.rerank(question, candidates, top_k=5)
return reranked
坑3:temperature设多少?
这个参数调了一周。
temperature=0:回答太死板,经常直接复制文档原文,用户觉得像在搜百度 temperature=0.1:稍微灵活一点,但偶尔会"发挥" temperature=0.7:太自由了,开始编造文档里没有的内容 temperature=0.3:最终选择,在准确性和自然度之间取了个平衡
但不同场景最优值不一样。我们的"合同问答"模块用0(必须精确),"知识百科"模块用0.3(可以适当总结)。
迁移前后的数据对比
说点实际的。迁移完成之后,我们跑了两周的A/B测试,数据如下:
回答准确率:从72%提升到89%(API模型比我们微调的7B强太多了) 平均响应时间:从3.2秒降到0.8秒(不用自己跑推理了) 月度成本:从8万降到不到2000块 模型迭代周期:从2周降到0(API方升级模型,我们直接受益)
省下来的钱:每月约7.8万,一年约94万。加上省下的GPU硬件投入(4张A100约80万),总节省约480万。
当然,切API也有代价:
数据隐私。企业客户的数据会经过第三方API,有些金融、医疗客户不接受。目前对这类客户保留了本地部署方案,但用的是开源模型+本地推理,不是之前的自建微调方案。
依赖风险。如果API方涨价或者服务中断,会受影响。所以同时接了两家API(DeepSeek + 阿里通义),做了自动切换。
定制化不足。对于非常垂直的场景(比如某个特定行业的专业术语),API模型的默认表现可能不够好。这种情况下需要通过prompt工程和few-shot来弥补,效果不如直接微调。
结论
对于90%的AI应用场景,调API+RAG是比自建模型更优的解。除非你有以下需求:
数据绝对不能出内网(金融核心、军工等) 需要极低延迟的实时推理(自动驾驶、高频交易等) 模型本身就是你的产品(你卖的就是模型能力)
否则,别折腾自建了。把省下来的时间和钱花在RAG架构优化、prompt工程、用户体验上,ROI高得多。
但我也不是完全否定自建。我觉得未来会有一个中间态------用API做基座,用LoRA做轻量微调,在本地做推理。这样既有API的效果,又有定制的灵活性。不过这个方案目前还不够成熟,还在观察。
最后想问几个问题:
你们在做RAG的时候,chunk size和overlap怎么选的?我们试了几种方案,不同文档类型差异很大,想看看大家的经验。
有没有人做过"多模型自动切换"的方案?我们现在手动切,想搞成自动的------根据问题类型自动选最合适的模型。
你们觉得2026年底,自建模型会完全被API替代吗?还是会有共存方案?
评论区聊聊。
企业级SaaS后端工程师,ACP持证。以上基于真实项目经验,欢迎拍砖。