fix(ci): 修復 docker socket 重複掛載 (1774793847)
Some checks failed
CD Pipeline / build-and-deploy (push) Failing after 3m22s
E2E Health Check / e2e-health (push) Failing after 11s

This commit is contained in:
OG T
2026-03-29 22:17:18 +08:00
parent 1b292e8ed4
commit 3eb3051a73
3 changed files with 445 additions and 11 deletions

View File

@@ -21,18 +21,40 @@ K8s 操作意圖分類器,用於智能路由模型選擇
from __future__ import annotations
import json
import re
import time
from dataclasses import dataclass, field
from enum import Enum
from typing import Protocol, runtime_checkable
import httpx
import structlog
from src.core.config import settings
from src.services.model_registry import get_model_registry
logger = structlog.get_logger(__name__)
# LLM 分類 Prompt 模板 (Phase 13.4)
_LLM_CLASSIFY_PROMPT = """你是 K8s 操作意圖分類專家。根據以下輸入,判斷用戶的操作意圖。
可選意圖類型:
- restart: 重啟 Pod/Deployment/StatefulSet
- scale: 擴縮容、HPA 調整
- config: ConfigMap/Secret/ENV 變更
- diagnose: 日誌查詢、健康檢查、RCA
- delete: 刪除資源(高風險)
- rollback: 回滾版本
- unknown: 無法判斷
輸入: {text}
請以 JSON 格式回答,只輸出 JSON:
{{"intent": "<類型>", "confidence": <0.0-1.0>, "reasoning": "<判斷依據>"}}"""
# =============================================================================
# 意圖類型定義 (Phase 13.3 #85)
# =============================================================================
@@ -503,9 +525,9 @@ class IntentClassifier:
async def _llm_classify(self, text: str) -> IntentResult:
"""
LLM 分類 (方案 B)
LLM 分類 (方案 B) - Phase 13.4
目標延遲: < 100ms (使用 qwen2.5:1b)
目標延遲: < 100ms (使用 qwen2.5:1b 或配置的 intent 模型)
Args:
text: 已轉小寫的輸入文字
@@ -513,20 +535,114 @@ class IntentClassifier:
Returns:
IntentResult: 分類結果
Note:
目前返回 UNKNOWN待 Ollama qwen2.5:1b 部署後啟用
2026-03-30 Claude Code: 實作 Ollama 整合
"""
# TODO: 整合 Ollama qwen2.5:1b (Phase 13.4)
# 預計使用 text 呼叫 Ollama API 進行分類
# 目前先返回 UNKNOWN規則引擎已能處理大部分情況
del text # 預留給 LLM 分類使用,避免 unused-parameter 警告
start_time = time.time()
try:
# 建構 Prompt
prompt = _LLM_CLASSIFY_PROMPT.format(text=text)
# 取得模型配置
model_name = self.llm_model # qwen2.5:1b 或配置值
# 呼叫 Ollama
async with httpx.AsyncClient() as client:
response = await client.post(
f"{settings.OLLAMA_URL}/api/generate",
json={
"model": model_name,
"prompt": prompt,
"stream": False,
"format": "json",
"options": {
"num_predict": 128, # 意圖分類只需短回應
"temperature": 0.0, # 確定性輸出
"top_p": 0.9,
},
},
timeout=httpx.Timeout(5.0, connect=2.0), # 嚴格超時
)
response.raise_for_status()
data = response.json()
result_text = data.get("response", "")
# 解析 JSON 回應
elapsed_ms = (time.time() - start_time) * 1000
try:
parsed = json.loads(result_text)
intent_str = parsed.get("intent", "unknown").lower()
confidence = float(parsed.get("confidence", 0.5))
reasoning = parsed.get("reasoning", "")
# 映射到 IntentType
intent = self._parse_intent_type(intent_str)
logger.info(
"intent_llm_classified",
intent=intent.value,
confidence=confidence,
elapsed_ms=round(elapsed_ms, 1),
model=model_name,
)
return IntentResult(
intent=intent,
confidence=confidence,
method="llm",
matched_keywords=[],
detected_resources=[],
reasoning=reasoning,
)
except (json.JSONDecodeError, KeyError, ValueError) as e:
logger.warning(
"intent_llm_parse_failed",
error=str(e),
response_preview=result_text[:100],
)
return self._llm_fallback_result("JSON 解析失敗")
except httpx.TimeoutException:
elapsed_ms = (time.time() - start_time) * 1000
logger.warning(
"intent_llm_timeout",
elapsed_ms=round(elapsed_ms, 1),
)
return self._llm_fallback_result("LLM 超時")
except Exception as e:
logger.warning(
"intent_llm_error",
error=str(e),
error_type=type(e).__name__,
)
return self._llm_fallback_result(f"LLM 錯誤: {type(e).__name__}")
def _parse_intent_type(self, intent_str: str) -> IntentType:
"""解析意圖字串為 IntentType"""
intent_map = {
"restart": IntentType.RESTART,
"scale": IntentType.SCALE,
"config": IntentType.CONFIG,
"diagnose": IntentType.DIAGNOSE,
"delete": IntentType.DELETE,
"rollback": IntentType.ROLLBACK,
"unknown": IntentType.UNKNOWN,
}
return intent_map.get(intent_str.lower(), IntentType.UNKNOWN)
def _llm_fallback_result(self, reason: str) -> IntentResult:
"""LLM 失敗時的 fallback 結果"""
return IntentResult(
intent=IntentType.UNKNOWN,
confidence=0.0, # 🔴 LLM 未啟用,非 AI 分析
confidence=0.0,
method="llm",
matched_keywords=[],
detected_resources=[],
reasoning="LLM 分類尚未啟用",
reasoning=reason,
)
def get_supported_intents(self) -> list[dict]: