做AI工程这件事,踩坑多了之后就会发现一个真相:真正决定项目能走多远的,不是模型选得有多新、参数调得有多花哨,而是工程化的基本功。尤其当你想从零开始搭一套AI应用,不靠复制别人的现成模板,而是自己一步步把环境、模型调用、提示词管理、Agent编排、测试部署这几层全打通的时候,这种“from scratch”的经验价值比任何教程都大。
这套东西适合谁?想系统入门AI应用开发的程序员、被各种AI框架文档绕晕的初学者、以及在团队里负责从0到1搭建AI服务的技术负责人。你不需要先读几百页论文,只需要跟着工程化思路走一遍,就能把一个能跑的AI系统拆成自己真正理解的模块。我下面写的这些,不是教科书式的概念复述,而是把我从零搭建AI工程时踩过的坑、验证过的方案、反复调整后留下来的最佳实践,完完整整摆出来。
1. 整体设计思路:为什么“从零开始”反而更高效
1.1 先搞懂AI工程和AI调包的本质区别
很多人以为AI工程就是“调库加写prompt”,实际上远不止如此。当你只调用一个现成的API做单轮问答时,那是“调包”;但当你需要构建一个能稳定服务生产环境的系统时,你面对的是完整的技术栈:模型层怎么抽象、prompt怎么版本管理、上下文怎么裁剪、Agent怎么编排工具、输出怎么校验、成本怎么跟踪、模型挂了怎么降级。这些环节任何一个出问题,整个应用都会崩。
我在从零搭建第一个AI工程时最深的感受是:框架帮你解决的只是“模型对话”这一步,剩下80%的工程问题全靠自己拆。比如你用了LangChain,看似封装了链式调用,但实际用到生产环境时,日志追踪、错误重试、token控制、JSON Schema校验还得自己写。所以,与其被框架牵着鼻子走,不如先想清楚自己的业务到底需要哪些层,再决定哪些用现成库、哪些自己实现。
1.2 从需求倒推技术栈,而不是从技术倒推需求
这是我在“from scratch”过程中最重要的一条原则:先列出业务场景需要的能力,再选技术栈。举个例子,如果你只是做内部知识库问答,那本地部署一个7B到14B的小模型完全够用,不需要上超大参数API;但如果你要做面向C端的复杂推理助手,那必须接商用大模型API,还要设计好异步任务和流式输出。
我自己常用的决策框架是这样的:第一,看交互模式是单轮还是多轮,多轮就必须做会话管理;第二,看是否依赖实时外部信息,依赖就必须预留工具调用接口;第三,看输出是自然语言还是结构化数据,结构化就必须设计输出校验层;第四,看调用频率和预算,高频就得考虑缓存和模型分级。按这四个维度拆完,技术栈根本不是选出来的,是需求逼出来的。
2. 核心细节解析与环境准备
2.1 Python环境与依赖管理:从混乱到有序
AI工程95%的代码生态都在Python这边,所以环境管理是第一道坎。我试过直接用系统Python跑项目,结果第三方库互相踩依赖,torch要的numpy版本和transformers要的冲突,一升级就把另一个库搞崩。后来老老实实用虚拟环境隔离,配合requirements.txt锁版本,才彻底治好了这个病。
这里分享一个我实战验证过的依赖组合,适合从零起步的AI应用项目:
python -m venv .venv source .venv/bin/activate # Windows用 .venv\Scripts\activate pip install --upgrade pip pip install openai # 兼容OpenAI接口的各类模型网关 pip install pydantic # 结构化输出解析 pip install fastapi uvicorn # 服务封装 pip install langgraph # Agent流程编排 pip install httpx # 异步HTTP请求为什么要选openai这个SDK而不是直接用requests手写调用?因为现在几乎所有主流模型服务商都提供兼容OpenAI格式的接口,你用一套SDK就能切换不同模型网关,后面换模型时不用改业务代码,只需要换base_url和api_key。这是我踩过最值的一坑:早期我直接用requests调用各家HTTP接口,每个供应商一套签名逻辑,维护成本直接爆炸。
2.2 模型选型与本地化部署判断
从零做AI工程必然会面临一个选择:用云端API还是本地模型?我的建议是先用云端API把业务逻辑跑通,再根据成本曲线决定要不要引入本地模型。原因很简单:本地部署要处理量化、显存管理、推理优化、并发控制一堆事,这些在项目早期会严重拖慢迭代速度。
真到了需要本地部署的时候,我的经验是优先考虑Qwen系列和Llama系列的量化版本。一个小技巧:用transformers加载4-bit量化模型时,记得配置bnb_4bit_compute_dtype为float16,否则推理速度会明显下降。另外,本地模型的并发吞吐远低于云端API,所以服务端必须做请求队列,不然多个用户同时提问时,显存直接被撑爆。
3. 从Prompt到结构化输出:构建可复用的提示层
3.1 提示词版本管理与模板化设计
Prompt是整个AI系统的灵魂,但很多人把prompt直接写在业务代码里,这是灾难的开始。你想想,prompt改了五六版之后,你根本不知道生产环境跑的是哪版。我自己做了一套很轻量的方案:把提示词全部抽成独立的模板文件(文本格式或专门的提示词配置文件),在代码中按版本号加载。
一个我强烈建议的结构是“基础指令 + 业务上下文 + 输出格式 + 示例”四段式:
【系统角色】 你是一名资深技术文档工程师,擅长将复杂概念转化为清晰易懂的说明文。 【任务目标】 根据用户提供的原始素材,生成一篇结构完整的技术笔记。 【输出要求】 - 使用中文回答 - 按“背景说明 - 核心要点 - 实操建议”三部分组织内容 - 每个部分控制在300字以内 - 不得虚构任何数据 【参考示例】 用户输入:“解释什么是HTTP协议” 输出:“背景说明:HTTP是浏览器和服务器之间通信的协议...”这个四段式的价值在于:角色定义让模型明确立场,任务目标消除歧义,输出要求约束边界,示例则提供格式参考。单独调整任何一个模块都不会破坏整体效果。我最常用的做法是每次修改prompt后,都会用同一组测试用例跑一遍对比效果,效果更好就更新版本号,形成事实上的A/B测试。
3.2 用JSON Mode和Pydantic锁死输出格式
如果AI输出的是聊天内容,格式乱一点没关系;但如果AI输出要直接拿去填数据库、驱动业务流程,那格式稳定性就是生死线。我的方案是强制启用JSON输出模式,再用Pydantic做二次校验。
比如你要让AI从用户输入里提取结构化信息:
from pydantic import BaseModel, Field class MeetingInfo(BaseModel): date: str = Field(description="会议日期,格式YYYY-MM-DD") attendees: list[str] = Field(description="参会人员列表") topic: str = Field(description="会议主题") # 调用大模型时,在system提示词里写明JSON模式 # 然后把模型返回的content用 json.loads 解析后交给MeetingInfo校验这个方案解决了我长期头疼的问题:之前用纯字符串解析AI输出,十个回复里有三个格式对不上,日期格式五花八门,列表偶尔变成逗号分隔,反正什么鬼样子都有。引入Pydantic校验后,把非法输出直接拦截在入口,配合重试机制提示模型返回合法格式,准确率从80%出头提升到99%以上。
3.3 上下文管理的滑动窗口技巧
多轮对话时,tokens会随着历史累积不断膨胀。我见过有人直接把全部聊天记录都塞给模型,结果对话到二十轮时,token超限报错,之前的有效信息还被挤出了上下文窗口。我的做法是维护一个“滑动窗口+关键摘要”的混合策略:
第一层,在短时记忆里保留最近4到6轮完整对话,保证回答的连贯性;第二层,对更早的对话做摘要压缩,只保留用户明确提到的关键约束和已确定的事实;第三层,如果业务场景涉及长期记忆,就额外把用户的历史偏好写进独立的记忆提示区,不占用主上下文。
这个分层策略我实测下来的效果是:单会话可用轮次从十轮出头扩展到四五十轮,回答质量下降也不明显。核心原因在于,模型当前轮次的推理只需要少量上下文就能维持一致性,喂太多无关历史反而会稀释注意力。
4. 构建工程化的Agent编排层
4.1 工具注册机制与Function Calling
从单一模型调用走向Agent,最大的变化是模型不再只会“说话”,而是能“动手”。动手能力靠的是工具调用。我设计Agent时,第一件事就是建立一套统一的工具注册机制:每个工具都有一个名称、一句功能描述、一个JSON Schema参数定义和一个执行函数。模型看了工具描述后,会决定“该调用哪个工具、传什么参数”,然后由程序真正执行。
下面是一个标准工具定义的关键结构:
tools = [ { "type": "function", "function": { "name": "web_search", "description": "搜索互联网获取最新信息", "parameters": { "type": "object", "properties": { "query": {"type": "string", "description": "搜索关键词"} }, "required": ["query"] } } } ]执行时,模型返回的tool_calls信息里会包含工具名称和参数,你的代码拿到后dispatch到对应函数就行。这个机制最大的好处是模型不依赖具体工具的内部逻辑,你随时可以新增、替换、下线工具,不会污染提示词里的指令描述。
4.2 用状态机思维编排Agent流程
Agent编排最怕的就是流程混乱。模型一会儿要搜索,一会儿要计算,还可能中途改主意,代码如果全是if-else控制会写成意大利面条。我的方案是用有向图的方式管理Agent状态转移:每个节点是一个动作(调用模型、调用工具、判断条件),每条边是根据上一步结果决定的下一个节点。LangGraph就是这个思路的一个开源实现,但理解了原理后你也可以自己写状态机。
我自己常用的编排结构是这样的:入口节点接收用户问题,交给模型决策;模型如果要调用工具,就进入工具执行节点,工具结果追加回消息列表,再回到模型节点继续推理;如果没有工具需要调用,就进入最终回答节点,输出结果。每一步都维护一个当前状态对象,记录已经执行过的工具和消息记录,这样不仅流程清晰,出了问题时日志排查也是按图索骥。
4.3 给Agent加记忆:短时对话与长期记忆双通道
记忆是Agent工程里最容易被低估的部分。没有记忆,Agent就是个“金鱼脑”,用户上一秒说自己喜欢简洁回答,下一秒它就开始长篇大论。我的做法是把记忆拆成两个通道来管理:短时通道是当前会话的消息列表,随请求一起发;长期通道是向量数据库里存储的用户偏好、历史事实,每次请求前做一次向量检索,只取相关的几条注入提示词。
这里踩过一个很关键的坑:长期记忆不要全量注入,否则上下文会被历史垃圾填满。我一开始把用户所有的历史记录都塞进prompt,结果模型反而被旧信息干扰,回答质量明显下降。后来改成向量检索Top-3到Top-5最相关记录,效果立刻改善。记住一个原则:记忆的价值在于精准,不在于全面。
5. API服务封装与生产化部署
5.1 FastAPI服务层的关键设计
当你把模型调用和Agent编排跑通后,下一步是封装成对外服务。我选择FastAPI的原因很直接:原生支持异步、自带请求校验和接口文档,在AI应用这种高并发IO场景下非常合适。但封装服务层不只是加路由那么简单,有四个细节必须处理好:
第一,超时设置。LLM推理往往需要几十秒甚至更久,普通HTTP请求默认超时只有几秒,必须把超时时间拉长到分钟级,或者改用异步任务轮询模式。第二,错误码规范。模型限流、服务超时、内容过滤必须返回不同错误码,方便前端做差异化处理。第三,请求ID链路追踪。每个请求生成唯一ID,从入口打到模型调用再到工具执行,全链路日志都带上这个ID。第四,响应流式输出。大模型逐步生成内容时,用StreamingResponse把内容块实时推给前端,用户体验会好非常多。
5.2 缓存与降级:控制成本的两条命脉
生产环境跑AI应用,成本控制是必须直面的问题。同样的问题反复问模型,每次都在烧钱,这是最典型的浪费。我的做法是在服务层加语义缓存:把每次请求的输入做向量化,存进向量库;新请求进来先做相似度检索,如果和已缓存问题的相似度超过阈值,就直接返回缓存答案,不再调用模型API。这个方案在知识库问答场景下效果尤其显著,命中率高的业务里能省下40%到60%的模型调用费用。
降级方案同样重要。云端API供应商偶尔会抖动或限流,这时候服务不能跟着一起挂。我在模型客户端做了两层降级:第一层是重试,遇到限流或网络超时,用指数退避重试三次;第二层是切换备用模型,主模型连续失败时自动切到备用模型网关。这套机制救过我多次,最严重的一次主模型服务故障了半个多小时,我的服务因为有降级链路,用户几乎无感知。
5.3 测试与评估:不能只靠“感觉还行”
AI工程的测试和传统软件工程有很大区别。传统测试断言的是确定性的输入输出,而模型输出是概率性的,同一道问题每次回答都可能不完全一样。所以,我建立了三层测试体系:第一层是单元测试,用模拟响应测试工具调度逻辑和状态流转;第二层是快照回归测试,把一组高质量的“问题-标准答案”对存起来,每次改动prompt或模型参数后跑一遍,人工判断输出质量是否下降;第三层是线上监控,把真实用户反馈和模型输出抽样记录,定期评估效果。
一开始我以为这很复杂,其实核心就是维护好那个“评估集”。我把评估集分成两类:一类是硬性指标集,比如“必须输出合法JSON”“必须包含关键实体”,用代码自动校验;另一类是主观质量集,涉及内容风格的,就人工打分。实测下来,这个体系能拦住大部分回归问题,尤其当你调整prompt时,它能立刻暴露新设置对旧场景的破坏。
6. 常见问题与排查技巧实录
6.1 Token超限与上下文丢失
这是一个极其高频的问题。排查时第一件事不是加tokens,而是看你的历史消息是怎么维护的。我遇到过好几次“之前还好好的,突然开始答非所问”,最后定位全是历史消息越积越多,塞满了上下文窗口,核心指令被挤了出去。
解决办法前面说过的滑动窗口加摘要,这里再补充一个细节:滑动窗口不能只按“最近N轮”粗暴截断,因为用户的某一轮发言可能特别长,挤占了大量token。我的实现里会先给每轮对话估算token数,超出了总预算就按“可牺牲程度”排序裁剪,优先精简的是系统中间输出和工具执行日志,而不是用户的原始指令。
6.2 模型输出幻觉与业务校验冲突
AI编造数据这件事,在生成式场景无所谓,但在生产系统里就是事故。比如你让AI从一段会议纪要里提取参会人名单,它凭“经验”补上了没出现的人名——这就是幻觉。我的排查结论是,完全靠模型自我约束不现实,必须靠外部校验兜底。
具体做法是:给模型能拿到的所有文档都打上确定性来源标记,在提示词里要求输出引用的来源ID,没有引用就不允许断言具体事实;更进一步的,结构化提取任务我会用Pydantic枚举约束字段值范围,比如参会人必须在组织架构表里存在,校验不过就触发追问模型,让它重新提取。这套“外部事实兜底”把幻觉造成的影响压到了最低。
6.3 API限流与并发控制
限流是每个从零做AI工程的人都会撞上的墙。云端API对每分钟请求数、每分钟token数都有硬性限制,你用并发直接怼,超过阈值就报429。我的做法是在客户端内置了限流器:用令牌桶算法控制请求速率,将tokens消费也纳入流量控制。简单来说,就是“攒一批请求再发”,而不是来一个发一个。
这里有个反直觉的经验:并发越高,失败率越高,总吞吐反而下降。与其无限加大并发,不如主动限速。我实测把并发压到API阈值的70%到80%,同时做请求排队,整体吞吐反而稳定提升,因为429重试带来的额外延迟和token浪费被彻底消除了。
6.4 配置管理与安全:API Key不能裸奔
最后一个问题不怎么起眼,但出事就是大事:密钥管理。我见过不少项目把API Key直接写死在代码或.env文件里,一不小心中传到代码仓库就泄漏了。从零开始做工程,第一天就应该把密钥管理纳入设计,而不是最后补。
我的做法是:本地开发用独立的密钥管理工具加载环境变量;生产环境用专门的密钥托管服务(如云厂商的密钥管理服务),应用运行时从托管服务读取,不落盘。同时,密钥需要定期轮换,每次轮换后用日志审计看是否有来源不明的调用记录。这套做法的成本很低,但能避免绝大多数安全事故。
7. 从零到一的成本评估与路线图建议
7.1 一次完整的成本结构拆解
很多人在启动AI项目前最关心的问题是“要花多少钱”。我的经验是把成本拆成四块:模型调用费、基础设施费、研发人力、以及隐性的模型调优迭代成本。模型调用费取决于你的业务量,这个可以用单价乘以预估调用量算出来;基础设施费主要是GPU服务器或云函数费用,本地模型尤其贵在显存;研发人力是最大开销,因为Prompt调优和Agent调试的时间往往远超预期;隐性成本最容易被忽略,比如测试评估集的构建、监控告警的搭建,这些一次性的投入在项目初期就要计划进去。
我自己从零做第一个AI工程时,最大的成本不在API调用,而在反复调整Prompt和Agent流程的时间。用了一个多月才把单个场景的准确率从能用提升到好用。这个时间比预想的长是正常的,关键是分阶段投入:先用小模型或便宜模型把流程跑通,确认业务价值后再加大投入优化效果。
7.2 分阶段的里程碑路线
给准备从零开始的人一个我验证过的路线图:第一阶段(1到2周),搭好环境,用开源模型或云端API先实现一个最简单的单轮问答Demo,目标是打通模型调用的最小闭环;第二阶段(2到4周),引入结构化输出、多轮会话和基础工具调用,把Demo升级成具备业务价值的原型;第三阶段(1到2个月),完善Agent编排、上下文管理、测试评估和服务化封装,让系统具备稳定服务的条件;第四阶段(持续),根据线上数据反馈持续优化Prompt、调整模型、扩展工具、完善降级链路。
这条路线最大的好处是每个阶段都有可验证的产出,不会出现在黑盒里闷头开发几个月的险况。每到一个阶段就回头检查一次,看看是不是有更简单的实现方式可以替代,很多时候你会发现自己做的复杂编排,用两条if-else就能解决。
7.3 开源模型与云端API的切换时机
最后再聊一个很现实的问题:到底什么时候该换更贵的模型,什么时候该降级到便宜的方案?我判断的标准很简单:看错误率。如果当前模型的错误主要不是推理能力不足导致的,而是工程链路问题,那换更贵的模型解决不了,先修工程;如果是复杂推理场景下模型逻辑能力确实不够,答案明显逻辑不通时,才考虑升级模型。
反过来也一样,如果你的业务场景用7B模型就能覆盖90%的需求,根本没必要上几百亿参数的API。早期我用一个很大的模型做信息提取,效果固然好,但成本是本地小模型的三倍以上,后来换了小模型加结构化约束,效果差距很小,成本却大幅下降。所以,模型选型永远是一个动态权衡的过程,不是一步定终身的。
我一直觉得,AI工程“from scratch”的价值不在于什么都自己造轮子,而在于你在亲手搭建的过程中,把每个模块为什么这么设计、每个参数为什么这样配置、每个环节哪里容易翻车都摸透了。这种理解深度,是直接套用别人框架和代码永远得不到的。哪怕最后你因为业务压力换成了成熟框架,你再去看它的源码和文档,视角也完全不一样了——你不再是一个只会调API的使用者,而是一个能判断框架设计好坏、知道自己要什么的工程师。这种能力,才是从零开始最大的收获。