-
v1.0.2 Stable
released this
2026-08-27 17:30:11 +08:00 | 5 commits to main since this release回应下游项目在 PolyLoop 仓库上提的五个 issue。
这一版发出去之前跑了一轮完整压测,与 1.0.1 那次验收逐项可比:正常负载 197 次运行、1845 次
真实模型调用,十一条不变量全部通过、零击穿;九类故障注入 58 条判据全部通过、零击穿。库本身
没有被压出 bug。升级时唯一会变的行为在参数快照(见下面「注入内容的通道维度」那一节):用 1.0.1 跑了一半的
运行,升上来之后续跑会抛ParameterDriftError,错误信息里逐个列出漂移的键。失败是响亮的,
不会静默跑出两段来自不同配置的轨迹。契约套件随包发布
五个接缝的公共行为一致性用例从
tests/contract/搬进polyloop.testing,跟着 wheel 一起
装到下游去。pip install polyloop之后 site-packages 里没有tests/,所以在此之前那套
被称作「任何新适配器的准入标准」的用例,第一个下游根本拿不到。接法同时换了:原来是在自己的
conftest.py里覆盖同名 fixture,现在是继承契约基类。from polyloop.testing import RunStoreContract class TestMyPostgresStore(RunStoreContract): @pytest.fixture def store(self, pg_pool): return MyPostgresStore(pg_pool)换掉是因为覆盖同名 fixture 在装到 site-packages 之后无处落脚——pytest 的
conftest.py只沿着
被收集文件的目录链往上找,而套件所在的那条链在 site-packages 里,看不见下游仓库里的
conftest.py。继承则不需要任何conftest.py魔法:子类定义在下游自己的测试文件里。顺带一个
接缝可以接多个实现,各写一个子类。跑它要装
polyloop[testing](pytest 与 pytest-asyncio,版本只写下界,不和下游已经在用的
pytest 打架)。async 用例的事件循环归下游管:把asyncio_mode设成"auto",或者自己给子类
打标记。本包还声明了一个 pytest 插件入口,它唯一的作用是让 pytest 重写这些模块里的
assert,失败时打印出等号两边的实际值。基类名、基类上的 fixture 名、每一条用例的方法名从此是公共承诺,改名的代价和改公共类型的
字段一样。新增一个内存存储实现
polyloop.stores.VolatileRunStore:日志攒在进程内存里,进程一退就没了。它不提供的是跨进程
恢复,不是「读不回来」——同一个进程里写进去的意图照样读得回来,它和逐行追加那个实现跑的是
同一套存储契约。桶里存的是编码后的载荷、读的时候才解码,所以调用方后来改自己手里那个 dict
改不到已经写下去的快照,读回来的日志被就地改动也污染不了存储本身——落盘那个实现每次都重新
解析文件,天然如此,这个实现靠同一条路径对齐它。一条字段类型不对的记录在两个实现上的下场也
一样:写得进去,读的时候抛同一个解码错误。对下游的意义是测试和「我不要跨进程恢复」那一档不必再自己写一个存储:存储接缝是必填的,
在此之前不想落盘的人只能自己造一个。关系数据库那种形态仍然由下游自己实现。RunRequest新增fingerprints字段Mapping[str, str],默认空映射,已有代码不受影响。它记的是这次运行用的材料是哪一版——
提示词模板的哈希、技能库的版本这类——全部键值以request.fingerprint.<name>进参数快照,
不透传给模型调用。它和
model_binding的分界是「坐标还是配方版本」:绑定记这次运行属于哪一格(哪个账本、
第几轮、哪道题),指纹记这次用的材料是哪一版。一条指纹都没有时快照里一个键都不写,
所以今天已经在跑的配置算出来的快照逐字节不变。同时补上一道校验:
fingerprints与model_binding的键值必须都是字符串,在构造请求时就拒绝
非字符串;五个接缝parameters()上报的键值在聚合成快照时同样校验。在此之前这类值要等到续跑
读日志的那一刻才炸——那时这次运行已经跑完、钱已经花了。⚠ 参数快照里注入内容的键形状变了
这是这一批里唯一一处会让已有行为变化的改动。 注入的条目标识原来拍平成一个键
request.injected_entry_ids,现在按通道分组,一个通道一个键
request.injected_entry_ids.<通道名>。用旧版本跑了一半的运行,升级之后
resume会抛ParameterDriftError。 失败是响亮的,不是
静默的:错误信息把漂移的键逐个列出来,能看到旧的那个键消失、新的那几个键出现。处置有两条,
和快照里任何一项变了时一样——那次运行重新开始,或者接受它跑不完。跑完了的运行不受影响,
参数快照只在续跑时被比对。改形状是因为拍平之后「声明了这个通道但一条都没选中」和「压根没有这个通道」得出同一个结果,
而有下游要比较的正是这两种情形。分组之后前者是一个值为空串的键,后者是这个键不存在。通道
之间按通道名字典序,通道内的顺序和贴进提示词的顺序一致。快照的键集合从来不是公共承诺,所以这不是
CLAUDE.md§1.3 意义上的破坏性变更:下游换一个
存储实现、改一个接缝的parameters()返回什么,快照照样会变、续跑照样报漂移,这本来就是这
套设计的一部分。动作执行接缝的契约补上「抛异常时会怎样」
ActionExecutor的 docstring 原来只说了动作本身报错算「已执行」,实现方干脆不返回、直接抛出
时会怎样一个字都没写,而库这一侧不捕获。现在正面写清三条:环境自己坏了(连不上、协议不对、
会话没了)返回ActionStatus.ENV_ERROR,不要以异常表达;真抛出来的异常库不捕获,原样穿出
run()与resume();asyncio.CancelledError必须原样穿过。行为没有变,变的是它被写下来了。 库不把执行器抛出的异常转成
ENV_ERROR,是因为那样会
把适配器自己的 bug 伪装成环境故障送进下游的统计,而且要替这一步编一条步记录——那条记录会被
恢复读成「上一步走完了」,把一个真正未知的状态抹成一个具体的值。异常抛出时日志停在「动作
意图有、步记录无」,恢复照实把它读成状态未知。把可预期的环境异常(连接超时、会话已关闭)捕获并返回
ENV_ERROR是适配器的正常工作,不算
CLAUDE.md§1.7 禁的那种吞错误——错误没有被吞,它变成了一个明确的状态值。Downloads