feat(Phase 3): Evolver loop 排程 + 手動觸發端點 — 合併演練閘道完工
Some checks failed
CD Pipeline / build-and-deploy (push) Has been cancelled
Some checks failed
CD Pipeline / build-and-deploy (push) Has been cancelled
- playbook_evolver.py: 新增 run_evolver_loop()(24h 無限迴圈)
- main.py: 掛載 run_evolver_loop asyncio.create_task
- api/v1/learning.py: POST /api/v1/learning/evolver/run(Phase 3 exit #6 演練端點)
- MASTER §8: 補錄 66c4eda AgentSession + 本次 Evolver 完整退出條件清單
ADR-083 Phase 3 — 2026-04-15 ogt + Claude Sonnet 4.6(亞太)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -18,7 +18,7 @@ Phase D-G P0 修正: 新增學習 API 端點
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import structlog
|
import structlog
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter, HTTPException
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from src.services.learning_service import get_learning_service
|
from src.services.learning_service import get_learning_service
|
||||||
@@ -125,3 +125,60 @@ async def get_recommendation(anomaly_key: str) -> RecommendationResponse:
|
|||||||
)
|
)
|
||||||
|
|
||||||
return RecommendationResponse(**recommendation)
|
return RecommendationResponse(**recommendation)
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Evolver Admin Endpoints
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
class EvolverRunResponse(BaseModel):
|
||||||
|
"""Evolver 執行報告回應"""
|
||||||
|
archived_count: int
|
||||||
|
merged_count: int
|
||||||
|
skipped_count: int
|
||||||
|
archived_ids: list[str]
|
||||||
|
merged_pairs: list[list[str]] # [[dropped_id, kept_id], ...]
|
||||||
|
errors: list[str]
|
||||||
|
total_affected: int
|
||||||
|
|
||||||
|
|
||||||
|
@router.post(
|
||||||
|
"/evolver/run",
|
||||||
|
response_model=EvolverRunResponse,
|
||||||
|
summary="手動觸發 Evolver Agent",
|
||||||
|
description=(
|
||||||
|
"立即執行 Playbook Evolver:低信任封存 + 休眠封存 + 相似合併。"
|
||||||
|
"需要 AIOPS_P3_EVOLVER_ENABLED=True,否則返回空報告(HTTP 200)。"
|
||||||
|
"ADR-083 Phase 3 手動演練端點。"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
async def run_evolver_now() -> EvolverRunResponse:
|
||||||
|
"""
|
||||||
|
手動觸發 Evolver Agent(Phase 3 exit condition #6 演練端點)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
EvolverRunResponse: 合併/封存報告
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from src.services.playbook_evolver import run_evolver
|
||||||
|
report = await run_evolver()
|
||||||
|
except Exception as exc:
|
||||||
|
logger.exception("evolver_manual_run_failed")
|
||||||
|
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"evolver_manual_run_done",
|
||||||
|
archived=report.archived_count,
|
||||||
|
merged=report.merged_count,
|
||||||
|
skipped=report.skipped_count,
|
||||||
|
)
|
||||||
|
return EvolverRunResponse(
|
||||||
|
archived_count=report.archived_count,
|
||||||
|
merged_count=report.merged_count,
|
||||||
|
skipped_count=report.skipped_count,
|
||||||
|
archived_ids=report.archived_ids,
|
||||||
|
merged_pairs=[list(p) for p in report.merged_pairs],
|
||||||
|
errors=report.errors,
|
||||||
|
total_affected=report.total_affected,
|
||||||
|
)
|
||||||
|
|||||||
@@ -345,6 +345,15 @@ async def lifespan(_app: FastAPI) -> AsyncGenerator[None, None]:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("approval_timeout_resolver_schedule_failed", error=str(e))
|
logger.warning("approval_timeout_resolver_schedule_failed", error=str(e))
|
||||||
|
|
||||||
|
# ADR-083 Phase 3: Evolver Agent(每日)— Playbook 自動合併 + 低信任封存
|
||||||
|
# 2026-04-15 ogt + Claude Sonnet 4.6(亞太): Phase 3 初始建立
|
||||||
|
try:
|
||||||
|
from src.services.playbook_evolver import run_evolver_loop
|
||||||
|
asyncio.create_task(run_evolver_loop())
|
||||||
|
logger.info("evolver_loop_scheduled", interval_sec=86400)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning("evolver_loop_schedule_failed", error=str(e))
|
||||||
|
|
||||||
# ADR-083 Phase 3: 知識遺忘 Job(每日)— 30d 未引用 KB entry 標記 archived
|
# ADR-083 Phase 3: 知識遺忘 Job(每日)— 30d 未引用 KB entry 標記 archived
|
||||||
# 2026-04-15 ogt + Claude Sonnet 4.6(亞太): Phase 3 初始建立
|
# 2026-04-15 ogt + Claude Sonnet 4.6(亞太): Phase 3 初始建立
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -354,3 +354,23 @@ async def schedule_daily_evolver() -> None:
|
|||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("evolver_daily_failed")
|
logger.exception("evolver_daily_failed")
|
||||||
|
|
||||||
|
|
||||||
|
DAILY_INTERVAL_SEC = 86_400 # 24h
|
||||||
|
|
||||||
|
|
||||||
|
async def run_evolver_loop() -> None:
|
||||||
|
"""
|
||||||
|
無限迴圈:每 24 小時執行一次 Evolver Agent。
|
||||||
|
在 main.py startup 以 asyncio.create_task 掛載。
|
||||||
|
|
||||||
|
ADR-083 Phase 3: Evolver 每日掃描(L4×D3 + L5×D2)
|
||||||
|
2026-04-15 ogt + Claude Sonnet 4.6(亞太): Phase 3 初始建立
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
await schedule_daily_evolver()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("evolver_loop_error", error=str(e))
|
||||||
|
|
||||||
|
await asyncio.sleep(DAILY_INTERVAL_SEC)
|
||||||
|
|||||||
@@ -1559,3 +1559,41 @@ Phase 6 完成後
|
|||||||
- [ ] Fine-tune JSONL ≥ 10 條(待 EvidenceSnapshot 累積 7 天後驗證)
|
- [ ] Fine-tune JSONL ≥ 10 條(待 EvidenceSnapshot 累積 7 天後驗證)
|
||||||
|
|
||||||
**下一步:** 推 Gitea → CD 部署 → 7 天生產觀察 Phase 3 退出條件
|
**下一步:** 推 Gitea → CD 部署 → 7 天生產觀察 Phase 3 退出條件
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2026-04-15 深夜(台北)Session 2 — Phase 3 AgentSession 學習接線 + Evolver Loop 補完
|
||||||
|
|
||||||
|
**commit 66c4eda:** `feat(Phase 3): AgentSession 學習接線 — record_agent_session() + orchestrator 辯證訊號`
|
||||||
|
|
||||||
|
| 檔案 | 修改內容 | 對應項目 |
|
||||||
|
|------|---------|---------|
|
||||||
|
| `services/learning_service.py` | 新增 `record_agent_session()`:5 Agent 辯證結果寫 Redis analytics + Critic challenges 觸發 Playbook 負向 EWMA | ADR-083 Phase 3 L7×D2 |
|
||||||
|
| `services/agent_orchestrator.py` | `run_agent_debate()` 成功後 best-effort 呼叫 `record_agent_session()`,辯證品質訊號接入飛輪 | Orchestrator 接線 |
|
||||||
|
|
||||||
|
**本次提交(未推):Phase 3 Evolver Loop + 手動觸發端點**
|
||||||
|
|
||||||
|
| 檔案 | 修改內容 | 對應項目 |
|
||||||
|
|------|---------|---------|
|
||||||
|
| `services/playbook_evolver.py` | 新增 `run_evolver_loop()`:無限迴圈 24h 一次,供 main.py `asyncio.create_task` 掛載 | Evolver 每日排程 |
|
||||||
|
| `main.py` | 掛載 `run_evolver_loop`(每 24h)於 knowledge_decay_loop 之前 | Job 調度補完 |
|
||||||
|
| `api/v1/learning.py` | 新增 `POST /api/v1/learning/evolver/run`:手動觸發 Evolver,回傳 `EvolverRunResponse`(合併/封存報告)| Phase 3 exit condition #6 演練端點 |
|
||||||
|
|
||||||
|
**Phase 3 退出條件更新(完整版):**
|
||||||
|
- [x] Root cause 1:fire-and-forget → await
|
||||||
|
- [x] Root cause 2:matched_playbook_id 永不填充
|
||||||
|
- [x] Root cause 3:驗證結果未傳學習
|
||||||
|
- [x] 2x EWMA 負向衰減(playbook_repository.py)
|
||||||
|
- [x] Evolver Agent(playbook_evolver.py)
|
||||||
|
- [x] Evolver Loop 每日排程(run_evolver_loop + main.py)
|
||||||
|
- [x] 手動觸發端點(POST /api/v1/learning/evolver/run)
|
||||||
|
- [x] 診斷 feedback(record_diagnosis_outcome)
|
||||||
|
- [x] AgentSession 學習接線(record_agent_session + orchestrator)
|
||||||
|
- [x] 知識遺忘 Job(knowledge_decay_job.py)
|
||||||
|
- [x] Fine-tune 管線(finetune_exporter.py)
|
||||||
|
- [ ] Evolver 合併演練 1 次成功(部署後呼叫 POST /learning/evolver/run 驗證)
|
||||||
|
- [ ] `matched_playbook_id` null 率 = 0(生產驗證 7 天)
|
||||||
|
- [ ] Playbook trust_score 有 ≥ 1 筆 24h 動態更新(生產驗證)
|
||||||
|
- [ ] Fine-tune JSONL ≥ 10 條(EvidenceSnapshot 累積 7 天後)
|
||||||
|
|
||||||
|
**下一步:** 推 Gitea → CD 部署 → 呼叫 `POST /api/v1/learning/evolver/run` 完成合併演練 → 7 天生產監控
|
||||||
|
|||||||
Reference in New Issue
Block a user