feat(km): add full corpus asset taxonomy readback
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 1s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / tests (push) Has been cancelled

This commit is contained in:
Your Name
2026-07-03 16:12:48 +08:00
parent 37dda358a3
commit f99305e481
10 changed files with 312 additions and 17 deletions

View File

@@ -265,6 +265,10 @@ class IKnowledgeRepository(Protocol):
"""取得分類統計 [(category, count)]"""
...
async def get_asset_taxonomy_counts(self) -> list[tuple[str, int]]:
"""取得 AI 自動化資產維度統計 [(taxonomy_key, count)]"""
...
async def search(self, query: str, limit: int = 20) -> list[KnowledgeEntry]:
"""關鍵字搜尋 (title + content + tags)"""
...

View File

@@ -29,6 +29,113 @@ logger = structlog.get_logger(__name__)
_DEFAULT_CATEGORY = "general"
_ASSET_TAXONOMY_KEYS = (
"project",
"product",
"website",
"service",
"package",
"tool",
"log",
"alert",
"playbook",
"rag",
"mcp",
"schedule",
)
_ASSET_TAXONOMY_TERMS: dict[str, tuple[str, ...]] = {
"project": (
"project",
"repo",
"repository",
"gitea",
"branch",
"workflow",
"source_control",
),
"product": (
"product",
"awoooi",
"awooop",
"iwooos",
"stockplatform",
"momo",
"awooogo",
"agent-bounty",
"agent_bounty",
"tsenyang",
"vibework",
"2026fifa",
),
"website": ("website", "site", "nginx", "ssl", "domain", "route", "frontend"),
"service": ("service", "daemon", "api", "worker", "runtime", "container", "pod"),
"package": (
"package",
"dependency",
"npm",
"pnpm",
"node",
"python",
"pip",
"prisma",
"next",
"library",
),
"tool": (
"tool",
"ansible",
"mcp",
"playbook",
"telegram",
"wazuh",
"kali",
"sentry",
"signoz",
"runner",
),
"log": (
"log",
"logs",
"event",
"timeline",
"trace",
"audit",
"callback",
"conversation_event",
"telemetry",
),
"alert": (
"alert",
"telegram",
"sentry",
"signoz",
"notification",
"warning",
"critical",
),
"playbook": ("playbook", "runbook", "sop"),
"rag": ("rag", "vector", "embedding", "semantic", "retrieval"),
"mcp": ("mcp", "connector", "gateway", "tool integration", "tool-integration"),
"schedule": ("schedule", "cron", "job", "worker", "patrol", "recurrence", "cadence"),
}
_ASSET_TAXONOMY_CATEGORY_HINTS: dict[str, tuple[str, ...]] = {
"website": ("external_site",),
"service": (
"infrastructure",
"application",
"ai_system",
"database",
"host_resource",
"kubernetes",
"alert_handling",
),
"tool": ("devops_tool", "AI自動化/Ansible受控修復"),
"alert": ("alert_handling",),
"playbook": ("auto_repair",),
}
def _enum_text(column):
"""Return a lowercase text view for enum/varchar columns across old KM schemas."""
@@ -85,6 +192,32 @@ def _knowledge_entry_columns():
)
def _asset_taxonomy_condition(key: str):
"""Build a broad-but-readable taxonomy matcher for AI automation surfaces."""
category_expr = _normalized_category_expr()
category_text = func.lower(category_expr.cast(String))
text_columns = (
KnowledgeEntryRecord.title,
KnowledgeEntryRecord.content,
KnowledgeEntryRecord.tags.cast(String),
category_expr.cast(String),
)
conditions = []
category_hints = tuple(
hint.lower() for hint in _ASSET_TAXONOMY_CATEGORY_HINTS.get(key, ())
)
if category_hints:
conditions.append(category_text.in_(category_hints))
for term in _ASSET_TAXONOMY_TERMS.get(key, ()):
pattern = f"%{term.lower()}%"
conditions.extend(func.lower(column).like(pattern) for column in text_columns)
if key == "playbook":
conditions.append(KnowledgeEntryRecord.related_playbook_id.is_not(None))
if not conditions:
return category_text == key
return or_(*conditions)
class KnowledgeDBRepository:
"""
Knowledge Repository - PostgreSQL 實作
@@ -290,6 +423,26 @@ class KnowledgeDBRepository:
)
return [(_normalize_category(row.category), row.cnt) for row in result.all()]
async def get_asset_taxonomy_counts(self) -> list[tuple[str, int]]:
"""取得 AI 自動化資產維度統計。
這不是替代原始 category而是讓 UI / Agent 可以用穩定 taxonomy
將 KM 依專案、產品、網站、服務、套件、工具、Log、Alert、PlayBook、
RAG、MCP 與排程分群。
"""
rows: list[tuple[str, int]] = []
for key in _ASSET_TAXONOMY_KEYS:
result = await self.db.execute(
select(func.count())
.select_from(KnowledgeEntryRecord)
.where(
_active_status_filter(),
_asset_taxonomy_condition(key),
)
)
rows.append((key, int(result.scalar() or 0)))
return rows
async def search(self, query: str, limit: int = 20) -> list[KnowledgeEntry]:
"""關鍵字搜尋 (title + content + tags)"""
like_q = f"%{query}%"