feat: judge whether reasoning actually happened from multiple signals
reasoning_tokens=None has been carrying two meanings at once, no reasoning and no report, and the library resolved the ambiguity by quietly claiming the first. ThinkingObservation splits them: UNKNOWN says the call left no signal, ABSENT says the provider reported zero. The verdict ranks evidence by hardness. Reasoning prose is the fact itself; reasoning_tokens is a report about the fact, so a missing report cannot overrule prose that is right there. The prose check strips first, since a gateway that returns whitespace is not evidence. The enum lives in types.py, not in the new thinking.py, because LLMResponse is typed on it and the innermost layer must not import a decision module.
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
"""推理这件事的全部**决策**: 请求侧注入形态、响应侧结果裁定、二者的对账。
|
||||
|
||||
与 `providers.py` 的分工: 那里是**注册表**(provider 长什么样,静态声明的存放
|
||||
与查找),这里是**决策**(拿声明和响应做判断)。P7"决策逻辑与状态存储分离"。
|
||||
|
||||
本模块**不定义** `ThinkingObservation` —— 它是 `LLMResponse` 的字段类型,归最
|
||||
内层 `types.py`;定义在这里会让 `types.py` 反向 import 决策模块(依赖铁律)。
|
||||
"""
|
||||
|
||||
from polygateway.types import ThinkingObservation
|
||||
|
||||
|
||||
def observe_thinking(*, thinking: str, reasoning_tokens: int | None) -> ThinkingObservation:
|
||||
"""由多信号裁定推理是否发生;判据按**证据硬度**排序(issue #16/#17)。
|
||||
|
||||
推理正文是事实本身,`reasoning_tokens` 是对事实的转述——转述缺失时事实仍然
|
||||
作数。2026-08-25 实测: MiniMax 这一路已不再返回
|
||||
`usage.completion_tokens_details`,而同一次调用里库拿得到 185 字符推理正文;
|
||||
只认 token 数的判据会把这种情形误判成"没推理"。
|
||||
|
||||
正文判据取 `strip()` 而非 truthy: 网关响应是外部输入,纯空白串不是证据(P5)。
|
||||
|
||||
判不出来时返回 `UNKNOWN` 而非 `ABSENT`——**不许把"没看见"说成"没发生"**。
|
||||
"""
|
||||
if thinking.strip():
|
||||
return ThinkingObservation.OBSERVED
|
||||
if reasoning_tokens is None:
|
||||
return ThinkingObservation.UNKNOWN
|
||||
return ThinkingObservation.OBSERVED if reasoning_tokens > 0 else ThinkingObservation.ABSENT
|
||||
Reference in New Issue
Block a user