[V10.284] 預設關閉 Code Review Hermes LLM scan | code_review_pipeline_service.py
All checks were successful
CD Pipeline / deploy (push) Successful in 1m1s

This commit is contained in:
OoO
2026-05-19 22:34:30 +08:00
parent 880d15b055
commit 0fc96837f4
6 changed files with 95 additions and 4 deletions

View File

@@ -231,6 +231,7 @@ CODE_REVIEW_HERMES_FALLBACK_TIMEOUT=20
CODE_REVIEW_HERMES_NUM_PREDICT=384
CODE_REVIEW_HERMES_MAX_FILES=2
CODE_REVIEW_HERMES_MAX_CHARS=900
CODE_REVIEW_HERMES_LLM_SCAN_ENABLED=false
CODE_REVIEW_AUTO_FIX_ENABLED=true
# [選填] 僅本機開發可設 true正式環境不得允許不安全 internal webhook

View File

@@ -4,6 +4,7 @@
================================================================================
【已完成】
- V10.284 關閉 Code Review Hermes LLM scan 預設路徑Step 2 改 deterministic fast static scan不再讓部署後先卡三段 Ollama timeout若需要 LLM scan 可用 `CODE_REVIEW_HERMES_LLM_SCAN_ENABLED=true` 顯式開啟,仍只走本地矩陣、不走 Gemini。
- V10.283 將 Code Review Hermes scan 收斂為 fast compact prompt預設 2 檔 × 900 字、輸出 384 tokens仍走 GCP-A → GCP-B → 111 本地矩陣,避免部署後 code_review_hermes 先卡三段 timeout。
- V10.282 補齊 Code Review Hermes scan 本地模型矩陣:掃描階段也走 GCP-A `qwen2.5-coder:7b` → GCP-B `gemma3:4b` → 111 `hermes3:latest`,避免 `hermes3` 在三主機各卡 35s 後只留下 errorHermes scan 不會啟用 Gemini。
- V10.281 強化 Code Review OpenClaw 本地備援矩陣:主機順序仍為 GCP-A → GCP-B → 111但改成 GCP-A `qwen2.5-coder:7b`、GCP-B `gemma3:4b`、111 `hermes3:latest`,三段本地 Ollama 全失敗後才允許 Claude/Gemini 備援。
@@ -162,6 +163,7 @@
- Phase 79 candidate queue review archive summary新增 `services/market_intel/candidate_queue_review_archive_summary.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_archive_summary` 與 UI summary 按鈕,在 review completion archive 後整理可供摘要/報表審核的結構化輸入API/UI 不呼叫 LLM、不派送 Telegram、不寫檔、不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不 commit、不掛 scheduler版本同步至 V10.276。
- Phase 80 candidate queue review AI summary preflight新增 `services/market_intel/candidate_queue_review_ai_summary_preflight.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_preflight` 與 UI preflight 按鈕,在 archive summary 後檢查 Ollama-first 三主機級聯與 Gemini 備援邊界API/UI 不呼叫 LLM、不派送 Telegram、不寫檔、不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不 commit、不掛 scheduler版本同步至 V10.278。
- Phase 81 candidate queue review AI summary run package新增 `services/market_intel/candidate_queue_review_ai_summary_run_package.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_run_package` 與 UI package 按鈕,在 AI summary preflight 後整理手動 Ollama 摘要任務包、prompt contract 與輸出 schemaAPI/UI 不呼叫 LLM、不派送 Telegram、不寫 run package、不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不 commit、不掛 scheduler版本同步至 V10.280。
- Phase 82 candidate queue review AI summary output receipt新增 `services/market_intel/candidate_queue_review_ai_summary_output_receipt.py`、POST `/api/market_intel/manual_sample_review/candidate_queue_review_ai_summary_output_receipt` 與 UI receipt 按鈕,在 run package 後驗收人工 Ollama 摘要輸出的 schema、evidence_refs 與 model_routeAPI/UI 不呼叫 LLM、不派送 Telegram、不寫 receipt、不讀 token、不執行 CLI、不更新 review_state、不寫 DB、不 commit、不掛 scheduler版本同步至 V10.282。
- V10.248 補市場情報 390px preview panel QAsample review 工具列改為 textarea + 可換行 action rail移除舊的硬編 8 欄 grid`check_responsive_overflow` 新增 `--screenshot-all`,本機 390x844 `/market_intel` 真頁面 QA 通過且 overflow=0。
- V10.250 補 Code Review Gemini 備援遙測護欄Ollama 主路徑失敗時 `fallback_to` 明確指向 `code_review_openclaw_gemini`測試鎖住「Gemini 不得記成 `code_review_openclaw` 主 caller」AI Calls 觀測台會把 legacy `code_review_openclaw + gemini` 顯示成 Gemini 備援,避免誤判 Gemini-first。
- Schema smoke`tests/test_market_intel_skeleton.py` 檢查 `Base.metadata` 內含 ADR-035 八張 `market_*` tables。

