
1. 从零搭建AI工程体系为什么我劝你别一上来就调包这两年“AI工程”这个词被炒得火热打开任何一个技术社区满屏都是“三天上手大模型”“一周搞定RAG”“零基础转行AI工程师”。但我自己带过几个项目、也面试过不少号称“做过AI应用”的候选人之后发现一个很尴尬的现实大部分人所谓的AI工程能力其实是“会调用某个API”或者“会跑通某个开源demo”。一旦线上出问题、一旦要换模型、一旦要控制成本立刻就抓瞎。ai-engineering-from-scratch这个标题我理解的核心不是“从零学AI理论”而是从零搭建一套能跑、能维护、能扩展的AI工程体系。它解决的是这样一个问题当你手里有一个真实的业务需求需要把模型能力接进系统里你该怎么设计、怎么选型、怎么落地、怎么排错。这套东西适合谁适合已经会写代码、但对AI工程没有系统认知的后端、全栈、数据工程师也适合那些调了半年API却始终觉得自己在“黑盒里干活”的开发者。我个人的判断是AI工程的门槛从来不在“会不会用模型”而在“能不能把模型当成一个不可靠的、昂贵的、有延迟的依赖来管理”。这个视角一旦建立起来后面所有的技术选型和架构设计都会顺理成章。下面我就按我自己实际搭过的一套流程把这件事从头到尾拆一遍。2. 整体架构设计把模型当成一个“不听话的第三方服务”2.1 为什么不能直接在前端或业务代码里调模型我见过太多项目第一版就是把API Key写在后端某个controller里收到请求直接转发给模型拿到结果返回。这个做法在demo阶段没问题但上线之后会暴露三个致命问题。第一是耦合。模型调用逻辑和业务逻辑混在一起想换个模型、加个缓存、做个降级都得动业务代码。第二是不可观测。你不知道每次调用花了多少钱、延迟多少、失败率多少出了问题只能靠猜。第三是不安全。Key泄露、Prompt注入、超长输入导致费用爆炸这些风险在裸调模式下几乎无法控制。所以我的做法是在业务代码和模型之间强制加一层AI网关层。这一层不处理业务只处理“和模型打交道”这件事。它负责统一入口、统一鉴权、统一日志、统一重试、统一限流。业务代码只需要说“我要一个摘要”不关心背后是哪个模型、走的是哪条链路。2.2 分层结构长什么样我习惯把整个体系分成四层从下往上说。最底层是模型接入层负责对接不同厂商的模型接口做协议适配。这一层的关键是抽象出一个统一的调用接口比如chat(messages, options)不管背后是哪个模型上层看到的都是一样的。往上是能力编排层也就是常说的Prompt管理、上下文组装、工具调用编排。这一层决定了“怎么问”和“怎么处理模型的回答”。比如RAG的检索拼接、多轮对话的历史裁剪、Function Calling的参数校验都在这一层。再往上是业务适配层把通用的AI能力包装成业务能直接用的服务比如“生成商品文案”“判断工单分类”“提取合同关键条款”。这一层是业务和AI的翻译层。最上面是应用层就是具体的接口、任务、前端交互。这个分层的好处是任何一层出问题都能快速定位任何一层要替换都不会影响其他层。我试过把一个项目从某厂商模型整体切到另一个厂商只改了接入层的一个适配器业务代码一行没动。2.3 关键设计原则一切皆可降级AI工程和传统后端最大的区别是模型是一个概率性的、会失败的、有延迟的依赖。传统后端你调数据库要么成功要么失败逻辑是确定的。但模型调用可能超时、可能返回垃圾、可能突然涨价、可能被限流。所以我在设计时有一条铁律任何AI调用都必须有降级路径。降级不是“报错返回”而是“用一个更差但可用的方案兜底”。比如摘要服务主路径用大模型超时或失败时降级到抽取式摘要分类服务主路径用模型失败时降级到关键词规则。这条原则听起来简单但真正落地需要在架构设计阶段就预留好接口。3. 核心模块拆解从Prompt到可观测性3.1 Prompt管理别把Prompt硬编码在代码里新手最容易犯的错就是把Prompt字符串直接写在代码里。我早期也这么干过结果就是改一个标点都要重新发版A/B测试没法做不同环境的Prompt不一致出了问题不知道线上跑的是哪个版本。我的做法是把Prompt当成配置来管理。每个Prompt有唯一ID、有版本号、有变量占位符、有对应的模型参数。存储上可以先用文件规模大了再上数据库或配置中心。关键是要做到改Prompt不发版、能回滚、能对比。一个Prompt配置大概长这样id: product_summary version: v3 model: default_chat temperature: 0.3 max_tokens: 500 template: | 你是一个电商文案助手。请根据以下商品信息生成一段不超过100字的卖点摘要。 商品名称{{name}} 商品参数{{specs}} 要求突出差异化不要夸大不要出现绝对化用语。 variables: - name - specs这里有个细节temperature我一般设得比较低0.2到0.4因为业务场景要的是稳定不是创意。只有营销文案类才需要调高。3.2 上下文组装Token是要花钱的很多人对Token没概念觉得多塞点上下文模型更聪明。实际上上下文越长成本越高、延迟越大、模型越容易“迷失在中间”。我做过一个测试同样的问题上下文从2K加到8K准确率反而下降了因为关键信息被淹没了。我的上下文组装策略是分层裁剪。系统指令永远保留最近几轮对话保留历史对话做摘要压缩检索到的文档按相关度排序只取Top-K并且做去重和截断。具体保留多少要看模型窗口和业务容忍度。我一般会留20%的余量防止边界情况溢出。这里有个实操技巧给上下文打标签。比如用[系统]、[历史]、[检索]、[当前问题]这样的标记分隔不同来源的内容。实测下来模型对结构化输入的遵循度明显更高尤其是需要引用来源的场景。3.3 输出解析别信模型会乖乖返回JSON如果你让模型返回JSON它大概率会返回但偶尔会带上json标记、会多一句解释、会在字符串里塞未转义的引号。我踩过最坑的一次是模型在JSON外面加了一句“好的以下是结果”导致整个解析失败。所以输出解析必须做容错处理。我的标准流程是先尝试直接解析失败则用正则提取第一个完整的JSON块再失败则调用一次“修复Prompt”让模型重新格式化最后还失败就降级到人工或规则。这套流程听起来繁琐但线上稳定性提升非常明显。对于Function Calling我的建议是永远做参数校验。模型给的参数可能类型不对、可能缺字段、可能超出范围。校验不通过就返回错误让模型重试重试两次还不行就降级。不要假设模型一定对。3.4 可观测性不知道花了多少钱的AI项目是危险的我见过一个团队上线一个月后收到账单才发现费用是预估的十倍。原因很简单没有监控没有告警没有按业务维度拆分成本。我的可观测性方案包含四个维度调用量、延迟、成功率、成本。每次调用都记录业务标识、模型、输入Token、输出Token、耗时、状态。这些数据落到日志或时序数据库然后做聚合看板。成本这块要特别说一下。不同模型价格差异巨大同一个业务用不同模型成本可能差几十倍。所以我会给每个业务线设置预算上限超过阈值就告警超过硬上限就自动降级到便宜模型或规则方案。这个机制救过我好几次。4. 实操落地一套可复现的最小可用体系4.1 环境与依赖准备我假设你用的是Python因为AI工程生态目前还是Python最顺手。核心依赖不多一个HTTP客户端httpx比requests更适合异步场景、一个配置管理库pydantic做校验很好用、一个日志库标准库logging够用复杂场景上structlog。目录结构我习惯这样组织ai_service/ gateway/ # 模型接入层 adapters/ # 各厂商适配器 router.py # 路由与降级 orchestration/ # 能力编排层 prompts/ # Prompt配置 context.py # 上下文组装 parser.py # 输出解析 business/ # 业务适配层 summary.py classify.py observability/ # 可观测性 metrics.py cost.py config/ settings.py这个结构的好处是职责清晰新人进来能快速找到该改哪里。4.2 统一调用接口的实现接入层的核心是定义一个抽象基类所有适配器都实现它from abc import ABC, abstractmethod from dataclasses import dataclass dataclass class ChatRequest: messages: list model: str default temperature: float 0.3 max_tokens: int 1000 dataclass class ChatResponse: content: str input_tokens: int output_tokens: int model: str latency_ms: int class BaseAdapter(ABC): abstractmethod async def chat(self, req: ChatRequest) - ChatResponse: ...然后每个厂商写一个适配器把各自的协议转成这个统一格式。路由层根据配置决定用哪个适配器并处理重试和降级。这里有个经验重试要区分错误类型。超时和限流可以重试参数错误和内容违规不要重试重试只会浪费钱。我一般对超时重试1次对限流做指数退避重试2次其他错误直接降级。4.3 一个完整的业务服务示例拿“工单自动分类”举例。业务需求是用户提交工单系统自动判断属于哪个类别退款、物流、产品咨询、投诉。第一步定义Prompt配置id: ticket_classify version: v2 model: cheap_chat temperature: 0.1 max_tokens: 50 template: | 请判断以下工单属于哪个类别只返回类别名称不要有其他内容。 可选类别退款、物流、产品咨询、投诉、其他 工单内容{{content}}第二步业务服务调用网关async def classify_ticket(content: str) - str: req ChatRequest( messages[{role: user, content: render_prompt(ticket_classify, contentcontent)}], modelcheap_chat, temperature0.1, max_tokens50, ) try: resp await gateway.chat(req) category resp.content.strip() if category not in VALID_CATEGORIES: raise ValueError(finvalid category: {category}) return category except Exception as e: logger.warning(fclassify failed, fallback to rules: {e}) return rule_based_classify(content)第三步规则降级函数def rule_based_classify(content: str) - str: if any(k in content for k in [退款, 退钱, 退货]): return 退款 if any(k in content for k in [物流, 快递, 发货]): return 物流 if any(k in content for k in [投诉, 差评, 举报]): return 投诉 return 其他这套代码看起来简单但它包含了AI工程的核心思想主路径用模型兜底用规则全程可观测。我实测下来模型分类准确率大概92%规则兜底大概70%但兜底的存在让整体可用性接近100%。4.4 成本控制的具体参数成本控制不是喊口号要落到具体参数上。我一般会设这几个阈值控制项建议阈值超限动作单次输入Token4000截断并告警单次输出Token1000强制截断单业务日调用量按预算反推告警单业务日成本预算的80%告警单业务日成本预算的100%降级到便宜模型单业务日成本预算的150%降级到规则方案这些阈值怎么定先估算假设你的业务每天1万次调用平均每次输入500 Token、输出200 Token用某中档模型输入价格假设是每百万Token 10元输出是每百万Token 30元。那么日成本 10000 × (500/1000000 × 10 200/1000000 × 30) 10000 × (0.005 0.006) 110元。按这个量级设预算留30%余量。5. 常见问题与排查技巧实录5.1 模型返回不稳定怎么办这是最高频的问题。同一个输入有时候返回A有时候返回B。排查思路是先看temperature是不是太高业务场景建议0.3以下再看Prompt是不是有歧义模型对模糊指令的解读本来就不稳定最后看是不是模型本身的问题有些小模型在复杂任务上确实不稳定。我的经验是能用规则的地方不要用模型。分类、抽取、格式转换这类任务如果规则能覆盖80%的情况就用规则做主路径模型做补充。这样既稳定又便宜。5.2 延迟太高怎么优化延迟主要来自三块网络、模型推理、输出长度。网络这块选就近的接入点模型推理选更小的模型或更快的版本输出长度通过max_tokens和Prompt约束控制。还有一个容易被忽略的点并发。如果你的服务是同步调用模型一个请求要等3秒那并发10个就要排队。改成异步之后同样的硬件能扛的并发量提升非常明显。我一般用asynciohttpx.AsyncClient配合信号量控制并发上限。5.3 费用突然暴涨怎么排查先看调用量是不是涨了再看单次Token是不是涨了最后看是不是有异常调用。我遇到过一次费用暴涨最后发现是某个定时任务出错每分钟重试几百次。所以重试必须有上限而且重试日志要单独标记方便排查。另外建议给每个业务线单独的API Key或配额这样费用能按业务拆分出问题能快速定位。5.4 常见问题速查表现象可能原因排查动作解决方向返回内容为空模型拒答/超长截断看输入长度和内容缩短输入/调整PromptJSON解析失败模型加了额外文字看原始返回加容错解析/修复Prompt延迟突然升高模型服务波动/网络看监控曲线切换模型/加超时降级费用异常重试风暴/输入超长看调用日志加重试上限/输入截断分类不准Prompt歧义/模型能力不足抽样对比优化Prompt/换模型并发上不去同步阻塞看线程/协程模型改异步信号量5.5 几个我踩过的坑第一个坑Prompt里用了中文标点模型偶尔理解偏差。后来我统一用英文标点做结构分隔中文只出现在内容里稳定性好了很多。第二个坑以为流式输出一定更好。流式确实体验好但它让错误处理变复杂而且有些场景比如需要完整结果才能做下一步根本用不上。我的建议是面向用户的对话用流式后台任务用非流式。第三个坑忽略了模型的版本更新。厂商会悄悄更新模型行为可能变化。所以我在配置里锁定模型版本号升级时先做回归测试。这个习惯帮我避免了好几次线上事故。第四个坑没有做输入清洗。用户输入里可能有特殊字符、超长文本、注入尝试。我在网关层加了输入长度限制和基础清洗虽然不能完全防住但能挡掉大部分低级问题。6. 从能跑到好用还差哪些工程细节6.1 缓存策略同样的钱不要花两次很多AI调用是重复的。比如同一个商品的摘要、同一个问题的分类。如果输入完全一样结果大概率可以复用。我在网关层加了一层缓存key是“Prompt ID 变量哈希 模型版本”命中缓存直接返回成本直接归零。缓存要注意两点一是过期时间业务数据变了缓存要失效二是缓存穿透恶意构造不同输入绕过缓存这个要靠限流和输入校验来防。6.2 灰度与A/B测试Prompt改了、模型换了怎么知道效果变好还是变坏靠拍脑袋不行要靠数据。我的做法是新版本先放10%流量对比核心指标准确率、延迟、成本、用户反馈达标再逐步放量。这个机制让Prompt迭代从“玄学”变成了“工程”。6.3 安全与合规的底线AI工程有几个安全底线必须守住不泄露用户隐私输入输出都要脱敏、不生成违规内容输出要做过滤、不暴露内部信息Prompt里不要放敏感数据。这些不是可选项是必选项。我在网关层加了输出过滤命中敏感词直接拦截并记录。6.4 团队协作的约定最后说点软的。AI工程不是一个人的事团队协作要有约定Prompt谁改谁负责、模型升级要通知、成本超限要复盘、线上问题要有值班。这些约定看起来琐碎但能避免很多扯皮。我个人在实际操作中的体会是AI工程最难的不是技术而是心态。你得接受模型是不完美的接受系统会有概率性失败然后在这个前提下把工程做得足够健壮。这套从零搭建的思路我用了两年多换过三个项目每次都能快速起步。希望对你也有用。