我第一次带团队做AI全栈项目时,最深的感受是:大家以为这是“调一下大模型API”,结果做成了一个涵盖模型网关、RAG管线、Agent编排、流式前端、成本治理的系统工程。我是传统全栈出身,写了好几年CRUD,真正上手AI应用开发后才发现,有一部分经验能复用,但更多经验需要推翻重来。
这篇文章是我近一年在AI全栈开发上的实践总结,适合三种人看:打算从0到1构建AI应用的工程师,负责AI产品落地的产品经理,以及想系统化提升团队AI工程能力的技术负责人。我会把技术选型、架构设计、前端联调、测试评估、部署运维,还有团队协作里容易踩的坑一起讲透。内容里面有很多是我自己踩过之后补起来的“应该这样做”,不是教科书式的理论。
1. 先理清边界:AI全栈项目与传统应用到底哪里不一样
1.1 传统全栈的思路为什么会失灵
传统全栈开发本质上是写一个确定的函数:用户传参,系统校验,业务逻辑处理,返回固定结构的结果。接口定义清楚,异常路径明确,一个订单接口从入参到出参几乎可以穷举所有情况。但AI应用不一样,用户输入是自然语言,模型输出具有概率性——同一个Prompt,温度调到0.7和调到0.2,产出的结果可能完全不同,甚至温度相同、连续调用两次也可能得到两个版本。
我见过最典型的翻车方式,是团队用传统接口思维去做AI后端:先定义一个“完美”的输出JSON Schema,然后要求大模型必须返回这个格式,结果模型偶尔多了一个字段或者漏了一个字段,整个接口就崩了。他们花了两周时间写各种正则和重试逻辑去修补,最后还是不稳定。正确的思路应该是:不要试图用if-else去兜住模型的不确定性,而是用架构去兜底。
1.2 模型在架构中的真实角色
很多AI应用架构图会把大模型画成一个位于中心的“大脑”,所有请求都直接打进模型。这是误导。模型在系统里的真实角色应该是一个“推理引擎”,它擅长的是理解语义、归纳总结、生成内容、拆解任务,但它不擅长稳定地存储事实、执行精确计算或保证事务一致性。你让模型去记用户余额,它会一本正经地编一个数字出来;你让模型去算价格,它甚至会在同一段话里出现前后矛盾的结果。
所以AI全栈架构的核心原则是:能用代码和数据库确定做的事,就别交给模型。举个例子,电商场景里计算优惠金额、库存扣减、订单状态流转,这些必须由业务代码做;模型只负责理解用户意图、生成话术、抽出关键信息,然后把抽取结果交给代码去执行。这个边界划清楚之后,系统稳定性会提升一个量级。
1.3 最常见的架构错误:把业务规则写进Prompt
我拆过不少“看起来能用但一上线就崩”的AI项目,发现一个通病:业务规则全在Prompt里。比如“如果用户是VIP且订单超过100元,则提示可以免运费”“如果商品库存小于10,要提示用户尽快下单”,这些规则全塞在系统提示词里。结果就是:提示词越来越长,模型偶尔漏掉某条规则,产品经理就让开发继续往Prompt里加话术,直到Token长度爆掉。
正确的做法是:凡是可枚举、可判断的逻辑,都抽到代码里做。模型只负责比较窄的一层任务——例如从用户消息中提取“是否询问运费”“有没有表达购买意愿”,然后代码根据这些结构化结果决定走哪条分支。Prompt里放的是模型必需的背景知识和表达风格,不是业务规则。你在评审代码的时候如果看到某条业务规则出现在字符串模板里,就要警惕了。
2. 技术选型实操:模型、网关与开发框架怎么搭配
2.1 模型选型:按任务复杂度分梯队
很多团队一上来就选最强的大模型,仿佛模型越强应用就越强。实际不是这样。大模型的能力强,但成本、延迟、合规风险也高。我现在的习惯是把任务分成几个梯队:
| 任务类型 | 适合的模型梯队 | 理由 |
|---|---|---|
| 意图分类、实体抽取、格式化输出 | 中小尺寸模型 | 延迟低、成本低,任务简单不需要太强推理 |
| 客服问答、内容总结、RAG生成 | 中高端通用模型 | 需要理解复杂上下文,但不需要极端推理 |
| 代码生成、复杂推理、多步任务规划 | 顶级大模型 | 一步错步步错,必须用最强推理能力 |
| 语音转写、图片理解 | 专门的多模态模型 | 不要用文本模型硬扛 |
这里有个容易忽略的点:同一个应用里可以同时用多个模型,没必要一锅端。例如我的一个知识库问答系统,入口先用一个小模型判断用户意图、提取关键词,然后只有真正需要长文生成时才调用大模型。这样整体成本能下降40%到60%,响应速度也快很多。
2.2 模型网关:为什么值得在业务与模型之间加一层LiteLLM
早期做AI应用,团队通常是直接调模型厂商的SDK,A厂商用一套SDK,B厂商又有一套,代码里写满了厂商特定的配置。换模型或做A/B测试时,恨不得改半个月的代码。后来我开始在业务代码和模型之间加一层模型网关,目前用得最多的是LiteLLM。
LiteLLM做的事情很简单:提供统一的OpenAI风格接口,后面接不同厂商的模型——无论是云厂商的API,还是自己用vLLM部署的开源模型。业务代码只认一个base_url,模型切换、路由、成本统计、限流都可以在网关层配置。
比如我们有一段代码原来是这么写的:
from openai import OpenAI client = OpenAI(base_url="http://your-gateway:4000", api_key="fake-key") response = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "你好"}], )不管后端实际跑的是哪家模型,业务代码都不变。你要做的只是改网关路由配置,把gpt-4o映射到另一个模型上。这在做模型灰度、成本控制、灾备切换时特别有用。我强烈建议,任何超过两个模型调用的项目,都先把模型网关搭起来,别等代码写完了再补。
2.3 开发框架:Spring AI、LangChain还是原生SDK
这是团队选型时问得最多的问题。我的答案分场景:
- 如果团队是Java后端,已经有很多Spring Boot服务,直接上Spring AI。它把AI调用、提示词模板、结构化输出都整合进了Spring生态,和事务、配置、监控打通,学习成本对Java工程师很低。
- 如果团队是Python技术栈,且需要灵活做RAG、Agent编排,LangChain可以用,但不能无脑用。LangChain最大的问题是抽象层级太多,出了问题很难排查。我建议只挑它里面成熟的部分用,比如文档加载、文本分割、向量存储接口,至于复杂的Agent链,自己写比硬套框架更可控。
- 如果应用逻辑简单,只是调用聊天和补全,直接用模型厂商的SDK就够了,不要为了用框架而用框架。
框架不是越重越好。AI技术迭代非常快,今天的最佳实践三个月后可能过时。保持薄封装,把可替换性留在模型网关那一层,比什么都强。
2.4 数据层:向量库与业务数据库的边界
RAG项目只要涉及资料检索,几乎都要接向量库,常见的有Milvus、pgvector、Qdrant、Chroma。但很多团队把向量库当成了“另存为”的数据库,把原始文档、元数据、用户数据全部塞进去,结果和业务数据库之间无法同步,数据一致性变得一团糟。
我的建议是:向量库存的应该是“文档切块后的向量+定位信息”,原始业务数据仍然留在业务数据库里。向量库里记录document_id、chunk_id和对应的业务主键,检索命中了某个切片之后,再通过业务主键去业务数据库取完整的最新数据。这样权限控制、数据更新、事务一致性都还能沿用传统数据库的方案。向量库不是万能的,它只解决“语义相似度检索”这一个问题。
3. RAG与Agent:从demo到可用系统的关键一跃
3.1 RAG不是接一个向量库那么简单
很多团队半天就能跑通一个RAG demo:文档丢进去,切块,向量化,检索,拼接Prompt,完成。但demo和可用系统之间差得很远。我见过最多的问题是“检索回了一大堆不相干内容,模型反而被带偏了”。
首先要做的是合理的文本切块。固定字数切块不叫方案,那是暴力破解。我通常的做法是:先按标题层级把文档切成语义段落,每个段落再按滑动窗口切成块,块大小控制在500到800个字符,重叠100到200字符。切完之后必须人工抽查切片,看看有没有把一个完整的意思断成两截。切块的质量直接决定检索质量,这一步偷懒后来都要还的。
其次要设计召回策略。只做向量召回不够,遇到数字、编号、专业术语,向量相似度往往不靠谱。我现在普遍采用“混合检索”:向量召回Top20,关键词召回Top20,然后做重排,把最终进入大模型的片段控制在3到5个。重排可以用Rerank模型,也可以简单做一个基于关键词命中和位置得分的加权公式。重点不在于算法多高级,而在于你能看到每个检索结果的得分,方便调试。
3.2 Agent编排:把工具调用变成可观测流程
Agent是AI全栈里最吸引人也最容易失控的部分。我的态度是:能用确定工作流解决的问题,就别让Agent自由发挥。比如填一个订单查询流程,你完全可以写一个状态机,读取用户意图之后走固定分支。Agent适合的是那种分支实在太多、预先无法穷举的场景。
当你确实需要Agent时,要注意把工具调用变成可观测的流程。我用的是很朴素的方案:每次Agent调用工具前,记录一条日志,包含任务ID、当前步骤、调用工具名、传给工具的参数;工具返回后,再记录工具的返回摘要。这些日志聚合起来,就是一次Agent执行的完整轨迹。排查问题时,如果没有这些轨迹,你根本不知道它是哪一步开始胡说的。
我踩过的另一个坑是Agent循环。早期代码里没有设置最大迭代次数,Agent在某个工具上报错后不断重试,一次交互跑了几十轮,Token费用蹭蹭涨,用户那边还一直转圈。现在所有Agent循环都必须设置两个上限:最大调用次数(比如5次)和最大耗时(比如30秒)。一旦超限,立刻停止,把当前状态返还给用户,并说明“事情没办完,但我会继续跟进”。
3.3 提示词与流程的工程化管理
提示词是代码资产,不是文案。很多团队把提示词写在聊天框里试来试去,最后不知道哪版在上线环境。我现在要求所有提示词都纳入版本管理,和代码一起走Git提交,每个Prompt都有明确的版本号、变更人和变更原因。
页面模板也要做灰度。最常见的办法是我们把所有Prompt模板放到单独的配置文件里,线上环境支持按用户ID或按比例路由到不同模板版本,然后对比业务指标。比如客服助手,先在10%的流量上试新话术,观察用户满意度、转人工率、回答准确率,再决定是否全量放开。这个过程如果靠开发改代码再发版,速度太慢,所以要在一开始就把模板和代码解耦。
4. 前后端联调实战:流式、会话与状态管理
4.1 SSE流式输出的几个坑
AI对话类应用几乎避不开流式输出,因为用户等不了好几秒才看到第一个字。最常用的方案是SSE(Server-Sent Events),但这里面的细节坑很多。
后端用FastAPI实现SSE并不难:
from fastapi import FastAPI, Request from fastapi.responses import StreamingResponse app = FastAPI() async def event_stream(request: Request): async for chunk in llm_stream(request): yield f"data: {chunk}\n\n" @app.post("/chat") async def chat(request: Request): return StreamingResponse(event_stream(request), media_type="text/event-stream")前端用fetch读取流式响应时,要注意浏览器对SSE连接数量的限制,同一个域名同时只能开6个HTTP连接。如果你在页面上做了流式输出的同时还要轮询其他接口,连接很容易被占满,表现为“第二个对话打开后一直不输出”。
另外一个坑是反向代理的缓冲。Nginx默认会对响应做缓冲,流式内容会被攒在一起,用户等半天看不到字。必须在Nginx里关掉缓冲,加上proxy_buffering off;和X-Accel-Buffering: no,才能真正做到逐字输出。还有,前端在中断请求时,后端流可能还在跑,需要监听连接断开事件,及时停止大模型调用,避免白白消耗Token。
4.2 上下文与Token预算管理
多轮对话一定会碰到上下文爆炸的问题。把历史消息全部塞给大模型,最开始还能忍,聊到第20轮,光历史就有上万Token,既贵又慢,模型还容易被早期信息干扰。
我的方案是分层管理上下文:
- 短期记忆:最近3到5轮消息原文,完整保留;
- 中期记忆:对前面的对话做周期性摘要,生成一个简短的“对话进展概要”;
- 长期记忆:从对话中抽取用户的关键信息(比如偏好、待办事项),存到业务库里。
每次调用模型时,按“系统提示词 + 长期关键词 + 中期摘要 + 最近几轮消息 + 当前问题”的顺序拼装。这个拼装顺序不是随意排的,模型对离当前位置越近的内容越敏感,所以当前问题必须放在最靠近的位置。实测下来,这种方案能把单次调用的Token消耗减少一半以上,同时对话体验几乎没有损失。
4.3 前端体验:从“等待”到“协作”
AI应用的前端交互和传统表单提交有本质区别。用户在等模型回答的时候,并不是无事可做,他可能会继续输入文字,可能想取消上一次请求,也可能需要看到模型“正在检索资料”的过程。
我建议至少做到三件事:第一,流式输出时必须支持“停止生成”按钮,否则用户看着一段错误答案被慢慢打出来会很崩溃;第二,要有阶段状态提示,比如“正在理解问题”“正在检索资料”“正在生成回答”,让用户知道系统在干什么;第三,历史会话要支持折叠和续聊,聊到一半刷新页面后应该能恢复上下文。
这些看起来是体验细节,但对于AI产品,用户容忍度很低。传统网页加载慢一点,用户可能等;但AI回答一旦让人觉得“这是在胡扯”或者“怎么不动了”,用户会立刻关掉页面。前端交互的稳定感,比UI好看重要得多。
5. 用评测集和回归测试兜住AI应用的质量底线
5.1 为什么传统测试方法会失效
传统后端测试可以用确定性断言:输入A,期待输出B,比较结果。但大模型输出每次都有细微差别,你不能断言“必须等于某个字符串”,也不应该断言“必须包含某个关键词”,因为这些都不稳定。
我曾经犯过一个错误:用一批历史问题测试新Prompt,发现效果明显提升,于是直接上线,结果第二天用户反馈大量问题。原因是那批测试问题是我手工挑的,集中在少数场景,根本没覆盖真实用户的边界情况。AI应用必须建立专门的评测集,而不是拿几个例子随便点点。
5.2 搭建最小可用评测集
评测集不需要一开始就很大,但必须覆盖核心场景和边界情况。我起步的方式是:从真实用户日志里挑200条问题,按场景分类——例如“查订单”“问价格”“投诉”“闲聊”“多轮追问”“模糊问题”等,每个场景至少20条。每条问题配上参考答案,参考答案可以由产品经理和运营一起写,不要求措辞完全一致,但要有明确的判定标准。
评测指标我一般分三个维度:
| 指标 | 含义 | 评测方式 |
|---|---|---|
| 准确率 | 回答内容是否正确 | 人工打分或LLM对照参考答案打分 |
| 相关性 | 回答是否切题 | 评估答案与问题的语义相关度 |
| 毒性/安全 | 是否出现违禁内容 | 规则加模型审核 |
每次Prompt模板有改动、模型版本有升级,都先跑一遍这个评测集。如果准确率低于某个阈值,不允许上线。这个流程听起来简单,但它能拦住大部分“凭感觉觉得变好了”的不靠谱改动。
5.3 LLM作为裁判与回归框架
人工评测200条问题一次要花半天,频率高了不现实。所以我现在引入了LLM-as-Judge,用一个大模型去给另一个模型的回答打分。当然这也有偏差,不能完全替代人工,但它适合做快速回归。
具体做法是:给裁判模型一份评分标准,例如“1分完全错误,2分部分正确,3分完全正确”,并且要求它先输出评分理由再给分数。每周对同一批测试集跑一遍,生成一份回归报告,比较当前生产版本和候选版本的分数差异。如果候选版本得分低于生产版本2个百分点以上,直接拒绝。这样做虽然不能保证所有问题都拦住,但至少能拦住大多数明显退化的情况。
6. 部署、监控与成本控制:上线只是开始
6.1 模型部署的三种方式与取舍
AI应用部署与传统后端最大的不同是,你必须先决定模型跑在哪。通常有三种选择:
| 部署方式 | 优点 | 缺点 | 典型场景 |
|---|---|---|---|
| 直接调用云厂商API | 速度快、维护少、模型最全 | 成本随量线性增长、数据出域 | 初期验证、对延迟不敏感的业务 |
| 自建模型服务(vLLM等) | 单次成本低、数据可控 | GPU运维复杂、需要弹性伸缩 | 稳定高并发、成本敏感 |
| 混合模式 | 灵活、成本与效果平衡 | 架构复杂、需要路由策略 | 大部分生产系统最终形态 |
如果自建模型服务,我强烈建议用vLLM,它对推理做了很多优化,吞吐量比原生Transformers高很多。部署时要注意设置合理的并发度和超时时间,GPU显存不够会直接OOM,服务不会自动恢复。监控要盯GPU利用率、显存占用、首token延迟、生成token速度这些指标,每一项都直接影响用户体验。
6.2 成本控制的第一道防线:缓存、限流与路由策略
AI应用的成本大头几乎都在模型调用上。我发现很多团队直到月底账单出来才惊呼“怎么花了这么多”,其实成本控制应该从第一天就设计进去。
第一层是缓存。相同的用户问题、相似的上下文,在一天内经常会被重复问到。我在模型网关前面加了一层语义缓存:把用户问题转成向量,命中相似度超过阈值的直接返回缓存答案。这个方案对知识库类问答特别有效,实测能减少30%到50%的模型调用。
第二层是限流和熔断。每个用户每分钟最多调多少次,每天最多多少Token,都要在模型网关层配置好。不限流的结果是,某个客户写了个脚本疯狂调用,账单直接爆掉。
第三层是路由策略。像我在模型选型一节说的,简单任务走便宜模型,难任务才走贵模型。网关配置里设置规则,例如意图分类用haiku,长文生成用opus,成本能肉眼可见地降下来。
6.3 可观测性:让每一次推理都留着“病历”
AI应用排查问题比传统应用困难得多,因为你看不到模型“为什么这样回答”。如果连日志都没有,出了问题就只能靠用户截图。我们现在有一个硬性要求:每次模型调用,必须记录以下几项内容——请求完整内容、响应完整内容、使用的模型名称和参数、Prompt模板版本、检索到的文档片段、Token消耗、响应耗时。
这些记录聚合到一起,就是一次推理的完整病历。出问题的时候,我们可以回放用户当时看到的上下文,复现模型到底是怎么得出这个答案的。我自己遇到过一个模型“失忆”的问题,就是靠日志才发现是前端没有按照约定传历史消息,导致模型每次都不知道之前聊了什么。没有完整日志,这种问题根本无从查起。
7. 团队协作与避坑清单:技术之外的最后一公里
7.1 产品经理、算法、测试与全栈工程师怎么配合
AI项目的失败,很多时候不是因为技术不行,而是角色之间没法协作。传统项目里,产品经理写需求文档,后端定接口,前端开发页面,测试验证逻辑,边界非常清晰。但AI项目里,产品经理要定义的是“模型应该怎么理解用户”,这个需求很难用传统的PRD写清楚,需要产品和工程一起迭代Prompt。
我给团队定的做事方式是:产品经理负责准备评测用例和表述“想要的回答风格”,全栈工程师负责把模型能力封装成接口,算法或AI工程师负责调优提示词和Agent逻辑,测试工程师负责维护评测集和回归流程。每个版本发布之前,产品、研发、测试三方必须一起过一遍评测集,而不是各看各的。
7.2 我反复踩过的十个坑
这十条是我近期项目里反复踩过、最后沉淀进团队规范里的经验:
- Prompt改了但没记录版本,上线后想回滚只能靠CTRL+Z。
- 向量化用的是大模型Embedding,切块不对导致召回质量差,还怪模型不行。
- 流式接口没做超时控制,用户关页面后模型还在跑,Token白白烧掉。
- 上下文管理直接丢全部历史,聊了20轮后单次成本飙到起飞。
- 评测集只有别人给的示例,没有真实用户问题,上线就被打脸。
- 没有踩刹车机制,Agent死循环把预算烧光。
- 模型输出JSON没有容错解析,少一个逗号整个服务报错。
- 缓存没有失效策略,用户资料更新了,回答还是旧版。
- 日志里不存请求原文,出问题想复现都复现不了。
- 一上来就接最强模型,成本高延迟大,后来换成分级路由,体验反而更好。
7.3 最后说点掏心窝的建议
如果你现在刚开始做AI全栈,我的建议是从一个窄场景切入,不要一上来就做“所有问题的答案”。先解决一个用户痛点,把模型、数据、评测、监控这条路跑通,再考虑扩展。技术栈的选择上,保持薄封装、多留接口、随时准备换模型,这比选哪个框架更重要。
AI全栈开发本质上不是“会调大模型”就行的,它考验的是你能否把一个不确定的模型引擎,镶嵌进一个确定性的业务系统里,并让它稳定地创造价值。这条路没有银子弹,但把这些基础实践做好,至少能让你少走很多弯路。