fix(ai-review): max_tokens=16000, fallback на reasoning_content (20.09.2026)
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
name: AI Code Review
|
name: AI Code Review
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
# Кнопка "Run workflow" в UI Gitea
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
+20
-9
@@ -8,7 +8,8 @@ AI Code Review for Filwords PL Backend
|
|||||||
Запускается через Gitea Actions (workflow_dispatch — вручную из UI).
|
Запускается через Gitea Actions (workflow_dispatch — вручную из UI).
|
||||||
|
|
||||||
(20.09.2026) Первая версия — конвейер без tool-calling, предсказуемо по токенам.
|
(20.09.2026) Первая версия — конвейер без tool-calling, предсказуемо по токенам.
|
||||||
(20.09.2026, вечер) Фикс: tool_choice=none, безопасное извлечение content.
|
(20.09.2026, вечер) Фикс: max_tokens=16000, fallback на reasoning_content
|
||||||
|
(DeepSeek V4.1 Flash — reasoning-модель, тратит много токенов на размышления).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@@ -27,7 +28,10 @@ GITEA_INSTANCE = os.environ.get("GITEA_INSTANCE", "https://git.filwordspl.com")
|
|||||||
GITEA_REPO = os.environ.get("GITEA_REPO", "Drobysh/filwordspl-backend")
|
GITEA_REPO = os.environ.get("GITEA_REPO", "Drobysh/filwordspl-backend")
|
||||||
|
|
||||||
MODEL = "deepseek-ai/DeepSeek-V4.1-Flash"
|
MODEL = "deepseek-ai/DeepSeek-V4.1-Flash"
|
||||||
MAX_OUTPUT_TOKENS = 4000
|
|
||||||
|
# 🔧 ФИКС (20.09.2026): было 4000 — DeepSeek V4.1 Flash тратит ~4000 на reasoning,
|
||||||
|
# не оставляя токенов на финальный ответ. 16000 хватает с запасом.
|
||||||
|
MAX_OUTPUT_TOKENS = 16000
|
||||||
|
|
||||||
# Что игнорировать при сборе кода
|
# Что игнорировать при сборе кода
|
||||||
IGNORE_DIRS = {
|
IGNORE_DIRS = {
|
||||||
@@ -71,7 +75,6 @@ def collect_code(repo_root: Path) -> str:
|
|||||||
if path.suffix not in INCLUDE_EXTENSIONS:
|
if path.suffix not in INCLUDE_EXTENSIONS:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Пропускаем игнорируемые папки
|
|
||||||
rel = path.relative_to(repo_root)
|
rel = path.relative_to(repo_root)
|
||||||
if any(part in IGNORE_DIRS for part in rel.parts):
|
if any(part in IGNORE_DIRS for part in rel.parts):
|
||||||
continue
|
continue
|
||||||
@@ -84,7 +87,6 @@ def collect_code(repo_root: Path) -> str:
|
|||||||
print(f"⚠️ Не прочитал {rel}: {e}", file=sys.stderr)
|
print(f"⚠️ Не прочитал {rel}: {e}", file=sys.stderr)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Пропускаем слишком большие файлы
|
|
||||||
if len(content) > MAX_FILE_SIZE:
|
if len(content) > MAX_FILE_SIZE:
|
||||||
print(f"⚠️ Пропущен большой файл: {rel} ({len(content)} символов)", file=sys.stderr)
|
print(f"⚠️ Пропущен большой файл: {rel} ({len(content)} символов)", file=sys.stderr)
|
||||||
continue
|
continue
|
||||||
@@ -176,7 +178,6 @@ def call_together(code: str) -> str:
|
|||||||
],
|
],
|
||||||
"max_tokens": MAX_OUTPUT_TOKENS,
|
"max_tokens": MAX_OUTPUT_TOKENS,
|
||||||
"temperature": 0.3,
|
"temperature": 0.3,
|
||||||
"tool_choice": "none",
|
|
||||||
},
|
},
|
||||||
timeout=300.0,
|
timeout=300.0,
|
||||||
)
|
)
|
||||||
@@ -189,18 +190,28 @@ def call_together(code: str) -> str:
|
|||||||
data = response.json()
|
data = response.json()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = data["choices"][0]["message"]["content"]
|
message = data["choices"][0]["message"]
|
||||||
if result is None:
|
result = message.get("content")
|
||||||
raise KeyError("content is None")
|
|
||||||
|
# 🔧 ФИКС (20.09.2026): если content пустой — используем reasoning_content.
|
||||||
|
# DeepSeek V4.1 Flash — reasoning-модель, может уйти в размышления
|
||||||
|
# и не выдать финальный ответ. Тогда берём reasoning.
|
||||||
|
if not result:
|
||||||
|
result = message.get("reasoning_content")
|
||||||
|
|
||||||
|
if not result:
|
||||||
|
raise KeyError("и content, и reasoning_content пустые")
|
||||||
except (KeyError, IndexError, TypeError) as e:
|
except (KeyError, IndexError, TypeError) as e:
|
||||||
print(f"❌ Неожиданный формат ответа: {e}", file=sys.stderr)
|
print(f"❌ Неожиданный формат ответа: {e}", file=sys.stderr)
|
||||||
print(f"Полный ответ: {data}", file=sys.stderr)
|
print(f"Полный ответ: {data}", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
usage = data.get("usage", {})
|
usage = data.get("usage", {})
|
||||||
|
finish = data["choices"][0].get("finish_reason", "unknown")
|
||||||
print(
|
print(
|
||||||
f"✅ Ответ получен. Токены: вход={usage.get('prompt_tokens', 0)}, "
|
f"✅ Ответ получен. Токены: вход={usage.get('prompt_tokens', 0)}, "
|
||||||
f"выход={usage.get('completion_tokens', 0)}"
|
f"выход={usage.get('completion_tokens', 0)}, "
|
||||||
|
f"finish={finish}"
|
||||||
)
|
)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
Reference in New Issue
Block a user