V10.577 補 Code Review Ollama preflight
All checks were successful
CD Pipeline / deploy (push) Successful in 1m6s

This commit is contained in:
OoO
2026-06-04 11:28:03 +08:00
parent 3b9448b776
commit 5f7073798a
6 changed files with 114 additions and 1 deletions

View File

@@ -4,6 +4,7 @@
================================================================================
【已完成】
- V10.577 補 Code Review Ollama host preflightOpenClaw 架構評估在 explicit GCP host generate 前先以短 `/api/version` 探測健康度GCP-A 不通時會快速跳 GCP-B不再等 15 秒 generate timeout仍維持 GCP-A/GCP-B 優先、111 預設禁用、Gemini hard-disabled 預設不呼叫。
- V10.576 補 PChome backfill backlog 的型錄 lane counts讓 `/api/ai/pchome-match/backfill/status` 也能回傳 `catalog_variant_review`、`catalog_unit_review`、`catalog_identity_review` 三條操作隊列;同版修正 `OllamaService.generate(allow_111_fallback=False)`,當 lazy resolver 快取到 111 時會強制改試 GCP-A/GCP-B allowlist不再直接 `all 0 hosts failed`,且仍不把長分析推給 111。
- V10.575 拆分 PChome 型錄可比覆核 lane`catalog_comparable` 不再只是一個總數,正式拆成 `catalog_variant_review`(選項/色號/款式待核)、`catalog_unit_review`(入數/檔期/商業條件待核)與 `catalog_identity_review`身份採用待核。Coverage、review queue filter、Dashboard 分段、decision envelope、Webcrumbs host data 都共用同一套 SQL helper 與 metadata仍維持 HITL、不自動寫正式價差讓營運可批次清理最有機會轉成單位價或正式身份的候選。
- V10.574 接上 PChome 型錄/任選可比覆核隊列:沿用 V10.572 的 `catalog_comparable_count` 安全口徑,將高分、無 hard veto、具同品線身份證據但仍有任選/型錄/商業條件待確認的 `true_low_confidence` 候選,拆成獨立 `catalog_comparable` 篩選與 decision envelope。此隊列仍維持 HITL不寫入正式 `competitor_prices`、不算 exact matched並把「型錄可比」與真正「證據不足」分開讓營運可以先批次處理最有機會轉成單位價或正式身份的候選。

View File

@@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
# ==========================================
# 系統版本與路徑
# ==========================================
SYSTEM_VERSION = "V10.576"
SYSTEM_VERSION = "V10.577"
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
public_url = PUBLIC_URL # 用於模板顯示

View File

@@ -104,6 +104,7 @@
- 2026-05-31 起,`V10.506` 新增市場情報 MCP Fetch Candidate Queue Writer Review Decision Approval gate在 review decision 通過後只審核 operator human approval 摘要,要求 decision linkage、approval identity、target table、row count、dedupe keys、`approved_for_writer_preflight` approval result、decision/approval evidence refs、artifact paths、matched row exact-identity/variant/overwrite guard 與 operator confirmation 對齊;仍不讀 token、不執行 CLI、不開 DB、不寫 approval record、不寫 decision record、不更新 review_state、不寫 match result、不補 queue、不掛 scheduler只放行到後續 writer preflight 設計。
- 2026-05-31 起,`V10.509` 新增市場情報 MCP Fetch Candidate Queue Writer Review Decision Approval Writer Preflight gate在 human approval 通過後只審核 operator writer preflight 摘要,要求 approval linkage、writer_preflight_id、target operation、row count、dedupe keys、approved decision 到 target review_state 的逐列映射、decision/approval/preflight evidence refs、matched row exact-identity/variant/overwrite guard 與 operator boundary仍不讀 token、不執行 CLI、不開 DB、不寫 preflight/approval/decision/match、不更新 review_state、不補 queue、不掛 scheduler只放行到後續 CLI review / run package 設計。
- 2026-06-01 起,`V10.566` 新增市場情報 Professional Source Governance gate將 robots/REP、sitemap/lastmod、JSON-LD / schema.org structured data、canonical URL、rate limit、公開資料邊界、provenance、snapshot hash 與 idempotency key 納入 source contract並接上 `/api/market_intel/mcp_professional_source_governance`、UI preview panel、deployment readiness check 與 production smoke target仍不抓外站、不讀 robots/sitemap、不開 DB、不寫檔、不掛 scheduler。
- 2026-06-04 起,`V10.577` Code Review OpenClaw 會在 explicit Ollama host generate 前先做短 `/api/version` preflightGCP-A 不通時快速跳 GCP-B避免 15 秒 timeout 後才降級,且仍不呼叫 Gemini / 111。
- 2026-06-04 起,`V10.576` 修正 GCP-only Ollama retrycaller 禁用 111 fallback 時resolver 若回到 111 會改試 GCP-A/GCP-B allowlist不再讓 Hermes / Code Review 類任務因 resolver 快取到 111 而 `all 0 hosts failed`
- 2026-06-04 起,`V10.575` 拆分 PChome 型錄可比覆核 lane`catalog_comparable` 會依 diagnostic evidence 分成選項/色號、單位/入數與身份採用三條人工處理路徑Dashboard、decision envelope、coverage 與 Webcrumbs host data 使用同一套統計與 HITL guardrail。
- 2026-06-03 起,`V10.574` 新增市場情報 Source Governance → Fetch Target bridge`/api/market_intel/mcp_fetch_target_source_governance_review` 交叉審核 Professional Source Governance 與 MCP Fetch Target Review要求 target `platform_code/source_key` 全部命中已治理 source contract仍不抓外站、不讀 robots/sitemap、不開 DB、不寫檔、不執行 CLI、不掛 scheduler只放行到後續人工 fetch run package review。