View File

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

View File

@@ -2,7 +2,7 @@
> **最後更新**: 2026-05-19 (台北時間)
> **狀態**: 🟢 四 AI Agent 自動化閉環已落地LLM 路由紅線升級為 Ollama-first 三主機級聯Gemini 僅備援 / 鎖定場景
> **適用版本**: V10.283
> **適用版本**: V10.284
---
@@ -17,8 +17,8 @@
- NemoTron qwen3 dispatch 的 `/api/chat` tool-calling 路徑也必須同一請求最多嘗試三台 Ollama第一台失敗要 `mark_unhealthy()` 後再試下一台,最後才 fallback NIM。
- PPT vision、PPT 文案 final fallback、MCP 離線 final fallback 等特殊 Ollama 路徑也不得只打單一 host如需 `/api/generate`,一律透過 `OllamaService.generate()`
- Code Review pipeline 也必須 Ollama-firstHermes scan 與 OpenClaw assessment 都走 `OllamaService` 三主機 retryGemini telemetry 只能以 `code_review_openclaw_gemini` 出現,表示 Ollama/可選 Claude 備援都失敗後才啟用。
- Code Review 的 OpenClaw assessment 預設使用 `qwen2.5-coder:7b` 與 45s/host timeoutHermes scan 只送 fast compact snippet預設 2 檔、每檔 900 字)並使用 35s primary timeout避免大 prompt 讓三主機依序超時後留下錯誤遙測
- Code Review Hermes scan 也使用同一條本地模型矩陣GCP-A `qwen2.5-coder:7b`、GCP-B `gemma3:4b`、111 `hermes3:latest`;不啟用 Gemini 備援,三段本地掃描失敗時只回空 findings 並交由 OpenClaw 本地矩陣續跑。
- Code Review Hermes scan 預設不呼叫 LLM改用 deterministic fast static scan避免部署後先卡三段 Ollama timeout需要 LLM 掃描時才以 `CODE_REVIEW_HERMES_LLM_SCAN_ENABLED=true` 啟用本地矩陣
- Code Review Hermes LLM scan 啟用時才使用本地模型矩陣GCP-A `qwen2.5-coder:7b`、GCP-B `gemma3:4b`、111 `hermes3:latest`;不啟用 Gemini 備援,三段本地掃描失敗時只回空 findings 並交由 OpenClaw 本地矩陣續跑。
- Code Review OpenClaw assessment 保持主機順序 GCP-A → GCP-B → 111但可使用主機適配本地模型GCP-A `qwen2.5-coder:7b`、GCP-B `gemma3:4b`、111 `hermes3:latest`;三段本地 Ollama 全失敗後才允許雲端備援。
- OpenClaw Telegram Q&A 主路徑也不得綁單一 host`_call_qwen3_qa()` 必須透過 `OllamaService` 跑 GCP-A → GCP-B → 111並把實際落點寫入 `ai_calls.provider`
- OpenClaw Telegram 圖片商品辨識也必須 Ollama-first`_identify_product_name_with_ollama_vision()` 透過 `OllamaService` 嘗試 GCP-A → GCP-B → 111Gemini 只允許以 `openclaw_bot_image_gemini` caller 作為失敗後備援。

