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:
@@ -1,37 +1,39 @@
|
||||
"""
|
||||
test_openclaw_nvidia.py - NVIDIA RCA 整合測試
|
||||
test_nvidia_chat.py - NvidiaProvider.chat() 測試
|
||||
|
||||
2026-03-29 ogt: P0 修復 - 新增 _call_nvidia 測試
|
||||
2026-03-29 ogt: 模組化重構 - 測試移至 NvidiaProvider
|
||||
符合 feedback_lewooogo_modular_enforcement.md 規範
|
||||
|
||||
測試策略 (遵循 feedback_no_mock_testing.md):
|
||||
- 使用真實 NVIDIA API (需 NVIDIA_API_KEY)
|
||||
- 跳過條件: 無 API Key 時跳過
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import json
|
||||
import os
|
||||
|
||||
from src.services.openclaw import OpenClawService
|
||||
from src.core.config import get_settings
|
||||
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 openclaw_service():
|
||||
"""建立 OpenClawService 實例"""
|
||||
return OpenClawService()
|
||||
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"
|
||||
reason="NVIDIA_API_KEY not configured",
|
||||
)
|
||||
async def test_call_nvidia_success(openclaw_service):
|
||||
async def test_chat_success(nvidia_provider):
|
||||
"""
|
||||
測試 _call_nvidia 成功回應
|
||||
測試 chat() 成功回應
|
||||
|
||||
驗證:
|
||||
- 回應格式正確 (4-tuple)
|
||||
@@ -43,7 +45,7 @@ async def test_call_nvidia_success(openclaw_service):
|
||||
{"status": "ok", "message": "test"}
|
||||
只回傳 JSON,不要其他內容。"""
|
||||
|
||||
response, success, total_tokens, cost_usd = await openclaw_service._call_nvidia(prompt)
|
||||
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)
|
||||
@@ -53,7 +55,7 @@ async def test_call_nvidia_success(openclaw_service):
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_call_nvidia_no_api_key(openclaw_service, monkeypatch):
|
||||
async def test_chat_no_api_key():
|
||||
"""
|
||||
測試無 API Key 時的處理
|
||||
|
||||
@@ -61,10 +63,10 @@ async def test_call_nvidia_no_api_key(openclaw_service, monkeypatch):
|
||||
- success = False
|
||||
- 回傳適當錯誤訊息
|
||||
"""
|
||||
# 暫時移除 API Key
|
||||
monkeypatch.setattr(settings, "NVIDIA_API_KEY", None)
|
||||
# 建立沒有 API Key 的 provider
|
||||
provider = NvidiaProvider(api_key=None)
|
||||
|
||||
response, success, total_tokens, cost_usd = await openclaw_service._call_nvidia("test")
|
||||
response, success, total_tokens, cost_usd = await provider.chat("test")
|
||||
|
||||
assert success is False
|
||||
assert "not configured" in response.lower()
|
||||
@@ -75,23 +77,21 @@ async def test_call_nvidia_no_api_key(openclaw_service, monkeypatch):
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(
|
||||
not os.getenv("NVIDIA_API_KEY") and not settings.NVIDIA_API_KEY,
|
||||
reason="NVIDIA_API_KEY not configured"
|
||||
reason="NVIDIA_API_KEY not configured",
|
||||
)
|
||||
async def test_call_nvidia_json_response(openclaw_service):
|
||||
async def test_chat_json_response(nvidia_provider):
|
||||
"""
|
||||
測試 JSON 格式回應
|
||||
|
||||
驗證:
|
||||
- 回應是有效 JSON
|
||||
"""
|
||||
import json
|
||||
|
||||
prompt = """回傳一個 JSON 物件,包含:
|
||||
- action: "NO_ACTION"
|
||||
- reason: "測試"
|
||||
只回傳 JSON。"""
|
||||
|
||||
response, success, _, _ = await openclaw_service._call_nvidia(prompt)
|
||||
response, success, _, _ = await nvidia_provider.chat(prompt)
|
||||
|
||||
assert success is True
|
||||
|
||||
@@ -106,9 +106,9 @@ async def test_call_nvidia_json_response(openclaw_service):
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(
|
||||
not os.getenv("NVIDIA_API_KEY") and not settings.NVIDIA_API_KEY,
|
||||
reason="NVIDIA_API_KEY not configured"
|
||||
reason="NVIDIA_API_KEY not configured",
|
||||
)
|
||||
async def test_call_nvidia_uses_model_registry(openclaw_service):
|
||||
async def test_chat_uses_model_registry(nvidia_provider):
|
||||
"""
|
||||
測試使用 ModelRegistry 取得模型
|
||||
|
||||
@@ -123,3 +123,37 @@ async def test_call_nvidia_uses_model_registry(openclaw_service):
|
||||
# 模型應該是 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)
|
||||
Reference in New Issue
Block a user