这个项目标题我盯着看了很久——ai-engineering-from-scratch。说实话,市面上讲AI工程化的教程和课程已经不少了,但绝大多数都默认你已经有了一定的机器学习背景、知道了什么是损失函数、什么是反向传播,然后一上来就扔给你几十个概念。真正从零开始、把一个完全不懂AI工程的人带到能独立搭建一个AI应用这条路走过来的人,反而没那么多。
我自己的经历是:从传统后端开发转到AI应用方向,最开始也踩了无数坑。今天这篇东西,我尽量还原我当时摸索出来的路径和思考方式,不堆概念,只讲实操。适合还没入门但很想入门的开发者和学生,也适合已经做了一两个AI demo、但始终觉得"不知道下一步该学什么"的朋友。看完你能建立起一条清晰的学习主线和第一个完整项目的动手路线,并且避开我当年没人提醒的那些坑。
1. AI工程化到底在做什么:先把概念掰开揉碎
1.1 不是训练模型,是让模型干活
很多人想到AI工程,第一反应是"训练模型"。这是最大的认知误区。
AI工程化真正要解决的问题,和传统软件开发有本质不同。传统开发是"你写了代码,机器执行你的逻辑";而AI工程是"你把意图和数据提供给模型,模型以概率的方式给你一个结果"。这中间引入了两个巨大的不确定性:一是模型的行为不完全由代码控制,二是数据的质量直接决定系统的上限。所以AI工程的核心工作,变成了如何在一个充满不确定性的系统里,通过流程设计和工程手段,把结果稳定控制在可接受范围内。
打个比方:写传统代码像你亲手做菜,每一道程序都是你的一道工序,完全可控;做AI工程像你雇佣了一个非常聪明但偶尔走神的大厨,你需要设计一套机制——告诉他做菜标准、给他备好菜、配好调料、做好品控——让他每次都做出水平以上的菜。这套机制,就是AI工程。
围绕这个目标,AI工程化的工作流大致是:数据准备 → 向量化 → 检索与上下文构建 → 模型生成 → 输出评估 → 部署与监控。拆成环节来看,每个环节都不难,但连起来是一个完整的系统工程,任何一个环节出问题,最终效果都会大打折扣。
1.2 "from scratch"到底指什么:一条完整的学习主线
"From scratch"不是让你从写神经网络开始,而是指你应该从零到一掌握上面那套工程链路,并亲手实现一遍。
我做过的总结是,一个合格的AI工程师至少需要四块能力:语言与工具基础(Python、命令行、Git);大模型应用核心(提示工程、上下文管理、RAG检索增强、Agent调用链);系统与部署能力(Docker、云服务、异步处理) ;评估与迭代能力(指标设计、模型对比、回归测试)。这四块能力的权重在不同团队不一样,但底层逻辑都绕不开它们。
如果你直接去看当前最热门的职位要求——前端也好后端也好测试也好,你会发现很多岗位的核心技能点已经从"调API"演变成"如何设计一条高质量、可监控、可复用的AI数据链路"。这个演化非常快,所以"从零开始学AI工程"不是上一个培训班就能搞定的事情,你需要自己动手搭项目,在真实的问题中把链路跑通。
2. 从零开始的技术栈选型:别一上来就背八股
2.1 Python到底要学到什么程度
我见过很多人卡在"Python还没学完"这个阶段,迟迟不敢动手做AI项目。实际上,AI工程日常开发用到的Python知识非常集中。
你不需要成为Python专家,但下面这些必须熟练:列表字典的基本操作、函数与类、文件读写、异常处理、装饰器只看懂即可、async/await并发能写出简单的并发任务。除此之外,最实用的一个技能是pip和venv管理,能创建虚拟环境,安装第三方包,解决依赖冲突。很多人AI项目跑不起来,就是卡在环境问题而非AI问题。
我给一个自测标准:你能否不查资料用Python写一个脚本,从一个PDF里读取所有文本,按段落切分,用openai的SDK批量向量化,然后存入一个向量数据库?如果你能独立完成这条链路里70%的代码,你的Python基础就已经足够上手了。
2.2 大模型应用开发的四大件
选型问题是我最常被问到的。很多人一上来就问"我应该学LangChain还是直接调API?",我的回答始终是:先裸手调API,再用框架。
在大模型应用开发这个领域,你绕不开四样东西:
第一,模型调用层。你至少要熟练OpenAI SDK的写法(兼容OpenAI接口的国产模型也都支持这格式),理解什么是chat completion、什么是流式输出、什么是temperature和top_p参数,以及它们如何影响生成结果。
第二,提示工程。好的提示词是AI应用的灵魂。你要学会几类经典的写法:角色设定、few-shot示例、步骤引导、输出格式约束。这个看起来人人都会,但实际调优非常吃经验。
第三,数据链路。包括文档解析、文本清洗、分块、向量化、存储、检索。这是RAG(检索增强生成)的主干,也是我见过最多人做得最粗糙的部分。
第四,部署与监控。模型服务、向量库服务、API服务和前端如何连接,如何记录每一次请求的输入输出,如何追踪响应延迟和成本。这些是生产环境才会暴露出来的问题,但一开始学就应该有意识。
框架方面,LangChain和LlamaIndex我都用过,第一遍学习建议用原始API裸写,跑通之后再引入框架。因为框架抽象度太高,很多细节被藏着,调试起来反而更痛苦。等你理解背后的原理,再用框架,就会觉得它是一个顺手的工具,而不是一个黑盒子。
2.3 基础设施:Docker和云的取舍
"从零开始"不代表你要从自己买服务器裸机开始。我的建议是:本地开发用Docker隔离环境,部署统一走云服务。
Docker是最值得提前学的工具。它解决的问题很简单:你的项目用Python 3.11,向量数据库用Milvus,模型API走远程,朋友的环境是Python 3.8。没有Docker,一次联调就能让人崩溃。有了Docker Compose,一个命令就能把依赖的全部服务拉起来。AI工程的项目依赖之复杂远超传统Web开发,所以我强烈建议在项目起步阶段就把Docker引入。
云服务主要用的是GPU算力,但如果你的项目走的是API调用路线(只调现成的模型接口),大部分情况下本地开发一台普通电脑就够用了,不需要GPU。真正需要云GPU的场景是把开源模型部署成自己的服务,这个可以放到第二阶段再学。第一阶段的从零路径,把精力集中在数据链路上,性价比最高。
3. 动手做第一个AI工程:搭建一个企业级RAG问答系统
技术选型说再多,不如动一个真实项目。我当时就是从"给同事做一个简历知识库问答"开始的,现在把它改成通用的文档问答场景,这依然是AI工程最典型、最有复现价值的入门项目。
3.1 项目背景与整体设计
场景是这样的:你手上有一批公司制度文档、产品手册、培训资料,分散在Word、PDF、Markdown里,同事每天要花大量时间翻文档找答案。你想做一个内部AI问答系统,让同事用自然语言提问,系统基于文档内容给出带着引用的回答。
这个项目看起来简单,但包含了AI工程的全链路:数据清洗、文本分块、向量化、检索、上下文注入、提示词设计、输出评估、接口部署。做完它,你的AI工程能力基本可以覆盖多数应用场景。
整体架构如下图(我说的是逻辑架构,不是代码结构)设计:
用户提问 → 检索模块(向量检索 + 关键词检索) → 合并排序 ↓ 上下文拼装 → LLM生成 → 返回带引用的回答这里选择RAG而不是微调模型,是为了适应场景里文档持续更新的特点。RAG的核心思路是:不把知识存进模型参数,而是把知识切成块,存进外部数据库,在回答时把相关知识检索出来,一并交给模型做参考。好处是更新知识只需更新数据库,无需重新训练模型,成本低且可控性强。
3.2 数据准备:最脏最累却决定成败的环节
数据环节做得好坏,取决于你对两个底层原则的理解。
第一个原则是:脏进脏出,数据质量决定了效果天花板。AI系统对输入数据极度敏感,文档里残留的页眉、页脚、表格错位、扫描版PDF乱码,都会在检索阶段制造大量干扰。我处理过一份产品手册,第8页的页眉是"第8页共42页",这个字符串被切进文本块,导致用户问"说明书有多少页"时,系统只能召回这个碎片,给不出任何有价值的内容。数据清洗的优先级永远高于一切花哨的模型。
第二个原则是:分块策略需要实验,不能拍脑袋。文本分块有两个方向性指标:一是块大小,二是块重叠度。块太大,检索时会把很多无关内容带进上下文,浪费token还容易产生错误答案;块太小,单块包含的信息量不足,模型理解不了完整逻辑。我实测过一批中文技术文档,按256字符分块,重叠50字符,检索命中率比512字符无重叠的方案高大约17%。
下面是我当时用的一个朴素但可靠的清洗与分块流程:
import re from typing import List def clean_text(text: str) -> str: # 去掉页眉页脚常见噪声(字段按你的文档调整) text = re.sub(r'第\s*\d+\s*页[,共\s\d页]*', '', text) # 多余空行压缩 text = re.sub(r'\n{3,}', '\n\n', text) return text.strip() def split_into_chunks(text: str, chunk_size: int = 256, overlap: int = 50) -> List[str]: """按段落优先、长度兜底的分块策略。""" if not text: return [] chunks: List[str] = [] current = "" for paragraph in text.split("\n\n"): # 段落超过上限则按句子继续切分 while len(paragraph) > chunk_size: current += paragraph[:chunk_size] chunks.append(current) paragraph = paragraph[chunk_size - overlap:] current = "" if len(current) + len(paragraph) <= chunk_size: current += "\n\n" + paragraph else: chunks.append(current) current = paragraph if current: chunks.append(current) return chunks这段代码的核心在于"段落优先、长度兜底"策略。直接按固定长度切,会把一个完整的语义段落拦腰截断,导致检索到的块语义不完整。先按段落天然边界走,段落过长再按句子边界二次切分,这样才能让每个文本块尽量自洽。
3.3 向量化与检索:核心逻辑拆解
数据准备好之后,你需要把文本块批量向量化,存入向量数据库。这里有几个关键的实操决策。
第一个决策是选embedding模型。我当时对比了几款,最终选择了中文效果比较稳定的BGE系列的API版本(当时我用的是bge-m3),维度1024,既支持中文检索理解能力强,也可以兼顾相似度计算。如果预算紧张想完全开源可私有化,也可以部署bge-large-zh服务。需要注意,模型一旦定下来就不要随便换,不然后面所有库里的向量全部作废,要整套重新向量化。
第二个决策是向量化代码怎么写。互联网上有太多教程把向量化弄得很复杂,实际上核心代码非常简单。下面是我当时批量向量化的一个实用脚本:
from openai import OpenAI import json client = OpenAI(base_url="https://你的API地址", api_key="你的Key") def embed_texts(texts: list[str]) -> list[list[float]]: # 一次请求最大批量,实测16条最稳妥 results = [] batch_size = 16 for i in range(0, len(texts), batch_size): batch = texts[i:i+batch_size] resp = client.embeddings.create( model="bge-m3", input=batch ) results.extend([item.embedding for item in resp.data]) return results chunks = split_into_chunks(raw_text) vectors = embed_texts(chunks) # 批量写入向量库,同时保存原始文本块的id与内容映射 for idx, vec in enumerate(vectors): collection.upsert(ids=[str(idx)], vectors=[vec])这里有一个小坑:如果数据量很大,比如几万条文本块,一次性embed很慢,接口还容易超时。我当时做了一个简单的断点续传:每处理1000个块就把本地缓存索引保存下来,出错了从最后一次成功的断点继续,而不是从头重跑。
第三个决策是检索策略。纯向量检索在实际项目中效果并不够用,因为用户问题里经常出现文档里没有按语义方式出现的精确关键词——比如产品型号"AB-2024X"、员工工号"E1023"。我的做法是使用混合检索:向量检索召回语义相近的内容,BM25关键词检索召回精确匹配的内容,两者按一定比例合并,再做一次去重和重排序。重排序我用的方案是给每组候选块计算分数,取前5个块拼进上下文。这个"向量+关键词+重排"的组合远比我最初纯向量检索效果好,实测用户满意度提升非常明显。
3.4 生成链路:模型选择与提示设计
检索到的相关文本块需要组装成上下文,与用户问题一起交给生成模型。这个环节最常见的错误是"把所有检索结果一股脑塞进去",导致上下文里有大量无关内容和互相矛盾的陈述。正确的做法是只保留高置信度的前几个块,并在提示词里明确告诉模型哪些可以参考、哪些不属于内容。
我当时的提示模板长这样:
SYSTEM_PROMPT = """ 你是一名严谨的文档问答助手。你的任务是依据提供的参考资料回答用户问题。 规则: 1. 只能依据参考资料回答,若参考资料不足以回答,请直接说"根据现有资料无法确认"。 2. 回答时在句末标注参考资料编号,例如[1][2]。 3. 严禁编造参考资料中没有的内容。 参考资料: {context} """ def build_messages(question: str, candidate_chunks: list[dict]) -> list[dict]: context = "\n\n".join( f"[{idx+1}] {chunk['text']}" for idx, chunk in enumerate(candidate_chunks) ) return [ {"role": "system", "content": SYSTEM_PROMPT.format(context=context)}, {"role": "user", "content": question} ]生成模型我用的gpt-4o-mini或者国内效果对标的模型,temperature调到了0.2。这里不要迷信大模型,检索质量不行,再强的生成模型也会一本正经地胡编。先尽力把检索做到位,模型只是最后一步的"表达者",不是"多知者"。
如果你愿意在成本上做一点调整,还有一个经验:为了控制上下文长度和成本,可以给候选块做一个压缩——如果候选块太长,把无关的段落截掉,只保留最相关的句子。我用关键词匹配+语义打分先选出每个块里最相关的句子,拼成精简上下文后,模型回答准确率和完整度反而上升了,因为无效信息减少了。
3.5 评估与上线:AI工程的最后一公里
我们接入合同审查场景后,有一个问题始终没有被很好解决——模型回答对错,谁来负责?这就是评估体系要回答的问题。没有评估,就没有优化依据。
我最开始做评估是纯人工测试20个问题,看一眼结果就打分。后来发现问题太多:第一,代码改了一个检索参数,20个问题都得重新测一遍,效率太低;第二,人工打分主观性太强,同一个答案我今天觉得合格明天觉得不行;第三,没有回归测试概念,上次通过的案例改了代码又挂了。
后来我搭建了一个轻量级回归评估集:80个高频问题,每个问题标注了标准答案或答案包含的关键点。评估时用LLM作为评判员,给每次回答打"完全正确/部分正确/错误"三档,并给出错误原因。代码改动后一键跑评估集,看整体通过率和错误分布。这带来的变化是巨大的:每次改动都有了依据,而不是凭感觉"好像好了"。
上线这步我用的FastAPI写了一个简单的查询服务,Docker打包部署在公司的内网服务器上。接口层面只暴露两个端点:一个接收问题返回答案和引用,另一个接收用户反馈标记错误答案。反馈数据日积月累,就是我后续优化检索和提示词的最宝贵素材。
这里特别提醒一点:AI应用上线一定要有日志和反馈机制。日志记录每次提问的原文、检索到的候选块、生成答案、延迟和token消耗;反馈机制让真实用户标记"这答案不对"。这两套数据组合起来,你才能真正知道线上系统在哪里出错、为什么出错。没有这两样,你的系统就是瞎子摸象,只能靠用户口头抱怨才能发现问题。
4. 从零开始的避坑清单:我踩过的14个坑
4.1 数据侧的坑
坑1:格式转换引入隐形噪声。PDF转文本时不小心把图片里的OCR识别错误也放进库,这种错误在检索时很难被发现,但它会把你答案的质量拉到很低。
坑2:重复数据没有去重。同一份文档被同步了好几次,检索时命中了两份几乎一样的块,模型会以为在收到"佐证",反而增加了重复内容在上下文里的比重。我在项目初期就吃过这个亏,问题问得越简单,重复数据影响越明显。
坑3:分块不记录原始位置信息。当时我切块后只存了文本和向量,没存文档名、章节路径、页码。结果检索到一个好答案,却不知道它来自哪份文档,引用信息根本给不出来。后来我重建了索引,强制每个块带上源文档ID和标题路径,这才解决了引用问题。
4.2 检索侧的坑
坑4:向量模型中途更换导致全库失效。这个我上文提过,但值得再强调。模型一换,所有旧的嵌套向量跟新模型算出的向量不在一个空间里,相似度计算完全失真,等于要重新embed整个库。
坑5:TopK参数拍脑袋。一开始我用TopK=10,把一堆不太相关的块都塞进上下文,结果模型被无关信息干扰,错误率上升。后来我结合重排序压缩到TopK=5左右,效果好很多。TopK没有标准答案,要看你文本块的粒度和实际数据的噪声程度。
坑6:检索不到就去调模型,而不是先查检索列表。这是原则性错误。问答系统出错,80%以上是检索的问题。排查时必须先看检索到的候选块里有没有正确答案——如果候选里压根没有,那再怎么调提示词也没用;如果候选有但模型没用对,那才是提示词和模型层的事。
4.3 评估侧的坑
坑7:评估集设计得太随机。没有按问题类型分层(事实型、流程型、观点型),导致评估结果没有参考价值。后来我按业务场景分了类,每一类单独统计通过率,立刻就能看到哪个环节最薄弱。
坑8:用LLM评估LLM时没有"参考答案"约束。直接让评判模型打分,它会偏宽松。我采用了"给参考要点列表,让评判模型逐项核对命中/缺失"的方式,评估稳定性明显提升。
坑9:只看准确率不看失败原因。我建议记录每次评估失败的具体错误类型——是"检索缺失"还是"模型幻觉"还是"上下文太长丢失关键信息"。按错误类型统计才真正有用。我见过很多人只盯着一个数字看,飞快调了一圈参数仍然原地踏步,就是因为分类信息没沉淀下来。
这9个坑是我印象最深的,另外还有几个偏细节的经验,也放在这里供参考:坑10,异步处理文档要控制并发数,防止被限流;坑11,Prompt里引用编号要和上下文实际编号对齐,不然引用像乱码一样不可用;坑12,线上输入要做长度限制,超长问题先截断或拆解,不然会浪费tokens;坑13,不要一开始就追求复杂的Agent工具链,先把单一RAG链路做稳,再考虑让模型主动调用工具;坑14,模型服务尽量透出温度参数给你自己控制,因为在不同问题类型下,温度0.2和0.7的效果差别明显,不是所有场景都适合低温。
5. 写在最后的几条个人体会
这个从零开始的路径,我实际走下来大概花了三个月——不算长,但中间充满曲折。我最想强调的有三点。
第一,先跑通,再优化。不要一开始就想着做一个完美的AI系统。第一次做出来的效果大概率不好,这很正常。关键是你能把整条链路从数据到部署跑通,哪怕是做一个只有20条文档的小型知识库问答,也比看十篇教程强得多。跑通之后你才有资格谈优化。
第二,永远不要停止记录失败。我坚持过一个习惯,每次系统出错,就记录当时的输入、检索结果、模型输出,并在旁边写下失败原因的判断。翻看这些记录,我能清晰地看到自己的认知是怎么一步步提升的——从最开始觉得"模型不行、得换更大模型",到后来发现是"检索到的块不对、切分策略有问题"。这个认知转变,是AI工程最核心的分水岭。
第三,AI工程会一直变,但链路永远不变。今天你用LangChain,明天可能出来一个更优秀的框架;今天的主流模型,明年可能就是过时的。但"数据清洗→分块→向量化→检索→上下文组装→生成→评估→迭代"这条链路,在未来很长时间内都不会变。从一开始就抓住它,你学任何新工具都会比别人快得多。
如果你刚拿到一个AI项目,大脑一片空白,先从搭建一个能跑的RAG问答系统开始,不要多想,动手。这是我从零到一最笨也最有效的方法。