From c516b6c1dd32394daf3a6e220416e6ed5d6ec8d0 Mon Sep 17 00:00:00 2001 From: Luongduytoan2006 Date: Thu, 2 Jul 2026 16:04:33 +0700 Subject: [PATCH] =?UTF-8?q?fix(ai):=209router=20fallback=20v=E1=BB=81=20lo?= =?UTF-8?q?cal=20qwen3=20+=20g=E1=BB=99p=20s=E1=BB=91=20=C4=90i=E1=BB=81u?= =?UTF-8?q?=20VBPL=20b=E1=BB=8B=20t=C3=A1ch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - llm.py: khi USE_NINE_ROUTER=true mà 9router lỗi hết retry, rớt thẳng về local qwen3 (OLLAMA_FALLBACK_MODEL) thay vì raise 500. Áp cho cả complete() async lẫn complete_sync(). - vbpl.py: VBPL editor tách số Điều ra nhiều ("Điều 1 3") làm vỡ số hiệu; gộp lại chữ số ngay sau "Điều" trong _strip_html. Co-Authored-By: Claude --- AI/backend_reasoning/src/services/llm.py | 34 +++++++++++++++++------ AI/backend_reasoning/src/services/vbpl.py | 6 ++++ 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/AI/backend_reasoning/src/services/llm.py b/AI/backend_reasoning/src/services/llm.py index 87e5199..6adaa4d 100644 --- a/AI/backend_reasoning/src/services/llm.py +++ b/AI/backend_reasoning/src/services/llm.py @@ -105,9 +105,11 @@ def _build_payload(messages: list, model: str, temperature: float) -> dict: # --- 9router (OpenAI-format) --------------------------------------------------- -# Khi settings.USE_NINE_ROUTER=True, mọi complete*/complete_json* đi qua đây thay -# vì Ollama. Payload "tiếng OpenAI" (KHÔNG think/keep_alive), parse choices[0]; vẫn -# _record(...) để token được đếm (response có usage.{prompt,completion}_tokens). +# Khi settings.USE_NINE_ROUTER=True, 9router là provider ƯU TIÊN của complete*/ +# complete_json*; nếu lỗi hết CHAT_RETRIES thì RỚT thẳng về local qwen3 (máy bạn, +# OLLAMA_BASE_URL) — BỎ QUA mac-mini — thay vì 500. Xem fallthrough trong +# complete()/complete_sync(). Payload "tiếng OpenAI" (KHÔNG think/keep_alive), parse +# choices[0]; vẫn _record(...) để token được đếm (response có usage.{...}_tokens). def _openai_payload(messages: list, model: str, temperature: float) -> dict: # max_tokens = GIỚI HẠN OUTPUT (không phải context window). Groq free-tier chặn # request có max_tokens lớn (qwen3-32b 413 khi =8192) → dùng NINE_ROUTER_MAX_TOKENS @@ -183,13 +185,20 @@ async def complete( # [LLM-TRACE] log gọn input/output mỗi lượt gọi LLM để xem "đưa gì vào, trả ra gì". _log(f"\n{'>'*70}\n[LLM-INPUT] system={(system or '')[:200]!r}\n[LLM-INPUT] user=\n{prompt[:2500]}\n{'>'*70}") + # 9router ưu tiên; lỗi hết retry → rớt THẲNG về local qwen3 (bỏ qua mac-mini). if settings.USE_NINE_ROUTER: - out = await _call_openai(messages, settings.NINE_ROUTER_MODEL, temperature) - _log(f"\n{'<'*70}\n[LLM-OUTPUT] (9router {settings.NINE_ROUTER_MODEL})\n{out[:2500]}\n{'<'*70}") - return out + try: + out = await _call_openai(messages, settings.NINE_ROUTER_MODEL, temperature) + _log(f"\n{'<'*70}\n[LLM-OUTPUT] (9router {settings.NINE_ROUTER_MODEL})\n{out[:2500]}\n{'<'*70}") + return out + except Exception as e: # noqa: BLE001 — 9router chết → local qwen3 + _log(f"[LLM-FALLBACK] 9router lỗi hết retry → local qwen3 ({settings.OLLAMA_FALLBACK_MODEL}): {e}") + providers = [(settings.OLLAMA_BASE_URL, settings.OLLAMA_FALLBACK_MODEL)] + else: + providers = settings.CHAT_PROVIDERS last_err: Optional[Exception] = None - for base_url, model in settings.CHAT_PROVIDERS: + for base_url, model in providers: payload = _build_payload(messages, model, temperature) for attempt in range(1, settings.CHAT_RETRIES + 1): try: @@ -220,11 +229,18 @@ def complete_sync(prompt: str, *, system: Optional[str] = None, temperature: flo messages.append({"role": "system", "content": system}) messages.append({"role": "user", "content": prompt}) + # 9router ưu tiên; lỗi hết retry → rớt THẲNG về local qwen3 (bỏ qua mac-mini). if settings.USE_NINE_ROUTER: - return _call_openai_sync(messages, settings.NINE_ROUTER_MODEL, temperature) + try: + return _call_openai_sync(messages, settings.NINE_ROUTER_MODEL, temperature) + except Exception as e: # noqa: BLE001 — 9router chết → local qwen3 + _log(f"[LLM-FALLBACK] 9router lỗi hết retry → local qwen3 ({settings.OLLAMA_FALLBACK_MODEL}): {e}") + providers = [(settings.OLLAMA_BASE_URL, settings.OLLAMA_FALLBACK_MODEL)] + else: + providers = settings.CHAT_PROVIDERS last_err: Optional[Exception] = None - for base_url, model in settings.CHAT_PROVIDERS: + for base_url, model in providers: payload = _build_payload(messages, model, temperature) for attempt in range(1, settings.CHAT_RETRIES + 1): try: diff --git a/AI/backend_reasoning/src/services/vbpl.py b/AI/backend_reasoning/src/services/vbpl.py index 39955f7..2bea45a 100644 --- a/AI/backend_reasoning/src/services/vbpl.py +++ b/AI/backend_reasoning/src/services/vbpl.py @@ -27,6 +27,10 @@ # Multispace gọn lại nhưng GIỮ xuống dòng (UnitTreeBuilder cắt Điều theo dòng). _MULTISPACE = re.compile(r"[ \t ]+") _MULTIBLANK = re.compile(r"\n{3,}") +# VBPL editor hay tách SỐ Điều ra nhiều rời ("Điều 1"+"3" → "Điều 1 3" sau khi +# get_text(' ') chèn space). Gộp lại chữ số bị tách NGAY SAU "Điều" để không vỡ số Điều. +# Chỉ đụng cụm "Điều ..." — an toàn, không chạm nội dung khác. +_ART_NUM_SPLIT = re.compile(r"(Điều)\s+(\d(?:\s+\d){1,3})\b", re.I) def parse_vbpl_url(url: str) -> Optional[str]: @@ -127,6 +131,8 @@ def _strip_html(html: str) -> str: if el.find(_BLOCK_TAGS): continue # block cha — để block con (lá) xử, tránh trùng nội dung txt = _MULTISPACE.sub(" ", el.get_text(" ", strip=True)).strip() + # VBPL tách số Điều ra rời → "Điều 1 3" → gộp lại "Điều 13". + txt = _ART_NUM_SPLIT.sub(lambda m: m.group(1) + " " + m.group(2).replace(" ", ""), txt) if txt: leaf_lines.append(txt) text = "\n".join(leaf_lines)