1. 技术选型:先想清楚问题,再挑轮子
1.1 模型选型:开源还是闭源,70B还是7B
做AI应用开发,第一个绕不开的决策就是选模型。我见过太多团队一上来就冲着最大的模型去,结果钱花了、延迟扛不住、效果也没比小模型好多少。选模型本质上是选“能力边界”和“成本边界”的交集,而不是选“排行榜第一名”。
先说闭源API和开源模型的取舍。闭源API的优势是效果稳定、迭代省心,适合对效果要求高、团队没有专门优化模型的人才储备的情况。但它的劣势也很明显:数据出域、单价随调用量线性上涨、接口变更不可控。开源模型则可以私有化部署,数据留在自己手里,长线成本更可控,但你需要有人懂推理优化、显存管理、并发调度,这些隐性成本经常被忽略。
我个人的经验是:先确认业务是否允许数据出域,如果不允许,直接锁定开源路线,不用犹豫。如果允许,再算一笔账——按你的日活和调用频次,闭源API一年的费用大概是多少;同样的预算拿去租GPU机器跑开源模型,能覆盖多大吞吐。这笔账算完,方向基本就清晰了。
再说参数量怎么选。这里有一个常见的误区:以为参数量越大越好。实际上,7B到14B的模型在多数垂直任务上,经过微调或好的提示词工程,效果并不比70B差太多,而推理成本可能差出10倍。选型时要结合任务复杂度:简单的分类、抽取、摘要,7B足够;复杂的多跳推理、长文本理解、代码生成,再考虑34B以上;如果还要处理极强的逻辑链和工具调用,闭源大模型仍然是稳妥选项。
还要注意一个细节:同样参数量,不同架构和训练数据的模型,擅长方向差异很大。有的模型中文效果好,有的数学推理强,有的指令跟随稳。选型时不要只看跑分,要找跟你的业务最接近的评测集自己跑一遍,把“基于常见实践的补充说明”落到实处。
1.2 框架与中间件选型:别一上来就堆全家桶
项目一开始,团队容易犯的另一个毛病是“全家桶依赖症”——见一个框架火就接一个,LangChain有了还要接LlamaIndex,向量库用Milvus还要再套一层Elasticsearch,最后项目还没跑起来,基础设施先铺了一大片。
框架选型的核心原则是:你的业务复杂度决定框架复杂度,而不是反过来。如果项目核心就是“文档问答”或“知识库检索”,一个成熟的RAG框架就够了,不需要引入完整的Agent编排框架。如果项目确实要做多工具调用、多步推理、状态管理,才需要考虑接入Agent框架。
以LangChain为例,它确实封装了很多工具,但版本迭代快、抽象层级多,出了问题排查成本很高。我现在的习惯是:对稳定业务,尽量少依赖框架的高级抽象,核心链路用原生代码写清楚,框架只做工具调用的薄封装。这样做的收益是——出了问题你能控制堆栈,而不是被框架牵着走。
数据层选型同样要克制。如果你已经有PostgreSQL在跑业务,优先考虑pgvector,省掉一套新组件;只有数据量到了千万级别、检索性能成为瓶颈,再升级到Milvus这类专用向量数据库。中间的迁移成本是真实的,选型时就要预留演进路径。
1.3 Embedding模型与检索方案:效果差异比你想的大
很多人把检索效果差归咎于“大模型不行”,其实问题经常出在Embedding模型上。不同的Embedding模型对文本语义的捕捉能力差异巨大,尤其在中英文混排、专业术语密集的场景里,选错Embedding会让召回率断崖式下降。
评估Embedding模型时,别只看公开榜单。我建议从业务样本里抽出100~200条真实query,人工标注期望命中的文档,然后分别用候选Embedding模型跑一次召回,计算TopK命中率。这个测试成本很低,但效果立竿见影——往往一轮测试下来,候选模型之间的差距就非常明显了。
检索方案上,纯向量检索不是唯一答案。关键词召回(BM25)和向量召回各有优劣,很多场景下混合召回+重排的效果会显著优于单一方案。举个直观的例子:用户搜一个精确型号“A3-200”,语义向量可能把它匹配到“A3系列设备”,但关键词检索能精准命中型号本身。两者结合,再让重排模型做最终排序,准确率能提升一个档次。
在实践中,我通常会把检索链路分成两段:第一段用BM25和向量检索各自召回Top50,第二段用交叉编码器或者LLM对候选集重排,取Top5输入给生成模型。这套方案在保证效果的同时,也控制了计算成本。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 架构设计:AI应用不是“调API”,是工程问题
2.1 分层架构:接入层、编排层、模型层的边界划分
AI应用的架构设计和传统后端最大的不同在于:你多了一个“模型层”,这个层有自己的特性——有不确定性、有延迟波动、有成本线性增长。如果模型层跟业务逻辑耦合在一起,系统会变得极其脆弱。我的建议是严格按照三层来划分:
接入层负责处理外部请求,做参数校验、鉴权、限流,它不关心你用的是GPT还是开源模型,只负责把用户请求标准化。编排层是核心业务逻辑所在,负责拆解任务、调用工具、维护上下文、决定什么时候调用模型。模型层再做一层统一抽象,屏蔽具体模型提供方的差异,向上提供统一的接口。
模型层的抽象非常关键。我见过一个项目把OpenAI的参数直接写在业务代码里,后来要换成开源模型,改了两个星期。正确做法是先定义一套模型接入接口(比如统一的chat接口,统一的消息格式、超时配置、错误码),然后每个模型提供方写一个适配器。换模型只是加一个适配器的事,业务层完全无感。
分层架构还有一个隐形好处:每一层都可以独立做监控和限流。接入层限流防刷,编排层控制令牌消耗,模型层做重试和熔断。这样任何一个环节出问题,都不会直接击穿整个系统——这一点在模型不稳定时尤其重要。
2.2 Agent架构:拆任务、管状态、控风险
Agent是最近很热的词,但真正把Agent落到生产环境的项目并不多,原因就在于Agent的架构难度被严重低估了。Agent不是简单地“给模型一个工具列表让它自己调”,而是一个完整的任务调度系统。
任务拆解要可控。不要一上来就让模型自由发挥,而是给它一个固定的“工作模板”:先理解用户意图,然后按预定义的策略选择工具,执行后检查结果,不满足再调整。这其实是一种半自主的Agent——既保留了大模型的决策能力,又把风险控制在一定范围内。
状态管理是Agent最容易翻车的地方。多轮工具调用之间,上下文会越积越多,模型容易“忘记”最初的用户意图。我的做法是引入一个独立的状态存储,把关键信息(用户目标、已完成步骤、当前进度)显式地保存下来,每一轮只把必要的上下文传给模型,而不是把对话框无限拼接。
工具调用的错误处理也要设计成闭环。模型调用工具失败的场景非常多——API超时、返回格式错误、结果为空。架构上一定要设计重试、降级、转人工的完整链路。我踩过最惨的一次坑是Agent调用工具抛异常后,直接把异常信息展示给了用户,这体验非常糟糕。后来我加了异常捕获和二次规划机制:工具失败后,模型会被要求重新分析原因并换一种方式解决问题。
2.3 可演进性:模块化与接口设计
AI技术的迭代速度非常快,今天的最优方案可能三个月后就过时了。架构设计如果不考虑演进性,很容易陷入“推倒重来”的窘境。我认为演进性设计有三个关键抓手:
第一,把“变”的部分和“不变”的部分分开。模型、提示词、工具列表是会频繁变化的,你把它收敛到配置中心,改起来不动代码。而请求链路、鉴权体系、数据存储这些相对稳定的骨架,要做扎实。配置和代码分离,是AI应用迭代速度的基本保障。
第二,接口设计要以“业务能力”为单位,而不是以“实现方式”为单位。比如你设计一个“意图识别”模块,接口应该是输入用户消息、输出意图标签,而不是“调用某个模型的接口”。这样哪怕以后从提示词工程换成了微调模型,对上层来说只是换了个实现,不需要改调用方。
第三,预留灰度与回滚能力。模型的输出是不确定的,新版提示词可能对大部分用户更好,但对某一类用户反而变差了。架构上要支持按用户维度做模型版本灰度,并且能一键回滚。这需要在设计之初就埋好流量标识和实验开关,否则后面补会非常痛苦。
3. 实操落地:一个RAG问答系统的选型与架构实录
3.1 项目需求与选型决策过程
接下来讲一个可落地的实例。背景是某企业内部知识库问答系统,要处理产品文档、技术方案、售后记录等多种文档,用户可以通过自然语言提问,系统给出有依据的回答。这个项目的特点是:数据量约百万级文档,答案要求可追溯,并且数据不能出内网。
基于约束条件,选型路径很清晰:数据不能出域,直接排除闭源API;文档量大、需要精准召回,走RAG路线;已有PostgreSQL基础设施,向量检索先用pgvector起步。模型方面选了14B级别的开源模型,在内部测试集上效果符合预期,同时显存和吞吐可以接受。整套方案没有引入任何一个“炫技”组件,全部是成熟稳定的选型。
这个选型过程的核心是:从约束条件倒推方案,而不是从方案倒推需求。很多项目失败不是因为技术不够先进,而是选型跟约束条件不匹配。
3.2 核心模块的落地实现
整个系统的核心模块分为四块:文档解析与切片、Embedding入库、检索服务、生成服务。这四块各自独立部署,之间通过接口通信。
文档解析与切片是RAG效果的地基,但也是最容易被忽视的环节。只按固定字数切片的做法很快会撞墙——一个表格被切断、一个代码块被拆散,检索效果会大打折扣。我的做法是采用结构感知切片:先用解析器提取文档的标题层级,以标题为单位切分,再根据段落语义调整边界,每一片保持在500~800字左右,相邻切片保留少量重叠,防止上下文断裂。
Embedding入库方面,批量入库时按256条一批走异步任务,避免高峰期占用太多资源。向量索引选择HNSW,参数M设为16、efConstruction设为128,这是千万级数据量以下兼顾查询速度和召回率的常用配置。pgvector建索引的语句也很直观,核心参数就是向量的距离类型——这里我选择余弦距离,对文本语义相似度更友好。
检索服务在接收到query后,会并行调用BM25和向量检索,分别取Top50候选,然后通过一个轻量级重排模型(一个3B的交叉编码器)对候选重新打分,最终取Top5。这个链路的延迟大约在300ms左右,其中重排占了大头,但在可控范围内。
生成服务接收到Top5文档后,会把它们和用户问题一起组装成提示词,并加入一个强约束:“如果答案不在给定的文档内容中,请明确回答无法回答”。这一步至关重要——它大幅降低了模型胡编乱造的概率。
3.3 部署与服务化设计
部署方案上,模型推理使用的是vLLM,支持连续批处理和PagedAttention,吞吐量比原生推理框架能提升数倍。显存方面14B模型按int8量化处理,单卡A10即可部署,实测并发在16路左右时,单Token延迟仍能保持在50ms以内。
服务化设计上,接入层用Nginx做负载均衡,后面挂两个无状态的API服务实例;模型推理单独部署,通过HTTP接口被上层调用。知识库更新走离线管道,每天凌晨定时增量更新Embedding,更新期间新旧索引共存,完成后自动切换,对在线服务无感知。
排查链路我用了OpenTelemetry标准来打追踪,从用户请求进入、检索、重排到生成,每个环节的耗时和Token数都有记录。这个做得晚了些,前期没有链路追踪,遇到“用户说回答慢”都定位不到瓶颈,后来补上以后,性能问题基本一眼就能看清。
4. 常见问题与排查技巧实录
4.1 效果类问题:召回为空、回答胡编乱造
效果类问题中,召回为空最常见的原因有三个:文档切片太碎导致语义不完整、Embedding模型和业务领域不匹配、query的表述跟文档中的表述相差太大。排查时先看检索日志,确认向量检索和关键词检索各自有没有召回结果。如果向量检索为空但关键词有结果,问题多半在Embedding模型的理解能力上;两边都为空,基本可以断定是切片策略出了问题。
回答胡编乱造,也就是常说的“幻觉”,解决思路是堵源头和加护栏双管齐下。源头层面,提高召回质量,确保给模型的都是相关上下文;护栏层面,在提示词中明确限制回答范围,同时在代码层增加出处校验——如果模型引用的文档ID不在召回结果里,就拒绝生成并提示补充信息。这套组合实测能把幻觉率降低到可接受范围内。
我还有一个调优技巧:记录所有“用户对回答点了踩”的样本,定期分析。这些负样本会非常清晰地暴露系统短板——是切片问题、检索问题还是生成问题,一眼就能定位。这是效果优化的数据基础,一定要从第一天就开始积累。
4.2 性能与成本问题:延迟高、GPU不够用
延迟高的排查要分环节看。我的经验是先看链路追踪里面的模型推理耗时占比。如果模型推理占了总耗时80%以上,优先优化的是推理侧;如果模型推理占比不高,瓶颈就在检索或重排环节。
模型推理侧的优化手段按性价比排序:量化(从fp16降到int8)、换更小的模型、开启vLLM的continuous batching、增加并发。值得注意的是,很多人一上来就加GPU卡,但并发低的时候加卡没用,瓶颈在单卡推理效率上,先把vLLM和量化做了,通常能释放3~5倍吞吐。
成本问题要盯两个指标:单次请求的平均Token消耗和缓存命中率。Token消耗过大通常意味着提示词冗余,把历史对话和系统提示词压缩一下,成本立刻降下来。缓存方面,相似问题的重复查询经常能撞到缓存,在接入层加一层语义缓存,相同或高度相似的请求直接返回缓存结果,实测能省掉30%以上的模型调用。
4.3 维护与迭代问题:知识库更新、模型升级
知识库更新是RAG系统长期维护中最容易被低估的问题。日常更新积压、Embedding入库任务失败、旧数据没有淘汰,运维压力非常大。我的建议是设计一套完整的状态机:每个文档生命周期有“待解析、已切片、Embedding中、已入库、已失效”五种状态,入库任务失败自动重试三次并告警,失效文档由定时任务清理。这套机制上线后,知识库维护基本做到了无人值守。
模型升级要遵循“小步快跑、随时可退”的原则。升级前先在预发环境跑一遍自动化评测集,确认核心指标没有回退再放量。放量也要按灰度节奏来:先5%流量,观察一两天,再逐步扩大。每次升级都要保存旧版本的模型配置和提示词,一旦发现问题能一键回滚。
这里还要提一个团队协作层面的坑:AI项目的“效果”太主观了,产品、测试、开发对“效果好”的判断经常不一致。我强烈建议从第一天就建立一个标注集,把典型用户问题、期望回答、判断标准写清楚,每次迭代都用同一套标注集做回归。没有这个集合,团队会在“到底变好还是变差”上无休止地扯皮。
最后分享一个我自己的习惯:每做完一个选型和架构决策,我都会把当时的背景、约束、备选方案、最终选择的原因记下来。AI技术更新太快,三个月前的决策理由很可能短期就被推翻,但有记录,你就能持续复盘、持续修正自己的判断,而不是每次都在重复同一个错误。这可能是高级工程师和普通工程师最本质的差别。
