test(contract): 0007 确认后关掉那三条 xfail
解释器不许抛异常是接缝自己的行为,改成正面断言(等实现接进来才跑)。另两条的答案 落在库这一侧不在接缝上:动作状态的触发条件是执行器自己的判断,套件面对任意实现逼 不出后两档,硬探会把 dissect 那种状态恒为 EXECUTED 的合法实现判成不合格;观察由谁 合成同理。两条留成不断言的说明,指向 tests/unit/test_session.py 里真正验它们的地方。 273 passed / 15 skipped / 4 xfailed。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,10 +3,10 @@
|
|||||||
两个已知形态差别很大:一个把一段代码交给已经开好的容器会话、状态恒为「已执行」,一个查
|
两个已知形态差别很大:一个把一段代码交给已经开好的容器会话、状态恒为「已执行」,一个查
|
||||||
工具注册表分发、工具不存在或参数不合法时返回「未执行」。下面每一条都要对两者同时成立。
|
工具注册表分发、工具不存在或参数不合法时返回「未执行」。下面每一条都要对两者同时成立。
|
||||||
|
|
||||||
## 写这份文件时撞出来的、`design/0006` 还答不上的问题
|
## 写这份文件时撞出来的两个问题,`design/0007` 决策一与决策二答了
|
||||||
|
|
||||||
1. 三个状态取值分别在什么条件下被赋上,从来没有正面写过。
|
三个状态取值各自在什么条件下被赋上、返回「未执行」时那段观察由谁给。两条的答案都落在
|
||||||
2. 返回「未执行」时,那段观察是执行器给的还是库合成的——两处都有来源,没说以谁为准。
|
**库这一侧**,所以它们的断言不在这份文件里,见文末那两条说明。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -70,32 +70,28 @@ async def test_cancellation_propagates_and_is_not_swallowed(action_executor, rec
|
|||||||
await task
|
await task
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(reason="design/0006 答不上,见 docstring", strict=True)
|
def test_status_trigger_conditions_are_asserted_against_the_library_not_here():
|
||||||
def test_status_values_have_defined_trigger_conditions():
|
"""三个状态的触发条件(`design/0007` 决策一)验不到这一层,原因在这里。
|
||||||
"""三个状态取值各自在什么条件下被赋上。
|
|
||||||
|
|
||||||
`design/0006` 只列了 `EXECUTED` / `NOT_EXECUTED` / `ENV_ERROR` 三个取值,没有正面写过
|
触发条件是**执行器自己的判断**:动作真的跑过了记 `EXECUTED`(哪怕它报错),没进执行
|
||||||
触发条件。现在只能从 `SyntheticObservations` 那两个字段名反推——工具不存在或参数不合法
|
记 `NOT_EXECUTED`,环境自己坏了记 `ENV_ERROR`。这套件面对的是一个任意实现,没有办法
|
||||||
大概是 `NOT_EXECUTED`,环境故障大概是 `ENV_ERROR`——而「大概」不能写成断言。
|
逼它进入后两档——拿一个「几乎不可能存在的工具名」去探,会把 dissect 那种动作语言里
|
||||||
|
根本没有工具名、状态恒为 `EXECUTED` 的合法实现判成不合格。
|
||||||
|
|
||||||
这条不定下来,两个下游会各自理解一套,而两套都不报错:dissect 的执行器状态恒为
|
库这一侧的连带后果是能验的,也验了:`ENV_ERROR` 必然导致 `StopReason.ENV_ERROR`、
|
||||||
`EXECUTED`,它撞不到这个分歧;GovDoc 撞得到,但表现是停止原因的分布变了,不是异常。
|
`NOT_EXECUTED` 不终止运行,两条在 `tests/unit/test_session.py` 里。
|
||||||
|
|
||||||
还有一处连带的:`ActionStatus.ENV_ERROR` 与 `StopReason.ENV_ERROR` 同名不同类型,前者
|
这条留成一个不断言的说明,是为了让下一个想在这儿补断言的人先看到上面那段。
|
||||||
出现是不是必然导致后者,也没写。
|
|
||||||
"""
|
"""
|
||||||
pytest.fail("三个 ActionStatus 取值的触发条件没有定义")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(reason="design/0006 答不上,见 docstring", strict=True)
|
|
||||||
def test_who_supplies_the_observation_when_the_action_is_rejected():
|
def test_who_supplies_the_observation_when_the_action_is_rejected():
|
||||||
"""动作被拒绝时,那段观察是执行器给的还是库合成的。
|
"""动作被拒绝时那段观察由库合成(`design/0007` 决策二),而判定发生在库这一侧。
|
||||||
|
|
||||||
两处都有来源:执行器的返回值里有 `observation` 字段,而定义上又挂着
|
执行器照常填自己的 `observation`——它不该知道库会不会采用,也不必知道:那段文本仍然
|
||||||
`SyntheticObservations.action_rejected`。`design/0006` 没说以谁为准。
|
随「一步走完」记录原样落盘,被拒绝那一档下它是日志里唯一的拒绝说明
|
||||||
|
(`design/0013` 决策六)。库只是不让它进历史,因为模型看得见的东西必须能进参数快照。
|
||||||
|
|
||||||
这不是风格问题。如果以库为准,执行器填的那段就被丢掉,而它可能带着「哪个参数不合法」
|
「库替换了它」是整次运行的行为,断言在 `tests/unit/test_session.py`,不在这个接缝的
|
||||||
这种只有执行器知道的信息;如果以执行器为准,那 `SyntheticObservations` 那个字段永远
|
契约里。
|
||||||
用不上,它就是个死字段。而 `observation_is_synthetic` 该填什么,取决于这个答案。
|
|
||||||
"""
|
"""
|
||||||
pytest.fail("动作被拒绝时观察的来源没有定义")
|
|
||||||
|
|||||||
@@ -3,13 +3,15 @@
|
|||||||
两个已知形态:一个从代码围栏里抽 Python 源码,一个从 JSON 里抽工具名与参数。库不带任何
|
两个已知形态:一个从代码围栏里抽 Python 源码,一个从 JSON 里抽工具名与参数。库不带任何
|
||||||
默认实现——带了就等于替某一家定了动作语言。
|
默认实现——带了就等于替某一家定了动作语言。
|
||||||
|
|
||||||
## 写这份文件时撞出来的、`design/0006` 还答不上的问题
|
## 写这份文件时撞出来的那个问题,`design/0007` 决策三答了
|
||||||
|
|
||||||
模型输出完全无法解释时,解释器是返回「无效决策」还是抛异常。
|
模型输出完全无法解释时,解释器返回「无效决策」,不抛异常。下面最后一条断言它。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from polyloop.ports import InvalidDecision
|
||||||
|
|
||||||
pytestmark = pytest.mark.contract
|
pytestmark = pytest.mark.contract
|
||||||
|
|
||||||
|
|
||||||
@@ -67,15 +69,16 @@ def test_action_carries_its_trace_form(decision_parser, samples):
|
|||||||
assert isinstance(parsed.decision.text, str)
|
assert isinstance(parsed.decision.text, str)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(reason="design/0006 答不上,见 docstring", strict=True)
|
def test_unparseable_output_returns_invalid_decision_rather_than_raising(decision_parser, samples):
|
||||||
def test_unparseable_output_returns_invalid_decision_rather_than_raising():
|
"""模型输出完全无法解释时返回「无效决策」,不抛异常(`design/0007` 决策三)。
|
||||||
"""模型输出完全无法解释时,解释器返回「无效决策」还是抛异常。
|
|
||||||
|
|
||||||
`design/0006` 定了三个分支——动作、最终回答、无效决策——但没说「解释器可以抛异常吗」。
|
|
||||||
两条路后果完全不同:返回无效决策,那一步照常留痕、说明文本回喂给模型、循环继续;抛
|
两条路后果完全不同:返回无效决策,那一步照常留痕、说明文本回喂给模型、循环继续;抛
|
||||||
异常,库要么把它翻译成某个停止原因终止整次运行,要么让它穿出去炸掉调用方。
|
异常,库要么把它翻译成某个停止原因终止整次运行,要么让它穿出去炸掉调用方。
|
||||||
|
|
||||||
dissect 的解析器不抛异常,所以它撞不到这个分歧。但契约测试是**任何新适配器的准入
|
dissect 的解析器不抛异常,所以它撞不到这个分歧。但契约测试是**任何新适配器的准入
|
||||||
标准**,一个会抛异常的实现照现在的契约既不算违规也不算合规。
|
标准**,所以这条要正面断言,不能靠「反正没人这么写」。
|
||||||
"""
|
"""
|
||||||
pytest.fail("解释器能不能抛异常、抛了怎么办,没有定义")
|
parsed = decision_parser.parse(samples.yields_invalid)
|
||||||
|
|
||||||
|
assert isinstance(parsed.decision, InvalidDecision)
|
||||||
|
assert parsed.decision.explanation != ""
|
||||||
|
|||||||
Reference in New Issue
Block a user