ARTICLE DETAIL

资讯详情

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

AI工程从零落地:从模型接入、Prompt工程到Agent部署的完整实践

AI工程从零落地:从模型接入、Prompt工程到Agent部署的完整实践 很多人第一次接触到“ai-engineering-from-scratch”这个项目名时第一反应是“又要从头学一遍机器学习理论”实际不是这样。它不教你手写反向传播也不推导注意力机制公式而是一整套从零开始把AI能力落地成工程系统的实操路线怎么规划需求、怎么选模型、怎么写提示词、怎么搭Agent骨架、怎么做服务化部署、怎么测试评估、怎么持续迭代。如果你已经能调用模型API但一碰到“要把这个东西做成一个稳定可用的系统”就卡壳或者你听说过AI Agent、RAG、模型部署这些词但不知道它们怎么组装成一条完整链路那么这个项目的定位就正好对准了你的需求。这篇文章会把项目背后的核心设计、关键模块的拆解逻辑、实操中的细节与坑点全部展开来讲读完之后你不仅知道怎么复现这条路还能根据自己场景去调整它。1. 项目定位为什么“从零开始”不等于“从基础理论开始”1.1 “从零”的真实含义从空目录到生产可用严格来说ai-engineering-from-scratch强调的“from scratch”不是让你从神经元开始搭神经网络而是指从一个空的项目目录开始逐步构建出完整的AI工程能力。它关注的是工程链路而不是算法原理。这条链路大致包括需求定义与可行性评估这个问题到底适不适合用AI解决成本是否可控模型接入与适配不同厂商、不同规模模型的选择统一的接入方式Prompt Engineering让模型的输出稳定、可控、符合结构化要求Agent 骨架搭建让模型具备工具调用、流程决策、多步推理能力工作流编排把多个AI节点和非AI节点组装成一条自动化流程服务化部署从Notebook里的原型变成一个HTTP服务制定合理的推理参数测试评估用客观指标检查系统的正确性、稳定性、延迟持续迭代根据线上反馈调整提示词、模型版本、工作流逻辑这个项目之所以把这么多东西打包在一起是因为真实的AI工程从来不是“调用一个接口”就结束。我在实际项目里见过太多这种情况原型阶段效果惊艳一到生产环境就各种翻车要么是输入格式稍微一变输出就崩要么是模型响应太慢被上游服务超时要么是某个环节的回复潜在地不讲格式导致下游解析直接抛异常。这些问题不是换个更强的模型就能解决的而是需要一套系统性的工程方法去约束和保障。1.2 它与单纯“学模型API”的区别很多人觉得会用ChatGPT的API就等于会做AI工程了这个误解比想象中更普遍。单纯调用API只是“拿到模型的回复”AI工程的核心其实是可控性。模型输出的随机性天然存在工程上必须通过提示词结构、输出格式约束、后处理校验、重试策略等一系列手段把这种随机性控制在一个可接受的范围内。这一点和传统软件开发很不一样传统代码是确定性的——同样的输入必然得到同样的输出而模型不是。工程的核心目标之一就是把不可控变成可控。可维护性。模型会升级、提示词会调整、业务规则会变化。如果没有工程化的抽象比如把模型调用封装成统一接口层把提示词抽离成独立模板把各环节的输入输出schema定义清楚那么任何改动都可能引发连锁故障。我在做这个项目的过程中发现凡是没有定义输入输出结构的环节最后都成了事故高发点。可观测性。模型是一条黑盒它的内部推理过程我们很难直接观测。工程上就需要记录完整的技术链路日志、置信度、每个环节的耗时、每次请求对应的提示词版本和模型版本。没有这些观测数据你根本不知道系统出问题该从哪个环节开始排查。成本控制。不同模型价格差距极大同一模型的不同部署形态成本也不同。工程上要做模型分级高价值环节用强模型简单任务用轻量模型能够用规则替代的环节干脆不用模型。如果你把这些要素串起来看就会明白为什么“AI工程”近几年能作为一个独立的岗位方向出现——它本身就是一个完整的工程学科而不是模型调用的附属品。这个项目就是带你把这一整套能力从零搭起来先建立一个能工作的最小系统再逐步把可靠性、扩展性、可观测性一层层叠加上去。2. 基础层搭建模型接入与统一接口设计2.1 模型接入层的“统一封装”怎么做很多从零开始的AI项目第一步就写死了某个厂商的SDK调用这其实是一个隐雷。真实场景里模型生态变化非常快今天用的模型明天可能降级、涨价或者下线更常见的是一开始用开源模型后来发现商业模型效果更好想切换结果发现代码里到处是某个厂商的特有调用方式改起来想死的心都有。ai-engineering-from-scratch在第一步就做了一个抽象统一模型接入接口。不管你后面接的是哪个厂商的模型对外暴露的只有一个统一接口这样上层业务代码就不用关心底层具体接的是哪家。一个合理的模型接入层至少要有这些方法文本对话给定消息列表、系统提示词、参数返回模型回复流式对话返回一个可迭代的流适合对首字延迟敏感的场景Embedding把文本转换为向量供检索、聚类使用工具调用Function Calling让模型输出结构化的调用意图每个方法都需要统一接受标准参数比如 temperature、max_tokens、top_p、stop 等。因为不同模型的这些参数取值范围不同——有的支持 stop 序列有的不支持有的 top_p 敏感有的基本没影响——所以接入层还要做一层参数转换把标准参数映射成不同模型的具体实现。这里有一个我在项目中反复强调的细节不要追求“完全抹平差异”因为不同模型的输出风格差异是客观存在的。比如推理能力强的模型擅长分步思考代码能力强的模型有自己偏好的格式。统一接入层是让调用方式一致而不是让模型行为一致。真正的效果调配还是要针对特定模型做提示词优化。2.2 缓存与容错让系统更可靠的两个隐性基础设施统一接口封装好了之后我建议紧接着就做两件很多人会忽略的事缓存和容错。这是让AI工程系统从“能用”走向“稳定”的关键。缓存的应用逻辑是复用计算结果。很多AI系统的请求重复度其实很高比如同一份文档的摘要、同一个产品的文案润色短时间内可能被反复请求。如果不做缓存每次请求都在烧钱烧时间。实现方式很简单用请求参数组合模型版本 提示词 输入内容做一个哈希作为缓存的key将结果缓存到内存或Redis里。需要注意的坑是缓存的key必须得把模型版本带上不然模型一升级你还用旧版本的结果响应容易踩坑。另外提示词模板的版本号也应该进key这样改了提示词之后自然就不会旧缓存避免测试的时候看到一堆莫名其妙的历史结果。容错方面AI请求不同于普通HTTP请求的地方在于错误类型丰富限流429、超时、内容安全拦截、上下文长度超限、服务端5xx错误等每种错误的处理策略还不一样。限流了要退避重试安全拦截了要记录并改变表达方式上下文超限要做裁剪或摘要压缩。如果你的接口层把这些情况都统一拦截并且采用重试、降级、返回默认兜底方案的策略整体的稳定性就上了一个台阶。注意重试必须考虑幂等性。如果一个请求反复重试了三次而每一次都会消耗真实的调用量成本控制就失控了。合理的做法是设置最大重试次数并且对重复错误做快速失败。2.3 实操搭建最小可用的统一接入层拿一个最小实现来说我通常会这样组织目录和代码# model_access/base.py from abc import ABC, abstractmethod from dataclasses import dataclass dataclass class ChatMessage: role: str # system / user / assistant content: str dataclass class ChatConfig: temperature: float 0.7 max_tokens: int 2048 top_p: float 1.0 stop: list[str] | None None class BaseModelClient(ABC): abstractmethod def chat(self, messages: list[ChatMessage], config: ChatConfig) - str: 返回模型回复文本 abstractmethod def stream_chat(self, messages: list[ChatMessage], config: ChatConfig): 返回流式响应迭代器 abstractmethod def embed(self, texts: list[str]) - list[list[float]]: 返回文本向量列表然后针对不同的模型服务商实现子类。这里我不打算给某个具体厂商做广告只是强调每一种接入都要自己在本地做充分的回归尤其注意各厂商在“系统提示词是否生效”“是否支持stop序列”这类细节上的差异。曾经我就遇到过一个模型服务商在文档里写着支持Function Calling实际调试时发现它的工具调用结果经常格式错乱后处理得很痛苦。这种情况只有实际压测才会暴露。统一封装完成之后上层业务就可以彻底忘记“模型到底是什么”这件事了。你可以随时换一个更强的模型来跑AB测试也可以对不同业务线配置不同的模型而业务代码不用改一行。这种模块化的思维是整个项目的基石。3. Prompt工程与输出控制的落地细节3.1 为什么Prompt工程是“最便宜的优化手段”模型接入搞定之后你会很快发现同一个模型同一个问题措辞不一样结果质量可能天差地别。这就是Prompt Engineering发挥作用的地方。之所以说它“最便宜”是因为它不需要改代码、不需要换更贵的模型只需要调整文本就能带来立竿见影的提升。我在项目里会把Prompt工程拆成三个层面来实践第一个层面是角色与目标设定。系统提示词里明确“你是什么角色你在做什么任务你要达成什么目标你要遵守什么准则”。这听起来简单但很多人只写一句“你是一个AI助手”然后就丢给模型一个任务。更有效的写法是给模型足够的背景上下文包括任务场景、目标用户、产出物格式、避免的误区等。模型本质上是一个超大规模的文本续写引擎它更倾向于沿着提示词设定的轨道续写。第二个层面是Few-shot示例。对于格式要求严格的任务单纯用语言描述“请输出JSON格式”是不够的模型可能会输出带markdown格式的、带注释的、或者直接胡编的JSON。如果你在提示词里给它一到两个输入输出对作为示例让它模仿效果会稳定很多。示例要尽量贴近真实数据分布避免用“假设有一个用户叫张三”这种与业务脱节的例子。第三个层面是约束与边界。包括长度限制、语言限制、风格限制、禁止事项等。比如“回复不超过200字不使用Emoji不输出Markdown表格”。但这个层面的实现必须配合后处理校验不能只靠提示词约束。模型不是规则的忠实执行者它只是在“看起来像是执行”。3.2 结构化输出的工程实现让模型按Schema输出在真正的工程系统中模型往往需要输出能被程序解析的结构化数据。JSON是事实标准然而大模型输出合法JSON是一个需要专门处理的问题。实际项目里常见的失败模式包括输出带了json 代码块标记导致无法直接解析、JSON里混入了多余的解释文字、字段名与定义不一致、嵌套结构出错等。我惯用的做法是“两层保障”第一层是提示词层面明确要求。给模型清晰Schema示例告诉它必须严格输出JSON对象字段类型必须匹配不要添加任何额外解释。比如请根据用户输入提取招聘信息严格输出JSON格式如下 { job_title: 职位名称字符串, salary_min: 最低薪资整数单位K若未知填0, salary_max: 最高薪资整数单位K若未知填0, company_name: 公司名称字符串, skills: [所需技能字符串数组] } 直接输出JSON对象不要包含任何其他文字。第二层是代码层面做兜底解析。写一个健壮的解析函数先尝试直接json.loads失败则尝试提取json 代码块中的内容再失败则尝试截取第一个{到最后一个}之间的内容最后还是失败就返回一个错误标记触发重试或人工介入。这里要说一个重要心得结构化输出做得好不好直接决定你的Agent能否稳定工作。我见过很多Agent项目表面上是Agent自己“思考”出了问题实际上是模型输出的JSON就是坏的工具调用参数都没解析出来后面所有步骤自然跟着乱。把结构化输出这个地基打好你会发现后续的路顺畅很多。3.3 提示词模板的工程化管理提示词本身也是一段需要维护的代码资产。我在项目里把提示词模板统一放在独立的目录下每个模板一个文件包含版本号。关键原因是提示词迭代频繁而且对结果影响巨大。如果不做版本管理你根本不知道“周五之后效果变好了”是因为哪条提示词改动引起的改回去的时候也不知道该改哪里。一个标准的提示词模板文件大概长这样版本: v2.3 用途: 工作总结摘要生成 模型适配: 适合上下文窗口8K的模型 变更记录: - v2.3: 增加“按条列出”的结构化要求修复v2.2中输出过于冗长的问题 - v2.2: 调整角色设定强调“客观总结不添加原文没有的信息” --- {{system}} 你是一名专业的职场分析助手... {{/system}} {{user}} 以下是员工提交的周报内容 {{content}} 请生成一份简明的总结按以下格式 - 本周核心进展不超过3条 - 遇到的阻碍不超过2条 - 下周计划不超过3条 {{/user}}在代码里使用Jinja2这类模板引擎来渲染变量可以避免字符串拼接带来的错乱和注入风险。重要的是提示词里不需要写得太冗长。我发现很多初学者喜欢把提示词写得像一篇小作文其实关键信息密度高才有效。长而空的意思是噪声模型会把无关信息当成上下文反而影响输出质量。4. Agent骨架与工作流编排让AI真正“做事情”4.1 Agent到底解决什么问题很多项目做到上面这一步就已经能跑通“输入文本 - 输出文本”了但真实的业务需求往往不止如此。用户需要的不是“一段分析”而是“帮我把这件事做完”。比如给他一份合同让它提取关键条款并生成风险评估报告给它一个日报列表让它汇总成周报并发送邮件给它一个任务目标让它自己去查资料、编写代码、运行验证、迭代修正。这就是Agent发挥价值的地方。所谓Agent它的核心能力是模型能够自主决定调用哪些工具、按什么顺序、根据工具的返回结果决定下一步动作。它和单纯“输入输出”的本质区别在于具备了一个循环思考 → 调用工具 → 观察结果 → 再思考 → 再调用直到任务完成。我在这个项目里设计的Agent骨架分解为四个模块工具注册表Tool Registry先把Agent需要用到的工具注册进去每个工具包含名称、描述、参数Schema、执行函数。这个描述很关键——模型就是靠这个描述来决定何时调用哪个工具的描述写得含糊模型就会乱调。记忆模块Memory保存对话历史、中间结果、已执行动作序列。短任务用简单的消息列表就能搞定长任务需要考虑上下文窗口限制要做摘要和裁剪。规划模块Planner模型根据用户目标和工具列表生成执行步骤。这里也有两种流派一种是让模型一次性规划好全部步骤再逐步执行另一种是每走一步规划一步ReAct风格。对不确定性高的任务后者更稳因为它能在中途修正方向。执行模块Executor实际调用工具、观察结果、把结果反馈给模型形成一个闭环。4.2 实操手写一个极简Agent循环纸上谈兵没有意义我直接给一个能跑通的最小实现骨架让大家看到Agent没那么玄乎class SimpleAgent: def __init__(self, model_client, tools: dict, max_steps8): self.model_client model_client self.tools tools # {tool_name: {execute: fn, description: str, params_schema: dict}} self.max_steps max_steps self.history [] def run(self, user_task: str) - str: self.history.append({role: user, content: user_task}) system_prompt ( 你是一个能调用工具的智能助手。 工具列表如下\n \n.join( f- {name}: {info[description]}参数{info[params_schema]} for name, info in self.tools.items() ) \n当你需要调用工具时严格输出JSON {tool: 工具名, args: {参数}}。其他情况直接输出最终回答。 ) for step in range(self.max_steps): response self.model_client.chat( [{role: system, content: system_prompt}] self.history, configChatConfig(temperature0.3), ) action try_parse_json(response) if action and tool in action: tool_name, args action[tool], action[args] if tool_name not in self.tools: self.history.append({role: assistant, content: f工具 {tool_name} 不存在请重新选择。}) continue result self.tools[tool_name][execute](**args) self.history.append({role: assistant, content: response}) self.history.append({role: user, content: f工具返回结果{result}}) else: return response return 达到最大步数任务未能完成。这个实现看起来简单但可以清晰展示Agent的核心循环逻辑。真正落地的时候需要增强的部分包括错误处理机制工具抛异常怎么办、上下文压缩历史过长怎么办、安全限制工具操作的权限边界等。一个常见的教训是工具描述必须精准。我曾经在项目里加入了一个“查询订单状态”的工具描述写的是“查询订单信息”结果Agent在用户问“改一下收货地址”的时候也去调用它完全不对题。后来把描述细化成“根据订单号查询订单当前状态待支付/已支付/已发货/已签收”调用就准确多了。4.3 多Agent协作的真实应用模式项目里还涉及“多AI协作”这个关键词。多Agent不是炫技而是为了解决两个实际问题第一上下文隔离。单Agent干所有事情会导致上下文爆掉一个复杂任务的背景资料、中间结果、用户指令混在一起模型会越来越无所适从。拆成多个角色每个Agent只关注自己的子任务上下文干净清晰效果反而更好。第二角色专业化。不同子任务对模型能力的要求不同。比如一个“写代码并运行验证”的任务拆成一个“规划者”负责拆解需求、一个“程序员”负责写代码、一个“审查者”负责检查代码质量比一个Agent单干更可靠。这就是为什么一些Agent系统的标准模式是“规划-执行-审查”三权分离。在多Agent协作设计上我最想强调的一点是“接口协议”。多Agent之间本质上是通过消息通信的所以每个Agent的输入输出必须明确定义。我见过失败的多Agent项目两个Agent之间传递的对象结构没人定义最后A输出一个Markdown表格B却按JSON解析整条链路直接断裂。在工程设计上先把Agent之间的消息Schema定义出来是比让Agent更聪明更重要的事情。5. 服务化部署与性能调优让原型变成产品5.1 从Notebook到服务的质变点原型阶段代码在Notebook里怎么跑都行一旦要变成服务需要考虑的事情就多了很多。这个项目里专门把模型部署作为关键章节也说明了这是原始模型和工程化之间的分水岭。先说部署形态选型。对于多数应用来说不推荐一开始就自己部署大模型。理由很简单GPU成本高、推理性能调优难度大、运维复杂。比较务实的路线是对外部API或云上的推理服务先把业务逻辑跑通等流量和需求确定了再考虑把高频率模型部署到自己的推理集群上。如果是自己部署通常会遇到推理框架选择、量化、批处理这几个核心问题。我在这里重点讲一个很多教程不讲但实际影响巨大的参数max_batch_size与动态批处理。推理模型的时候如果逐条请求单独计算GPU利用率往往很低因为显存占用很大但计算单元没有跑满。动态批处理会把多个并发请求合并成一个batch喂给模型显著提高吞吐量。在工程实现上就是做请求排队攒到一定数量或者等待一定时间再统一推理用一点点延迟换来吞吐量的大幅提升。5.2 关键推理参数的选择逻辑有些人部署模型后效果和测试时差距很大一个常见原因就是乱调推理参数。这里我分享几个实际经验temperature这个参数控制的是采样的随机性。0表示基本每次都选最高概率的token输出确定性强越大越随机。工程系统里需要稳定输出的任务结构化提取、分类、工具调用建议temperature取0或趋近于0。创意写作任务可以取0.7以上。我见过很多工程师所有任务都用0.7导致同一个输入每次输出都不一样测试和调试都痛苦不堪。max_tokens限制输出的最长度。它不能太小否则长文本会被截断。但设太大也有风险模型在一些错误场景下会自说自话疯狂输出直到耗尽配额。合理做法是根据任务类型动态设置比如结构化提取任务512就够多轮分析则要2048以上。stop序列可以在生成到某个位置时强制停止。比如结构化输出的场景让模型输出完JSON的右大括号就停止能显著减少多余内容。这个参数很多模型都支持但不少人完全没用过。还有一个容易被忽略的配置是上下文长度管理。大模型对超长上下文的处理有两个问题一是成本高token越多越贵二是注意力分散极长上下文中模型容易忽略关键信息。工程上要做上下文压缩把历史对话做摘要把检索出的长文档切片把不重要的中间步骤丢弃。这个跟做传统系统里的“内存管理”是一个思路只不过AI系统的内存是token数。5.3 完整服务化的最小架构模式部署一个完整的AI服务一般包含这些组件API网关、业务服务、模型推理服务、异步任务队列、向量数据库、对象存储。对于中小规模应用一个实际可用的最小架构是用户请求 → API网关 → 业务服务编排Agent/调用模型 → 推理服务或外部API → 返回结果异步任务走消息队列。这里面有一个设计取舍同步还是异步。简单说需要立刻给用户回应的聊天、交互式分析用同步处理耗时长、不需要即时反馈的批量文档处理、报表生成用异步任务队列。异步的好处是响应快、可控性强、且更容易做重试和失败补偿。很多AI任务天然就是耗时操作尤其涉及多Agent多步骤的流程跑个几十秒是家常便饭HTTP请求早就超时了。这个线上跑起来再改架构就很伤筋动骨前期就要想清楚。提示不要试图让同步请求承担异步任务的负载。将一个耗时30秒的Agent编排直接挂在HTTP同步请求里会让你的网关、负载均衡、客户端全面超时。异步化是AI工程的关键架构决策之一。5.4 性能测试与监控的关键指标服务上线前必须建立起性能基线。我习惯关注的指标有这几类首字延迟TTFT从请求发出到第一个token返回的时间聊天场景首字延迟尤其重要卡顿感直接影响体验。端到端延迟整条链路的完成时间包括内部工具调用、模型多轮往返等。吞吐量QPS/TPS单位时间能处理多少请求压测要模拟真实数据分布而不是理想情况。错误率与重试率各种错误的分布情况包括超时、解析错误、限流、内容安全触发等。成本每次请求的平均token消耗、模型调用次数。成本隐藏在整体链路中多Agent一个任务调用十几次模型单次便宜也会积少成多。监控系统要能够按“链路”追踪一次请求在各环节的耗时和调用情况。比如一次请求先做检索再调用模型再调工具环节多任何一个变慢都会影响整体。没有链路追踪排查问题就像黑夜里找东西。6. 测试评估体系的建立AI系统的质量如何保证6.1 为什么传统测试方法对AI不奏效测试是AI工程里最容易被忽视的部分。很多项目上线前“试了一下感觉还不错”就直接发布结果线上遇到的问题千奇百怪。传统的软件测试有一个前提是被测对象行为是确定性的而模型天然具备随机性同一个提示词可能每次输出都不同。这种情况下断言“输出是否等于期望值”是没意义的需要建立一套概率性的评估体系。我在项目里首先建立的是“评估数据集”。每个任务类型都有一批带标注的样本里面包含典型输入、边界输入、易错输入。这些样本不是用来做模型训练的而是用来做回归测试的——每次修改提示词、更换模型版本、调整Agent逻辑之后都用同一批样本跑一遍看效果有没有下降。这个机制很像传统开发的回归测试“AI版的回归测试”。6.2 评估指标的选型与落地不同任务的评估指标完全不同。拿文本分类来说可以用准确率、召回率拿信息提取来说可以看字段级别精确匹配率拿代码生成来说可以直接执行测试用例来判定正确性。但还有相当多AI任务没有标准答案比如写一段产品文案没法用一个值去衡量好还是不好。这时候我有两种方法基于规则的评估检查输出格式是否合法、长度是否达标、是否包含关键要素、是否触碰违禁词。虽然没有评价“好不好”但能拦截“格式崩坏”的大多数问题。基于模型的评估用另外一个更强的模型当裁判对输出进行打分。比如让裁判模型按照“相关性、完整性、可读性、逻辑一致性”几个维度打分然后再和人工评估做校准。模型评估有一个重要前提裁判模型本身也会有偏好和偏差。比如有的裁判模型对长回答有天然偏好、对特定表述风格有偏好。所以评估提示词本身也要经过精心设计且评估结果要以多次采样的均值作为依据。6.3 测试金字塔在AI系统的适配版传统测试金字塔的理念在AI系统里依然适用只是内容变了底层是单元测试测试各个独立工具函数、解析函数、模板渲染逻辑这些和传统函数测试没有区别。中间层是集成测试测试编排链路各环节能否正确衔接。上层是评估测试跑全部评估数据集统计各项指标做前后对比。最上层是人机协同的验收抽取典型case人工检查输出质量同时积累这些case进入评估数据集。还有一个必须做的测试是对抗性测试故意输入异常数据例如缺失字段的输入、极长文本、脏数据、恶意试探检验系统是否能够优雅地失败而不是直接崩溃或者产生危险输出。这类测试在传统系统里叫健壮性测试在AI系统里同样重要有时甚至更重要——模型的不可控性会放大异常输入的破坏性。我在实际项目里最深的一个体会是评估数据集必须持续积累。今天线上出现的问题明天就可能成为回归用例。每次线上翻车都把对应的输入和人工修正后的理想输出纳入评估集。三个月之后这个评估集就是你最宝贵的资产——它比任何一个单一模型都更能代表你业务的质量基准。7. 常见问题与排查技巧从实战中总结的避坑列表7.1 典型故障对照速查表AI工程系统的问题五花八门但高频问题其实就那几类。我整理了这样一张速查表给自己排查用也建议你在自己的项目里建一份现象可能原因排查方法解决建议模型回复突然变长或变乱提示词被隐式修改/model更新/上下文残留检查模板版本、检查历史消息是否污染固定提示词版本、清空上下文重测结构化输出解析失败模型未按Schema输出看原始输出、检查是否被截断增加解析兜底、强制few-shot示例同一输入结果差异大temperature过高检查调用参数结构化任务temperature设为0响应延迟飙升上游API限流/正文字过长查链路日志、查token用量加缓存、压缩上下文、降级模型多Agent链路中断Agent间消息格式不匹配检查每个Agent输入输出Schema统一定义消息协议、加schema校验线上效果不如测试评估集过拟合/真实数据分布变化对比测试样本与真实样本扩充评估集、增加对抗样本成本快速上涨模型调用次数过多或用了高配模型按链路统计各环节消耗模型分级、缓存复用、减少无效调用工具调用错误频繁工具描述不清晰/参数Schema不准看Agent决策日志优化工具描述、给更具体的few-shot例还有一个特别容易被新手忽视的问题模型版本不是一成不变的。你调好了一个提示词模板模型厂商后端悄悄升级了模型版本结果效效果就变了。做AI工程要用固定版本或者固定日期快照定期做回归评估后再决定是否升级。这个跟传统软件升级依赖库有异曲同工之妙。7.2 日志与追踪设计的实操心得出问题时没有完整的日志神仙也难排查。AI系统的日志设计有几个特别值得花心思的地方每次模型调用的日志至少要有时间戳、调用方、模型名称与版本、提示词模板版本、实际输入token数、输出token数、temperature参数、完整输出内容、各环节耗时。以及最重要的最终响应是否被后处理逻辑修改过修改前和修改后的内容分别是什么。没有这一步你根本分辨不清问题是模型造成的还是你的后处理代码有bug。多Agent链路尤其需要贯穿式trace。一次用户请求会触发多个Agent、多次模型调用、多次工具执行如果没有一个全局request_id把这些信息串起来你是没法把每个步骤的日志关联起来的。我在项目里从最开始就强制每个入口请求生成一个request_id在所有内部调用日志里带上这个id。之后不管出什么诡异问题先按request_id把日志拉出来链路一目了然。在排查提示词问题时我强烈推荐使用“最小复现法”。线上效果出现了问题拿线的真实输入去跑然后逐步删除上下文里非关键的部分直到问题消失或者问题可稳定复现。这个过程能帮你判断是上下文干扰造成的还是提示词本身的固有问题。我曾经遇到过一个问题同一个提示词删掉上下文里的一段话之后输出质量明显变好。原因就是那段话包含了一个和任务不完全匹配的“示例”模型把每一条示例都想当成硬性要求来遵循。找到原因后我删掉了那个干扰示例问题就消失了。7.3 关于“系统为什么这样设计”的复盘习惯这个项目后续还可以扩展的内容其实很多我在完成主线之后做的一个重要复盘是“把每个设计决策及理由记录下来”。很多AI项目过一两个月回头看代码经常记不清当初为什么这样配置。比如某个环节temperature为什么设0.2而不是0.5某个缓存TTL为什么是10分钟而不是1小时某个Agent为什么用了3步限制而不是5步。这些配置看似随意其实背后都踩过坑。把决策记录维护起来后续调整时才有据可依。对于想要把项目延伸到生产环境的读者我个人的建议是先定一个“最小可用”的范围别想着一次就把Agent、多模态、RAG、部署全塞进去。把一个最简单的端到端链路跑通加上日志、评估、错误处理这个系统就已经超过大多数演示级项目了。然后再逐步加复杂度加工具、加Agent、加缓存、加部署优化。我现在回头做这一类项目时已经不会迫不及待地先写模型调用代码了。我会先花时间把问题定义清楚把评估指标定好把日志埋点规划好把接口抽象设计好。看起来前几步都在“做无用功”但正是这些准备工作决定了项目上线之后是“稳步迭代”还是“天天救火”。AI工程不是一个“调模型”的活儿它是一个系统工程——模型只是链条上的一环你真正要建设的是围绕模型的一整套可靠、可控、可观测的基础设施。
返回列表