View File

@@ -85,6 +85,9 @@ CODE_REVIEW_HERMES_FALLBACK_TIMEOUT = int(os.getenv("CODE_REVIEW_HERMES_FALLBACK
CODE_REVIEW_HERMES_NUM_PREDICT = int(os.getenv("CODE_REVIEW_HERMES_NUM_PREDICT", "384"))
CODE_REVIEW_HERMES_MAX_FILES = int(os.getenv("CODE_REVIEW_HERMES_MAX_FILES", "2"))
CODE_REVIEW_HERMES_MAX_CHARS = int(os.getenv("CODE_REVIEW_HERMES_MAX_CHARS", "900"))
CODE_REVIEW_HERMES_LLM_SCAN_ENABLED = (
os.getenv("CODE_REVIEW_HERMES_LLM_SCAN_ENABLED", "false").lower() == "true"
)
INTERNAL_TOKEN = os.getenv("INTERNAL_WEBHOOK_TOKEN", "")
AUTO_FIX_ENABLED = os.getenv("CODE_REVIEW_AUTO_FIX_ENABLED", "true").lower() == "true"
ALLOW_INSECURE_WEBHOOK = os.getenv("MOMO_ALLOW_INSECURE_INTERNAL_WEBHOOK_FOR_DEV", "").lower() == "true"
@@ -244,6 +247,16 @@ class CodeReviewPipeline:
def _hermes_scan(self, files: Dict[str, str]) -> List[Dict]:
"""走 OllamaService 三主機級聯GCP-A → GCP-B → 111。"""
try:
if not CODE_REVIEW_HERMES_LLM_SCAN_ENABLED:
findings = self._static_code_scan(files)
for f in findings:
sev = f.get("severity", "LOW").lower()
if sev in self.state["severity_summary"]:
self.state["severity_summary"][sev] += 1
self.state["findings"] = findings
self._sync_global()
return findings
compact_files = []
for name, content in list(files.items())[:CODE_REVIEW_HERMES_MAX_FILES]:
clipped = content[:CODE_REVIEW_HERMES_MAX_CHARS]
@@ -367,6 +380,55 @@ class CodeReviewPipeline:
logger.warning("[CodeReview] Hermes 掃描失敗: %s", e)
return []
def _static_code_scan(self, files: Dict[str, str]) -> List[Dict]:
"""Hermes LLM 關閉時的快速 deterministic 掃描,避免部署後卡 Ollama timeout。"""
findings: List[Dict] = []
patterns = [
("HIGH", "security", r"\beval\s*\(", "使用 eval() 有安全風險", "改用安全 parser 或白名單映射"),
("HIGH", "security", r"\bexec\s*\(", "使用 exec() 有安全風險", "移除動態執行或改成明確函式"),
("MEDIUM", "maintainability", r"except\s+Exception\s*:\s*pass\b", "例外被靜默吞掉", "改用 logger.exception 並保留可診斷訊息"),
("MEDIUM", "maintainability", r"except\s*:\s*pass\b", "裸 except 被靜默吞掉", "指定例外類型並記錄錯誤"),
]
request_pattern = re.compile(r"requests\.(get|post|put|delete|patch)\s*\(")
secret_pattern = re.compile(
r"(api[_-]?key|token|password|secret)\s*=\s*['\"][^'\"]{12,}['\"]",
re.IGNORECASE,
)
for path, content in files.items():
for line_no, line in enumerate(content.splitlines(), start=1):
stripped = line.strip()
if request_pattern.search(stripped) and "timeout=" not in stripped:
findings.append({
"severity": "MEDIUM",
"type": "performance",
"file": path,
"line_hint": str(line_no),
"description": "HTTP request 未設定 timeout可能拖住 worker",
"suggestion": "加入明確 timeout 並處理例外",
})
if secret_pattern.search(stripped) and "os.getenv" not in stripped:
findings.append({
"severity": "HIGH",
"type": "security",
"file": path,
"line_hint": str(line_no),
"description": "疑似硬編碼敏感字串",
"suggestion": "改用環境變數或 secret store",
})
for severity, issue_type, pattern, description, suggestion in patterns:
if re.search(pattern, stripped):
findings.append({
"severity": severity,
"type": issue_type,
"file": path,
"line_hint": str(line_no),
"description": description,
"suggestion": suggestion,
})
if len(findings) >= 8:
return findings[:8]
return findings
# ── Step 3OpenClaw 評估 ──────────────────────────────────────────────────
def _openclaw_assess(self, files: Dict[str, str], findings: List[Dict]) -> str:

View File

@@ -211,6 +211,7 @@ def test_code_review_ollama_defaults_use_fast_local_model(monkeypatch):
"CODE_REVIEW_HERMES_NUM_PREDICT",
"CODE_REVIEW_HERMES_MAX_FILES",
"CODE_REVIEW_HERMES_MAX_CHARS",
"CODE_REVIEW_HERMES_LLM_SCAN_ENABLED",
):
monkeypatch.delenv(key, raising=False)
@@ -234,6 +235,7 @@ def test_code_review_ollama_defaults_use_fast_local_model(monkeypatch):
assert svc_mod.CODE_REVIEW_HERMES_NUM_PREDICT == 384
assert svc_mod.CODE_REVIEW_HERMES_MAX_FILES == 2
assert svc_mod.CODE_REVIEW_HERMES_MAX_CHARS == 900
assert svc_mod.CODE_REVIEW_HERMES_LLM_SCAN_ENABLED is False
def test_openclaw_uses_secondary_local_model_before_gemini(monkeypatch):
@@ -304,6 +306,7 @@ def test_openclaw_uses_secondary_local_model_before_gemini(monkeypatch):
def test_hermes_scan_uses_compact_prompt_and_short_timeout(monkeypatch):
"""Hermes scan 只送 compact snippet避免大檔讓三主機各卡 120 秒。"""
monkeypatch.setenv("CODE_REVIEW_HERMES_LLM_SCAN_ENABLED", "true")
monkeypatch.setenv("CODE_REVIEW_HERMES_TIMEOUT", "7")
monkeypatch.setenv("CODE_REVIEW_HERMES_MAX_FILES", "2")
monkeypatch.setenv("CODE_REVIEW_HERMES_MAX_CHARS", "20")
@@ -331,6 +334,29 @@ def test_hermes_scan_uses_compact_prompt_and_short_timeout(monkeypatch):
assert "截斷" in prompt
def test_hermes_scan_defaults_to_static_scan_without_ollama(monkeypatch):
"""預設不打 LLM避免部署後 Hermes scan 卡三段 Ollama timeout。"""
monkeypatch.delenv("CODE_REVIEW_HERMES_LLM_SCAN_ENABLED", raising=False)
_stub_logger(monkeypatch)
svc_mod = _reload_pipeline()
pipeline = _make_pipeline(svc_mod)
findings = pipeline._hermes_scan({
"services/foo.py": (
"import requests\n"
"def risky(payload):\n"
" requests.get('https://example.com')\n"
" return eval(payload)\n"
)
})
descriptions = {finding["description"] for finding in findings}
assert "HTTP request 未設定 timeout可能拖住 worker" in descriptions
assert "使用 eval() 有安全風險" in descriptions
assert pipeline.state["severity_summary"]["high"] == 1
assert pipeline.state["severity_summary"]["medium"] == 1
def test_flag_true_still_uses_ollama_before_claude(monkeypatch):
"""flag=true 也不得跳過 OllamaOllama 成功時 Claude/Gemini 都不呼叫"""
monkeypatch.setenv('CODE_REVIEW_USE_CLAUDE', 'true')