feat(api): Phase 13.2 AI Rate Limiter + RAG 基礎設施 (#84)
Rate Limiter (防止 Gemini 用量暴衝): - ai_rate_limiter.py: RPM/Daily/Token 三層閥值 - openclaw.py: 整合 rate limit 檢查,超限自動降級 - health.py: /health/ai-usage 監控端點 RAG Tool 基礎 (#84 進行中): - embedding_service.py: Ollama embedding 封裝 - rag_service.py: Redis vector search 服務 閥值設定: - Gemini: 10 RPM, 500/day, 100K tokens/day - Claude: 5 RPM, 200/day, 50K tokens/day Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,7 @@ from src.models.ai import (
|
||||
OpenClawDecision,
|
||||
)
|
||||
from src.services.langfuse_client import langfuse_trace
|
||||
from src.services.model_registry import get_model_registry
|
||||
from src.services.signoz_client import GoldMetrics, get_signoz_client
|
||||
from src.utils.k8s_naming import normalize_resource_name
|
||||
from src.utils.timezone import now_taipei_iso
|
||||
@@ -270,17 +271,22 @@ class OpenClawService:
|
||||
prompt_length=len(prompt),
|
||||
)
|
||||
|
||||
# 從 ModelRegistry 取得模型配置
|
||||
registry = get_model_registry()
|
||||
model_name = registry.get_model("ollama", "rca")
|
||||
options = registry.get_provider_options("ollama")
|
||||
|
||||
response = await client.post(
|
||||
f"{settings.OLLAMA_URL}/api/generate",
|
||||
json={
|
||||
"model": "qwen2.5:7b-instruct", # 使用更大的模型提高品質
|
||||
"model": model_name,
|
||||
"prompt": prompt,
|
||||
"stream": False,
|
||||
"format": "json", # 強制 JSON 輸出
|
||||
"options": {
|
||||
"num_predict": 1024, # 增加輸出長度
|
||||
"temperature": 0.1, # 低溫度確保穩定輸出
|
||||
"top_p": 0.9,
|
||||
"num_predict": options.get("num_predict", 1024),
|
||||
"temperature": options.get("temperature", 0.1),
|
||||
"top_p": options.get("top_p", 0.9),
|
||||
},
|
||||
},
|
||||
timeout=httpx.Timeout(float(settings.OPENCLAW_TIMEOUT), connect=10.0),
|
||||
@@ -324,9 +330,12 @@ class OpenClawService:
|
||||
try:
|
||||
client = await self._get_client()
|
||||
|
||||
# Gemini 1.5 Flash 支援 JSON Mode
|
||||
# 從 ModelRegistry 取得模型配置
|
||||
registry = get_model_registry()
|
||||
model_name = registry.get_model("gemini", "rca")
|
||||
|
||||
response = await client.post(
|
||||
f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key={settings.GEMINI_API_KEY}",
|
||||
f"https://generativelanguage.googleapis.com/v1beta/models/{model_name}:generateContent?key={settings.GEMINI_API_KEY}",
|
||||
json={
|
||||
"contents": [{"parts": [{"text": prompt}]}],
|
||||
"generationConfig": {
|
||||
@@ -737,6 +746,24 @@ class OpenClawService:
|
||||
|
||||
return response, provider, success, False # from_cache=False
|
||||
|
||||
# =========================================================================
|
||||
# Public LLM Interface (ILLMProvider Protocol)
|
||||
# =========================================================================
|
||||
|
||||
async def call(self, prompt: str) -> tuple[str, str, bool]:
|
||||
"""
|
||||
呼叫 LLM (ILLMProvider Protocol 實作)
|
||||
|
||||
#39 Error Analyzer Agent 使用此方法
|
||||
|
||||
Args:
|
||||
prompt: 完整的 prompt
|
||||
|
||||
Returns:
|
||||
(response, provider, success)
|
||||
"""
|
||||
return await self._call_with_fallback(prompt)
|
||||
|
||||
# =========================================================================
|
||||
# Fallback Chain
|
||||
# =========================================================================
|
||||
@@ -784,7 +811,23 @@ class OpenClawService:
|
||||
DeepLinking.langfuse_trace_url(trace.langfuse_trace_id),
|
||||
)
|
||||
|
||||
# Phase 13.2: Rate Limiter 整合 (2026-03-26)
|
||||
# 防止雲端 API 用量暴衝,超限自動降級
|
||||
from src.services.ai_rate_limiter import get_ai_rate_limiter
|
||||
rate_limiter = get_ai_rate_limiter()
|
||||
|
||||
for provider in settings.AI_FALLBACK_ORDER:
|
||||
# Rate Limit 檢查 (gemini/claude 需檢查,ollama 不限)
|
||||
if provider in ("gemini", "claude"):
|
||||
allowed, reason = await rate_limiter.check_and_increment(provider)
|
||||
if not allowed:
|
||||
logger.warning(
|
||||
"ai_rate_limit_skip",
|
||||
provider=provider,
|
||||
reason=reason,
|
||||
)
|
||||
continue # 跳過此 provider,嘗試下一個
|
||||
|
||||
logger.info("ai_provider_attempt", provider=provider)
|
||||
|
||||
start_time = time.time()
|
||||
@@ -829,13 +872,9 @@ class OpenClawService:
|
||||
return self._generate_mock_response(alert_context or {}, signoz_metrics), "mock_fallback", True
|
||||
|
||||
def _get_model_name(self, provider: str) -> str:
|
||||
"""取得 provider 對應的模型名稱"""
|
||||
model_map = {
|
||||
"ollama": "qwen2.5:7b-instruct",
|
||||
"gemini": "gemini-1.5-flash",
|
||||
"claude": "claude-3-haiku-20240307",
|
||||
}
|
||||
return model_map.get(provider, provider)
|
||||
"""取得 provider 對應的模型名稱 (從 ModelRegistry)"""
|
||||
registry = get_model_registry()
|
||||
return registry.get_model(provider, "rca")
|
||||
|
||||
# =========================================================================
|
||||
# Response Parsing (防禦性解析)
|
||||
|
||||
Reference in New Issue
Block a user