feat(playbook): version generated playbooks
All checks were successful
CD Pipeline / tests (push) Successful in 1m34s
Code Review / ai-code-review (push) Successful in 28s
Type Sync Check / check-type-sync (push) Successful in 1m10s
CD Pipeline / build-and-deploy (push) Successful in 10m19s
CD Pipeline / post-deploy-checks (push) Successful in 3m1s

This commit is contained in:
Your Name
2026-04-30 23:59:39 +08:00
parent 474b913ac9
commit f154ac022e
13 changed files with 426 additions and 5 deletions

View File

@@ -179,10 +179,31 @@ class LLMPlaybookGenerator:
playbook.status = PlaybookStatus.DRAFT
if persist:
playbook = await self._service().create(playbook)
playbook = await self._persist_with_lineage(playbook)
return self._record(playbook, "success", provider, "")
async def _persist_with_lineage(self, playbook: Playbook) -> Playbook:
"""Create a new lineage version when a close approved Playbook exists."""
try:
recommendations = await self._service().get_recommendations(
symptoms=playbook.symptom_pattern,
top_k=1,
use_rag=False,
)
if recommendations and recommendations[0].similarity_score >= 0.85:
base = recommendations[0].playbook
created = await self._service().create_new_version(
base_playbook_id=base.playbook_id,
candidate=playbook,
reason="ADR-104 local LLM generated improved Playbook from successful incident",
)
if created is not None:
return created
except Exception as exc:
logger.warning("playbook_generation_lineage_fallback", error=str(exc))
return await self._service().create(playbook)
async def _call_local_llm(
self,
prompt: str,

View File

@@ -28,6 +28,7 @@ from src.models.playbook import (
RepairStep,
RiskLevel,
SymptomPattern,
generate_playbook_id,
)
from src.repositories.interfaces import IPlaybookRepository
from src.repositories.playbook_repository import get_playbook_repository
@@ -79,10 +80,32 @@ class IPlaybookService(Protocol):
self,
symptoms: SymptomPattern,
top_k: int = 3,
use_rag: bool = True,
) -> list[PlaybookRecommendation]:
"""取得 Playbook 推薦"""
...
async def get_by_id(self, playbook_id: str) -> Playbook | None:
"""取得 Playbook"""
...
async def create_new_version(
self,
base_playbook_id: str,
candidate: Playbook,
reason: str,
) -> Playbook | None:
"""從既有 Playbook 建立下一版"""
...
async def update_with_validation(
self,
playbook_id: str,
update_data: dict,
) -> Playbook | None:
"""驗證後更新 Playbook"""
...
async def approve(
self,
playbook_id: str,
@@ -463,6 +486,51 @@ class PlaybookService:
"""更新 Playbook"""
return await self._repository.update(playbook)
async def create_new_version(
self,
base_playbook_id: str,
candidate: Playbook,
reason: str,
) -> Playbook | None:
"""
從既有 Playbook 建立下一版。
ADR-104 T4: LLM 生成的改良方案不覆蓋舊 Playbook而是建立 lineage
root(parent_playbook_id) -> v2 -> v3。舊版在新版 APPROVED 前仍可用。
"""
base = await self._repository.get_by_id(base_playbook_id)
if not base:
logger.warning("playbook_version_base_missing", base_playbook_id=base_playbook_id)
return None
root_id = base.parent_playbook_id or base.playbook_id
candidate.playbook_id = generate_playbook_id()
candidate.version = base.version + 1
candidate.parent_playbook_id = root_id
candidate.supersedes_playbook_id = base.playbook_id
candidate.version_reason = reason[:500]
candidate.success_count = 0
candidate.failure_count = 0
candidate.last_used_at = None
candidate.approved_by = None
candidate.approved_at = None
if base.playbook_id not in candidate.source_incident_ids:
candidate.notes = (
(candidate.notes or "")
+ f"\n[Version lineage: v{candidate.version} supersedes {base.playbook_id}]"
).strip()
created = await self._repository.create(candidate)
logger.info(
"playbook_version_created",
playbook_id=created.playbook_id,
base_playbook_id=base.playbook_id,
root_playbook_id=root_id,
version=created.version,
reason=reason,
)
return created
async def update_with_validation(
self,
playbook_id: str,