refactor(ai): 模組化重構 - NVIDIA chat 移至 NvidiaProvider

符合 feedback_lewooogo_modular_enforcement.md 規範:
- 移除 openclaw.py 中的 _call_nvidia() (重複邏輯)
- 新增 NvidiaProvider.chat() 方法
- 更新 INvidiaProvider Protocol
- openclaw.py 改用 get_nvidia_provider().chat()
- 測試移至 test_nvidia_chat.py

架構層次:
- Router → Service → Provider (正確)
- 禁止 Service 層重複實作已存在的 Provider 功能

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-03-29 20:49:23 +08:00
parent 1eb0be8f3f
commit 04bfff9d19
7 changed files with 333 additions and 194 deletions

View File

@@ -0,0 +1,159 @@
"""
test_nvidia_chat.py - NvidiaProvider.chat() 測試
2026-03-29 ogt: 模組化重構 - 測試移至 NvidiaProvider
符合 feedback_lewooogo_modular_enforcement.md 規範
測試策略 (遵循 feedback_no_mock_testing.md):
- 使用真實 NVIDIA API (需 NVIDIA_API_KEY)
- 跳過條件: 無 API Key 時跳過
"""
import json
import os
import pytest
from src.core.config import get_settings
from src.services.nvidia_provider import NvidiaProvider, get_nvidia_provider
settings = get_settings()
@pytest.fixture
def nvidia_provider():
"""建立 NvidiaProvider 實例"""
return NvidiaProvider()
@pytest.mark.asyncio
@pytest.mark.skipif(
not os.getenv("NVIDIA_API_KEY") and not settings.NVIDIA_API_KEY,
reason="NVIDIA_API_KEY not configured",
)
async def test_chat_success(nvidia_provider):
"""
測試 chat() 成功回應
驗證:
- 回應格式正確 (4-tuple)
- success = True
- total_tokens > 0
- cost_usd = 0 (免費 tier)
"""
prompt = """你是一個 JSON 產生器。請回傳以下格式:
{"status": "ok", "message": "test"}
只回傳 JSON不要其他內容。"""
response, success, total_tokens, cost_usd = await nvidia_provider.chat(prompt)
assert success is True, f"Expected success, got error: {response}"
assert isinstance(response, str)
assert len(response) > 0
assert total_tokens > 0, "Expected token count > 0"
assert cost_usd == 0.0, "Expected $0 for free tier"
@pytest.mark.asyncio
async def test_chat_no_api_key():
"""
測試無 API Key 時的處理
驗證:
- success = False
- 回傳適當錯誤訊息
"""
# 建立沒有 API Key 的 provider
provider = NvidiaProvider(api_key=None)
response, success, total_tokens, cost_usd = await provider.chat("test")
assert success is False
assert "not configured" in response.lower()
assert total_tokens == 0
assert cost_usd == 0.0
@pytest.mark.asyncio
@pytest.mark.skipif(
not os.getenv("NVIDIA_API_KEY") and not settings.NVIDIA_API_KEY,
reason="NVIDIA_API_KEY not configured",
)
async def test_chat_json_response(nvidia_provider):
"""
測試 JSON 格式回應
驗證:
- 回應是有效 JSON
"""
prompt = """回傳一個 JSON 物件,包含:
- action: "NO_ACTION"
- reason: "測試"
只回傳 JSON。"""
response, success, _, _ = await nvidia_provider.chat(prompt)
assert success is True
# 驗證是有效 JSON
try:
data = json.loads(response)
assert isinstance(data, dict)
except json.JSONDecodeError:
pytest.fail(f"Response is not valid JSON: {response[:200]}")
@pytest.mark.asyncio
@pytest.mark.skipif(
not os.getenv("NVIDIA_API_KEY") and not settings.NVIDIA_API_KEY,
reason="NVIDIA_API_KEY not configured",
)
async def test_chat_uses_model_registry(nvidia_provider):
"""
測試使用 ModelRegistry 取得模型
驗證:
- 使用 models.json 中定義的 rca 模型
"""
from src.services.model_registry import get_model_registry
registry = get_model_registry()
expected_model = registry.get_model("nvidia", "rca")
# 模型應該是 llama-3.1-nemotron-70b-instruct
assert "nemotron" in expected_model.lower()
assert "70b" in expected_model or "mini" in expected_model
@pytest.mark.asyncio
def test_get_nvidia_provider_singleton():
"""
測試單例模式
驗證:
- get_nvidia_provider() 返回同一實例
"""
provider1 = get_nvidia_provider()
provider2 = get_nvidia_provider()
assert provider1 is provider2
@pytest.mark.asyncio
@pytest.mark.skipif(
not os.getenv("NVIDIA_API_KEY") and not settings.NVIDIA_API_KEY,
reason="NVIDIA_API_KEY not configured",
)
async def test_chat_includes_otel_tracing(nvidia_provider):
"""
測試 OTEL 追蹤整合
驗證:
- chat() 執行時有 OTEL span
"""
# 這個測試主要驗證代碼不會拋出異常
prompt = '{"test": true}'
response, success, _, _ = await nvidia_provider.chat(prompt)
# 只要沒有拋出異常就算通過
assert isinstance(response, str)