fix: address completion verdict review findings
- fidelity cacheability also requires a non-empty projected body, so think-tag-only answers are no longer cached - a no-argument tool sent with arguments null only is a deterministic dialect: ResultInvalid tool_arguments_null instead of retried - text mode treats choice index null as 0 and no longer lets False pose as 0 - null-only tool_calls elements no longer produce an empty tool_calls list; non-stream fidelity checks role before the verdict; warning paths are length-bounded - docs: ARCHITECTURE 7.8 field counts, README cache/watchdog wording, CHANGELOG notes, review record in the design, hook note in the plan Red: 8 new tests failed before this change (e.g. test_openai_cacheable_requires_projected_body, test_null_arguments_tool_is_result_invalid_stream, test_text_mode_choice_index_null_is_zero[None-True]).
This commit is contained in:
@@ -85,7 +85,7 @@ async def test_eof_after_finish_usage_only_frame_and_tools():
|
||||
result = await _run(frames, done=False, mode="openai")
|
||||
tool = result.completion.message["tool_calls"][0]
|
||||
assert tool["function"] == {"name": "lookup", "arguments": '{"q": 1}'}
|
||||
assert result.stream_end == "eof_after_finish"
|
||||
assert result.stream_end == "eof_after_finish" and result.usage_source == "measured"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -416,3 +416,38 @@ async def test_openai_reasoning_length_single_attempt_via_client():
|
||||
await client._transport.aclose()
|
||||
assert len(calls) == 1 and response.call_stats.attempts == 1
|
||||
assert response.stream_end == "eof_after_finish" and response.finish_reason == "length"
|
||||
|
||||
|
||||
async def test_eof_after_finish_stop_single_attempt_via_client():
|
||||
"""#31 验收 1 经治理链: stop + usage 无 DONE,attempts=1、不重试到耗尽。"""
|
||||
from tests.unit.test_client import _client
|
||||
|
||||
calls = []
|
||||
|
||||
def handler(_):
|
||||
calls.append(1)
|
||||
return _sse_stream(_assistant(content="ok"), _f({}, "stop", _USAGE), done=False)
|
||||
|
||||
client = _client(handler=handler)
|
||||
try:
|
||||
response = await client.chat([{"role": "user", "content": "hi"}])
|
||||
finally:
|
||||
await client._transport.aclose()
|
||||
assert len(calls) == 1 and response.call_stats.attempts == 1
|
||||
assert response.stream_end == "eof_after_finish" and response.usage_source == "measured"
|
||||
|
||||
|
||||
async def test_text_salvage_with_length_partial_content_delivered():
|
||||
"""文本 salvage + length + 部分正文(finish 后无 usage): 交付、salvaged。"""
|
||||
frames = [_assistant(content="part"), _f({}, finish="length")]
|
||||
result = await _run(frames, done=False, missing_done="salvage")
|
||||
assert result.content == "part" and result.stream_end == "salvaged"
|
||||
assert result.finish_reason == "length"
|
||||
|
||||
|
||||
async def test_null_arguments_tool_is_result_invalid_stream():
|
||||
"""审查 I2: 无参函数 arguments 全程 null → ResultInvalid,不重试。"""
|
||||
tool = {"index": 0, "id": "c", "type": "function", "function": {"name": "f", "arguments": None}}
|
||||
frames = [_assistant(tool_calls=[tool]), _f({}, "tool_calls", _USAGE)]
|
||||
with pytest.raises(ResultInvalidError, match="tool_arguments_null"):
|
||||
await _run(frames, done=True, mode="openai")
|
||||
|
||||
Reference in New Issue
Block a user