1. 从零搭建AI工程能力:为什么我劝你别一上来就啃论文
"ai-engineering-from-scratch"这个标题,我第一次看到的时候心里咯噔了一下。过去两年多,我带过不少想转AI工程方向的朋友,也面试过几十个候选人,发现一个特别普遍的现象:大部分人一提到"从零学AI工程",第一反应就是去啃Transformer原论文、刷吴恩达的课、背反向传播公式。结果呢?三个月过去,论文看了七八篇,课刷了两三门,但让他真正动手搭一个能跑起来、能上线、能处理真实数据的AI服务,直接卡死。
问题出在哪?出在把"AI研究"和"AI工程"混为一谈了。研究关注的是模型能不能work、指标能不能刷高;工程关注的是这套东西怎么稳定地跑在生产环境里、怎么处理脏数据、怎么控制延迟和成本、怎么在模型效果下降时快速定位。这两件事需要的能力栈完全不同。ai-engineering-from-scratch这个项目标题,核心价值恰恰在于它瞄准的是后者——从工程视角出发,把AI能力从零搭建起来,而不是从数学公式推导开始。
这篇文章我想聊的是:如果你是一个有基础编程能力、想系统补齐AI工程能力的开发者,或者是一个已经在做后端/数据方向、想往AI方向转的工程师,应该怎么规划这条从零到一的路。我会把整个学习路径拆成可执行的阶段,每个阶段给出具体的工具选型理由、实操步骤、以及我自己踩过的坑。全文不涉及任何敏感内容,纯粹是技术路径和工程实践的分享。
适合谁看?有Python基础、了解基本的数据结构、能看懂简单的API文档,但对AI工程的全貌没有系统认知的人。如果你已经能独立部署模型服务了,这篇文章可能对你偏基础,但里面关于工程细节和避坑的部分仍然值得扫一眼。
2. 整体学习路径设计与思路拆解
2.1 为什么不能按"数学→算法→框架→应用"的顺序学
传统科班路线是:线性代数→概率论→机器学习算法→深度学习→框架→应用。这条路本身没错,但它的问题在于反馈周期太长。你学完线性代数和概率论,可能已经过去两个月了,但你还没写过一行能跑出结果的AI代码。这种延迟反馈对成年人自学来说是致命的,大部分人会在第三周就放弃。
我推荐的路线是倒过来的:先跑通一个最小可用的AI应用,哪怕你完全不懂它背后的原理,先让东西跑起来,建立"我能做出东西"的正反馈,然后再往下挖原理。这就像学开车,你先上路开起来,再慢慢理解发动机原理,而不是先把内燃机拆一遍再上车。
具体来说,我把整个路径分成四个阶段:调用阶段(会用现成的API和模型)、集成阶段(能把AI能力嵌到自己的系统里)、调优阶段(能针对具体场景优化效果和成本)、自建阶段(能自己训练、微调、部署模型)。这四个阶段不是严格线性的,但大方向是这样递进的。
2.2 每个阶段的核心目标和产出物
调用阶段的目标很简单:你能用Python调通至少三个主流模型服务,理解什么是token、什么是上下文窗口、什么是temperature。产出物是一个能跑的小脚本,比如一个命令行问答工具。
集成阶段的目标是:你能把模型能力封装成一个服务,处理真实的输入输出,加上错误处理、重试、日志。产出物是一个能对外提供HTTP接口的小服务,比如一个文档问答机器人。
调优阶段的目标是:你能针对具体任务调整提示词、选择合适的模型、控制成本、评估效果。产出物是一套评估脚本和优化后的提示词模板。
自建阶段的目标是:你能在本地或云端部署开源模型,能做简单的微调,理解推理框架的选型。产出物是一个自己部署的模型服务,以及一份微调实验记录。
这四个阶段走下来,快的话三到四个月,慢的话半年。关键不是速度,而是每个阶段都要有能拿得出手的产出物。没有产出物的学习,等于没学。
2.3 工具选型的底层逻辑
工具选型这件事,我的原则是:在每个阶段只引入当前阶段必需的工具,绝不提前引入。很多教程一上来就让你装一堆东西——LangChain、LlamaIndex、向量数据库、各种Agent框架,结果你连最基本的API调用都没搞明白,就被这些框架的抽象层绕晕了。
调用阶段,你只需要一个HTTP客户端(requests或httpx)和一个API key,连SDK都不一定要用。集成阶段,引入一个Web框架(FastAPI足够)和一个配置管理工具。调优阶段,引入评估工具和日志工具。自建阶段,才需要考虑推理框架(比如vLLM、TGI这类)和向量数据库。
这个原则背后的逻辑是:每引入一个工具,你就多了一层需要理解的黑盒。在你不理解底层原理的时候,黑盒越多,出问题时你越无从下手。我见过太多人用LangChain搭了个demo,结果一上线就各种诡异bug,因为他根本不知道框架在背后做了什么。
3. 核心细节解析与实操要点
3.1 调用阶段:把API调明白比什么都重要
这个阶段最容易被轻视,但恰恰是最重要的。很多人觉得"调API谁不会",但真正调明白的人不多。你需要理解几个核心概念。
Token是什么。Token不是字,也不是词,它是模型处理文本的最小单位。英文里一个token大约对应0.75个单词,中文里一个汉字大约对应1到2个token。为什么要在意这个?因为计费按token算,上下文窗口按token算,输出长度也按token算。你写一个提示词,如果不知道它大概多少token,就没法控制成本。
上下文窗口的边界。每个模型都有一个最大上下文长度,比如8K、32K、128K。这个长度是输入加输出一起算的。如果你输入了7K token,那输出最多只能有1K(假设窗口是8K)。超出窗口会直接报错。实操中,你需要一个token计数工具,比如tiktoken这个库,在发送请求前先算一下。
Temperature和采样参数。Temperature控制输出的随机性。0表示确定性输出(每次一样),1表示比较随机。做事实性问答用低temperature,做创意写作用高temperature。还有一个top_p参数,控制采样范围,一般和temperature二选一调就行,不要同时调。
实操上,我建议你写一个封装函数,把API调用、token计数、错误重试都包进去。这个函数后面每个阶段都能复用。代码大概长这样:
import httpx import tiktoken def count_tokens(text, model="gpt-4"): enc = tiktoken.encoding_for_model(model) return len(enc.encode(text)) def call_model(prompt, model="gpt-4", temperature=0.7, max_retries=3): for attempt in range(max_retries): try: resp = httpx.post( "https://api.example.com/v1/chat/completions", json={ "model": model, "messages": [{"role": "user", "content": prompt}], "temperature": temperature }, timeout=30 ) resp.raise_for_status() return resp.json()["choices"][0]["message"]["content"] except Exception as e: if attempt == max_retries - 1: raise time.sleep(2 ** attempt)注意:上面的API地址是示意,实际使用时替换成你所用服务的地址。重试逻辑用指数退避,避免短时间内反复打爆服务。
这个阶段的一个常见误区是:过早追求"高级"用法。比如一上来就搞function calling、搞多轮对话管理、搞流式输出。这些都有用,但前提是你先把最基本的单轮调用调稳。我建议这个阶段至少写够20个不同场景的调用脚本,覆盖问答、摘要、分类、抽取、翻译这几类任务,每个都跑通。
3.2 集成阶段:从脚本到服务的跨越
脚本能跑,不代表能上线。脚本和服务之间隔着好几道坎:并发、错误处理、超时、限流、日志、配置管理。这个阶段就是把这些坎一个个填平。
并发处理。脚本是串行的,一个请求处理完再处理下一个。服务必须能并发。Python里最简单的并发方案是FastAPI加异步。但要注意,模型API调用本身是IO密集型的,用async能显著提升吞吐。不过如果你用的是同步的HTTP库,那就得用线程池。
超时和重试。模型服务偶尔会慢,偶尔会挂。你的服务不能因为一次调用超时就整个卡死。每个外部调用都要设超时,超时后要有降级方案。重试要区分错误类型:网络错误可以重试,参数错误重试也没用。
限流。模型服务通常有QPS限制。你的服务如果并发太高,会被限流甚至封禁。需要在客户端做限流,比如用令牌桶算法控制请求速率。
日志。这个太重要了。每次调用都要记录:输入、输出、耗时、token数、是否成功。出问题时,日志是你唯一的线索。我建议用结构化日志,比如JSON格式,方便后续检索和分析。
配置管理。API key、模型名、超时时间这些不能硬编码在代码里。用环境变量或配置文件管理。生产环境的key和测试环境的key要分开。
这个阶段的产出物,我建议是一个完整的文档问答服务。用户上传文档,服务切分、向量化、存储,然后用户提问时检索相关片段、拼成提示词、调用模型、返回答案。这个项目麻雀虽小五脏俱全,能把上面所有工程点都覆盖到。
3.3 调优阶段:效果和成本的双重博弈
服务能跑了,接下来就是让它跑得好、跑得省。这个阶段的核心是评估。没有评估,你所有的优化都是盲猜。
建立评估集。针对你的具体任务,准备至少50到100个测试用例,每个用例有输入和期望输出。这个评估集要覆盖典型场景和边界场景。比如做客服问答,就要覆盖常见问题、模糊问题、超出范围的问题。
定义评估指标。分类任务用准确率、召回率、F1。生成任务用BLEU、ROUGE这类指标,或者用另一个模型来打分(这叫LLM-as-judge)。人工评估最准,但成本高,适合小规模抽查。
提示词优化。这是调优阶段性价比最高的手段。同样的模型,好的提示词和差的提示词,效果能差出一大截。优化提示词的几个原则:指令要具体、给例子(few-shot)、让模型一步步思考(chain-of-thought)、明确输出格式。
模型选择。不是越贵越好。很多任务用小模型就够了。我一般会先用最强的模型跑一遍,拿到效果上限,然后逐步换小模型,看效果下降多少、成本降多少,找到性价比最优点。
成本控制。几个实用手段:缓存相同请求的结果、压缩提示词、用更短的输出格式、批处理。缓存这个特别有效,很多场景下重复请求比例很高。
这个阶段我踩过最大的坑是:过早优化。一开始就想着怎么省钱,结果用了小模型,效果不行,又回头换大模型,来回折腾。正确做法是先保证效果达标,再在达标的前提下优化成本。
3.4 自建阶段:什么时候该自己部署模型
不是所有场景都需要自己部署模型。自己部署的成本包括:硬件成本、运维成本、调优成本。只有当你有以下需求时才考虑:数据不能出本地、需要深度定制、调用量极大导致API成本过高、需要极低延迟。
自己部署的第一步是选推理框架。主流的有vLLM、TGI、llama.cpp这几类。vLLM吞吐高,适合服务端;llama.cpp轻量,适合本地和边缘设备。选型要看你的硬件和场景。
第二步是模型量化。原始模型动辄几十GB,量化后能压到几分之一,代价是精度略降。常见的量化方案有GPTQ、AWQ、GGUF。量化等级用4bit通常能在精度损失很小的情况下大幅降低显存占用。
第三步是微调。微调不是必须的,很多场景用提示词工程就能解决。只有当你有大量标注数据、且任务非常特定时,微调才划算。微调方法有全量微调和参数高效微调(LoRA、QLoRA)。LoRA只训练一小部分参数,显存需求低,效果接近全量微调,是首选。
这个阶段的产出物是一份完整的部署文档,包括硬件配置、框架选型、量化方案、压测结果。这份文档的价值在于,下次你要部署新模型时,可以直接复用这套流程。
4. 实操过程与核心环节实现
4.1 环境搭建:从一台干净的机器开始
我习惯从一台干净的Linux机器开始,把所有依赖装明白。这样出问题时排查范围小。以下是完整的环境搭建流程。
第一步,装Python。用pyenv管理版本,避免系统Python被污染。装3.10或3.11,这两个版本对AI生态的兼容性最好。
curl https://pyenv.run | bash pyenv install 3.11.6 pyenv global 3.11.6第二步,建虚拟环境。每个项目一个独立环境,这是铁律。
python -m venv venv source venv/bin/activate第三步,装核心依赖。调用阶段只需要httpx和tiktoken。集成阶段加fastapi、uvicorn、pydantic。调优阶段加pandas、numpy。自建阶段加torch、transformers、vllm。
pip install httpx tiktoken fastapi uvicorn pydantic注意:torch的安装要匹配CUDA版本,装错了会各种报错。先用nvidia-smi看驱动支持的CUDA版本,再去PyTorch官网找对应的安装命令。
第四步,配置密钥管理。用.env文件存密钥,用python-dotenv加载。.env文件加到.gitignore里,绝不提交到代码仓库。
pip install python-dotenvfrom dotenv import load_dotenv import os load_dotenv() API_KEY = os.getenv("API_KEY")这套环境搭下来大概20分钟。我建议你把这套流程写成一个shell脚本,下次直接跑。
4.2 第一个可用的AI服务:文档问答机器人
这个项目我做过不下五遍,每次都有新体会。它的价值在于把AI工程的核心环节都串了一遍。
文档切分。文档不能整篇塞给模型,太长。要切成小块。切分策略有按固定长度切、按段落切、按语义切。最简单的是按固定长度加重叠。比如每块500 token,相邻块重叠50 token,避免关键信息被切断。
def split_text(text, chunk_size=500, overlap=50): tokens = text.split() chunks = [] for i in range(0, len(tokens), chunk_size - overlap): chunk = " ".join(tokens[i:i + chunk_size]) chunks.append(chunk) return chunks向量化。把每个文本块转成向量,存到向量数据库。向量化的模型可以调API,也可以用本地的sentence-transformers。本地的好处是免费、数据不出本地,坏处是要占资源。
检索。用户提问时,把问题也向量化,然后在向量数据库里找最相似的几个块。相似度用余弦相似度。检索数量一般取3到5个,太多会超出上下文窗口,太少可能漏掉关键信息。
生成。把检索到的块拼成提示词,加上用户问题,调模型生成答案。提示词模板大概是这样:
基于以下资料回答问题。如果资料中没有相关信息,就说"资料中未提及"。 资料: {context} 问题:{question}服务化。用FastAPI包成HTTP接口。上传文档一个接口,提问一个接口。加上日志、错误处理、超时。
这个项目做完,你对AI工程的全流程就有了体感。后面所有的优化都是在这个基础上做加法。
4.3 评估脚本:让优化有据可依
评估脚本是我认为最被低估的工程能力。大部分人做AI项目,改完提示词就凭感觉说"好像好了一点",这完全不可靠。
评估脚本的核心是:读入评估集,对每个用例跑一遍,对比输出和期望,算指标,输出报告。评估集用JSON或CSV存,每条包含input和expected。
import json def evaluate(predict_fn, test_file): with open(test_file) as f: cases = json.load(f) results = [] for case in cases: output = predict_fn(case["input"]) results.append({ "input": case["input"], "expected": case["expected"], "output": output, "match": case["expected"] in output }) accuracy = sum(r["match"] for r in results) / len(results) return accuracy, results这个脚本简单,但威力大。每次改完提示词或换模型,跑一遍,看准确率变化。有了这个,你的优化就从玄学变成了科学。
提示:评估集要定期更新。随着你对任务理解加深,会发现原来的评估集覆盖不全,要补充新用例。评估集的质量直接决定优化的方向是否正确。
4.4 成本监控:别等账单来了才后悔
成本监控要尽早做。我见过有人跑了个批处理任务,一晚上烧掉几百块,第二天才发现。
监控的核心是记录每次调用的token数和费用。在封装函数里加一行记录,把数据写到日志或数据库。然后定期汇总,看每天、每周的花费趋势。
def log_usage(model, input_tokens, output_tokens, cost): with open("usage.log", "a") as f: f.write(json.dumps({ "time": time.time(), "model": model, "input_tokens": input_tokens, "output_tokens": output_tokens, "cost": cost }) + "\n")有了这个日志,你能清楚看到钱花在哪了。很多时候你会发现,某个不起眼的功能消耗了大量token,优化它就能省一大笔。
5. 常见问题与排查技巧实录
5.1 调用报错速查表
| 错误类型 | 常见原因 | 排查方向 | 解决方法 |
|---|---|---|---|
| 401 | 密钥无效或过期 | 检查key是否正确、是否过期 | 重新生成key,检查环境变量 |
| 429 | 请求频率超限 | 检查并发数和QPS | 加限流、加退避重试 |
| 400 | 参数错误 | 检查模型名、参数范围 | 对照文档核对参数 |
| 超时 | 网络问题或服务慢 | 检查网络、看服务状态 | 加超时、加重试、降级 |
| 上下文超限 | 输入太长 | 算token数 | 截断输入或换大窗口模型 |
这张表我贴在显示器边上,出问题先对一遍,能解决八成常见错误。
5.2 效果不达预期的排查思路
效果不好,先别急着换模型。按这个顺序排查:
第一,看输入。模型收到的输入是不是你期望的?很多时候是提示词拼接出了问题,或者检索到的内容不相关。把实际发给模型的完整提示词打印出来看。
第二,看提示词。指令是否清晰?有没有给例子?输出格式是否明确?我遇到过很多次,加一句"请用JSON格式输出"效果就大幅提升。
第三,看评估集。评估集本身有没有问题?期望输出是否合理?有时候是评估集标注错了,导致你以为效果差。
第四,换模型。前面都排查过了,再考虑换模型。换的时候要控制变量,一次只换一个因素。
5.3 我踩过的几个坑
坑一:忽略token计数。早期我没做token计数,有次用户输入了一篇长文,直接超限报错。后来加了计数和截断逻辑才解决。
坑二:重试没做退避。有次服务不稳定,我的重试逻辑是立即重试,结果把服务打得更挂。改成指数退避后好多了。
坑三:日志记了但没看。日志记了一堆,但从来没分析过。后来做成本优化时才发现,日志里全是线索。现在我会定期看日志,找异常和优化点。
坑四:过早引入框架。一开始就用LangChain,结果框架版本升级,代码全挂。后来回归原生调用,稳定多了。框架不是不能用,但要等你理解底层之后再引入。
坑五:评估集太小。一开始只准备了10个用例,优化来优化去,发现线上效果还是不行。后来扩到100个,才发现问题所在。评估集要足够大才能反映真实分布。
5.4 性能优化的几个实用技巧
批处理。如果有很多独立的请求,可以合并成一个批次发给模型,减少网络往返。很多API支持批量输入。
缓存。相同或相似的请求,结果可以缓存。用输入内容的哈希做key。缓存命中率高的话,能省大量成本和时间。
流式输出。对于长文本生成,用流式输出能显著提升用户感知速度。用户看到字一个个出来,比等半天一次性出来体验好得多。
异步并发。IO密集型的调用用异步能大幅提升吞吐。但要注意控制并发数,别把服务打挂。
提示词压缩。提示词里的冗余信息可以精简。比如重复的指令、不必要的例子,都可以删。提示词短了,成本和延迟都降。
6. 从工程视角看AI能力的长期演进
6.1 工程能力比模型能力更保值
模型迭代太快了。今天最强的模型,半年后可能就被超越。但工程能力不一样——你怎么设计一个稳定的服务、怎么建立评估体系、怎么控制成本、怎么排查问题,这些能力不会因为模型换代而失效。
我见过太多人追着新模型跑,每出一个新模型就重写一遍代码,结果什么都没沉淀下来。正确的做法是把模型当成一个可替换的组件,用工程手段把它包起来,换模型时只改配置不改架构。
6.2 建立自己的工具箱
走完这四个阶段,你应该积累了一套自己的工具和模板。包括:API调用封装、评估脚本、日志和监控、提示词模板库、部署脚本。这些东西是你真正的资产。
我建议把这些工具整理成一个内部库,每个新项目直接复用。这样你的启动成本会越来越低,做新项目的速度会越来越快。
6.3 持续学习的正确姿势
AI工程这个领域,学习不能停。但学习要有方法。我的做法是:关注几个高质量的信息源,每周花固定时间扫一遍,看到有用的就动手试一下。不追热点,只关注能解决实际问题的东西。
另外,多动手。看十篇文章不如自己跑通一个项目。每学一个新东西,就找个小场景用起来。用起来的知识才是你的。
6.4 关于"从零"的一点个人体会
"从零"不等于"从最底层"。从零搭建AI工程能力,不是让你从写矩阵乘法开始,而是让你从第一个能跑的AI应用开始,一步步往上加工程能力。这个过程中,你会不断遇到问题、解决问题,能力就在这个循环中长出来了。
我自己走这条路的时候,最大的收获不是学会了某个具体技术,而是建立了一套解决问题的框架:遇到问题先定位、再分析、再验证、再固化。这套框架比任何具体技术都值钱。
最后分享一个小技巧:每完成一个阶段,写一篇总结,把学到的东西、踩过的坑、还没搞懂的问题都记下来。过一段时间回头看,你会发现自己进步得比想象中快。这个习惯我坚持了两年多,受益良多。