feat: carry the reasoning verdict through to LLMResponse

Both assembly paths fill it, streaming and non-streaming alike. Filling
only one is exactly the divergence this issue exposed: M3 returns
reasoning prose over SSE and nothing at all over the plain endpoint, so
a verdict computed on one path says nothing about the other.

The field defaults to UNKNOWN on both TransportResult and LLMResponse.
A transport that does not judge should not get to declare absence on
the provider's behalf, and a default that stays silent is the only one
that cannot lie.
This commit is contained in:
2026-08-26 00:03:28 -04:00
parent 59d2e442e6
commit 8c5c23ae72
6 changed files with 157 additions and 3 deletions
+24
View File
@@ -27,6 +27,7 @@ from polygateway.types import (
GlobalLimits,
RetryPolicy,
SourceConfig,
ThinkingObservation,
TransportResult,
)
from tests.contracts.conftest import FakeClock
@@ -225,6 +226,29 @@ class TestObservabilityPassthrough:
assert resp.cached_prompt_tokens is None and resp.model_reported is None
assert resp.reasoning_tokens is None
async def test_thinking_observation_reaches_the_response(self):
"""issue #16/#17: 裁定归 transport,中间件只透传,不得在途中改判。"""
result = TransportResult(
content="ok",
thinking="想一下",
prompt_tokens=10,
completion_tokens=5,
usage_source="measured",
ttft_ms=12.0,
max_inter_token_ms=3.0,
raw={},
thinking_observation=ThinkingObservation.OBSERVED,
)
mw, *_ = _harness([_src("a")], [result])
resp = await mw(_REQ)
assert resp.thinking_observation is ThinkingObservation.OBSERVED
async def test_unjudged_transport_result_stays_unknown(self):
"""不裁定的 transport(如 OCR)透传出来仍是 UNKNOWN,不被默认成 ABSENT。"""
mw, *_ = _harness([_src("a")], [_ok()])
resp = await mw(_REQ)
assert resp.thinking_observation is ThinkingObservation.UNKNOWN
class TestRetryAndFailover:
async def test_transient_switches_source_then_succeeds(self):