多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

AI工程从零开始:模型调用、Prompt、Agent与质量保障实践

AI工程从零开始:模型调用、Prompt、Agent与质量保障实践 去年我把一套学习笔记整理成了开源仓库名字就叫 ai-engineering-from-scratch。起这个名字的时候没想太多就是觉得“从零开始做AI工程”这件事市面上讲得太散一部分教程只教调API一部分只讲模型原理还有一部分全在讲论文。真正把一个AI应用从想法变成能上线、能维护、能迭代的系统很少有人把整条链路串起来讲清楚。这份笔记就是干这个的。它不是什么高深理论更不是论文复现而是一条我自己踩过无数坑之后整理出来的学习路径从大模型的基本调用开始到Prompt Engineering的细节再到Agent、Harness Engineering、测试评估、部署监控每一层都有能直接抄作业的代码和配置。适合两类人一种是刚接触AI开发、想系统入门的工程师另一种是已经写了不少prompt或调过API但总觉得自己在“玄学调参”、想上升到工程化思维的同学。看完你能收获一套完整的AI应用开发框架而不是零散的技巧碎片。1. 项目全景AI工程到底在解决什么问题1.1 为什么强调“from scratch”我见过太多人学AI开发上来就装LangChain然后照着文档写几个Agent demo跑通了觉得自己会了。结果一换业务场景就懵换模型要改什么上下文管理怎么做工具调用断了怎么恢复评估系统好不好用靠什么标准这些问题拆开看每一个都指向同一个根因——没搞懂底层机制就想盖楼。所以“from scratch”不是指重复造轮子而是指学习路径必须从地基开始。这份项目的核心思路是先把每个环节的原始形态摸清楚不借助框架直接调用模型API完成一个任务不借助Agent框架手写一个工具调用循环不借助现成的评估库自己定义评测指标和数据集。当你亲手用几十行代码把这些“轮子”造了一遍再回头用框架你会知道它在帮你解决什么问题、引入了什么限制、哪些地方需要自己做二次开发。这个思路和学Web开发是一样的先用原生HTTP写接口理解请求响应模型再上Spring或Express的时候才不会被“魔法”迷惑。AI工程领域更吃这一套因为这个领域变化太快框架的迭代速度远远赶不上模型能力的演进速度理解原理解释权才能长久。1.2 AI工程和传统软件工程的差异做AI工程的人如果带着纯后端思维进来大概率会在几个地方栽跟头。我整理了一下两者的关键差异这也是整个学习体系设计的基础维度传统软件工程AI工程核心复杂度逻辑确定性bug是能修好的概率性输出错误只能缓解测试方式断言结果是否等于期望值评估结果是否满足质量标准有模糊空间依赖重点数据库、缓存、消息队列模型、上下文、数据、工具链运维视角关注QPS、延迟、错误率额外关注Token成本、幻觉率、效果漂移开发范式编码为主编码数据提示词评测循环这个区别直接影响工作方式。传统开发里系统输出不符合预期你定位到代码改掉就行AI应用里同样的输入可能这次答得好、下次答得差你甚至无法稳定复现问题。这就要求开发范式从“写代码”转向“调系统”——给模型搭好脚手架设计好数据流程和反馈回路让系统整体变好而不是盯着一次输出修修补补。我在这份项目里把这种思维贯穿始终每个模块的重点不是“这段代码能跑”而是“如果模型输出变了我改哪里、怎么改、怎么验证改得好”。这种思维方式才是AI工程和传统开发最大的分水岭。1.3 学习路径全景拆分整个仓库按依赖顺序分成六个层次每层解决一类问题模型基础层API调用、参数语义、Token计算、上下文窗口解决“怎么和模型对话”的问题。内容构造层Prompt Engineering的系统方法、结构化输出、多轮上下文管理解决“怎么让模型稳定输出”的问题。应用逻辑层函数调用Function Calling、工具定义与编排、Retrieval增强解决“怎么让模型使用外部能力”的问题。Agent与Harness层规划、记忆、反思、执行循环解决“怎么让模型自主完成复杂任务”的问题。质量保障层回归集构建、评估指标、可观测性解决“怎么知道系统好不好”的问题。工程架构层流控、缓存、成本控制、部署架构解决“怎么让系统稳定跑起来”的问题。每一层都配有可运行的示例代码和踩坑记录后面的章节我会挑最核心的几层展开讲。2. 模型调用与Prompt Engineering的核心细节2.1 别小看参数temperature、max_tokens和top_p的计算逻辑很多人调API的时候是先跑通就完事参数全部默认。但实际做工程化的时候这几个参数每一个都直接影响产出质量和成本。先说temperature它控制的是采样的随机性取值范围通常是0到2。这个参数要按任务类型选分类、信息抽取、代码生成这类要求确定性的任务建议设在0到0.3之间头脑风暴、文案改写这类创意任务可以放到0.7到1.0。我见过不少人把temperature拉到1.5搞创意结果模型输出开始语无伦次——因为过高温度会让模型从“合理但多样”滑向“离谱但随机”。max_tokens是最容易踩坑的。它限制的是生成的最大token数注意不是字符数。中英文的token换算不一样英文一般是1个token对应4个字符左右中文通常1个汉字对应1到2个token实际视分词器而定。如果你的输出经常被截断不要盲目把max_tokens调大先算一下你期望的输出长度对应的token数再结合成本做权衡。我给的估算方法是先用模型自带的tokenizer离线数一遍正常响应里的token量再乘1.5作为上限留出缓冲。top_p和temperature是互相配合的核采样参数。工程实践里通常的做法是先固定一个再去调另一个。我个人习惯固定temperature把top_p设为1只在需要更严格约束输出时把它降到0.9以下。两个一起调容易顾此失彼还不好排查问题。2.2 Prompt Engineering的系统套路从“咒语”到“工程”prompt engineering这个词被用烂了很多人觉得它就是在试验各种话术。但在系统化做AI应用之后我的理解完全变了Prompt Engineering的本质是接口设计——你设计的是用户意图和模型行为之间的接口。既然叫接口设计就要有规范、有版本、有测试。我总结了一套五段式prompt模板几乎所有任务类型都能套角色定义你是什么角色具备什么知识边界 任务描述要完成什么任务输入是什么输出是什么 约束条件不得做什么必须遵守什么规则 示例引导给出正例和反例少样本示例 输出格式明确结构、字段、格式要求这五段不是随意的。角色定义约束模型的“知识倾向”任务描述解决目的对齐约束条件筛掉不该出现的输出示例引导降低理解偏差输出格式保障机器可解析。每一段对应一类问题哪类问题频发就重点强化哪段。举个例子我做一个客服工单分类模块最初prompt只写了“将以下工单分类为故障、咨询、投诉”。模型经常把“想投诉但其实是咨询”的工单分错。后来在示例引导里加了两个边界案例错误率直接降了六成。这就是示例引导段的价值——它在给模型划定决策边界而不是光靠描述让模型猜。2.3 结构化输出JSON Mode和Function Calling的正确用法如果AI应用要对接业务系统模型输出必须是结构化数据。这里有两套主流方案适用场景完全不同。第一种是JSON Mode或者要求模型输出JSON格式。这套方案适合“一次计算、输出结果”的简单场景比如摘要提取、分类打标。实操时要注意如果模型输出的JSON格式偶尔不合法最稳的做法是在prompt里给出一个最小可用的JSON Schema并明确要求“只输出JSON不要添加任何解释”。我用下来加了Schema约束后解析失败率能压到接近零。第二种是Function Calling适合模型需要在对话过程中决定调用什么工具的场景。它的机制是先给模型声明一组工具函数模型在生成回复时会先输出一个结构化的函数调用请求你的代码再真正执行对应函数把结果传回给模型继续生成。这套机制是Agent的基础设施之一后面我会专门讲。工程上有个常被忽略的细节函数调用的声明信息要写得像文档而不是像代码。模型是通过描述来理解“什么时候该调用”的所以每个参数的description要比参数名更重要。我通常会给参数写“用途取值范围示例值”这份投入换来的调用准确率提升非常明显。3. 实操过程手写Agent与Harness Engineering3.1 什么是Harness Engineering和Agent是什么关系最近“harness engineering”这个词开始火了很多人不知道它和Agent是什么关系我用自己的话解释一遍。一个Agent的本质是一个“模型循环”模型决定下一步做什么执行工具观察结果再决定下一步直到任务完成。但光有这个循环远远不够一个真正可用的Agent系统需要有很多“外围的脚手架”保护这个循环稳定可靠地转起来谁来存历史消息工具调用超时怎么办模型输出一个不存在的函数名怎么处理单次循环的Token上限是多少如何防止Agent陷入死循环这些外围的机制就是Harness——中文可以叫“夹具”或“护栏”。它不直接参与决策但它的质量决定了Agent能不能在生产环境里稳定工作。你在网上看到的Agent demo能跑是因为恰好没触发异常路径生产环境里异常触发是常态这时候拼的就是Harness的工程水平。举个生活化的例子Agent是发动机Harness是整台车的底盘、悬挂、安全带和仪表盘。发动机决定车能跑多快但底盘决定你能不能安全地开回家。3.2 最小Agent实现手写工具调用循环为了让读者理解原理我在项目里用不到两百行代码实现了一个最小可用的Agent循环。这里我把核心步骤拆开讲解第一步定义工具集合。每个工具是一个函数加上它的JSON Schema描述。比如一个“查询天气”的工具Schema里要写清楚参数是城市名、返回什么格式。这个Schema不仅给模型看也是你的校验依据。第二步编写循环逻辑。伪代码如下messages [{role: system, content: 你是助手使用工具完成任务}] while True: response call_model(messages) if response.has_function_call(): tool_result execute_tool(response.function_call) messages.append(response_message) messages.append(tool_message(tool_result)) else: return response.content这段循环最关键的一点是把模型的每一步回复和工具执行结果都追加回消息列表里保持上下文完整。很多人手写Agent出问题就是只回传了工具结果而丢掉了模型自己的中间输出导致模型后面的推理失去上下文。第三步加防护。我在循环体里做了三件额外的事最大迭代次数限制默认10次、单次工具执行超时控制默认30秒、全局Token上限检查。这三样缺一不可尤其是迭代次数没有它Agent一旦陷入“调用工具-报错-再调用工具”的循环你的Token账单会非常难看。3.3 实战案例让Agent控制一个待办事项系统纸上谈兵不如跑个真实案例。我在项目里设计了一个演示场景让Agent通过工具调用来操作一个内存中的待办事项系统包括新增任务、列出任务、标记完成、删除任务四个工具。用户输入“帮我加三个任务明天开会、后天交周报、周末去理疗然后把理疗标记完成”。Agent的执行过程大致是先解析出三个新增请求逐个调用add_todo工具然后解析出标记完成的请求调用complete_todo工具最后还可以调用list_todo来确认最终状态。整个过程中模型输出的每一步函数调用都被我的循环捕捉、执行、回传用户根本不需要接触API。这个案例看起来简单但背后有三个容易踩的坑值得说明第一是参数提取。用户口语里的“后天交周报”转换成任务内容“交周报”加日期“后天”模型经常把“后天”直接当成任务名的一部分。解决方法是工具schema里把due_date的description写成“相对日期请转换为具体日期”并在示例里给一个转换范式。第二是工具调用的并行性。有些模型支持一次返回多个函数调用我的循环需要对这类响应做批量执行。如果一个个串行执行响应时间会拉长如果全并行又要考虑工具之间有没有依赖关系。稳妥做法是先执行无依赖的工具有依赖的再走一轮循环。第三是失败重试策略。工具执行可能抛异常比如删除一个不存在的任务这时要把错误信息原样回传给模型让模型自己决定是更正参数还是换一种方式。很多新手在这里直接终止Agent其实模型有很强的自纠错能力给它错误信息它往往能自己修复。3.4 Harness的进阶记忆、规划与自我反思基础Agent能跑通之后想要应对更复杂的任务就需要在Harness里加入三个进阶机制。记忆机制分两层短期记忆是消息列表本身受上下文窗口限制长期记忆需要引入向量数据库把重要信息检索出来再注入上下文。实操中我的做法是对话超过上下文窗口的60%时自动把早期消息压缩成摘要并保留关键结构化信息比如用户偏好、已确认的约束条件。这个阈值可以按模型窗口大小调整但压缩时机宜早不宜迟临到窗口上限再压缩容易丢掉正在用的上下文。规划机制解决的是任务过长、单轮循环搞不定的问题。我采用的是最通用的“计划-执行-回顾”模式Agent先拆解任务步骤生成计划然后逐步执行并在每步后核对是否需要调整计划。这一步的工程坑在于计划本身也要占Token计划写得越详细占得越多所以要设计计划长度上限同时允许计划在执行中动态修改。自我反思机制是我认为和模型能力提升最相关的一环。做法很简单当任务执行完或者出现错误时追加一轮“请总结刚才出问题的原因并给出改进步骤”的提示把这段反思写入上下文。实测表明这个机制能显著减少同类错误重复出现的概率。代价是一次额外的模型调用但这点成本在效果提升面前完全值得。4. 测试与质量保障AI工程的关键一环4.1 为什么AI应用测试不能靠“肉眼看看”传统开发里我们写完功能跑一遍看结果对不对就能基本判断质量。但AI应用不行原因有两点第一输出的正确性有模糊地带比如文案润色任务没有一个唯一正确答案第二同样的输入多次调用结果不完全一致你在测试时“看着好”不代表用户拿到也好。所以AI应用的质量保障必须建立在“批量评估”的基础上准备一个足够覆盖典型场景的测试集每次改动后批量运行用指标量化评估结果。这套做法在业界叫LLM-as-Evaluator或者基于回归集的评测本质上是把“凭感觉”变成“看数据”。我在项目里设计了一个极简的评估框架核心就三样东西组件作用我的推荐测试集覆盖典型输入和期望行为至少50条含边界案例和反例评估指标量化输出质量任务类型不同指标不同回归机制改动后自动重跑每次prompt、模型、代码改动都跑一遍这套框架不要一开始就追求完美先跑起来比什么都强。我自己是从10条测试集开始搭的后来才逐步补到几百条。关键是建立“改完必须跑回归”的习惯这个习惯能救命的时刻会出现在你上线后的某次模型升级中——新模型跑出了老模型从没犯过的错误回归集第一时间就能暴露出来。4.2 常见问题排查实录幻觉、上下文溢出、输出不稳定排查AI应用问题的思路和排查后端问题有很大区别。后端问题找堆栈AI问题要先分层归因是模型能力问题、prompt设计问题、数据问题还是系统架构问题我整理了四类最高频的问题和排查路径都是实操中验证过有效的方法。幻觉问题是最多的。要不要把“不知道就说不知道”写进prompt要但真正管用的是让模型有“验证路径”本来只让模型直接回答改成让模型先调用工具检索数据、再基于数据回答幻觉率会大幅下降。所以排查幻觉的第一件事不是改prompt而是看有没有给模型引入外部事实源。如果系统里有RAG链路那优先排查检索质量和引用机制——很多幻觉是因为检索出来的上下文本身不相关模型只能东拼西凑。上下文溢出问题表现为对话到一半模型突然“失忆”忘记早期信息或者开始答非所问。排查时先看Token用量曲线通常在接近模型窗口上限时出问题。解法前面提过——主动压缩摘要、检索相关历史、拆分任务、缩短单轮输出。这里我特别提醒不要试图靠“记住我刚才说的”这类的prompt来补救这是模型能力边界之外的事必须靠系统设计解决。输出不稳定问题分为内容和格式两种。内容不稳定优先检查temperature、样本量、prompt里有多义表述格式不稳定优先检查输出Schema约束必要时加一层输出校验代码解析失败就自动重试一次实测能把格式化失败率从5%压到1%以下。重试时要记得把“上次输出格式错误”作为反馈喂回给模型这样它才知道自己错在哪而不是盲目地再生成一遍。还有一个很隐蔽的坑业务代码吞掉了模型的部分输出。比如你在middleware里对消息做了截断或者清洗导致模型看到的上下文和测试时不一致输出质量就会诡异地波动。排查这类问题最好的办法是日志里同时记录发送给模型的完整请求和模型返回的完整响应逐次对比。上线前就把“完整请求响应留痕”这套可观测性建设好能省掉后面无穷无尽的排查时间。4.3 给测试集“排雷”边界案例比正常案例更重要建设测试集的时候新手最容易犯的错是只收集正常场景比如“用户输入规范的查询语句”。但生产环境真正出问题的永远是边界案例用户输入有错别字、文本超长、内容空、突然全是英文、问了一个涉及时效性的问题、表达前后矛盾……我后来养成了一个习惯每上一个新功能第一件事就是写10条“故意找茬”的测试用例。让用户输入空字符串会怎样让模型任务和数据检索目标完全不匹配会怎样让输入文本长度超过模型的单次处理能力会怎样把这些反例写进回归集里每次改动后一起跑就会发现很多你以为改好了的东西在反例面前原形毕露。另一个实用技巧是把生产环境中真实发生的失败案例持续沉淀进测试集。我每周会从日志里捞几条真实出错的输入整理成新的回归用例。这样测试集不是静态的摆设而是跟系统一起进化的活资产。上线三个月之后这套测试集会变成你团队里最有价值的数据资产之一甚至比部分代码更宝贵。5. 工具选型与工程化落地的个人经验5.1 从零开始应该用什么框架一种务实的选择逻辑很多初学者在文章开头问我既然项目强调从零开始是不是不该用LangChain这类框架我的态度是要先手写理解原理再用框架提升效率而不是绝对地排斥框架。具体的选择逻辑我总结成一句话重复的轮子用框架差异化的业务自己写。消息队列、流控、并发管理、工具调用循环这类通用能力框架做得很成熟自己重新实现一遍没价值但对业务效果影响大的环节——比如prompt模板、数据预处理、评估逻辑、harness调优——必须自己掌握控制权因为这些地方每一处改动都直接影响模型效果黑盒用框架等于放弃优化的主动权。目前实际项目中我用的组合是框架负责基本的编排和工具调用基础设施自己的代码负责数据转换、prompt管理、质量评估和异常恢复逻辑。这套组合的交付速度比纯手写快很多同时保留了对自己业务逻辑的完全掌控。5.2 可观测性建设Token成本和延迟要“看得见”AI应用上线后工程团队最先看到的往往是两张账单一张是云服务器账单一张是模型调用账单。后者很多人根本没做监控直到月底看到数字才慌。我在项目里专门加了一个可观测性模块基础思路极其简单每次模型调用都记录以下信息。记录项包括模型名、输入输出token数、单次调用耗时、使用的工具名、返回的错误码、请求的业务标签。然后把这些信息写入结构化日志再聚合出几个关键指标每日总Token消耗、平均单请求成本、工具调用失败率、p95响应延迟。有了这些指标成本优化才有依据。举一个实际例子我调一个分类任务最初用大模型处理每条数据日均成本很高。监控日志一分析发现其中60%的请求属于几个固定的高频分类这些完全可以先用规则命中只有规则判定不了的才进模型。改造之后成本直接降到原来的三成延迟也降了一截。这个优化路径不看数据根本不可能想到。5.3 关于“AI测试开发”和“AI辅助编程”的实践思考现在各种热点词满天飞“AI测试开发”“AI辅助编程”被反复提起。我的个人观点是AI工程化学习里最值得投入精力的两件事是“用AI做测试”和“测试AI应用”两者刚好对应上我前面讲的评测体系和回归集体系。用AI辅助写代码这件事准确说应该是“人机协作编程”AI负责生成样板代码和初稿工程师负责设计架构、审查逻辑、补充边界处理。它真正提效的场景是那些模式化特别强的代码——数据清洗、接口对接、重复的CRUD。指望AI直接生成完整业务系统现在还很不现实。但“测试AI应用”这件事不一样它必须靠工程师自己搭。因为模型输出的不确定性和多轮交互的复杂性传统测试工具很难直接覆盖需要专门设计评测框架和回归机制。这也是我这份项目最看重的能力一个会写AI应用的人很多一个能把AI应用测试评估做扎实的人很少。往这个方向深耕是AI工程时代工程师最稀缺的技能组合之一。5.4 最后分享一个小技巧给自己的学习路径建仓库很多人学AI工程素材囤了一堆笔记记了几百条但真正要用的时候还是找不到。我的建议是不要零散地记而是像这份项目一样把自己从零构建每个模块的过程沉淀成一个系统化的仓库。每学一个新概念就配套写一段最小可运行的代码每踩一个坑就整理成“现象原因解法”的条目每完成一个阶段性项目就梳理一篇复盘文档。形式不重要目录结构也不重要重要的是这个过程逼迫你把知识“做出来”而不是“看过就完”。我真实的体验是动手做过一遍的Agent循环比读十篇文章都管用亲手调过的prompt参数比背下所有规则都深刻。这份项目现在还在持续更新。我每接触到新的模型能力或者新的工程实践就会回来补充案例、修正代码。如果你也在从零摸索AI工程不妨照着这个路径走一遍亲手写写看跑跑看栽几个跟头收获会比看我写十篇文章都大。
返回列表