import pytest from agr_assistent.config import ProviderConfig from agr_assistent.llm.client import ( LLMClient, LLMError, TextDelta, ToolCall, ToolCalls, ToolsNotSupportedError, ) from tests.fake_llm import FakeLLMServer, Reply _TOOLS = [{"type": "function", "function": {"name": "remember", "parameters": {"type": "object"}}}] def _client(server: FakeLLMServer) -> LLMClient: return LLMClient( ProviderConfig("fake", server.base_url, "key", "fake-model"), temperature=0.5, timeout_seconds=10, ) def test_text_and_tool_calls_are_assembled(fake_llm: FakeLLMServer) -> None: fake_llm.replies.append( Reply(text="Сейчас запомню.", tool_calls=[("remember", '{"text": "Любит кофе"}')]) ) events = list(_client(fake_llm).stream_chat([{"role": "user", "content": "привет"}], _TOOLS)) text = "".join(event.text for event in events if isinstance(event, TextDelta)) assert text == "Сейчас запомню." assert events[-1] == ToolCalls([ToolCall("call_0", "remember", '{"text": "Любит кофе"}')]) assert fake_llm.requests[0]["tools"] == _TOOLS def test_tools_are_not_sent_when_absent(fake_llm: FakeLLMServer) -> None: fake_llm.replies.append(Reply(text="ок")) list(_client(fake_llm).stream_chat([{"role": "user", "content": "привет"}])) assert "tools" not in fake_llm.requests[0] @pytest.mark.parametrize( "message", [ "registry.ollama.ai/library/gemma:2b does not support tools", "No endpoints found that support tool use", ], ) def test_tools_not_supported_is_recognized(fake_llm: FakeLLMServer, message: str) -> None: fake_llm.replies.append(Reply(status=400, error=message)) with pytest.raises(ToolsNotSupportedError): list(_client(fake_llm).stream_chat([{"role": "user", "content": "привет"}], _TOOLS)) def test_other_api_errors_stay_generic(fake_llm: FakeLLMServer) -> None: fake_llm.replies.append(Reply(status=400, error="context too long")) with pytest.raises(LLMError) as error: list(_client(fake_llm).stream_chat([{"role": "user", "content": "привет"}], _TOOLS)) assert not isinstance(error.value, ToolsNotSupportedError) assert "context too long" in str(error.value)