ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI全栈开发实战:从Demo到生产级应用的架构与稳定性设计

AI全栈开发实战:从Demo到生产级应用的架构与稳定性设计 最近这一个多月我密集地把手头好几个项目从“能用就行”重构到了“能上线、能维护、能交出去”的状态过程中感触最深的一件事是AI全栈开发这个词已经被用得太泛了——很多人以为接个大模型API再套个网页就算是AI全栈。但真正把一个AI应用推到生产环境你要面对的不是“模型聪不聪明”而是Prompt怎么治理、上下文怎么管理、工具调用怎么设计、输出怎么校验、模型挂了怎么降级、并发上来怎么控成本。这套东西市面上没有哪份文档会一次性讲清楚基本都是自己踩出来的。这篇文章我打算完全抛开“AI全栈 前端 后端 大模型API”这种粗浅理解直接把我实际项目中验证过的架构拆解、技术选型逻辑、核心模块实现以及高频问题的排查方式整理出来。不是教程式地平铺是按我真实做项目时“先想什么、再做什么、最后怎么调”的顺序写。适合正在做AI应用落地的开发者、准备带AI项目的技术负责人以及想从传统Web开发转过来的朋友参考。1. 重新认识AI全栈开发它和传统全栈到底差在哪1.1 为什么“会接API”不等于“会做AI应用”先讲一个我面试中很常见的场景。候选人简历上写着“精通AI应用开发”聊到项目细节发现他做的事情就是调用ChatGPT的API把用户问题拼到Prompt里拿到返回值展示在页面上。这种项目demo可以但放到生产环境问题一大堆用户多问几句上下文就超限模型偶尔返回一段JSON但漏了个逗号导致整个页面报错第三方接口一限流全线超时线上出了问题翻日志发现连当时传了什么都查不到。真正做AI全栈核心难点在于模型的不确定性。传统后端的输入输出是可控的参数校验、类型约束、异常分支逻辑是确定性的。但大模型本质是个概率系统同样的Prompt这次和下次输出可能不一样格式不稳定、内容可能有幻觉、响应时长波动很大。工程上要做的不是“拥抱不确定性”而是把不确定性控制在一个业务可接受的范围内。这就牵扯到结构化输出、校验重试、上下文压缩、工具调用的可靠编排、可观测性埋点等一系列工作。所以我的定义是AI全栈开发 传统全栈工程能力 模型交互设计能力 AI基础设施的理解与运用。三者缺一不可。1.2 一条完整的AI应用链路从用户提问到结果落地很多人做AI应用脑子里只有“用户 → 模型 → 用户”这条线但实际上生产级链路要长得多。我一般拆成下面六段接入层处理用户请求鉴权、限流、参数校验以及Web端或客户端的长连接。编排层决定调用哪个模型、传哪些上下文、是否触发工具调用搜索、查库、请求内部接口。模型层大模型推理服务可能是云端API也可能是私有化部署。工具层Function Calling背后真正执行动作的服务比如文档检索、数据库查询、写工单。存储层会话记录、向量库、缓存、Prompt版本、埋点日志。可观测层记录每个请求的模型调用数据、Token消耗、延迟、成本用于优化和分析。每一层都有各自的问题边界。比如接入层解决“谁在用”编排层解决“模型怎么用”工具层解决“模型说的话怎么落到真实动作上”存储层解决“系统记忆怎么持久化”。如果你的架构里没有明确区分这些层项目规模一变复杂代码就会揉成一团改哪里都疼。1.3 这篇文章适合谁来参考我在写的过程中脑子里预设的读者是这么几类人第一类是AI应用开发者手里已经有一两个项目想把自己的代码从“demo水准”提升到“可交付水准”。第二类是技术负责人或架构师需要给团队定一套AI项目的开发规范避免每个人按自己的方式乱接一通。第三类是产品经理或测试工程师虽然不直接写代码但需要理解AI项目里常见的坑在哪里方便和技术团队对齐预期。如果你目前只是个初学者刚跑通一个聊天机器人那这篇文章可能稍微偏工程向但第二章的选型思路和第四章的排查方式依然值得提前建立认知。2. 技术选型和架构设计的关键决策2.1 模型层别一开始就纠结“用哪个大模型”模型选型是我见过最容易让团队卡住的问题之一。大家会花好几周对比各家模型的中文能力、代码能力、数学能力最后发现业务上线后真正影响体验的是延迟和成本。我的做法很简单先用一个能力中上、生态成熟、价格合理的模型把业务跑通再建立一套评测集用数据决定要不要换模型。这里说的评测集不是那种网上下的“大模型综合评测”而是结合你业务场景整理的几十条典型输入每条都标注了期望的输出行为。比如你做客服助手就整理“用户对订单不满”怎么回、“用户问发票怎么开”怎么回然后拿不同模型跑一遍人工打标看哪个更符合业务预期。以当前生态来看我把模型大致分成几类类型代表方向适用场景选型注意点通用对话模型GPT、Qwen、DeepSeek、GLM 等主流大模型大部分AI应用的主模型关注中文能力、指令遵循、上下文长度推理增强模型OpenAI o系列、DeepSeek-R1 等数学、逻辑、代码生成、复杂规划延迟较高不适合所有请求都走它长文本模型各家128K以上版本及专门的长上下文模型论文分析、超长文档问答注意实际有效上下文商家标注往往有水分嵌入模型text-embedding系列、BGE等检索增强、向量召回看维度和检索效果维度不是越高越好多模态模型支持图像、音频输入输出的模型图片理解、音视频分析确认是“真理解”还是“OCR套壳”一句话总结业务场景决定模型类型评测数据决定具体型号成本和延迟决定最终能不能上生产。2.2 应用框架框架是工具不是信仰聊天时经常有人问“现在搞AI该学LangChain还是Spring AI还是自己封装”。说实话框架不是越流行越好而是越契合你的团队越重要。如果你团队是Python背景项目以数据分析和快速原型为主LangChain或LlamaIndex的生态能帮你省不少事。但要注意LangChain抽象层次多出了问题排查链路长Debug成本不低。我自己一个经验是小项目或者对稳定性要求高的核心链路宁愿直接用OpenAI兼容SDK手写也不要引入重量级框架。因为框架最大的价值是提供了预制件但业务真正复杂起来你需要的恰恰是精确控制。Java生态里Spring AI最近热度确实高。如果你整个技术栈是Java微服务那用Spring AI确实能统一开发体验。特别是结合Spring AI Alibaba这些国内生态接入国产模型方便团队不用在“Python开了个AI服务、Java怎么调”之间纠结协议。但如果只是单个AI功能模块我认为在Spring Boot项目里直接封装一个ChatClient也不是什么坏事简单直接依赖最少。我自己在主力项目里采用的是轻量封装路线Python后端 FastAPI OpenAI兼容SDK自定义了一个ModelGateway类来统一处理所有模型调用能自己控制重试逻辑、超时策略、Token记录和日志格式维护成本反而比用大框架低。2.3 模型网关与AI基础设施LiteLLM Proxy这类工具的价值这个概念很多刚接触AI开发的人会忽略但它直接决定了你的系统还能不能继续长大。当项目只有一两个模型接口时代码里直接写API地址和Key没毛病。但当你开始接多个模型、多套密钥、多个环境问题就来了Key散落在代码里、模型切换要改代码重新发布、每个模型的计费数据没法统一统计。我现在的做法是引入一个模型网关层生产环境用的就是LiteLLM Proxy。它的作用说白了就是一个“AI请求的交通调度中心”对外提供统一的OpenAI兼容接口对内管理不同的模型供应商。核心收益是三个统一接入业务代码只认一个Base URL和一种协议后端再也不用关心用户用的是哪家模型。统一管控接口Key可以按项目、按环境隔离出问题能直接踢掉某个Key而不影响其他服务。统一观测每个请求的模型、Token数、延迟、花费都自动记录成本看得见摸得着。类似的能力还有Kong的AI网关插件、Portkey、Helicone等核心思路一样把模型供应商的差异和策略收拢到一层让上层业务保持稳定。这个思想也是AI Infra里非常核心的一块——AI应用的基础设施不只是GPU和向量库还有这种流量治理层。2.4 架构分层把“变的部分”和“稳的部分”拆开经过几个项目迭代我最终固定下来的AI应用分层模型是这样适配层负责对接不同的模型供应商统一请求响应格式。供应商变只改这里。服务层业务逻辑比如“聊天”“生成摘要”“写周报”每种能力一个Service。智能体编排层复杂流程的控制逻辑决定模型是否需要调用工具、多轮任务怎么拆分。工具层模型可以触发的真实函数比如“查订单”“搜文档”“发邮件”每个工具必须有明确的入参和返回。数据层会话持久化、向量存储、业务数据库。这个分层最大的好处是模型升级不影响业务业务变化不动模型逻辑工具演进不碰上层编排。一次线上模型故障你只需要改适配层的降级策略完全没有必要让上层服务感知。我在项目里把这种依赖关系画给团队看配合接口定义大家协作边界就非常清晰了。3. 从零到一一个可上线AI应用的核心实操3.1 拿一个具体项目当例子AI知识库助手光讲架构概念太抽象我用一个实际项目来演示。项目叫“AI知识库助手”核心功能三块第一面向内部员工的多轮对话问答能从公司知识库若干PDF、Wiki、工单记录中检索信息回答。第二回答时能引用来源文档并附带“这是基于内部资料生成的”这类免责说明。第三用户可以通过对话让助手生成指定格式的周报摘要。技术栈我定的是前端React TypeScript后端Python FastAPI模型走LiteLLM Proxy统一接入主模型用一个市面主流的通用大模型检索用向量数据库pgvectorRedis做会话缓存。为什么不用LangChain因为业务链路不算特别复杂手写Agent逻辑反而更可控。整体的数据流是这样的用户提问 → 后端从Redis取最近上下文 → 生成检索Query去pgvector召回相关片段 → 拼入系统Prompt → 调用模型 → 流式返回。如果模型判断需要查最新公告再触发一个search_latest_announcement工具拿到结果后二次调用模型生成最终回答。3.2 后端服务的模块拆分与实现后端目录结构我按分层思想组织app/ adapters/ # 模型供应商适配 openai_adapter.py qwen_adapter.py services/ # 业务服务 chat_service.py summary_service.py agents/ # Agent编排 knowledge_agent.py tools/ # 工具定义 search_kb.py search_announcement.py schemas/ # Pydantic模型 chat_schema.py tool_schema.py core/ # 公共能力 config.py logger.py llm_client.py每个工具Service必须有三个东西description对模型描述什么时候用这个工具、parameters入参的JSON Schema、execute真正执行动作的方法。别小看这些定义模型能不能正确调用工具一半靠Prompt一半靠工具描述写得是否清楚。实际请求处理我建议做成无状态服务所有会话状态都外置到Redis。这样服务可以随便横向扩容而不用考虑粘性会话的问题。聊天请求进来ChatService从Redis取出会话历史拼到一起调用LLM流式写回等整个请求完成后再把这段对话追加回Redis。这种做法非常简单但对大部分场景足够稳。3.3 函数调用与Agent工具设计的落地细节这个项目里最核心的代码就是知识Agent的循环逻辑。我用伪代码说明一下整体的判断过程messages build_messages(session, user_query) for step in range(max_steps): response llm_client.chat_with_tools( messagesmessages, toolsavailable_tool_schemas ) if response.tool_calls: # 模型决定调用工具 tool_results [] for call in response.tool_calls: result execute_tool(call.function.name, call.function.arguments) tool_results.append({ tool_call_id: call.id, output: result }) messages.append(response.message) messages.extend(tool_results) continue # 带着工具结果继续让模型推理 else: return response.content # 模型给出了最终回答这里有几个非常容易被新手忽略的点首先所有工具结果都必须明确声明“这条结果是工具来的”大多数SDK里就是直接在assistant消息后面追加“tool”角色的消息。其次要设置最大循环次数我一般设为4次防止模型反复调用工具不收敛。第三每个工具的执行都要有超时单个工具超过15秒就直接返回“工具超时”不要让用户等太久。实践下来工具描述写得好不好的差别极其明显。parameters里每个字段的description要写清楚“什么时候填这个、取值范围是什么、不填会怎样”。模型看到模糊的描述就会频繁漏参或传错类型这是很多Agent稳定不了的隐藏原因。3.4 流式输出与前端联调体验好坏的隐形分水岭AI应用给用户的响应体感差别很大一部分在流式输出。如果用户在网页上等模型完整生成后一次性显示那几十秒体验极差用SSE流式输出实现打字机效果首字出来大概0.5~1秒感官差距天壤之别。后端FastAPI实现的SSE核心逻辑比较简单from fastapi.responses import StreamingResponse async def chat_stream(request: ChatRequest): async def event_generator(): async for chunk in llm_client.stream_chat(messages): if chunk: yield fdata: {json.dumps({delta: chunk}, ensure_asciiFalse)}\n\n yield data: [DONE]\n\n return StreamingResponse(event_generator(), media_typetext/event-stream)前端用EventSource或者fetch配合ReadableStream解析即可。注意几个工程细节第一代理层不能缓冲响应如果前面有Nginx必须关掉proxy_buffering否则流式效果完全消失。第二要处理用户中断比如用户停止生成时前端断开连接后端要能感知并在生成器里退出不然模型还在背后烧Token。第三流式响应的错误也要以SSE格式返回比如“data: {error: model_timeout}\n\n”前端统一解析不要出现连接突然断开但没有任何提示的情况。3.5 稳定性工程重试、降级、缓存模型服务再稳也不敢保证100%可用所以生产级AI应用必须有稳定性设计。我在项目里配置了三个层次的保护重试策略。对于网络抖动、5xx错误、限流这类可恢复错误采用指数退避重试第一次等0.5秒第二次1秒第三次2秒超过3次就放弃。注意重试只针对“幂等请求”——也就是用户没看到结果的阶段。如果是流式输出中途断了前端已经渲染出一部分文本了这时不能盲目重发整个请求比较好的做法是提示用户“生成中断是否重新生成”。降级策略。主模型超时或不可用时系统自动切换到备用模型。我在LiteLLM Proxy里配置了主备两条Provider路由当主路由连续失败超过阈值网关自动把流量切到备用。业务代码完全无感知。这个机制在一次第三方服务大规模故障时帮我保住了项目的基本可用性实测主模型服务不可用的三小时里问答功能一直能用就是回复质量稍微降了一档。缓存策略。对于相同或近似的问题短期内重复提问是做无用功。我在Redis里做了一个语义缓存请求进来先把用户问题向量化在缓存库里找相似度超过0.95的已答问题有就直接返回缓存答案没有才走完整链路。实测周报生成这类高频且重复度高的场景缓存命中率能到30%以上成本和延迟都降了一大块。4. 实际项目里的高频问题和排查实录4.1 模型输出不稳定结构化输出的正确姿势做AI应用几乎避不开“让模型返回一段JSON但模型偶尔给你一段夹杂散文的伪JSON”的问题。我的解决方案分三层第一层能用结构化输出能力就用。OpenAI的response_format参数设为json_object或者用能约束输出Schema的接口让模型在生成时尽量符合格式。第二层对接层做严格解析和校验用Pydantic定义好响应结构解析失败或者字段缺失不要把错误直接抛给前端。第三层把解析错误回喂给模型让它自己修正。做法是把错误信息拼进Prompt再调用一次你之前返回的内容格式有误{error_message} 请严格按照要求的JSON格式重新回答 {expected_schema}实测这种“错误回喂”的恢复成功率非常高第二次基本都能给出合法格式。还有一个更稳的思路是让模型优先输出Markdown格式的代码块再从代码块里解析JSON容错率更高适合业务复杂度高又不方便用固化Schema的场景。4.2 Token超限与上下文管理上下文无限堆积是所有对话类应用都会撞上的墙。模型上下文窗口再大也有满的一天而且塞太多无关历史回答质量不升反降。我在项目里设计了一套三级上下文管理策略滑动窗口最近10轮对话完整保留更早的只保留每轮的一句摘要。摘要压缩当会话轮数超过阈值用模型对历史生成一段整体摘要把摘要作为“记忆”放进系统Prompt。关键信息抽取对于客服、助手类场景把用户身份、订单号、偏好这类关键实体单独提取即使对话历史被压缩了也不会丢。实现时这三个策略按顺序触发每次写回Redis时同步更新“记忆块”。我的经验是摘要压缩这个步骤不要每次对话都做平时几行代码记录最新对话就够了只有会话超过15轮或Token接近上限时才触发。4.3 并发场景下的限流与成本控制模型API是按Token计费的没有限流措施的话一次热点活动就可能烧掉一大笔钱甚至被打爆。我在LiteLLM Proxy层做了两类控制限制每个用户每分钟的请求次数和Token数量。比如普通用户每分钟最多10次请求峰值Token不超过3万。另外做账号级别的并发数控制避免某个异步任务一次性发几十个请求把额度打穿。成本建模方面我每次请求都会记录prompt_tokens、completion_tokens实时累加到按天维度。OpenAI类模型的成本公式大概是总费用 prompt_tokens单价 × prompt_tokens completion_tokens单价 × completion_tokens。不同模型价格差异很大需要建立一张价格表在日志查询或监控大盘里直接折算金额。有了这些数据你才能回答老板最常问的一句话“我们这个AI功能一个月到底花了多少钱”4.4 可观测性日志到底要记哪些字段AI应用的传统日志在排查问题时会非常无助因为关键信息不在于“状态码200”而在于“当时让模型看了什么”“模型回了什么”。我在项目里建立了专门的AI审计日志表字段如下request_id # 请求唯一ID user_id # 用户标识 conversation_id # 会话ID model # 实际使用的模型名 prompt_tokens # 输入Token数 completion_tokens # 输出Token数 total_latency_ms # 总延迟 ttft_ms # 首Token时间流式中很重要 cost_usd # 成本预估 system_prompt # 用户看到的系统Prompt截断 user_input # 用户输入 model_output # 模型输出 tool_calls # 调用了哪些工具 error_type # 错误类型如timeout/parse_error有了这张表线上出了“回答不对”“响应太慢”“费用暴增”之类的问题都能快速定位是模型问题、Prompt问题、还是工具执行问题。我还养成了一个习惯所有Prompt和模型响应都做脱敏后全量记录方便事后复盘和优化。这个习惯在迭代Prompt的时候帮了大忙很多“为什么这次效果好了/差了”的结论都是从历史日志里对比出来的。4.5 部署阶段的几个容易忽视的坑代码写完之后部署往往是另一场“踩坑之旅”。先说并发模型。用FastAPI的Uvicorn部署时很多人直接Uvicorn默认单worker启动结果模型调用是IO密集型的单worker并发能力严重受限。我通常用uvicorn --workers 4配合--limit-concurrency来控制并发更多场景会扔到Kubernetes里做HPA自动扩缩容。再说内存问题。有些模型SDK会在内存里做缓存当并发量上来后内存飙升。我在项目里吃过一次亏K8s Pod设置了512Mi内存限制结果高峰期直接OOMKilled。后来排查发现是默认的Token缓存策略在作怪调整缓存大小和回收频率后稳定多了。这里建议所有AI服务容器都配上内存限制同时监控RSS趋势别等崩了才注意到。最后是冷启动问题。加载一个几GB的模型文件再启动服务时间可以拖到几分钟。如果没有做优雅启动和健康检查流量直接打进来就是一片5xx。一般做法是加/healthz和/readyz接口K8s里分别对应存活探针和就绪探针就绪探针等模型真正加载完才返回200保证流量只在服务可用后进入。5. 一些经验总结和补充建议这个项目从最初的原型到稳定上线前后差不多三周。过程中我最大的体会是AI应用开发的难点其实不太在“模型调参”而在“怎么把不确定性变成产品里可控的一部分”。模型能力提升是外部趋势你控制不了但你可以控制的是Prompt怎么设计、上下文怎么管理、工具怎么编排、输出怎么校验、出问题怎么降级这些才是团队真正的核心竞争力。补两个我最近还在用的小技巧。一个是Prompt版本管理。很多人改Prompt是直接在代码里改字符串改完上线效果变差想回滚还得靠Git翻历史。我现在把每个业务场景的Prompt都放到单独配置中心带版本号和发布时间配合数据回看Prompt优化就变成了一件可迭代、可验证的事情。另一个是模型调度逻辑一定要做成可配置。哪些用户走精简模型、哪些场景必须走增强推理不要写死在代码里用一个规则表控制。今天你觉得某类问题不需要强推理明天业务要求变了改配置就能切换不用跟着发布窗口走。如果说还有什么最后的建议那就是别迷信某个框架或者某个模型能解决所有问题老老实实地把你自己的业务链路吃透把每个环节的观测做好这个AI应用大概率就离稳定不远了。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进