作为从传统研发转过来的工程师,我第一眼看到"ai-engineering-from-scratch"这个标题时,心里其实打了个问号。过去一年里,市面上关于AI开发的讨论很多,但绝大多数内容要么停留在"猜Prompt"的层面,要么是某个框架API的快速上手手册。真正能回答"如果今天我要从零开始构建一个AI工程系统,应该怎么设计、怎么选型、怎么保证质量、怎么防坑"的内容,反而是稀罕东西。我带着这个问题,把自己过去一年从玩具项目到生产系统的经历重新复盘了一遍,整理出这篇偏向实战的笔记,希望能给正在入局或准备入局的同行一些参考。
1. 首先得搞清楚:AI工程和普通软件开发到底差在哪
很多人第一次接触AI工程时,会想当然地把它理解成"给大模型写Prompt然后用HTTP包一层API"。表面看确实像,但一旦进入生产环境,差异会迅速显现出来。最核心的一点是:传统软件的逻辑是人写的、可预期的,而AI应用的行为是模型推断出来的、概率性的。这意味着你没法用"单元测试覆盖率高=质量好"那套思路来交付一个AI功能,必须重新建立一套工程方法论。
1.1 传统研发的确定性,在这里彻底失效
我举个例子,做个电商订单系统,你给"用户下单"这个函数传入合法的用户ID和商品ID,返回值是确定的:要么成功要么失败,边界条件事先可以枚举。AI工程则完全不是这样。你给大模型一段用户问题、几份参考文档和一条系统提示词,今天返回的结果和明天返回的结果大概率不一样,哪怕Prompt一行都没改。模型版本升级、推理参数波动、甚至上下文里的标点符号,都可能让输出漂移。
这就带来了一个很现实的转变:AI工程的第一性原理不是"精确控制",而是"约束不确定性"。你没有办法让模型永远输出对的内容,但你可以通过工程手段把错误概率压到可控范围,并在出错时快速发现、有效兜底。这个思维转变,是很多从传统研发转过来的工程师最难适应、也最需要尽早迈过去的一道坎。
我看到不少团队在立项时兴致勃勃,觉得接入GPT-4级别的模型后产品就"智能"了,结果上线第一天就翻车:模型把用户的退单指令理解成下单,客服那边炸了锅。原因很简单,他们拿传统软件的设计习惯去套AI应用:没有输出校验、没有意图兜底、没有人工确认环节。所以这篇文章不想急着罗列框架和API,而是先把底层的工程认知掰扯清楚。
1.2 AI工程的最小闭环,远不止"调接口"
要构建一个合格的AI工程系统,我认为至少需要跑通这样一个最小闭环:场景定义、上下文工程、模型调用、结果校验、反馈回流。它不是一条从A到B的直线,而是一个持续迭代的环。
场景定义解决"这个功能到底要解决什么问题";上下文工程解决"模型需要哪些信息才能正确回答";模型调用解决"用哪个模型、什么参数、怎么省钱";结果校验解决"模型输出怎么保证格式合法、内容不越界";反馈回流解决"线上的坏案例怎么变成下一轮的训练或Prompt优化素材"。
这个闭环里,任何一环缺失,系统都撑不了多久。现实中我看到最多的缺失是"反馈回流"。很多团队做完前四步就急着上线,发现模型回答质量下降后也无从下手,只能靠人工一条条看日志,效率极低。而在生产环境里,一个完善的评测和回流机制,往往决定了一个AI项目能活多久。
2. AI工程的核心技术栈:不是单点能力,而是整套配合
聊到具体落地时,很多人会急着问"用LangChain还是LlamaIndex""微调还是RAG"。这些问题都指向了同一个真相:AI工程根本没有银弹,它是一个由多个能力件组合起来的系统。下面这四块是我认为"from scratch"构建AI工程绕不开的基础件。
2.1 Prompt工程:从玄学变成可迭代的工程工艺
Prompt engineering可能是AI工程里最被高估也最被低估的部分。说它被高估,是因为满屏都是"万能提示词模板""五个技巧让GPT输出完美答案";说它被低估,是因为大多数人只把它当"写提示词",而不是当成一套上下文结构协议来设计。
我在生产项目里的做法是,把Prompt拆成固定的元信息区(角色、任务目标、输出格式)、动态拼接区(用户输入、检索命中片段)、约束边界区(禁止行为、兜底话术、引用规则)三个段落。这么做的好处是,每次模型输出出问题时,我能快速定位是角色没立住、还是上下文污染了、还是边界没有约束紧,而不是对着一个600字的大杂烩反复猜。
举个例子,我要做一个客服系统的意图识别模块。第一版Prompt是"判断用户意图,输出JSON",结果模型在模糊场景下摇摆不定。迭代后我把输出层拆成"意图编码、置信度、需要补充的槽位、兜底应答建议"四个字段,并且对每个字段写清楚取值边界。模型的表现立刻上了一个台阶,不是因为提示词更"华丽",而是把原来隐式的任务,显式拆解成模型容易遵循的结构。
可以给一个精简的Prompt工程模板作为参考:
## 角色 你是一名资深客服助理,负责识别用户意图并返回结构化结果。 ## 任务 判断用户本次消息的真实诉求,归类到以下意图之一:[退换货, 物流查询, 价格咨询, 投诉建议, 闲聊] 如果意图不明确,输出 uncertain。 ## 输入格式 用户消息:{user_message} 对话历史:{chat_history} ## 输出格式 严格输出 JSON,字段如下: {"intent": "...", "confidence": 0.0~1.0, "missing_slot": "...", "reply_suggestion": "..."} ## 约束 - intent 只能从给定枚举中选一个 - confidence 低于0.6时必须把 intent 设为 uncertain - 不要编造不存在的订单信息这个Prompt的核心价值不在于"写得好",而在于它把输出变成了可解析、可校验、可降级处理的结构化数据。我见过太多团队栽在"让模型自由发挥"上,自由发挥在Demo里很惊艳,在生产里就是灾难。
2.2 RAG的胜负手在检索质量,而不在模型本身
RAG(检索增强生成)已经成了AI工程里的标配,但很多人的认知还停留在"切文档、向量化、塞进向量数据库、召回TopK丢给模型"这四步。这个流程没错,只是太粗糙。真正决定RAG效果上限的,是"切分粒度是否匹配业务语义"和"召回内容是否精准命中答案所在片段"。
切分这件事,我踩过很深的坑。一开始图省事,按固定512个字符硬切,结果一段用户手册里"保修条款"被拦腰斩断,模型拿到的是半截子话,回答自然前言不搭后语。后来改成按语义块切,结合标题层级和段落边界,命中率提升非常明显。实测下来,切分策略对回答质量的提升,比换一个更强的模型还明显,这一点很多刚接触RAG的人很难第一时间体会到。
召回的优化也不能只依赖向量相似度。我的经验是"向量召回+关键词召回+重排序"三件套才是可生产的最小配置。纯向量召回在语义相近但表述不同的场景下会漏掉关键命中,加一层BM25关键词召回做互补,再用交叉编码器模型(比如bge-reranker)对候选集重新排序,最终的TopK片段质量会稳很多。这套组合比单靠一个向量库多不了几次调用,但效果差距肉眼可见。
| 方案 | 召回精度 | 延迟 | 实现成本 | 适用场景 |
|---|---|---|---|---|
| 纯向量召回 | 中低 | 低 | 低 | 内部知识库Demo、低并发原型 |
| 向量+关键词 | 中 | 低 | 中 | 文档类型单一、对精度要求尚可 |
| 向量+关键词+重排 | 高 | 中 | 中高 | 客服、医疗问答、法律检索等精度敏感场景 |
2.3 Agent编排:先从小闭环做起,别一上来就画巨型流程图
Agent是热搜词里的当红炸子鸡。但在生产环境里,我见过太多"为了Agent而Agent"的设计:让模型自主决定调用十几个工具,运行链路动不动几十步,结果模型在第五步就迷路了。真正务实的做法是:给Agent一个最小可用循环——理解任务、检索可用工具、执行动作、观察结果、决定下一步,并且每一步都要用代码做硬约束,不能全靠模型自觉。
我做过一个需要多步操作的内部工单处理Agent。第一版设计是开放所有工具给模型自由调用,结果模型经常在"查余额"和"生成报价单"之间反复横跳,浪费token还出错。后来我把工具调用权限按角色划分:普通查询Agent只能读,不能写;只有审批Agent能调用写操作,且写操作前后必须输出明确的变更说明。这个约束一加,稳定性立刻上来了。
所以对于Agent,我的建议是:先用子Agent完成单一任务、任务之间用代码编排,再逐步扩展到"计划-执行-反思"的复杂循环。每一步都要记录"模型看到了什么、决定做了什么、结果是否符合预期",这个trace日志是后期排查问题的重要依据。Level越高的自主性,需要的约束和观测能力就越多,别让模型承担它不该承担的决策责任。
2.4 评测体系:没有评估标准,一切优化都是空谈
评测是AI工程质量的根本保障,也是最容易被忽略的部分。很多团队上线了功能才发现"感觉变笨了",但拿不出任何数据支撑,只能靠感觉去调Prompt,调完也不知道是变好了还是变坏了。
我从实际项目中得到的经验是,AI工程的评测体系至少要分三层:单测级(给定固定输入,断言输出格式和核心内容是否满足要求)、场景级(一批覆盖典型业务路径的测试集,跑完整流程看成功率)、线上抽检级(从生产日志里随机抽样本,人工标注后观察指标趋势)。这三层不是替代关系,而是层层递进。
场景级的测试集建设,我会把常见的失败案例按类型分组放进回归集里。比如客服系统里,"金额计算""时效承诺""退换货政策"各建一组,每改一版Prompt或检索策略,就跑一遍全套回归。一次全绿,再放线上。这句话说起来简单,真正做到位的团队,在行业里其实不多,但恰恰是这些团队能持续迭代出质量更高的产品。
3. 从零搭建一个AI工程项目的完整实战路径
前面讲的都是底层认知和技术栈拆解,这一节进入具体操作层面。我以构建一个"基于企业知识库的智能问答系统"为例子,走一遍从设计到落地的完整过程,顺便把环境准备和代码骨架一起给出。
3.1 环境准备:选型和安装里最容易忽略的细节
项目开始的第一步是拉环境。我推荐的起步组合是Python 3.10+、LangChain或LlamaIndex(用于编排)、Chroma或Milvus(用于向量存储)、FastAPI(用于服务封装)。之所以选这一套,是因为它们的社区活跃度最高,遇到问题更容易找到解决方案,对新手相对友好。
环境准备最常被忽略的,是向量化和模型调用的版本兼容问题。装依赖时不要无脑装最新版。我自己的习惯是先锁定大版本,再根据自己的模型服务商SDK版本做兼容测试。比如OpenAI的openai包从0.x升到1.x时接口变动很大,不少项目就是升级后一夜之间全线报错。建议所有依赖写进requirements.txt或者pyproject.toml里,并且锁死版本范围,别给线上部署留隐患。
另外强烈建议做好密钥管理:不要把API Key硬编码在代码里,更不要提交到Git仓库。用环境变量或者密钥管理服务(比如Vault、KMS)来处理。这个坑几乎每个AI工程师都踩过——GitHub的爬虫会扫描公开仓库里的密钥,你的账单不出三天就会多出几位数。
3.2 系统骨架:把上下文工程、模型调用和检索层拆开
一个可维护的AI工程系统,第一原则就是分层。我习惯至少拆成三层:数据接入层(负责文档解析、清洗、切分)、检索与上下文组装层(负责向量化和召回拼接)、模型调用与校验层(负责Prompt渲染、LLM推理、输出解析)。
拿知识库问答来举例:
# retrieval.py - 检索与上下文组装层 from langchain_community.embeddings import OpenAIEmbeddings from langchain_community.vectorstores import Chroma def build_retriever(docs): # 先对文档做语义切分,再向量化和入库 embeddings = OpenAIEmbeddings(model="text-embedding-3-small") vectorstore = Chroma.from_documents(docs, embeddings) return vectorstore.as_retriever(search_kwargs={"k": 5})这层最不该做的事就是把业务逻辑写进去。检索层只负责"根据问题找到最相关的片段",至于"模型该怎么组织答案",是下一层的事。把职责理清楚,后续替换组件时才能不动筋骨。
模型调用层我习惯封装一个统一的函数,把系统Prompt、用户问题、检索片段拼接好,然后调用LLM,对输出做JSON解析。一旦解析失败,要有重试和降级逻辑,不要直接让整个服务崩掉。
# llm.py - 模型调用与输出校验层 import json from openai import OpenAI client = OpenAI() def call_llm(system_prompt, user_content, fallback="我暂时无法回答这个问题。"): try: resp = client.chat.completions.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_content} ], temperature=0.2, response_format={"type": "json_object"} # 强制JSON输出,强烈推荐 ) parsed = json.loads(resp.choices[0].message.content) return parsed except Exception as e: # 打日志、走降级策略,而不是静默失败 logger.error(f"LLM调用失败: {e}") return {"answer": fallback, "error": str(e)}这里有两个细节,我吃了不少亏才意识到有多重要。第一是response_format,如果你的模型服务商支持强制结构化输出,一定要用,这能让后面解析层的故障率下降一大截。第二是temperature,问答类场景我通常调到0.2以下,减少无意义的花式表达;创意生成类场景再调高到0.7以上,两者完全不同。
3.3 第一个可用的Demo之路:先跑通端到端,再谈优化
入门的最大敌人是"完美主义"。我见过很多新手花了两天研究用什么向量数据库、什么Embedding模型、什么微调方案,结果一行代码都没跑起来。我的建议是:先用最简路径跑通端到端——加载PDF、切分、向量化、检索、问一个最简单的问题、看到模型输出。哪怕回答质量一般,这个过程你已经完成了AI工程的60%。
跑通之后再逐项优化:先优化切分策略,再看召回命中率,再调Prompt结构,最后才考虑是否微调或换更强的模型。这样每一步都有测试集把关,也能清楚知道每次改动到底带来多少提升。反过来,一上来就上全套大而全的架构,出了问题反而不知道从哪查起。
3.4 model selection:根据需求选模型,而不是只追最强
选模型这件事,我见过两种极端:一种是无脑用最强型号,不管什么任务都上最贵的;另一种是盲目追求轻量级,结果效果没法看。理性的做法是根据任务的复杂度和容错率来安排。
客服问答、信息提取这类任务,我一般用中等档位的模型(类似GPT-4o mini或Claude Haiku级别)就够,速度快、成本低。对复杂推理、代码生成、长文本总结这类任务,再动用高档次模型。更精细的做法是"路由分发":先用一个轻量模型做意图分类,简单问题走便宜模型,复杂问题再升级,整套下来成本能省40%以上,效果还不掉链子。
我做一个工具实测过成本对比,同样是100万次客服问答调用,用最强档模型的花销大约是使用中等档位加路由分发方案的9倍,而用户满意度几乎持平。数字背后说明的不是模型不重要,而是用对地方比用贵的更容易带来工程收益。
4. AI工程的质量关卡:可观测和评测体系是保命底牌
跑通Demo不难,难的是让系统在生产环境长期稳定运行。这一节我把AI应用上线后最关键的几个质量关卡展开讲讲,这些都是传统研发里没有、或者说很少去重仓投入的环节。
4.1 每个AI能力都要有"可控性设计"
所谓可控性设计,就是给系统加上各种制动阀,让模型在不该"过分自由"的时候能被强行拉回来。具体落地包括:输出模式限定(JSON Schema校验)、设定不确定性阈值(低置信度时走人工兜底)、敏感操作要二次确认(不能模型说改就改)、在关键链路上加入代码断言。
比如在线客服系统里,模型给出的回复如果带出个人信息或内部数据,必须被代码拦截并替换为通用话术。这个不是靠Prompt里写"不要泄露数据"就能保证的,必须在系统层面用正则、白名单或者语义过滤做一道硬校验。我见过一个真实的翻车案例:模型在调试日志里读到了内部数据库地址,然后在一个正常回答中把这串主机名带了出来,完全没有违规意图,就是上下文里看到了就顺手写上了。读日志和输出校验是两码事,出问题的一定是缺了后者。
在代码里,我习惯给每个可能出错的环节都设置降级路径:
def generate_safe_answer(query, context): parsed = call_llm(system_prompt, query, context) # 安全过滤与格式断言 if not parsed.get("answer"): raise ValueError("模型未返回答案字段") if contains_sensitive_info(parsed["answer"]): parsed["answer"] = "抱歉,我暂时无法提供相关信息。" return parsed4.2 可观测性:AI日志比普通日志多一个维度
传统系统的日志关注"请求参数、状态码、耗时、异常栈"基本就够了。AI系统的日志还必须多一个维度——内容。你需要记录模型实际收到了什么上下文、生成了什么内容、用户对结果的反馈是什么。没有这些,线上问题你连复现都做不到,因为模型是概率性的,同一个输入第二次跑可能结果完全不同。
我目前的生产日志至少会包含这些字段:请求ID、会话ID、用户问题、命中片段ID列表、Prompt版本号、模型版本、推理参数、原始输出、解析后输出、耗时、token消耗、是否走了降级路径。这些数据不只能排查问题,还能沉淀成评测集和优化素材,一鱼两吃。
另外一个容易被忽视的是版本管理。Prompt不是写一次就完事,它和代码一样需要版本化。每次修改Prompt,都建议连同对应的评测结果一起记录。我习惯在路上每个功能的Prompt维护一个变更表,记下"改了什么、为什么改、评测集跑分变化、线上效果观察"。否则三个月后你根本不知道当前Prompt为什么会变成这副样子。
4.3 LLM评测体系的搭建顺序和实际案例
评测体系的搭建,我建议从"抠细节的单元评测"开始,而不是一上来就搞复杂的人工标注平台。第一步,准备30-100条固定问题,覆盖每个核心功能点,写好期望输出规则;第二步,写一个评测脚本,自动跑完这批问题,输出通过率;第三步,每次改动后先跑脚本再决定要不要上线。
等团队熟悉这个流程后,再引入线上抽检和人工标注打分。模型输出的质量有时比传统软件的接口返回更难量化,所以评分标准我会尽量落到可观测的行为上,例如"是否引用了正确文档""是否包含错误日期""是否推卸责任"等,越具象越好。
拿一个实战数据来说,我做一个内部AI助手时,第一版评测通过率只有62%,看着很差。但正是因为有这62%的基线,之后每次改Prompt、换切分策略、调重排序,都能明确知道提升了几个点。经过三轮迭代做到87%,当时再上线心里就有底多了。没有基线的优化只能叫碰运气,有基线的优化才叫工程。
4.4 成本控制:用工程手段把token花在刀刃上
大模型API的计费逻辑决定了AI应用的成本和token消耗强相关。成本控制不是上线后再优化的,而是架构阶段就要考虑。常见的有效手段包括:使用缓存(相同的用户问题命中缓存后直接返回历史答案)、优先用小模型做初筛、控制上下文长度(别把不相关的内容一股脑塞给模型)、对长文档做摘要预处理再让模型回答。
我实测过,接入语义缓存后,重复问题占比高的客服场景,API成本能降40%左右,而用户几乎感知不到差别。另外,上下文长度的控制也很有讲究。很多团队觉得"能塞的都塞进去,模型能处理",却忽略了每一轮对话都在为超长上下文付费,而且过长上下文还可能引入噪音、降低回答准确率。我的习惯是:先做召回过滤,精选Top5片段拼接,控制总长度在3000字以内,再让模型回答,省钱且效果更好。
5. AI工程化的下一步:从个人技巧到团队协作范式
聊完了技术细节,最后想谈谈更宏观层面的东西。AI engineering发展到现在,已经不只是一个人的"工程技巧",而是在逐步沉淀为一套团队协作范式。这个转变是否顺畅,往往决定了一个组织能在这个领域走多远。
5.1 harness engineering是什么:给模型能力和团队协作戴上缰绳
热搜词里出现的一个概念“harness engineering”值得认真对待。直译过来是"约束工程"或"缰绳工程",它强调的是:大模型的能力再强,也必须被套进一套可控的框架里——包括明确的输入输出协议、内部工具的访问边界、责任审计和熔断机制。一个有具体场景的AI系统,不只是"模型经过编排后的产物",更是一套组织能力沉淀的流程。
我在团队里推行这套理念时,落地成三样具体的东西:功能请求模板、模型变更评审表、上线前的检查清单。功能请求模板要求任何人都能说清楚"这个功能的用户场景是什么、当前方案的缺陷是什么、模型参与哪一步决策、失败时如何兜底";模型变更评审表用来记录模型版本升级前后的评测对比;上线检查清单则纯粹是把可控性设计里那些"容易忘的硬约束"全部列在上面,逐项打勾。
这些流程看着繁琐,但真正救过我一次。当时我们把一个核心模型从旧版升到新版,上线前跑一遍评审表发现新版在"时间表达理解"上误判率明显升高,差一点就带着这个隐患直接上了线。没有评审流程,这种回归问题靠肉眼很难提前发现。
5.2 与AI编程工具/测试开发环节的协同
AI工程化离不开研发环节本身的效率提升。现在行业内已经有大量AI编程辅助工具,能帮工程师自动生成夹具、生成单元测试、解释报错信息等。我的体会是,这些工具的核心价值不是"替你写代码",而是帮你缩短"从想法到验证"的反馈循环。简单的函数、样板代码、数据结构定义这类基础工作,AI生成后人工确认,效率提升非常明显。
但AI编程工具要辩证去看。生成代码的质量取决于描述质量,而描述质量又取决于工程师的抽象能力。我见过同事让AI写两百行代码,结果最后删掉重写的也不少。关键是要把AI定位成"结对搭档",而不是"外包工人":你负责拆解需求和把关质量,它负责加速打字和列举方案。想清楚人和模型的分工边界,AI才能真正成为研发流程的杠杆,而不是新负担。
在AI测试开发环节也一样,用大模型生成测试用例和测试脚本写得很欢,但这些样本必须经过严格的人工审核和场景拟合。尤其涉及边界条件和异常注入时,大模型生成的用例往往太平滑、太理想,对真实环境里的脏数据缺乏想象力。正确姿势是拿线上真实日志里的坏案例去喂测试集,而不是依赖模型凭空想象。
5.3 从Demo文化到工程文化的转变
最后想聊一个文化层面的坑。很多团队做AI项目时,习惯于"拿着Demo去汇报",因为Demo天然适合讲故事。但Demo文化一旦占据主导,会让团队忽视那些最不性感但最重要的工程环节:评测集建设、可观测性、安全过滤、成本控制。
我自己的感受是,AI工程文化的核心不是"把AI用起来",而是"让AI用得住"。用起来只需要一个下午和一次API调用,用得住则需要一整套从设计、开发、评测、部署到监控的体系。这套体系不会让Demo更惊艳,却能让项目活过半年、一年,而不会被一次线上事故打回原形。
回到标题"ai-engineering-from-scratch",我的看法是:从零开始构建AI工程,真正的起点不是某个框架的安装命令,而是你对"约束、评测、观测、成本"这四个词的理解有多深。技术栈会持续迭代,模型会更聪明,但这套工程底座,会一直是你做AI应用最值钱的家底。