217 lines
7.9 KiB
Python
217 lines
7.9 KiB
Python
"""LLM Provider (OpenAI Compatible) 测试"""
|
|
|
|
import json
|
|
|
|
import pytest
|
|
from pytest_httpx import HTTPXMock
|
|
|
|
from agentkit.core.exceptions import LLMProviderError
|
|
from agentkit.llm.protocol import LLMRequest, LLMResponse, TokenUsage
|
|
from agentkit.llm.providers.openai import OpenAICompatibleProvider
|
|
|
|
|
|
class TestOpenAICompatibleProviderBasic:
|
|
"""基本 chat 功能测试"""
|
|
|
|
async def test_chat_returns_llm_response(self, httpx_mock: HTTPXMock):
|
|
httpx_mock.add_response(
|
|
url="https://api.openai.com/v1/chat/completions",
|
|
json={
|
|
"id": "chatcmpl-123",
|
|
"model": "gpt-4o-mini",
|
|
"choices": [
|
|
{
|
|
"index": 0,
|
|
"message": {"role": "assistant", "content": "Hello! How can I help?"},
|
|
"finish_reason": "stop",
|
|
}
|
|
],
|
|
"usage": {"prompt_tokens": 10, "completion_tokens": 6, "total_tokens": 16},
|
|
},
|
|
)
|
|
|
|
provider = OpenAICompatibleProvider(api_key="test-key")
|
|
request = LLMRequest(
|
|
messages=[{"role": "user", "content": "Hi"}],
|
|
model="gpt-4o-mini",
|
|
)
|
|
response = await provider.chat(request)
|
|
|
|
assert isinstance(response, LLMResponse)
|
|
assert response.content == "Hello! How can I help?"
|
|
assert response.model == "gpt-4o-mini"
|
|
assert response.usage.prompt_tokens == 10
|
|
assert response.usage.completion_tokens == 6
|
|
assert response.usage.total_tokens == 16
|
|
|
|
async def test_chat_with_custom_base_url(self, httpx_mock: HTTPXMock):
|
|
httpx_mock.add_response(
|
|
url="https://api.deepseek.com/v1/chat/completions",
|
|
json={
|
|
"id": "chatcmpl-456",
|
|
"model": "deepseek-chat",
|
|
"choices": [
|
|
{
|
|
"index": 0,
|
|
"message": {"role": "assistant", "content": "DeepSeek response"},
|
|
"finish_reason": "stop",
|
|
}
|
|
],
|
|
"usage": {"prompt_tokens": 5, "completion_tokens": 3, "total_tokens": 8},
|
|
},
|
|
)
|
|
|
|
provider = OpenAICompatibleProvider(
|
|
api_key="test-key",
|
|
base_url="https://api.deepseek.com/v1",
|
|
default_model="deepseek-chat",
|
|
)
|
|
request = LLMRequest(
|
|
messages=[{"role": "user", "content": "Hi"}],
|
|
model="deepseek-chat",
|
|
)
|
|
response = await provider.chat(request)
|
|
|
|
assert response.content == "DeepSeek response"
|
|
assert response.model == "deepseek-chat"
|
|
|
|
async def test_timeout_parameter_passed_to_httpx_client(self):
|
|
"""Verify that the timeout parameter is passed to the httpx client."""
|
|
provider = OpenAICompatibleProvider(
|
|
api_key="test-key",
|
|
base_url="https://api.openai.com/v1",
|
|
timeout=180.0,
|
|
)
|
|
# httpx stores timeout config on the client
|
|
assert provider._client.timeout.read == 180.0
|
|
await provider.close()
|
|
|
|
async def test_default_timeout_is_120s(self):
|
|
"""Verify that the default timeout is 120s (not the old hardcoded 60s)."""
|
|
provider = OpenAICompatibleProvider(api_key="test-key", base_url="https://api.openai.com/v1")
|
|
assert provider._client.timeout.read == 120.0
|
|
await provider.close()
|
|
|
|
|
|
class TestOpenAICompatibleProviderToolCalls:
|
|
"""Function Calling (tool_calls) 测试"""
|
|
|
|
async def test_response_contains_tool_calls(self, httpx_mock: HTTPXMock):
|
|
httpx_mock.add_response(
|
|
url="https://api.openai.com/v1/chat/completions",
|
|
json={
|
|
"id": "chatcmpl-789",
|
|
"model": "gpt-4o",
|
|
"choices": [
|
|
{
|
|
"index": 0,
|
|
"message": {
|
|
"role": "assistant",
|
|
"content": None,
|
|
"tool_calls": [
|
|
{
|
|
"id": "call_abc",
|
|
"type": "function",
|
|
"function": {
|
|
"name": "get_weather",
|
|
"arguments": '{"city": "Beijing"}',
|
|
},
|
|
}
|
|
],
|
|
},
|
|
"finish_reason": "tool_calls",
|
|
}
|
|
],
|
|
"usage": {"prompt_tokens": 20, "completion_tokens": 15, "total_tokens": 35},
|
|
},
|
|
)
|
|
|
|
provider = OpenAICompatibleProvider(api_key="test-key")
|
|
request = LLMRequest(
|
|
messages=[{"role": "user", "content": "What's the weather in Beijing?"}],
|
|
model="gpt-4o",
|
|
tools=[
|
|
{
|
|
"type": "function",
|
|
"function": {
|
|
"name": "get_weather",
|
|
"description": "Get weather",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {"city": {"type": "string"}},
|
|
},
|
|
},
|
|
}
|
|
],
|
|
)
|
|
response = await provider.chat(request)
|
|
|
|
assert response.has_tool_calls is True
|
|
assert len(response.tool_calls) == 1
|
|
assert response.tool_calls[0].id == "call_abc"
|
|
assert response.tool_calls[0].name == "get_weather"
|
|
assert response.tool_calls[0].arguments == {"city": "Beijing"}
|
|
|
|
async def test_response_without_tool_calls(self, httpx_mock: HTTPXMock):
|
|
httpx_mock.add_response(
|
|
url="https://api.openai.com/v1/chat/completions",
|
|
json={
|
|
"id": "chatcmpl-101",
|
|
"model": "gpt-4o-mini",
|
|
"choices": [
|
|
{
|
|
"index": 0,
|
|
"message": {"role": "assistant", "content": "Just a text response"},
|
|
"finish_reason": "stop",
|
|
}
|
|
],
|
|
"usage": {"prompt_tokens": 5, "completion_tokens": 5, "total_tokens": 10},
|
|
},
|
|
)
|
|
|
|
provider = OpenAICompatibleProvider(api_key="test-key")
|
|
request = LLMRequest(
|
|
messages=[{"role": "user", "content": "Hello"}],
|
|
model="gpt-4o-mini",
|
|
)
|
|
response = await provider.chat(request)
|
|
|
|
assert response.has_tool_calls is False
|
|
assert response.content == "Just a text response"
|
|
|
|
|
|
class TestOpenAICompatibleProviderErrors:
|
|
"""API 错误处理测试"""
|
|
|
|
async def test_api_error_raises_provider_error(self, httpx_mock: HTTPXMock):
|
|
httpx_mock.add_response(
|
|
url="https://api.openai.com/v1/chat/completions",
|
|
status_code=401,
|
|
json={"error": {"message": "Invalid API key", "type": "invalid_request_error"}},
|
|
)
|
|
|
|
provider = OpenAICompatibleProvider(api_key="bad-key")
|
|
request = LLMRequest(
|
|
messages=[{"role": "user", "content": "Hi"}],
|
|
model="gpt-4o-mini",
|
|
)
|
|
|
|
with pytest.raises(LLMProviderError):
|
|
await provider.chat(request)
|
|
|
|
async def test_api_rate_limit_raises_provider_error(self, httpx_mock: HTTPXMock):
|
|
httpx_mock.add_response(
|
|
url="https://api.openai.com/v1/chat/completions",
|
|
status_code=429,
|
|
json={"error": {"message": "Rate limit exceeded", "type": "rate_limit_error"}},
|
|
)
|
|
|
|
provider = OpenAICompatibleProvider(api_key="test-key")
|
|
request = LLMRequest(
|
|
messages=[{"role": "user", "content": "Hi"}],
|
|
model="gpt-4o-mini",
|
|
)
|
|
|
|
with pytest.raises(LLMProviderError):
|
|
await provider.chat(request)
|