From 702f09e6b75da5a612dac125d48940677e532853 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 5 Oct 2026 18:11:19 +0800 Subject: [PATCH] =?UTF-8?q?fix(vision):=20#2204=20lite=20VLM=E5=85=B3?= =?UTF-8?q?=E9=97=ADthinking=E6=A8=A1=E5=BC=8F=EF=BC=8C=E5=93=8D=E5=BA=94?= =?UTF-8?q?=E4=BB=8E10-12s=E9=99=8D=E5=88=B0<3s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因定位:staging直连测试发现 doubao-seed-2-1-lite-260915 是思考模型, 单次调用产生~520 reasoning_tokens,耗时10-12s,导致fast路径必超时。 修复: 1. vlm_fast_json.py 直接用 httpx 发最小 payload(不走 ai_client 包装), 显式设置 thinking={"type":"disabled"} + reasoning_effort="low" 关闭思考链, 期望响应降至 <3s;若API不支持thinking参数返回400,自动降级重试一次。 2. 超时恢复合理值:lite JSON 8s、OCR 6s、fast总超时8s(关闭thinking后预计<3s,余量充足)。 3. vlm_fallback.py 保持单次pro调用,pro走原ai_client路径(兜底场景对延迟不敏感,45s足够)。 预期:3图<10s/8图<15s目标可达。 --- .../worker_app/tasks/vision/fast_path.py | 6 +- .../worker_app/tasks/vision/vlm_fast_json.py | 88 ++++++++++++++----- 2 files changed, 69 insertions(+), 25 deletions(-) diff --git a/apps/worker/worker_app/tasks/vision/fast_path.py b/apps/worker/worker_app/tasks/vision/fast_path.py index 715eb2194..323ee44ea 100644 --- a/apps/worker/worker_app/tasks/vision/fast_path.py +++ b/apps/worker/worker_app/tasks/vision/fast_path.py @@ -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 = { diff --git a/apps/worker/worker_app/tasks/vision/vlm_fast_json.py b/apps/worker/worker_app/tasks/vision/vlm_fast_json.py index 3b93b6580..c68cf739a 100644 --- a/apps/worker/worker_app/tasks/vision/vlm_fast_json.py +++ b/apps/worker/worker_app/tasks/vision/vlm_fast_json.py @@ -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) -- 2.54.0