ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

AI Engineering From Scratch:从零搭建稳定可落地的AI应用工程

AI Engineering From Scratch:从零搭建稳定可落地的AI应用工程 1. 先聊聊我对“AI Engineering From Scratch”的理解我正式把这个名字当真是在自己动手写了三版AI应用、又推倒了两版之后。最早我以为“AI Engineering”就是调API能把OpenAI的接口接进业务里让用户问一句、系统答一句就算入门了。真正做下去才发现这个领域最难的部分根本不是“调用模型”而是把模型、数据、工具、评测、成本、异常处理全部捏合成一条稳定的生产链路。名字叫From Scratch核心意思也很直白不依赖现成的低代码平台不套某个成熟框架的脚手架从选型、写调用层、设计Prompt、接工具、做评测这一步一步搭起来。这篇文章面向几类人一是想从“会调接口”走向“能做系统”的开发者二是团队里刚接手AI项目的核心工程师需要一个能落地的参考主干三是不想被各种成熟框架限制、希望理解底层控制的同学。我这里不会讲特别高深的数学也不会只用理论说服你全部是跑了多轮任务、线上跑过真实负载之后沉淀下来的取舍逻辑。从标题也能看出一个态度AI Engineering 不是“机器学习建模”也不是“提示词工程”的换皮。它实际是“怎么围绕大模型构建可靠系统”的工程问题。你的模型可以强到逆天但如果你不会控制输出结构、不会管理调用失败、不会评估效果那它在生产环境里就是一台昂贵的随机文本生成器。这个领域的技能栈很杂需要懂一点模型能力边界需要懂系统设计需要懂数据清洗还需要懂成本计算和评测设计。所以我建这个从零开始的工程模板本质上是在给自己搭一套“可重复、可观测、可迭代”的AI应用底座。2. 从零搭起之前的全局设计思路2.1 先分清层模型层、接口层、应用层、工具层我从一开始就给自己定了一条规矩不管项目多小代码结构必须按层划分。这条规矩让我后面每次迭代都没有翻车。一个典型的AI工程至少要分成四层模型层解决“用哪些模型”包括主模型、备用模型、小模型。不是所有任务都要上最强模型后续你会发现分层调用能让成本下降一半以上。接口层负责把模型能力封装成统一调用入口屏蔽不同厂商的API差异。这一层决定你的代码是不是容易被某个模型厂商绑架。应用层承载业务逻辑比如用户提问的预处理、上下文组装、工具选择、输出校验。这是AI工程师写得最多的部分。工具层让模型能调用外部能力比如搜索、数据库查询、计算器、内部文档检索。没有工具层的AI应用基本就是个聊天玩具。我最初犯的错是跳过接口层直接在所有业务流程里散装调用模型API。结果模型升级后所有业务代码都要跟着改那真是一次让人头大的重构。如果你从零开始一定先写一层统一的接口抽象哪怕它只有两个函数complete(prompt, schema)和stream_complete(prompt, on_token)。2.2 为什么不用现成框架部分场景还是要From Scratch市面上有非常多的LangChain、LlamaIndex、Flowise之类工具我为什么还要从头搭原因很简单框架抽取的抽象层级不一定适合你的业务而且当你遇到诡异问题的时候不懂底层你连排查方向都没有。框架能帮你省时间但抽象也会掩盖细节。举个例子我当时想实现一个“多步推理工具调用”框架写起来确实快几行代码就能把“规划-执行-观察-总结”串起来。可一旦走到生产环境问题就全来了中间一步的Token消耗怎么算工具结果太长截断策略怎么控制某一步返回不可解析的JSON时框架默认的处理方式是否符合业务预期框架给你的是一套通用答案但AI工程的难点恰恰在于特殊情况太多通用答案经常不够用。所以我给出的实践建议是如果你在探索不适合直接上框架先手工写一套最简Pipeline理解每一步的数据流等你把问题摸清楚了再用框架辅助。这个“自己先写一遍”的路径就是“AI Engineering From Scratch”的价值所在。2.3 确定第一版要覆盖的最小闭环不要一上来就想做几十个Agent协同的大系统。我踩过的坑非常典型第一版想直接把“知识库问答 自动工具调用 多轮对话记忆 报表生成”一次做完结果开发了三个月连一个环节都没跑到生产环境。后来我调整策略第一版只做一件事用户提问 → 判断是否需要工具 → 调用工具 → 组织回答 → 输出结构化JSON。这个最小闭环跑通之后我立刻把评测脚本跟上确保每次改Prompt和改代码都能量化对比。之后再往里面加记忆、加更多工具、加路由策略。记住这句话AI应用工程化很多复杂度不是一开始就要解决的而是你有了稳定基线之后才能逐步加调节项。没有基线所有变量都在抖动你会分不清效果变差是Prompt问题、模型问题还是上下文构造问题。3. 核心模块怎么搭从调用封装到Prompt管理3.1 统一模型接口再麻烦也值得我非常推荐在你的项目里建一个model_provider.py它对外只暴露统一的调用函数。内部再做三件事读取配置、根据任务路由到不同模型、做统一错误处理。配置大概长这样# config.yaml models: main_llm: provider: openai model_name: gpt-4o-mini temperature: 0.2 max_tokens: 2000 extract_llm: provider: openai model_name: gpt-4o-mini temperature: 0 max_tokens: 500你在接口层做的事情其实不复杂把不同模型的参数差异封装掉比如有的模型叫max_tokens有的叫max_completion_tokens有的是流式输出有的不走流式有的支持JSON mode有的不支持。统一接口的意义在于上面业务逻辑只认你自己的参数签名下面模型随便换完全不影响上层。我实际开发里还会加一个“调用模式开关”fast_mode用便宜小模型处理分类、抽取、格式化任务。normal_mode用主力模型处理生成和推理任务。heavy_mode用长上下文模型处理大规模文档分析。这个开关是控制成本的重中之重。纯靠一个强大模型跑所有任务成本会线性失控而把任务拆细用不同性能和价格的模型去匹配效果和成本都能兼顾。3.2 Prompt版本管理与“可回滚”能力做AI工程之后我养成了把Prompt当代码管理的习惯。每个Prompt都有版本号、有提交记录、有对应的评测结果。为什么因为模型在升级业务在调整Prompt在不经意间可能就漂移了。你今天改了一个措辞感觉结果还行但没记录改了什么下周效果变差你想回退都不知道退到哪一版。我常用的目录结构是这样prompts/ analysis/ v1_initial.yaml v2_add_fewshot.yaml tools/ v1_selector.yaml output/ v1_json_schema.yaml每个Prompt文件不仅写内容还在头部维护元信息version: v2 model: gpt-4o-mini temperature: 0 last_eval_score: 0.94 change_log: 增加了一个少样本示例处理“价格查询”意图不要小看这个做法。你只有量化对比才知道某个Prompt改动是正向还是负向。所有Prompt都选定一个CURRENT指针方便快速切换版本。上线之后如果效果回退一键恢复到上一版。这比在代码里硬编码字符串要科学得多。3.3 输出结构JSON Schema不是可选项是必修课模型输出最大的问题是不可控。你说让它“返回一个包含城市和天气的JSON”它可能给你返回带Markdown代码块的内容也可能给出多余的说明文字。所以我在接口层强制做了解构校验。先定Schemafrom pydantic import BaseModel class WeatherResponse(BaseModel): city: str date: str temperature: float unit: str celsius class ToolPlan(BaseModel): thought: str use_tool: bool tool_name: str | None args: dict然后在Prompt里给模型极简且明确的指令比如你是一个工具规划器。请根据用户的问题判断是否需要调用工具。 只输出JSON不要输出任何解释。必须符合如下格式 {thought: ..., use_tool: true, tool_name: weather, args: {city: 北京}}收到输出后第一步不是直接解析而是先做清洗去掉可能包裹的Markdown代码块、修复未转义的引号。接着再用Pydantic做严格校验。如果校验失败我会触发重试最多两次。超过两次就进入兜底流程比如返回“暂时无法处理请换一种问法”。这套流程看着笨但它能挡住生产环境中大量低级错误。那些跑到线上才发现“模型返回的JSON解析失败了”的惨案多半是省略了这层约束和校验。4. 工具调用让模型从“会聊天”到“能办事”4.1 工具注册机制与参数收敛AI应用不接工具就像让一个知识渊博的人闭着眼睛回答问题。但要接工具你面临的就不仅是“调用API”而是模型怎么准确选择工具、怎么填对参数。我的做法是做一个工具注册表TOOL_REGISTRY {} def register_tool(name: str, description: str, parameters_schema: dict): def decorator(func): TOOL_REGISTRY[name] { func: func, description: description, parameters_schema: parameters_schema, } return func return decorator register_tool( nameget_stock_price, description查询某只股票的最新价格输入为股票代码, parameters_schema{ symbol: {type: string, description: 股票代码如000001}, }, ) def get_stock_price(symbol: str): ...每个工具的描述必须能概括它“什么时候该用、什么时候不该用”。我发现模型选错工具的很大原因不是能力不行而是工具描述写得模糊。比如一个叫search_docs的工具描述只写“搜索文档”是远远不够的应该写“当你需要查找内部产品手册、FAQ、售后规范时使用不要用于搜索实时股价股价请用 get_stock_price”。工具描述越接近“决策边界”模型选得越准。4.2 工具结果太长怎么办实际跑下来最影响模型回答质量的因素不是Prompt写得不好而是工具返回结果塞爆了上下文。如果你把一个数据库表3000行记录全部塞回给模型它会直接“迷失”在长文本里回答会退化成复读机。我的处理策略是分层压缩第一层工具层尽力返回结构化摘要比如只返回Top 10结果和统计指标。第二层把完整结果控制在2000 Token以内超出部分先做截断。第三层若是检索类结果先做相关度重排只保留跟用户问题最相关的段落。我还设置了一个变压器的小模块模型给出的查询、工具返回的内容全部要经过一次“压缩和重排”。这样进入最终生成阶段的数据信息密度足够高模型才能给出有洞察的回答。4.3 有限步骤终止防止Agent无限循环让模型自由决定“下一步做什么”是很危险的。我最早调试时出现过模型连续调用同一个搜索工具十几遍整个会话不可控。后来项目里强制设置了最大步骤数3到5步每轮完成后都要求模型输出is_finished: true/false。一旦超过上限强制终止并生成兜底回答。同时我会给每轮工具调用做审计日志记录了调用顺序、输入输出摘要、Token消耗、耗时。有了审计日志排查线上问题时才能快速定位“到底是哪一步走错了”。没有日志的AI系统等于开车没录像出车祸只能靠猜。5. 评测、成本与安全AI工程的三大隐藏支柱5.1 用一套评测集守住效果底线不做评测的AI工程优化就是开盲盒。我维护了一套非常小的种子评测集但每次迭代都会跑。评测集包含三类样本黄金样例有标准答案用于跑准确率。边界样例比如用户问题含混不清、工具查询结果为空用于检查兜底逻辑。稳定性样例同样的问题问两次看输出是否会剧烈抖动。实际跑评测时我还在代码里自动对比前后两次的模型输出提取差异点。如果某些Prompt改动让核心指标下降了代码会直接输出告警而不是等它在线上用户那里暴雷。评测集的规模不用大50到80条就够日常迭代。关键是样本要覆盖业务的“最小可接受场景”。等你的系统稳定了再把评测集扩充到几百条引入更细的维度比如答案是否利用了工具信息、引用是否准确、格式是否符合预期。5.2 Token成本核算别等到月底看账单才慌我见过太多团队Demo阶段跑得飞起上线之后每个月账单吓死人才发现根本没有成本控制。AI工程的成本从来不是“模型贵不贵”的问题而是“你让模型干了多少不该它干的活”。我的成本控制三板斧第一板斧能用小模型绝不用大模型。分类、命名实体识别、抽取这些任务用小模型就够了。只有复杂推理、长文总结、代码生成才上大模型。第二板斧Prompt别塞无关上下文。很多人喜欢把所有历史对话一股脑塞进请求结果Token数量爆炸。我的做法是多轮对话存到向量库只检索相关片段放回上下文旧对话默认不参与计算。第三板斧优先用流式输出和缓存。稳定的高频问题可以直接走缓存不再重复调用模型。设置好每用户每月的预算上限超了就降级到备用模型或简化服务。我在项目里写了一个简单的成本统计装饰器每次调用都会记录prompt_tokens,completion_tokens, 估算金额并写入日志。这样一天结束后你能按用户、按功能、按模型粒度去看成本分布而不是月底看到一个总账单傻眼。5.3 输出安全与敏感信息过滤这是所有AI应用上线前必须过的一关。模型不是系统它不认你的红线规则。我强烈建议在接口层做两层过滤输入侧检测用户是否上传了不该上传的内容对隐私信息做脱敏。输出侧对模型生成的文本做关键词和模式匹配防止泄露手机号、身份证号、银行卡号等敏感数据。此外工具调用参数也要校验。用户可能通过Prompt注入让模型调用危险参数比如让搜索工具去访问一个内网地址。所以每个工具接收参数前都必须做白名单校验。别以为模型会很聪明地防守绝大多数情况下它只是个“鹦鹉”用户绕两句话它就照做了。防线必须写在代码里而不是指望模型自觉。6. 常见问题与排查经验速查6.1 高频故障及对策表症状常见原因排查思路解决办法模型频繁返回JSON解析失败提示词约束不足、温度过高查看原始返回判断是多了前缀还是格式错误提高输出约束强度、降低temperature、加清洗层工具调用名称经常选错工具描述不清晰或工具数量太多统计一次请求中所有工具描述占用的Token查看模型是否“看不过来”精简工具描述按场景分组或先做意图路由多轮对话出现上下文遗忘旧会话全量塞入导致注意力被稀释检查上下文Token构成引入摘要记忆或向量检索只保留高价值片段效果忽好忽坏无规律Prompt版本混乱或模型路由参数不一致对比评测集输出看飘移是否集中在某些样例统一模型版本Prompt纳入版本管理成本快速上涨大量长上下文重复请求按功能维度统计Token消耗引入缓存、压缩、小模型分流Agent陷入死循环缺少步骤上限和终止条件查看审计日志中的调用链最大步骤限制强制终止兜底回答6.2 心态与调试习惯比技术更重要的几条原则AI工程调试和传统软件开发最大的不同是错误往往不是“报错”而是“沉默地给出一个不准确结果”。如果你没有建立基线评测你连“什么时候变坏了”都发现不了。我的调试习惯是任何改动只改一个变量。改Prompt就不动代码改代码就不动Prompt。每次实验前先挑三个边界样例快速跑一遍看是否触雷。手工调好后把样例补进自动化评测集锁住迭代基线。遇到“怎么调都不行”的局面不要硬调Prompt先思考问题本身是不是需要换模型、加工具或改流程。一个词总结就是纪律。AI工程最大的坑不是模型不行而是你自己对系统失去了控制。只要你用版本管理、评测集、审计日志把闭环建立起来出现的每一个问题都可以定位、修复和回归没有这套闭环AI项目做一年也还是在打地鼠。7. 建这个项目之后我对“From Scratch”的重新理解刚开始做“ai-engineering-from-scratch”时我以为重点在“工程实现”就是把代码写工整、把模块拆清楚。后来跑完整个项目我更愿意把“From Scratch”看成一种工程态度不盲目相信模型替你搞定一切不盲目相信框架替你藏掉所有细节也不盲目相信调参能解决所有问题。我最深的一点体会是模型能力会持续升级框架会不断更迭但“可控、可测、可回滚、可观测”这套工程原则不会过时。你掌握的这些底层能力才是从众多“会接API的人”里真正拉开差距的地方。如果你现在正准备搭自己的AI应用我的建议是从一个很小的闭环开始一个模型、一个Prompt、一个工具、一个JSON输出加一套自动化评测。先把它做到稳定再慢慢加复杂度。后面如果想深入了解工具调用、评测体系或者成本控制完全可以在我这个主干上继续扩展——但地基打得稳楼上盖多高都不慌。
返回列表