生成式软件工程
搜索

常驻助手与自动化

一次性对话结束就结束;长期在线的助手结束时,事情可能正做到一半。

这一章讲什么
  • 一次性对话与长期在线的助手,多出哪些必须处理的问题;
  • 状态保存、任务排队、失败重试、费用控制这四件事怎么做;
  • 定时任务与自我唤醒;为什么不能依赖某个一直开着的窗口;
  • 记忆该分层,以及长任务可恢复的三个前提。

一、从一次问答到长期在线

用一次对话解决问题,状态很简单:你问,它答,关闭窗口,一切结束。出错了重新问一遍就行。

当助手开始长期为你工作时,这个模型就不够用了。它可能同时在处理三件事,其中一件在等外部系统回应, 另一件刚失败需要重试,第三件需要你确认才能继续。你关掉网页,它们不该全部消失。

这些要求彼此交叉,不能靠每个按钮各写一份状态来解决。把它们归成四类:

问题具体表现
状态关掉窗口后任务是否继续?重启之后怎样恢复?两个设备看到的是不是同一个进度?
排队同时来了五个任务,按什么顺序做?某个任务卡住了,其他任务是否要等?
重试失败几次算失败?哪些错误值得重试?重试会不会重复执行已经完成的部分?
费用一次任务花多少?到达上限时应该停下、降级,还是向你请示?
生活类比 一次性对话像去食堂打饭,打完就走。常驻助手像请了一个长期帮你跑腿的人:他有自己的待办清单, 会和别人同时办事,会在路上遇到堵车,会需要你签字才能继续,还会花你的钱。你需要管的是「他现在做到哪一步了」,「他有没有回答」已经不重要。

二、状态:任务有阶段,不只是一句话

一个看起来简单的需求,背后已经有一组状态需要说清楚。以「把本周项目记录整理成周报」为例:

执行过程中你还会改变主意,而每一种改变都对应一组状态迁移:

你说的话正确的行为
「补充这个文件」把新材料加入当前上下文,不重启任务
「暂停一下」保留进度,停止发起新步骤,已经完成的修改不回退
「不要修改原件」切换为只读模式,后续写入改为副本
「先把已经完成的部分给我」导出当前中间产物,任务继续在后台进行
「取消」停止任务,已经产生的外部动作不会自动撤销,需要单独处理

「停止」这一个按钮背后,就有一组需要讲清楚的状态迁移。这也是为什么长期在线的助手不能靠「每个功能各写一套状态」来实现。

三、排队与并发:哪些能并行,哪些必须排队

同时来多个任务时,先要分类:

排队机制本身也要能表达优先级与插队规则。紧急的任务应该能插到前面,但插队不能让已经执行一半的任务停在半途而废的状态。