View File

@@ -13,6 +13,7 @@
## 📅 詳細更新日誌 (考古存檔)
### 2026-06-01PChome 比價新鮮度操作閉環
- **V10.577 Code Review Ollama host preflight**: OpenClaw 架構評估在 explicit GCP host generate 前先以短 `/api/version` 探測健康度;若 GCP-A 從 188 連線 timeout會快速跳到 GCP-B `gemma3:4b`,避免每次等 primary generate timeout。此 preflight 只作用於 Code Review Ollama-first 路徑,仍維持 111 預設禁用、Gemini hard-disabled 預設不呼叫。
- **V10.576 PChome backlog lane 與 GCP-only Ollama retry 修補**: `/api/ai/pchome-match/backfill/status` 的 coverage 與 operation backlog 同步輸出 `catalog_variant_review``catalog_unit_review``catalog_identity_review`,讓 Dashboard 操作建議可直接跳到三條人工隊列。同版修正 `OllamaService.generate(allow_111_fallback=False)`:若 resolver 已快取到 111會改試尚未嘗試的 GCP-A/GCP-B allowlist不再直接 `all 0 hosts failed`,同時仍避免長分析落到 111。
- **V10.575 PChome 型錄可比覆核 lane 分流**: `catalog_comparable` 進一步拆成 `catalog_variant_review``catalog_unit_review``catalog_identity_review`。Coverage SQL、review queue filter、Dashboard 分段、decision envelope 與 Webcrumbs host data 共用同一套 helper將選項/色號/款式、入數/商業條件、身份採用三種人工閉環路徑分開統計與瀏覽;仍維持 HITL不自動寫正式價差。
- **V10.574 PChome 型錄/任選可比覆核隊列**: 將 V10.572 的 `catalog_comparable_count` 派生口徑正式接進 PChome review queue。高分、無 hard veto、具同品線身份證據但仍有任選/型錄/商業條件待確認的 `true_low_confidence` 會進獨立 `catalog_comparable` 篩選、狀態標籤與 decision envelope真正 `true_low_confidence` 會排除這批候選,避免重複出現在「證據不足」。此變更不放寬 `MIN_MATCH_SCORE`、不寫正式 `competitor_prices`、不算 exact matched只把最有機會人工批次確認的候選變成可操作隊列。

View File

