首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Agent 框架设计:循环、上下文与故障恢复

Agent 框架设计:循环、上下文与故障恢复

原创
作者头像
用户12777917注液學讠果
发布于 2026-11-13 15:42:35
发布于 2026-11-13 15:42:35
400
举报

一、先破题:框架真正要解决的问题是什么

市面上多数 Agent 框架的宣传点是"编排"——把节点连成图、把多个 Agent 串起来、拖拽出工作流。但对真正要上线的人来说,这些不是难点。难点在于:一个不可靠的模型,被放进一个可能失败的循环里,如何让它稳定地、可恢复地、成本可控地跑完。

把这个问题拆开,Agent 框架的核心职责只有三块:

职责

本质问题

常见误判

循环控制

什么时候继续、什么时候停、什么时候该问人

以为这是编排问题

上下文管理

有限窗口里放什么、丢什么、怎么压缩

最被忽视,也是效果差距的主因

工具运行时

schema、执行、错误语义、权限、幂等

以为只是注册函数

一个框架如果只做好了编排而没做好这三块,做出来的东西就是"能演示、不能上线"。下面逐块展开,并在关键处给出设计取舍和少量代码。


二、抽象边界:薄框架与厚框架的取舍

这是设计 Agent 框架的第一个、也是最根本的决策。

厚框架(图 DSL、节点、边、状态对象、检查点原语、多 Agent 原语)的好处是开箱即用,坏处是抽象泄漏时你无处可逃。Agent 的控制流本质上是"模型输出决定的动态分支",把它硬塞进静态图,一旦模型行为超出图能表达的范围,你就要开始绕框架的弯。调试时你会对着框架内部的 trace 猜测"到底哪一步出了问题"。

薄框架(只提供循环骨架、上下文管理、工具运行时,控制流由调用方用普通代码写)的代价是自己写更多胶水,好处是所有控制流都是你读得懂的代码,出问题直接断点调试。

专业建议是:默认选薄的,只在确证需要时加厚。 具体地说,框架应该提供的是"可组合的原语",而不是"规定了写法的框架":

  • 一个 run_loop(model, tools, state, limits) 的执行器
  • 一套上下文构建/压缩策略接口
  • 一个工具注册与执行运行时(含错误语义)
  • 一个可插拔的持久化后端

把"流程长什么样"交给调用方。这样做的另一个好处是:工作流和 Agent 可以用同一套原语表达——确定性的步骤直接写顺序代码,不确定的地方调用循环原语。生产系统绝大多数是这种混合形态,而不是纯 Agent。


三、执行模型:循环、状态机、图,怎么选

三种执行模型对应三种任务确定性:

模型

适用

优点

代价

自由循环

路径不可预知(研究、排障、开放式任务)

表达力最强

不可预测、易死循环、难评估

状态机

阶段明确、可枚举(审批流、多轮表单)

可验证、可测试、可恢复

表达力受限

图/工作流

步骤确定、依赖明确(ETL、编排)

可观测、可并行

不支持动态分支

生产实践里最常见的错误是用自由循环去做本该状态机化的任务。 一个"先收集信息、再校验、再提交"的流程,如果写成 Agent 自由发挥,你会得到:偶尔跳过校验、偶尔重复提交、无法保证顺序。这类任务应该显式建模为状态,循环只用于单个状态内部的决策。

反过来,把开放式排障任务塞进状态机,就会不断遇到"这个分支图里没有"的情况。

判断标准很简单:问一句"如果模型这一步做错了,业务上能不能接受?" 不能接受的部分必须有显式结构,能接受的部分才交给循环。


四、上下文:真正的战场

Agent 效果的上限,一大半由上下文管理决定。而多数框架对这一层几乎没有设计,只是把 messages 列表一直往后 append。结果是:跑到第 15 步上下文爆了,或者关键信息被埋在几万 token 之前。

上下文管理需要三个明确的机制:

1. 预算分配。 给每一类内容划出 token 预算:系统提示、工具定义、历史、检索结果、当前观察。总预算要留出输出空间。没有预算意识,等于把窗口当成无限。

2. 分层压缩。 不是"超了就截断最后 N 条",那会丢掉最早的任务定义。合理的顺序是:

  • 旧工具结果 → 摘要或只保留关键字段
  • 已完成子任务的过程 → 折叠成一句结论
  • 历史对话 → 阶段性总结替换原文
  • 任务定义、当前目标、关键约束 → 永不压缩

3. 外部化。 大对象不放上下文,放外部存储,上下文里只留引用和摘要,需要时再取。这是子 Agent 隔离和长任务能跑下去的底层手法。

一段最小但完整的上下文构建示意:

代码语言:javascript
复制
def build_context(state, budget_tokens=120_000):
    # 1) 不可压缩的核心:任务定义与约束永远保留
    head = [state.system_prompt, state.task_definition]

    # 2) 近期消息原样保留(最近几轮信息密度最高)
    recent = state.messages[-state.keep_recent:]

    # 3) 更早的历史折叠为摘要
    older = state.messages[:-state.keep_recent]
    summary = state.running_summary  # 增量维护,而非每次重算
    if older:
        summary = summarize_incremental(older, prev=summary)

    # 4) 工具结果按类型裁剪:大结果只留引用
    for m in recent:
        if m.role == "tool" and len(m.content) > 2000:
            ref = state.store.put(m.content)          # 外部化
            m.content = f"[已存为 {ref},摘要: {head_of(m.content)}]"

    ctx = head + ([{"role": "system", "content": summary}] if summary else []) + recent
    return enforce_budget(ctx, budget_tokens)         # 按优先级裁剪,触底则中止并上报

要点在最后一行:预算兜底时要显式中止或降级,而不是静默截断。静默截断导致的"模型突然忘了任务",是最难排查的一类问题。


五、工具层:契约比实现重要

工具设计最常见的三个错误:

1. 描述写给人类看。 工具的 description 和参数说明是给模型看的提示词,不是 API 文档。要说清"什么时候该用""什么时候不该用""参数含义和常见取值",而不是复述函数签名。

2. 错误抛异常。 工具失败(参数错、超时、权限不足、下游 500)必须作为结果回填给模型,并附带可操作的错误信息,让模型自己决定重试、换参数还是换路径。直接抛异常会终止整个循环,模型失去了纠错机会。

3. 没有幂等和权限。 模型会重试,会重复调用。所有有副作用的工具(下单、发邮件、写库)必须支持幂等键;所有危险工具必须走独立的权限校验,不能依赖提示词约束。

代码语言:javascript
复制
def execute_tool(call, registry, ctx) -> dict:
    spec = registry.get(call.name)
    if spec is None:
        return {"ok": False, "error": f"未知工具 {call.name}", "hint": "可用工具见工具列表"}

    # 权限与前置条件:框架层强制,不交给模型判断
    if not ctx.policy.allow(call.name, call.arguments):
        return {"ok": False, "error": "无权限执行该操作", "hint": "需要用户确认或提升权限"}

    try:
        args = spec.validate(call.arguments)          # schema 校验在框架侧做
        result = spec.fn(**args, _idem_key=call.id)   # 幂等键由框架统一注入
        return {"ok": True, "data": spec.truncate(result)}
    except SpecError as e:
        return {"ok": False, "error": str(e), "hint": "参数不符合要求,请参考 schema 修正"}
    except TransientError as e:
        return {"ok": False, "error": str(e), "retryable": True}
    except Exception as e:
        log.exception("tool crashed")
        return {"ok": False, "error": "内部错误", "retryable": False}

所有错误都以结构化结果返回,循环永不因工具异常中断。 这一条决定了 Agent 的鲁棒性下限。


六、可靠性:持久化执行是分水岭

Demo 级 Agent 和上线级 Agent 的差别,几乎全在这一节。

一个跑 30 步、涉及 15 次工具调用、耗时几分钟的 Agent,中间可能遇到:进程重启、超时、下游限流、模型返回异常。如果状态只在内存里,任何一次故障都意味着从头重来——重复的副作用、重复的花费、不可接受的延迟。

解法是持久化执行(durable execution):把循环的每一步作为一个可重放的事件记录,每次状态变更后落 checkpoint,恢复时从最后一个 checkpoint 重放,已完成的步骤直接跳过。

代码语言:javascript
复制
def run_durable(agent_id, state_store, executor):
    state = state_store.load(agent_id) or executor.init_state()

    while not state.done and state.step < state.max_steps:
        # 已完成的步骤重放时直接跳过(幂等保证副作用不重复)
        if state.step in state.completed_steps:
            state.step += 1
            continue

        action = executor.decide(state)              # 模型决策
        state.begin_step(state.step, action)
        state_store.save(agent_id, state)            # 先记意图

        result = executor.act(action, state)         # 执行(含幂等键)
        state.finish_step(state.step, result)
        state_store.save(agent_id, state)            # 再记结果

        state.step += 1

    return state_store.finalize(agent_id, state)

关键设计点:

  • 先写意图再执行(write-ahead),崩溃后能判断"这一步到底做没做"
  • 副作用带幂等键,重放不会重复下单/发信
  • 最大步数与最大成本双上限,必须硬性截断,不能靠提示词让模型"注意别跑太久"
  • 可中断、可恢复、可人工接管:长任务必须支持"暂停等人工确认"作为一个正式状态,而不是靠轮询

这一层做不做,决定了 Agent 是玩具还是系统。


七、多 Agent:本质是上下文隔离,不是能力增强

多 Agent 最容易走进的误区,是以为"拆成多个 Agent 就更聪明"。实际上,多 Agent 唯一可靠的收益是上下文隔离——每个子 Agent 有自己的窗口,只返回摘要给主 Agent,避免主上下文被过程细节撑爆。

它能做到的:并行探索多条路径、隔离不同领域的工具集与提示词、把长过程压缩成结论。

它做不到的:真正提升单步推理质量。把任务拆给两个 Agent,不会让任何一个变得更聪明;如果拆分引入了信息损失(子 Agent 看不到主 Agent 的某些约束),整体还会变差。

所以设计原则是:

  • 默认单 Agent + 好工具 + 好上下文管理,先把这个做到极限
  • 只在上下文确实装不下、或需要并行探索时引入子 Agent
  • 子 Agent 的返回契约必须严格:只返回结构化结论 + 引用,不返回原始过程
  • 子 Agent 失败要能被主 Agent 感知并降级,而不是静默丢失

八、可观测性与评估

Agent 的调试难度远高于普通程序,因为失败往往是概率性的、多步累积的。可观测性需要覆盖到决策依据,而不只是调用日志:

  • 每一步的完整上下文快照(或可重建的引用)
  • 模型输出与工具调用的对应关系
  • 每步的 token 消耗与累计成本
  • 中止原因(步数上限、预算耗尽、权限拒绝、人工中断)必须显式区分

评估上,要区分过程指标和结果指标:工具调用正确率、无效循环次数、上下文压缩是否丢信息,属于过程;任务完成率、人工接管率、单任务成本,属于结果。只看结果无法定位问题,只看过程无法判断价值。

一个实用做法是把每次线上失败样例固化成回归用例,Agent 的评估集是长出来的,不是一开始就设计好的。


九、设计清单

给正在做或准备做 Agent 框架的人:

  1. 三块核心职责优先:循环控制、上下文管理、工具运行时。编排是次要的
  2. 默认薄框架:提供原语,不规定写法;控制流用普通代码写
  3. 按确定性选执行模型:不能容忍出错的部分显式结构化,其余才交给循环
  4. 上下文设预算:分层压缩,核心约束永不压缩,兜底时显式中止而非静默截断
  5. 工具错误结构化回填:循环永不因工具异常中断;幂等键由框架统一注入
  6. 权限在框架层强制:不依赖提示词约束危险操作
  7. 持久化执行:先写意图再执行、可重放、双上限(步数 + 成本)、支持人工接管
  8. 多 Agent 只用于上下文隔离,返回契约严格,失败可降级
  9. 可观测到决策依据,失败样例沉淀为回归集

Agent 框架的专业性,不体现在支持多少种编排方式,而体现在它是否认真对待了"模型不可靠、循环会失败、上下文会溢出、进程会重启"这四个现实。把这四件事处理好的框架,哪怕只有几百行,也比功能齐全但回避它们的框架更值得用在生产里。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、先破题:框架真正要解决的问题是什么
  • 二、抽象边界:薄框架与厚框架的取舍
  • 三、执行模型:循环、状态机、图,怎么选
  • 四、上下文:真正的战场
  • 五、工具层:契约比实现重要
  • 六、可靠性:持久化执行是分水岭
  • 七、多 Agent:本质是上下文隔离,不是能力增强
  • 八、可观测性与评估
  • 九、设计清单
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档