最近频繁有做Web开发的朋友问我同样的问题:大家都在聊的LLM和Agent,到底是什么?学了对我写业务代码有什么帮助?为什么照着网上教程做出来的东西一上点复杂度就废?
这类问题问多了,我发现一个普遍的困境——Web开发者不缺编程能力,缺的是把LLM和Agent“去神秘化”的思维转换。很多人把大模型当成数据库或者普通API用,结果发现它既不可靠又不可控。还有人以为Agent是什么高深莫测的科幻技术,其实拆开看,背后就是一套工程模式。
这篇文章我会完全站在Web开发者的视角,把LLM到Agent这条链路拆开揉碎:先搞懂LLM底层到底怎么工作,然后讲提示词优化这种纯工程化的东西,再一步步过渡到Agent的架构设计、工具链选型,最后把上线前后会踩的坑也一并交代清楚。不管你是刚接触这个概念,还是已经跑过几个demo,这篇内容应该都能给你一些能直接用的东西。
1. 先搞明白:Web开发者为什么要关心LLM和Agent
1.1 你写的CRUD和大模型应用之间,差的不是技术而是思维
Web开发的底层模式其实很固定:接收请求、处理逻辑、读写数据、返回响应。哪怕你用了再复杂的微服务架构,核心还是这套“请求-响应”模型。这套模型有个巨大的隐含假设——所有逻辑都是确定性的。一个参数传进去,结果是可预测的,bug是可定位的。
LLM应用则完全不同。同样的Prompt,模型每次返回的内容可能都不一样;同一个问题,换个温度参数结果就飘了;甚至同一个输入,在不同时间点调用,模型能力可能已经悄悄升级导致输出风格变了。这种不确定性让习惯了“代码即逻辑”的Web开发者非常难受。
我刚上手LLM应用开发时最大的挫败感就来自于此。我习惯性地想“如果用户输入A,我就调用B接口”,但LLM的世界里没有这么线性的逻辑。后来我才想明白,做LLM应用,你的思维模式要从“编写指令”切换到“设计约束”。你不再告诉程序每一步做什么,而是通过Prompt、工具定义、上下文、上下文窗口等手段,划出一个能让模型稳定发挥的活动区间。这个思维转变,比学任何技术难,也比学任何技术重要。
1.2 LLM不是数据库,也不是API——它是个“会猜的实习生”
我经常用这个类比帮Web开发者理解LLM的本质:当你把一个问题抛给大模型,它不是去数据库里查答案,也不像普通API一样执行逻辑,它是在做一件事——根据你给的上下文,逐字预测最有可能出现的内容。
就像你新招了一个学东西很快但经验不足的实习生。你给他一个任务,他不会像老员工那样按照工作手册一步步执行,而是根据他“读过的资料”(训练数据)和对你的意图的理解,推断出你认为最合理的回答。这个回答可能看起来非常专业,但如果他“读过的资料”本身有问题,或者你的任务描述有歧义,他就会一本正经地给出一个错误答案。
理解这一点,你就明白了为什么LLM会有幻觉、为什么Prompt措辞不同结果天差地别、为什么RAG(检索增强生成)能有效缓解幻觉——因为你把上下文从“模型猜”变成了“你喂”。你不再指望模型凭空记住你的业务数据,而是主动把相关资料塞进上下文,让它基于这些资料去“猜”。这是Web开发者最容易接受也最应该先掌握的增强手段。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. LLM是一台“高级自动补全机”——Web开发者视角的原理拆解
2.1 理解Transformer不用啃论文,抓住四个关键点就够了
Transformer架构的细节足够水几十小时的课,但作为Web开发者,你只需要在脑子里留下四个关键词:Token、注意力机制、上下文窗口、预训练。
Token是最小文本单元。你可以把它理解成“能独立理解的分词块”。一个Token可能是一个英文单词的一部分、一个汉字、或者一个标点符号。模型处理文本时做的事情,就是先把整段话切成Token序列,然后用注意力机制来判断这些Token之间的关系。
注意力机制听起来玄乎,其实可以用一句话概括:当模型要预测下一个Token时,它会“回看”前面所有Token,并计算每个Token对当前预测有多重要。“我在北京,我爱吃___”这句话里,“北京”和“爱吃”对预测下一个词至关重要,模型通过注意力机制给它们更高的权重,预测出大概率是“烤鸭”而不是“寿司”。
上下文窗口则是模型单次能“看到”的最大Token数。它就像你的应用能接收的最大请求体大小——一旦超出,模型要么报错,要么丢弃超出部分的信息。预训练就是模型在海量文本上做的“自动补全练习”,这个阶段让模型掌握了语言规律、知识甚至部分推理能力。
这四个概念连起来,你就明白LLM的本质了:它根据前文所有Token,通过注意力机制计算每个Token的权重,然后预测最合理的下一个Token。如此反复,一整个句子、甚至一整篇文章就诞生了。
2.2 Context Window、Token和Temperature:三个决定应用上限的参数
这三个参数是你在实际调优中打交道最多的。它们之间不是独立的,而是互相牵制。
Context Window决定了你能塞进多少上下文。拿GPT-4o来说,128K的上下文窗口看起来很大,但真实可用的没有那么多。一个很典型的场景是:你塞了大量业务文档进去,结果发现模型开始忘记你最开始给它的指令,或者在回复后半段时开始“发疯”。这有点像浏览器开了太多标签页导致内存吃紧——不是崩溃,但性能明显下降。实际开发中,我建议有效上下文用量控制在窗口上限的60%到70%以内,留出余量给模型生成的内容。
Token不仅是计费单位,也是模型处理信息的基本单位。这意味着你给模型的文本和你希望模型生成的文本,都在消耗同一份配额。优化Token的用量等于同时优化成本和上下文空间。很多自以为写得非常详尽的Prompt,实际浪费了大量Token在废话和重复描述上。
Temperature是控制“随机性”的旋钮。它的范围一般在0到2之间,数值越低模型越保守,越高越天马行空。这里有个常见的误区:很多人以为把Temperature调成0,模型输出就一定稳定。实际上,即使Temperature是0,模型输出也不是完全确定的——因为采样阶段依然存在少量随机性,而且API部署版本不同,结果也可能不同。我在生产环境里处理结构化数据时会把Temperature设为0,但依然会在业务层做格式校验和重试,不能假设模型“一定会输出合法JSON”。
2.3 用Web开发的类比理解“幻觉”和“上下文长度”
前端开发者一定见过这样的情况:页面上某个数据是空的,但UI设计上这个位置不能留白,于是你写了个兜底逻辑显示“暂无数据”。LLM的幻觉和这个非常像——当模型没有足够的信息来回答问题时,它不会诚实地告诉你“我不清楚”,而是会像那个兜底逻辑一样,生成一段读起来像模像样的内容来填补空缺。只不过模型的“暂无数据”文案会伪装得非常专业,因为你问的每个问题背后,模型都预测了一个最像答案的内容。
再举个类比理解上下文长度:普通API的请求体是有限制的,你传太多参数,服务器直接给你414(URI太长)或者413(Payload太大)。LLM的Context Window限制也是这样,只不过到了上限,LLM不会干脆地拒绝,而是默默遗忘上下文中间的部分——你可以理解为一种“软性截断”。这导致了非常讨厌的场景:你给了模型20页资料,它处理到第15页时,突然忘了第2页的关键信息。
这就是为什么RAG和记忆机制出现得这么必要——它们本质上是在帮模型做“精准的信息检索”,而不是让模型在有限上下文里硬塞所有信息。RAG就像给你的应用加了一个外部数据库:用户提问时,先检索出最相关的几段内容,连同问题一起作为Prompt输入给模型。这样上下文窗口里的内容不再是“尽力而为”的全部资料,而是一份“精选”资料。
3. 提示词优化:把Prompt当成API接口来设计
3.1 一个让LLM稳定输出JSON的完整提示词模板
Web开发里有个根深蒂固的习惯:对接接口一定要有明确的Schema,前端才知道怎么处理返回值。LLM开发同样如此,如果你的模型输出不结构化,后续所有代码都没法写。
我最常用的方式,是在提示词里直接给模型一个JSON Schema样式的说明,并明确要求只输出JSON。下面这个模板我用了很久,在各大主流模型上都很稳:
code复制你是一个数据抽取助手。根据用户提供的文本,抽取以下字段,并严格按照指定的JSON格式返回。
字段说明:
- name: 字符串,人名或机构名
- amount: 数字,涉及的金额
- date: 字符串,ISO 8601格式的日期
- tags: 字符串数组,从["催收", "合同", "发票", "通知"]中选择
输出要求:
1. 只输出JSON,不要输出任何解释、Markdown代码块或其他内容
2. 如果某个字段无法提取,用null填充
3. 严格遵守字段说明中的类型
示例输入:7月1日收到张三转来的5000元合同款。
示例输出:{"name": "张三", "amount": 5000, "date": "2024-07-01", "tags": ["合同"]}
用户输入:{user_input}
这个模板能稳定生效,关键在于四件事:角色设定、字段Schema、输出约束、一个One-shot示例。角色设定让模型进入特定工作模式,字段Schema告诉它提取什么,输出约束阻断它添加额外内容,One-shot示例演示你到底要什么。
注意:我特意写了“只输出JSON,不要输出任何解释”和“示例输入/示例输出”。实测这两个要求能显著降低模型“好心”地给输出包一层Markdown代码块,或者输出完JSON后再加一句“以上是提取结果”的概率。如果你不要求这一点,解析后端代码时总得写容错来剥掉多余的包裹。
3.2 系统提示词、Few-shot、思维链:分别解决什么问题
提示词优化不是把一句话写得更漂亮,而是针对模型不同层面的弱点做补强。系统提示词管的是“身份与全局行为”,Few-shot管的是“通过示例传达格式和逻辑偏好”,思维链管的是“让模型展示推理过程”。
系统提示词就是你给模型的“岗位说明书”。在Web开发里,这相当于你用代码初始化一个对象时传入的配置项。它定义了模型面对任何用户输入时的默认行为:角色是什么、语气是什么、能做什么不能做什么。我建议系统提示词保持精简,只放“你是谁、你的目标、你的红线”这个级别的信息。
Few-shot是给模型看示例。为什么要看示例?因为大模型有强大的少样本学习能力,你不需要写一堆规则去解释你想要的格式,直接给它两个“输入→输出”的例子,它就能模仿。这很像是给前端工程师看一个交互稿——不用你费劲描述,他看到UI图就知道这个页面怎么做了。
思维链(Chain-of-Thought)则是我在遇到模型推理出错时最常用的技巧。它的做法很简单:在Prompt里加上“请一步一步思考,先分析问题,再给出结论。”让模型把推理过程显式写出来,最终答案的准确率会有明显提升。原因其实也很“工程化”:注意力机制让它能更好地利用自己刚才生成的中间推理Token作为基础来推理下一步,而不是一上来就急着给结论。不过代价是输出Token变多、响应变慢、成本上升。现实中我通常只在需要复杂推理的任务上才用思维链,简单提取类任务用不上。
3.3 提示词版本管理与回归测试:把Prompt当代码管
大部分Web开发者写Prompt的习惯很糟糕:直接在网页端或API调用里随手改,改到“看起来OK了”就上线。这在做演示时没问题,但一旦接入生产环境就原形毕露——因为Prompt是代码的一部分,它出了问题就是线上事故。
我自己踩过的坑是:某次给客服系统优化提示词,我在网页端调试了几十轮,感觉效果很不错,便把最新版本直接复制到生产环境。第二天业务方反馈,模型开始拒绝处理一部分正常工单。排查后发现,我调试时无意中让模型变得过度谨慎,导致它对任何信息不完整的工单都一律拒绝。因为Prompt没有版本管理,我甚至无法快速回滚到上一个可用版本。
从那以后,我把Prompt当成API接口来管。具体做法是:每个Prompt都有一个版本号,连同它依赖的模型版本、Temperature参数记录在同一个配置文件里;修改Prompt前先拉分支,改完在一组固定的测试用例上跑回归,确认输出稳定无误后才“合并”到生产配置。这组测试用例覆盖面要广,既包含正常情况下应当正确处理的输入,也应包含边界情况和历史上曾经出过问题的输入。
你还可以在系统中加一道“门槛”——对模型输出做校验,如果不符合预期就重试或报警。用代码去兜底Prompt的不确定性,这是Prompt工程和Web开发思维真正结合的地方。
4. Agent架构实战:从一个问题到一连串决策
4.1 Agent到底是个什么东西:LLM + 规划 + 工具 + 记忆
很多人以为Agent就是“更聪明的LLM”。初期我也这么误解,直到自己上手才发现:Agent是一个系统。LLM只是这个系统里的“决策大脑”,真正让Agent产生能力的是它周围的工程组件。
拆开来看,Agent有四个核心模块。规划模块负责把一个复杂目标拆解成若干可执行步骤——这就像你拿到一个需求,先拆成多个接口和页面,再排期开发。工具层是Agent的“手”,它让模型能调用外部API、搜索网页、操作数据库,突破纯文本生成的能力结界。记忆模块则让Agent具备处理长时间任务的能力,既包括同一轮对话内的短期记忆,也包括跨会话的长期记忆。
把这四个组件串起来,Agent的工作流程就变成了:接收用户目标 -> 规划模块拆解任务 -> 根据任务选择并调用工具 -> 观察工具返回结果 -> 根据结果决定下一步动作 → 循环直到目标完成。全程不需要人为介入,Agent自己就成了一个“会使用工具的实习生”。
Web开发者看到这里应该很熟悉——这不就是一个带状态机的后端服务吗?只不过状态转移不再是if-else,而是由大模型来决策。你保留了自己写业务逻辑的能力,只是把“决策”这个职责交给了模型,把“执行”这个职责交给了你自己写的工具函数。
4.2 工具调用(Function Calling)是Agent的地基
Agent的“工具”不是自己凭空变出来的,它需要开发者显式定义,并提供给模型。这个机制叫Function Calling。它的工作方式很有意思:你把工具函数的结构化描述(包括名称、参数列表、功能说明)以JSON Schema的形式告诉模型,模型在回答时不会直接执行函数,而是“声明”它想调用哪个函数、传入什么参数,然后由你的代码来实际执行。
这就好比前端把按钮和表单提交给用户,用户点按钮后由后端去真正处理请求。模型负责“判断何时需要调用工具以及调用哪个”,你的代码负责“真正执行调用并把结果返回给模型”。
我在设计工具函数时有一条核心原则:一个工具只做一件事,且参数尽量少。原因是模型选择正确的工具依赖你提供的描述是否清晰。假如你把一个工具描述成“处理所有文本操作”,模型就很难判断传哪个参数控制替换还是提取。反之,拆成“replace_text”和“extract_json”两个独立工具,描述精准了,模型就不太会选错。
另外,工具的返回结果一定要精简。我给模型设计的工具返回结果通常控制在几百个Token以内。工具返回的结果本身也会占据上下文窗口,一旦工具返回的数据过大,模型很容易在下一步忘记了最初的用户意图。这跟Web开发里把冗余字段截断再传给前端的道理完全一样——只传必要的数据。
4.3 记忆系统:短期记忆和长期记忆怎么设计
没有记忆的Agent,每轮对话都在“失忆”。这在一次性问答场景没影响,但一旦任务需要多轮操作,问题就来了。
短期记忆直接映射到上下文窗口。你把对话历史、当前状态、工具调用的中间结果都塞进Context Window,模型就能“记得”本次对话的完整脉络。它的实现最简单,但消耗大,而且随着对话变长会触顶上下文上限。我在实际项目里的做法是:对话历史只保留最近N轮,更早的内容压缩成摘要放回上下文。这个“摘要替代完整历史”的思路很像Web前端的虚拟列表——大量的历史Item不再渲染,只保留一个总览。
长期记忆则需要外部存储。你可以用向量数据库存用户的历史偏好、产品资料、关键事实,当需要时通过语义检索召回与当前场景相关的片段,放入短期上下文。这个模式运行起来,Agent才真正像一个“有经验的人”:它记得昨天聊过的话题,知道用户的偏好,不再每次从零开始。
实现长期记忆的时候,有一点值得注意:写入记忆和读取记忆最好由不同的工具函数完成,并分别设计合适的索引策略。很多初级项目图省事,把“记住这件事”和“回答这个问题”绑在一起,结果常常记了不该记的,或者该用时查不到。这跟做业务表时要区分OLTP和OLAP是一个道理。
4.4 编排模式:ReAct、Plan-and-Execute和多Agent协作
有了工具和记忆,Agent还需要一个“行为框架”来决定下一步该做什么。目前业界用得最广的编排模式有三种,理解它们对架构选型很重要。
ReAct(Reasoning + Acting)是目前最常见的Agent实现方式,它的循环很朴素:思考下一步该怎么做,调用一个工具,观察结果,再思考下一步。它相当于一个自己给自己下达任务的循环,直到用户目标被满足。这种模式适合执行步骤明确的场景,比如“查一下这个订单的物流状态,然后提醒收货人”。
Plan-and-Execute模式把“规划”和“执行”解耦:第一轮先让模型根据用户目标生成一份完整计划(比如“先查库存,再生成采购单,最后发邮件给供应商”),之后逐个执行计划中的步骤,遇到失败再修正计划。这种模式适合目标复杂但步骤相对稳固的业务场景,它的最大好处是用户能在实际执行前看到Agent的完整计划,便于提前干预和审核。
多Agent协作则是把不同职责拆成多个Agent,每个Agent负责一个角色(研究Agent、编写Agent、审查Agent),通过消息传递让它们协同完成一个大任务。这种架构的灵活性很高,但调试复杂度成倍上升。我的经验是:如果单个Agent加工具能解决的,就绝对不要上多Agent,否则你很快会陷入Agent之间传递信息格式不一致、互相干扰的泥潭。
5. 从零搭建一个Agent应用:场景拆解与工具链实战
5.1 一个具体场景:让Agent做自动化竞品信息收集
纸上谈兵没有意义,我拿一个实际做过的场景来完整走一遍:假设你是一家SaaS公司的Web开发者,业务方希望你做一个自动化工具——每天去竞品官网、公告栏和知识库抓取更新,汇总成一份简报发到团队群。
用传统Web开发的思路,这是一件比较麻烦的事:你需要为每个数据源写爬虫、做页面解析器、处理反爬机制、写去重逻辑、生成固定格式的简报、再对接群机器人。整条链路要维护的代码量不小,而且数据源一小改版你就得跟着改。
用Agent来做的话,整体架构会简洁很多。底层是调用LLM的Agent循环,上面挂几个工具:一个网页抓取工具(输入URL,输出网页正文文本)、一个数据去重工具(输入一批文本,输出新增内容)、一个简报生成工具(把零散的更新拼成Markdown格式的简报),以及一个飞书群机器人发送工具(输入文本,发送到指定群聊)。
Agent运行时的逻辑由模型自己编排:先访问竞品官网,发现页面更新了,就抓取正文并调用去重工具判断是不是新增内容,如果是,就保存并汇总到简报里,最后在约定的时间生成简报、调到发送工具推送到群里。
这个过程中,Web开发者没有写任何“判断逻辑”的代码——比如“如果某个URL返回状态码200且内容包含某关键词,则保存”。所有的判断都交给了LLM,开发者唯一要保证的是提供可靠的输入输出工具和稳定的执行环境。
5.2 框架选型对比:LangChain、Dify、Coze、自研该选谁
做Agent应用时第一件烦心事是选框架。社区里LangChain、Dify、Coze、Semantic Kernel等选项很多,各有各的拥趸。我自己的选型经验可以用一句话概括:项目规模决定框架,团队能力决定边界。
LangChain是当前生态最全的Agent编排框架,优点是可编程性强,支持的模型、向量库和工具连接器非常多;缺点也很明显——抽象层级太多,排查问题时你要穿过很多层才能找到底层原因。它适合已经有较多AI开发经验、需要深度定制逻辑的团队。
Dify是一个开源的可视化Agent开发平台,它把很多常用能力(知识库、工作流、模型管理、日志追踪)做成了可视化编排。Web开发者上手成本很低,很适合烂熟业务逻辑但不想折腾底层的团队快速交付产品。它的局限性在于复杂业务逻辑的组合灵活性不如LangChain。如果业务里需要非常多自定义工具和复杂的流程编排,纯Dify的Workflow可能会让你感觉束手束脚。
Coze(扣子)则是字节旗下的Agent平台,最大的优势是集成了大量字节生态的能力(比如飞书、抖音相关组件),如果目标场景恰好绑定这些生态,开发效率极高。不过作为托管平台,它在数据的私有化部署和数据隐私方面天然受限。
至于自研Agent框架,有人说这是“重复造轮子”,我不完全同意。如果你要构建的Agent核心逻辑非常专,现有框架反而会成为你的约束。自己实现一个基于ReAct循环的轻量Agent,本质上就是几十行代码,加上你自己的工具注册和记忆机制。它让你完全掌控数据流和排查链路。我自己的经验是,内部工具数量超过八个、交互逻辑被框架别扭地实现过一两次之后,自研反而省心。
5.3 构建步骤拆解:从定义目标、写工具函数、写System Prompt到联调测试
我一般按照五步来搭建一个Agent应用。
第一步,精确定义Agent的目标边界。明确它能做什么、不能做什么,以及完成任务的验收标准。这里容易犯的错误是设定一个模糊目标,比如“帮我分析竞争对手”。正确的目标是“每天自动抓取指定竞品网站的更新页面,提取产品功能、定价、公告三类信息,生成Markdown简报并发送至指定飞书群”。
第二步,写工具函数。每个工具函数都是一个Web后端接口,输入输出均为JSON。要在函数里做好异常处理,比如网页抓取失败时返回错误信息,而不是抛异常让Agent循环崩溃。工具的描述信息要非常精确,因为模型是依靠描述来选择工具的,工具描述就是Agent的“API文档”。
第三步,写System Prompt。在Prompt里精确描述Agent的身份、可用工具及其使用方式、输出格式和红线边界。许多新手把Agent的Prompt写得含糊不清,导致Agent不停选择错误的工具,或者输出格式千变万化。一个严谨的System Prompt应该像一份工作交接文档,写清楚“遇到什么情况做什么事”“做事有什么优先级”“绝对不要做什么”。
第四步,用一个精简但覆盖正常路径的测试用例集走一遍端到端流程。在这个阶段,我建议把Agent每一步的思考过程、工具调用参数、工具返回结果都打印出来。这一步约等于Web开发里的联调,你必须逐个模块确认数据流转符合预期。
第五步,加入异常分支处理和重试机制。比如工具调用失败后让Agent稍后重试;模型输出不符合预期格式时自动重试一次;如果连续多次失败,终止流程并通知人工介入。
6. 上线前必须知道的坑:错误排查与成本控制
6.1 最常见的几个运行时错误和排查思路
Agent应用的运行时错误与普通Web应用的bug有本质区别——普通报错有确定的错误原因和代码位置,而Agent的错误往往隐藏在多轮“模型决策”和“工具调用”中间。上线前一定要对下面这几个高频问题有预案。
第一个是“工具调用参数与工具定义不匹配”的错误,典型报错类似provider rejected the request schema or tool payload。出现这类错误,通常是你给模型传的工具JSON Schema有格式问题,或者工具函数的必填参数定义得不合理。排查方式是:直接把工具Schema打出来,自己用JSON Schema校验工具过一遍。
第二个是“LLM请求超时”,典型报错类似llm request timed out。这种情况通常不是模型服务本身故障,而是你的Agent循环太慢,或者某个工具函数耗时太长导致单轮循环超时。解决思路是给工具调用设置单独的较长的超时时间,同时给整个Agent执行设置一个总超时上限,避免一次任务挂十几分钟。
第三个是“Agent执行意外终止”,典型报错类似agent execution terminated due to error。这通常是Agent循环中某一步工具报错,并且连续重试后仍未恢复。排查方式还是去看完整的Trace日志。我强烈建议你在开发阶段就把Agent的每一步行动记录下来:思考内容、选择的工具、传入参数、工具返回结果、模型对结果的理解。这份日志就是Agent应用调试的“断点”。
6.2 超时、限流、重试:如何让Agent应用在中度负载下保持稳定
Agent类应用和普通Web应用在稳定性设计上有显著差异。普通Web应用你可以靠水平扩容堆机器来解决负载问题,但Agent应用的核心瓶颈往往在大模型API的限流和你自己的架构设计上。
由于Agent单次任务会多次调用LLM,限流问题会被无限放大。比如你的模型供应商限制了每分钟1000次请求,一个重度Agent任务可能一轮循环就要消耗20次调用,几个任务并发下去立刻就会触发限流。我的应对策略是:在Agent的调度层加一个简单的令牌桶限流器,并设置一个调度队列,限制同时运行的Agent任务数量。这样即使有大量用户触发任务,也会排队执行,而不是一拥而上把模型API打爆。
重试机制也有讲究。通常我使用指数退避策略:第一次失败后等1秒重试,第二次等2秒,第三次等4秒,最多重试三次后进入人工兜底流程。对于“模型输出格式不合法”这类重试,我会在重试时附带一段“你上次的输出格式不符合要求,请严格按指定JSON格式输出”,否则单纯重试大概率还是同样的结果。
如果你在架构上引入了RAG,那么“向量数据库查询延迟”也可能成为瓶颈。不要指望每次对话都实时查询向量库——线上环境我会给检索结果加一层Redis缓存,热点问题直接命中缓存,能省掉大量不必要的模型调用。
6.3 成本估算:Token是流量,超了是要付钱的那种
我把Token消耗比作Web应用的流量消耗,是非常贴切的。每次用户与LLM交互,你都在为输入和输出Token付费,这些费用在毫秒级之内悄悄累积。如果你没有做成本治理,月底账单会让你重新认识“万物皆有价”这句话。
你需要掌握三条基本成本规律。第一,输入Token很便宜,输出Token很贵,两者的价格可以相差一倍以上。所以让模型少说废话、精简输出格式,本质上是在省大钱。第二,对话历史越长成本越高,而长对话未必带来更好的效果。所以一定要限制历史轮数,或对早期历史做摘要压缩。第三,缓存能极大降低成本。各大模型服务商通常会对相同的输入Token提供缓存折扣,你可以在应用层主动设计“把固定的System Prompt和其他静态上下文拼成一个固定前缀”,让更多请求命中缓存。
当你把成本模型表画出来后,你会发现最省钱的做法反而不是换更便宜的模型,而是减少无效调用。很多Agent任务在Simple Query上就能解决,你不必一上来就上最贵的旗舰模型。实际项目中,我会在系统里面加一条路由规则:简单任务走便宜的小模型,复杂推理任务才走旗舰模型。这个策略能把整套系统的月成本降一个数量级,而用户体验几乎无感知。
6.4 一个容易忽视的细节:模型输出校验和兜底
Web开发中,你对后端接口的返回数据一定不会直接信任,拿到之后要先做类型校验、判空、异常捕获。但对LLM的返回结果,很多人却直接信任,不做任何校验就进入下一个环节。
这个习惯必须改。模型输出是人类语言,天然带有不确定性和格式漂移风险。哪怕你Prompt里写了“只输出JSON”,偶尔也会有模型自作聪明加一段Markdown代码块标注。我现在的做法是:模型每一次输出都要过一层“结构校验器”,把模型输出解析成结构化数据,解析失败就触发重试或修复操作。对于JSON输出,我甚至写了一个通用的格式修复函数,专门处理模型常见的错误,比如多余的尾逗号、未转义的引号、被Markdown代码块包裹等等。
把模型当成不可靠的第三方接口来对待,而不是当成可信任的内部函数。这个心态转变,是所有Web开发者做LLM应用时最需要完成的底层思维升级。
写到这里,这篇文章的主体内容已经讲完了。从我个人经验来看,Web开发者转型做LLM和Agent应用,真正难的从来不是某个具体的API或框架怎么用,而是一次思维模式的转换:从“写确定性的代码”到“设计不确定性的系统”。只要跨过了这道坎,LLM和Agent无非就是你工具箱里另一件趁手的工具,和数据库、缓存、消息队列没有本质区别。你在Web开发里练就的工程化能力、架构思维和排查问题的方法,恰恰是许多只懂模型不懂工程的人最缺的东西——这也是Web开发者在AI时代最大的底气。
