fix(ai): classify empty PixelRAG VLM offers as platform barriers

This commit is contained in:
ogt
2026-07-10 00:41:39 +08:00
parent 2059ab3cc2
commit 9c7eb6f895
5 changed files with 87 additions and 3 deletions

View File

@@ -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不得拿來當最新版本真相。
================================================================================

View File

@@ -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 # 用於模板顯示

View File

@@ -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` preflightsmoke 預設仍不呼叫模型。`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 已授權。 |

View File

@@ -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:

View File

@@ -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