feat: нативное поле рассуждений (reasoning_field) — правка 400 от Console Go
Build LLM Proxy / Build and push (release) Successful in 39s
Build LLM Proxy / Build and push (release) Successful in 39s
Диагноз: апстрим deepseek-v4.1-flash (провайдер opencode, Console Go) в thinking-режиме требует reasoning_content в assistant-сообщениях с tool_calls, а клиент opencode присылает рассуждения как reasoning + reasoning_details. Отсюда 400 'The reasoning_content in the thinking mode must be passed back to the API'. - providers[].reasoning_field / reasoning_empty_ok: прокси аддитивно достраивает нативное поле в assistant-сообщениях с непустым tool_calls (текст из reasoning или reasoning_details[].text, тип reasoning.text); существующее непустое поле не перезаписывается, ничего не переименовывается, прочие сообщения не трогаются; - устойчивость разбора ответов к JSON-null (choices/delta/content/tool_calls) — было 290 фейловеров с локального апстрима на платные из-за нашего же исключения; - лог: класс исключения в сообщении об ошибке; для 4xx логируется тело ответа апстрима (читается безопасно: 4xx — не стрим); - тесты: ReasoningFieldTest (13), NullToleranceTest (6), ConfigLogicTest (+2) — 100 всего; - CONFIG.md, TESTING.md (фактические замеры A/B против Console Go).
This commit is contained in:
+40
-1
@@ -13,7 +13,9 @@
|
||||
| Файл | Что проверяет |
|
||||
| --- | --- |
|
||||
| `ThinkTagSplitterTest` | Автомат рассечения think-тегов: passthrough при off, вырезание рассуждений при split, отбрасывание при strip, удержание разрезанного тега, несколько блоков, незакрытый блок; |
|
||||
| `ConfigLogicTest` | Разбор конфига, приоритет источников (апстрим важнее провайдера), слияние патчей, выбор апстрима и лимиты конкурентности, заголовки, сессии; |
|
||||
| `ConfigLogicTest` | Разбор конфига (`reasoning_field`/`reasoning_empty_ok` провайдера и их дефолты), приоритет источников (апстрим важнее провайдера), слияние патчей, выбор апстрима и лимиты конкурентности, заголовки, сессии; |
|
||||
| `ReasoningFieldTest` | Достройка нативного поля рассуждений (`applyReasoningField`/`reasoningTextOf`): форма клиента opencode (`reasoning` + `reasoning_details`), чужой `type` в details, склейка нескольких details, запрет перезаписи непустого поля, неприкосновенность сообщений без `tool_calls` (в т.ч. `[]`) и `user`/`tool`, пустая строка при `emptyOk`, `reasoning_field: null`, отсутствие `messages`, сохранение порядка и прочих полей; |
|
||||
| `NullToleranceTest` | Устойчивость разбора ответов апстрима к JSON-`null` (`choices`, `delta.tool_calls`, `delta.content`) — пропуск вместо исключения; |
|
||||
| `ThinkTagTransformTest` | Non-stream путь `transformThinkMessage`: перенос рассуждений в `reasoning_content`, дописывание к уже имеющемуся, strip, незакрытый блок, отсутствие изменений → null; |
|
||||
| `ThinkTagChunkTest` | SSE-чанки `transformThinkChunk`: удержание хвоста тега между чанками, независимые сплиттеры по index, удаление пустого `content`; |
|
||||
| `ThinkTagStreamTest` | Обвязка стрима `streamSseWithThinkTags`: разрез тега между data-событиями, сброс удержанного хвоста в финиш-чанке, прохождение служебных строк и `[DONE]`, битый JSON, чанк без choices, strip. |
|
||||
@@ -44,6 +46,43 @@
|
||||
|
||||
Правило: боевой код нельзя подгонять под тест; если тест не проходит, неверен тест.
|
||||
|
||||
## Поле рассуждений для Console Go (релиз 11) — замеры приёмки
|
||||
|
||||
Причина правки: апстрим `deepseek-v4.1-flash` у провайдера `opencode` (Console Go) в thinking-режиме
|
||||
требует `reasoning_content` в assistant-сообщениях с `tool_calls`, а клиент opencode присылает
|
||||
рассуждения как `reasoning` + `reasoning_details` — отсюда `400 The reasoning_content in the
|
||||
thinking mode must be passed back to the API`.
|
||||
|
||||
**Границы требования (замер прямыми запросами к Console Go, одинаковое тело):**
|
||||
|
||||
| assistant-сообщение | HTTP |
|
||||
| --- | --- |
|
||||
| с `tool_calls`, без reasoning вовсе | 400 |
|
||||
| с `tool_calls`, `reasoning` + `reasoning_details` (форма opencode) | 400 |
|
||||
| с `tool_calls`, только `reasoning_details` | 400 |
|
||||
| с `tool_calls`, `reasoning_content` + `reasoning` + `reasoning_details` (аддитивно) | 200 |
|
||||
| с `tool_calls`, `reasoning_content: ""` | 200 |
|
||||
| без `tool_calls`, без reasoning | 200 |
|
||||
|
||||
**Живая приёмка (локальный инстанс на 8101, конфиг-копия боевого, модель с единственным апстримом
|
||||
Console Go; тело — как у opencode: assistant + `reasoning` + `reasoning_details` + `tool_calls`):**
|
||||
|
||||
| Конфиг | `stream=false` | `stream=true` |
|
||||
| --- | --- | --- |
|
||||
| без правки (`reasoning_field` не задан) | **400** — та самая ошибка про `reasoning_content` | **400** |
|
||||
| с правкой (`reasoning_field: reasoning_content`) | **200**, модель продолжила диалог после tool-результата | **200** |
|
||||
|
||||
**Регрессия на боевых цепочках (тот же инстанс, то же тело с `tool_calls`):** `codding-big` → 200
|
||||
(ушло на `minimax-m3`, поле не добавляется — флаг объявлен только у провайдера `opencode`),
|
||||
`codding` → 200 (`qwen-3.8`, local), `assistant` → 200 (Console Go с правкой).
|
||||
|
||||
**Мутационная приёмка:** 4 мутации из ТЗ отработал кодер, две проверены вручную
|
||||
(`cleanAllTests jvmTest`): снятие охраны `tool_calls` в `applyReasoningField` → падают
|
||||
`assistantWithoutToolCallsIsUntouched` и `assistantWithEmptyToolCallsIsUntouched`;
|
||||
возврат `obj["choices"]?.jsonArray` в `rebuildFromChunks` → падает `rebuildFromChunksToleratesNullChoices`.
|
||||
|
||||
## Известное ограничение
|
||||
|
||||
Живой стрим в реальном апстриме модульными тестами не проверяется: обвязка испытывается на синтетическом SSE через каналы ktor. Реальный апстрим проверяется только после деплоя.
|
||||
Правка поля рассуждений — исключение: она проверена живым инстансом против настоящего Console Go (таблица выше) до релиза.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user