资讯详情

资讯详情

从零搭建AI工程链路:Prompt、Agent与上线实践

如果你让我用一句话总结最近三个月做的事情那就是把一个想法从零推到线上中间经历了无数次“我以为很简单”和“原来是这样”。项目代号叫 ai-engineering-from-scratch意思是“从零开始搭建一套可用的AI工程链路”。注意这里说的不是跑通一个调模型的demo而是真正能交给用户、能扛住流量、能持续迭代的产品级系统。这篇文章默认读者已经会用Python、听说过大模型API但没系统做过AI方向的工程落地。我会按项目的真实推进顺序来写先讲清楚AI工程和算法实验的边界再讲环境与工具链怎么选然后讲Prompt Engineering和Agent工作流的关键细节最后说从原型到上线必须处理的评估、监控和成本问题。整篇没有藏着掖着的操作都是可以直接抄作业的方案。1. 别急着写代码——先搞清楚AI工程到底在做什么1.1 AI工程不是算法研究两者的评价标准完全不同我见过很多人一上来就纠结“要不要微调模型”“要不要用最新的模型架构”这其实是把AI工程和算法研究混在了一起。算法研究的核心是模型效果上限也就是在固定数据集上把指标刷到多高而AI工程的核心是系统稳定性也就是在真实、动态、充满噪声的输入下系统的可用度、响应速度和可维护性能不能达标。举个实际的例子。我在这个项目里用了一个通用大模型做信息抽取最开始在20条测试数据上效果很好准确率几乎100%。一放到线上用户输入里出现各种奇怪的格式、口语化表达、错别字准确率直接掉到70%。这个时候你不可能靠继续调模型来解决而是要在系统工程层面做兜底预处理、提示词约束、后处理校验、失败重试。这就是“from scratch”和“跑通demo”之间的本质区别——从零开始的每一步沉淀都是在给系统的确定性添砖加瓦。1.2 把总目标拆成五层每层都有独立验收标准我在项目启动那天画了一张很大的目标拆解图后来发现真正靠谱的做法是把它简化成五层每一层都想清楚“做到什么程度算过关”。第一层环境与工具链。能用一个命令启动整个项目复制代码到另一台机器也能跑依赖可锁定。验收标准是“新机器5分钟内能跑起来”。第二层模型接入层。所有模型调用走统一接口切换模型不改业务代码。验收标准是“换一个模型供应商只改环境变量”。第三层Prompt与输出控制。模型输出格式稳定、可解析、可校验。验收标准是“连续调用100次失败率低于1%”。第四层Agent与工作流。把模型能力封装成可编排的流程支持工具调用和自主决策。验收标准是“多步骤任务能自动完成中途出错会自己恢复”。第五层评估、监控与迭代。每一次改动都能被评价每一次线上事故都能查日志。验收标准是“改一个Prompt能在30分钟内完成回归验证”。这五层对应的时间分配我的建议是3:2:3:1:1。前两层是地基但是不能恋战第三层往往是决定项目成败的关键第四层决定了你能不能做更复杂的业务第五层决定这个项目能活多久。1.3 为什么叫“from scratch”而不是“基于某某框架”现在网上铺天盖地的教程都在教你怎么用现成的Agent框架、工作流平台。不是说这些工具不好而是如果你一上来就套用重型框架你会分不清“到底是模型的能力在起作用还是框架的默认行为在起作用”。项目名叫from scratch就是要逼着自己亲手把每一个环节搭一遍哪怕最后你发现某个开源库做得更好你也知道它好在哪里、改动它会有什么后果。这个过程确实会更慢但非常值得。我在没有用任何Agent框架的情况下从零写了一个仅有一百多行的Agent循环却因此彻底搞懂了工具调用、上下文管理和错误恢复的底层逻辑。现在再去用任何高级框架我看文档一眼就能明白它内部发生了什么。这种“手搓过一遍再选轮子”的路径是我最推荐给新手的成长方式。2. 环境与工具链把地基打稳后面才不返工2.1 开发环境的技术选型逻辑我的技术选型原则很简单不追新只追稳。Python版本选3.11不用3.12是因为部分依赖库的预编译包还没跟全虚拟环境和依赖管理用uv因为它比poetry和pipenv都快很多也没有复杂的配置文件语义。整个项目依赖数量控制得很克制核心依赖不到十个主要就是openai、fastapi、pydantic、httpx、loguru这些。这背后有一个很重要的工程经验依赖越少出问题的面就越小。很多人喜欢装各种“锦上添花”的库一旦Python大版本升级所有依赖全部要重新验证兼容性。我在这个项目里宁可自己写二三十行代码处理一个小功能也不愿意为了省事拉进来一个几千行依赖的库。后续证明这个策略让项目在多个部署环境之间迁移异常顺利。硬件方面我整条开发链路是纯API调用没有跑任何本地大模型所以不需要昂贵的GPU机器。如果你确实需要本地调试小模型推荐Ollama跑7B以下的模型但要注意它不负责高并发只适合本地验证。生产环境的模型服务是云厂商的事你不需要把自己的精力耗在运维上。2.2 模型接入层要设计成“开关切换”而非“代码写死”模型接入是AI工程里最容易翻车的一环。今天你用的是A模型明天发现B模型效果好一点后天又出了C模型价格更便宜如果所有代码里都是直接调用某个模型的SDK换模型等于重写业务逻辑。我设计了一个极简的模型网关核心就两层第一层是配置化。所有模型相关的信息都放到环境变量或一个config.yaml里包括base_url、api_key、model_name、max_tokens、temperature。第二层是接口统一。业务代码只跟一个chat()函数打交道这个函数内部负责把请求发给哪个模型、用什么样的参数、超时多久、失败后怎么办。具体代码长这样非常简单但很实用import os import json import time from openai import OpenAI client OpenAI( base_urlos.getenv(LLM_BASE_URL, https://api.example.com/v1), api_keyos.getenv(LLM_API_KEY), timeout60.0, ) def chat(messages, temperature0.2, max_tokens2048, modelNone): model model or os.getenv(LLM_MODEL, default-model) resp client.chat.completions.create( modelmodel, messagesmessages, temperaturetemperature, max_tokensmax_tokens, response_format{type: json_object}, ) return resp.choices[0].message.content这段代码里最容易被忽略的是response_format{type: json_object}。如果模型支持按JSON格式输出这个参数能省掉你百分之八十的解析烦恼。但注意不是所有模型都支持所以要封装成可选开关。def chat(messages, temperature0.2, max_tokens2048, modelNone, force_jsonFalse): model model or os.getenv(LLM_MODEL, default-model) kwargs {} if force_json: kwargs[response_format] {type: json_object} resp client.chat.completions.create( modelmodel, messagesmessages, temperaturetemperature, max_tokensmax_tokens, **kwargs, ) return resp.choices[0].message.content2.3 工程骨架与目录设计我见过无数AI项目的代码库就是几个孤零零的.py文件堆在根目录下这种项目跑通可以迭代两周后必乱。这个项目从一开始就规划了目录结构核心原则是业务逻辑、模型能力、基础设施三层分离src/ api/ # 对外接口FastAPI路由写在这里 agents/ # Agent定义与工具逻辑 prompts/ # 提示词模板每个模板一个文件 services/ # 模型网关、缓存、评估等公用服务 utils/ # 小工具函数 tests/ # 单元测试与回归测试 config.yaml # 各类配置 scripts/ # 启动、部署等运维脚本这个结构的价值在于当你需要修改一个Prompt的时候你不会动到Agent逻辑当你需要换工具实现的时候你不会动到API层。边界清晰出问题就知道去哪里找。这一点对于AI项目尤其重要因为AI项目的失败模式往往是“模型输出不可预期”你必须在代码里留下足够的隔离层来容纳这种不确定性。3. Prompt Engineering让模型按你的工程规范干活3.1 三类任务就要有三套提示词策略很多教程把Prompt Engineering讲得像玄学其实拆开来看工程场景下你只需要三类策略。第一类是结构化抽取任务比如从一段文本中提取“时间、地点、事件”。这类任务的核心是严格。系统提示词里要写明输出JSON的完整Schema并且加上“不要输出任何其他内容”。温度参数设为0.2以内确保结果稳定。第二类是开放式生成任务比如写总结、写文案。这类任务的核心是风格对齐。提示词里给出角色设定和几个高质量示例温度可以设在0.7到1.0之间增加表达多样性。我实际测试下来角色设定对风格的影响比很多人想象的大同样的模型给它“资深行业分析师”的身份后输出的敏锐度会明显提升。第三类是推理与规划任务比如让模型根据一堆条件制定一个执行方案。这类任务的核心是链路完整。提示词要引导它先分析条件、再列出约束、最后给出方案并且要求它用步骤编号输出。温度设在0.3左右兼顾推理稳定性和一点灵活性。3.2 提示词模板怎么管理才算真正的“工程”提示词不是写在代码字符串里就完事的。我在项目里把提示词全部抽成了prompts/目录下的模板文件格式统一用YAML带Jinja2变量渲染。这样做直接解决了两个问题一是非技术人员比如运营同事要微调语气风格时不用进代码库改Python二是提示词支持版本管理下一次改动有据可查。模板结构我固定分四段system: | 你是{role}。请严格按照用户要求输出不要添加任何多余内容。 task: | 任务目标{goal} 输入信息{input_data} constraints: | 1. 输出必须是合法JSON字段严格遵循{schema} 2. 不要编造不存在的信息 3. 如果信息不足在json的confidence字段中填写0.6以下 examples: | 示例1{example_1} 示例2{example_2}说句实在话constraints这一段是重中之重。给模型划红线比给模型划目标更有用——目标描述太模糊模型就会自由发挥红线清清楚楚模型反而会收敛很多。我用“不要编造”四个字就把线上幻觉率降低了一个等级配合confidence置信度字段还能实现低置信度自动转人工审核的兜底机制。3.3 上下文长度与Token预算省钱和质量的平衡点上下文管理是AI工程里最容易掉头发的问题。模型Token越长单位成本越高、响应越慢而且模型对长上下文的“关注力”是衰减的——放在对话开头的指令跑了几千字之后它经常“忘记”。我的做法是给核心指令设了一个守则系统提示词永远放在第一条并且简练到64个Token以内。任务相关内容紧跟其后历史对话和参考资料放最后。一旦总Token接近窗口上限先把历史对话做摘要再继续绝对不直接截断参考资料。具体到预算分配我一般按这样的比例切割一条请求的总预算比如4K Token系统提示词占5%用户核心指令占20%参考输入占50%输出预留25%。前两者短而精输出预留充足才能保证模型完成结果而不是写到一半被截断。如果你发现模型的输出总是不完整先检查是不是输出预算给得太少而不是模型能力不行。4. Agent与工作流把单次调用变成可自主运行的流程4.1 Agent的核心循环观察、思考、行动做Agent之前你得先明白它和普通API调用的本质区别。普通调用是“request进去response出来”Agent则是一个循环模型观察当前状态思考下一步动作执行工具调用把结果追加进上下文然后继续观察直到任务完成。我在项目里手写了一个极简的Agent循环核心逻辑如下def run_agent(task, max_steps10): messages [{role: system, content: SYSTEM_PROMPT}] messages.append({role: user, content: task}) for step in range(max_steps): reply chat(messages, temperature0.3, force_jsonTrue) result json.loads(reply) if result.get(action) finish: return result.get(answer) tool_result execute_tool(result[tool], result[args]) messages.append({role: assistant, content: reply}) messages.append({role: tool, content: json.dumps(tool_result, ensure_asciiFalse)}) raise RuntimeError(reached max steps)这段代码看起来简单但里面藏着两个关键设计。第一max_steps10必须设死否则模型会在一个循环里绕圈出不来线上系统会无限消耗Token。第二工具调用的结果是以roletool的消息回传的这是为了让模型明确区分“这是工具的结果”和“这是对话内容”避免它在后续推理中混淆信息来源。4.2 工具描述写得好不好直接决定Agent聪明不聪明Agent的智能程度一半取决于模型本身另一半取决于你给它的工具描述。我见过太多人把工具描述写成“用于执行查询”这种等于没写。模型根本不知道什么时候该用这个工具、用什么参数、返回结果长什么样。好的工具描述要包含四个要素触发场景、参数说明、返回值格式、失败情形。比如你给Agent提供一个“查天气”的工具不要写“查天气”而要写当用户询问某地当前温度、降水、风速等天气信息时使用。 参数: location 城市名或经纬度。 返回: JSON包含 temp、condition、humidity、wind_speed。 如果location不存在返回 error 字段不要猜测数据。为什么这样写因为模型是靠“语义匹配”来决定调用哪个工具的工具描述越接近用户问题的自然表达触发的准确率就越高。我之前没写触发场景的时候Agent经常把“查天气”用在了“问时间”的场景里改完描述后这个问题基本消失了。4.3 手动挡多Agent协作规划、执行、审查的三角结构项目后期遇到了单Agent做不了的事情任务链路太长一个模型既要规划又要执行还要自我检查上下文很容易乱。我没有直接上重型编排框架而是手工搭了一个三个Agent协作的流程规划Agent负责拆解目标和分配子任务执行Agent负责具体干活审查Agent负责检查结果质量。这个三角结构并不复杂但效果显著。规划Agent的输出是一份任务清单每一项都明确指定给哪个执行Agent执行Agent只管自己那一段上下文短且专注审查Agent把执行结果按标准逐条打钩不通过就退回重新执行最多退两次。整个过程是串行的但胜在每一步都可靠。我还试过让两个Agent并行执行互不依赖的子任务这里有一个教训并行确实能缩短总耗时但要注意共享上下文的同步问题而且调试难度会成倍增加。如果你初次做多Agent建议老老实实先串行等日志和容错机制都成熟了再上并行。5. 从原型到上线评估、监控、成本一个都不能少5.1 评估集是AI工程的“测试用例”传统软件工程里有单元测试AI工程里对应的东西就是评估集。没有评估集你就没法判断“这次Prompt改动到底是变好了还是变坏了”。我一开始在这上面偷了懒后果就是每次改动都像在赌运气。后来老老实实建了一套评估集才算是把迭代主动权掌握在了自己手里。评估集的构建原则很简单覆盖典型场景确保多样性。我从真实使用日志里抽了50条case覆盖正常输入、边缘输入、异常输入三类。正常输入要求模型输出完全正确边缘输入允许部分信息缺失但格式必须合法异常输入要求触发兜底逻辑而不是崩溃。每次改完Prompt或Agent逻辑就跑一遍这50条case跑完看通过率有没有下降。这个习惯用了一周后我敢说整个系统的稳定性肉眼可见地提升了。评估跑批的工具不需要复杂一个脚本读取case文件、调用系统接口、对比期望结果就够了。但要注意期望结果有时候不能是固定的文本而应该是一组“必须满足的条件”比如“答案里包含指定编号”、“JSON能成功解析”、“不包含敏感词”。条件式断言比文本式断言灵活得多也更贴近真实业务。5.2 日志系统救人一命的完整链路AI系统的日志和传统系统的日志有一个巨大差异传统日志记下的是“发生了什么”而AI日志必须记下“模型看到了什么、模型输出了什么、为什么输出这个”。我的做法是为每一次模型调用和Agent步骤都生成结构化日志至少包含这些字段字段名含义request_id一次业务请求的唯一IDmodel_version调用的模型和系统提示词版本prompt_content发送给模型的完整消息response_content模型返回的完整内容tool_callsAgent调用的工具及参数latency_ms调用耗时token_usage输入和输出的Token数error_info异常信息没有则为空这套日志体系的价值在线上故障排查时完全体现出来用户报告一个问题我拿到request_id就能复现整条调用链路看到模型是被哪一方的输入误导的是解析出的JSON字段错了还是工具返回的数据有问题。没有这套日志AI系统里的问题排查基本就是盲人摸象。5.3 成本控制不是事后算账而是架构设计的一部分大模型API是按Token计费的成本失控是所有AI项目上线后遇到的头号运营问题。我总结出来的成本控制三板斧都是在架构层面做文章而不是靠事后控制预算。第一板斧是模型分级。简单任务用便宜的小模型复杂任务才用贵的大模型。我没有让业务代码决定用哪个模型而是在模型网关里做一层路由规则短文本分类、实体抽取这类任务一律走轻量模型长文档总结、复杂推理才走旗舰模型。实测成本直接降了一半以上。第二板斧是结果缓存。同一段输入文本在短期内被重复请求的概率其实很高尤其是固定格式的抽取类任务。我用Redis做了一层缓存Key是“输入内容的哈希任务类型”有效期为24小时。命中缓存直接返回上次结果既不花Token又大幅降低了响应延迟。第三板斧是限流与配额。对每个调用方、每个API Key设定每分钟调用次数上限防止异常流量把成本一下子打爆。这个配额机制同时还能保护下游模型服务防止某个业务方的突发流量挤占其他业务的资源。6. 常见问题与排查技巧实录6.1 高频问题速查表这些问题是项目推进中最常遇到的我整理成了一张速查表每一个都是亲手踩过的现象可能原因处理方式模型返回的JSON经常解析失败模型把JSON包在了markdown代码块里先剥离代码块再解析或打开force_json开关长对话后模型“忘记”早期指令上下文超长模型注意力衰减压缩历史对话为摘要保留关键指令在开头Agent陷入反复调用工具的循环工具结果没有改变上下文状态设置最大步数在Prompt中明确“找不到答案就停止”API请求偶发超时上游服务不稳定加超时重试退避时间为1s/2s/4s最多三次相同输入多次输出不同答案温度参数设得太高结构化任务降到0.2以内或加缓存用户反馈答案里有明显错误信息模型幻觉用RAG补充真实数据源并让模型输出置信度字段Token消耗比预估高很多上下文重复发送长文本对长文本做切分和摘要只保留必要信息同一天线上效果突然变差上游模型供应商更新了模型版本固定模型快照版本不追最新6.2 排错心法先定位是模型问题还是工程问题遇到问题最大的忌讳是上来就改Prompt。我在项目后期总结出一个排错顺序遇到任何异常都按这个顺序走效率至少提升三倍。第一步看输入与输出。先确认发给模型的内容和预期一致这一步靠日志系统。第二步看解析与校验。如果模型输出了正确内容却上报系统错误去查后处理代码大概率是JSON解析或字段校验写错了。第三步看工具与数据。Agent场景里工具返回的数据质量经常是问题根因。最后一步才是模型效果。如果前三步都没问题再去优化Prompt而且优化完一定要跑评估集。这个顺序建议新手仔仔细细贴在显示器旁边。我见过太多人遇到问题就调Prompt结果折腾一晚上发现是网络超时没配重试。AI工程出问题概率最大的是工程链路根本还没轮到模型层面的问题。6.3 两个最容易忽略的实操细节第一个细节是每一版系统提示词都要带版本号。我之前有一次改了系统提示词里的措辞结果线上效果变差了但因为没记版本根本不知道是什么时候改的、改之前长什么样。后来我在每次调用时把系统提示词的版本号写进日志这个问题彻底解决。第二个细节是不要在生产环境直接改提示词。你可能觉得改几个字有什么大不了但AI系统的行为是非线性的——你以为改的是语气实际上可能改变的是整个输出分布。我的做法是任何Prompt改动都先在评估集上跑通再切一个线上小流量做AB对比确认效果后才全量发布。虽然过程麻烦一点但这是产品级AI系统该有的严谨。最后想说的两句话如果你问我这次从零构建AI工程项目最大的收获是什么我会说AI工程真正难的不是模型能力不够而是把模型这个充满不确定性的组件嵌入到一个要求确定性的系统里时需要一套完整的工程方法来兜底。Prompt要模板化模型要可替换Agent要有边界效果要可评估成本要可控制——每一件事单看都不难但把它们组合起来并且跑得稳才是“ai-engineering-from-scratch”这个项目的全部价值。还有一个非常实用的小技巧也是我现在做任何AI项目的默认习惯从第一天开始就写评估集。哪怕只有十条case也要写。因为在没有评估集的漫漫长夜里你永远不会知道天亮是什么样子。有了它你的每次改动都站在了“被验证过的确定”上这比任何漂亮的架构设计都更能让一个AI项目活下来。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →