From 86e36efe6bbae10286767b44c6a79913e5199de1 Mon Sep 17 00:00:00 2001 From: H Lohaus Date: Sat, 28 Dec 2024 16:50:08 +0100 Subject: Add Path and PathLike support when uploading images (#2514) * Add Path and PathLike support when uploading images Improve raise_for_status in special cases Move ImageResponse to providers.response module Improve OpenaiChat and OpenaiAccount providers Add Sources for web_search in OpenaiChat Add JsonConversation for import and export conversations to js Add RequestLogin response type Add TitleGeneration support in OpenaiChat and gui * Improve Docker Container Guide in README.md * Add tool calls api support, add search tool support --- etc/unittest/__main__.py | 1 + etc/unittest/mocks.py | 20 +++++++---- etc/unittest/web_search.py | 89 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 7 deletions(-) create mode 100644 etc/unittest/web_search.py (limited to 'etc') diff --git a/etc/unittest/__main__.py b/etc/unittest/__main__.py index 6594e6a2..5a29da34 100644 --- a/etc/unittest/__main__.py +++ b/etc/unittest/__main__.py @@ -8,6 +8,7 @@ from .client import * from .image_client import * from .include import * from .retry_provider import * +from .web_search import * from .models import * unittest.main() \ No newline at end of file diff --git a/etc/unittest/mocks.py b/etc/unittest/mocks.py index c43d98cc..50d1a5a4 100644 --- a/etc/unittest/mocks.py +++ b/etc/unittest/mocks.py @@ -5,40 +5,45 @@ from g4f.errors import MissingAuthError class ProviderMock(AbstractProvider): working = True + @classmethod def create_completion( - model, messages, stream, **kwargs + cls, model, messages, stream, **kwargs ): yield "Mock" class AsyncProviderMock(AsyncProvider): working = True + @classmethod async def create_async( - model, messages, **kwargs + cls, model, messages, **kwargs ): return "Mock" class AsyncGeneratorProviderMock(AsyncGeneratorProvider): working = True + @classmethod async def create_async_generator( - model, messages, stream, **kwargs + cls, model, messages, stream, **kwargs ): yield "Mock" class ModelProviderMock(AbstractProvider): working = True + @classmethod def create_completion( - model, messages, stream, **kwargs + cls, model, messages, stream, **kwargs ): yield model class YieldProviderMock(AsyncGeneratorProvider): working = True + @classmethod async def create_async_generator( - model, messages, stream, **kwargs + cls, model, messages, stream, **kwargs ): for message in messages: yield message["content"] @@ -84,8 +89,9 @@ class AsyncRaiseExceptionProviderMock(AsyncGeneratorProvider): class YieldNoneProviderMock(AsyncGeneratorProvider): working = True - + + @classmethod async def create_async_generator( - model, messages, stream, **kwargs + cls, model, messages, stream, **kwargs ): yield None \ No newline at end of file diff --git a/etc/unittest/web_search.py b/etc/unittest/web_search.py new file mode 100644 index 00000000..1c7a4c84 --- /dev/null +++ b/etc/unittest/web_search.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +import json +import unittest + +try: + from duckduckgo_search import DDGS + from duckduckgo_search.exceptions import DuckDuckGoSearchException + from bs4 import BeautifulSoup + has_requirements = True +except ImportError: + has_requirements = False + +from g4f.client import AsyncClient +from .mocks import YieldProviderMock + +DEFAULT_MESSAGES = [{'role': 'user', 'content': 'Hello'}] + +class TestIterListProvider(unittest.IsolatedAsyncioTestCase): + def setUp(self) -> None: + if not has_requirements: + self.skipTest('web search requirements not passed') + + async def test_search(self): + client = AsyncClient(provider=YieldProviderMock) + tool_calls = [ + { + "function": { + "arguments": { + "query": "search query", # content of last message: messages[-1]["content"] + "max_results": 5, # maximum number of search results + "max_words": 500, # maximum number of used words from search results for generating the response + "backend": "html", # or "lite", "api": change it to pypass rate limits + "add_text": True, # do scraping websites + "timeout": 5, # in seconds for scraping websites + "region": "wt-wt", + "instructions": "Using the provided web search results, to write a comprehensive reply to the user request.\n" + "Make sure to add the sources of cites using [[Number]](Url) notation after the reference. Example: [[0]](http://google.com)", + }, + "name": "search_tool" + }, + "type": "function" + } + ] + try: + response = await client.chat.completions.create([{"content": "", "role": "user"}], "", tool_calls=tool_calls) + self.assertIn("Using the provided web search results", response.choices[0].message.content) + except DuckDuckGoSearchException as e: + self.skipTest(f'DuckDuckGoSearchException: {e}') + + async def test_search2(self): + client = AsyncClient(provider=YieldProviderMock) + tool_calls = [ + { + "function": { + "arguments": { + "query": "search query", + }, + "name": "search_tool" + }, + "type": "function" + } + ] + try: + response = await client.chat.completions.create([{"content": "", "role": "user"}], "", tool_calls=tool_calls) + self.assertIn("Using the provided web search results", response.choices[0].message.content) + except DuckDuckGoSearchException as e: + self.skipTest(f'DuckDuckGoSearchException: {e}') + + async def test_search3(self): + client = AsyncClient(provider=YieldProviderMock) + tool_calls = [ + { + "function": { + "arguments": json.dumps({ + "query": "search query", # content of last message: messages[-1]["content"] + "max_results": 5, # maximum number of search results + "max_words": 500, # maximum number of used words from search results for generating the response + }), + "name": "search_tool" + }, + "type": "function" + } + ] + try: + response = await client.chat.completions.create([{"content": "", "role": "user"}], "", tool_calls=tool_calls) + self.assertIn("Using the provided web search results", response.choices[0].message.content) + except DuckDuckGoSearchException as e: + self.skipTest(f'DuckDuckGoSearchException: {e}') \ No newline at end of file -- cgit v1.2.3