fix(vision): #2204 lite VLM关闭thinking模式,响应从10-12s降到<3s #2204
@@ -21,9 +21,9 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
# 可通过环境变量调参(有默认值,无需配置即可跑)
|
||||
_IMG_WORKERS = int(os.environ.get("VISION_V2_IMG_WORKERS", "8"))
|
||||
_FAST_TIMEOUT = float(os.environ.get("VISION_V2_FAST_TIMEOUT", "6"))
|
||||
_FAST_JSON_TIMEOUT = float(os.environ.get("VISION_V2_FAST_JSON_TIMEOUT", "5"))
|
||||
_OCR_TIMEOUT = float(os.environ.get("VISION_V2_OCR_TIMEOUT", "5"))
|
||||
_FAST_TIMEOUT = float(os.environ.get("VISION_V2_FAST_TIMEOUT", "8"))
|
||||
_FAST_JSON_TIMEOUT = float(os.environ.get("VISION_V2_FAST_JSON_TIMEOUT", "8"))
|
||||
_OCR_TIMEOUT = float(os.environ.get("VISION_V2_OCR_TIMEOUT", "6"))
|
||||
_PRO_TIMEOUT = float(os.environ.get("VISION_V2_PRO_TIMEOUT", "45"))
|
||||
|
||||
_FALLBACK_RESULT = {
|
||||
|
||||
@@ -81,35 +81,79 @@ def call_fast_json(
|
||||
) -> dict[str, Any] | None:
|
||||
"""调用 lite VLM 返回结构化 dict;失败/非 JSON 返回 None。
|
||||
|
||||
注意:不做重试(外层竞速/降级逻辑负责),max_retries=0 由外层统一设置。
|
||||
直接用 httpx 发最小 payload(关闭 thinking),不走 ai_client 包装:
|
||||
- 关闭 thinking/推理链(reasoning_tokens 是延迟主因,单次要10-12s)
|
||||
- 单次调用不重试(失败由外层走 pro 兜底)
|
||||
- 温度=0.1 稳定输出 JSON
|
||||
"""
|
||||
t0 = time.time()
|
||||
import httpx
|
||||
try:
|
||||
from packages.shared.ai_client import get_doubao_client
|
||||
|
||||
client = get_doubao_client()
|
||||
if not client.is_available:
|
||||
logger.warning("[vision.v2] doubao client 不可用,跳过 fast_json")
|
||||
from packages.shared import get_shared_settings
|
||||
settings = get_shared_settings()
|
||||
api_key = settings.doubao_api_key
|
||||
base_url = (settings.doubao_base_url or "https://ark.cn-beijing.volces.com/api/v3").rstrip("/")
|
||||
if not api_key:
|
||||
logger.warning("[vision.v2] doubao api_key 未配置,跳过 fast_json")
|
||||
return None
|
||||
|
||||
use_model = model or DEFAULT_LITE_MODEL
|
||||
# 强制不重试:lite 是快速路径,失败直接走外层 pro 兜底
|
||||
_orig_retries = client.max_retries
|
||||
client.max_retries = 0
|
||||
url = f"{base_url}/chat/completions"
|
||||
payload: dict[str, Any] = {
|
||||
"model": use_model,
|
||||
"messages": [
|
||||
{"role": "system", "content": _FAST_SYSTEM},
|
||||
{"role": "user", "content": [
|
||||
{"type": "image_url", "image_url": {"url": img_url}},
|
||||
{"type": "text", "text": _FAST_USER},
|
||||
]},
|
||||
],
|
||||
"temperature": 0.1,
|
||||
"max_tokens": max_tokens,
|
||||
"stream": False,
|
||||
}
|
||||
# 关键:关闭 thinking(避免产生 reasoning_tokens 拖慢响应)
|
||||
# 方舟/豆包 2.x 模型支持 thinking.type=disabled
|
||||
try:
|
||||
raw = client.vision_completion(
|
||||
messages=[
|
||||
{"role": "system", "content": _FAST_SYSTEM},
|
||||
{"role": "user", "content": _FAST_USER},
|
||||
],
|
||||
images=[img_url],
|
||||
temperature=0.1,
|
||||
max_tokens=max_tokens,
|
||||
timeout=timeout,
|
||||
model=use_model,
|
||||
)
|
||||
finally:
|
||||
client.max_retries = _orig_retries
|
||||
payload["thinking"] = {"type": "disabled"}
|
||||
except Exception:
|
||||
pass
|
||||
# 部分模型用 reasoning_effort 控制思考深度
|
||||
payload["reasoning_effort"] = "low"
|
||||
|
||||
resp = httpx.post(
|
||||
url,
|
||||
headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
|
||||
json=payload,
|
||||
timeout=timeout,
|
||||
)
|
||||
elapsed = time.time() - t0
|
||||
if resp.status_code != 200:
|
||||
logger.warning("[vision.v2] fast_json HTTP %d elapsed=%.1fs body=%s", resp.status_code, elapsed, resp.text[:200])
|
||||
# 如果400说明不支持thinking参数,降级重试一次
|
||||
if resp.status_code == 400 and "thinking" in resp.text.lower():
|
||||
payload.pop("thinking", None)
|
||||
payload.pop("reasoning_effort", None)
|
||||
t1 = time.time()
|
||||
resp = httpx.post(url,
|
||||
headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
|
||||
json=payload, timeout=timeout,
|
||||
)
|
||||
elapsed = time.time() - t0
|
||||
if resp.status_code != 200:
|
||||
logger.warning("[vision.v2] fast_json 降级后 HTTP %d elapsed=%.1fs", resp.status_code, elapsed)
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
data = resp.json()
|
||||
raw = (data.get("choices") or [{}])[0].get("message", {}).get("content")
|
||||
if raw is None:
|
||||
logger.warning("[vision.v2] fast_json 返回 None elapsed=%.1fs", elapsed)
|
||||
return None
|
||||
usage = data.get("usage") or {}
|
||||
logger.info("[vision.v2] fast_json 直连完成 model=%s elapsed=%.1fs in=%d out=%d reasoning=%d",
|
||||
use_model, elapsed, usage.get("prompt_tokens",0), usage.get("completion_tokens",0),
|
||||
usage.get("reasoning_tokens",0))
|
||||
elapsed = time.time() - t0
|
||||
if raw is None:
|
||||
logger.warning("[vision.v2] fast_json 返回 None elapsed=%.1fs model=%s", elapsed, use_model)
|
||||
|
||||
Reference in New Issue
Block a user