
1. 从零搭建AI工程体系为什么我劝你别一上来就啃论文ai-engineering-from-scratch这个标题第一次看到的时候我以为是又一个教你怎么调包的教程。点进去翻了翻发现它想做的事情比调包大得多——它试图回答一个很实际的问题一个没有机器学习背景的普通开发者怎么一步步把AI能力真正落地到工程项目里而不是停留在跑通一个demo的层面。我自己带过几个从传统后端转AI方向的同学最深的感受是卡住他们的从来不是数学公式而是工程上的断层。论文里的模型在Jupyter里跑得挺好一旦要接进真实业务数据管道、推理延迟、显存管理、版本回滚这些问题全冒出来了而这些恰恰是论文不会教你的。所以从零做AI工程这件事核心不是从零推导反向传播而是从零建立一套能支撑AI应用运转的工程骨架。这篇内容适合三类人看一是有一定编程基础、想往AI方向转的开发者二是已经在做AI应用、但总觉得系统脆、想补齐工程能力的同学三是技术负责人想搞清楚一个AI项目从原型到上线到底要跨过哪些坎。我会把整个搭建过程拆成设计思路、核心细节、实操落地、问题排查几个部分尽量把每一步为什么这么做讲透而不是甩一堆代码让你自己悟。需要先说明一点下面涉及的具体工具选型和参数是基于我实际项目里比较通用的做法做的合理补充不是唯一答案。你完全可以根据自己的技术栈替换但背后的取舍逻辑是相通的。2. 整体设计思路AI工程到底在工程什么2.1 先搞清楚AI工程和传统软件工程的边界很多人把AI工程理解成会调模型API就行这是最大的误区。传统软件工程里输入输出是确定的你写个函数给定参数就返回固定结果测试用例能覆盖绝大多数情况。AI工程不一样它的核心变量是概率——同一个输入模型可能给出不同输出甚至给出看起来合理但完全错误的答案。这个本质差异决定了AI工程要多做几件事数据要可追溯因为模型表现高度依赖训练和推理数据的分布输出要可评估因为对不对不再是布尔值而是一个分布上的判断系统要可观测因为线上出问题时你没法像调试普通代码那样打个断点就完事。所以ai-engineering-from-scratch的第一层设计思路应该是把AI系统当成一个数据驱动的流水线来对待而不是一个孤立的模型调用。流水线大致分四段数据准备、模型推理、结果后处理、反馈闭环。每一段都有独立的工程要求也都能单独优化。2.2 为什么选择分层架构而不是一把梭我见过不少项目一开始为了快把数据加载、模型推理、业务逻辑全塞在一个脚本里。原型阶段确实爽两周就能演示。但等到要换模型、要加缓存、要做A/B测试的时候改一处崩三处。分层架构的价值在这里就体现出来了。我通常会把系统切成这么几层接入层负责请求接收、鉴权、限流不碰任何AI逻辑编排层决定一次请求走哪条推理路径做prompt组装、上下文管理推理层真正调用模型的地方封装成统一接口屏蔽底层是本地模型还是远程服务数据层管理向量库、缓存、日志、评估数据集可观测层贯穿所有层收集延迟、token消耗、输出质量指标这么分的好处是换模型只动推理层改业务逻辑只动编排层互不干扰。代价是前期多写一些接口定义和胶水代码。我的经验是只要项目预期生命周期超过三个月这个投入一定回本。2.3 技术选型的取舍逻辑选型这块我不给标准答案给判断标准。以推理层为例你到底用本地部署的开源模型还是调用云端API取决于三个维度维度本地部署云端API数据敏感性数据不出内网适合敏感场景数据要出网需评估合规成本结构前期硬件投入高边际成本低无前期投入按量付费迭代速度换模型要重新部署慢切换模型改个参数快可控性完全可控可深度定制受服务方限制我的建议是验证阶段用云端API快速试错等业务模式跑通、调用量稳定了再评估哪些环节值得迁到本地。一上来就自建推理集群大概率是给自己挖坑。编排层同理简单的prompt拼接用代码写就行一旦涉及多轮对话、工具调用、复杂分支就该考虑引入编排框架了。但框架不是越重越好我踩过的坑是过早引入复杂框架结果框架本身的学习成本比业务还高。3. 核心细节解析那些决定成败的关键环节3.1 数据管道AI工程里最容易被低估的部分如果说模型是发动机数据管道就是油路。油路堵了发动机再好也白搭。AI工程里的数据管道要解决三个问题数据从哪来、怎么清洗、怎么喂给模型。数据来源通常有三类业务系统里的结构化数据、用户上传的非结构化文档、外部采集的公开数据。这三类的处理方式完全不同。结构化数据相对好办重点是字段映射和缺失值处理非结构化文档要经过解析、分块、向量化外部数据则要额外关注质量和版权。清洗环节我特别想强调分块策略。做RAG检索增强生成的同学应该深有体会文档切得好不好直接决定检索质量。我常用的策略是按语义边界切而不是按固定字数切。具体做法是先按段落分如果段落超过阈值比如500字再按句子边界二次切分同时保留一定的重叠overlap避免关键信息被切断。提示分块大小没有万能值。技术文档适合大块800-1000字保留完整上下文FAQ类适合小块200-300字提高检索精度。一定要用真实数据测别拍脑袋定。向量化这块选embedding模型要考虑语言支持、维度、推理成本。中文场景下维度在768到1024之间的模型通常够用维度太高存储和检索成本都会上去收益却不一定明显。3.2 推理层的封装让模型调用变得可替换推理层封装的核心目标是接口稳定实现可换。我一般会定义一个统一的推理接口输入是标准化的消息列表和参数输出是标准化的结果对象包含生成内容、token用量、耗时等元信息。class InferenceResult: def __init__(self, content, prompt_tokens, completion_tokens, latency_ms): self.content content self.prompt_tokens prompt_tokens self.completion_tokens completion_tokens self.latency_ms latency_ms class BaseInference: def generate(self, messages, temperature0.7, max_tokens1024): raise NotImplementedError这样上层业务只依赖BaseInference底层换成任何模型实现都不用改业务代码。这个模式看起来简单但能省掉后期大量的重构工作。参数设置上temperature控制随机性做事实性问答时调低0.1-0.3做创意生成时调高0.7-1.0。max_tokens要设上限防止模型生成过长内容拖垮响应时间。这些参数最好做成可配置而不是硬编码。3.3 上下文管理多轮对话的隐形战场单轮问答简单多轮对话就复杂了。模型本身没有记忆所谓记得之前说过什么全靠你把历史对话拼进prompt里。但上下文窗口是有限的不可能无限拼接。我的处理策略是分层最近几轮对话完整保留更早的对话做摘要压缩。摘要可以用模型自己生成也可以用规则提取关键信息。具体保留几轮取决于业务场景——客服场景可能保留5-8轮创作场景可能只需要保留当前任务相关的部分。这里有个容易忽略的点上下文里的信息要标注来源和时间。比如用户三小时前说我要订明天的票三小时后又问那个票怎么样了如果你不标注时间模型可能理解错。我习惯在拼接时给每条历史消息加上时间戳和角色标记实测能明显降低指代错误。4. 实操落地从空目录到能跑的系统4.1 环境准备与依赖管理第一步永远是环境。我强烈建议用虚拟环境隔离别在系统Python里直接装包。依赖管理用requirements.txt或者pyproject.toml都行关键是要锁定版本。python -m venv venv source venv/bin/activate # Windows用 venv\Scripts\activate pip install -r requirements.txtrequirements.txt里我一般会分两组核心依赖推理框架、向量库客户端和开发依赖测试、格式化工具。生产环境只装核心依赖减少攻击面和体积。注意AI相关的库版本兼容性经常出问题尤其是涉及CUDA的。建议在requirements.txt里精确到小版本号比如torch2.1.0而不是torch2.0避免某天自动升级后跑不起来。4.2 数据准备实操从原始文档到可检索知识库假设你有一批PDF文档要处理完整流程是这样的文档解析用解析库把PDF转成纯文本保留段落结构。扫描件要先做OCR。文本清洗去掉页眉页脚、多余空行、乱码字符。分块按前面说的语义边界策略切分每块记录来源文档和位置。向量化调用embedding接口把每块文本转成向量。入库向量和原文、元数据一起存进向量库。def chunk_text(text, max_len800, overlap100): paragraphs text.split(\n\n) chunks [] current for para in paragraphs: if len(current) len(para) max_len: current para \n\n else: if current: chunks.append(current.strip()) current para \n\n if current: chunks.append(current.strip()) return chunks这个分块函数是最简版本实际用的时候还要处理超长段落、表格、代码块等特殊情况。分块完成后每块都要带上source、chunk_id这些元数据方便后续溯源。4.3 推理链路搭建一次请求的完整旅程一次用户请求进来系统内部大概经历这些步骤接入层收到请求做鉴权和限流编排层取出用户历史对话组装prompt如果涉及知识问答先拿用户问题去向量库检索相关片段把检索结果和问题一起拼进prompt推理层调用模型生成回答后处理层做格式校验、敏感词过滤返回结果同时异步记录日志和指标这里面第3步的检索质量很关键。检索时通常用向量相似度但纯向量检索有时会漏掉关键词匹配的情况。我的做法是混合检索向量检索和关键词检索各取一批结果再用重排序模型合并排序。实测召回率比单用向量高不少。def hybrid_retrieve(query, top_k5): vector_results vector_store.search(embed(query), top_ktop_k*2) keyword_results keyword_index.search(query, top_ktop_k*2) merged rerank(query, vector_results keyword_results) return merged[:top_k]4.4 可观测性建设让系统的问题看得见AI系统最怕的是静默失败——用户觉得回答不对但你从日志里看不出任何异常。所以可观测性必须从第一天就建。我关注的核心指标有这么几个首token延迟用户感知的响应速度、总生成耗时、token消耗量直接关系成本、检索命中率、用户反馈率。这些指标要能按时间、按用户、按请求类型切片查看。日志记录要包含完整的输入输出但要注意脱敏。用户隐私信息不能明文落盘。我一般会把原始输入做哈希处理只保留用于排查的必要信息。提示日志量大的时候全量记录成本很高。可以采样记录比如正常请求记10%报错请求记100%。这样既控制成本又不漏掉问题。5. 常见问题与排查技巧实录5.1 模型输出不稳定怎么办这是最高频的问题。同一个问题问两次答案不一样用户会觉得系统不靠谱。排查思路分三层先看temperature是不是设太高了。事实性问答场景temperature超过0.5就容易飘。其次看prompt里有没有歧义表述模型对模糊指令的解读每次可能不同。最后看是不是检索结果本身就不稳定导致每次喂给模型的上下文不一样。解决办法降低temperature、把prompt写得更明确、对检索结果做缓存。如果业务允许还可以对同一问题做多次生成然后投票选最一致的答案代价是成本翻倍。5.2 响应太慢怎么优化延迟问题要分段定位。先看是检索慢还是推理慢。检索慢通常是向量库索引没建好或者检索的候选集太大。推理慢则可能是模型太大、max_tokens设太高、或者并发没控制好。优化手段按性价比排序加缓存相同问题直接返回 减小max_tokens 用更小的模型 流式输出让用户先看到部分结果。流式输出对感知延迟的改善特别明显虽然总耗时没变但用户等待焦虑大大降低。5.3 检索不到相关内容怎么排查RAG系统最常见的失败模式是检索为空或检索到无关内容。排查清单如下现象可能原因排查方法检索结果为空向量库没数据/索引未建直接查库确认数据量检索结果无关分块不合理/embedding不匹配打印检索片段人工检查明明有答案却检索不到查询和文档表述差异大试混合检索加关键词通道检索到旧版本内容数据没更新检查数据同步流程我踩过最坑的一次是embedding模型换了但向量库里的老向量没重新生成导致新旧向量不在同一空间检索结果乱七八糟。换embedding模型一定要全量重建索引这个坑记住就行。5.4 成本失控怎么控制token消耗是AI应用的主要成本。控制手段有这么几个prompt精简去掉冗余指令、上下文裁剪别把整个历史都塞进去、结果缓存高频问题不重复推理、模型分级简单问题用小模型复杂问题才用大模型。我一般会设一个成本告警阈值比如日消耗超过预算的80%就报警。同时按业务线拆分成本搞清楚钱花在哪了才能有针对性地优化。6. 一些踩坑之后的个人体会做AI工程这几年最大的体会是别追求一步到位要追求每一步都可回退。模型会换、数据会变、业务需求会调整系统设计时留好扩展点和回退路径比一开始就设计一个完美架构重要得多。另一个体会是关于评估的。很多人做完系统就上线了没有建立评估机制。结果出了问题只能靠用户投诉发现。我的做法是维护一个小规模的评估集每次改动后跑一遍看关键指标有没有退化。这个评估集不用很大几十到几百条就够但一定要覆盖典型场景和边界情况。最后说个具体的prompt一定要版本化管理。我见过太多项目prompt改来改去最后没人记得哪个版本效果好。把prompt当代码一样管理每次改动记录原因和效果这个习惯能帮你省下大量返工时间。