如果说这两年圈子里哪个词出现频率最高,“ai-engineering”绝对排第一梯队。但很多人对它有个误解,以为是在算法岗后面加个工程字眼,或者把后端服务接上模型接口就算 AI 工程了。我在一线做过几年算法平台和模型服务,从零把一个算法团队的模型落地路径、数据链路、线上监控全部重新搭过一遍,最大的感受是:AI 工程真正解决的问题,是把“一个能跑的模型”变成“一套可靠、可控、能持续迭代的系统”。
这篇文章不是教程合集,也不打算列一堆工具链接。我想把从零搭建 AI 工程能力体系这件事拆开讲清楚:边界在哪里、模块怎么划、每一步为什么这么做、踩过哪些坑。不管你是想转型 AI 工程师的开发者,还是团队里负责把模型变成产品的技术负责人,只要顺着这条链路走一遍,基本就知道自己现在缺在哪一环了。
1. 先想清楚:AI 工程到底是什么,和普通开发有什么区别
1.1 从模型到系统,AI 工程在补齐中间这层
如果你去看一个算法团队的日常,会发现他们研究的核心对象是模型结构和指标。给一个数据集,训练出一个模型,在测试集上把准确率从 92% 提到 93%,这就是算法同学的工作边界。但模型文件只是产物,离“用户能用上”还差十万八千里。
我见过很多团队第一次把模型上线时的手忙脚乱:把 PyTorch 模型塞进 Flask 接口,推理请求一来,GPU 显存直接爆掉;输入数据的分布跟训练集不一样,模型输出一堆乱码,后端同学根本不知道是模型坏了还是接口坏了;模型跑得慢,数据库连接池被打满,整个服务跟着挂。
这些问题的共性在于:模型本身没问题,是模型外部的那套支撑系统没有建立起来。AI 工程干的活,就是补齐模型到业务之间的这一层——数据怎么进来、特征怎么算、模型怎么部署、服务怎么保障、效果怎么度量、下一轮迭代怎么反馈。它不直接产出模型,但决定了模型能不能真正产生价值。
1.2 AI 工程和算法研究、后端开发的关键差异
我经常用一个比喻:算法研究是在实验室里把一道菜的做法研究透,后端开发是把餐厅的厨房流水线盖好,AI 工程则是既要懂这道菜的火候,又要把出菜速度、食材新鲜度、顾客反馈全管起来。它横跨两边,但思考方式完全不同。
- 关注点不同:算法关心模型指标,AI 工程关心系统指标。准确率是模型的指标,P99 延迟、可用率、数据新鲜度才是系统的指标。
- 验收标准不同:后端的验收标准是功能是否符合预期,AI 工程的验收标准是“在输入不确定、模型会出错的前提下,系统仍然能稳定服务”。
- 故障模式不同:后端故障通常是确定性的,代码写错、依赖挂了,排查路径清晰。AI 工程的故障很多时候是渐变式的,模型效果悄悄变差、数据分布悄悄偏移,不盯着看根本发现不了。
这三点差异导致 AI 工程不能用普通的研发流程来管。你需要为不确定性设计——输入校验、输出兜底、效果监控、灰度回滚,缺一个环节,系统就是裸奔状态。
1.3 为什么值得从零开始,而不是直接搬一套平台工具
现在市面上现成的 AI 平台很多,拖拽式建模、自动训练、一键部署都有。但对于真正想把 AI 工程能力建起来的团队,我反而建议至少从零手写一条最简链路,哪怕是用很糙的方式。
原因不是工具不好,而是黑盒工具的“隐藏失败模式”太坑了。你可以用现成平台完成 80% 的常规操作,但剩下 20% 的异常情况,平台的文档不会告诉你,社区的帖子也覆盖不全。比如平台的数据版本管理是不是真的固化了数据集状态?模型服务在流量突增时是怎么扩容的?效果监控的指标是模型分还是业务指标?这些问题不亲手把链路搭一遍,你连问都问不出来。
而且,从零搭建会让你对整个系统的掌控力完全不一样。当模型预测出错时,你能有条不紊地从数据链路一路排查到模型服务,而不是对着平台日志一头雾水。这个能力在承担核心业务时是保命的。
2. 从零搭建的模块拆解与技术选型
2.1 一张模块地图,看清 AI 工程的完整链路
从零搭 AI 工程体系,第一件事不是写代码,而是把模块边界画清楚。我习惯把整套链路分成五层:数据接入与存储、特征与样本管理、模型训练与实验管理、模型服务与部署、线上反馈与监控。
数据接入层负责把业务数据、日志、外部数据收进来,做清洗、校验、格式转换,落到存储里。特征与样本管理层负责把原始数据变成模型能吃的东西,同时管理样本版本和特征一致性。训练与实验层负责模型训练、超参调优、实验记录和模型产物管理。服务与部署层负责把模型变成在线接口或离线批处理任务,处理并发、缓存、灰度。反馈与监控层负责收集线上的推理结果、业务反馈、延迟指标,形成闭环。
这五层不是先做完全部再做下一层,而是先打通一条最细的通路,让数据能从源头流到监控,再逐步把每一层做厚。很多团队失败,不是因为某一层做得不好,而是链路根本没通,每个模块都是孤岛。
2.2 技术栈选择:先保守,再演进
说到技术选型,我见过最多的错误是照搬大厂的架构。一上来就 K8s、Flink、Milvus、Ray 全上,结果团队光运维就累得半死,核心业务还没跑起来。
我给自己定过一个选型原则:能用数据库解决的不上额外组件,能用单机解决的不上分布式,能用简单方案解决的不上复杂框架。整套链路我推荐的基础组合是:
| 链路环节 | 推荐工具 | 选择逻辑 |
|---|---|---|
| 数据存储 | PostgreSQL + 对象存储 | 结构化数据、样本文件都够用,运维成本低 |
| 数据管道 | Python 脚本 + Airflow(或 Prefect) | 初期用 Crontab 都行,调度器只解决依赖和重跑问题 |
| 特征/样本管理 | 特征表 + 样本表 + DVC | 版本可追溯,不做重型特征平台 |
| 实验管理 | MLflow | 开箱即用,实验对比、模型注册都覆盖 |
| 模型服务 | FastAPI + ONNX Runtime / Triton | 在线推理性能足够,部署简单 |
| 监控告警 | Prometheus + Grafana | 技术栈通用,容易扩展 |
| LLM 应用检索 | pgvector / Qdrant | 数据量不大的时候不需要上分布式向量库 |
核心原则是:每个组件都要能回答“为什么用它替代另一个”。比如向量数据库,数据量在千万级以下、并发量不高,pgvector 完全够用,非要上 Milvus 只会增加运维负担。等数据量真的涨起来了,再迁也来得及,数据模型设计的坑比组件本身的坑好填得多。
2.3 先搭一个最小闭环,再逐步扩展
从零搭建最容易陷入的误区是想一次到位。我建议第一版只搭一个最小可行闭环:一个数据集、一个模型、一个在线接口、一套最朴素的监控。目标是让数据能够“进去再出来”,让模型“从训练到上线再到反馈”完整走一遍。
这个最小闭环有四个验收标准:数据从源头到训练样本的流程是可重复的;模型训练是带版本记录的;在线服务能持续稳定运行 24 小时;监控面板能同时看到系统指标和模型效果指标。只有这四条都满足,这个闭环才算真正通了。在此基础上再横向扩展数据源、纵向加深模型效果,复杂度是可控的。
我踩过的坑是:第一版就想把所有数据源都接进来,结果接入逻辑写了一大堆,模型还在用假数据训练,线上效果根本没法看。倒不如先接一个最核心的数据源,把链路跑通,再一个个加。每个数据源的接入都是独立的子项目,不要混在一条巨型管道里。
3. 核心细节解析与实操要点
3.1 数据版本管理:AI 工程最容易忽略的地基
AI 工程和传统软件工程最大的区别,是你的系统行为不仅取决于代码,还取决于数据。代码可以通过 Git 回滚,但数据如果不可控,整个系统的可追溯性就是零。
我第一次正经做数据版本管理,是因为一个线上事故。模型在测试集上效果很好,上线后表现却一塌糊涂。排查到最后发现:训练时用的数据集跟线上输入的数据分布根本不一致,而且团队里谁都不知道这份训练数据是从哪一版清洗脚本里产生的。从那以后,我把数据版本管理列进最高优先级。
实操上,我用 DVC 管理数据集版本,把每个版本的样本文件打上哈希,跟代码提交绑定。每次训练,配置里都写清楚用的是哪个数据版本、哪个特征版本、哪个代码提交。这样一旦模型效果异常,你能立刻定位到是代码变了、数据变了还是模型参数变了,而不是三个人围着电脑猜。
数据版本的核心是血缘关系。不光是数据集有版本,清洗脚本的版本、特征工程的版本、训练代码的版本都要能串联起来。你可以用 DVC 也可以用 MLflow 的 dataset tracking,甚至自己写个 JSON 记录所有版本号。手段不重要,关键是形成强制习惯:每次训练必须留痕。
3.2 实验管理和评估体系:别让“效果不错”变成一句空话
算法团队常说“效果不错”,但对 AI 工程来说,这句评价没有任何意义。效果不错是相对什么说的?在哪个数据集上?用了什么指标?基线是哪个版本?这些都说不清楚,迭代就是原地打转。
实验管理的第一步是标准化评估协议。每类任务都要定义好离线评估指标,分类任务看准确率、精确率、召回率、AUC,回归任务看 MAE、RMSE,排序任务看 NDCG。同时要固定评估数据集,不允许随手切一块数据就当测试集。模型升级的标准是:在新模型上跑同一份评估集,各项指标至少不能低于旧模型。
我推荐用 MLflow 做实验跟踪,每次训练自动记录超参数、代码版本、数据集版本、评估指标。这样团队里任何人打开实验列表,都能清晰看到每个模型的完整画像。更重要的是,要把评估做成流水线的一部分,训练完自动触发评估,结果直接进数据库,而不是训练完再手动跑一遍。
这里有个容易被忽略的细节:评估数据本身也需要版本管理。评估集变了,模型效果对比就失真了。我们每季度会重新采样更新评估集,但旧版本的评估集也留着,方便回溯模型在旧评估集上的表现变化。
3.3 模型服务的工程化:从“能调通”到“能抗住”
模型部署看起来是写个接口调模型,实际上坑极多。我见过太多模型服务死于并发、超时和异常输入。模型服务和普通接口服务最大的不同在于:模型推理的耗时通常比普通接口长不少,而且 GPU 资源昂贵,不能像普通服务那样随意起实例。
在线推理服务我通常分成两步走。第一步用 FastAPI 包一个同步接口,模型加载在进程启动时完成,避免每次请求都重新加载模型。这里最关键的是配置好超时和并发控制,用信号量限制同时进行推理的请求数量,防止流量突增时 GPU 显存被打爆。第二步再把模型转成 ONNX Runtime 格式,部署性能会有明显提升,实测在 CPU 推理场景下能快 2 到 3 倍。
服务上线前,必须做一次压测。不是为了测测能扛多少并发,而是要搞清楚瓶颈在哪:是模型推理慢,还是数据预处理慢,还是后端存储慢。我习惯先固定 QPS 阶梯式加压,把服务端的 P50、P95、P99 时延都记录下来,再对比不同环节的耗时分布。很多服务你以为瓶颈在模型,实际是 Python 侧的数据序列化耗了一大半。
3.4 LLM 应用工程的特殊性:提示词、检索、缓存与评测
如果说传统 AI 工程的核心是模型训练和部署,那 LLM 应用工程的核心则从训练转向了编排。你不训练大模型,但要把大模型用好,这同样需要工程手段。
做 RAG 应用时,我把主要精力放在四个环节:文档切分策略、向量检索质量、上下文组装逻辑、生成结果评估。文档切分看起来简单,实际上一个 chunk 的大小直接影响检索准确率。太大的 chunk 会把不相关内容混进去,太小的 chunk 又会丢失上下文。我的经验是先分析文档结构,按标题层级和语义段落切分,而不是机械地按固定字符数切。
检索质量评估要单独建一套评测集。拿一批真实用户问题,标注出标准答案,然后去测检索系统能不能把相关片段捞出来。我见过太多 RAG 应用只看生成结果的流畅度,结果检索模块早跑偏了都不知道。生成结果的评估,也不能只看 LLM 自己给自己的打分,至少要对齐到用户真实需求上。
上下文组装同样有讲究。不是所有检索到的内容都塞进 prompt 就是好的,相关内容太多,反而会稀释模型的注意力,还浪费 token。我在实际项目中会先让检索模块召回 Top 20 个片段,再经过一次重排,只取最相关的 Top 3 到 5 个进上下文。这个步骤简单,但对最终回答质量的提升非常明显。
4. 实操过程与核心环节实现
4.1 从零搭建一个最小系统的目录结构
我在这里给出一份经过实际验证的目录结构,是我反复调整后觉得最顺手的版本。它不复杂,但每一层职责清晰:
ai-engineering-from-scratch/ ├── configs/ │ ├── data_config.yaml │ ├── training_config.yaml │ └── serving_config.yaml ├── data/ │ ├── raw/ │ ├── processed/ │ └── sample_versions/ ├── src/ │ ├── data/ │ │ ├── ingest.py │ │ ├── validate.py │ │ └── version.py │ ├── training/ │ │ ├── train.py │ │ └── evaluate.py │ ├── serving/ │ │ ├── api.py │ │ └── model_wrapper.py │ └── monitoring/ │ ├── metrics.py │ └── alerts.py ├── tests/ ├── mlruns/ ├── docker-compose.yml └── README.md这个目录的核心逻辑是把配置、数据、代码、实验记录分开。配置全部用 YAML,训练参数、数据版本、服务参数都集中在这里,代码里不硬编码任何路径或超参。这样实验的可复现性从源头就保证了。
4.2 数据接入与校验:把垃圾挡在门外
数据接入的第一道工序是校验。我在 ingest.py 里会对每个字段做类型检查、范围检查、缺失率统计。不要小看这一步,线上数据和训练数据最大的不同就是脏数据特别多,字段为 null、日期格式不统一、枚举值溢出都是常态。
下面是一个简单的数据校验示例,思路是定义 schema 约束,然后对输入数据做逐项检查:
# src/data/validate.py import pandas as pd from pandera import DataFrameSchema, Column, Check schema = DataFrameSchema({ "user_id": Column(str, nullable=False), "item_id": Column(str, nullable=False), "category": Column(str, Check.isin(["c1", "c2", "c3"])), "price": Column(float, Check.greater_than(0)), "ts": Column("datetime64[ns]", Check.less_than("2026-01-01")), }) def validate_data(df: pd.DataFrame) -> pd.DataFrame: validated = schema.validate(df, lazy=True) print(f"校验通过,共 {len(validated)} 条样本") return validated校验规则的核心逻辑是“宁可拒绝,不可静默”。数据不符合预期就明确报错,而不是悄悄改掉。我见过团队为了省事,在清洗时把负数价格直接置为 0,结果模型学到一堆异常模式。清洗逻辑要透明,每一步处理都要记录,这样问题出现时能追溯。
4.3 训练实验记录:每次实验都留下完整档案
训练脚本我会用 MLflow 做全自动记录。每次跑训练,代码将从 data_config 里读取数据版本、从 training_config 里读取超参与模型参数,并在训练完成后自动记录评估指标。
# src/training/train.py import mlflow import yaml with open("configs/training_config.yaml") as f: config = yaml.safe_load(f) with mlflow.start_run(run_name=config["run_name"]): mlflow.log_params(config["model_params"]) mlflow.log_artifact("configs/training_config.yaml") # 训练模型逻辑 ... mlflow.log_metrics({"accuracy": 0.923, "auc": 0.981}) mlflow.pytorch.log_model(model, "model")这套流程能保证至少两件事:任何一次实验都能回溯到它用的数据和配置;任何两次实验的效果对比都是可信的。我带过的团队里,凡是养成了这个习惯的,迭代效率明显高于“跑完一个实验随手记在聊天里”的团队。
4.4 模型部署与监控:上线不是终点,是起点
模型服务我用 FastAPI 封装,模型加载放到进程启动的钩子里。这里有个容易踩的坑:模型加载往往需要几十秒,如果放在请求里才加载,第一个请求必然超时。正确的做法是启动时加载,并配合服务健康检查。
# src/serving/api.py from fastapi import FastAPI from src.serving.model_wrapper import ModelWrapper app = FastAPI() model = ModelWrapper() @app.on_event("startup") def load_model(): model.load() @app.get("/health") def health(): return {"status": "ok", "model_version": model.version} @app.post("/predict") def predict(req: PredictRequest): result = model.predict(req.features) return {"prediction": result}上线只是模型生命周期的开始。我会在 Prometheus 里记录三类指标:系统指标(QPS、延迟、错误率)、模型指标(预测得分分布、各类别占比)、业务指标(转化率、点击率,这个要由业务侧上报)。监控面板上这三类指标放在同一页,任何一类的异常都能第一时间关联到其他两类。
预测得分分布是我特别关注的一个指标。模型上线后,我会盯着预测得分的均值、方差、分位数是否和训练集上的一致。一旦分布突然偏移,大概率是输入数据分布变了,这是模型效果恶化的最早信号,比等用户投诉来得快得多。
4.5 LLM 应用的最小闭环:从文档到问答的完整链路
如果你想做的是 LLM 应用,我建议从最简单的 RAG 闭环起步。核心流程是:文档加载 -> 切片 -> 向量化 -> 存入向量库 -> 检索 -> 组装 -> 生成。我给出一个可参考的索引和检索骨架:
# src/rag/index_and_retrieve.py from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.vectorstores import Qdrant text_splitter = RecursiveCharacterTextSplitter( chunk_size=512, chunk_overlap=64, separators=["\n\n", "\n", "。", "!", "?"], ) def build_index(documents, embedding_model, collection_name="docs"): chunks = text_splitter.split_documents(documents) vectorstore = Qdrant.from_documents( chunks, embedding_model, collection_name=collection_name, ) return vectorstore def retrieve(vectorstore, query, top_k=20): candidates = vectorstore.similarity_search_with_score(query, k=top_k) # 在这里做重排,只取最相关的 Top 4 reranked = rerank_by_score(candidates)[:4] return reranked这里我用的 chunk_size 是 512,chunk_overlap 是 64,这三个参数需要根据自己的文档类型调。技术文档可以切大一点,对话类文本切小一点。重排逻辑在示例里简化了,实际项目中我习惯用 cross-encoder 做重排,效果比纯向量相似度好不少。
5. 常见问题与排查技巧实录
5.1 模型线上效果不如离线评估,问题出在哪
这是 AI 工程圈最高频的问题,没有之一。我遇到过的原因主要有四类:
第一,训练数据和线上数据分布不一致。比如训练数据是用户近一年的行为,但线上用户的行为模式已经变了。解决思路是监控输入特征的分布,做数据漂移检测。第二,离线评估指标选错了。比如线上关注的是用户转化,离线却只测了点击率。第三,样本选择偏差。训练时用的样本本身就过滤掉了某类用户,模型自然学不到那类用户的规律。第四,特征一致性问题。训练时用的特征和线上实时算出来的特征不是同一套公式。这种情况我在一些团队里见过,离线 TF-IDF 和在线实时算的 TF-IDF 因为版本没有对齐,结果完全不同。
排查思路我建议按“数据 -> 特征 -> 评估 -> 模型”的顺序来,每层都做一次一致性校验。多数问题的根子不在模型,而在更靠前的环节。
5.2 服务延迟突然飙升,怎么快速定位瓶颈
有一次我们线上模型服务 P99 延迟从 80ms 飙到 900ms,排查过程让我印象很深。
我先看监控,发现 QPS 并没涨多少,GPU 利用率也不高,那就不是流量和算力问题。再看调用链,发现耗时主要卡在模型推理前的一个用户画像查询接口上。那个接口用的是外部服务,它出问题了,我们这边的请求全在排队等它响应。
这个经历让我养成了一个习惯:所有服务都要有明确的超时设置和降级方案。外部依赖超时不能无限等,宁可返回兜底结果,也不要拖垮整个服务。现在我的所有预测接口都会设置三层超时——连接超时、处理超时、总超时,并配合线程池隔离,一个依赖挂了不会拖垮全部请求。
压测和容量规划也很重要。我会在每轮新模型上线前做一次压测,记录下在不同并发下的延迟表现,形成一个简单的容量基线。这样后续服务出问题,我一眼能看出是流量增长导致的正常退化,还是代码或资源出现了异常。
5.3 评估得分虚高,模型上线就“现原形”
评估集如果构造不合理,得分再高也是自欺欺人。最常见的三个问题:时间泄漏、标签泄漏、数据重复。
时间泄漏是指在训练集中混入了未来信息。比如用 T 时刻的特征预测 T 时刻的行为,但特征里包含了 T+1 时刻才能拿到的数据。标签泄漏更隐蔽,比如预测用户是否流失,却把“用户已经提交了注销申请”当作特征。数据重复则会导致同一批样本同时出现在训练集和评估集里,模型等于看过答案了。
我的解决办法是建立一套严格的样本切分规则。按时间序列切分,前 80% 做训练,后 20% 做评估,并且保证评估集和训练集没有同一天的数据。同时用一个“泄漏检测”脚本来扫描特征和标签的相关性,一旦发现有特征与标签高度相关,单独拎出来人工审查。
5.4 常见问题速查表
| 现象 | 可能原因 | 排查思路 |
|---|---|---|
| 上线效果远差于离线评估 | 数据漂移、评估集构造不合理 | 先查特征分布,再查评估集的切分逻辑 |
| 模型服务频繁超时 | 并发控制缺失、外呼依赖无超时 | 加信号量控制并发,给外部依赖设超时和降级 |
| GPU 利用率低但推理慢 | 数据预处理在 CPU 上串行执行 | 把预处理做成批处理,或同步转异步 |
| 每次复现训练结果不同 | 未锁定依赖版本、随机种子未固定 | requirements 锁版本,设置全局随机种子 |
| 预测结果和训练时差异大 | 特征工程代码版本不一致 | 统一特征计算代码,训练线上共用一套代码库 |
| 向量检索出来的内容不相关 | 切分策略不合理、评分方式单一 | 调整 chunk 尺寸,增加重排环节,检查 embedding 是否和领域匹配 |
这些坑是几乎所有团队都会遇到的,区别只在于碰到的时候手里有没有一套排查方法。我始终觉得 AI 工程拼的不是你用了多高级的框架,而是在这些问题出现时,你能不能快速定位到真正的原因。
如果让我对刚开始搭 AI 工程体系的朋友说一句最想说的话,我会说:把时间花在数据的可观测性和系统的可追溯性上,不要急着堆模型和框架。
我见过太多团队花几个月训练了一个大模型,却连“这个模型是拿哪份数据训出来的”都说不清楚。数据和实验的可追溯性,是 AI 工程和普通开发最本质的差别。这套能力前期建设会让人觉得枯燥,但等你真正需要排查一个线上模型事故时,会感谢当初愿意花时间做这些“琐事”的自己。
从零开始搭链路,一开始会很慢,但慢是正常的。只要最小闭环通了,后面每一层的扩展都会越来越顺。如果你正在走这条路,可以按我上面说的五层链路对照一下自己卡在哪一层,先解决最痛的那一处,整套系统就能往前滚动了。