1. 先别急着选框架Agent架构分几档够用就行这两年接触了不少AI Agent相关的项目我发现团队最容易踩的坑不是模型调不好而是架构段位和业务复杂度完全对不上。有人刚写完一个request_llm()封装就急着给整个系统挂上智能体平台的名头也有人业务里只有三个工具调用却上了一整套带任务编排、状态持久化、人工审批的Harness Runtime。两边最后都会在维护性和成本上栽跟头。这篇文章我想把这条演进路径完整拆开讲一遍从最简单的一次LLM调用到带工具调用的中间态再到真正扛得住生产压力的Harness Runtime。重点不是什么框架更好用而是每一步架构变化背后到底在解决什么问题、边界在哪里、什么时候该升级、什么时候该克制。适合正在做Agent服务端设计、或者想把现有原型改造成生产级系统的开发者参考。1.1 一次LLM调用为什么不够用我见过很多项目的第一版Agent本质上就是一次LLM调用messages [{system: 你是客服助手}, {user: user_input}] result request_llm(messagesmessages) print(result.content)这段代码放在Demo里非常顺滑请求过去、文本回来、页面展示。但一旦放到真实业务里问题立刻冒出来。首先模型是无状态的它不记得上一轮说过什么你得自己把全部历史消息塞进请求里而这个拼接历史的动作很快会变成一团乱麻。其次是它没有手你说帮我查一下订单、它只会回你一段文本实际怎么去查数据、调什么接口全靠下游代码自己想办法。更麻烦的是同步阻塞。一次调用如果因为模型端限流、网络抖动、响应超时整个请求就卡在那里。用户盯着转圈后端不知道是继续等还是放弃等也不是重试也不是。整个过程还没有任何中间日志你只知道出错了不知道错在第几步、哪个工具、哪一段上下文。我这么说并不是否定这种简单架构。如果业务就是文本摘要、翻译、情感分类、命名实体抽取或者一个读一次库里数据就能回答的轻量问答那一次LLM调用完全够用。它的问题只出现在你的业务开始需要状态、工具、异步、恢复这四个东西的时候。1.2 判断Agent化改造的三个硬信号什么信号出现说明你该从单次调用往Agent架构走了我一般看三个硬指标。第一个是多轮状态。模型要记住用户中途改过的条件、前面说过的话、当前进行到哪一步。如果你的代码里已经出现了大段手拼历史消息的逻辑还时不时要截断、要压缩、要防止Token爆炸那就说明你缺一个明确的会话状态管理层而不是继续在prompt里打补丁。第二个是外部工具。用户的诉求必须落到真实动作上查订单、发通知、改配置、生成文件。也就是说模型不仅要想还要做。这时候你要让模型输出结构化指令再有一个执行层去调用对应系统而不是用正则表达式去解析模型回复里的命令词。第三个是长耗时任务。任务可能跑上几十秒甚至几分钟用户不可能一直盯着同步请求等结果。你需要把任务丢到队列里后台跑跑完通过事件或回调通知。一旦引入异步就必须考虑任务中断、失败重试、进度反馈这些已经远远超出一次LLM调用的范畴了。还有一个容易忽略的附加信号失败能不能自动恢复。用户点了提交后端执行到一半挂了你重启之后是靠日志硬啃还是能从断点继续跑如果一个动作重复执行会产生副作用重复扣款、重复发消息那你对重试这两个字必须格外小心——这也是后面Harness Runtime要解决的幂等问题的起点。1.3 从调用到运行时真正变化的是边界我经常用一个类比来解释架构升级一次LLM调用像是问一次路问完就忘了Agent Loop像是带了个导航知道往哪走、实时纠偏Harness Runtime则更像一套交规加交警体系车可以跑、但得有车道、有红绿灯、有事故处理流程。从技术边界上看升级发生在四个维度从无状态变成有状态从同步变成异步从单次不可重入变成可恢复可重跑从自由调用变成有权限约束、有审计记录。下面这张表能比较直观地看出差距维度单次LLM调用Agent LoopHarness Runtime状态管理无靠调用方拼历史有会话记忆但偏内存态状态持久化支持重启恢复执行方式同步请求-响应循环执行异步可简单排队事件驱动任务队列可重放工具能力基本没有能调工具但无统一治理统一注册、权限校验、熔断限流失败处理超时报错循环内可重试幂等、补偿、快照恢复可观测性单请求日志有步骤日志全链路追踪决策快照所以架构选型的第一步不是去看哪家框架文档写得好看而是老老实实回答我的业务到底需要四个维度里的哪几个都不需要就停在单次调用只缺工具和状态走到Agent Loop就够了如果开始有并发、有审计要求、有跨系统长流程再上Harness Runtime也不迟。2. 三种基础架构拆开看从直连调用到Agent Loop2.1 直连调用模式最快但最脆直连调用是最容易理解的架构客户端请求进来服务端把prompt拼好请求模型API拿到结果直接返回。def generate_summary(text: str) - str: messages [ {role: system, content: 你是专业的摘要助手。}, {role: user, content: text}, ] result request_llm(messagesmessages) return result.content这套架构的优点是实现成本极低只有一条链路调试直观几乎不需要任何Agent概念。缺点是前面说的那四条——没有状态、不能调工具、同步阻塞、失败不可恢复。我见过有些内部小工具一直跑在直连模式上好几个月也没出事因为它的使用场景就是单轮、低并发、失败重来一遍就完事。这种就该继续用简单方案别为了转型而转型。2.2 工具增强模式给模型装上手和眼睛如果需求变成帮我查一下订单状态然后回复我直连模式就做不到了。这时候要引入工具增强模式把工具列表告诉模型让模型决定用哪个工具、传什么参数再由你的代码执行工具最后把执行结果送回模型生成最终回答。TOOLS [ { name: query_order, description: 根据订单编号查询订单当前状态, parameters: { order_id: {type: string, description: 订单编号} } } ] response request_llm_with_tools(messages, toolsTOOLS) if response.tool_calls: for call in response.tool_calls: result execute_tool(call.name, call.args) messages.append({role: tool, content: json.dumps(result, ensure_asciiFalse)}) final_answer request_llm(messagesmessages) print(final_answer.content)这段代码看起来不复杂但真正用起来会发现几个隐藏问题。一个问题是工具描述写得好不好直接影响模型选工具的正确率描述模糊的模型就像进了大超市没有导购牌拿错东西是常事。另一个问题是工具执行后返回的结果往往很大比如查询订单返回了十来个字段全部塞进上下文下一轮模型处理和费用都会受影响。还有一个问题是工具执行异常时要不要把这个异常信息回传给模型回传吧可能让模型陷入反复自我怀疑的循环不回传吧它就不知道发生了什么。工具增强模式已经算Agent雏形了因为系统不再只是输出文字而是具备了感知外部世界并行动的能力。检索增强生成RAG本质上也是这种模式的一种特例——把检索结果当作特殊的工具结果拼进上下文让模型基于真实资料回答。如果业务主要靠工具调用和内容检索驱动这个模式通常够用。2.3 完整Agent Loop模式任务粒度的编排再往前走一步就是完整的Agent Loop模型不再只做一次工具调用决策而是在一个循环里不断进行规划—执行—观察—再规划直到任务完成。while not task_done and step MAX_STEPS: plan planner.get_plan(task, memory) for action in plan.actions: result executor.execute(action) memory.observe(result) if plan.is_finished: break这个阶段系统已经接近于一个任务执行引擎。它的能力边界从回答一个问题扩展到了完成一个任务。比如用户说帮我整理本周所有未发货订单生成催发货邮件草稿系统可能需要先查数据库、再筛选、再调用另一个工具生成文案、最后按客户归类。每一步都依赖上一步的结果中间还可能出错。Agent Loop引入了一个直连模式完全不存在的问题循环控制。模型有概率在同一件事上来回折腾所以必须限制最大Step数、设定全局超时、提供人为中断入口。上下文也会在一个循环里快速增长因为每一步的工具结果都留在了memory里不做裁剪就会让Token费用线性上升。这时候怎么把上下文管好已经从技巧变成了必修课。2.4 三种架构的选型对照表架构模式业务复杂度状态管理失败恢复代码量适合场景直连调用很低无无几十行单轮问答、摘要、分类工具增强中等会话内存手动补几百行带工具查询的问答、RAGAgent Loop较高循环内状态可写补偿逻辑上千行多步骤任务、自动化编排这里有个很容易踩的误区总有人认为Agent Loop一定比工具增强模式高级。其实多数业务根本不需要完整的规划—执行循环只要一个工具选择器就够了。你让模型直接选中某个工具并返回答案比让它自己拆解成子任务要稳得多。架构没有高下之分只有匹配不匹配。3. Harness Runtime到底解决什么问题模块边界讲清楚3.1 Agent工程化把场景变成系统问题当一个Agent任务开始有并发、有共享数据、有严格的成功标准时你的关注点就从模型怎么答转移到了系统怎么不出错。我见过一个典型的翻车现场一个Agent任务在内存里维护当前状态单机Demo一切正常一上多实例部署两台机器各跑各的用户请求被负载均衡分到不同实例状态就丢了。还有的人因为模型一次偶然的乱序返回导致工具执行顺序错了订单状态被覆盖最后只能人工修复数据。这些都是典型的工程问题。解决它们靠的不是更好的prompt而是一个能承担运行环境职责的底座它知道任务当前在哪一步、状态存在哪里、失败之后怎么办、哪个工具被谁用了、资源配额还剩多少。这个底座就是我说的Harness Runtime。3.2 Harness Runtime的模块边界一个完整的Harness Runtime我习惯分五个模块看第一个是调度内核。它负责从队列里取出任务驱动Agent循环往下走处理并发、超时、取消。说白了就是这个任务该轮到哪一步的总指挥。第二个是上下文管理器。它决定哪些内容能进模型窗口、哪些该压缩、哪些该存到外部存储。上下文管理器不是简单的历史拼接器它要做增量摘要、结果截断、关键信息抽取。第三个是工具网关。所有外部调用都通过它进出校验参数、执行权限检查、设置超时、做熔断和重试。工具网关是Agent的手臂也是安全边界。第四个是策略引擎。它负责回答能不能做的问题当前用户有没有权限调这个工具这个工具今天的调用次数到没到配额敏感操作需不需要人工审批在面向企业的Agent系统里策略引擎往往比模型能力更早成为瓶颈。第五个是可观测层。它记录每个步骤的输入输出、每个工具的调用耗时、Token消耗和决策快照。没有这层线上出了问题就只能对着日志文件瞎猜。这五个模块不是每个项目都要完整实现。业务还小的时候一个中等复杂度的Agent Loop脚本就够了但当出现跨团队、跨系统、需要审计和恢复的长流程任务时这五个模块一个都省不了。3.3 事件驱动与状态持久化让Agent可以被重跑Harness Runtime和普通Agent Loop最本质的区别我总结为两个字可重跑。普通循环里状态都活在内存变量中进程一退烟消云散。而在Harness Runtime里每次模型决策、每次工具调用、每次状态迁移都会落成一条持久化事件。系统任何时候崩溃都能从最后一条事件继续恢复。这个思路很像拍电影时的场记每一场戏拍了什么、谁参与了、结果如何全都被记录下来。出问题时可以把录像倒回去重看发现问题出在哪一场然后从那一场重新拍。Agent也一样只要有完整的事件流和定期快照就能定位和复盘。具体落地时有两件事必须做。一是每次LLM调用和工具调用都要带唯一标识比如session_id加步骤序号这样才能把一次决策的前因后果串起来重放。二是所有写操作都要支持幂等用idempotency_key让同一个动作无论执行一次还是多次结果都一样防止恢复过程中产生重复副作用。3.4 沙箱、权限与安全收敛Agent一旦能调用工具它实际上拥有了操作真实系统的权利。这时候最危险的设计是让Agent直接去碰数据库、直接调内部接口。正确的做法是工具网关后面接一个受控执行层比如云函数、容器任务或独立进程Agent只拿到最小够用的凭证而不是一把万能钥匙。权限也要收得尽可能细。同样是订单相关工具查询订单和取消订单的权限等级必须分开用户的身份标识要从请求头里取而不是让模型在prompt里猜当前用户是谁。敏感操作退款、删除、对外发消息建议走策略引擎的人工审批通道默认不自动执行。还要注意任何时候都不要把密钥、令牌写进模型可见的上下文里。模型只要在上下文中看到了一个值就有可能在输出中被意外引用或泄露。所有凭证应存放在执行环境变量或密钥管理服务中工具网关在做实际调用时注入而不是拼在prompt里。4. 实操怎么把单体Agent平稳迁到Harness Runtime4.1 先画好运行时状态机迁移的第一步不是写代码是把Agent的运行状态画清楚。我一般用六状态模型pending任务已创建等待调度runningAgent正在规划或执行waiting_tool等待外部工具返回completed任务正常结束failed任务失败等待处理或人工介入cancelled任务被取消状态之间的迁移要严格限定pending - running、running - waiting_tool、waiting_tool - running、running - completed/failed、running - cancelled。为什么要把waiting_tool单列出来因为工具调用往往是长耗时操作你必须能让任务让出CPU而不是锁住整个线程。状态机画好后用一个agent_runs表把它落库CREATE TABLE agent_runs ( run_id TEXT PRIMARY KEY, session_id TEXT NOT NULL, status TEXT NOT NULL, current_step INTEGER DEFAULT 0, max_steps INTEGER DEFAULT 50, last_event_at TIMESTAMP, checkpoint JSONB, created_at TIMESTAMP DEFAULT now() );current_step和checkpoint是恢复的关键进程崩溃后重启只要拿到这两列就能知道任务跑到哪一步、关键现场是什么。4.2 统一工具协议与注册表把工具从散落在各个业务模块里的函数升级成有统一描述和生命周期的资源是迁移中最费体力也最值得的一步。我给工具定义过一套最小协议每个工具都要提供{ name: query_order_status, version: 1.2.0, description: 根据订单编号查询订单状态用于售前、售后查询场景, input_schema: { type: object, properties: { order_id: { type: string, description: 订单编号格式如 ORD2025xxxx } }, required: [order_id] }, permission: order:read, timeout_ms: 8000, idempotent: false, audit_level: normal }这里有几个细节值得注意。permission字段用来做权限匹配用户必须有order:read权限才能调用。idempotent字段标记工具是否幂等如果为false调用前必须生成幂等键并做防重。audit_level则告诉审计模块这个操作是否敏感。工具描述越具体模型犯错越少。我见过一个项目把查询订单和删除订单写在同一个工具里让模型通过参数区分结果某次模型传错了参数险些造成事故。从那以后我坚持一个原则一个工具只做一件事名字和描述里把边界写死。4.3 上下文与Memory的分层设计迁移到Harness Runtime后就不能再一股脑把历史全塞进prompt了。我把记忆分成三层。L1是当前上下文也就是真正放到模型窗口里的内容通常只保留最近几轮对话和当前任务必需的资料Token预算严格控制。L2是工作记忆属于会话级数据存在Redis或数据库里保存一些关键事实用户偏好、已经确认过的订单号、尚未完成的目标。L2的好处是下一次进入LLM调用前只把跟当前步骤相关的信息注入上下文而不是把所有历史翻出来。L3是长期记忆时常向量化后放到向量库里用于检索增强用户过去问过什么、知识库里的相关文档、某个业务规则的说明。模型不需要记住全部只需要需要时查一下。分层带来的直接收益是费用和稳定性。我见过一个团队把三个小时的多轮聊天记录每次都原样塞给模型单轮请求消耗好几万Token不仅贵而且模型被无关历史干扰回答质量反而下降。分层后每次请求只带精炼的事实和检索结果费用降了一半效果还更稳。4.4 调度循环与重试语义落地下面是一个简化版但足够说明思路的调度循环骨架我用Python伪代码写class AgentRuntime: def __init__(self, state_store, policy_engine, tool_gateway, llm_client): self.state_store state_store self.policy_engine policy_engine self.tool_gateway tool_gateway self.llm_client llm_client async def run(self, run_id: str, user_request: str): state await self.state_store.load(run_id) while state.status running and state.current_step state.max_steps: context await self.state_store.build_context(run_id) response await self.llm_client.chat( contextcontext, toolsself.policy_engine.visible_tools(run_id), request_idf{run_id}:{state.current_step}, ) if response.is_final_answer(): await self.state_store.finish(run_id, response.content) return for call in response.tool_calls: if not await self.policy_engine.allows(run_id, call): await self.state_store.record_denied_action(call) continue await self.state_store.enter_waiting_tool(run_id, call) result await self.tool_gateway.invoke( call.name, call.args, idempotency_keyf{run_id}:{call.tool_call_id}, ) await self.state_store.record_tool_result(run_id, call, result) await self.state_store.increment_step(run_id)这个循环里有两个非常关键的工程细节。第一个是enter_waiting_tool这个状态迁移它把状态先落库再执行工具这样即使执行到一半进程崩溃任务也不会丢失。第二个是重试语义工具调用的超时和重试参数配置在工具网关层而不是业务层重试时使用同一个idempotency_key保证所有写操作不会重复生效。4.5 可观测性与审计的落地方案很多Agent系统的第一版几乎没有日志出了问题只能复现。进入生产环境后可观测性是硬需求。我建议用结构化事件流来代替零散的文本日志每个事件至少包含这些字段字段含义event_type事件类型llm_call / tool_call / state_change / deniedrun_id任务ID用于恢复和关联session_id会话ID用于追溯用户step_seq当前步骤序号trace_id链路追踪ID串联多个服务model使用的模型标识input_tokens输入Token数output_tokens输出Token数tool_name工具名称status本次事件状态除了日志我强烈建议做决策快照每次调用模型请求和返回的完整prompt、完整输出、温度、模型版本都落一份到存储里至少保留一段时间。这样做不是为了好看而是为了复盘线上问题——没有决策快照你永远不知道模型是怎么一步一步变成错误结果的。成本确实会增加但这是Agent系统里最值得花的一笔钱。5. 架构选型决策矩阵六个维度给结论5.1 六个关键维度与权重设计做架构选型时我建议把讨论从用哪个框架拉回到你的约束条件是什么。我常用六个维度打分按业务场景调权重稳定性、开发速度、成本、故障恢复能力、扩展性、安全性。举个例子一个内部知识库问答项目权重可能这样排成本30、开发速度25、稳定性20、安全性15、故障恢复5、扩展性5。因为它不需要复杂编排唯一的要求是别太贵、尽快上线。而一个跨系统订单自动化项目权重完全反过来稳定性30、故障恢复25、安全性20、扩展性15、开发速度5、成本5。因为这种系统一旦出错损失远超开发成本。权重定下来之后再逐个方案打分乘以权重求和。你会发现很多时候结论并不需要纠结方案之间差距巨大你只是被框架知名度蒙住了眼。5.2 用两个虚构场景验一验我用两个虚构场景来演示这个过程。场景A某企业内部知识库问答系统。工具只有检索文档和获取用户信息单轮交互为主用户容忍度较高。这个场景我直接选工具增强模式配合向量检索。不需要任务编排不需要Agent Loop更不需要Harness Runtime。最多把L2工作记忆做到会话级避免用户重复发背景信息。这套方案最快、最省钱、也最稳定。场景B某跨系统订单处理流程。用户提交申请后系统要查库存、算价格、锁库存、创建订单、发通知、生成物流单号而且中途可能有人工审批环节。这种场景我会毫不犹豫上Harness Runtime必须有状态机、必须有事件持久化、必须支持断点恢复、必须有幂等处理。因为任何一个环节失败都可能导致库存被锁死或者用户收到重复通知。这两个场景说明一件事架构选型跟你用什么模型、多少参数、效果多强基本无关。真正决定架构高度的是你的业务流程到底有多少个必须保证不丢、不错、可恢复的环节。5.3 什么时候不必自研、什么时候必须自研要不要自研Harness Runtime这个问题我的答案其实很明确。如果业务场景简单、并发低、时间紧直接用成熟的开源Agent编排框架或云平台的托管服务就够了没必要自己写一套调度和持久化。很多团队在原型期就想着底层自研才显得有技术含量结果三个月后连版本迭代都顾不上那是最冤的。反过来如果你们有这几个需求多团队共用Agent能力、需要深度定制权限模型与审批流、要求私有化部署且数据不出内网、或者需要在极端场景下做秒级恢复那就值得自研。但自研也要克制LLM客户端、向量库、队列系统、日志系统这些基础设施能复用就复用内核部分只需要把调度、上下文、工具网关、策略、可观测这五件事做扎实。自研成本从来不在写代码的那几天而在之后半年里运维和迭代它的时间。6. 常见问题与排查技巧实录6.1 上下文膨胀与Token费用失控典型现象任务跑到后半段每次模型请求的Token数越来越大费用飙升响应越来越慢模型甚至开始忘掉最早的用户要求。原因几乎都是把历史消息、工具返回、检索文档一股脑塞进上下文。排查方法很简单看单次请求里历史占了多少Token。如果超过总量的一半且大部分是重复内容就该做上下文压缩了。我常用的手段滑动窗口只保留最近N轮做过时的历史做一轮摘要工具返回结果做字段裁剪检索文档只保留得分最高的Top-K段落。记住上下文管理不是保留一切而是只保留当前决策需要的。清理得越狠效果越好但别把关键事实也清掉比如用户确认过的订单号这种锚点信息要单独落到L2工作记忆里。6.2 工具乱调与权限越界模型传错了参数、调用了不该调的工具、甚至在被拒绝之后反复尝试同一个操作这些在生产环境里都发生过。这类问题大多不是模型坏而是工具描述有歧义、权限边界不清晰、参数校验不够严。我的排查流程是先翻决策快照看模型当时根据什么信息选择了那个工具再查工具描述看有没有这个工具只能用于A场景禁止用于B场景的明确约束最后检查工具网关的参数校验是不是只做了必填字段检查、没有做字段取值范围检查。长期方案是把敏感工具拆开独立授权、把参数schema写得像接口文档一样严格、给每个工具设置deny_reason回调模型被拒绝时能明确知道为什么。6.3 死循环与因果错乱Agent在某个步骤上反复调用同一个工具或者两个步骤之间互相矛盾是编排类系统最容易出的问题。死循环需要靠硬性限制兜底我已经在前面提到过max_steps和全局超时这两个必须默认开启且不可被业务绕过。更好的做法是加同参检测同一工具、同样参数连续执行超过N次直接熔断并触发人工告警。因果错乱的问题一般出在并发上多个任务共享某个内存对象或数据库记录一个任务把状态改了另一个任务还拿着旧数据继续算。解决办法是强制所有状态读写走run_id隔离任何工具调用都要基于快照数据而不是基于当时的全局值。这一步对复杂业务流程来说安全性怎么强调都不过分。6.4 超时重试与幂等设计工具调用超时的处理比想象中复杂。表面看重试只是再调一次但在真实业务里第二次调用可能产生完全不同的结果比如第一次其实已经创建了订单只是响应超时了重试又创建一个新订单就是重复下单。所以我把幂等设计列为写操作工具的第一要求。每次工具调用都带上idempotency_key由调度层生成格式通常是run_id加tool_call_id。工具实现方根据这个key去查重如果同样的key已经处理过直接返回上一次的结果。配合数据库唯一索引这套机制能挡住绝大多数重复执行事故。你如果刚起步无法改造所有老系统至少要把这个key传递到最关键的库存、支付、通知类操作上。6.5 排查速查表症状常见原因快速排查方向长期方案Token费用飙升历史消息无限塞入查单次请求的Token构成三层Memory设计窗口压缩模型反复调错工具工具描述歧义、权限过大看决策快照、检查工具Schema细化工具边界、独立授权任务卡住不动工具超时无兜底查waiting_tool状态是否超时工具网关统一超时重试失败恢复后重复执行缺少幂等控制查执行记录是否有重复事件幂等键数据库唯一约束多实例状态错乱状态只存内存查是否有共享状态存储状态落库按run_id隔离模型回答与事实矛盾检索内容与问题无关查检索Top-K内容和排序优化检索召回引用溯源7. 最后再分享一点我自己的体会做了这么多Agent项目我最大的感受是架构这层东西真的不需要一步登天。你可以从一次LLM调用开始跑通了再加工具工具多了再上循环循环不稳定再补状态和审计。每一步都是被真实业务需求推着走的而不是被别人都有Harness所以我也要上的心态逼出来的。但如果让我回到过去重新做一次有一件事我会提前做尽早把状态、幂等、审计当成Agent系统的一等公民来设计而不是等项目出了问题再补。重新设计永远比重构便宜尤其当你已经靠它扛着真实线上流量的时候。最后再说一个具体的小技巧任何一次模型调用都要能在事后用日志完整还原当时的prompt、输出和上下文摘要。这件事听起来琐碎但它是你在线上排查所有疑难杂症的最强底牌。把这个习惯从第一天就养起来未来你会感谢自己。
阅读完成 · 觉得有帮助?