fix(ci): 修復 docker socket 重複掛載 (1774793847)
This commit is contained in:
@@ -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]:
|
||||
|
||||
Reference in New Issue
Block a user