关键结论 并行的前提是隔离,不是数量。同一个工作区上开两个任务,不会因为「有两个」而变快, 只会因为互相覆盖而变慢。能安全并行的标志是:它们不需要共享可写的状态。
flowchart LR
  A["任务失败"] --> B["判断失败类型"]
  B -->|"暂时性"| C["有限次重试
带幂等标识"] B -->|"确定性"| D["不重试
报告给用户"] B -->|"结果未知"| E["先查实际状态"] E -->|"已完成"| F["直接继续下一步"] E -->|"未完成"| C C --> G["达到预算上限"] G --> H["停止并交回
已完成部分与原因"]

四、重试:区分三种失败

第 15 章讲过失败分三类,这一章把它落到长期运行的场景里。重试策略应该按类型区分:

失败类型处理要注意
暂时性(超时、限流)有限次重试,间隔递增要带上幂等标识,避免重复执行
确定性(权限、输入错误)不重试,报告给用户原样重试只会浪费时间与费用
结果未知先查实际状态,再决定这是最容易重复执行的一类

还要设一个总预算:连续失败达到次数上限就停止,并把失败原因与已完成的部分一起交给你。 无限重试是最坏的设计,它会在你不知情的情况下持续消耗费用。

五、费用:预算不是界面上的一个数字

长期在线的助手会持续消耗资源。预算要管住三件事:

  1. 计量:模型请求、工具调用、执行时间都要计量,不能只算其中一项。
  2. 预警:接近上限时提前告知,让你有机会调整。
  3. 到达上限后的明确行为:是停下、降级到更省的模式,还是向你请示?必须事先定义,不能到时候各模块自行其事。

权限也是同理。读取一个目录、修改其中的文件、把内容发给外部服务,是不同的动作,需要不同的授权。 仅仅在提示词里写「请注意权限」,不等于实现了一条可靠的边界。

常见坑 网页或文档里出现的命令,只是待处理的资料,不能自行变成新的用户授权。 否则一个恶意网页就能让你的助手替它执行命令。这条边界必须在程序层面实现,不能只写在提示词里。

六、定时任务与自我唤醒

常驻之后很自然的需求是「每天早八点整理一次昨日总结」「每小时检查一次有没有新通知」。这类需求靠两样东西实现:

两者都要考虑重复触发与错过触发。错过触发怎么补:一次补完、只补最近一次,还是直接跳过?不同业务要求不同,必须明确。 重复触发怎么办:用幂等标识与去重表。

七、不要依赖某个一直开着的窗口

早期做自动化最常见的设计错误,是把整个系统挂在一个始终打开的窗口上:必须有人一直开着某个页面, 定时任务才会跑。这个设计有三个致命问题:

正确做法是把执行放在服务端或一个独立的后台进程里,界面只是查看与操作的入口。 这叫瘦客户端、胖服务端:界面轻,逻辑重,状态存在能被多家设备访问的地方。

八、记忆:什么记成信息,什么记成命令

长期助手需要记忆,但记忆不是把所有对话都存下来。至少分三层:

层存什么例子
事实层稳定的事实与偏好「我的课表在每周三下午没有课」
规则层你纠正后形成的做法「整理通知时,不要把报名截止时间当成活动开始时间」
过程层当前任务的进度与中间结果「周报已生成初稿,等待核对数字」

三层混在一起会出问题。事实与规则会被误当成当前的指令;过程记录会被误当成长期知识。 一个实用的原则是:记忆是信息,不是命令。从资料里读到的一句话,不能自动变成对助手的授权。

九、长任务可恢复的三个前提

「接着做」这件事要成立,需要三样东西同时具备:

  1. 知道做到哪了:进度是可查询的,不是靠回忆。这通常意味着每一步都有记录。
  2. 知道有没有副作用:哪些动作已经对外界发生了,重做会出问题。这需要幂等标识与执行记录。
  3. 知道外部状态有没有变:恢复时外部文件、数据可能已经被人改过,需要实际检查,不能凭假设。

三样缺一样,恢复就可能变成二次破坏。这也是为什么很多「支持恢复」的功能实际上只在理想情况下有效: 它只做了第一条。

关键结论 长期运行的可靠性,来自「每一步都可查」与「每个动作都可重入」, 不来自「我们尽量不出错」。前者是设计,后者是愿望。

十、一台普通的电脑也能开始

这套东西听起来需要服务器,其实起步阶段用一台常开的旧笔记本或一台小主机就够。 关键是三件事:任务不在你的工作窗口里跑、状态存在能被多处访问的地方、失败能被你看见。

如果连一台常开的机器都没有,可以先做「半自动」:任务由你手动触发,但状态与记录完整保存, 下次触发时能接着上次的进度。这已经能解决大部分「重复劳动」的问题,也比一上来就搭一套复杂的调度系统更容易成功。

现在就做 选一件你每天或每周都要做的重复小事,设计它的自动化方案,只写不实现:
① 触发方式(定时、手动、事件);
② 状态存在哪里(文件、数据库、还是某个服务);
③ 哪一步可能重复执行,用什么标识去重;
④ 失败时你怎么知道,怎么接着做。
四行写完,你已经把「自动化」从想法变成可实施的设计。

自测

1. 为什么常驻助手不能靠「每个按钮各写一套状态」?
因为状态要求彼此交叉:关窗口是否继续、重启怎样恢复、多设备是否同一进度、暂停与取消的语义,这些属于同一份状态的不同侧面。各写一套会出现互相矛盾的副本,且难以统一恢复。
2. 并行的前提是什么?
隔离。两个任务能安全并行的标志是它们不需要共享可写的状态。同一个工作区上开两个任务只会互相覆盖,不会更快。
3. 无限重试为什么是最坏的设计?
它会在用户不知情的情况下持续消耗费用,并且可能反复执行有副作用的外部动作。正确做法是区分失败类型、设总预算、连续失败达上限就停止并报告,同时把已完成的部分交回。
4. 为什么说「记忆是信息,不是命令」?
从资料或历史中读到的内容只是待处理的信息,不能自动变成授权或指令。否则一个恶意文档、一封邮件里的文字就能让你的助手执行动作。授权必须来自用户本人,并且在程序层面实现。
5. 长任务可恢复需要哪三个前提?
知道做到哪了(进度可查)、知道有没有副作用(幂等标识与执行记录)、知道外部状态有没有变(恢复前实际检查,不能凭假设)。只做第一条的「支持恢复」在真实场景里常常变成二次破坏。