feat(api): Phase 13 智能路由 + CI/CD 整合 (#74-88)

Phase 13.1 CI/CD Integration:
- #76 workflow_run handler for CI failure diagnosis
- #77 SignOz log query (query_logs, error_logs_summary MCP)
- #78 CIAutoRepairService with risk-based execution decisions

Phase 13.3 Smart Routing:
- #85 Intent Classifier v2.0 (rule engine + LLM fallback)
- #86 Complexity Scorer (9-dimension scoring)
- #87 AI Router v3.0 (routing decision matrix)
- #88 Token Counter (OTEL + Langfuse integration)

New files:
- services/ci_auto_repair.py (risk stratification)
- services/model_registry.py (centralized model config)
- services/token_counter.py (677 lines)
- Skill 08: Model Router Expert
- Skill 09: Strangler Pattern Expert
- ADR-023: Smart Routing Architecture
- ADR-024: API Layer Architecture

Tests:
- phase11-conversational.spec.ts (E2E tests)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-03-26 15:32:52 +08:00
parent b79e5f1a1a
commit 579da38b8b
15 changed files with 5895 additions and 245 deletions

View File

@@ -6,10 +6,17 @@ SignOz MCP Tool Provider - ADR-015 模組化架構
- gold_metrics: 取得 Gold Metrics (RPS, Error Rate, P99)
- trace_url: 生成 Trace 查詢 URL
- system_metrics: 取得系統指標 (CPU/Disk)
- query_logs: 查詢日誌 (Phase 13.1 #77)
- error_logs_summary: 錯誤日誌摘要 (Phase 13.1 #77)
透過 DI 注入 SignOzClient不直接 import services。
@see docs/adr/ADR-015-mcp-modular-architecture.md
版本: v1.1
最後修改: 2026-03-26 16:45 (台北時區)
修改者: Claude Code
變更: Phase 13.1 #77 新增 query_logs, error_logs_summary
"""
import uuid
@@ -84,6 +91,34 @@ class SignOzProvider(MCPToolProvider):
},
server_name=self.name,
),
MCPTool(
name="query_logs",
description="Query logs from SignOz (Phase 13.1 #77). Use for CI failure diagnosis or service debugging.",
input_schema={
"type": "object",
"properties": {
"service_name": {"type": "string", "description": "Service name (e.g., awoooi-api, awoooi-worker)"},
"severity": {"type": "string", "description": "Log severity filter (ERROR, WARN, INFO, DEBUG). Comma-separated for multiple."},
"search_text": {"type": "string", "description": "Text to search in log messages"},
"time_window_minutes": {"type": "integer", "description": "Time window in minutes (default: 30)"},
"limit": {"type": "integer", "description": "Max logs to return (default: 100)"},
},
},
server_name=self.name,
),
MCPTool(
name="error_logs_summary",
description="Get error logs summary with counts and sample messages. Useful for quick diagnosis.",
input_schema={
"type": "object",
"properties": {
"service_name": {"type": "string", "description": "Service name (required)"},
"time_window_minutes": {"type": "integer", "description": "Time window (default: 60)"},
},
"required": ["service_name"],
},
server_name=self.name,
),
]
async def execute(
@@ -101,6 +136,10 @@ class SignOzProvider(MCPToolProvider):
output = self._trace_url(client, parameters)
elif tool_name == "system_metrics":
output = await self._system_metrics(client, parameters)
elif tool_name == "query_logs":
output = await self._query_logs(client, parameters)
elif tool_name == "error_logs_summary":
output = await self._error_logs_summary(client, parameters)
else:
return MCPToolResult(
success=False,
@@ -184,6 +223,48 @@ class SignOzProvider(MCPToolProvider):
"time_range": metrics.get("time_range", {}),
}
async def _query_logs(self, client, parameters: dict) -> dict:
"""Query logs from SignOz (Phase 13.1 #77)"""
service_name = parameters.get("service_name")
severity = parameters.get("severity")
search_text = parameters.get("search_text")
time_window = parameters.get("time_window_minutes", 30)
limit = parameters.get("limit", 100)
logs = await client.get_logs(
service_name=service_name,
severity=severity,
search_text=search_text,
time_window_minutes=time_window,
limit=limit,
)
return {
"logs": logs,
"count": len(logs),
"filters": {
"service_name": service_name,
"severity": severity,
"search_text": search_text,
"time_window_minutes": time_window,
},
}
async def _error_logs_summary(self, client, parameters: dict) -> dict:
"""Get error logs summary (Phase 13.1 #77)"""
service_name = parameters.get("service_name")
if not service_name:
return {"error": "Missing 'service_name' parameter"}
time_window = parameters.get("time_window_minutes", 60)
summary = await client.get_error_logs_summary(
service_name=service_name,
time_window_minutes=time_window,
)
return summary
async def health_check(self) -> bool:
"""Check if SignOz is accessible"""
try: