退出一个托管智能体运行时

8 分钟读完

O15
Operation · AgentOps: Deploy & Operate

离开时,代码从来不是贵的那部分。

当一个托管智能体运行时对新客户关闭时,公告通常会承诺既有负载继续运行——而正是这个承诺让团队一等再等,因为真正的期限不是停服日,而是被冻结的模型目录不再提供你需要的某个模型的那一天。与此同时,你以为最难的那个循环一个迭代周期就搬完了,而你从没想过的对话状态才是那次迁移。先取状态;它是唯一一个在你反复斟酌期间还在长大的资产。

STEP 1

退出价在采纳那天就定好了,而今天仍然读得出来。

关于迁移的任何事,都不是在迁移期间决定的。它是在有人选择"循环住在你的仓库里还是厂商的配置格式里"、以及"对话存储是你拥有的数据库还是你调用的服务"时定下的。在规划任何事之前,先把自己当年的答案读一遍,因为它们决定了这是一个两周的项目还是两个季度的项目。

  • 循环表达在哪里。写在你自选框架里的循环,是一次重新部署。写在厂商声明式格式里的循环,是一次重写——而这次重写很小,这正是团队低估了其余部分的原因。
  • 原语能否比循环产品活得更久。如果记忆、网关与身份是可分别调用的服务,那么循环产品被冻结只让你损失那个循环。如果它们只能透过它够到,那这个产品的生命终点就是你的迁移日。这正是托管智能体运行时一文所围绕的那个分野。
  • 有没有人真的导出过对话存储。不是"有没有导出 API"——而是有没有活人在生产量级上跑过它、并看过跑出来的东西。答案通常是没有,而"文档上的能力"与"能用的能力"之间那道缝,正是迁移滑坡的地方。

如果你是在采纳一个运行时之前、而不是在离开一个运行时的路上读到这页,那么整页可以缩成两句话。对着框架写循环;并在第一天就证明状态导出可用——那时量还小,失败很便宜。这两件事在采纳阶段几乎免费,而且事后都补不回来。

STEP 2

找到真正的期限,因为没人会把它发给你。

维护模式听起来是个宽厚的状态:既有负载继续、什么都不坏、不宣布任何日期。这套说法藏起了期限,而不是取消了它。进入维护模式的服务不再新增功能,并把模型目录冻结在进入维护模式那天,这意味着你的到期日是以下几项里最先到的那一个:

  • 模型到期。你需要的某个模型成了被冻结目录永远不会提供的那一个的那天。对多数团队来说这是约束项,而且会在一年内到来,远早于任何停服。
  • 合规到期。某项你被要求具备的控制——一个新的数据驻留区域、一项保留能力、一份审计导出——以功能的形式落地,而它永远不会发给一个被冻结的产品的那天。
  • 依赖到期。被冻结服务所依赖的某个客户端 SDK、某个运行时版本或某种鉴权机制,在别处走到生命终点的那天。
  • 已宣布的停服。所有人都围着它做规划的那个,通常也是最后到的那个。

写下四者中最近的那个,并给它一个日期。没有日期的迁移不会被排人,而"他们说了既有负载继续运行"正是一次两周的取数在十八个月后变成一场紧急事件的路径。

STEP 3

清点运行时手里持有、而你手里没有的东西。

厂商边界内住着四类资产,它们失败的方式各不相同。在写任何代码之前,逐一列出,各配一个负责人与一个规模。

  • 对话状态——线程、会话、抽取出来的记忆。每天在长,带厂商特有结构,也是唯一一个在你做规划期间迁移成本还在上升的资产。现在就量:多少线程、多少事件、多少字节,以及各自长得多快。
  • 身份绑定——运行时代表智能体所代理的凭据,以及授予它们的每一个下游作用域。工作量有界,但它触及别的团队的系统,因而也触及别的团队的日程。
  • 轨迹历史——智能体做过什么的记录。常常是那个没人认领负责人的资产,也常常是那个背着法定保留义务、而这份义务并不在乎你换了厂商的资产。
  • 工具与网关配置——如果网关讲一个标准协议,这是四者中最不痛的;如果不讲,那是真的痛。

最常被漏掉的是轨迹历史。团队迁走了状态与身份、切换、下线——然后才发现自己正打算搭的那个评测集原本活在轨迹里,或者某位审计师要看某个季度的运行,而那个季度如今只存在于一个已弃用的控制台里。请明确决定:轨迹历史是跟着走、冷归档,还是放弃。这三种都站得住;站不住的是"稀里糊涂"。

STEP 4

先取状态——"先搬代码"的直觉是错的。

每个团队都从重写循环开始,因为那是工程师爱做、也演示得出来的部分。它同时也是成本固定且很小的部分。状态恰好相反:它的成本是流逝时间的函数,而你花在循环上的每一周都让它更大。把顺序倒过来。

# Wrong: cost grows while you work on the fixed-cost part.
port_loop()            # 2 weeks, fixed
rebind_identity()      # 1 week, fixed
export_state()         # grew for 3 weeks while you did the above

# Right: freeze the growing asset first, then do the fixed-cost work.
prove_export()         # on a sample, day one — this is the go/no-go
start_dual_write()     # new state lands in BOTH stores from here on
port_loop()            # now the backlog is bounded, not growing
backfill_state()       # one-time, against a fixed set

在一份样本上证明导出可用,是一个决策点,不是一项任务。如果导出丢掉了让那些记忆变得有用的结构——时间戳、到会话的归属、厂商抽取层推断出的一切——那你已经学到:状态挺不过这次搬迁,而诚实的方案是让新部署冷启动、同时让旧部署一直跑到它的会话自然老化。这是一个正当的结局,而在第一周发现它,远好过在第四个月发现它。

双写是把移动靶变成固定靶的那件事。从新状态同时落进两个存储的那一刻起,回填就成了一个针对停止增长的集合的有界任务,而此后的每一个决定都会变得更容易。

STEP 5

在会话边界上切换,绝不在对话中途切。

智能体迁移不是一次无状态发布,所以通常的灰度形状要改一处:你迁移的单位是会话,而进行中的会话必须在它开始的地方结束。前十轮用一套记忆模型、后十轮用另一套的对话,是一个以"智能体变笨了"的形式表现出来的 bug,而且从一条横跨两套系统的轨迹里几乎无法诊断。

  • 按会话路由,并在其生命周期内钉住。新会话去新运行时;既有会话在旧的上排空。
  • 一次迁一个任务类,从失败最便宜、状态最薄的那个开始——而不是从旗舰工作流开始。
  • 按结果比对,而不是按延迟。状态迁移的失败模式是答案微妙地变差,而这一点没有任何基础设施指标会显示。把同一个任务类同时跑过两边,然后 diff 结果。
  • 保留旧运行时的只读可达性,直到 STEP 3 里那个轨迹历史的问题有了一个签过字的答案。

身份的重新绑定通常才是这里的日程决定项,因为它触及别的团队拥有的系统。请在做导出验证的同时就把那些沟通启动,而不是等循环搬完之后——它们是最长的那根杆子,而且压不短。

STEP 6

什么时候留下才是对的答案。

不是每一个被冻结的运行时都需要撤离。一个跑在稳定任务类上、其质量不依赖冻结之后发布的模型、其合规姿态已经满足、其状态小到可以放弃的部署,完全有理由在维护模式的服务上一直跑到宣布停服为止。留下的成本有上限、也已知;迁移的成本是一个季度的工程时间,而这些时间本可以花在别处。

站不住的是"默认留下"。留下的决定应当被写下来,旁边附上 STEP 2 里那四个到期日,在其中任何一个变动时复审,并由一个具名的人负责。失败模式不是选择留下——而是从来没有做过选择,最后通过一次事故、而不是通过一份计划,得知了期限。

无论你迁不迁,这周都做三件事。量一量对话存储——线程数、事件数、字节数、增长率——好让这次迁移有一个数字而不是一个耸肩。在一千个会话上跑一遍导出,看看回来的是什么。再给四个到期日里最近的那个定一个日期。这三件事花一天,却把一个开放式的风险变成了一个排上日程的决定;这一页上其余的一切,都在做完它们之后。

相关:模型弃用与迁移——低一层的同一个问题;持久状态与可恢复性——运行时当初在解决什么;以及自建、购买还是编排——先于这一切的那个决定。