This commit is contained in:
@@ -320,7 +320,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
|
||||
# ==========================================
|
||||
# 系統版本與路徑
|
||||
# ==========================================
|
||||
SYSTEM_VERSION = "V10.269"
|
||||
SYSTEM_VERSION = "V10.270"
|
||||
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
||||
public_url = PUBLIC_URL # 用於模板顯示
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
> **最後更新**: 2026-05-19 (台北時間)
|
||||
> **狀態**: 🟢 四 AI Agent 自動化閉環已落地;LLM 路由紅線升級為 Ollama-first 三主機級聯,Gemini 僅備援 / 鎖定場景
|
||||
> **適用版本**: V10.269
|
||||
> **適用版本**: V10.270
|
||||
|
||||
---
|
||||
|
||||
@@ -107,7 +107,7 @@ SQL漏斗(~300筆)
|
||||
- ElephantAlpha 使用 NVIDIA NIM hosted API;production 預設模型為 `nvidia/llama-3.3-nemotron-super-49b-v1.5`,`ELEPHANT_ALPHA_FALLBACK_MODELS` 需保留至少一個可呼叫備援;403/404、408/409/425/429、5xx、timeout 與 connection error 必須嘗試下一個模型。
|
||||
- ElephantAlpha L3 HITL 只允許發送有實證、可審核、可行動的升級告警;價格類 trigger 無 Hermes 具體威脅時,只記錄 suppressed escalation telemetry 與 cooldown,不寫 pending `human_review`,不發 Telegram 空告警。
|
||||
- `resource_optimization` 不再交給 LLM 生成「預期效益 / 已執行」敘事。此 trigger 必須先由程式量測 `action_plans` backlog、P1/P2 數、pending_review、逾時項目與 CPU load;只有 CPU 達門檻、P1/P2 積壓或逾時積壓才發 Telegram「資源壓力告警」。單純 queue 大但 CPU 正常只記錄 telemetry,不派發 Hermes/NemoTron、不宣稱 48 小時效益。
|
||||
- `resource_optimization` 會先執行 `ActionPlanHygieneService` 清理過期噪音:只關閉超過 72 小時的 `code_review_fix` / `openclaw_recommendation` 類 advisory action_plans,將狀態改為 `auto_disabled` 或 `rejected` 並寫入 `metadata_json.hygiene_history`;不刪資料、不碰 NemoTron 業務行動。
|
||||
- `resource_optimization` 會先執行 `ActionPlanHygieneService` 清理過期噪音:只關閉超過 72 小時的 `code_review_fix` / `openclaw_recommendation` 類 advisory action_plans,以及 NemoTron `direct_response/reply_simple` 舊聊天回覆計畫;將狀態改為 `auto_disabled` 或 `rejected` 並寫入 `metadata_json.hygiene_history`。不刪資料,也不碰 NemoTron human_review / pricing / tool action 類業務行動。
|
||||
- OpenClaw/Hermes embedding 優先呼叫 Ollama `/api/embed`,只在舊節點不支援時 fallback `/api/embeddings`;timeout 由 `EMBEDDING_TIMEOUT` / `OLLAMA_EMBED_TIMEOUT` 控制。
|
||||
- PPT 自動產線由 `momo-scheduler` 依節奏執行 `run_ppt_auto_generation_task(schedule_kind)`:每日 20:30 產日報、週一 20:40 產週報/市場情報、每月 1 日 20:50 產月報與管理型簡報、季初 21:00 產季報、半年初 21:10 產半年報、年初 21:20 產年報,再交給 22:00 `ppt_vision_audit` 做視覺審核;每次嘗試會寫入 `ppt_generation_runs`,`/observability/ppt_audit_history` 以精準參數檢查目標版本是否已產生,並可用 `/observability/ppt_audit/generate_missing` 手動補齊缺漏,總開關為 `PPT_AUTO_GENERATION_ENABLED`。PPT vision 需 `PPT_VISION_ENABLED=true` 與容器內 LibreOffice;`/observability/ppt_audit_file/<filename>` 會把 PPTX 轉成 PDF 快取供站內線上預覽,原始 PPTX 仍保留下載。
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ CLOSABLE_STATUSES = frozenset({"pending", "auto_pending", "pending_review"})
|
||||
SOURCE_TARGET_STATUS = {
|
||||
"code_review_fix": "auto_disabled",
|
||||
"code_review_pipeline": "auto_disabled",
|
||||
"nemotron_direct_response": "auto_disabled",
|
||||
"openclaw_recommendation": "rejected",
|
||||
"openclaw": "rejected",
|
||||
}
|
||||
@@ -51,15 +52,6 @@ def _coerce_datetime(value: Any) -> Optional[datetime]:
|
||||
return None
|
||||
|
||||
|
||||
def _source_for_row(row: Any) -> str:
|
||||
return str(
|
||||
_row_get(row, "action_type")
|
||||
or _row_get(row, "plan_type")
|
||||
or _row_get(row, "created_by")
|
||||
or "unknown"
|
||||
)
|
||||
|
||||
|
||||
def _parse_metadata(raw: Any) -> Dict[str, Any]:
|
||||
if isinstance(raw, dict):
|
||||
return dict(raw)
|
||||
@@ -72,6 +64,39 @@ def _parse_metadata(raw: Any) -> Dict[str, Any]:
|
||||
return {"legacy_metadata_raw": str(raw)[:1000]}
|
||||
|
||||
|
||||
def _parse_payload(raw: Any) -> Dict[str, Any]:
|
||||
if isinstance(raw, dict):
|
||||
return dict(raw)
|
||||
if not raw:
|
||||
return {}
|
||||
try:
|
||||
parsed = json.loads(str(raw))
|
||||
return parsed if isinstance(parsed, dict) else {}
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def _source_for_row(row: Any) -> str:
|
||||
created_by = str(_row_get(row, "created_by") or "")
|
||||
if created_by == "nemotron":
|
||||
payload = _parse_payload(_row_get(row, "payload"))
|
||||
actions = payload.get("action_plan") if isinstance(payload.get("action_plan"), list) else []
|
||||
is_direct_response = payload.get("dispatch_to") == "direct_response"
|
||||
is_reply_only = bool(actions) and all(
|
||||
isinstance(action, dict) and action.get("action") == "reply_simple"
|
||||
for action in actions
|
||||
)
|
||||
if is_direct_response or is_reply_only:
|
||||
return "nemotron_direct_response"
|
||||
|
||||
return str(
|
||||
_row_get(row, "action_type")
|
||||
or _row_get(row, "plan_type")
|
||||
or created_by
|
||||
or "unknown"
|
||||
)
|
||||
|
||||
|
||||
def build_action_plan_hygiene_preview(
|
||||
rows: Iterable[Any],
|
||||
*,
|
||||
@@ -140,12 +165,12 @@ class ActionPlanHygieneService:
|
||||
try:
|
||||
rows = session.execute(text("""
|
||||
SELECT id, status, priority, created_at, action_type, plan_type,
|
||||
created_by, description, metadata_json
|
||||
created_by, description, metadata_json, payload
|
||||
FROM action_plans
|
||||
WHERE status IN ('pending', 'auto_pending', 'pending_review')
|
||||
AND (
|
||||
action_type IN ('code_review_fix', 'openclaw_recommendation')
|
||||
OR created_by IN ('code_review_pipeline', 'openclaw')
|
||||
OR created_by IN ('code_review_pipeline', 'openclaw', 'nemotron')
|
||||
)
|
||||
ORDER BY created_at ASC
|
||||
""")).fetchall()
|
||||
@@ -163,12 +188,12 @@ class ActionPlanHygieneService:
|
||||
try:
|
||||
rows = session.execute(text("""
|
||||
SELECT id, status, priority, created_at, action_type, plan_type,
|
||||
created_by, description, metadata_json
|
||||
created_by, description, metadata_json, payload
|
||||
FROM action_plans
|
||||
WHERE status IN ('pending', 'auto_pending', 'pending_review')
|
||||
AND (
|
||||
action_type IN ('code_review_fix', 'openclaw_recommendation')
|
||||
OR created_by IN ('code_review_pipeline', 'openclaw')
|
||||
OR created_by IN ('code_review_pipeline', 'openclaw', 'nemotron')
|
||||
)
|
||||
ORDER BY created_at ASC
|
||||
""")).fetchall()
|
||||
|
||||
@@ -38,12 +38,40 @@ def test_action_plan_hygiene_preview_closes_only_stale_advisory_sources():
|
||||
"action_type": "openclaw_recommendation",
|
||||
"description": "fresh advisory",
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"status": "pending",
|
||||
"priority": 3,
|
||||
"created_at": now - timedelta(hours=110),
|
||||
"created_by": "nemotron",
|
||||
"payload": {
|
||||
"dispatch_to": "direct_response",
|
||||
"action_plan": [{"action": "reply_simple"}],
|
||||
},
|
||||
"description": "old chat response",
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"status": "pending",
|
||||
"priority": 3,
|
||||
"created_at": now - timedelta(hours=110),
|
||||
"created_by": "nemotron",
|
||||
"payload": {
|
||||
"dispatch_to": "human_review",
|
||||
"action_plan": [{"action": "flag_for_human_review"}],
|
||||
},
|
||||
"description": "must stay",
|
||||
},
|
||||
]
|
||||
|
||||
preview = build_action_plan_hygiene_preview(rows, now=now, stale_hours=72)
|
||||
|
||||
assert preview["candidate_count"] == 2
|
||||
assert preview["by_source"] == {"openclaw_recommendation": 1, "code_review_fix": 1}
|
||||
assert {item["id"] for item in preview["candidates"]} == {1, 2}
|
||||
assert preview["candidate_count"] == 3
|
||||
assert preview["by_source"] == {
|
||||
"openclaw_recommendation": 1,
|
||||
"code_review_fix": 1,
|
||||
"nemotron_direct_response": 1,
|
||||
}
|
||||
assert {item["id"] for item in preview["candidates"]} == {1, 2, 5}
|
||||
target_by_id = {item["id"]: item["to_status"] for item in preview["candidates"]}
|
||||
assert target_by_id == {1: "rejected", 2: "auto_disabled"}
|
||||
assert target_by_id == {1: "rejected", 2: "auto_disabled", 5: "auto_disabled"}
|
||||
|
||||
Reference in New Issue
Block a user