ADK-Python 捕获 StaleSessionError 后如何重读会话并重放 append_event 修复并发写冲突
2026/9/14 18:37:15 网站建设 项目流程

ADK-Python 捕获 StaleSessionError 后如何重读会话并重放 append_event 修复并发写冲突

【免费下载链接】adk-pythonAn open-source, code-first Python toolkit for building, evaluating, and deploying sophisticated AI agents with flexibility and control.项目地址: https://gitcode.com/GitHub_Trending/ad/adk-python

在 ADK-Python 中用DatabaseSessionServiceSqliteSessionServiceFirestoreSessionService持久化会话时,如果两个进程(或两个写者)持有同一个会话的副本并先后调用append_event,后写入的一方会收到StaleSessionError。这是 ADK 的乐观并发保护:你手里的内存Session已经落后于存储中的副本,如果不拦截,你的追加会静默覆盖另一个写者刚写入的历史。本文给出排查这个异常、修正错误捕获方式,以及按官方恢复路径重读会话、重放append_event的完整操作。

先确认这个异常什么时候会抛

StaleSessionError只由做了并发写检查的三种会话服务在append_event时抛出:DatabaseSessionServiceSqliteSessionServiceFirestoreSessionService。触发条件是你持有的内存Session在你读取之后、写入之前,已经被别人更新过。InMemorySessionService不做这项检查,文档也明确说明它不适合多线程生产使用,所以如果你在开发环境没看到这个异常,不代表生产环境不会有。

异常的完整定义见 异常说明,会话生命周期见 Session and BaseSessionService。

第一步:检查是否被宽泛的except ValueError吞掉了

这是排查StaleSessionError时最容易被忽略的一点。StaleSessionError为了向后兼容继承了ValueError,所以会话代码附近任何except ValueError都会把它静默吞掉。错误的写法看起来像严谨的错误处理,实际效果是把这一轮的对话历史直接扔掉:

try: await session_service.append_event(session, event) except ValueError: logger.warning("bad event, skipping")

用户侧的症状是对话缺了一整段,且日志里只有一行无关紧要的 warning。如果你正在排查"会话历史突然少了几条事件",先全局搜索except ValueError是否覆盖了append_event调用。

修正方式是捕获具体类型,并且把StaleSessionError子句放在任何ValueError子句之前(Python 取第一个匹配的except子句,宽泛的放前面会遮住下面所有窄的):

try: await session_service.append_event(session, event) except StaleSessionError: # 恢复逻辑见下一步 ... except ValueError: # 其他真正的参数错误 ...

如果一个except子句确实需要同时覆盖多个 ADK 异常,用元组显式列出,例如except (StaleSessionError, SessionNotFoundError)。ADK 的六个自定义异常没有公共基类,不存在能兜住所有 ADK 错误的单个except。导入方式只有包级入口:

from google.adk.errors import StaleSessionError

注意反向的坑:AlreadyExistsErrorNotFoundErrorToolExecutionError继承的是Exception而不是ValueErrorexcept ValueError对它们一无所获。

第二步:重读会话并对新副本重放 append_event

StaleSessionError是六个 ADK 异常中唯一有官方恢复路径的:用get_session重新读取会话,再对新副本重放本次append_event。完整写法:

try: await session_service.append_event(session, event) except StaleSessionError: session = await session_service.get_session( app_name=session.app_name, user_id=session.user_id, session_id=session.id, ) await session_service.append_event(session, event)

两个细节决定这段代码能否直接跑通:

  • 会话由(app_name, user_id, session_id)三元组唯一标识,get_session三个参数都必须传,不能只靠session_id
  • 重放前先用旧副本上的app_nameuser_idid重新读取,拿到的是包含其他写者最新写入的会话副本;对新副本追加时,存储层会再次做版本校验,通过后才落库。

如果你在写自定义后端(继承BaseSessionService),append_event里要先持久化事件,再调用await super().append_event(session, event)保持内存副本同步,否则自己也会制造 stale 会话。

第三步:验证写入没有再丢事件

修复是否生效,用一次get_session读回即可判断,检查两点:

  1. 被重放的event出现在session.events里;
  2. 该事件actions.state_delta中的键值已合并进session.state

仓库单元测试对同一场景的断言可以作为参照(见 测试文件):对一个 stale 会话直接append_event会抛StaleSessionError,消息匹配modified in storage;失败的追加不会落库,读回的会话events只包含已提交的事件,stale 事件携带的sk3键在state中不存在。执行你的恢复逻辑后,读回的会话应同时包含其他写者的事件和你重放的事件,两者都不缺。

错误消息、异常细节与文档间的口径

  • 异常消息来自服务实现中的常量(见 database_session_service.py):The session has been modified in storage since it was loaded. Please reload the session before appending more events.按这个文本可以精确定位是否命中了 stale 检查。
  • StaleSessionError没有定义自己的构造函数,也没有.message属性,str(StaleSessionError())是空字符串——不要依赖读异常上的默认文本来判断,直接用类型判断。
  • Session 指南在"Detecting a stale session"一节里说DatabaseSessionService"raisesValueError",与 异常文档的StaleSessionError不矛盾:后者就是前者的具体子类,两份文档指向同一个异常。
  • 六个 ADK 异常之间没有结构化错误码或 cause 字段,除ToolExecutionErrorerror_type外,消息字符串是全部信息。

修复完成后,如果你的会话由Runner驱动,日常代码通常只直接调用服务来创建、列举和删除会话,get_sessionappend_eventRunner代劳;上面这段手动恢复逻辑适用于你直接调用会话服务的场景,例如自定义服务封装或多进程共享同一会话的部署。

【免费下载链接】adk-pythonAn open-source, code-first Python toolkit for building, evaluating, and deploying sophisticated AI agents with flexibility and control.项目地址: https://gitcode.com/GitHub_Trending/ad/adk-python

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询