做了几年AI应用落地我越来越觉得很多人对“ai-engineering”的理解是偏的。大家以为AI工程就是从零训练一个模型或者把ChatGPT套个壳接上API就完事但真正把标题里“from-scratch”这个状态走一遍之后你会发现自己进入的是一条完全不同于传统软件开发、也不同于算法调参的第三条路。这篇内容我围绕“ai-engineering-from-scratch”这条主线展开把从零开始搭建一个可用AI应用的关键环节拆开讲清楚环境怎么搭、模型怎么选、Prompt怎么工程化、RAG链路怎么落地、微调和评估什么时候值得做、部署上线要盯住哪些指标以及我踩过的那些坑。适合正在入门AI工程、准备把项目从实验阶段推向生产环境的开发者参考也适合已经在写业务代码但想系统性理解AI应用开发逻辑的朋友。1. 先想清楚再做AI工程和传统软件开发到底差在哪1.1 “从零开始”不是指Python零基础而是思维方式的归零如果你已经在读这篇文章大概率代码基础是有的工程能力也不差真正缺的是一个能指导你从头把一个AI应用落地的完整框架。“from-scratch”在这里指的是不依赖某个现成平台的拖拽界面不靠别人封装好的“智能体模板”拼拼凑凑而是从选模型、写第一个调用代码、设计数据流、加评估、做监控这条链路自己完整走一遍。我见过很多传统后端工程师第一次接触AI工程时的状态他们习惯了一切都是确定的接口文档写明了输入输出数据库事务要么提交要么回滚逻辑错误可以靠断点一步步查出来。但到了大语言模型这里输出的正确性变成了一个概率问题。同一个Prompt温度调到0可能稳定一些但只要模型版本一升级、上下文稍微变化回答就可能漂移。这种“不确定性”会让很多工程师极其难受。所以AI工程的第一步不是学某个框架而是接受一个现实你面对的是一个概率系统你要做的是把概率系统的行为约束、度量、控制在可接受的范围内。1.2 为什么要强调工程化能力验证与落地门槛很多人只停留在“调API写Prompt”的阶段觉得自己已经会做AI应用了。但一个真正从零做起来的AI项目要过的关卡远不止这些模型输出不稳定怎么设计Prompt和解析逻辑才能保证下游不崩知识库内容动辄几百个PDF怎么切分、怎么向量化、怎么检索才靠谱线上跑起来之后效果变差了你靠什么指标发现它变差用户量上来之后API费用和推理延迟怎么平衡这些问题单独拿出来都不算难但在一个项目里全部串起来就是AI工程和“写个Demo”之间的本质区别。工程化的本质是让系统在无人干预的情况下稳定运行而AI应用的不确定性决定了它比传统系统需要更多的“护栏”和“度量”。我后面讲的每一部分本质上都是在给不确定性加上约束。2. 从零搭环境选型、依赖管理和第一个能跑的程序2.1 本地开发环境的具体配置建议先记结论AI工程现阶段最舒服的开发环境组合是 Python 3.11、UV依赖管理工具、.env 管理密钥、Jupyter只用来做探索实验正式代码一律用工程目录结构。Python版本建议直接用3.11或3.12。很多AI库对3.12的支持已经很成熟了3.11则是最稳的过渡选项。如果你机器上同时有多个项目强烈建议先把uv用起来。它比pip和conda快得多而且锁依赖的方式非常清晰。我现在的流程是项目根目录放一个pyproject.toml加依赖用 uv add openai uv add chromadb装完之后自动生成uv.lock提交到Git里团队其他人拉下来之后执行 uv sync 就得到完全一致的环境。密钥管理这块我见过太多人把API Key直接写在代码里提交到GitHub然后被爬虫扫走产生大额账单。正确做法是把Key放在项目根目录的.env文件里用python-dotenv或者pydantic-settings加载同时把.env加入.gitignore。工具层面用direnv的话还可以做到进入目录自动加载环境变量。硬件方面如果你只是调用API做应用层开发任何最近五年的笔记本电脑都够用。如果你要跑本地开源模型做推理测试Mac建议选带M系列芯片且统一内存至少16G的版本Windows/Linux则优先考虑NVIDIA显卡显存大于等于8G。但请务必记住在项目初期不要为了“本地模型”硬买显卡先用云端API把原型跑通验证商业价值之后再考虑推理成本优化。2.2 第一个可运行程序从Chat接口到工程化调用选哪个云的API作为起步我不过度展开。一个务实的选择标准是文档质量好、生态成熟、模型能力有代差优势。另一条路线是选开源模型比如通过云厂商的推理服务调用或者本地部署Qwen系列。起步阶段我建议你无所谓选哪家关键是先用起来感受完整链路。下面以最常见的OpenAI兼容接口为例演示一个工程上合格的调用方式而不是网上随处可见的那种无结构化的一次性脚本。import os from openai import OpenAI from dotenv import load_dotenv load_dotenv() client OpenAI( api_keyos.getenv(OPENAI_API_KEY), base_urlos.getenv(OPENAI_BASE_URL), ) def chat(messages, modelgpt-4o-mini, temperature0.3, max_tokens1024): try: resp client.chat.completions.create( modelmodel, messagesmessages, temperaturetemperature, max_tokensmax_tokens, ) return resp.choices[0].message.content except Exception as exc: # 工程化调用一定要把错误显式暴露而不是让程序裸奔 print(f[chat error] type{type(exc).__name__}, detail{exc}) raise if __name__ __main__: messages [ {role: system, content: 你是一位资深AI工程顾问回答要简洁且结构化。}, {role: user, content: 请用三句话说明AI工程和普通软件开发的核心区别。}, ] answer chat(messages) print(answer)这个例子看起来简单但里面已经包含了几条工程原则环境变量与代码分离、异常捕获与上抛、把temperature显式传入而不是靠默认值碰运气。你在后续开发里会不断给这个chat函数增加功能比如记录token用量、加上重试逻辑、支持流式输出。从零开始并不是一次性写一个完美框架而是先有一个“可用的地基”再不断生长出你需要的结构。2.3 跑通一个最小闭环后再谈框架我强烈建议前两周不要引入任何重量级框架包括那些野心很大的Agent框架。基础的事实是原生SDK 一个几十行的调用封装已经足以支撑你的第一个原型。把LangChain之类的东西放在这个阶段最大的风险不是学不会而是你搞不清楚错误到底是你自己的逻辑问题还是框架封装导致的排查成本会翻倍。跑通最小闭环的标志是什么是你写了一个脚本输入一个领域问题模型基于你的指令给出了格式符合预期的回答并且你把整个流程放进了一个干净的Git仓库里队友clone下来之后十秒之内就能运行。到这一步“从零开始”算是正式迈出了第一步。3. Prompt工程落地模板版本化与结构化输出3.1 模型选型的工程视角能力和成本的分层Prompt工程的前提是选一个合适的模型。很多人一上来就想用参数最大的旗舰模型但从工程角度看模型选型本质上是一个“能力-成本-延迟”的三角权衡问题。我给一个简化但不离谱的选型参考模型档位适用场景成本量级相对延迟表现部署复杂度旗舰大模型复杂推理、长文档总结、代码生成高中-高低API中型模型通用对话、信息抽取、RAG生成中低低API本地开源模型数据敏感、离线环境、高并发低延迟低算力另算取决于硬件高我自己的习惯是先默认用一款便宜且能力够用的中型API模型做原型把Prompt和流程调通之后再考虑是不是需要升级到旗舰模型。很多场景下问题不在模型不够聪明而在你的检索和指令没做好盲目升档只会让账单变难看。3.2 Prompt模板管理的实战要点代码里硬编码字符串、写几百个版本叫“final_v3_真的不改了.py”这个阶段我们都经历过。Prompt工程化的第一课就是版本化和结构化。我自己现在管理Prompt的方式很简单把Prompt拆成system、task、format、few-shot四个部分用字符串模板或专门的.md文件维护代码里通过读取文件加载。system描述角色和通用规则task描述当前任务的具体目标format定义输出的格式要求few-shot放两到三个示例。这样改Prompt的时候不需要动代码而且每个版本都能进Git做diff。一个非常核心的建议Prompt里凡是涉及“格式”的要求尽量给出正反示例。模型对“不要怎么做”的理解通常弱于“该怎么做”的正向示范所以与其反复说“不要输出多余说明”不如直接给一个期望输出的完整示例。3.3 结构化输出把概率系统接到确定性的工程链路上这是我从零实践时觉得收益最大的一步。如果模型返回的是自由文本你下游所有代码都要去解析字符串一天到晚处理莫名其妙的换行和缩进问题。一旦你用工具调用或结构化解码的方式让模型直接给你JSON整个链路就干净得多。这里用Pydantic加OpenAI工具调用做一个示例from pydantic import BaseModel, Field from openai import OpenAI import json client OpenAI() class AnalysisResult(BaseModel): summary: str Field(description一句话总结) risks: list[str] Field(description风险列表最多5项) confidence: float Field(description置信度0到1之间) def analyze(text: str) - AnalysisResult: resp client.chat.completions.create( modelgpt-4o-mini, messages[ {role: system, content: 你是金融风控分析助手。}, {role: user, content: f分析以下文本\n{text}}, ], tools[{ type: function, function: { name: submit_analysis, description: 提交分析结果, parameters: AnalysisResult.model_json_schema(), }, }], tool_choice{type: function, function: {name: submit_analysis}}, ) args json.loads(resp.choices[0].message.tool_calls[0].function.arguments) return AnalysisResult(**args) result analyze(某公司应收账款周转天数连续三个季度上升现金流承压。) print(result.summary) print(result.risks) print(result.confidence)这段代码的价值在于模型虽然还是概率输出但你的代码拿到的是一个经过Pydantic校验过的类型化对象。后续无论是存数据库、做规则判断还是渲染前端都不需要再处理字符串残渣。而且model_json_schema()这个方法让你不用手写JSON Schema字段描述也能直接传递给模型等同于一种轻量级工程约束。这里有一个容易踩的细节Pydantic的Field描述对模型生成质量的影响比很多人想象中更大。描述写得越具体模型输出的字段内容就越准确。如果你发现某些字段经常缺失或者格式不对先检查描述里有没有说清楚这个字段到底要什么。4. RAG为主的应用流水线检索增强的完整实现4.1 RAG到底是什么为什么“让模型读文档”这个朴素想法需要工程化大模型有知识截止日期而且无法天然知道你私有的业务文档内容。最开始大家想到的笨办法是把所有文档塞进上下文很快撞上两个现实一是上下文窗口有限塞不进去二是就算窗口够大塞满无关信息后模型注意力被稀释回答质量和速度都会变差。RAG检索增强生成的核心思路是把“大海捞针”变成“先定位到针再问你”。用户提问之后系统先从知识库里检索最相关的一小段内容再把这部分内容作为上下文交给模型生成回答。这个思路朴素但落地环节的坑远比想象的多文档怎么切、向量怎么算、检索结果怎么排序、检索不到的时候模型要怎么表现每一环都影响最终质量。4.2 文档加载与分块RAG效果的第一决定因素很多人以为RAG效果不好是embedding模型选得不行但我做过一组对照实验之后发现最影响检索精度的往往是你文档切分的方式。分块的目标是让每一段“语义自包含”。一段文本应该尽量完整地表达一个主题或一个论点而不是被硬生生拦腰截断。实操中我通常这样处理优先按文档结构切分比如Markdown的一二级标题、PDF的章节、表格的行组。如果文档没有明显结构用固定大小加重叠的方式兜底比如chunk_size512个token左右overlap50个token左右。每个chunk保留一句话级别的摘要作为metadata。这样在向量检索之后你可以先用metadata做粗筛再决定要不要取整个chunk。我吃过一个很实在的亏曾经把PDF按固定3000字符切切完之后很多段落前言不搭后语检索出来的内容看起来“相关”但信息不完整生成环节自然也就东拼西凑。后来用了“按标题层级切分兜底固定大小”的混合策略效果立竿见影。代码上加载PDF我用的是LlamaParse或者PyMuPDF普通文本直接用LangChain的分割器也行但如果你不想被框架绑定自己写一个按标题切分的函数也不难。核心是把“切分结果可追溯”也就是每一段都知道自己来自哪个文件的哪个章节这点对排查问题至关重要。4.3 Embedding与向量库选型不要追新追准Embedding模型负责把文本变成向量向量之间的语义距离决定了检索质量。选Embedding模型的核心指标不是你听说的那个榜单分数而是“在你的领域语料上检索准不准”。我自己实践中用的比较多的是OpenAI的text-embedding-3-small以及开源的bge-m3系列。后者优点是本地部署且多语言支持不错但维度更高存储和计算开销更大。向量库的选择可以按项目规模来场景推荐方案理由原型验证Chroma零部署pip安装就能跑适合几百上千条文档中等规模生产Qdrant或Milvus支持过滤、分布式、性能稳定已有PostgreSQL的团队pgvector少一个组件运维成本低适合几百万向量以下初学者最容易犯的错误是上来就搭一个专门的向量数据库集群结果发现数据量可能还不到十万条。小型数据量用Chroma或者pgvector完全够用省下的运维精力用来调检索质量更划算。4.4 检索优化从top_k到混合检索和重排第一次把RAG跑通的时候你会觉得“能回答”已经很爽了。但用上几天就会发现检索出来的chunk经常不是用户真正需要的那一段。检索优化的方向有三个top_k调整k太小容易漏k太大容易混入噪音一般先取10到20个候选。混合检索向量检索擅长语义相似关键词检索擅长精确命中专有名词和编号。两者结果做加权融合能显著提升召回。重排用更强的模型对候选段落做精细排序只取前3到5段进最终上下文。这个环节对最终回答质量的提升通常比换embedding模型还明显。举一个实际案例在技术文档问答场景里如果用户问“第3.2节中配置超时的参数名是什么”纯向量检索的效果经常不如“超时”“timeout”“3.2节”这几个关键词的精确匹配。混合检索可以同时兼顾两种信号而重排模型会倾向于把同时含有关键词和语义相关的段落排在前面。我自己的标准流水线是向量召回50条 关键词召回50条融合去重之后用重排模型选Top 5进上下文。这个流水线在延迟和效果之间的平衡最理想。下面是一个简化版的RAG检索核心流程用伪代码展示思路def search(query: str, top_k: int 10) - list[Document]: # 1. 生成query向量 query_vector embed_model.encode(query) # 2. 向量召回 vector_hits vector_store.search(query_vector, top_ktop_k) # 3. 关键词召回用BM25或数据库全文索引 keyword_hits keyword_store.search(query, top_ktop_k) # 4. 融合按加权分数合并 fused merge_hits(vector_hits, keyword_hits, alpha0.7) # 5. 重排用cross-encoder模型精排 final reranker.rerank(query, fused)[:5] return final重排这一步是RAG效果的关键我建议在这个环节花的时间比在其他任何单点优化上都多。所谓“加分项”要做在主链路上而不是在周边打转。5. 微调、评估与监控何时微调如何量化好坏5.1 微调不是默认选项三类情况才值得做从零开始做AI工程的人很容易陷入“我要微调一个自己的模型”的冲动。但实际经验告诉我绝大多数应用场景直接调API加Prompt工程加RAG就够用。微调的合理场景大致只有三类输出格式与风格需要高度固定。比如你要模型生成某种特定方言的客服话术、特定排版结构的报告。领域能力有特殊门槛。比如医疗术语、法律条款、代码规范要求极高通用模型容易犯错。成本优化驱动。比如用一个很小的模型微调后替代大模型完成同一任务能显著降低推理成本。如果决定微调那么技术栈建议走LoRA路线不要全参数微调。全参数微调的成本和存储开销都大得多而LoRA用很小的适配器就能实现大部分效果并且可以随时替换。5.2 数据准备微调成败的关键在数据质量微调的数据集质量比数量重要太多。宁可用300条高质量、手工校对过的数据也不要用30000条从网上扒来、给模型标注好的低质量数据。一套好的微调数据在结构上通常包含三列指令、输入、期望输出。数据里必须覆盖你线上真实会遇到的输入分布比如用户各种奇怪的表达方式、边界情况、拒答场景。我在微调实践里养成了一个习惯每个样本都写“为什么这样回答合理”。听起来很费时但正是这个行为让标注者不断确认自己的逻辑一致性而不是凭感觉给答案。数据一致性越高微调后的模型行为就越可控。5.3 评估体系没有度量就没有工程一个AI应用上线之后最怕的不是效果达不到100分而是你不知道它今天比昨天好还是差。我强烈建议任何从零开始的AI项目都要有一个“评估集加评估脚本”的最小体系。评估集建议至少100条覆盖不同场景的真实用户问题每条标注了期望的答案要点或评分标准。每次改动Prompt、换模型、调RAG参数之后都跑一遍评估脚本比较整体通过率。注意要用同一份评估集、同一个评估标准否则对比就没有意义。评估维度上我常用的几项如下评估维度说明量化方式忠实度回答是否完全基于提供的上下文有没有编造人工打分或LLM评判相关性回答是否针对用户问题而不是自说自话1-5打分格式通过率结构化输出是否能被Schema校验通过通过百分比端到端成功率用户问题是否能得到可用结果通过百分比在工具上LangSmith和Langfuse都支持评估集和追踪。我自己的做法比较朴素评估代码用pytest组织每个用例就是一个测试函数断言部分用LLM-as-judge或规则判断。这样每次提交代码之前跑一下pytest就能快速知道是否回归。5.4 上线后的监控指标不要只看成本很多人上线AI应用之后只看API账单这是一个容易踩的坑。账单超了代表用量大了但你可能完全不知道质量早就悄悄崩了。真正需要监控的指标至少有这些请求成功率、平均首token延迟、端到端延迟、每次请求的token消耗、用户的负面反馈率。建议这些指标全部接入日志平台并且设置告警阈值。有一件事要特别留意模型输出被截断和格式解析失败的次数。这两个指标一旦上升通常不是用户行为变了而是你的上下文变长或者模型版本有变化。这类问题要第一时间定位因为它直接影响用户体验。6. 部署上线与成本优化从实验到生产的关键一跃6.1 服务化封装FastAPI是最低成本的起点实验脚本能跑通和线上服务能扛住用户访问之间间隔着一整个工程化阶段。一个从零起步的AI服务用FastAPI包一层是最自然的选择因为它是异步的、自带数据校验和交互文档社区生态也好。 下面是一个最小但完整的AI服务示例结构from fastapi import FastAPI, HTTPException from pydantic import BaseModel from openai import OpenAI app FastAPI() client OpenAI() class ChatRequest(BaseModel): query: str history: list[dict] [] class ChatResponse(BaseModel): answer: str trace_id: str app.post(/chat, response_modelChatResponse) async def chat_endpoint(req: ChatRequest): # 这里把完整的流程拉进来查检索、拼上下文、调模型、返回 try: answer run_pipeline(req.query, req.history) return ChatResponse(answeranswer, trace_idgenerate_trace_id()) except Exception as exc: raise HTTPException(status_code500, detailstr(exc))生产环境还需要注意几个细节加一层限流防止单用户打爆账单设置合理的超时时间把trace_id贯穿全链路这样用户反馈问题时你能在日志里精确还原它经过的检索和模型调用。落库方面对话记录和评估数据要沉淀下来这些是你后续迭代优化的基础。6.2 成本优化的几条实在路径第一优先减少无效token。系统Prompt写得冗长、历史记录无限膨胀、检索结果不加筛选全塞进上下文这些都是成本黑洞。第二优先启用Prompt缓存。现在主流模型服务商大多支持prompt缓存命中缓存的输入token价格会低很多。如果让系统Prompt保持稳定长上下文场景下成本能明显下降。第三优先模型蒸馏与降档。如果你的数据积累到一定程度可以评估一下是否能用更小、更便宜的模型处理大部分简单请求而把复杂请求留给大模型处理。我可以给一个具体的估算例子假设每天有10万次请求每次输入输出合计约2000 token如果单价是每百万token 5美元量级那么一天的模型成本大约是10美元量级一个月大概300美元左右。这个数字会随着上下文长度和数据量快速增长所以不要等账单吓到你再优化一开始就养成看token的习惯。6.3 安全护栏AI工程不可跳过的一部分如果应用面向真实用户安全这块迟早要补。最基本的几条输入侧做敏感信息过滤比如手机号、身份证号、密钥这类内容在进模型前就要脱敏或拦截输出侧做内容合规校验避免模型生成不妥内容系统Prompt里要明确告诉模型“如果涉及无法回答的话题应当坦诚说明不要编造”。 还有一个很多团队容易忽略的点把模型自己的输出也当成不可信数据来处理下游任何解析、落库、自动化执行都要做校验。7. 高频踩坑清单与排查实录7.1 我实际遇到过的几个典型问题这个清单不是网上摘的是我自己从零做一个AI问答项目时真实出现过的问题按出现频率排列症状根因解决办法回答经常引用不存在的内容检索到的chunk被截断或metadata错乱检查分块逻辑和metadata来源保证chunk完整性结构化输出偶尔解析失败输出被max_tokens截断预留足够长的max_tokens并加上截断检测检索出来的内容不相关只用了向量搜索加上关键词检索和重排线上效果比测试差很多测试集太干净线上提问五花八门做一个覆盖真实分布的评估集定期更新API费用增长异常历史记录无限拼接进上下文限制历史轮数启用Prompt缓存7.2 排查技巧永远从日志和trace入手碰到AI应用出问题我最常做的事不是改代码重跑而是先去看那几条有问题的请求全链路日志。检索到了什么、拼进上下文的哪些内容、模型到底收到什么、输出了什么每一步都要有记录。没有trace的AI服务就像没有日志的传统服务出了问题只能瞎猜。所以从第一次部署开始就要让trace成为基础架构的一部分这能帮你节省大量的排查时间。7.3 给零基础起步者的几条避坑建议第一不要一开始就追最新最强的大模型。模型能力再强你的链路没理顺也是浪费钱。先用参数小、成本低的模型把整个流程跑通再回头升级。第二Prompt是迭代出来的不是写出来的。第一次写的Prompt几乎不可能一次到位保存好每个历史版本用评估集量化每次Prompt改动带来的变化。第三所有变量显式化。temperature、top_p、max_tokens这些参数不要依赖默认值每个都搞清楚含义再设置。这是最容易忽视的工程问题也是排查问题时最便宜的线索。第四文档类知识的RAG项目先用一个小规模高质量知识库验证方法再扩展到大规模数据。很多人在一开始就索引了几百万个PDF最后发现检索质量一塌糊涂却不知道怎么排查因为连问题出在哪一环都不知道。最后的个人体会把“ai-engineering-from-scratch”这条路完整走一遍之后我最大的感受是AI工程的复杂度并不在训练模型本身而在于把概率输出接入确定性系统的每一个细节里。Prompt怎么写、Retrieval怎么调、Evaluation怎么做、错误怎么追踪这些环节单独看起来都不算高深但连成一条链路之后它对人的系统性思维能力要求很高。如果你正准备从零开始做自己的AI项目我的建议是不要等读完所有文档才动手先搭一个最简的闭环然后围绕评估持续迭代。效果差不要紧只要你能量的出来、能追踪原因、能验证改进你就是在做真正的AI工程而不是在“用AI碰运气”。
阅读完成 · 觉得有帮助?