fix(ai): classify empty PixelRAG VLM offers as platform barriers
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
python scripts/ops/check_production_version_truth.py
|
||||
|
||||
目前最新版本仍以 production `https://mo.wooo.work/health` readback 為準。
|
||||
本輪 source target 為 `V10.755`;部署完成前不得宣稱正式環境已是 `V10.755`。
|
||||
本輪 source target 為 `V10.756`;部署完成前不得宣稱正式環境已是 `V10.756`。
|
||||
舊 iCloud checkout 不是 Gitea dev worktree,不得拿來當最新版本真相。
|
||||
|
||||
================================================================================
|
||||
|
||||
@@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
|
||||
# ==========================================
|
||||
# 系統版本與路徑
|
||||
# ==========================================
|
||||
SYSTEM_VERSION = "V10.755"
|
||||
SYSTEM_VERSION = "V10.756"
|
||||
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
||||
public_url = PUBLIC_URL # 用於模板顯示
|
||||
|
||||
|
||||
@@ -885,6 +885,7 @@ POSTGRES_HOST=momo-db
|
||||
| 2026-07-10 | PixelRAG VLM execute preflight 必須驗證 generate route | V10.753 起 `/api/ai-automation/pixelrag-vlm-route-readiness?probe_generate=true` 與 `scripts/ops/report_pixelrag_vlm_route_readiness.py --probe-generate` 可對已安裝候選模型執行極小 `/api/generate` preflight;smoke 預設仍不呼叫模型。`run_pixelrag_vlm_replay_worker.py --execute` 預設先執行 generate preflight,若 GCP-A direct / 110 proxy timeout、GCP-B candidate 雖安裝但 generate 不健康,worker 必須在送入 screenshot tiles 前寫 `model_route_not_ready` artifact receipt,輸出 `tag_model_route_ready`、`generate_route_ready`、`route_model_call_performed` 與 `tile_model_call_performed=false`,下一步固定為 `repair_ollama_vlm_generate_runtime_or_proxy_timeout`;不得把 tags 可見誤當 VLM 可執行。 |
|
||||
| 2026-07-10 | PixelRAG VLM tile generate 必須綁定 preflight 選出的 host / exact model | V10.754 起 `run_pixelrag_vlm_replay_worker.py --execute` 若 route readiness 已輸出 `candidate_host`,tile VLM generate 必須使用該 approved host 與 exact `candidate_model` 直呼 `/api/generate`,不得重新進入全域 Ollama resolver、不得因 111 fallback 規則把 VLM 模型降級成文字模型、不得再出現 preflight 選 111 但 tile generate tried GCP-B/GCP-A/111 的 route drift。receipt 必須輸出 `route_candidate_host`,模型錯誤時下一步為 `repair_ollama_vlm_generate_runtime_or_proxy_timeout`,仍然不寫 DB、不寫正式價格表。 |
|
||||
| 2026-07-10 | PixelRAG VLM no-probe 監控不得把 unknown 顯示成 failed | V10.755 起 smoke / scheduled-health 的 `pixelrag_vlm_route_readiness.details.generate_route_ready` 在 `generate_probe_performed=false` 時必須保留為 `null`,表示尚未做 `/api/generate` preflight;不得把未探測狀態轉成 `false`,避免 UI 或監控把 tags-only readiness 誤讀為 generate failure。 |
|
||||
| 2026-07-10 | PixelRAG VLM 空欄位與 Shopee language interstitial 必須自動轉 platform probe | V10.756 起 VLM prompt 明確把 language / region / app-download / landing / loading / logo-only / cookie consent pages 視為非商品卡;若 VLM 回傳所有 offer fields 皆空且 required title / price 缺失,worker 會標記 `non_product_or_interstitial_detected=true`,receipt 下一步改為 `run_platform_probe_or_use_structured_api`,不得反覆要求人工審核或無限 rerun VLM。 |
|
||||
| 2026-06-29 | PChome DB apply 授權 lane 必須先通過 no-write guard / decision preflight / decision closeout / issuer gate / signing-decision preflight / signing-decision closeout / signing-issuer guard | V10.725 的 PChome mapping backlog auto-policy 已新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-lane-guard`、`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-decision-preflight`、`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-decision-closeout`、`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-issuer-gate`、`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-decision-preflight`、`/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-decision-closeout` 與 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-issuer-guard`;這些 endpoint 只驗證 final exact request package、same-run production truth requirement、secret rejection、rollback boundary、lane entry requirements、decision input requirements、rejection policy、post-apply verifier、future authorization decision package、final nonsecret authorization envelope、signing decision preflight inputs、unsigned signing decision package 與 signable request boundary,不讀 secret、不執行 shell/SQL、不寫 DB,也不簽發 database apply authorization。 |
|
||||
| 2026-06-29 | PChome DB apply 授權簽署發行者 lane 必須先產出 final signable request package | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-issuer-closeout`;此 endpoint 只把 signing-issuer guard 的 signable request boundary 收斂成 final signable request package 與 closeout contract,確認 fresh production truth、post-apply verifier、migration hash、secret boundary 與 no-side-effect checks,不讀 secret、不簽發 authorization、不執行 shell/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
| 2026-06-29 | PChome DB apply 授權簽署執行 lane 必須先通過 operator-held secret boundary preflight | V10.725 的 PChome mapping backlog auto-policy 新增 `/api/ai/pchome-growth/mapping-backlog/auto-policy-db-apply-authorization-signing-execution-preflight`;此 endpoint 只把 final signable request package 轉成 future signing execution preflight package、operator-held secret boundary contract、nonsecret signing inputs、command-shape preview、rollback boundary 與 abort conditions,不讀 secret、不接受 plaintext secret、不簽發 authorization、不執行 shell/SQL、不寫 DB,也不代表正式 DB apply 已授權。 |
|
||||
|
||||
@@ -153,6 +153,8 @@ def _prompt_for_item(item: Mapping[str, Any]) -> str:
|
||||
"Use only visible tile evidence and cite evidence_refs like tile:1.\n"
|
||||
"If the tile is access denied, captcha, login, traffic verification, or not a product/search card, "
|
||||
"set blocked_page_detected=true and leave product fields empty.\n"
|
||||
"Language selection, region selection, app-download, landing, loading, logo-only, or cookie consent "
|
||||
"pages are not product/search cards; set blocked_page_detected=true for them.\n"
|
||||
"Required JSON schema:\n"
|
||||
"{\n"
|
||||
' "blocked_page_detected": false,\n'
|
||||
@@ -183,6 +185,7 @@ def _validate_model_payload(
|
||||
missing_required: list[str] = []
|
||||
field_evidence_missing: list[str] = []
|
||||
low_confidence_fields: list[str] = []
|
||||
present_field_count = 0
|
||||
blocked_detected = bool(parsed.get("blocked_page_detected"))
|
||||
|
||||
for contract in list(item.get("field_contract") or []):
|
||||
@@ -198,6 +201,8 @@ def _validate_model_payload(
|
||||
confidence = 0.0
|
||||
min_confidence = float(contract.get("min_confidence") or DEFAULT_CONFIDENCE_THRESHOLD)
|
||||
present = _field_value_present(value)
|
||||
if present:
|
||||
present_field_count += 1
|
||||
if present and not evidence_refs:
|
||||
field_evidence_missing.append(field_name)
|
||||
if present and confidence < min_confidence:
|
||||
@@ -213,14 +218,22 @@ def _validate_model_payload(
|
||||
for field in declared_missing:
|
||||
if field not in missing_required:
|
||||
missing_required.append(field)
|
||||
non_product_or_interstitial_detected = (
|
||||
not blocked_detected
|
||||
and present_field_count == 0
|
||||
and bool(missing_required)
|
||||
)
|
||||
|
||||
return {
|
||||
"blocked_page_detected": blocked_detected,
|
||||
"non_product_or_interstitial_detected": non_product_or_interstitial_detected,
|
||||
"present_field_count": present_field_count,
|
||||
"missing_required_fields": missing_required,
|
||||
"field_evidence_missing": field_evidence_missing,
|
||||
"low_confidence_fields": low_confidence_fields,
|
||||
"valid_for_identity_matcher_replay": (
|
||||
not blocked_detected
|
||||
and not non_product_or_interstitial_detected
|
||||
and not missing_required
|
||||
and not field_evidence_missing
|
||||
),
|
||||
@@ -501,9 +514,12 @@ def _execute_item(
|
||||
missing_required = list(validation.get("missing_required_fields") or [])
|
||||
evidence_missing = list(validation.get("field_evidence_missing") or [])
|
||||
blocked_detected = bool(validation.get("blocked_page_detected"))
|
||||
non_product_or_interstitial = bool(
|
||||
validation.get("non_product_or_interstitial_detected")
|
||||
)
|
||||
status = "executed_ok"
|
||||
next_action = "run_identity_matcher_replay_then_promotion_gate"
|
||||
if blocked_detected:
|
||||
if blocked_detected or non_product_or_interstitial:
|
||||
status = "executed_warning"
|
||||
next_action = "run_platform_probe_or_use_structured_api"
|
||||
elif missing_required or evidence_missing:
|
||||
|
||||
@@ -177,6 +177,73 @@ def test_pixelrag_vlm_replay_worker_writes_model_error_receipt(tmp_path, monkeyp
|
||||
assert receipt["next_machine_action"] == "repair_ollama_vlm_runtime_or_model_route"
|
||||
|
||||
|
||||
def test_pixelrag_vlm_replay_worker_empty_fields_routes_to_platform_probe(tmp_path, monkeypatch):
|
||||
from services import pixelrag_vlm_replay_worker_service as service
|
||||
|
||||
_write_receipt(
|
||||
tmp_path,
|
||||
platform="shopee_tw",
|
||||
manifest_id="shopee-language",
|
||||
title="蝦皮購物 | 花得更少買得更好",
|
||||
url="https://shopee.tw/search?keyword=sunscreen",
|
||||
)
|
||||
|
||||
class FakeOllama:
|
||||
def __init__(self, model):
|
||||
self.model = model
|
||||
|
||||
def generate(self, *args, **kwargs):
|
||||
return SimpleNamespace(
|
||||
success=True,
|
||||
content=json.dumps({
|
||||
"blocked_page_detected": False,
|
||||
"fields": {
|
||||
"title": {"value": None, "confidence": 0.0, "evidence_refs": []},
|
||||
"price": {"value": None, "confidence": 0.0, "evidence_refs": []},
|
||||
"currency": {"value": None, "confidence": 0.0, "evidence_refs": []},
|
||||
},
|
||||
"quality": {
|
||||
"overall_confidence": 0.0,
|
||||
"missing_required_fields": [],
|
||||
"requires_identity_matcher_replay": True,
|
||||
"requires_promotion_gate": True,
|
||||
},
|
||||
"notes": [],
|
||||
}),
|
||||
model="minicpm-v:latest",
|
||||
error=None,
|
||||
total_duration=2.0,
|
||||
host="http://192.168.0.111:11434",
|
||||
input_tokens=10,
|
||||
output_tokens=40,
|
||||
)
|
||||
|
||||
monkeypatch.setattr(service, "OllamaService", FakeOllama)
|
||||
payload = service.run_pixelrag_ollama_vlm_replay_worker(
|
||||
artifact_root=tmp_path,
|
||||
output_root=tmp_path / "receipts",
|
||||
platform="shopee_tw",
|
||||
execute=True,
|
||||
write_receipt=True,
|
||||
tile_limit=4,
|
||||
auto_select_model=False,
|
||||
)
|
||||
|
||||
assert payload["status"] == "warning"
|
||||
assert payload["summary"]["executed_warning_count"] == 1
|
||||
assert payload["summary"]["required_field_missing_count"] == 2
|
||||
assert payload["next_machine_action"] == "rerun_vlm_replay_with_more_tiles_or_platform_probe"
|
||||
item = payload["worker_items"][0]
|
||||
assert item["worker_status"] == "executed_warning"
|
||||
assert item["next_machine_action"] == "run_platform_probe_or_use_structured_api"
|
||||
assert item["validation"]["present_field_count"] == 0
|
||||
assert item["validation"]["non_product_or_interstitial_detected"] is True
|
||||
assert item["validation"]["valid_for_identity_matcher_replay"] is False
|
||||
receipt_path = tmp_path / "receipts" / "shopee_tw" / "shopee-language" / "vlm_replay_receipt.json"
|
||||
receipt = json.loads(receipt_path.read_text(encoding="utf-8"))
|
||||
assert receipt["next_machine_action"] == "run_platform_probe_or_use_structured_api"
|
||||
|
||||
|
||||
def test_pixelrag_vlm_replay_worker_auto_selects_installed_candidate(tmp_path, monkeypatch):
|
||||
from services import pixelrag_vlm_replay_worker_service as service
|
||||
|
||||
|
||||
Reference in New Issue
Block a user