1. 从零搭建AI工程能力为什么我劝你别一上来就调包这两年AI应用开发的门槛肉眼可见地降低了随便拉个框架、调个API就能跑出一个能对话的Demo。但我带过的新人里十个有八个卡在同一个地方Demo跑通了一上真实业务就崩。要么是响应慢得离谱要么是成本失控要么是数据一多就各种诡异报错。问题的根子不在模型本身而在于大多数人跳过了“AI工程”这一层直接从“调包”开始缺少对整条链路的掌控力。ai-engineering-from-scratch这个方向说白了就是把这层缺失的能力补回来。它不是教你从头训练一个大模型那是另一回事它关注的是当你手里有一个可用的模型之后怎么把它变成一个稳定、可维护、成本可控、能扛住真实流量的工程系统。这里面涉及的东西很杂数据管道、推理服务、缓存策略、评测体系、监控告警、成本核算每一项单独拎出来都不算新鲜但组合在一起就是AI应用能不能落地的分水岭。我写这篇东西是想把过去几年在几个真实项目里踩过的坑、验证过的方案按一个从零开始的顺序梳理出来。适合两类人看一类是刚转进AI方向的工程师想搞清楚除了调API之外还需要补哪些课另一类是有后端或数据背景、想系统性地把AI能力接进自己系统里的开发者。我不会假设你懂深度学习但会假设你懂基本的编程和系统概念。整篇内容偏实操能直接抄作业的地方我会尽量给到具体参数和配置。2. 整体思路拆解AI工程到底在解决什么问题2.1 先想清楚模型只是系统里的一个零件很多人做AI项目的思维是“以模型为中心”先选一个最强的模型然后围绕它搭东西。这个思路在原型阶段没问题但到了工程阶段会出大问题。因为模型是会换的今天用这个明天可能因为成本、延迟或者效果换另一个。如果你的系统跟某个特定模型深度耦合换一次就要伤筋动骨。正确的思路是“以数据流为中心”。把整个系统看成一条从用户输入到最终输出的流水线模型只是流水线上的一个加工环节。这条流水线上还有输入预处理、上下文组装、结果后处理、缓存、日志、评测等一堆环节。每个环节都应该有清晰的输入输出契约模型换了只要契约不变其他环节不用动。我自己的做法是定义一个内部的“推理接口层”所有对模型的调用都走这一层。这一层负责把业务侧的请求翻译成具体模型能理解的格式再把模型返回的结果翻译回业务侧能用的结构。这样上层业务完全不知道底层用的是哪个模型切换成本被压到最低。这个接口层的设计是整个AI工程的地基后面所有的缓存、限流、降级、监控都挂在这一层上。2.2 分层设计把关注点拆开一个能扛事的AI系统我一般会拆成这么几层从下往上说。最底下是模型接入层负责跟具体的模型服务打交道处理鉴权、重试、超时、格式转换这些脏活。这一层要足够薄薄到换模型只需要改一个配置文件。往上是能力编排层这一层是AI工程的核心。它负责把一次复杂的AI任务拆成多个步骤比如先检索、再改写、再生成、最后校验。很多业务场景不是一次模型调用能搞定的需要多次调用串起来还要处理中间结果的传递和异常。这一层的设计直接决定了系统的上限。再往上是业务逻辑层处理具体的业务规则比如权限校验、计费、审计。这一层尽量不碰模型相关的细节保持纯粹。最上面是接口层对外暴露API或者消息队列处理协议转换、限流、鉴权。这么分层的好处是每一层可以独立演进。模型接入层可以随时换供应商能力编排层可以调整流程业务逻辑层可以改规则互不影响。我见过太多项目把所有逻辑揉在一个大函数里改一处崩三处维护成本高得吓人。2.3 成本与延迟两个绕不开的硬约束做AI工程你可以不懂算法但必须对成本和延迟有肌肉记忆。这两个指标是真实业务里最硬的约束比效果还硬。效果差一点用户可能忍但慢得离谱或者账单爆炸项目直接就被砍了。成本这块核心是搞清楚钱花在哪。一次模型调用的成本大致由输入token数、输出token数、调用次数决定。很多人只盯着单次调用的单价忽略了调用次数这个乘数。一个设计不好的流程可能一次用户请求触发五六次模型调用成本直接翻几倍。所以优化成本的第一刀往往不是换便宜模型而是砍掉不必要的调用。延迟这块要区分首token延迟和总延迟。流式输出场景下用户感知的是首token延迟这个指标对交互体验影响最大。非流式场景下总延迟才是关键。优化延迟的手段包括缓存、并行调用、预计算、模型降级等。我后面会专门讲缓存这是性价比最高的优化手段没有之一。3. 核心细节解析几个必须吃透的关键环节3.1 数据管道别让脏数据毁掉整个系统AI系统对数据质量的敏感度远高于传统系统。传统系统里一条脏数据可能只是显示错一个字段AI系统里一条脏数据可能让模型输出完全跑偏而且这种跑偏很难排查因为模型是个黑盒。我踩过最惨的一次坑是上游数据里混进了一批带特殊控制字符的文本模型处理之后输出了一堆乱码排查了两天才定位到是数据清洗环节漏了。从那以后我养成了一个习惯所有进入模型的数据必须经过一道强制的清洗和校验。清洗这块至少要处理这几类问题。第一类是编码问题统一转成UTF-8把非法字符替换掉。第二类是长度问题超长的文本要截断或者分段但截断不能简单按字符数切要按语义边界切否则会把一句话切两半。第三类是格式问题比如多余的空格、换行、HTML标签这些会浪费token还干扰模型。第四类是敏感内容过滤这个不用多说合规底线。校验这块我一般会定义一套schema对每条数据做结构校验。字段缺失、类型不对、枚举值越界全部拦在管道入口。宁可让请求失败也不要让脏数据流进模型。失败至少能报警能重试脏数据流进去产生的错误输出可能悄无声息地污染下游。提示数据清洗不要写成一次性脚本要封装成可复用的组件每个环节有独立的单元测试。清洗规则会随着业务变化不断调整没有测试兜底改一次崩一次。3.2 上下文管理token是有限的怎么花是门学问大模型的上下文窗口是有限的而且不是免费的输入越长成本越高、延迟越大。所以上下文管理本质上是一个资源分配问题在有限的token预算里塞进最有价值的信息。我的做法是把上下文分成几个优先级不同的区块。最高优先级是系统指令和当前用户输入这部分必须完整保留。次高优先级是最近几轮对话保留最近N轮N根据场景定一般3到5轮够用。再次是检索到的相关知识这部分按相关度排序从高到低塞塞到预算用完为止。最低优先级是历史摘要把更早的对话压缩成一段摘要占用少量token。这里有个细节很多人忽略不同区块之间要有明确的分隔标记让模型能区分哪部分是指令、哪部分是知识、哪部分是历史。分隔标记本身也占token但这点开销值得花能显著提升模型对上下文的理解准确度。预算分配上我一般按这个比例来系统指令占10%当前输入占20%最近对话占30%检索知识占30%历史摘要占10%。这个比例不是死的要根据场景调。比如客服场景最近对话权重高一些知识问答场景检索知识权重高一些。关键是这个预算要显式管理不能任由上下文无限增长。3.3 缓存策略省钱的终极武器缓存是AI工程里投入产出比最高的优化手段没有之一。一个设计良好的缓存层能把成本和延迟同时砍掉一大半。但缓存不是简单加个Redis就完事AI场景下的缓存有几个特殊之处。第一缓存key的设计很关键。不能只用用户输入做key因为同样的输入在不同上下文下结果可能不同。我的做法是把影响输出的所有因素都拼进key输入文本、上下文摘要、模型版本、温度参数、系统指令版本。任何一个变了key就变了缓存自然失效。这样虽然命中率会低一些但保证了正确性。第二要区分精确缓存和语义缓存。精确缓存是输入完全一样才命中实现简单正确性高。语义缓存是输入意思相近就命中需要算向量相似度命中率高但有误判风险。我的经验是对正确性要求极高的场景只用精确缓存对体验要求高、容错空间大的场景可以上语义缓存但相似度阈值要调高一般0.95以上才认为是同一类请求。第三缓存要有分层。最热的数据放内存次热的放Redis冷门的可以落盘。TTL也要分层设置变化快的数据TTL短稳定的数据TTL长。比如模型版本相关的缓存可以设长一点用户会话相关的缓存设短一点。实测下来一个中等规模的问答系统加上合理的缓存层之后模型调用量能降到原来的30%到40%延迟P99能降一半以上。这个收益比换更快的模型划算多了。3.4 评测体系没有度量就没有优化AI系统最麻烦的地方是效果很难量化。传统系统跑个单元测试就知道对不对AI系统输出的是自然语言对错往往没有绝对标准。没有评测体系你根本不知道改了一个prompt之后效果是变好了还是变差了只能靠感觉这是很危险的。我一般会搭三层评测。最底层是单元评测针对具体的功能点准备一批固定的输入和期望输出每次改动跑一遍看通过率。这层评测要快几分钟能跑完适合开发阶段频繁跑。中间层是回归评测用一批有代表性的真实数据覆盖主要场景每次发版前跑看整体指标有没有退化。最上层是线上评测通过A/B测试或者灰度发布看真实用户的反馈指标比如点赞率、追问率、人工介入率。评测数据的准备是个体力活但值得投入。我的做法是从线上日志里采样人工标注一批高质量的标准答案然后定期补充新的case。评测集要覆盖正常场景、边界场景和已知的失败场景。每次线上发现bad case就把它加进评测集防止同样的错误再犯。指标这块自动评测可以用BLEU、ROUGE这类文本相似度指标但它们跟人类判断的相关性有限只能做参考。更靠谱的是用模型来评测模型让一个强模型给输出打分但要注意这个评测模型本身也可能有偏见。最终还是要靠人工抽检和线上真实反馈来校准。4. 实操过程从零搭一个最小可用的AI工程骨架4.1 环境准备与依赖选型假设你现在要从零开始搭一个AI应用的后端我把我常用的技术栈列一下都是经过生产验证的。语言用Python生态最全招人也好招。Web框架用FastAPI异步支持好写起来简洁自动生成文档。任务队列用Celery或者RQ处理异步的模型调用。缓存用Redis这个基本是标配。数据库用PostgreSQL存业务数据和日志。向量检索如果需求不复杂先用pgvector跟PostgreSQL集成在一起省得再维护一套向量数据库。监控用Prometheus加Grafana日志用ELK或者Loki。模型接入这块我建议抽象一个统一的客户端不要直接在业务代码里调各家SDK。这个客户端至少要有这几个能力统一的请求响应格式、自动重试、超时控制、并发限制、用量统计。重试策略要注意不是所有错误都值得重试网络超时和限流可以重试参数错误重试多少次都没用。重试要加退避避免雪崩。# 一个简化的模型客户端接口示例 class ModelClient: def __init__(self, config): self.config config self.semaphore asyncio.Semaphore(config.max_concurrency) async def complete(self, prompt, **kwargs): async with self.semaphore: for attempt in range(self.config.max_retries): try: resp await self._call(prompt, **kwargs) self._record_usage(resp) return resp except RetryableError: await asyncio.sleep(2 ** attempt) raise MaxRetriesExceeded()这个客户端要记录每次调用的token用量和延迟这些数据是后续成本分析和性能优化的基础。没有这些数据你就是在盲人摸象。4.2 搭建推理服务的最小闭环先跑通一个最小的闭环接收请求、组装上下文、调用模型、返回结果。这个闭环不需要多完美但要能跑通能测。请求进来之后第一步是参数校验把不合法的请求挡在外面。第二步是查缓存命中就直接返回。第三步是组装上下文按前面说的优先级策略拼prompt。第四步是调用模型这里要注意设置合理的超时一般非流式场景设30秒流式场景首token超时设10秒。第五步是后处理把模型输出清洗一下去掉多余的空格和标记。第六步是写缓存、记日志、返回结果。这个流程里每一步都要有异常处理。模型调用超时了怎么办返回一个友好的降级回复同时记录日志。缓存挂了怎么办直接跳过缓存走模型不能因为缓存故障导致整个服务不可用。这些降级路径要在设计阶段就想清楚不能等线上出事了再补。流式输出这块单独说一下。流式能显著提升用户感知的响应速度但实现上比非流式复杂。要注意几个点流式响应的错误处理更麻烦因为响应已经开始发送了没法再返回错误码只能在流里发一个错误标记。流式响应的缓存也更麻烦一般只缓存完整结果流式的时候边收边拼拼完了再写缓存。4.3 加上缓存和限流最小闭环跑通之后马上加缓存和限流。这两个是生产环境的刚需越早加越好。缓存这块前面讲了key的设计。实现上用Redis设置合理的TTL。要注意缓存穿透问题对于肯定不存在的key可以缓存一个空值避免每次都打到模型。缓存雪崩也要防TTL加随机抖动避免同一时间大批key同时失效。限流这块分两个维度。一个是用户维度的限流防止单个用户刷爆系统。一个是全局维度的限流保护下游的模型服务。用户维度可以用令牌桶算法每个用户一个桶按配置的速率放令牌。全局维度可以用滑动窗口统计最近一段时间的调用量超过阈值就拒绝或者排队。限流的拒绝策略要讲究。直接返回429会让用户困惑最好是返回一个友好的提示告诉用户稍后再试。如果是排队要告诉用户大概要等多久。这些细节影响体验但很多项目忽略了。4.4 接入监控和告警服务上线之后你必须能知道它现在是什么状态。监控指标至少要覆盖这几个维度请求量、成功率、延迟分布、token用量、缓存命中率、错误分类。延迟分布不要只看平均值平均值会骗人。要看P50、P95、P99P99高说明有长尾请求可能是某些复杂请求拖慢了整体。token用量要按用户、按接口、按模型分别统计这样才能定位成本大头在哪。缓存命中率低了要查原因是key设计有问题还是数据变化太快。告警要设置合理的阈值太敏感会天天误报太迟钝会漏掉真问题。我的经验是成功率低于95%告警P99延迟超过设定阈值告警token用量突增告警。告警要能定位到具体原因不能只报一个“服务异常”那样排查起来很痛苦。5. 常见问题与排查技巧实录5.1 模型输出不稳定怎么排查模型输出不稳定是最常见的问题同样的输入两次结果不一样。先确认温度参数温度大于0本身就有随机性要稳定就设成0。如果温度已经是0还不稳定那可能是模型服务本身的问题或者上下文里有随机因素比如检索结果每次不一样。排查顺序我一般是这样的先固定所有输入包括上下文和参数看是否稳定。如果固定输入还不稳定那就是模型侧的问题联系供应商或者换模型。如果固定输入稳定了那就逐个放开变量看是哪个变量引入的不稳定。这个过程要耐心但能定位到根因。5.2 成本突然飙升的几种可能成本飙升一般有几个原因。最常见的是缓存失效可能是key设计变了或者缓存服务重启了导致大量请求穿透到模型。其次是某个用户或者某个接口的调用量突增可能是被刷了或者有bug导致循环调用。再次是上下文变长了可能是检索环节返回的知识变多了或者历史对话没做截断。排查的时候先看监控按用户、按接口、按模型分别看用量曲线定位到是哪个维度涨了。然后顺着这个维度往下查看具体是哪些请求导致的。我遇到过一次是某个接口的缓存key里带了一个每次都变的时间戳导致缓存永远不命中改掉就好了。5.3 延迟高的常见原因延迟高要区分是首token慢还是总时长长。首token慢一般是模型服务的问题或者上下文太长导致模型处理慢。总时长长可能是输出太长或者中间有多次串行调用。优化手段上首token慢可以试试减少上下文长度或者换一个首token更快的模型。总时长长可以试试并行化中间的调用或者对输出做长度限制。流式输出能改善用户感知但不改变总时长这个要分清楚。5.4 常见问题速查表问题现象可能原因排查方向解决手段输出不稳定温度参数、上下文随机固定输入测试温度设0、固定检索结果成本飙升缓存失效、调用量突增看用量监控修缓存key、加限流首token慢上下文过长、模型慢看上下文长度精简上下文、换模型总延迟高输出长、串行调用多看调用链限制输出、并行化缓存命中率低key设计问题、数据变化快看key构成优化key、调TTL脏数据导致输出异常清洗不彻底看输入数据加强清洗校验提示这张表建议打印出来贴在工位上出问题的时候按表排查能省很多时间。我自己的经验是八成的问题都能在这张表里找到对应。6. 一些踩坑之后的个人体会做AI工程这几年最大的体会是这活儿的难点从来不在模型本身而在模型之外的那些“脏活累活”。数据清洗、缓存设计、监控告警、成本控制这些听起来不酷但恰恰是决定项目生死的东西。我见过太多团队把精力全花在调prompt和选模型上结果系统一上量就各种问题最后项目黄了复盘的时候才发现是工程基础没打好。另一个体会是不要追求一步到位。AI系统的迭代速度很快今天的最优解明天可能就过时了。所以架构要留出足够的灵活性能快速试错、快速调整。我一般会先把最小闭环跑通然后根据实际遇到的问题逐步加东西而不是一开始就设计一个大而全的架构。大而全的架构往往在真正需要灵活的时候反而成了束缚。最后说一个具体的技巧把每次线上bad case都记录下来定期复盘。这些bad case是最宝贵的财富比任何评测集都真实。我维护了一个bad case库每次出问题就加一条标注原因和解决方案。时间长了这个库就成了团队的避坑指南新人来了先看这个能少走很多弯路。这个习惯看起来笨但长期收益巨大。