这两年,“从零开始学AI”几乎成了程序员圈里的最热话题。但真正动手之后你会发现,网上那些教程要么只教“怎么调API”,要么直接丢给你一堆数学公式,能跑通原型和能交付一个可靠的AI工程系统,中间隔着整整一片沼泽地。
我花了大半年时间,完全从零把一个基于大语言模型的想法推进到可用状态,这期间接触了Prompt工程、RAG、Agent、评测、部署等一整套链路。今天这篇内容,就是我个人实践的经验复盘,重点聊“AI工程到底涉及哪些环节”“每一步的核心难点在哪”“哪些坑值得提前避开”。如果你正准备入门,或者已经卡在某个环节,这篇应该能给你省下不少走弯路的时间。
1. 内容整体设计与思路拆解
1.1 我理解的“从零开始”其实包含两层含义
“From scratch”这个说法,我认为要拆开看。第一层是“零基础也能跟上”,第二层是“不依赖现成大数据平台的脚手架,自己把核心链路搭起来”。
很多教程默认你会用某某云服务、某某框架,但我的实践体会是:越依赖黑盒,出问题时越难定位。所以我给自己定的路线是——先搞清楚大模型API背后发生了什么,再考虑用框架。
整个AI工程链路,我建议分成五个阶段来理解:输入处理(分词、格式化)、模型推理(生成结果)、输出固化(结构化解析)、记忆与上下文管理(会话、向量库)、评估与监控(质量、成本、延迟)。前两个阶段决定了你的应用“能不能用”,后三个阶段决定它“能不能一直用得稳”。
很多人一上来就学Agent、学多模型协作,这没问题,但前提是对单次对话的完整生命周期有清晰认识。所谓“从零开始”,本质上是把这条链路每个环节都亲手走一遍,而不是直接跳到最后一步。
1.2 学习路径规划与技术选型逻辑
我的建议是“先窄后宽”。具体路径如下:
- 第一步:直接用最朴素的代码调用大模型接口,不套任何框架,理解请求和返回。
- 第二步:手动实现一个Prompt模板渲染器,理解上下文拼接。
- 第三步:做一个最小RAG,理解文档切分、向量检索、回答生成的闭环。
- 第四步:做一个带循环的Agent,理解工具调用。
- 第五步:再回头看LangChain、LangGraph等框架,你会发现它们只是把这些步骤封装了。
技术选型上,有几个经验值得说。运行时语言首选Python,没有悬念,因为大模型生态绝大多数以Python为中心;但如果你做的是面向内部的小工具,Node.js也不是不行,只是后续想要接评测、监控等生态时选择面会窄不少。
模型接口方面,建议从一开始就对接OpenAI兼容协议。原因很简单:现在无论是国外闭源模型、国内开源模型,还是本地部署的vLLM服务,大多都支持这种风格,这样你的代码可以在不同模型之间平滑切换,避免被单家供应商栓死。
部署环节,本地实验推荐Ollama或LM Studio这类工具,快速跑通验证;正式服务建议用vLLM,吞吐量大且显存控制好。框架层面,LangChain适合快速验证,但真正进入生产我还是更建议自己写轻量封装,可控性会强很多。
2. 核心细节解析与实操要点
2.1 Prompt设计不是“写话术”,而是“设计上下文窗口”
我见过太多人把Prompt工程理解成“文字游戏”,其实是跑偏了。Prompt设计的本质是对模型上下文窗口的资源分配。
一个可控的System Prompt,我会拆成四个区域:角色定位区、任务描述区、约束条件区、输出格式区。角色定位告诉模型“你以什么身份回答”,任务描述告诉它“你要做什么”,约束条件区告诉它“哪些不能做”,输出格式区则规定“返回的JSON该长什么样”。四个区域分开维护,比写一大段自然语言好改得多。
几个具体参数的经验值:Temperature,普通问答调到0.7左右,提取或分类任务就调到0.1甚至0;Top-P尽量保持默认或配合Temperature一起调,不要两个同时大幅改动。max_tokens是我踩过坑的地方,生成结果被截断往往不是模型问题,而是这个值设小了。
Few-shot示例不是越多越好。我测试过同一任务,3个示例和8个示例的效果差异并不大,但Token开销增加了近两倍。示例质量比数量重要得多,关键是覆盖边界情况。
2.2 Agent循环的关键:控制、记忆与失败恢复
Agent本质上是把“调用模型”从一次变成了多次。模型每轮输出可能是一个动作指令,系统执行这个动作后把结果反馈给模型,再让模型做下一步决策。
这个循环里最大的坑是“死循环”。我建议一定要在代码层面做两层保护:第一层是最大迭代次数,10次就足够;第二层是“观察结果不再改变判断”时主动终止。
工具调用的设计,核心是函数定义。如果你用的是OpenAI兼容API,函数定义其实是一段JSON Schema,要写明参数名、类型、必填项。这里有个容易忽略的细节:工具描述要写清楚“什么时候用这个工具”,模型是靠描述来做选择的。
记忆管理更复杂一些。简单场景,直接维护一个消息列表,超过窗口长度就裁剪最早的对话;复杂场景,就要引入“摘要记忆”机制,把旧对话的关键信息浓缩后保留。向量记忆听起来高级,但实际工程里它的定位是“检索过去的对话片段”,不是所有项目都需要一上来就上向量库。
3. 实操过程与核心环节实现
3.1 环境准备与最小对话接口实现
我不喜欢讲太多空理论,直接放一段最朴素的代码。用FastAPI写一个最小的对话接口,不套任何框架:
from fastapi import FastAPI, Request from openai import OpenAI app = FastAPI() client = OpenAI(base_url="http://localhost:8000/v1", api_key="EMPTY") SYSTEM_PROMPT = """ 你是项目助理。回答用户问题时,必须遵循: 1. 只回答与问题直接相关的内容; 2. 使用中文; 3. 如果不知道答案,直接说“不知道”,不要编造。 """ @app.post("/chat") async def chat(req: Request): payload = await req.json() messages = [{"role": "system", "content": SYSTEM_PROMPT}] messages.extend(payload.get("messages", [])) resp = client.chat.completions.create( model="qwen2.5-7b-instruct", messages=messages, temperature=0.7, max_tokens=1024 ) return {"reply": resp.choices[0].message.content}这段代码的重点在于:base_url指向本地模型服务地址,api_key先随便填,模型名按你部署的实际名称来。部署方式可以直接用Ollama跑一个Qwen2.5 7B(量化版),单张消费级显卡也能跑起来,或者直接用vLLM起一个服务。
先跑通这个接口非常重要,因为它验证的是“你的环境能完整执行一次模型推理”。我见过很多人后面排查了半天,最后发现是模型服务本身就没起好,所以务必先确认这一步。
3.2 从原型到工程化,我加的几个关键模块
跑通最小接口后,我给自己列了一个“工程化改造清单”,每完成一项,系统的稳定性和可维护性都上一个台阶。
第一项是结构化输出。不再让模型自由发挥文本,而是强制它返回JSON:
resp = client.chat.completions.create( model="qwen2.5-7b-instruct", messages=messages, tools=[{ "type": "function", "function": { "name": "answer", "description": "结构化输出最终答案", "parameters": { "type": "object", "properties": { "summary": {"type": "string", "description": "回答摘要"}, "keywords": {"type": "array", "items": {"type": "string"}} }, "required": ["summary", "keywords"] } } }], tool_choice={"type": "function", "function": {"name": "answer"}} ) result = json.loads(resp.choices[0].message.tool_calls[0].function.arguments)这个办法比“在Prompt里说请输出JSON”要可靠得多。我实测下来,结构化输出模式能把解析失败率从5%降到0.1%以内。注意要先用输出格式区定义结构,再用工具调用约束模型必须走这个结构。
第二项是缓存层。用户问题如果是重复的,直接命中缓存返回历史答案,可以省下大量Token成本。我用的方案是Redis,key设计规则是“模型名+prompt哈希”,TTL按场景设置,知识库问答场景可以设24小时,对话场景里缓存只用于短问题。
第三项是评测与回归。在线跑多少都有风险,所以准备30~50条代表性测试问题和对应参考答案,每次改动Prompt后批量跑一遍。评估用“LLM-as-judge”模式,让一个固定的评估模型按标准打分,效率高而且指标统一。
4. 常见问题与排查技巧实录
我把自己实操中遇到的典型问题整理成一张速查表,方便你现场排查:
| 现象 | 常见原因 | 处置建议 |
|---|---|---|
| 输出总是被截断 | max_tokens设置过小 | 调大,或改用最大长度的75%预估计 |
| JSON解析经常失败 | 模型输出受Prompt自由发挥影响 | 改用工具约束的结构化输出 |
| 检索回答不准确 | 文档切分粒度不合理 | 尝试按小标题切块,重叠区设为100字符 |
| Agent循环不结束 | 缺少最大迭代限制 | 加循环上限,同时检测动作是否重复 |
| 历史对话丢失 | 上下文超出窗口被清理 | 改用摘要机制保留关键信息 |
| 响应延迟高 | 模型未量化或并发限制 | 换量化版模型,或用vLLM部署 |
| 相同问题答案不同 | 缓存缺失或失败 | 确认缓存命中策略,加参数归一化 |
这里有几个具体避坑心得,都是文档不会写给你的。
第一,不要一上来就想着微调模型。我遇到的绝大多数场景,靠优化Prompt和检索逻辑就能解决。微调更适合“固定风格、固定格式、固定知识字典覆盖”的任务,成本很大,而且数据集构建、迭代流程特别长。
第二,数据质量决定了AI工程的上限。你检索用的是RAG,那么检索源文档的质量直接决定回答质量。如果文档本身写得含糊、前后矛盾,那模型再强大也没用。我花了大量时间清洗知识库文本,最值得做的事就是拆结构、去重复、补上下文。
第三,延迟优化通常不是从换模型开始的。先用日志看一下耗时分布,如果是模型本身慢,再考虑换小模型或量化;如果是检索慢,那才需要优化Embedding或索引结构。不要一上来就上高端方案,先定位瓶颈。
第四,成本估算要提前做。按三个维度估算:单次会话平均Token数、预计峰值并发数、每个用户每天平均会话数。这个估算决定了你后面选什么规模的模型、要不要加缓存,甚至要不要用省Token的技术方案。
第五,日志要尽早规范。我在早期忽略了这里,后面调试RAG和Agent时吃尽苦头。后来定下规范:每次请求记录消息数、Prompt总Token数、返回Token数、耗时、模型名、是否走了缓存。这样出了问题翻日志就能定位,不需要猜。
5. 关于Agent协作、AI测试与持续演进的一些思考
走到多Agent协作这一步,我的体会是“多Agent不是银弹”。两到三个角色分工协作是有意义的,比如“检索员”和“回答员”分开,各司其职,既保证知识引用独立,又不互相干扰。但如果把任务拆得过细,Agent间通信开销会陡然上升,延迟增加,而且调用链路越长,失败可能性指数级增长。
AI测试开发和传统软件测试的差异,也值得聊一下。传统测试是断言“输入固定,输出固定”,但大模型应用天然是概率性的。所以我的测试策略变成了三个层次:功能性测试(输出里是否包含必要要素)、格式性测试(JSON结构是否合法、字段是否完整)、质量性测试(用另一套模型打分评估)。这样既能兜底,又能兼顾灵活性。
最后再分享一点我个人的体会。从零开始做AI工程,最有价值的收获不是学会了某个具体框架,而是建立了一种“调试心智”:模型输出不对,不要急着换模型,先拆解问题出在哪一个环节——是Prompt不清晰、检索给的材料不对、还是结构化输出没做好。这套拆解问题的能力,比什么都值钱。
AI迭代的速度确实很快,但底层逻辑是稳定的:上下文管理、接口协议、评估闭环、成本控制。把这些基本功练扎实,换什么模型、什么框架,你都能快速适应。