fix(chain): checkpointer 连接串剥离 SQLAlchemy 驱动后缀

多章链 job 一发起即 failed(exc_type=ProgrammingError)。根因:runtime
checkpointer 工厂把 settings 的 sync URL `postgresql+psycopg://…` 原样喂给
`AsyncPostgresSaver.from_conn_string`,psycopg 不识别 `+psycopg` 方言段 →
`ProgrammingError: missing "=" …`,链在打开 saver 时即崩。

E2E 用 MemorySaver 覆盖 get_checkpointer_factory,从不走真 conn string,故此
路径无回归保护、bug 未被测出(迁移 d3e4f5a6b7c8 里 `_psycopg_conn_string` 已
正确剥离,runtime 缝漏了同款处理)。

修复:chain_deps 加 `_libpq_conn_string()`,剥离 `+psycopg`/`+asyncpg` 还原为
标准 libpq 串后再交 from_conn_string。+ 单测 test_chain_deps.py(3 参数化用例)。
已直连真 pg 复验:saver.aget OK,ProgrammingError 消除。
This commit is contained in:
Yaojia Wang
2026-06-24 12:40:40 +02:00
parent 9d94957a9b
commit b5002a9864
2 changed files with 57 additions and 2 deletions

View File

@@ -28,6 +28,18 @@ if TYPE_CHECKING:
CheckpointerFactory = Callable[[], AbstractAsyncContextManager[Any]]
def _libpq_conn_string() -> str:
"""从 settings 的 sync URL 派生 psycopg 的 libpq conn string。
`database_url_sync` 带 SQLAlchemy 驱动后缀(``postgresql+psycopg://…``),但
`AsyncPostgresSaver.from_conn_string` 直接交给 psycopg 解析——psycopg 不识别
``+psycopg``/``+asyncpg`` 方言段,会抛 ``ProgrammingError: missing "=" …``。
故剥离后缀还原为标准 libpq 串(``postgresql://…``)。与迁移 `d3e4f5a6b7c8`
的 `_psycopg_conn_string()` 同义。
"""
return get_settings().database_url_sync.replace("+psycopg", "").replace("+asyncpg", "")
def get_checkpointer_factory() -> CheckpointerFactory:
"""运行时 checkpointer 工厂:建连 `DATABASE_URL` 的 `AsyncPostgresSaver` 上下文。
@@ -38,7 +50,8 @@ def get_checkpointer_factory() -> CheckpointerFactory:
def _factory() -> AbstractAsyncContextManager[BaseCheckpointSaver[Any]]:
from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
# langgraph 的 Postgres saver 走 psycopg(同步驱动串)用 sync 串建连接。
return AsyncPostgresSaver.from_conn_string(get_settings().database_url_sync)
# langgraph 的 Postgres saver 走 psycopg(同步驱动串)须剥离 SQLAlchemy 驱动
# 后缀(+psycopg否则 psycopg 解析连接串报 ProgrammingError。
return AsyncPostgresSaver.from_conn_string(_libpq_conn_string())
return _factory