这两年做AI应用,我最大的感受是:AI全栈开发和传统全栈开发完全是两码事。很多人觉得会调API、会写前后端,再套个大模型就是AI全栈了,真正上手才发现,模型输出不稳定、上下文管理混乱、Agent一跑长链路就崩、成本一天天失控,问题一个接一个。说到底,AI全栈开发的核心不在“写代码”,而在“驾驭不确定性”,这也是所谓从vibe coding到harness×SDD全栈开发实战这条路背后的逻辑。
这篇文章把我自己在多个AI项目里的沉淀做一次系统整理,覆盖技术栈选型、提示词与上下文管理、Agent开发、模型部署与成本优化、常见问题排查,全是可落地的实操经验。如果你是独立开发者、全栈工程师、AI产品经理,或者正准备从0到1做一款AI应用,这篇内容应该能帮你少踩很多坑。
1. AI全栈开发到底在开发什么:从可视化编程到工程化
1.1 AI全栈和传统全栈的本质区别
传统全栈开发的边界很清晰:前端、后端、数据库、部署,每一层都有成熟规范和稳定接口。你写一个request发出去,返回结果大概率是确定的,出错也可以从堆栈日志里找到根因。AI全栈完全不一样,最核心的依赖——大模型——本身就是一个概率系统。同一个Prompt,同一套参数,这次输出60分,下次可能输出90分,这个不确定性会从模型层一路传导到产品层,所以AI全栈的第一课不是学框架,而是学会“接受不确定性”。
我见过很多传统后端转AI开发的同事,初期最大的痛苦就是“没有Bug可修”。模型输出不符合预期,你说它是Bug?修改Prompt后还是不稳定,你说它是配置问题?调了温度参数、换了模型版本,效果依然飘忽。这种时候,你需要建立一套新的工程思维:把模型当作一个需要“对齐”的队友,而不是一个可以严格定义的函数。AI全栈开发的核心工作,是在模型能力之上搭建约束、校验、兜底和评估的体系,让概率系统尽量表现得像一个确定系统。
1.2 从vibe coding到harness×SDD的演进路径
Vibe coding是最近很火的一个概念,大意是开发者只负责描述意图,让AI生成代码,自己凭“感觉”把代码拼起来。这种模式做Demo、写脚本、做一次性工具非常高效,我平时很多内部小工具也是这么写的。但你要是拿它去做严肃的产品级应用,大概率会在项目中期开始崩溃:AI生成的代码没有统一约束、函数命名混乱、数据流不清晰、依赖关系一塌糊涂,一旦需要加需求,改动一个点会牵连一片。
所以就有了harness×SDD这套思路。Harness可以理解成给AI加“围栏”,把AI的行为约束在边界内;SDD是Specification-Driven Development,规格驱动开发,要求你在写代码之前,先把系统行为、接口契约、数据格式、边界条件都用清晰规格定义好。这个组合在AI全栈实践中非常有用,人负责定规格、把方向,AI负责在规格围栏内高速生成实现,既发挥了AI的速度,又守住了工程的底线。
1.3 AI全栈工程化的四个关键层次
我把一个完整的AI应用拆成四个层次:应用层、编排层、模型层、基建层。应用层是用户看到的产品,包括前端交互、业务逻辑、数据存储;编排层是AI应用最独特的部分,包括Agent状态机、工具调用、多步任务编排、上下文管理等;模型层是LLM接入、提示词管理、模型路由和降级策略;基建层则包含统一模型网关、向量数据库、可观测性、成本监控和私有化部署等基础设施。
很多刚入行的朋友,注意力全放在模型层,天天研究哪个Prompt写得妙,哪个模型更强。但你去看那些真正稳定盈利的AI应用,它们的竞争力不在模型层,而在编排层和基建层。同样的模型,别人做得稳定、成本可控、可观测、能迭代,这才是工程化的价值。
2. AI全栈技术栈选型:模型网关、Agent框架与部署方案
2.1 模型接入层的统一网关:LiteLLM Proxy实战配置
先聊模型接入。如果你只对接OpenAI一家,那直接在代码里调用OpenAI SDK完全没问题。但真实项目很少只有一家模型,你需要支持GPT、Claude、Gemini、国内的多个开源模型,甚至还要在本地模型和云端模型之间切换。这时候,没有统一网关会很痛苦,每个模型一套SDK、一套鉴权、一套计费,代码里五花八门的调用逻辑,维护成本飙升。
我之前项目中采用LiteLLM Proxy作为统一模型网关,它就是你的“模型路由器”。一次接入,所有模型都可以通过OpenAI兼容的接口调用,底层自动做密钥管理、负载均衡、重试和成本记录。它的配置就是一份YAML文件,核心结构大概是这样的:
model_list: - model_name: gpt-4o-mini litellm_params: model: openai/gpt-4o-mini api_key: os.environ/OPENAI_API_KEY - model_name: claude-sonnet litellm_params: model: anthropic/claude-3-5-sonnet-20241022 api_key: os.environ/ANTHROPIC_API_KEY - model_name: local-llama litellm_params: model: openai/local-llama api_base: http://localhost:8000/v1 api_key: dummy-key litellm_settings: drop_params: true set_verbose: false general_settings: master_key: sk-your-master-key database_url: postgresql://user:pass@localhost:5432/litellm配置好之后,启动服务只需要一行命令:litellm --config config.yaml。之后你所有代码都只认gpt-4o-mini、claude-sonnet、local-llama这三个名字,背后是哪个模型由网关决定。哪天想从Claude 3.5切到Claude 4,只改YAML,不改业务代码,这在模型更新频率极高的当下太关键了。
这里有一个实操心得:LiteLLM Proxy的model_name是业务代号,最好和具体模型版本解耦。比如你对外叫claude-sonnet,内部映射到具体版本,升级模型时业务代码完全无感。另外,建议从一开始就开启database_url,把所有请求日志、Token用量、成本数据落到PostgreSQL里,后面做成本分析和模型效果对比时,这些历史数据就是决策依据。
2.2 Agent框架选型:LangGraph、AutoGen还是自研
Agent开发是AI全栈里最让人纠结的部分,框架选择特别多。以我自己的实践来看,选型就是三个方向:用LangGraph做有向图编排、用AutoGen做多Agent对话协作、自己写一个轻量编排内核。
LangGraph的核心思想是把Agent流程定义成一张有向图,节点是“调用模型”或“执行工具”,边是“状态转移”。好处是复杂流程可视化、可控性很强,特别适合那些步骤固定、分支明确的任务,比如客服工单处理、多轮审核流程。它的状态管理基于LangChain生态,如果项目本来就用了LangChain,上手非常顺。AutoGen则相反,它擅长的是多个Agent之间的对话协作,一个Agent当“发言人”,一个当“审查员”,来回讨论得到结果。这种方式在处理开放式问题时效果有意思,但代价是成本高、不可控,生产环境要谨慎使用。
我的建议很简单:如果你的Agent流程是确定的,就别上重型框架,自己维护一个while循环加工具注册表足够了。很多项目第一步用LangGraph,后来发现真正复杂的不是图,而是工具的参数校验和错误恢复,这跟用什么编排框架没关系。最近我做一个文档处理Agent,最终用的是自研的“任务清单”模式,把所有步骤定义为可重试的任务,中间加一个“人类确认”节点,代码反而比框架更简洁。选框架前,先问自己一个问题:你的Agent到底有没有复杂的动态路由?没有就不需要上图形框架。
2.3 模型部署方案:API调用与私有化推理的权衡
模型部署是另一道选择题。最省事的是直接用云端API,按Token付费,无需关心GPU和推理优化。但如果你的场景涉及私有数据、高并发、低延迟或者长期高调用量,API的成本和合规问题就会暴露出来。这时候需要私有化部署,目前我实测下来最稳的开源推理方案是vLLM,吞吐量高,并且兼容OpenAI接口格式,接LiteLLM Proxy非常顺。
vLLM部署一个大模型的基本命令大致是这样:
vllm serve Qwen/Qwen2.5-72B-Instruct \ --tensor-parallel-size 4 \ --max-model-len 32768 \ --gpu-memory-utilization 0.9 \ --port 8000 \ --served-model-name local-qwen72b参数里--tensor-parallel-size表示用几块GPU做张量并行,--gpu-memory-utilization控制显存利用率,--max-model-len决定最大上下文长度。这几个参数直接影响推理性能和稳定性。我的经验是,gpu-memory-utilization不要拉满到0.95以上,留一点余量给KV Cache的碎片化,否则长时间运行后容易OOM。max-model-len也不是越大越好,长度越大,KV Cache占用的显存呈线性增长,实际业务中大部分请求根本用不到32K,设成16K甚至8K,吞吐量会明显提升。
私有化和云API不是二选一,成熟的方案是混合路由:常规请求走便宜的私有化模型,复杂任务自动路由到云端更强的模型,LiteLLM Proxy的router_settings就是干这个的。比如,先让一个小模型处理分类任务,置信度低的时候再升级到大模型,这套“级联路由”方案能把成本降到单纯用大模型的30%左右。
3. 从写提示词到写指令集:AI编程的核心方法论
3.1 提示词工程:从“万能咒语”到结构化指令集
很多初学者把提示词工程理解为“怎么把需求说得更清楚”,其实提示词工程的核心是“把不可控的输出空间压缩到可控范围”。你给模型的自由度越高,它发挥的空间就越大,出错概率也越高。好的提示词不是一段“请帮我写一个……”,而是一套完整的指令集,包括角色定义、任务目标、输入输出格式、约束条件、示例,以及遇到边界条件时该怎么办。
我常用的一个结构化提示词模板是这样的:
你是[角色],擅长[领域]。 任务:[具体任务描述]。 输入数据:[数据结构说明]。 处理步骤:[步骤1] -> [步骤2] -> [步骤3]。 输出要求: - 必须是合法的JSON,结构为 { "result": ..., "confidence": ... } - 如果信息不足,result返回"UNKNOWN",confidence返回0 - 禁止输出任何解释性文字 示例: 输入:{...} 输出:{ "result": "xxx", "confidence": 0.95 }这个结构看起来平平无奇,但它解决了三个核心问题:一是输出格式可控,方便程序解析;二是边界行为明确,信息不足时知道怎么兜底;三是给了一两个示例,让模型理解预期,而不是靠“感觉”猜。输出格式统一这一点极其重要,我见过太多项目花大量时间写正则解析模型输出,根本原因是提示词里没有明确要求输出合法JSON。把这一步做好,解析代码能少写一半。
另外还有一个小技巧:把提示词和系统Prompt分级管理。业务相关的指令放System Prompt,用户输入相关的内容放User Message,不要把两者混在一起,否则迭代提示词时很容易互相干扰。提示词本身也要版本化,每次修改都记录效果变化,像管代码一样管提示词,这对后续回归测试和问题定位非常重要。
3.2 上下文管理:决定AI应用质量的关键变量
AI应用的上下文管理,比提示词本身更影响最终效果。我常跟团队说一句话:你给模型看到什么,它就只能答什么。大部分AI应用效果差,不是模型不行,而是上下文给得不对。上下文管理主要处理三个问题:该放什么、放多少、怎么放。
该放什么,是要分析用户请求真正需要哪些信息。比如一个客服机器人,用户问“退款多久到账”,它需要的上下文是订单状态、退款进度、支付渠道,而不是用户三年前的浏览记录。放多少,是要控制上下文在Token预算内。我维护过一套上下文预算分配体系:系统指令占10%,对话历史占30%,知识库检索结果占40%,用户当前输入占20%。当然比例因场景而异,但这个思路是对每个模块的Token消耗做预算,而不是放任自由增长。
怎么放,涉及消息结构的组织。长对话场景建议做“滑动窗口摘要”,当对话超过一定长度时,把早期的对话用模型压缩成摘要,替代原始消息。知识库场景建议做“先检索后拼接”,先用Embedding把最相关的片段捞出来,而不是把所有文档一股脑塞进去。这个“预算-检索-压缩”的框架,是上下文管理的核心方法论,每个AI开发者都应该熟练掌握。
3.3 AI辅助编码中的代码审查与测试策略
AI编程工具现在已经很成熟,我日常写代码大概60%到70%是AI生成的。但AI生成的代码有一个显著特点:单点功能实现得又快又好,系统性设计一塌糊涂。你让它实现一个排序函数,它三秒给你写出来;你让它设计一套订单状态机,它给出的方案可能完全没考虑幂等和并发。所以,AI辅助编码的黄金法则是:AI负责写,人负责审。
代码审查时,我最关注的是资源释放、边界条件、异常处理和数据类型。AI很擅长写“正常路径”的代码,但经常忽略“异常路径”。比如调用外部API之后忘记处理超时,文件读取之后忘记关闭,列表越界,字典取Key时没有判空。这些在小Demo中不会暴露,一旦上线跑真实流量就会变成事故。
针对这个问题,我给AI下的指令里一定包含“考虑所有可能的异常情况,并在代码中处理”。代码生成后,要求AI自己写单元测试覆盖边界条件,并主动执行测试。这个“AI生成代码+AI生成测试+人做审查”的组合,目前效率最高。但要注意,不要让AI生成核心业务逻辑的测试,尤其是涉及金钱、权限、数据一致性的逻辑,必须由人来写测试用例,AI生成的测试容易和实现“同错”,测试通过也说明不了问题。
4. 从vibe coding到harness×SDD:一个AI应用从0到1的实战拆解
4.1 需求定义:先写行为契约,再谈代码
我做AI应用项目有一个习惯:在写第一行代码之前,先和团队一起写一份“行为契约”。这份契约不描述怎么写,只描述做什么,格式大概是:输入是什么,输出是什么,边界条件是什么,失败时怎么办。这个习惯就是从SDD里学到的,它最大的价值不是文档本身,而是逼你想清楚产品边界,也让AI后续生成代码时有据可依。
举个例子,之前做一个智能工单分类系统,我们的行为契约中有一条是“若用户消息同时命中多个类目,按优先级排序并返回置信度最高的前两个”。这句话看着简单,但如果没有契约,AI生成的系统可能只返回一个类目,也可能返回全部类目,不同的返回值直接决定了后续人工流程怎么设计。SDD就是把这种“模糊地带”在开发前全部清掉。
写行为契约时有一个技巧:所有“如果……那么……”的规则都要显式写清楚。比如“如果模型返回JSON解析失败,那么系统返回错误码ERR_PARSE,并触发一次模型重试”“如果用户输入超过2000字,那么截断并提示用户精简内容”。这些规则看起来琐碎,但正是它们把AI的不确定性挡在产品逻辑之外。
4.2 快速原型:让AI帮你完成第一版
行为契约确定之后,快速原型阶段就可以大胆用vibe coding方式。这个阶段的目标不是写出完美代码,而是把整个链路跑通,验证核心假设。我一般是让AI根据行为契约直接生成全套代码,包括后端接口、前端页面和数据库表结构,然后自己快速看一遍关键路径,把明显问题修掉。
这里分享一个我自己总结的“AI快速原型Prompt”:
请根据以下需求生成一个最小可用的Web应用: - 技术栈:FastAPI + React + SQLite - 功能: - 用户输入一段文本 - 后端调用LLM接口进行分类 - 返回分类结果和置信度 - 接口契约: - POST /api/classify,入参 { "text": string },出参 { "category": string, "confidence": float } - 模型调用失败时返回HTTP 503 - 目录结构: - backend/main.py - frontend/src/App.jsx - 代码生成后,同时生成一个测试文件,覆盖正常分类和模型异常两种情况这个Prompt的效果通常还不错,因为我把接口契约和目录结构都定了,AI不需要做设计决策,只需要执行。原型跑通后,这个版本的价值是帮团队看到一个可以点、可以点的真东西,让产品讨论从“想象”变成“对着实物讲”。
4.3 工程加固:把AI生成代码变成可维护的系统
原型能跑,离生产可用还差得很远。工程加固阶段,我一般做四件事:第一,整理项目结构,把AI生成的一锅烩代码按职责拆分到独立的模块;第二,补全错误处理和日志,所有外部调用必须有超时、重试、熔断机制;第三,加强数据校验,所有进入系统的数据,用Pydantic或类似工具做严格校验;第四,补监控指标,至少要有请求量、成功率、延迟、Token消耗四个核心指标。
这个阶段的核心思路是harness,给AI生成的原型装上工程护栏。举个例子,原型中的模型调用代码可能是这样:
response = client.chat.completions.create( model="gpt-4o-mini", messages=messages ) return response.choices[0].message.content加固之后,至少要是这样:
try: response = client.chat.completions.create( model="gpt-4o-mini", messages=messages, timeout=30 ) content = response.choices[0].message.content if not content: raise AIServiceError("model returned empty response") return content except TimeoutError: logger.error("model call timeout", extra={"model": "gpt-4o-mini"}) raise AIServiceError("model_timeout") except APIError as e: logger.error("model api error", extra={"status": e.status_code}) raise AIServiceError("model_api_error")加超时、判空、异常分类、结构化日志,这一套下来,系统才算有了基本的可观测性和可恢复性。很多AI项目死在生产环境,不是死在AI能力不足,而是死在最基础的工程保障缺失。
5. AI Agent开发:工具调用、状态管理与失败恢复
5.1 Agent的四个核心组件
Agent是AI应用里复杂度最高的形态,我拆开来讲。一个最小可用的Agent有四个核心组件:模型大脑、工具集、状态管理器和执行循环。模型大脑负责推理决策,决定下一步做什么;工具集是Agent可以调用的外部能力,比如查数据库、调API、发邮件;状态管理器存储当前任务的所有上下文,包括用户目标、已执行步骤和中间结果;执行循环是Agent的引擎,不断重复“思考-调用-观察”的ReAct循环,直到任务完成或达到最大步数。
这四个组件里,最容易被忽视的是状态管理器。很多Agent跑着跑着就“失忆”了,前面拿到的数据后面又找不到了,根本原因是状态设计不到位。我的做法是给Agent定义一份显式的任务状态对象,存放当前目标、已完成步骤、关键中间产物、当前进度和错误记录。每一步执行完都更新这个对象,并把它序列化存储,这样即使Agent中途崩溃,也可以从状态对象恢复执行。
5.2 工具调用:参数校验与精确的JSON Schema
工具调用是Agent能力的延伸,但也是出错高发区。模型生成的工具调用参数经常出现字段缺失、类型不对、枚举值非法等问题。解决办法是在工具定义中使用严格的JSON Schema,并在执行工具前先做一层参数校验。
一个工具定义示例:
{ "type": "function", "function": { "name": "create_ticket", "description": "创建一条新的工单记录", "parameters": { "type": "object", "properties": { "title": { "type": "string", "minLength": 1, "maxLength": 200 }, "priority": { "type": "string", "enum": ["low", "medium", "high", "urgent"] }, "customer_id": { "type": "string", "pattern": "^CUS-[0-9]{6}$" } }, "required": ["title", "priority", "customer_id"], "additionalProperties": false } } }additionalProperties: false非常关键,它告诉模型不能输出Schema之外的字段,能有效抑制“幻觉字段”。还有一些模型会“编造”不存在的工具名,这种要在解析时加白名单校验,如果模型请求了一个未注册的工具,直接中止本次调用并返回错误提示,而不是硬着头皮去执行。
工具调用的另一个要点是结果反馈。工具返回的结果结构要统一,至少包含status、data、error三个字段。Agent在看到工具返回后,能明确判断“这个步骤成功了,下一步做什么”或“这个步骤失败了,我该重试还是换方案”。很多Agent效果差,不是因为工具能力不够,而是工具返回的信息不结构化,模型根本看不懂结果。
5.3 多步骤任务的状态管理与失败恢复
多步骤任务的复杂点在“每一步都可能失败”,而失败的原因千奇百怪:模型抽风返回了无意义内容、工具调用超时、数据格式不对、外部API服务不可用。我设计Agent时会遵循一个原则:默认每步都可失败,默认每步都要可重试,默认整体失败要能恢复到“安全状态”。
具体实现上,一是给每个步骤设置重试计数器和超时时间,例如默认重试2次、单步超时30秒;二是在重试之间做降级处理,比如第一次用强模型,重试时切换成弱模型加更明确的指令,或者反过来;三是整个流程设置最大步数上限,比如最多执行10步,超过就强制结束,返回当前状态让用户决策,避免Agent陷入死循环导致成本和时间的双重失控。
还有一类情况很隐蔽:Agent看似成功完成任务,但结果其实是错误的。比如,它本应调用工具A获取订单数据,结果调用了工具B获取了用户数据,然后基于错误数据生成了“正确格式”的答案。这种“格式正确但语义错误”的情况,比显式失败更危险。我的应对办法是双保险:对关键数据,要求Agent返回数据来源标识,同时在前端提示“此结果由AI生成,请注意核对关键信息”。AI应用永远要把责任边界画清楚,这是产品设计中不可妥协的一环。
6. 常见问题与排查技巧实录
6.1 模型输出不稳定的处理思路
模型输出不稳定是AI应用最常见的投诉,我排查这个问题的顺序基本是固定的。先看是不是Prompt歧义,同一个指令换一种表达是否结果就不一样了,如果是,把指令写得更具体,并补充示例。再看是否上下文变化导致,比如对话历史里埋了看似无关但实际干扰模型判断的内容,这种情况优先清理上下文。最后看是不是参数问题,比如温度设得太高导致输出发散,对需要稳定的场景(如分类、抽取、格式化输出),把temperature调到0或接近0。
如果以上都排查完还是不稳定,就要考虑是不是任务本身超出了模型能力边界。比如让一个轻量模型做复杂推理,它不稳定是正常的。处理办法是升级模型,或者把任务拆细,让每一步都更简单。这里我有一个经验:每当你觉得“换个提示词就能解决”,先冷静试三次,如果三次结果都有明显差异,基本可以判断不是提示词的问题,而是任务复杂度或模型能力的问题。
6.2 上下文窗口爆掉的处理方案
上下文窗口爆掉是长对话或长文档处理中的高频问题。模型上下文上限是硬约束,超过就会直接报错。解决思路不是去“扩容”,而是“瘦身”。我维护了一个三层上下文处理策略:第一层,对消息做裁剪,保留最近的N轮对话,更早的做摘要;第二层,对长文档做切分和检索,每次只放入与当前问题相关的片段;第三层,如果还是超限,就触发“追问澄清”机制,请用户把问题说得更具体,缩小处理范围。
比如处理一份100页的PDF,正确的做法不是把PDF全文塞进Prompt,而是先做解析分块,再根据用户问题做检索召回,最后把Top5片段和问题一起交给模型。这个过程中,Embedding模型的选型、分块大小、检索策略都会影响最终效果。分块大小我一般设成500到1000字,重叠200字,既保证语义完整性,又避免召回时切碎关键内容。
6.3 成本控制与Token消耗优化
成本失控是AI应用跑起来之后迟早会遇到的问题。我见过一个项目,上线一个月API账单比预期高了8倍,最后排查发现是日志系统把完整Prompt和响应都打到了日志里,而这些日志又被当作上下文喂给了模型,一层层放大。成本优化首先要能看到钱花在哪,LiteLLM Proxy的成本报表能按模型、按用户、按接口维度统计Token消耗,这是优化的前提。
在成本控制上,我常用的手段有五个:一是用缓存,对相同或相似的请求做语义缓存,命中缓存就直接返回历史答案,能省掉很大一部分重复消耗;二是用小模型兜底,先把意图分类、实体抽取这类简单任务交给轻量模型,只有复杂推理才调用大模型;三是Prompt瘦身,删掉冗余指令和无关的上下文,每一轮调用前都检查最小必要Token量;四是模型降级,日常流量走便宜模型,在关键节点才升级到强模型;五是做并发限制和告警,设置每日Token预算,超过阈值自动触发降级或熔断,防止意外流量把账单打爆。
7. 一些写在最后的实战体会
滚了这么多项目,我最大的体会是:AI全栈开发真正难的不是技术,而是技术决策的节奏感。每引入一个模型、一个框架、一个工具,都要想清楚它解决什么问题、带来什么新问题。群里天天有人争论LangGraph好还是自研好、GPT好还是Claude好,这些争论很多时候没有意义,因为脱离业务场景谈技术选型就是空谈。
我自己的选择标准很简单:优先选能快速验证的方案,优先选生态成熟的技术,优先选可观测性强的架构。AI技术迭代实在太快,花三个月打磨一个“完美方案”,上线时可能模型已经换了好几代。先跑起来,再逐步加固,这才是AI全栈开发在当前阶段最务实的路径。
最后分享一个小习惯:每个AI项目我都维护一份“AI踩坑日志”,把遇到的每一次异常输出、每一次上下文失控、每一次成本炸裂记录下来,附上当时的触发条件和处理方案。这个日志的复用价值远超预期,很多时候项目里出现新问题,一翻日志发现之前就遇到过类似情况,直接拿方案改改就能用。AI全栈开发的知识迭代太快,光靠记忆靠不住,把经验沉淀成文档,才是应对这个快速发展领域的最好方式。