1. 从零搭建AI工程体系,为什么我劝你别一上来就搞模型
"ai-engineering-from-scratch"这个标题,乍一看像是又一份教你从零训练大模型的教程。但我做了十多年一线工程,见过太多团队在这条路上摔跟头,所以先把话说在前头:从零做AI工程,最不该先碰的就是模型本身。你真正要搭的是一整套让AI能力稳定跑起来、能被业务调用、能持续迭代的工程底座,模型只是其中最容易被替换的一个零件。
我见过一个典型场景:某团队花了三个月调模型,指标在测试集上刷得很漂亮,结果一上线就崩——请求一多就超时,日志里全是显存溢出,业务方要个简单的意图分类接口,他们却给不出一个稳定的响应时间。问题不在模型,在于他们跳过了工程化这一步。所谓AI工程,本质是把"实验室里能跑通"变成"生产环境里天天跑不挂",这中间隔着的不是算法,是缓存、队列、限流、监控、版本管理这一整套东西。
这篇内容适合三类人:一是刚转行做AI应用、只会调API不会搭系统的开发者;二是带团队做AI落地、被"demo很惊艳、上线很拉胯"折磨过的技术负责人;三是想理解AI工程全貌、不想被各种框架名词绕晕的产品和运维同学。我会按真实项目推进的顺序,把从零搭建AI工程体系的思路、选型、实操和踩坑经验完整讲一遍,代码和配置都能直接抄。
核心关键词就一个:ai-engineering-from-scratch,也就是从零开始的AI工程。但"从零"不等于"从模型开始",而是从需求拆解和系统边界开始。下面我按四个大块展开:整体设计思路、核心细节与实操要点、完整落地流程、以及问题排查实录。每一块都是我实际项目里验证过的东西,不是纸上谈兵。
2. 整体设计与思路拆解:先画边界,再谈技术
2.1 为什么"从零"要先定义系统边界
很多人一听到"从零做AI工程",脑子里立刻浮现的是选框架、配环境、拉数据、训模型这条线。但真实项目里,第一步永远是搞清楚这个AI系统要解决什么问题、输入输出是什么、谁来用、用多频繁。这一步没做清楚,后面所有技术选型都是空中楼阁。
我习惯用一个"三问法"来定边界:第一问,这个AI能力是同步调用还是异步任务?比如实时对话是同步的,用户发一句要立刻回;而批量文档摘要、离线数据标注就是异步的,可以排队慢慢跑。第二问,延迟容忍度是多少?是要求200毫秒内返回,还是可以等5秒?这直接决定了你要不要上缓存、要不要做模型量化、要不要用流式输出。第三问,失败可接受度是什么?是绝对不能出错,还是可以降级返回兜底结果?这三问定下来,系统的骨架就清晰了一半。
举个我实际做过的例子。有个需求是给客服系统加一个"工单自动分类"能力,把用户提交的文字分到十几个类别里。表面看是个文本分类任务,但三问之后发现:它是异步的(工单提交后几秒内分类即可),延迟容忍度高(5秒内都行),失败可接受(分错了人工可以改)。这三个答案直接让我放弃了实时推理服务,改用消息队列+批处理的方案,成本降了七成,稳定性还更好。如果一上来就搞个实时API,纯属给自己找麻烦。
所以"从零"的第一层含义,是从业务边界从零推导技术方案,而不是从某个热门框架从零搭环境。这个顺序错了,后面全是返工。
2.2 技术选型的取舍逻辑:够用比先进重要
定完边界,才轮到选型。AI工程涉及的技术栈很宽:推理框架、向量数据库、编排工具、监控系统、部署方式……新手最容易犯的错是追新,哪个火用哪个,结果拼出一套自己都维护不了的系统。
我的选型原则就一条:在当前团队能力和业务规模下,选维护成本最低的方案。具体拆成几个维度看:
| 维度 | 保守选择 | 激进选择 | 我的建议 |
|---|---|---|---|
| 推理框架 | 直接用官方推理接口 | 自研推理引擎 | 除非有极致性能需求,否则用成熟方案 |
| 服务编排 | 单体服务+简单队列 | 微服务+服务网格 | 团队小于10人,单体优先 |
| 向量检索 | 单机向量库 | 分布式向量集群 | 数据量低于百万级,单机足够 |
| 模型部署 | 托管API | 自建GPU集群 | 先托管跑通业务,再考虑自建 |
这张表不是绝对的,但方向很明确:从零搭建时,每一层都选"能跑通且好维护"的方案,把复杂度留到真正需要的时候再加。我见过太多团队在日请求量还不到一万的时候,就上了分布式向量集群和微服务架构,结果运维成本高得吓人,业务却没跑起来。
还有一个常被忽略的点:模型和工程的解耦。从零搭建时,一定要把模型调用封装成一个独立的接口层,业务代码只依赖这个接口,不直接依赖某个具体模型。这样将来换模型、加模型、做A/B测试,都不用动业务代码。这个抽象层看起来简单,但它是整个AI工程体系能不能持续演进的关键。
2.3 分层架构:把系统切成能独立演进的块
边界和选型定了,接下来是架构分层。我推荐的分层是这样的,从上到下:
- 接入层:负责请求接收、鉴权、限流、路由。这一层不碰任何AI逻辑,只做流量治理。
- 编排层:负责把一次业务请求拆解成若干AI调用步骤,比如"先检索、再生成、后校验"。这一层是AI工程的核心大脑。
- 能力层:封装具体的AI能力,比如文本生成、向量化、分类、抽取。每个能力是一个独立模块,可单独替换。
- 模型层:真正跑模型的地方,可以是托管API,也可以是自建服务。
- 数据层:存向量、存缓存、存日志、存版本。这一层决定了系统能不能持续迭代。
这么分的好处是每一层可以独立演进。比如你想换个更好的向量模型,只动能力层;想加个新的业务编排逻辑,只动编排层;想扩容,只动接入层。层与层之间通过明确定义的接口通信,互不干扰。
我特别想强调编排层的价值。很多从零搭建的项目根本没有这一层,业务代码里直接写"调模型A、拿结果、再调模型B",逻辑散落各处。一旦业务变复杂,比如要加个"结果不满足条件就重试"的逻辑,就得改一堆地方。有了编排层,这些逻辑集中在一处,改起来清爽得多。编排层可以用代码写,也可以用现成的编排框架,但核心是把流程控制和能力调用分开。
3. 核心细节解析与实操要点:每个环节的坑在哪
3.1 环境与依赖管理:别让"在我机器上能跑"重演
从零搭建AI工程,环境管理是第一个大坑。AI项目依赖多、版本敏感,稍不注意就是"在我机器上能跑,到你那就报错"。我的做法是从一开始就容器化,而且不是简单写个Dockerfile就完事,要遵循几条硬规矩。
第一条,基础镜像固定版本。不要用latest标签,要用带具体版本号的镜像,比如python:3.11.6-slim。latest今天和明天可能就不是一个东西,生产环境用它是灾难。
第二条,依赖锁定。Python项目用requirements.txt时,一定要把每个包的精确版本写死,最好用pip freeze生成。更推荐用poetry或pip-tools做依赖解析和锁定,生成lock文件。我踩过的坑是:本地开发时某个包是2.1版本,部署时自动装了2.3,结果API变了,服务起不来。
第三条,分层构建镜像。AI项目镜像动辄几个G,如果每次改代码都重新装依赖,构建一次要十几分钟。正确做法是把依赖安装和代码拷贝分成两层,依赖不变时直接命中缓存。一个典型的Dockerfile结构是这样的:
FROM python:3.11.6-slim WORKDIR /app # 先拷贝依赖文件并安装,这层能被缓存 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 再拷贝代码,代码改动不影响依赖层 COPY . . CMD ["python", "-m", "app.main"]这个顺序很关键。很多人习惯先COPY . .再装依赖,结果改一行代码就要重装所有依赖,构建时间翻好几倍。
第四条,区分开发和生产依赖。开发时用的调试工具、测试框架不要装进生产镜像,能显著减小体积、降低攻击面。用requirements-dev.txt单独管理开发依赖。
注意:AI项目经常需要装CUDA相关的库,这类库体积巨大且和驱动版本强绑定。如果用的是托管推理服务,生产镜像里根本不需要装这些,别把训练环境的依赖一股脑塞进部署镜像。
3.2 模型调用的封装:一个接口层省下无数返工
前面说过模型和工程要解耦,具体怎么落地?核心就是定义一个统一的模型调用接口,所有业务代码都通过这个接口调模型,不直接碰任何具体模型的SDK。
这个接口至少要包含几个要素:输入输出的数据结构、超时设置、重试策略、错误类型定义。我用Python举个例子,接口大概长这样:
from abc import ABC, abstractmethod from dataclasses import dataclass @dataclass class ModelRequest: prompt: str max_tokens: int = 512 temperature: float = 0.7 @dataclass class ModelResponse: text: str model_name: str latency_ms: int class ModelClient(ABC): @abstractmethod def generate(self, req: ModelRequest) -> ModelResponse: ...然后针对不同模型写不同的实现类,比如OpenAIClient、LocalModelClient。业务代码只依赖ModelClient这个抽象,具体用哪个实现,通过配置注入。
这么做的好处,我在一个项目里体会特别深。当时业务用的是某托管模型,后来因为成本和数据合规要求,要换成自建模型。因为有了这层抽象,我们只写了一个新的实现类,改了一行配置,业务代码一行没动,半天就切换完了。如果没有这层抽象,估计得改几十处调用点,还得重新测试。
封装时还有几个细节要注意。超时和重试必须在这一层统一处理,不要散落在业务代码里。超时时间要根据业务延迟容忍度来定,重试要区分可重试错误(如网络超时)和不可重试错误(如参数错误)。错误类型要定义清楚,业务层才能针对性地做降级。日志埋点也要在这一层做,记录每次调用的模型、耗时、token消耗,这些数据是后续优化和成本核算的基础。
3.3 缓存与限流:让系统扛住真实流量
Demo和生产最大的区别就是流量。Demo只有你一个人点,生产可能瞬间来几千个请求。从零搭建时,缓存和限流是两个必须提前设计的机制,不能等出问题了再加。
先说缓存。AI调用又慢又贵,能缓存的绝不重复调。缓存分几层:结果缓存,相同输入直接返回上次结果;向量缓存,文本向量化结果缓存起来,避免重复计算;会话缓存,多轮对话的上下文缓存,减少重复传输。缓存的关键是键的设计,要把影响输出的所有参数都纳入键,比如prompt、模型名、temperature,少一个都可能导致返回错误结果。
我用Redis做结果缓存的典型逻辑是这样的:
import hashlib import json def cache_key(req: ModelRequest) -> str: raw = json.dumps({ "prompt": req.prompt, "max_tokens": req.max_tokens, "temperature": req.temperature, }, sort_keys=True) return "ai:cache:" + hashlib.sha256(raw.encode()).hexdigest()注意sort_keys=True,保证字典序列化顺序一致,否则同样的内容可能生成不同的键。这个细节不注意,缓存命中率会莫名其妙地低。
再说限流。限流的目的有两个:保护后端模型服务不被打挂,以及控制成本。限流可以按用户、按接口、按全局三个维度做。我一般用令牌桶算法,允许一定的突发流量,但长期速率受控。限流阈值怎么定?看后端模型服务的承载能力和你的成本预算,取两者中较小的那个。
提示:限流被触发时,不要直接返回冷冰冰的错误。可以返回排队提示,或者降级到更轻量的模型。用户体验比一个500错误好得多。
缓存和限流都要考虑失效和降级。缓存服务挂了怎么办?限流组件异常怎么办?我的原则是:缓存挂了就直连模型(牺牲性能保可用),限流组件挂了就放行(宁可超载不可全挂)。这些降级逻辑要提前写好,别等出事才想。
3.4 可观测性:没有监控的AI系统等于裸奔
AI系统比传统系统更难排查,因为它的输出是不确定的,同样的输入可能得到不同结果。所以可观测性必须从第一天就建起来,不能等出了问题再补。
可观测性包含三块:日志、指标、追踪。日志要记录每次AI调用的完整上下文,包括输入、输出、模型、耗时、token数、是否命中缓存。指标要暴露关键数据,比如QPS、延迟分布、错误率、缓存命中率、token消耗速率。追踪要能把一次业务请求涉及的所有AI调用串起来,看清每一步的耗时。
我特别推荐在日志里记录输入输出的哈希值而不是原文,既方便排查又避免敏感数据泄露。同时记录trace_id,把一次请求的所有日志串起来。排查问题时,拿到一个trace_id就能还原整个调用链。
指标方面,我习惯用Prometheus的格式暴露,几个必看的指标:
| 指标名 | 含义 | 告警阈值建议 |
|---|---|---|
| ai_request_duration_seconds | 调用延迟分布 | P99超过业务容忍度 |
| ai_request_errors_total | 错误计数 | 错误率超过1% |
| ai_cache_hit_ratio | 缓存命中率 | 低于预期值 |
| ai_token_usage_total | token消耗 | 突增或超预算 |
这些指标不只是为了告警,更是优化的依据。比如发现缓存命中率低,就去分析键的设计;发现延迟高,就去看是模型慢还是网络慢。没有数据,优化就是瞎猜。
4. 实操过程与核心环节实现:从空目录到能跑的系统
4.1 项目骨架搭建:目录结构决定维护成本
从零开始,第一步是搭目录结构。别小看这个,目录结构直接决定了后期维护的难易。我推荐的结构是这样的:
ai-engineering/ ├── app/ │ ├── api/ # 接入层:路由、鉴权、限流 │ ├── orchestration/ # 编排层:业务流程编排 │ ├── capabilities/ # 能力层:各类AI能力封装 │ ├── models/ # 模型层:模型客户端实现 │ └── common/ # 公共:配置、日志、工具 ├── configs/ # 配置文件 ├── tests/ # 测试 ├── scripts/ # 运维脚本 ├── Dockerfile └── requirements.txt这个结构对应前面说的分层架构,每一层一个目录,职责清晰。新人接手时,看目录就知道系统怎么组织的。
配置管理要特别注意。不要把配置写死在代码里,也不要把敏感配置提交到代码库。我用的是环境变量加配置文件的方式:非敏感的默认配置放配置文件,敏感信息和环境相关的配置走环境变量。配置加载用一个统一的模块,启动时校验必填项,缺了就报错退出,别等运行到一半才发现配置缺失。
4.2 核心编排逻辑实现:把流程控制抽出来
编排层是整个系统的大脑,它决定了一次业务请求怎么被处理。我以一个"智能问答"场景为例,讲编排逻辑怎么写。
这个场景的流程是:用户提问 → 检索相关知识 → 拼装prompt → 调模型生成 → 校验结果 → 返回。用代码表达,编排逻辑大概是这样:
class QaOrchestrator: def __init__(self, retriever, model_client, validator): self.retriever = retriever self.model_client = model_client self.validator = validator def handle(self, question: str) -> str: # 第一步:检索 docs = self.retriever.search(question, top_k=3) # 第二步:拼装prompt prompt = self._build_prompt(question, docs) # 第三步:调模型 resp = self.model_client.generate(ModelRequest(prompt=prompt)) # 第四步:校验 if not self.validator.check(resp.text): resp = self.model_client.generate( ModelRequest(prompt=self._build_retry_prompt(question)) ) return resp.text这段代码的关键在于每一步都是独立的、可替换的。检索器可以换,模型客户端可以换,校验器可以换,编排逻辑本身不用动。这就是分层解耦的价值。
编排逻辑里最容易出问题的是异常处理。检索失败怎么办?模型超时怎么办?校验不通过怎么办?我的做法是给每一步都定义明确的失败策略:检索失败就跳过检索直接生成(降级),模型超时就重试一次再失败就返回兜底话术,校验不通过就重试一次。这些策略要写进编排逻辑,而不是散落在各处。
还有一个细节:编排逻辑要可测试。因为每一步都依赖抽象接口,测试时可以注入mock实现,不用真的调模型就能测编排逻辑。这一点在持续迭代时特别重要,改编排逻辑不用每次都跑真实模型,测试速度快很多。
4.3 部署与灰度:让新版本安全上线
系统能跑通之后,下一步是部署。AI系统的部署有个特殊难点:模型或prompt的改动会直接影响输出质量,而这种影响很难用传统测试覆盖。所以灰度发布是必须的。
我的部署流程是这样的:先在测试环境跑通,然后小流量灰度(比如5%的流量),观察关键指标(延迟、错误率、以及业务侧的质量反馈),没问题再逐步放量。灰度期间要能随时回滚,所以版本管理要做好,每个版本可追溯、可回退。
部署方式上,小团队用容器加编排工具就够了,不用一上来就搞复杂的发布系统。关键是部署脚本要自动化,从构建镜像到启动服务一条命令搞定,减少人为失误。我习惯写一个deploy.sh,把构建、推送、更新、健康检查串起来。
健康检查要专门设计。AI服务的健康检查不能只检查端口通不通,还要检查模型是否可用。可以设计一个轻量的探针请求,定期调一次模型,确认能正常返回。探针失败就触发告警,甚至自动重启。
注意:灰度期间要特别关注成本指标。新版本可能因为prompt变长、重试变多导致token消耗激增,这种问题在功能测试里发现不了,只有看成本数据才能发现。
4.4 数据回流与迭代:让系统越用越好
AI工程和传统工程最大的不同,是它需要持续迭代。模型会更新,prompt要优化,业务需求会变。所以从零搭建时就要设计好数据回流机制,把线上产生的数据收集起来,用于后续优化。
数据回流包括几类:输入输出对,用于分析模型表现;用户反馈,比如点赞点踩,是最直接的质量信号;失败案例,特别是那些触发了降级或重试的请求,是优化的重点。这些数据要脱敏后存储,并且和trace_id关联,方便追溯。
有了数据,迭代就有了方向。比如发现某类问题的回答质量差,就去优化对应的prompt或补充知识库;发现某类请求特别多,就考虑做专门的缓存或优化。这个闭环建起来,系统才能越用越好,而不是上线即巅峰。
我特别建议做一个简单的评估集。从线上数据里挑一批有代表性的样本,人工标注期望输出,每次改动后跑一遍评估集,看指标有没有退步。这个评估集不用很大,几百条就够,但能有效防止"改了一个问题引入三个新问题"。
5. 常见问题与排查技巧实录:踩过的坑都在这
5.1 延迟忽高忽低:先分清是模型慢还是系统慢
延迟问题是AI工程里最常见的。用户反馈"有时候很快有时候很慢",这时候别急着优化模型,先定位瓶颈在哪。
我的排查顺序是:先看监控里的延迟分布,是整体偏高还是P99偏高。整体偏高通常是模型本身慢或网络慢;P99偏高通常是偶发的排队、重试或GC。然后看trace,把一次慢请求的每一步耗时拆开,看是检索慢、模型慢还是后处理慢。
常见的几个原因:一是没有连接池,每次请求都新建连接,握手开销大;二是同步阻塞,一个慢请求把线程占住,后面的请求排队;三是重试放大,一个请求失败重试三次,延迟翻三倍。对应的解法分别是加连接池、改异步、优化重试策略(比如加退避)。
我踩过最坑的一次是:延迟高但CPU和内存都不高,查了半天发现是日志同步写磁盘,磁盘IO成了瓶颈。改成异步写日志后,延迟直接降了一半。所以排查延迟,别只盯着模型,系统层面的因素往往更隐蔽。
5.2 输出不稳定:prompt、参数、模型版本挨个查
AI系统另一个头疼的问题是输出不稳定,同样的输入有时好有时坏。这个问题要分三层查。
第一层查参数。temperature是不是设高了?高temperature会让输出更随机。top_p、max_tokens这些参数有没有被意外改动?我遇到过因为配置文件被误改,temperature从0.3变成1.0,输出质量断崖式下跌。
第二层查prompt。prompt里有没有引入不确定的内容?比如把检索结果直接拼进去,检索结果变了输出就变了。prompt的模板有没有被改动?建议prompt也做版本管理,每次改动记录在案。
第三层查模型版本。托管模型可能悄悄更新了版本,行为就变了。所以要在调用时记录模型的具体版本号,发现异常时能对比。如果是自建模型,检查模型文件有没有被替换。
排查这类问题,日志的完整性是关键。如果日志里记录了完整的输入、参数、模型版本,排查就是几分钟的事;如果没记,就只能靠猜。
5.3 成本失控:token消耗的监控和优化
AI调用是按token计费的,成本失控是很多团队上线后才发现的痛。控制成本的核心是监控加优化。
监控方面,要按天、按接口、按用户统计token消耗,设置预算告警。发现某天消耗突增,立刻查原因。常见原因是:缓存失效导致重复调用、prompt变长、重试变多、或者被恶意刷量。
优化方面,几个立竿见影的手段:压缩prompt,去掉冗余的指令和示例;限制输出长度,max_tokens设合理值,别让它无限生成;提高缓存命中率,把高频相同请求缓存起来;分级调用,简单问题用轻量模型,复杂问题才用大模型。
我做过一个优化,把prompt里的示例从10个减到3个,输出质量几乎没降,token消耗降了四成。所以prompt不是越长越好,精简往往双赢。
5.4 常见问题速查表
把上面这些整理成一张速查表,出问题时按图索骥:
| 现象 | 可能原因 | 排查方向 | 解决手段 |
|---|---|---|---|
| 延迟整体偏高 | 模型慢/网络慢 | 看trace各步耗时 | 换模型/加连接池 |
| 延迟P99偏高 | 排队/重试/GC | 看并发和重试日志 | 异步化/优化重试 |
| 输出不稳定 | 参数/prompt/版本变动 | 对比历史日志 | 锁定参数/版本管理 |
| 成本突增 | 缓存失效/重试/刷量 | 看token消耗分布 | 修缓存/限流/压缩prompt |
| 服务起不来 | 配置缺失/依赖冲突 | 看启动日志 | 校验配置/锁依赖版本 |
| 缓存命中率低 | 键设计不合理 | 分析键的分布 | 规范键的生成逻辑 |
这张表不是万能的,但覆盖了八成以上的常见问题。遇到新问题,解决后记得补充进来,慢慢就形成自己团队的排查手册了。
6. 几个我反复验证过的实操心得
做AI工程这些年,有几个心得是反复验证过的,分享出来。
第一,抽象层要早建,但别过度设计。模型调用抽象、能力封装这些,从第一天就该有,因为它们改动成本低、收益高。但像复杂的插件系统、动态编排引擎这类,等真有需求再上,过早引入只会增加维护负担。
第二,日志和监控的投入永远不亏。我见过太多团队为了赶进度省掉监控,结果上线后排查问题靠猜,浪费的时间远超当初省下的。日志和监控是AI工程的"眼睛",没有它寸步难行。
第三,把prompt当代码管理。prompt的改动要经过评审、要版本化、要能回滚。很多团队prompt改得很随意,出了问题都不知道是哪个版本。用管理代码的方式来管理prompt,能避免大量低级问题。
第四,成本意识要贯穿始终。AI调用是真金白银,从设计阶段就要考虑成本。缓存、限流、分级调用这些手段,越早引入越好,等账单来了再优化就被动了。
第五,评估集是迭代的锚。没有评估集,每次改动都是盲改。建一个几百条的评估集,成本不高,但能让迭代有据可依,避免"修一个坏三个"。
最后再分享一个小技巧:给每个AI能力定义一个"健康分"。综合延迟、错误率、缓存命中率、成本等指标算一个分数,低于阈值就告警。这样不用盯着十几个指标看,一个分数就能判断系统状态。这个健康分我用了两年,救过好几次场,推荐你也试试。
这套从零搭建AI工程的方法,我在不同规模的项目里都用过,核心思路是一致的:先定边界,再分层,抽象要早,监控要全,迭代要稳。具体的技术选型可以随团队和业务调整,但这套骨架是通用的。希望这些经验能帮你少走点弯路,把AI能力真正稳稳地跑在生产环境里。