fix: 诊断提速与稳定性修复(reasoning 2048→512、输出精简、上限16000→8000、loading提示、观测日志+配置错误可读)
This commit is contained in:
@@ -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,模型烧满全部思考 token;DeepSeek 思考
|
||||
# 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 tokens(prompt 已压紧凑);
|
||||
# 模型偶发啰嗦打满 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)。
|
||||
# OpenRouter:effort 与 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 配 handler,root 的 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(
|
||||
|
||||
Reference in New Issue
Block a user