@@ -27,6 +27,7 @@ import threading
from datetime import datetime
from typing import Any, Dict, List, Optional
import requests
from database.manager import get_session
from sqlalchemy import text
# ADR-027Code Review 走 OllamaService 取得三主機級聯 retry。
@@ -42,6 +43,13 @@ def _env_bool(name: str, default: str = "false") -> bool:
return os.getenv(name, default).strip().lower() in {"1", "true", "yes", "on"}
def _env_float(name: str, default: str) -> float:
try:
return float(os.getenv(name, default))
except (TypeError, ValueError):
return float(default)
# ── Pipeline 全域狀態(供前端 polling─────────────────────────────────────
_current_pipeline: Dict[str, Any] = {}
_pipeline_lock = threading.Lock()
@@ -70,6 +78,14 @@ CODE_REVIEW_OLLAMA_FALLBACK_TIMEOUT = int(
CODE_REVIEW_OLLAMA_NUM_PREDICT = int(os.getenv("CODE_REVIEW_OLLAMA_NUM_PREDICT", "384"))
CODE_REVIEW_OLLAMA_KEEP_ALIVE = os.getenv("CODE_REVIEW_OLLAMA_KEEP_ALIVE", "5m")
CODE_REVIEW_ALLOW_111_FALLBACK = _env_bool("CODE_REVIEW_ALLOW_111_FALLBACK", "false")
CODE_REVIEW_OLLAMA_HOST_PREFLIGHT_ENABLED = _env_bool(
"CODE_REVIEW_OLLAMA_HOST_PREFLIGHT_ENABLED",
"true",
)
CODE_REVIEW_OLLAMA_HOST_PREFLIGHT_TIMEOUT = _env_float(
"CODE_REVIEW_OLLAMA_HOST_PREFLIGHT_TIMEOUT",
"2.5",
)
CODE_REVIEW_HERMES_TIMEOUT = int(os.getenv("CODE_REVIEW_HERMES_TIMEOUT", "35"))
CODE_REVIEW_HERMES_PRIMARY_MODEL = os.getenv(
"CODE_REVIEW_HERMES_PRIMARY_MODEL",
@@ -111,6 +127,21 @@ def _aider_allowed_fix_files(files: List[str]) -> List[str]:
return [f for f in files if AIDER_AUTO_FIX_FILE_PATTERN.match(f or "")]
def _code_review_ollama_host_reachable(host: str) -> bool:
"""Short-circuit dead GCP Ollama hosts before a generate timeout."""
if not CODE_REVIEW_OLLAMA_HOST_PREFLIGHT_ENABLED:
return True
try:
resp = requests.get(
f"{str(host or '').rstrip('/')}/api/version",
timeout=CODE_REVIEW_OLLAMA_HOST_PREFLIGHT_TIMEOUT,
)
return resp.status_code == 200
except Exception as exc:
logger.warning("[CodeReview] Ollama host preflight failed host=%s error=%s", host, exc)
return False
# ═══════════════════════════════════════════════════════════════════════════════
# Pipeline Class
# ═══════════════════════════════════════════════════════════════════════════════
@@ -539,6 +570,18 @@ class CodeReviewPipeline:
'timeout_s': timeout_s,
},
) as _ctx:
_ctx.add_meta('host', host)
_ctx.add_meta('host_label', get_host_label(host))
if not _code_review_ollama_host_reachable(host):
last_ollama_error = (
"ollama host preflight failed "
f"host={host} timeout={CODE_REVIEW_OLLAMA_HOST_PREFLIGHT_TIMEOUT}s"
)
_ctx.add_meta('preflight', 'api_version')
_ctx.set_error(last_ollama_error)
if attempt_index == len(ollama_attempts):
_ctx.fallback_to_caller(fallback_caller)
continue
ollama = OllamaService(host=host, model=model_name)
resp = ollama.generate(
prompt=user_prompt,

View File

@@ -28,6 +28,11 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# 共用工具
# ─────────────────────────────────────────────────────────────────────────────
@pytest.fixture(autouse=True)
def _disable_real_code_review_ollama_preflight(monkeypatch):
monkeypatch.setenv("CODE_REVIEW_OLLAMA_HOST_PREFLIGHT_ENABLED", "false")
def _reload_pipeline():
"""重新載入 pipeline 模組(讓 module-level CODE_REVIEW_USE_CLAUDE flag 即時生效)"""
import services.code_review_pipeline_service as svc_mod
@@ -306,6 +311,68 @@ def test_openclaw_uses_secondary_local_model_before_gemini(monkeypatch):
fake_elephant.generate.assert_not_called()
def test_openclaw_preflight_skips_dead_primary_before_generate(monkeypatch):
"""GCP-A preflight 不通時OpenClaw 應直接進 GCP-B避免等 primary generate timeout。"""
monkeypatch.setenv('CODE_REVIEW_USE_CLAUDE', 'false')
monkeypatch.setenv('CODE_REVIEW_OLLAMA_HOST_PREFLIGHT_ENABLED', 'true')
monkeypatch.setenv('GEMINI_API_KEY', 'test-key')
_stub_logger(monkeypatch)
svc_mod = _reload_pipeline()
import services.ollama_service as ollama_mod
monkeypatch.setattr(
svc_mod,
"_code_review_ollama_host_reachable",
lambda host: host == ollama_mod.OLLAMA_HOST_SECONDARY,
)
calls = []
class FakeResp:
success = True
content = "SECONDARY-PREFLIGHT-OK"
error = None
input_tokens = 20
output_tokens = 8
def __init__(self, *, host, model):
self.host = host
self.model = model
class FakeOllama:
def __init__(self, host=None, model=None):
self.host = host
self.model = model
def generate(self, **kwargs):
calls.append({
"host": self.host,
"model": kwargs["model"],
"timeout": kwargs["timeout"],
})
return FakeResp(host=self.host, model=kwargs["model"])
monkeypatch.setattr(ollama_mod, "OllamaService", FakeOllama)
fake_claude = _stub_anthropic(monkeypatch, svc_mod, available=True)
fake_genai, fake_elephant = _stub_gemini_and_elephant(monkeypatch)
pipeline = _make_pipeline(svc_mod)
result = pipeline._openclaw_assess(
files={"services/foo.py": "def x(): pass"},
findings=[],
)
assert result == "SECONDARY-PREFLIGHT-OK"
assert calls == [{
"host": ollama_mod.OLLAMA_HOST_SECONDARY,
"model": "gemma3:4b",
"timeout": 60,
}]
fake_claude.generate.assert_not_called()
fake_genai.GenerativeModel.assert_not_called()
fake_elephant.generate.assert_not_called()
def test_openclaw_skips_111_and_cloud_by_default_when_gcp_pair_fails(monkeypatch):
"""GCP-A/B 都失敗時,預設不把 Code Review 重分析丟給 111 或雲端。"""
monkeypatch.setenv('CODE_REVIEW_USE_CLAUDE', 'false')