"""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" 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)