
先说结论ai-engineering-from-scratch这个主题拆开看就一句话——真正把一个AI能力做成能上线、能维护、能持续迭代的产品靠的不只是模型调参和写提示词而是一整套工程体系。这篇文章我把从零搭建这套体系的过程、选型逻辑、踩过的坑和沉淀下来的方法一次性写清楚。1. AI工程到底在解决什么问题1.1 一个AI Demo到可用产品之间差了什么很多团队第一次做AI应用时都会经历一个典型路径先用现成的大模型API调通了一个Demo回答效果还不错大家都很兴奋。但Demo和产品之间隔着一条很深的沟瓶颈不在“模型行不行”而在“怎么让模型稳定地运行在真实业务里”。我举一个实际场景。你写了一个基于大模型的文档问答助手在测试环境里用精心挑选的几份文档做验证效果拉满。但上线后用户丢进来一堆格式五花八门的PDF和扫描件问题从“帮我总结这段”变成“这个合同第几条说的是违约金”模型开始答非所问、上下文混乱、漏看关键信息。这时你才发现真正要解决的不是Prompt写得不够好而是从文件解析、文本切片、向量化、检索召回到大模型调用的全链路质量问题。这就是AI工程的核心把模型能力嵌入业务流程后用工程手段保证它在不确定性中尽量稳定。它不同于单纯的算法研发也不同于传统后端开发而是两者的交集需要同时处理概率系统的不可控和分布式系统的复杂度。1.2 AI工程的全链路关键环节我习惯把一套完整的AI工程链路拆成六个环节每个环节都有独立的难点模型接入层选择API调用还是私有化部署决定成本、延迟和可控性。数据处理层从原始文档到可被检索的知识库涉及清洗、解析、切片、向量化。检索增强层从向量数据库召回相关内容涉及召回精度、重排策略和上下文组装。应用编排层把检索结果、Prompt模板、工具调用组合成最终模型输入涉及提示词管理和状态设计。评估观测层离线评估集和线上监控指标回答好不好不能靠感觉。部署迭代层灰度发布、版本回滚、成本优化和持续收集反馈。这六个环节任何一个拖后腿整个系统的体验就会被拉低。国内很多团队前两个环节做得不够扎实以为换个更强的模型就能解决结果只是把问题转移到了别处。下面我按实际落地顺序逐层展开。2. 从零搭建基础设施与技术栈选型2.1 模型接入直接调API还是自己部署模型接入是第一道选择题也是贯穿始终的成本和性能决策。直接调云端API的优势是起步快、无需考虑GPU运维劣势是数据出域风险和长线成本不可控私有化部署则反过来前期投入大但单次调用成本会随规模下降。我的建议是按“业务敏感度和调用量”两条轴来判断数据敏感度高、调用量大优先私有化部署开源模型比如在国产卡或消费级GPU上跑7B~14B量级的模型配合量化技术把单卡推理做到可用。数据敏感度一般、需要最强推理能力直接调商业API把精力省下来做业务流程。中间状态用API做前期验证验证清楚业务Pattern后再评估是否迁移到私有化方案。这里有一个容易忽略的点模型接入不是写完代码就结束要在一开始就设计好模型网关统一管理不同提供方的API格式拆分、超时控制、重试策略和成本统计。我见过不止一个团队上线三个月后想换模型供应商结果发现所有调用代码散落在各个服务里改起来伤筋动骨。2.2 向量数据库、缓存和检索组件的选型逻辑知识库场景里向量数据库几乎是标配但选型时要避免“性能参数崇拜”。不同向量数据库在千万级数据下的ANN检索性能差距确实存在但对大多数中小业务来说几十万到几百万条向量量级下选型更重要的是是否支持Metadata过滤、是否方便增量更新、运维成本多高、能否和现有存储统一。我实际用过几种方案做一个主观但真实的对比如下方案适合场景注意点PostgreSQL pgvector数据量不大、已有PG依赖事务一致性天然具备但大规模向量检索需要调参Milvus数据量大、高并发检索组件多运维复杂度较高适合有专门运维的团队Qdrant中等规模、需要快速上手Rust编写性能好过滤功能灵活Elasticsearch 向量插件已有ES体系、需要全文检索和向量混合混检方便但内存开销较大缓存也要提前规划。同样的用户问题在短时间内重复查询是很常见的行为尤其在企业内部工具中几十个人对同一批制度文档反复提问。加一层语义缓存或者直接按问句哈希缓存能把响应延迟从秒级压到毫秒级同时大幅降低模型调用费用。2.3 最小工程骨架代码参考下面这个骨架不是完整生产实现但覆盖了工程化接入需要的基本要素超时、重试、缓存、简单的调用统计。我建议每个团队初期至少做到这个水平再往上加功能import hashlib import time from dataclasses import dataclass from typing import Optional import redis import requests dataclass class LLMClient: api_base: str api_key: str model: str cache_ttl: int 600 timeout: int 30 max_retries: int 3 def __post_init__(self): self.redis redis.Redis.from_url(redis://localhost:6379/0) def _cache_key(self, prompt: str) - str: return fllm:{self.model}:{hashlib.md5(prompt.encode()).hexdigest()} def _call_once(self, prompt: str) - str: resp requests.post( f{self.api_base}/v1/chat/completions, headers{Authorization: fBearer {self.api_key}}, json{model: self.model, messages: [{role: user, content: prompt}]}, timeoutself.timeout, ) resp.raise_for_status() return resp.json()[choices][0][message][content] def chat(self, prompt: str) - str: cache_key self._cache_key(prompt) cached self.redis.get(cache_key) if cached: return cached.decode() last_exc None for attempt in range(self.max_retries): try: result self._call_once(prompt) self.redis.set(cache_key, result, exself.cache_ttl) return result except Exception as exc: last_exc exc time.sleep(2 ** attempt) raise last_exc这段代码里有两个值得说的细节。第一重试策略用指数退避而不是固定间隔避免瞬时故障时所有请求同时打爆服务。第二缓存必须区分场景像“总结这份对话记录”这种个性化请求就不适合直接哈希缓存应该按业务语义设计缓存键。3. 数据工程与知识库AI应用的地基3.1 文档解析与清洗的隐藏工作量数据上最常见的认知偏差是“把数据喂进去就行”。实际情况是企业内部的文档数据远比想象中脏扫描版PDF没有文字层、有页眉页脚和页码干扰、表格被拆成错乱的文本、Word里嵌的图片里的信息全部丢失。做AI工程有一半时间在“洗数据”这不是夸张。一个实战经验根据文档类型分成不同的预处理管线不要试图用一套流程处理所有文件。文本型PDF用PDF解析库提取文字重点是版面还原。扫描件/图片型PDF必须走OCR管线中文老文档推荐偏专业的OCR引擎通用大模型OCR虽然能力提升快但批量处理时成本和延迟都比较高。Word/Markdown保留标题层级信息这对后续切片有巨大价值。Excel/CSV每行转成自然语言描述比较麻烦容易被低估但表格问答场景下结构化数据转文本的规则决定了检索质量。清洗环节要时刻有“模型是纯文本消费者”的意识。模型不读二进制不读PDF里的图表只读你转换出来的纯文本。所以转换质量就是系统质量的上限。3.2 文本分块策略块大小、重叠与分隔符分块是知识库检索中最容易被忽略却又影响最大的参数。切太大召回结果可能涵盖太多无关内容既稀释注意力又浪费Token切太小语义完整性被破坏单块信息量不足以回答复杂问题。我建议从下面这组初始参数开始调块大小chunk size300~500个汉字左右中英文混合时按Tokenizer计数更准确。重叠overlap块大小的10%~20%用来缓解“好死不死切断关键句”的问题。优先按标题/段落分隔Markdown标题、PDF章节、换行符都算语义边界优先在这些位置切而不是硬按字符数切。保留元数据每块都带上文档ID、来源页码、章节路径便于溯源和后续过滤。分块为什么不能照抄最佳实践因为知识库内容差异太大。给制度文档做知识库和给研发文档做知识库理想的块大小可能差一倍。我是通过建立一个小型标注集人工标记“哪些检索结果对回答是有用的”然后用不同分块参数跑一遍看召回效果来确定参数的。这个工作听起来很笨但比拍脑袋选参扎实得多。3.3 Embedding模型与检索优化Embedding模型的选择影响的是“语义相关”的判定基准。中文场景下通用模型能力已经很能打但如果你的知识库有大量专业术语建议在几个开源Embedding模型之间做一下对比测试用你真实的数据算召回率不要只看公开榜单。检索优化方面几个立竿见影的手段如下Metadata过滤优先于向量检索先在业务维度上缩小范围比如“只查2024年合同”“只要产品手册”再对小块做向量检索比全局检索更准、也更快。Hybrid Search把向量检索和关键词检索结合解决纯向量方案对专有名词、英文缩写不敏感的问题。召回后重排先用低成本方式召回Top 20再用重排模型或规则过滤到Top 5。这个环节对最终效果提升很明显。上下文精炼不要把整段原始文本都塞进Prompt用大模型做一次“提炼关键信息”的中间步骤能有效减少幻觉。有一类错误是“为了RAG而RAG”——问题根本不需要检索外部知识系统却非要从知识库里硬拉一堆段落出来反而污染了模型输出。所以应用层还需要一个判断逻辑什么情况下走检索、什么情况下直接生成。4. 应用编排层的工程化细节4.1 提示词管理不能只靠聊天记录我见过太多团队的Prompt像“个人笔记”散落在Jupyter Notebook、微信聊天记录和某次会议的白板里。一旦模型升级或换了供应商同样的Prompt效果可能大幅波动这时如果没有版本管理排查问题会非常痛苦。建议把Prompt当成代码来管理每个模板有独立文件内部变量用占位符表示禁止拼接字符串写死在业务代码里。提交到Git仓库每次调整都走Code Review。重要Prompt要有备注记录当时调优的背景和预期效果。线上运行版本和测试版本分开方便做A/B对比。小技巧Prompt模板的变更往往比代码变更更频繁而且每次变更都直接影响回答质量所以日志里一定要记录“这一次请求用的是哪个版本的Prompt”。4.2 对话状态、缓存、并发与超时AI应用一旦进入生产环境工程化细节就开始占据主导地位。对话服务不仅要处理“单次问答”还要管理多轮上下文这里最容易出的问题是上下文无限膨胀——每轮都把历史消息全部塞给模型很快超出上下文窗口。我常用的方案是维护一个“可裁剪的滑动窗口”def build_messages(history, current_query, max_tokens4000): messages [{role: system, content: SYSTEM_PROMPT}] budget max_tokens for item in reversed(history): text item[content] tokens estimate_tokens(text) if budget - tokens 0: break messages.insert(1, item) budget - tokens messages.append({role: user, content: current_query}) return messages从后往前累加先保住离当前最近的信息旧内容超预算就丢弃。这里有个更进阶的做法对历史消息做摘要而不是简单截断比如每三轮对话生成一条浓缩摘要作为长期记忆。缺点是会引入额外模型调用所以实现时要评估成本收益。并发和超时也要同时考虑。大模型推理是慢操作一个请求动辄几秒如果服务是同步处理几个用户就能拖垮线程池。生产上要引入异步队列把请求和响应解耦用户先拿到“任务已受理”结果生成后通过轮询或回调获取。同时为不同类型的请求设置差异化超时简单分类闲聊给短超时复杂分析任务给长超时。4.3 安全护栏与输入输出过滤AI应用的安全不是“有防火墙就够”而是要在应用层加两道护栏。输入侧对用户提交的内容做限制避免注入类提示词尝试改变系统设定输出侧拦截不合规内容同时过滤模型可能输出的内部提示词或无关格式。具体做法举例输入长度限制超出后前置截断并提示用户精简。关键词分类模型双通道检测敏感内容。输出内容经过规则过滤后才返回前端。所有涉及个人信息的问题默认走“脱敏后回答”。我强调一下护栏设计要早做。系统上线后再补拦截往往只能挡部分链路而且容易误伤正常业务。更推荐把护栏作为应用编排层的一个独立网关组件所有模型请求和响应都统一经过它。5. 评估体系与可观测性别让回答质量靠“感觉”5.1 离线评估集怎么设计与维护很多团队对AI应用的评价停留在“看起来还可以”但无法回答两个问题这个版本比上个版本好多少换模型后效果是提升还是下降没有评估体系所有优化都是盲人摸象。我建议从第一天就建立一个小而有效的评估集不必一上来就追求大规模。起步可以就准备四类样本标准问答从真实业务中挑选50~100个高频问题附带标准答案要点。边界Case检索不到相关内容、问题超出知识库范围、模糊提问等场景。对抗样本故意绕开规则、诱导模型输出不该有的内容的提问。多轮对话覆盖状态切换、指代消解和追问场景。评估方式可以先用LLM-as-Judge自动化打分再用人工抽检校准。这里提醒一句让模型给自己打分天然有偏好所以Judge提示词的设计要明确打分维度和参考标准而且要记录评分一致性太低时说明评估设计本身有问题。5.2 线上监控指标除了延迟还要关心“无效回答率”线上环境不能只监控CPU、内存和接口延迟。AI应用特有的指标包括Token消耗、缓存命中率、检索召回数量、重排后采纳比例、空回答率、超时重试率、用户反馈点击率。在这些指标里我认为“无效回答率”比平均延迟更重要。一个回答如果用户根本不采纳延迟再低也没意义。在实际业务里可以通过前端加反馈按钮、会话中检测用户是否重复提问、回答后是否立即离开页面等方式来近似估算。监控数据不仅用来发现故障更是产品迭代的输入。我在团队里一直强调模型升级、Prompt调整、检索参数变更都必须有对应的指标对比哪怕简单到就是一张Excel记录也比“凭感觉说变好了”有价值。5.3 日志记录的正确姿势日志是所有可观测性的基础。AI应用的日志和传统后端日志有几点不同必须记录完整的Prompt和模型输出否则排障时无从查起。必须携带版本标识包括模型版本、Prompt模板版本、向量库版本。必须记录检索到的文档块ID不然用户说“你答错了”你都不知道它看了什么资料。日志要脱敏用户上传的文档内容可能包含敏感信息。我有一个印象很深的排障经历用户反馈某类问题回答质量下降查日志发现不是Prompt变了而是上游数据同步任务某天开始把错误格式的文档写进了知识库所有新增Block都是乱码。如果日志里没有检索来源ID这个问题可能要好几天才能定位。6. 部署上线与持续迭代6.1 从单体应用到可灰度发布AI应用上线初期可以用单体架构但不必为了“架构先进”过早拆分微服务。我更推荐的路径是先让服务整体跑起来再按“变更频率”来决定拆分边界。在实际生产中知识库更新频率通常远高于模型调用服务如果两者耦合在一个服务里每次向量库更新都要重新发版。把数据处理和在线推理拆成两个独立服务是性价比最高的第一步。在线推理服务内部则可以按“调用链版本化”的思路来做灰度让一部分流量走新Prompt或新模型另一部分还是旧逻辑对比指标后全量切换。6.2 Token成本优化的几个实用手段大模型应用的成本大头就是Token费用这块不能只在月底看账单才心疼。事中控制才是关键缓存策略分级高频重复问题用语义缓存对应的高频Token直接省掉。模型分级调度简单问题走小模型复杂推理才走大模型。我见过不少团队不管什么问题都统一调最强的模型成本高效果也没更好。输出Token限制把max_tokens按场景限死有些场景其实100字就能回答完整却默认生成500字。知识库回答优先引用原文让模型在检索到的内容里提炼而不是自由发挥既减少幻觉又能控制输出长度。给一个可落地的预算思路把月Token预算拆到日均再估算每个请求的平均Token开销反推出“每天能支撑多少次问答”这个数字要同步给业务方避免上线后超预算措手不及。6.3 持续迭代闭环反馈、分析、调整三步走上线只是开始。我把迭代节奏总结成“一个闭环、每周一轮”收集从日志和用户反馈里提取“回答不佳”的案例。分析定位是检索没召回、Prompt指令不清晰还是模型本身能力不足。调整针对原因做定向修改记录改动内容并触发评估集回归。有个重要经验是不要每件事都归因到模型。排查顺序应该是“数据有没有问题 → 检索有没有召回 → Prompt是否给了正确的任务定义 → 最后才怀疑模型能力”。现实中大部分Case都出在前两层很多人却直接跳到换模型。7. 踩坑实录与实战心得7.1 最容易被低估的三个问题第一个是数据更新的一致性问题。知识库里旧版本数据和新版本数据混在一起用户提问时召回结果不稳定今天答A明天答B。工程上要引入数据版本和生效时间确保同一次会话内上下文一致。第二个是长文档处理。50页以上的文档直接切片后每块信息密度极低检索效果显著变差。我后来改成分层检索先做全文摘要和信息索引再做二次定位阅读效果改善非常明显。第三个是“测试集与线上分布不一致”。团队精心构造的测试集永远比真实用户提问“收敛”得多于是离线评估高分线上体验翻车。解决方法是需要持续把线上真实低分Case补充进评估集保持评估集的动态更新。7.2 我沉淀下来的几条工程守则做了几个AI项目之后我总结出几条很朴素的守则分享给你作为参考先跑通最小闭环再增加复杂度。第一个版本不追求完美但指标采集必须到位。所有变更都可追溯。Prompt、模型、数据、参数每个环节都要有版本意识。能离线解决的问题不要推到线上。数据清洗、分块策略、评估评分都是离线完成的反复在线上试错成本很高。把模型当组件而不是当银弹。模型的角色是“给定输入生成合理输出”业务逻辑仍然要靠代码来约束和校验。7.3 最后再分享一个小技巧我现在做AI工程有个习惯每次调优只改一个变量。比如这周只调分块大小、下周只换Embedding模型其他全部保持不变然后看评估集和监控指标的变化。同时修改多个变量会让结果无法归因最后连怎么优化的都不知道。保持单变量实验记录每个实验的结论积累三个月你会拥有一套“属于自己业务场景的最佳实践文档”这比任何公开的通用教程都有价值。