5eb01a0096
The thinking matrix had been running inside make ci all along, which is not what the design claimed. It takes seven minutes, spends 137 real calls, and its criteria are statistical, so a network hiccup fails the build for reasons unrelated to the change under test -- one run died on three consecutive network errors exhausting the source. The project already has the mechanism for this: the slow marker, which addopts excludes by default and the config comments describe as "CI runs it on demand". Marking the matrix slow brings make ci back down from seven minutes to ninety seconds while the matrix stays a merge requirement via -m slow. The design also claimed e2e does not run in CI. It does: make test runs pytest over tests/, e2e included, and the existing smoke tests really call the gateway whenever .env has credentials. Only slow-marked tests are excluded. Both documents now say so. Version sources are pyproject and __init__; a test enforces they agree, and it caught the second one being missed.
277 lines
22 KiB
Markdown
277 lines
22 KiB
Markdown
---
|
||
type: plan
|
||
node_id: plan:2026-08-02-thinking-capability
|
||
title: "推理开关能力建模与 reasoning_tokens 采集实施计划(issue #5 + #6)"
|
||
date: 2026-08-02
|
||
---
|
||
|
||
# 推理开关能力建模与 reasoning_tokens 采集实施计划(issue #5 + #6)
|
||
|
||
**目标**:让 `enable_thinking` 对每个源要么真实生效、要么显式报错,并采集 `reasoning_tokens` 以区分推理开销与生成开销。
|
||
|
||
**方案概述**:`ProviderProfile` 保留为「形态层」(参数长什么样,按 provider),新增 model 级「能力层」声明该模型能否关闭推理;两层在单一判定函数 `resolve_thinking` 相遇,装配期与请求期共用。同时照搬 issue #3 的 `_coerce_cached_tokens` 采集 `reasoning_tokens`,并把 `enable_thinking` 纳入缓存指纹。
|
||
|
||
**涉及技术**:Python 3.11 frozen dataclass、`MappingProxyType` 只读注册表、httpx、SQLite/PostgreSQL DDL 迁移、pytest。
|
||
|
||
**依据文档**:设计 `designs/2026-08-02-thinking-capability-design.md`;事实基础 `findings/2026-08-02-thinking-switch-and-reasoning-tokens.md`。
|
||
|
||
**保真校验**:本计划不涉及 `reference/` 参考实现迁移,保真校验不适用。
|
||
|
||
## 文件结构
|
||
|
||
| 文件 | 动作 | 职责 |
|
||
|---|---|---|
|
||
| `src/polygateway/types.py` | 修改 | `LLMResponse` / `TransportResult` 尾部各加 `reasoning_tokens` |
|
||
| `src/polygateway/providers.py` | 修改 | 形态层放宽为 `dict \| None`;新增能力层与 `resolve_thinking` |
|
||
| `src/polygateway/transports/openai_compat.py` | 修改 | 采集 `reasoning_tokens`;`_build_payload` 接入 `resolve_thinking`;收 `capabilities` |
|
||
| `src/polygateway/middleware/retry.py` | 修改 | `_build_response` 透传 `reasoning_tokens` |
|
||
| `src/polygateway/ports.py` | 修改 | `record_llm_call` 21 → 22 字段 |
|
||
| `src/polygateway/telemetry/sqlite.py` | 修改 | 建表列 + `_BACKFILL_COLUMNS` + `_COLUMNS`(新列排末尾) |
|
||
| `src/polygateway/telemetry/postgres.py` | 修改 | 同上 |
|
||
| `src/polygateway/middleware/telemetry.py` | 修改 | `_record` + 三个 `emit_*` 入口 |
|
||
| `src/polygateway/client.py` | 修改 | `capabilities` 参数贯通;装配守卫;缓存指纹纳入 `enable_thinking` |
|
||
| `tests/e2e/test_thinking_live.py` | 新建 | 真实 API 矩阵 L1–L9 |
|
||
| `CHANGELOG.md` / `research-wiki/schemas/llm-calls.md` | 修改 | 行为变更说明与字段表 21 → 22 |
|
||
|
||
**任务顺序不可调换**:T1–T3 先把 `reasoning_tokens` 打通(#6 是 #5 的验收仪器),T4–T7 再改推理开关,T8 用真实 API 验证,T9 收尾文档。
|
||
|
||
## 关键接口(跨任务消费,此处给出实际代码)
|
||
|
||
`providers.py` 新增:
|
||
|
||
```python
|
||
@dataclass(frozen=True)
|
||
class ThinkingCapability:
|
||
"""某个具体模型的推理能力(model 级);登记必须附实测证据与日期。"""
|
||
|
||
can_disable: bool
|
||
evidence: str
|
||
|
||
|
||
def get_capability(
|
||
model: str, *, table: Mapping[str, ThinkingCapability] | None = None
|
||
) -> ThinkingCapability | None:
|
||
"""按模型名精确查找;未登记返回 None(= 能力未知,由调用方决定退化)。"""
|
||
```
|
||
|
||
```python
|
||
def register_capability(
|
||
model: str,
|
||
capability: ThinkingCapability,
|
||
*,
|
||
base: Mapping[str, ThinkingCapability] | None = None,
|
||
) -> dict[str, ThinkingCapability]:
|
||
"""纯函数注册: 返回 base(缺省 DEFAULT_CAPABILITIES)+ 新条目的新表,同名覆盖。"""
|
||
|
||
|
||
def resolve_thinking(
|
||
profile: ProviderProfile,
|
||
capability: ThinkingCapability | None,
|
||
enable_thinking: bool | None,
|
||
*,
|
||
model: str,
|
||
) -> Mapping[str, Any]:
|
||
"""三态 + 两层能力 → 注入片段;不可满足时 ValueError(调用点翻译为领域错误)。
|
||
|
||
model 只用于错误与告警文案: 报错必须能定位到具体模型才有可操作性,
|
||
而 capability 为 None(未登记)时无从从别处取得模型名。
|
||
"""
|
||
```
|
||
|
||
`resolve_thinking` 的判定顺序(**顺序即语义,不可调换**):
|
||
|
||
| 步 | 条件 | 行为 |
|
||
|---|---|---|
|
||
| 1 | `enable_thinking is None` | 返回 `{}`(不干预) |
|
||
| 2 | 对应档 `slot is None` | `ValueError`:形态未知,指路 `register_provider` / `extra_body` |
|
||
| 3 | `capability is None` | `loguru.warning` 后返回 `slot`(能力未登记,从宽放行) |
|
||
| 4 | `enable_thinking is False` 且 `capability.can_disable is False` | `ValueError`:该模型无法关闭推理 |
|
||
| 5 | 其余 | 返回 `slot` |
|
||
|
||
第 2 步必须先于第 4 步:形态未知时无从注入,能力如何无关紧要。第 3 步先于第 4 步:未登记模型无 `can_disable` 可读。
|
||
|
||
`transports/openai_compat.py` 新增:
|
||
|
||
```python
|
||
def _coerce_reasoning_tokens(usage: Any) -> int | None:
|
||
"""取 usage.completion_tokens_details.reasoning_tokens(issue #6);形态异常一律 None。"""
|
||
```
|
||
|
||
## 任务清单
|
||
|
||
### T1 — `reasoning_tokens` 进入类型与采集路径
|
||
|
||
- [ ] **文件**:改 `src/polygateway/types.py`、`src/polygateway/transports/openai_compat.py`、`src/polygateway/middleware/retry.py`;改测 `tests/unit/test_types.py`、`tests/unit/test_openai_compat.py`、`tests/unit/test_retry.py`
|
||
|
||
**行为**:`LLMResponse` 与 `TransportResult` **尾部**各加 `reasoning_tokens: int | None = None`(字段顺序是公共承诺,见 `types.py:1-5`,只增不删不改名)。新增 `_coerce_reasoning_tokens`,语义与 `_coerce_cached_tokens`(`openai_compat.py:161-177`)逐条对齐:非 `dict` 返回 `None`;`completion_tokens_details` 非 `dict` 返回 `None`;`bool` 显式排除(`isinstance(True, int)` 为真,放行会把 `True` 记成 1);负数返回 `None`;`0` 如实保留。流式(`:401` 附近)取 `sink.get("usage")`、非流式(`:485` 附近)取 `body.get("usage")`,与 `cached_prompt_tokens` 同处填值。`retry.py:_build_response` 透传。
|
||
|
||
**docstring 措辞**(必须逐字,理由见 findings §4c):`None` = **本次调用**未上报,**不可**写「该源未上报」——中转在上游不返回 usage 时会本地补算并吃掉该字段。
|
||
|
||
**验收**:非流式与流式响应含 `completion_tokens_details.reasoning_tokens: 7` → `reasoning_tokens == 7`;该键为 `0` → `0`(不与 `None` 混同);`completion_tokens_details` 缺失 / 非 dict / 值为 `True` / 值为 `-1` → 均为 `None`;`missing_done="salvage"` 打捞路径(无 usage 帧)→ `None` 而非 `0`。
|
||
|
||
**测试证据**:先加断言 → 失败(字段不存在)→ 实现 → 通过。
|
||
|
||
**验证**:`conda run -n PolyGateway pytest tests/unit/test_types.py tests/unit/test_openai_compat.py tests/unit/test_retry.py -v` → 全部 PASS。
|
||
|
||
### T2 — 遥测端口 21 → 22 字段与两后端落库
|
||
|
||
- [ ] **文件**:改 `src/polygateway/ports.py`、`src/polygateway/telemetry/sqlite.py`、`src/polygateway/telemetry/postgres.py`、`src/polygateway/middleware/telemetry.py`;改测 `tests/unit/test_ports.py`、`tests/unit/test_telemetry.py`、`tests/integration/test_postgres_telemetry.py`
|
||
|
||
**行为**:`record_llm_call` 在 `sampling` 之后追加 `reasoning_tokens: int | None`(不设默认值——库外无第三方实现者,见 `ports.py:250` 注释)。两个后端在建表 DDL、`_BACKFILL_COLUMNS`(sqlite)/ 迁移语句列表(postgres)、`_COLUMNS` 三处各加一项,**新列必须排在末尾**(两文件均有明文注释:旧表只能 ALTER 追加,新建库若插在前面会与迁移路径的物理列序分叉)。`middleware/telemetry.py` 的 `_record` 加参数,三个 `emit_*` 入口按 `cached_prompt_tokens` 的既有形态填值:`emit_attempt` 用 `response.reasoning_tokens if response else None`,`emit_cache_hit` 原样回放,`emit_terminal_failure` 填 `None`。
|
||
|
||
**不改 `pricing.py`**:推理 token 已含在 `completion_tokens` 内,单列计价即重复计费。
|
||
|
||
**验收**:新建库与经 ALTER 迁移的旧库物理列序一致;`reasoning_tokens=7` / `0` / `None` 三种值各自如实落库(`0` 与 `NULL` 可区分);遥测写失败仍降级为 warning 不冒泡。
|
||
|
||
**测试证据**:先扩字段清单断言 → 失败 → 实现 → 通过。
|
||
|
||
**验证**:`conda run -n PolyGateway pytest tests/unit/test_ports.py tests/unit/test_telemetry.py -v` → PASS;`conda run -n PolyGateway pytest tests/integration/test_postgres_telemetry.py -v` → PASS 或按既有约定 SKIP(无 PG 凭据时)。
|
||
|
||
### T3 — 提交点:issue #6 完整可用
|
||
|
||
- [ ] 运行 `conda run -n PolyGateway make ci`,确认全绿后提交。提交信息类型 `feat`,正文说明 `LLMResponse` 新增字段与遥测端口 21 → 22。此处独立成一个提交,便于 #6 单独回滚。
|
||
|
||
**测试证据**:本任务不引入新行为,证据即 T1 与 T2 各自的「先失败后通过」记录;提交前需确认这两组记录都已产生,不得以 `make ci` 全绿代替。
|
||
|
||
### T4 — 形态层放宽与 profile 修正
|
||
|
||
- [ ] **文件**:改 `src/polygateway/providers.py`;改测 `tests/unit/test_providers.py`
|
||
|
||
**行为**:`ProviderProfile.thinking_on` / `thinking_off` 类型由 `dict[str, Any]` 改为 `Mapping[str, Any] | None`。三值语义写进类 docstring:`{...}` = 已知注入片段;`{}` = 已知无需注入即处于该档;`None` = **未知**(库不知道该 provider 如何表达)。删除现有 docstring 里「两档皆空 ⇒ 不产生任何效果」那段(`providers.py:23-25` 与 `:50-51`)——它正是把「不支持」与「未知」编码成同一个值的根因。
|
||
|
||
`minimax` 填 `thinking_on={"reasoning_effort": "medium"}`、`thinking_off={"reasoning_effort": "none"}`;`openai` 两档改 `None`。qwen / deepseek **不动**(实测正确)。两处均加注释写明:取值依据 2026-08-02 经自建 new-api 中转的实测,直连官方端点未验证。
|
||
|
||
**验收**:`get_provider("minimax").thinking_off == {"reasoning_effort": "none"}`;`get_provider("openai").thinking_on is None`;qwen / deepseek 两档与改动前逐字相同。
|
||
|
||
**测试证据**:`tests/unit/test_providers.py:29,34` 现有断言锁的是空字典,先改成新期望 → 失败 → 实现 → 通过。
|
||
|
||
**验证**:`conda run -n PolyGateway pytest tests/unit/test_providers.py -v` → PASS。
|
||
|
||
### T5 — 能力层与 `resolve_thinking`
|
||
|
||
- [ ] **文件**:改 `src/polygateway/providers.py`;改测 `tests/unit/test_providers.py`
|
||
|
||
**行为**:按「关键接口」一节的签名实现 `ThinkingCapability`、`DEFAULT_CAPABILITIES`、`get_capability`、`register_capability`、`resolve_thinking`。注册表用 `MappingProxyType` 只读,注册走纯函数返回新表(不修改共享状态,纯 asyncio 中立铁律),与既有 `register_provider`(`providers.py:84-90`)同形。
|
||
|
||
`DEFAULT_CAPABILITIES` 首发五条,`evidence` 逐条写明实测日期与样本量:
|
||
|
||
| 键 | `can_disable` | `evidence` 要点 |
|
||
|---|---|---|
|
||
| `MiniMax-M3` | `True` | 2026-08-02 实测 N=10,`reasoning_effort=none` 稳定关闭 |
|
||
| `MiniMax-M2.7` | `False` | 三形态各 N=3 全无效;OpenRouter 登记 `mandatory:true` |
|
||
| `MiniMax-M2.5` | `False` | 同上 |
|
||
| `qwen3.7-plus` | `True` | 实测 `enable_thinking=false` 关闭 |
|
||
| `deepseek-v4-pro` | `True` | 实测 `thinking:{"type":"disabled"}` 关闭 |
|
||
|
||
**验收**:`resolve_thinking` 五条判定各有一例;未登记模型返回 `slot` 并产生一条 warning(**loguru 不经标准 logging,pytest 的 `caplog` 抓不到**——必须复用项目既有写法 `logger.add(messages.append, level="WARNING")`,见 `tests/unit/test_config.py:29-31`);`enable_thinking=False` + `MiniMax-M2.7` 抛 `ValueError` 且消息含模型名与"无法关闭"字样;`enable_thinking` 任意非 `None` + `openai` profile 抛 `ValueError` 且消息含 `register_provider` 与 `extra_body` 两个指路词;`register_capability` 不修改 `DEFAULT_CAPABILITIES`。
|
||
|
||
**测试证据**:先写五条判定的参数化测试 → 失败(函数不存在)→ 实现 → 通过。
|
||
|
||
**验证**:`conda run -n PolyGateway pytest tests/unit/test_providers.py -v` → PASS。
|
||
|
||
### T6 — transport 接入与请求期兜底
|
||
|
||
- [ ] **文件**:改 `src/polygateway/transports/openai_compat.py`;改测 `tests/unit/test_openai_compat.py`
|
||
|
||
**行为**:`OpenAICompatTransport.__init__` 增加 `capabilities: Mapping[str, ThinkingCapability] | None = None`,与既有 `registry` 参数同形并存于 `self._capabilities`。`_build_payload` 的两个 `if` 分支(`:293-296`)收敛为两行——先 `capability = get_capability(source.model, table=self._capabilities)`,再 `payload.update(resolve_thinking(profile, capability, source.enable_thinking, model=source.model))`;其后 `payload.update(source.extra_body)` 与 `payload.update(overlay)` 两行**顺序不变**(顺序即优先级,issue #4 决策 A)。`complete()` 中把构造 payload 的 `ValueError` 翻译为 `RequestRejectedError`(四分类之一,不重试不换源)。
|
||
|
||
**绝不在 `_build_payload` 内抛裸 `ValueError` 让它冒泡**:该处位于 RetryMW 内侧,裸异常不属错误四分类、`TelemetryMW` 也不捕,会导致一行遥测都没有就逃出 `chat()`。
|
||
|
||
**验收**:`enable_thinking=True` + minimax 源 → 请求体含 `reasoning_effort: "medium"`;`False` → `"none"`;`None` → 请求体无 `reasoning_effort` 键;`extra_body={"reasoning_effort":"high"}` 时实发 `high`(覆盖 profile);`enable_thinking=False` + M2.7 源经 transport 调用 → `RequestRejectedError` 而非裸 `ValueError`。
|
||
|
||
**测试证据**:扩 `tests/unit/test_openai_compat.py:418-431` 的三态参数化,加 minimax 用例 → 失败 → 实现 → 通过。
|
||
|
||
**验证**:`conda run -n PolyGateway pytest tests/unit/test_openai_compat.py -v` → PASS。
|
||
|
||
### T7 — 装配守卫、参数贯通与缓存指纹
|
||
|
||
- [ ] **文件**:改 `src/polygateway/client.py`;改测 `tests/unit/test_cache.py`(指纹相关)、新增装配守卫测试至 `tests/unit/test_config.py`
|
||
|
||
**行为**(三件事,同一文件):
|
||
|
||
其一,`from_settings` 与 `from_env` 各增加 `capabilities` 参数并透传给 `OpenAICompatTransport`;`from_settings` 在已解析 `profiles` 之后(`client.py:248`)加装配守卫:对 `zip(sources, profiles, strict=True)` 的每一对,先 `get_capability(src.model, table=capabilities)` 取能力,再调用一次 `resolve_thinking(prof, cap, src.enable_thinking, model=src.model)` 并丢弃返回值——只为让配置错误在装配期即抛 `ValueError`。守卫与 transport 内的判定共用同一函数,不复制逻辑——这与 `get_provider` 在 `client.py:248` 与 `openai_compat.py:313` 双点调用的既有形态一致。
|
||
|
||
其二,`build_model_fingerprint`(`client.py:63-80`)把 `enable_thinking` 纳入摘要。实现必须保持既有不变量——**全源不配 `enable_thinking` 时指纹字面量与改动前逐字相同**:
|
||
|
||
```python
|
||
def _fingerprint_mark(s: SourceConfig) -> str:
|
||
parts: list[Any] = [s.model, dict(s.extra_body)]
|
||
if s.enable_thinking is not None: # 仅在表态时追加,保证存量指纹字面量不变
|
||
parts.append(s.enable_thinking)
|
||
return json.dumps(parts, sort_keys=True, ensure_ascii=False)
|
||
```
|
||
|
||
筛选条件由 `if s.extra_body` 扩为 `if s.extra_body or s.enable_thinking is not None`。
|
||
|
||
其三,为守卫补测:`enable_thinking=False` + `provider=minimax` + `model=MiniMax-M2.7` 的 `GatewaySettings` 经 `from_settings` → `ValueError`;`provider=openai` + 任意非 `None` 的 `enable_thinking` → `ValueError`。
|
||
|
||
**验收**:装配期报错两例;改 `enable_thinking` → 指纹变化;只配 `extra_body`、不配 `enable_thinking` 的源 → 指纹与改动前逐字相同(用硬编码的历史字面量断言,防回归)。
|
||
|
||
**测试证据**:先写三条断言 → 失败 → 实现 → 通过。
|
||
|
||
**验证**:`conda run -n PolyGateway pytest tests/unit/test_cache.py tests/unit/test_config.py -v` → PASS;随后 `conda run -n PolyGateway make ci` → 全绿。
|
||
|
||
### T8 — 真实 API e2e 矩阵
|
||
|
||
- [ ] **文件**:新建 `tests/e2e/test_thinking_live.py`
|
||
|
||
**行为**:沿用既有 e2e 约定(`tests/e2e/test_smoke_gateway.py:19-26`)——`dotenv_values(".env")` 读凭据、`skipif(not _HAS_SOURCE, ...)`、结构化报告写入 `tests/outputs/e2e/`。**不新造开关机制**:另加项目既有的 `slow` 标记,靠 `pyproject.toml` 的 `addopts = "-m 'not slow'"` 把本组挡在 `make ci` 之外(137 次真实调用、约 7 分钟,且判据是统计性的,网络抖动会造成假红——执行期实测撞到过一次 `network_error` 耗尽源)。合并前用 `pytest -m slow tests/e2e/test_thinking_live.py` 显式真跑。
|
||
|
||
**源映射**:L1–L5、L8 的 MiniMax 行用现有的 `LLM__MINIMAX__1__*`(`MODEL=MiniMax-M3`);M2.7 / M2.5 行经 `dataclasses.replace(source, model=...)` 派生,不新增 `.env` 键。**L6 / L7 目前无对应源**——`.env` 里只有 MINIMAX 与 MONKEY 两类;需新增 `{SCOPE}__QWEN__1__*` 与 `{SCOPE}__DEEPSEEK__1__*`(同一中转 `BASE_URL` 与密钥,仅 `MODEL` 不同)。未配置时按既有 `skipif` 约定跳过,并在报告中记为「未覆盖」,**不得静默计入通过**。
|
||
|
||
覆盖矩阵(轮数经环境变量可调,默认值如下):
|
||
|
||
| # | 场景 | 源 | 轮数 | 判据 |
|
||
|---|---|---|---|---|
|
||
| L1 | `enable_thinking=False` | MiniMax-M3 | 10 | **每轮** `completion_tokens < 30`(主判据)且 `reasoning_tokens in (None, 0)`(辅判据,与下游口径一致) |
|
||
| L2 | `enable_thinking=True` | MiniMax-M3 | 10 | 多数轮 `completion_tokens > 100`;请求体实发 `reasoning_effort=medium` |
|
||
| L3 | `enable_thinking=None` | MiniMax-M3 | 10 | 请求体无 `reasoning_effort` 键 |
|
||
| L4 | `extra_body` 覆盖 profile | MiniMax-M3 | 5 | 实发 `high` |
|
||
| L5 | L1 / L2 的**流式**重跑 | MiniMax-M3 | 各 10 | 同 L1 / L2(`stream=True` 是库的默认主路径) |
|
||
| L6 | `enable_thinking=False` | qwen | 10 | 每轮 `completion_tokens < 30` |
|
||
| L7 | `enable_thinking=False` | deepseek | 10 | 每轮 `completion_tokens < 30` |
|
||
| L8 | 能力表漂移哨兵 | 全部登记模型 | 各 5 | 实测行为与 `can_disable` 声明一致 |
|
||
| L9 | M2.7 + `enable_thinking=False` → 装配期报错 | — | — | 纯本地,无需真实调用 |
|
||
|
||
**三条必须遵守的测试纪律**:
|
||
|
||
其一,**判别量只能是 `reasoning_tokens`**。(执行时按 e2e 实测修正:本条初稿写的是「主判据用 `completion_tokens`」,被数据推翻——两档的输出长度分布**重叠**,关闭档实测最高 46、开启档最低 13,按长度阈值判两个方向都会误判。)`completion_tokens` 仅作 `reasoning_tokens` 被中转吃掉时的退路(findings §4c、§2.5)。
|
||
|
||
其二,**关闭方向要求每轮满足,开启方向只要求多数轮满足**。中转吃掉 ctd 时开启方向可能偶尔观测不到,关闭方向不受影响。
|
||
|
||
其四,**必须有不依赖输出侧噪声的锚点**:L2b 比较两档的 `prompt_tokens`(相对比较,无魔数),L3b 用非法值反证 `none` 是被识别而非被静默丢弃——后者正是 issue #5 的原始故障形态,不排除它,关闭方向的证据就只到「未回归」,够不到「已生效」。
|
||
|
||
其三,**源不可用必须跳过并在报告中显式记为「未覆盖」**,不得静默计入通过(实测中 kimi 渠道 429 后被中转下线并返回 404)。报告要能一眼看出哪些矩阵行没跑到。
|
||
|
||
**报告内容**(`tests/outputs/e2e/test_thinking_live_<ts>.md`):逐轮记录实际注入的 thinking 片段、`prompt_tokens` / `completion_tokens` / `reasoning_tokens`、单轮判定结果;逐行记录矩阵编号、通过或跳过及其原因;文末给出总调用次数与时间戳。原始数字必须落盘——结论可以复核,才算证据。
|
||
|
||
**验收**:矩阵九行全部有结论(通过 / 明确跳过),报告落盘 `tests/outputs/e2e/`。
|
||
|
||
**验证**:`conda run -n PolyGateway pytest tests/e2e/test_thinking_live.py -v -s` → PASS,人工核对报告。
|
||
|
||
### T9 — 文档同步与收尾
|
||
|
||
- [ ] **文件**:改 `CHANGELOG.md`、`research-wiki/schemas/llm-calls.md`
|
||
|
||
**行为**:CHANGELOG 必须醒目标注这是**行为变更而非纯修复**——MiniMax 源的 `ENABLE_THINKING` 从「无效」变为「生效」,且配了该项的 scope 会有一次性缓存冷启动。同时写明 `reasoning_tokens` 的语义:`None` = 本次调用未上报,下游判据须为 `in (None, 0)`,写 `== 0` 永远不成立。`schemas/llm-calls.md` 的字段表由 21 改 22,新增行说明该列。
|
||
|
||
Gitea Wiki(独立仓库)**本任务内必须同步**:按 `docs-convention.md` §2「新公共 API / 新能力」一行,需改 `参考-公共API`(`LLMResponse` 新字段)与相关指南页;该表把同步绑定在**变更**上而非发版上,不可推迟。`Home.md` 的版本号与安装命令等发版项不在本计划范围。
|
||
|
||
**验收**:CHANGELOG 含行为变更与冷启动两处提示;schema 文档字段数与 `_COLUMNS` 长度一致。
|
||
|
||
**验证**:人工核对;`conda run -n PolyGateway make ci` → 全绿。
|
||
|
||
## 完成后的独立验证
|
||
|
||
按 `verification-before-completion` 的强制档,本计划跨多文件,合并前须派**全新上下文**的 verifier subagent 逐条核对设计 §11 的九条验收标准与本计划各任务的测试证据,不得自审代替。
|
||
|
||
### T10 — 同步结论给 dissect
|
||
|
||
- [ ] **动作**:在本分支合并时,向 dissect 提一条 issue 或在其 `ROADMAP` 风险表中记录下述结论,并确认对方已读。
|
||
|
||
**验收**:dissect 侧存在可追溯的记录(issue 编号或文档行号),不以口头告知为准。
|
||
|
||
## 需要同步给下游的结论
|
||
|
||
`MiniMax-M2.7` / `M2.5` 的推理**关不掉**是模型固有属性,任何库层改动都无法改变。dissect 的 Phase-0 若要做「开思考 vs 关思考」对照,只能在 M3 上做,或把因子改为「高档 vs 低档」。此结论须在本分支合并时同步给 dissect。
|