ARTICLE DETAIL

资讯详情

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

AI应用开发不止调接口:上下文、提示词与Agent编排实战

AI应用开发不止调接口:上下文、提示词与Agent编排实战 每次在饭局上被人问起最近在做什么我说在做AI应用开发对方基本都会接一句AI应用开发不就是调个接口么刚开始我还一本正经解释后来发现解释也解释不清就笑着点头了。这句评价没错但就像说程序员不就是敲键盘的一样——正确到没有任何信息量。真正做了一年多大模型应用开发之后我越来越确信调通模型接口只是入场券接口之外的那套系统工程才是AI应用能不能从Demo变成产品、从玩具变成工具的分水岭。这篇就当是给自己踩过的坑做个整理也写给那些正准备从第一次调通API往前走一步的人。内容包括一个只调接口的Demo是如何被真实业务击穿的上下文和记忆的设计为什么是分水岭提示词工程如何成为接口之外的第二层接口Agent应用开发中工具编排和异常恢复的复杂度从哪来以及上线前评测、成本和灰度这三道绕不开的必答题。1. 调接口只是入场券那个只把模型当黑盒的项目最后怎么了1.1 一个只调接口的Demo为什么跑不动真实业务我见得太多的入门流程是这样的注册一个模型厂商的账号拿到API Key用现成的SDK跑通一个请帮我总结这篇文章的调用控制台输出一段看起来很有道理的话于是一个AI应用就诞生了。我2023年底做过一个文档总结工具当时的实现特别粗暴把整份PDF按顺序塞进messagessystem prompt写请你总结这份文档然后把大模型返回的文本原样展示给用户。Demo演示时确实惊艳领导当场拍板让上生产。结果真实用户一用问题一个接一个爆出来。第一个是上下文溢出。一份常见的行业报告三五十页是家常便饭哪怕用PDF抽取文本之后也有两三万字。模型上下文窗口有限塞进去直接报错。有的用户文档单篇超过模型最大上下文接口直接拒了。第二个是输出质量问题。语言模型不是检索器它会一本正经地编造原文里根本没有的数据和结论。有用户拿合同让我总结模型把违约金条款整条编错了还编得特别真——这种幻觉在Demo阶段是没人关心的放到正式业务里就是事故。第三个是多轮对话失忆。用户问完第一章接着问第二章我当时的实现每次都是从头重新塞整份文档没有维护会话状态模型根本不知道刚才聊到哪了对话体验支离破碎。第四个是成本和并发。真实用户不是演示时那三五个人几十个人同时用接口调用频率一高超时、限流、账单翻倍全来了。而我当时的代码里没有任何重试、降级、缓存机制。所以你会发现调接口在教科书里是终点在真实业务里只是起点。模型API做的事情本质是输入一堆文本、输出一堆文本它不关心你的文档从哪里来、用户是谁、业务规则是什么、成本是不是能兜住。这些模型之外的事情才是AI应用开发的重量级部分。1.2 模型黑盒外面还有三层调度、数据、业务一层都不能少我后来给自己总结了一张分层图在这里用文字描述一下最里面是模型API外面至少包着三层。第一层是调度层。你的应用不可能只用一个模型也不能在模型服务商出故障时眼睁睁看着业务挂掉。调度层做的事情包括根据任务复杂度路由到不同规格的模型给每个上游请求设置超时和重试模型服务不可用时自动降级到备用通道对高并发做削峰和排队。我见过很多小团队一开始就忽略这一层某个下午模型服务商一个故障整个应用白屏四个小时用户全跑了。第二层是数据层。数据层不一定是数据库那么简单它关乎模型看到什么东西。你要决定用户上传的文档怎么解析、怎么清洗长文档怎么切分、怎么摘要多轮对话的历史怎么存、怎么取用户画像和记忆怎么建模。模型是无状态的它不记得上一通对话所有记忆感都需要你用自己的存储和检索去模拟。这是只调接口的人完全不会想到的工程量。第三层是业务层。业务层负责把大模型编的故事拉回现实输出的文本要做格式校验该是JSON的要能parse该匹配业务枚举的要匹配上模型漏掉的关键字段要能识别出来并触发补充提问生成结果要落库、要溯源、要审计。没有这层AI应用就只剩下看着很聪明实际不靠谱。前面说的三层每层单独拿出来都够写一篇文章。把这三层加进来再看那句话——不就是调个接口么——里头藏的工程量远远超出一个API调用。接下来几节我从自己踩坑最深的三块展开细说上下文与记忆、提示词工程、Agent工具编排。2. 从能出结果到真正可用上下文与记忆才是分水岭2.1 token的数学题为什么长对话必然爆掉我接触的不少新手第一次看到上下文窗口32k会觉得这个数字好大中文也能算两万字绝对够用。但这是没把多轮对话算进去。模型每次请求你都要把历史对话一并送进去token数会滚雪球。我算过一笔账假设一次普通的问答平均消耗800 token提问加回答20轮对话之后仅历史就是16000 token再加当前用户提问和系统提示词已经接近窗口边缘了。如果哪轮回答稍微长一点比如生成一段代码或一页总结单轮就吃掉三四千token那几乎撑不到10轮就爆了。除了爆掉还有一个容易被忽略的问题历史越长模型对早期信息的关注度越低。模型把注意力用在整个上下文上但过长历史里真正重要的信息很容易被淹没。你塞了3万token的历史进去最后问它用户最初说过什么它大概率会顾头不顾尾。说句大白话上下文不是硬盘想存多少存多少它是舞台站上去的演员越多每个演员能分到的聚光灯越少。所以做AI应用首先要学会的其实是不让模型看太多。2.2 三种记忆方案全量、滑动窗口、摘要加检索既然不能全塞就要在设计上做取舍。我在项目里用过三种方案各有各的适用场景。全量上下文方案最简单把整段对话历史原封不动传给模型开发成本最低上下文一致性也最好。代价是贵和短命每轮请求都要重复计费历史一长成本直线上升而且对话窗口很快用尽。它只适合轻量场景比如单轮问答、内部工具不建议直接用在用户面向的多轮对话里。滑动窗口方案是大多数人的第一步优化只保留最近N轮对话更早的直接丢掉。实现简单token可控但弊端非常明显——用户在两小时前提到的一个重要信息一旦滑出窗口模型就彻底忘了。我在客服场景里吃过它的亏后面细说。摘要加检索是我现在的主力方案。思路是每轮对话结束后把这段对话浓缩成摘要存储新问题来了之后先用embedding在历史记录里做相似度检索捞出最相关的几条连同最近几轮完整对话一起拼进上下文。这样模型既看得到刚说的也捡得回早前说过的关键信息token消耗还控制得住。大致代码长这样def build_memory_prompt(user_id: str, current_question: str) - str: # 1. 召回相关历史按向量相似度取前3条 relevant memory.search(user_id, current_question, top_k3) # 2. 携带最近2轮完整对话 recent memory.recent_conversations(user_id, rounds2) # 3. 拼装成结构化上下文 return \n.join([ 以下是该用户更早对话中与本问题相关的片段, *relevant, 以下是最近两轮对话, *recent, f当前问题{current_question} ])这个方案的最大成本在embedding检索的工程实现但收益是实打实的我上线后对比过同样的提示词和模型用户的你还记得我说过吗这类问题答对率从五成提到了八九成。记忆方案实现成本上下文一致性token开销适用场景全量上下文最低好线性膨胀很快爆单轮问答、内部工具滑动窗口低差早期信息丢失可控轻量闲聊摘要加检索中好较低多轮客服、长期记忆2.3 一个客服问答项目的记忆返工记录记忆方案选错初期看不出来用户量一上来就现形。我有一次给某电商客服做AI问答第一版图省事直接上滑动窗口只保留最近5轮。客服场景里用户的对话特点是什么一句话里塞着大量历史信息我上周买的那台白色冰箱当时用了优惠券现在想换货。用户在第3轮提到过白色冰箱用了优惠券等聊到第8轮说要换货时这些信息早就滑出窗口了。模型完全不知道冰箱的颜色和购买时用了券回复变成了通用话术请问您购买的是什么型号用户当场就烦躁了——这些问题他半小时前刚回答过。后来我改成摘要加检索的方案用户的关键诉求在对话中会不断被抽取到会话摘要里即使发生在几十轮之前检索照样能捞回来。这个改动不算什么高深技术但直接把答非所问的投诉量降了大半。也是从这个项目开始我才意识到AI应用开发里记忆这块的复杂程度不是因为技术有多难而是因为它建在概率模型上面你不可能像查数据库一样精确地取出用户信息你得在模型的概率世界里建立一套够用就好的检索与组织机制。这套机制显然不是调接口能解决的。3. 提示词不是写话术藏在接口外的第二层接口3.1 提示词的本质是定义输入输出契约很多人觉得提示词工程就是写一段更有文采的话让模型回答得更高级。这个理解有点浅。从我自己的实践看提示词在AI应用开发里扮演的角色是定义输入输出的契约你要把模型从一个能聊天的黑盒变成一个可被程序调用的函数。最典型的是结构化输出。业务系统要用模型的结果就必须保证输出能解析。我们总不能拿一段自然语言答案是3.14去喂给报价系统。正确做法是用约束让模型输出JSON。比如from openai import OpenAI import json client OpenAI() def extract_order_info(raw_text: str) - dict: resp client.chat.completions.create( modelgpt-4o-mini, messages[ { role: system, content: ( 你是一个订单信息抽取器。只输出JSON对象 字段必须包括 order_id、user_name、amount。 ) }, {role: user, content: raw_text} ], response_format{type: json_object} ) return json.loads(resp.choices[0].message.content)看起来平平无奇但这里藏着很深的一个观念转变你不是在跟模型聊天你是在给模型定义一份接口文档。字段名、类型、取值范围、缺失时的行为全部要提前说清楚。这跟设计一个RESTful接口的思考方式其实一模一样。还有更进阶的玩法用函数调用格式。模型会先输出一个结构化的工具调用意图然后你的代码去执行本地逻辑再把执行结果回传给模型生成最终话术。这样模型其实被拆成了大脑和嘴巴两部分你可以在中间加入任何程序逻辑比如查库存、查价格、校验权限。很多Agent应用的底层就是这套机制后面第四章细讲。3.2 动态组装与版本管理把提示词当成代码真实应用里提示词不可能写死。一个系统可能同时服务不同角色、不同商品、不同语境提示词必须动态组装。我的做法是把提示词拆成三块系统角色块固定身份与通用规则比如你是客服助手只能基于知识库回答。上下文块从数据层取出来的记忆、知识库检索结果、用户画像这个部分是动态变化的。指令块当前这轮的具体任务说明比如请从用户消息中提取退款原因或请生成一封安抚邮件。三块拼装完成之后传给模型。拼接结果大致是这个样子system: 你是{{bot_name}}你的性格是{{persona}}服务对象是{{customer_name}}。 instruction: 本次任务是{{task}}。请严格遵循以下规则{{rules}} memory: {{relevant_history}} knowledge: {{search_results}}这时候提示词已经变成了一段有版本、有测试、可回归的代码资产。我这里特别想强调版本管理绝不要在生产环境直接改prompt文本。我用git管理所有prompt模板每次改动都提交一个版本号。为什么因为大模型对措辞极其敏感哪怕只是把请改成务必都可能让输出风格大变。我在一个项目里曾经把temperature从0.7调低到0.3想减少随机性结果所有回答都变得干巴巴还开始丢字段。谁也没想到一个参数会影响这么大。提示词这个东西是典型的改一行字出大事故的领域。管理方式上建议给每个线上prompt配一个版本号接口层支持按版本号或者百分比路由流量这样新提示词上线后可以小流量观察出了事能立刻回滚。3.3 输出校验模型说的话不能直接当数据用这是我在内部培训时反复强调的一点模型API的返回值本质上只是一个对自然语言的推测结果它不是数据库查询结果更不是业务事件。你不能拿它直接去下单、改库存、发通知。我维护过的AI应用上线初期最大的bug来源就是信任模型输出。它说自己已经给用户退款了但实际上只是生成了一段已退款的文字。为了防止这类事故我在业务层加了一道完整的校验与重试链路格式校验用JSON Schema校验模型输出字段缺失或者类型不对直接判失败。语义校验关键业务字段要做业务规则匹配比如订单号是否存在于系统、金额是否在合理区间。重试机制校验不通过时把错误信息回传给模型让它重新生成。这招很管用很多情况下模型知道自己错在哪重来一次就规范了。人工兜底重试多次仍失败转入人工流程不允许静默失败。简单说模型输出要过三道闸门才准进入业务系统。很多只调接口的初学者最缺的恰恰是这一步——他们以为模型返回的JSON天然就是可靠的结果上线第一天就被脏数据教育了。我在一次实践中就见过因为模型输出里金额字段多了个小数点报价直接被客户否决。这类问题靠调接口永远调不出来。4. Agent不等于堆接口工具编排与异常恢复的复杂度盘点4.1 从单次调用到多轮工具调用的那一步这两年Agent概念火得一塌糊涂有些人觉得Agent开发就是把几个接口串起来。如果真的只是串接口那确实没什么难度。但你自己写过一次就会明白Agent最难的不是调通而是不出错地调通很多次并且每一次出错都能自己爬起来。先说说Agent的基本工作方式你把一组工具定义好放进系统提示词模型自主决策要调用哪个工具、传什么参数执行完后你把工具结果回传给模型模型再决定下一步。整个过程循环直到它认为任务完成。这里有一个非常关键的细节模型生成的是调用意图不是可靠的程序调用。举例来说它可能生成下面这样的JSON{ tool: query_order, parameters: {order_id: 20250115-001, user_id: u_1000} }看起来没问题但模型可能把order_id和user_id张冠李戴可能漏参数可能在订单号里引入幻觉数字。工具参数错误率这玩意儿在真实场景里远比官方Demo里看起来高。4.2 编排层要解决的四类实际问题我在Agent项目里整理了四类高频问题想做Agent应用开发的同学直接对着这个清单自查。第一类上下文膨胀。工具返回的结果会作为下一轮输入送回模型每多一次工具调用上下文就长一截。假如模型连环调用七八次工具每次工具返回一大段数据上下文可能直接翻倍。我的对策是工具结果返回前先做结构化截断只保留模型需要的关键字段工具结果不必要时不全部送入上下文。第二类死循环。模型可能会反复调用同一个工具比如用户问天气它查完北京又问北京没完没了。我在一个项目里见过模型连环调用同一个接口二十多次账单哗哗涨。对策是设置最大轮数上限并在达到上限时强制切换为直接向用户坦白的模式。第三类错误恢复。工具的接口是有可能挂掉的上游数据库可能抖动某个第三方服务可能超时。模型本身不感知这些它只会觉得没有拿到结果。我们必须在上游代码里做重试、超时、降级还要引导模型在工具失败后向用户如实说明而不是编造一个结果。第四类可观测性。语言模型的推理过程是隐性的它为什么选了这个工具、为什么放弃那个工具你从日志里很难看出来。没有好的trace链路Agent一旦在线上抽风排查起来跟大海捞针差不多。我现在每个Agent请求都会记录完整的调用链每轮模型响应、工具选择、参数、执行结果、耗时全部落日志方便复现。4.3 订单查询Agent实战复盘一次参数混淆引发的大修说一个我自己翻过车的例子。当时做一个订单查询客服Agent模型在系统提示词里看到有两个工具一个是query_order按订单号查订单一个是query_user按用户ID查用户信息。结果在真实对话里用户说帮我查一下我的20250115-001号订单模型生成了工具调用参数却是这样{ tool: query_order, parameters: {order_id: u_1000, user_id: 20250115-001} }它把用户ID和订单号整个搞反了。query_order返回了空结果事件到这里本来应该结束但模型干了件更离谱的事它看到工具返回空就发挥想象力给用户编了一条您的订单已发货预计明天送达。用户真的相信了第二天没收到货来投诉我们排查半天才发现是Agent参数混淆加上幻觉输出叠加出来的事故。这个事故让我做了三件事一是在工具调用参数上加了规则化约束。明确告诉模型order_id字段必须以OD-开头并在业务侧做前缀与格式强校验不合法直接拦截重试。二是增加空结果兜底。工具返回空时系统强制给模型插入一条系统消息工具未查询到结果请如实告知用户订单不存在或需要重新核对订单号不得自行编造。把模型编造的路径堵死。三是给所有Agent请求加上最大轮数与成本告警。一个请求超过6轮工具调用或者消耗token超过阈值自动熔断转入人工。这三项改动上线后同类事故再没出现过。回头看这中间没有一步是调接口能解决的。Agent应用开发真正考验的是当模型在概率世界里乱走的时候你的系统和代码能不能像安全带一样把它兜住。5. 上线之前绕不开的三道必答题评测、成本与灰度5.1 没有评测集的应用等于裸奔先建回归基准再改任何东西我承认自己对评测这件事的轻视吃过很大的亏。传统软件开发有单元测试有CI改一行代码可以跑一遍测试集到了AI应用这里模型输出是概率性的你改了一个prompt可能十个case里八个变好、两个变差而且变差的往往是你没注意到的那两个。所以后来我无论项目大小第一件事就是建评测集。不用太复杂先手工整理一批有代表性的用户问题、期望行为、通过标准样本不用太多三五十条就能拦住大部分回归。我来给个参考格式用例分类覆盖场景样例描述通过标准意图识别常见问题用户询问退款流程识别出refund意图且置信度0.8边界输入空消息、超长消息用户连发50个哈哈哈不崩溃返回引导话术多轮上下文跨轮引用第3轮提过型号第10轮再问正确引用之前提到的型号幻觉高风险知识库查无询问文档中不存在的内容明确回答未找到而非编造格式契约结构化输出提取订单信息JSON字段完整金额可解析每次改prompt、换模型、加功能先把这批用例跑一遍对比基线。哪怕有百分之十的case退化也要搞清楚原因再上线。这个习惯帮我挡住了至少三次原本会造成线上事故的改动。说白了AI应用开发的质量保证逻辑就是把概率性问题变成可回归的已知问题。5.2 账不能不算token计费模型下的成本结构成本这个事在Demo阶段谁都不会在意因为调用量小到可以忽略。等到用户真正用起来你会发现账单涨幅远比你预期快。先看清计费结构。模型按token计价输入token和输出token价格通常不一样输出往往更贵。举个例子假设一个生成型任务每次调用平均输入2000 token、输出2000 token一天10万次调用那么一个月的消耗是输入token 60亿输出token 60亿。哪怕输入输出单价都按一个非常便宜的档位算一个月纯模型费用也在一两万块这个量级这还没有算向量化、存储、GPU自建等其他成本。真实项目里让成本失控的最大推手有两个一是输出token失控模型越啰嗦越贵二是无效重试循环调用工具一次失败重试三次成本直接翻倍。我的降本组合拳是这样打的模型分层简单任务比如意图分类、信息抽取走便宜的小模型复杂推理任务才上大模型。分层之后成本可以压掉一半以上。Prompt约束输出长度明确要求回答控制在200字以内并设置max_tokens上限双保险。结果缓存相同或高相似度的请求直接命中缓存不再调用模型。语义相似度用embedding做粗判命中率可观。对Agent加预算熔断单次会话设置token上限超过直接终止或转人工。这套组合拳下来我给客户做过一次优化同样的业务量月度模型成本下降了接近六成。成本控制不是省钱强迫症它决定的是你的应用能不能撑到商业模式跑通的那一天。5.3 版本路由与灰度每一轮模型升级都是一次外科手术最后一道必答题是灰度。模型厂商会更新版本你自己的prompt也会迭代但是大模型的一个特性是小改动大波动。我的教训是永远不要在一个早上把全量流量切到新版本上。灰度要做到三个级别第一是模型层路由。请求带上版本标识比如某个复杂任务要用v1还是v2的model通过路由配置动态切换。这样哪天新版本表现异常只要改配置就能立刻切回旧版。第二是Prompt版本绑定。prompt本身要带版本号同一个模型搭配不同版本的prompt输出千差万别。线上做小流量对照先让10%的用户走新版本监控用户反馈和错误率稳定后再扩大到50%再到全量。第三是语义回归。刚才说的评测集在灰度期间要每天跑。新版本上线后哪怕各项指标看起来都OK每周也要随机抽样一批真实对话做人工复核因为模型输出有随机性单次评测通过不代表长时间稳定。我还记得一次特别典型的灰度翻车我们把某个客服模型的temperature从0.5调成0.2目的是让回答更稳定、少些发散。结果基线评测集里两个边界case直接不合格原来那些创意性回应虽然跳脱但对某些刁钻问题反而能给出意外好用的答案。温度降低后这种意外没有了输出变得四平八稳但针对性也弱了。好在当时是小流量灰度发现得早退回旧配置没有大面积影响用户。这个案例后来成了我培训材料里的经典反面教材。做过AI项目越多我越觉得调接口这个说法就像说开发App不就是写界面么一样它描述的是最外层的那十分钟而真正决定成败的是内层那些日复一日要打磨的细节上下文要不要清理记忆怎么组织prompt怎么当成接口来约束工具调用失败时系统如何体面地站起来模型升级后怎么保证不让一万个用户的心凉透。如果让我给刚入行的朋友一个建议我会说别满足于第一次调通API时的兴奋把那个Demo放到十个人手里用一周你很快就会明白AI应用开发真正难的从来不是调通接口而是在接口的阴影下为不确定性建立一座足够结实的桥。
返回列表