fix: 诊断提速与稳定性修复(reasoning 2048→512、输出精简、上限16000→8000、loading提示、观测日志+配置错误可读)

This commit is contained in:
LuminousRuoxi
2026-08-25 13:21:49 +08:00
parent d6f70b02f4
commit c04b6a0fd0
6 changed files with 70 additions and 11 deletions
+38 -5
View File
@@ -12,6 +12,7 @@ import json
import logging
import os
import re
import time
from typing import Any, Callable
import httpx
@@ -87,7 +88,13 @@ def reasoning_config(disable_reasoning: bool = False) -> dict[str, Any]:
"""OpenRouter rejects payloads that set both ``effort`` and ``max_tokens``."""
if disable_reasoning:
return {"effort": "none", "exclude": True}
return {"max_tokens": 2048}
# 思考预算实测(2026-08-25 两次):
# 2048 → 单次 diagnose 309.8s,模型烧满全部思考 tokenDeepSeek 思考
# token 生成速度远慢于正文(约 1/10),5 分钟几乎全花在想。
# 512 → 同文书同 prompt 实测 70.2s,输出完整合规(4 briefs / 5 patterns)。
# 早期 2048→1024 曾因旧版冗长 prompt 引入 502(思考受限→输出不合规→校验
# 失败);prompt 压紧凑后 512 可行,JSON 校验失败仍有 complete_json 重试兜底。
return {"max_tokens": 512}
def extract_json(content: str | None) -> dict[str, Any]:
@@ -138,7 +145,10 @@ class LlmClient:
model: str | None = None,
proxy: str | None = None,
timeout: float = 180.0,
max_tokens: int = 16000,
# 实测(2026-08-25):诊断正常输出 ~2.5k tokensprompt 已压紧凑);
# 模型偶发啰嗦打满 16000 → 截断 → JSON 失败 → 重试又 258s,页面卡 5 分钟。
# 8000 给正常输出 3 倍余量,打满时更快失败并走既有重试兜底。
max_tokens: int = 8000,
temperature: float = 0.3,
transport: httpx.BaseTransport | None = None,
) -> None:
@@ -196,13 +206,36 @@ class LlmClient:
# diagnose 这类长 JSON 经常只返回空 content(页面上就是 502)。
# OpenRoutereffort 与 max_tokens 只能二选一。
payload["reasoning"] = reasoning_config(disable_reasoning)
# 观测:每次 LLM 调用都打耗时 + usage,慢链路(同事反馈 diagnose 卡 5 分钟)靠它定位
t0 = time.monotonic()
try:
resp = self._post(payload)
except httpx.HTTPError as exc: # network / timeout
raise LLMError(f"调用大模型失败(网络/超时):{exc.__class__.__name__}") from exc
except (httpx.HTTPError, ValueError, TypeError) as exc: # 网络/超时;代理 URL 或配置错误(如 .env 解析出整行变量名)
dt = time.monotonic() - t0
logger.warning("llm http-error dt=%.1fs %s", dt, exc.__class__.__name__)
if isinstance(exc, httpx.HTTPError):
raise LLMError(f"调用大模型失败(网络/超时):{exc.__class__.__name__}") from exc
# 2026-08-25 实测:代理 URL 带变量名前缀 → httpx 构造期 ValueError → 裸 500
# (此前只捕 httpx.HTTPError)。配置类错误同样转可读 LLMError(PRD §11 失败兜底)
raise LLMError(f"调用大模型失败(配置错误,请检查代理/地址设置):{exc}") from exc
dt = time.monotonic() - t0
if resp.status_code != 200:
logger.warning("llm http %s dt=%.1fs retry-hint=%s", resp.status_code, dt, resp.headers.get("retry-after", ""))
raise LLMError(f"调用大模型失败(HTTP {resp.status_code}):{_readable_error(resp)}")
return message_text(resp.json())
try:
body = resp.json()
except Exception as exc:
raise LLMError("模型未返回内容(可能被截断或拒绝)") from exc
usage = body.get("usage") or {}
# uvicorn 默认只给 uvicorn.* logger 配 handlerroot 的 info 会被吞;
# 观测行用 warning 级别保证进入 stderr 日志(慢链路定位依据)
logger.warning(
"llm ok model=%s dt=%.1fs prompt=%s completion=%s reasoning=%s total_time_ms=%s",
self.model, dt,
usage.get("prompt_tokens"), usage.get("completion_tokens"),
usage.get("reasoning_tokens"), usage.get("total_time"),
)
return message_text(body)
# -- public ------------------------------------------------------------
def complete_json(