feat(learning): 實作 Playbook 信心度調整機制 (ADR-030)

- 新增 _promote_playbook: 高評分提升信心度 +0.1
- 新增 _demote_playbook: 低評分降低信心度 -0.15
- 新增 find_by_source_incident: 按 incident_id 查詢 Playbook
- 新增 adjust_confidence: 信心度調整 + 狀態自動轉換
- 新增 Playbook.failure_rate 屬性

自動狀態轉換:
- ai_confidence >= 0.9 + DRAFT → 自動 APPROVED
- ai_confidence < 0.3 + failure_rate > 50% → 自動 DEPRECATED

測試: 13 案例全部通過

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-03-29 22:10:49 +08:00
parent a0ef323d75
commit f5b19cf108
6 changed files with 553 additions and 14 deletions

View File

@@ -244,6 +244,39 @@ class IPlaybookRepository(Protocol):
"""更新執行統計"""
...
async def find_by_source_incident(
self,
incident_id: str,
) -> list[Playbook]:
"""
根據來源 Incident ID 找 Playbook
2026-03-30 Claude Code: Learning Service 信心度調整用
尋找 source_incident_ids 包含此 incident_id 的 Playbooks
"""
...
async def adjust_confidence(
self,
playbook_id: str,
delta: float,
reason: str,
) -> Playbook | None:
"""
調整 Playbook 信心度
2026-03-30 Claude Code: Learning Service 信心度調整用
Args:
playbook_id: Playbook ID
delta: 調整量 (+/- 0.0~1.0)
reason: 調整原因 (審計用)
Returns:
更新後的 Playbook或 None (如果不存在)
"""
...
@runtime_checkable
class ILearningRepository(Protocol):

View File

@@ -393,6 +393,118 @@ class PlaybookRepository:
except Exception as e:
logger.warning("playbook_index_update_failed", error=str(e))
# === Learning Service 信心度調整 (2026-03-30 Claude Code) ===
async def find_by_source_incident(
self,
incident_id: str,
) -> list[Playbook]:
"""
根據來源 Incident ID 找 Playbook
2026-03-30 Claude Code: Learning Service 信心度調整用
尋找 source_incident_ids 包含此 incident_id 的 Playbooks
"""
try:
redis_client = get_redis()
# 掃描所有 Playbook keys
pattern = f"{PLAYBOOK_KEY_PREFIX}PB-*"
results: list[Playbook] = []
async for key in redis_client.scan_iter(match=pattern, count=100):
data = await redis_client.get(key)
if data:
playbook = Playbook.from_redis_dict(json.loads(data))
if incident_id in playbook.source_incident_ids:
results.append(playbook)
return results
except Exception as e:
logger.error(
"playbook_find_by_incident_failed",
incident_id=incident_id,
error=str(e),
)
return []
async def adjust_confidence(
self,
playbook_id: str,
delta: float,
reason: str,
) -> Playbook | None:
"""
調整 Playbook 信心度
2026-03-30 Claude Code: Learning Service 信心度調整用
邏輯:
- ai_confidence += delta (clamp 到 0.0~1.0)
- 若信心度 >= 0.9 且 status == DRAFT → 自動升級為 APPROVED
- 若信心度 < 0.3 且 failure_rate > 50% → 自動降級為 DEPRECATED
"""
try:
playbook = await self.get_by_id(playbook_id)
if not playbook:
return None
old_confidence = playbook.ai_confidence
# 調整信心度 (clamp 到 0.0~1.0)
playbook.ai_confidence = max(0.0, min(1.0, playbook.ai_confidence + delta))
# 狀態自動轉換
old_status = playbook.status
# 高信心度自動升級
if (
playbook.ai_confidence >= 0.9
and playbook.status == PlaybookStatus.DRAFT
):
playbook.status = PlaybookStatus.APPROVED
playbook.approved_by = "auto_learning"
playbook.approved_at = now_taipei()
note = f"\n[Auto-approved: confidence {playbook.ai_confidence:.2f}]"
playbook.notes = (playbook.notes or "") + note
# 低信心度 + 高失敗率 → 棄用
elif (
playbook.ai_confidence < 0.3
and playbook.total_executions >= 5
and playbook.failure_rate > 0.5
):
playbook.status = PlaybookStatus.DEPRECATED
conf = playbook.ai_confidence
fail = playbook.failure_rate
note = f"\n[Auto-deprecated: conf={conf:.2f}, fail={fail:.0%}]"
playbook.notes = (playbook.notes or "") + note
# 儲存
updated = await self.update(playbook)
logger.info(
"playbook_confidence_adjusted",
playbook_id=playbook_id,
old_confidence=old_confidence,
new_confidence=playbook.ai_confidence,
old_status=old_status.value,
new_status=playbook.status.value,
reason=reason,
)
return updated
except Exception as e:
logger.error(
"playbook_confidence_adjust_failed",
playbook_id=playbook_id,
delta=delta,
error=str(e),
)
return None
# =============================================================================
# Singleton