From 335c971f6a9cd071d18f9fffeb76df4eda8876d5 Mon Sep 17 00:00:00 2001 From: H Lohaus Date: Fri, 13 Dec 2024 22:20:58 +0100 Subject: Add multiple images support (#2478) * Add multiple images support * Add multiple images support in gui * Support multiple images in legacy client and in the api Fix some model names in provider model list * Fix unittests * Add vision and providers docs --- docs/providers.md | 575 +++++++++++++++++++++++++++++++++ docs/vision.md | 83 +++++ etc/tool/readme_table.py | 141 ++++---- etc/unittest/models.py | 18 +- g4f/Provider/Blackbox.py | 8 +- g4f/Provider/Blackbox2.py | 12 +- g4f/Provider/Copilot.py | 29 +- g4f/Provider/DeepInfraChat.py | 48 +-- g4f/Provider/Flux.py | 3 +- g4f/Provider/PerplexityLabs.py | 3 +- g4f/Provider/RubiksAI.py | 2 +- g4f/Provider/needs_auth/Gemini.py | 75 ++--- g4f/Provider/needs_auth/GeminiPro.py | 21 +- g4f/Provider/needs_auth/HuggingFace.py | 2 +- g4f/Provider/needs_auth/OpenaiAPI.py | 10 +- g4f/Provider/needs_auth/OpenaiChat.py | 152 +++++---- g4f/__init__.py | 52 +-- g4f/api/__init__.py | 15 +- g4f/client/__init__.py | 14 +- g4f/gui/client/index.html | 2 +- g4f/gui/client/static/js/chat.v1.js | 46 ++- g4f/gui/server/api.py | 2 +- g4f/gui/server/backend.py | 11 +- g4f/image.py | 3 +- g4f/requests/curl_cffi.py | 1 + g4f/typing.py | 6 +- 26 files changed, 1008 insertions(+), 326 deletions(-) create mode 100644 docs/providers.md create mode 100644 docs/vision.md diff --git a/docs/providers.md b/docs/providers.md new file mode 100644 index 00000000..d0da9491 --- /dev/null +++ b/docs/providers.md @@ -0,0 +1,575 @@ + +## Free + +### AmigoChat +| Provider | `g4f.Provider.AmigoChat` | +| -------- | ---- | +| **Website** | [amigochat.io](https://amigochat.io/chat/) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Models** | gpt-4o, gpt-4o-mini, llama-3.1-405b, mistral-nemo, gemini-flash, gemma-2b, claude-3.5-sonnet, command-r-plus, qwen-2.5-72b, grok-beta (37)| +| **Image Models (Image Generation)** | flux-realism, flux-pro, dall-e-3, flux-dev | +| **Authentication** | ❌ | +| **Streaming** | ✔️ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### Blackbox AI +| Provider | `g4f.Provider.Blackbox` | +| -------- | ---- | +| **Website** | [blackbox.ai](https://www.blackbox.ai) | +| **Status** | ![Active](https://img.shields.io/badge/Active-brightgreen) | +| **Models** | gpt-4, gpt-4o, llama-3.1-8b, llama-3.1-70b, llama-3.1-405b, gemini-pro, gemini-flash, claude-3.5-sonnet, blackboxai, blackboxai-pro (41)| +| **Image Models (Image Generation)** | flux | +| **Vision (Image Upload)** | ✔️ | +| **Authentication** | ❌ | +| **Streaming** | ✔️ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### Blackbox2 +| Provider | `g4f.Provider.Blackbox2` | +| -------- | ---- | +| **Website** | [blackbox.ai](https://www.blackbox.ai) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Models** | llama-3.1-70b (2)| +| **Image Models (Image Generation)** | flux | +| **Authentication** | ❌ | +| **Streaming** | ❌ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### ChatGpt +| Provider | `g4f.Provider.ChatGpt` | +| -------- | ---- | +| **Website** | [chatgpt.com](https://chatgpt.com) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Models** | gpt-3.5-turbo, gpt-4, gpt-4-turbo, gpt-4o, gpt-4o-mini (7)| +| **Authentication** | ❌ | +| **Streaming** | ✔️ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### ChatGptEs +| Provider | `g4f.Provider.ChatGptEs` | +| -------- | ---- | +| **Website** | [chatgpt.es](https://chatgpt.es) | +| **Status** | ![Active](https://img.shields.io/badge/Active-brightgreen) | +| **Models** | gpt-4, gpt-4o, gpt-4o-mini (3)| +| **Authentication** | ❌ | +| **Streaming** | ✔️ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### Cloudflare AI +| Provider | `g4f.Provider.Cloudflare` | +| -------- | ---- | +| **Website** | [playground.ai.cloudflare.com](https://playground.ai.cloudflare.com) | +| **Status** | ![Active](https://img.shields.io/badge/Active-brightgreen) | +| **Models** | llama-2-7b, llama-3-8b, llama-3.1-8b, llama-3.2-1b, qwen-1.5-7b (37)| +| **Authentication** | ❌ | +| **Streaming** | ✔️ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### Microsoft Copilot +| Provider | `g4f.Provider.Copilot` | +| -------- | ---- | +| **Website** | [copilot.microsoft.com](https://copilot.microsoft.com) | +| **Status** | ![Active](https://img.shields.io/badge/Active-brightgreen) | +| **Models** | gpt-4 (1)| +| **Authentication** | ❌ | +| **Streaming** | ✔️ | +| **System message** | ❌ | +| **Message history** | ❌ | +### DuckDuckGo AI Chat +| Provider | `g4f.Provider.DDG` | +| -------- | ---- | +| **Website** | [duckduckgo.com](https://duckduckgo.com/aichat) | +| **Status** | ![Active](https://img.shields.io/badge/Active-brightgreen) | +| **Models** | gpt-4, gpt-4o, gpt-4o-mini, llama-3.1-70b, mixtral-8x7b, claude-3-haiku (8)| +| **Authentication** | ❌ | +| **Streaming** | ✔️ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### DarkAI +| Provider | `g4f.Provider.DarkAI` | +| -------- | ---- | +| **Website** | [darkai.foundation](https://darkai.foundation/chat) | +| **Status** | ![Active](https://img.shields.io/badge/Active-brightgreen) | +| **Models** | gpt-3.5-turbo, gpt-4o, llama-3.1-70b (3)| +| **Authentication** | ❌ | +| **Streaming** | ✔️ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### Flux (HuggingSpace) +| Provider | `g4f.Provider.Flux` | +| -------- | ---- | +| **Website** | [black-forest-labs-flux-1-dev.hf.space](https://black-forest-labs-flux-1-dev.hf.space) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Image Models (Image Generation)** | flux-dev | +| **Authentication** | ❌ | +| **Streaming** | ✔️ | +| **System message** | ❌ | +| **Message history** | ❌ | +### Free2GPT +| Provider | `g4f.Provider.Free2GPT` | +| -------- | ---- | +| **Website** | [chat10.free2gpt.xyz](https://chat10.free2gpt.xyz) | +| **Status** | ![Active](https://img.shields.io/badge/Active-brightgreen) | +| **Authentication** | ❌ | +| **Streaming** | ✔️ | +| **System message** | ❌ | +| **Message history** | ✔️ | +### FreeGpt +| Provider | `g4f.Provider.FreeGpt` | +| -------- | ---- | +| **Website** | [freegptsnav.aifree.site](https://freegptsnav.aifree.site) | +| **Status** | ![Active](https://img.shields.io/badge/Active-brightgreen) | +| **Models** | gemini-pro (1)| +| **Authentication** | ❌ | +| **Streaming** | ✔️ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### GizAI +| Provider | `g4f.Provider.GizAI` | +| -------- | ---- | +| **Website** | [app.giz.ai](https://app.giz.ai/assistant) | +| **Status** | ![Active](https://img.shields.io/badge/Active-brightgreen) | +| **Models** | gemini-flash (1)| +| **Authentication** | ❌ | +| **Streaming** | ❌ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### HuggingFace +| Provider | `g4f.Provider.HuggingFace` | +| -------- | ---- | +| **Website** | [huggingface.co](https://huggingface.co/chat) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Models** | llama-3.2-11b, llama-3.3-70b, mistral-nemo, hermes-3, phi-3.5-mini, command-r-plus, qwen-2.5-72b, qwen-2.5-coder-32b, qwq-32b, nemotron-70b (11)| +| **Image Models (Image Generation)** | flux-dev | +| **Authentication** | ❌ | +| **Streaming** | ✔️ | +| **System message** | ❌ | +| **Message history** | ✔️ | +### Liaobots +| Provider | `g4f.Provider.Liaobots` | +| -------- | ---- | +| **Website** | [liaobots.site](https://liaobots.site) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Models** | gpt-4, gpt-4o, gpt-4o-mini, o1-preview, o1-mini, gemini-pro, gemini-flash, claude-3-opus, claude-3-sonnet, claude-3.5-sonnet, grok-beta (14)| +| **Authentication** | ❌ | +| **Streaming** | ✔️ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### GPT4All +| Provider | `g4f.Provider.Local` | +| -------- | ---- | +| **Website** | ❌ | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Authentication** | ❌ | +| **Streaming** | ✔️ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### Meta AI +| Provider | `g4f.Provider.MetaAI` | +| -------- | ---- | +| **Website** | [meta.ai](https://www.meta.ai) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Models** | meta-ai (1)| +| **Authentication** | ❌ | +| **Streaming** | ✔️ | +| **System message** | ❌ | +| **Message history** | ❌ | +### Mhystical +| Provider | `g4f.Provider.Mhystical` | +| -------- | ---- | +| **Website** | [api.mhystical.cc](https://api.mhystical.cc) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Models** | gpt-4 (1)| +| **Authentication** | ❌ | +| **Streaming** | ❌ | +| **System message** | ❌ | +| **Message history** | ✔️ | +### Ollama +| Provider | `g4f.Provider.Ollama` | +| -------- | ---- | +| **Website** | [ollama.com](https://ollama.com) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Authentication** | ❌ | +| **Streaming** | ✔️ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### OpenAI ChatGPT +| Provider | `g4f.Provider.OpenaiChat` | +| -------- | ---- | +| **Website** | [chatgpt.com](https://chatgpt.com) | +| **Status** | ![Active](https://img.shields.io/badge/Active-brightgreen) | +| **Models** | gpt-4, gpt-4o, gpt-4o-mini, o1-preview, o1-mini (8)| +| **Vision (Image Upload)** | ✔️ | +| **Authentication** | ❌ | +| **Streaming** | ✔️ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### PerplexityLabs +| Provider | `g4f.Provider.PerplexityLabs` | +| -------- | ---- | +| **Website** | [labs.perplexity.ai](https://labs.perplexity.ai) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Models** | llama-3.1-8b, llama-3.1-70b, llama-3.3-70b, sonar-online, sonar-chat, lfm-40b (8)| +| **Authentication** | ❌ | +| **Streaming** | ✔️ | +| **System message** | ❌ | +| **Message history** | ❌ | +### Pi +| Provider | `g4f.Provider.Pi` | +| -------- | ---- | +| **Website** | [pi.ai](https://pi.ai/talk) | +| **Status** | ![Active](https://img.shields.io/badge/Active-brightgreen) | +| **Authentication** | ❌ | +| **Streaming** | ✔️ | +| **System message** | ❌ | +| **Message history** | ❌ | +### Pizzagpt +| Provider | `g4f.Provider.Pizzagpt` | +| -------- | ---- | +| **Website** | [pizzagpt.it](https://www.pizzagpt.it) | +| **Status** | ![Active](https://img.shields.io/badge/Active-brightgreen) | +| **Models** | gpt-4o-mini (1)| +| **Authentication** | ❌ | +| **Streaming** | ✔️ | +| **System message** | ❌ | +| **Message history** | ❌ | +### Pollinations AI +| Provider | `g4f.Provider.PollinationsAI` | +| -------- | ---- | +| **Website** | [pollinations.ai](https://pollinations.ai) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Models** | gpt-4, gpt-4o, llama-3.1-70b, mistral-nemo, mistral-large, claude-3.5-sonnet, command-r, qwen-2.5-coder-32b, p1, evil, midijourney, unity, rtist (25)| +| **Image Models (Image Generation)** | flux, flux-realism, flux-cablyai, flux-anime, flux-3d, any-dark, flux-pro, turbo, midjourney, dall-e-3 | +| **Authentication** | ❌ | +| **Streaming** | ✔️ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### Prodia +| Provider | `g4f.Provider.Prodia` | +| -------- | ---- | +| **Website** | [app.prodia.com](https://app.prodia.com) | +| **Status** | ![Active](https://img.shields.io/badge/Active-brightgreen) | +| **Authentication** | ❌ | +| **Streaming** | ✔️ | +| **System message** | ❌ | +| **Message history** | ❌ | +### ReplicateHome +| Provider | `g4f.Provider.ReplicateHome` | +| -------- | ---- | +| **Website** | [replicate.com](https://replicate.com) | +| **Status** | ![Active](https://img.shields.io/badge/Active-brightgreen) | +| **Models** | gemma-2b (4)| +| **Image Models (Image Generation)** | sd-3, sdxl, playground-v2.5 | +| **Authentication** | ❌ | +| **Streaming** | ✔️ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### Rubiks AI +| Provider | `g4f.Provider.RubiksAI` | +| -------- | ---- | +| **Website** | [rubiks.ai](https://rubiks.ai) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Models** | gpt-4o, gpt-4o-mini, o1-mini, llama-3.1-70b, claude-3.5-sonnet, grok-beta (8)| +| **Authentication** | ❌ | +| **Streaming** | ✔️ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### TeachAnything +| Provider | `g4f.Provider.TeachAnything` | +| -------- | ---- | +| **Website** | [teach-anything.com](https://www.teach-anything.com) | +| **Status** | ![Active](https://img.shields.io/badge/Active-brightgreen) | +| **Models** | llama-3.1-70b (1)| +| **Authentication** | ❌ | +| **Streaming** | ✔️ | +| **System message** | ❌ | +| **Message history** | ❌ | +### TheB.AI +| Provider | `g4f.Provider.Theb` | +| -------- | ---- | +| **Website** | [beta.theb.ai](https://beta.theb.ai) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Authentication** | ❌ | +| **Streaming** | ✔️ | +| **System message** | ❌ | +| **Message history** | ❌ | +### You.com +| Provider | `g4f.Provider.You` | +| -------- | ---- | +| **Website** | [you.com](https://you.com) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Models** | gpt-4, gpt-4-turbo, gpt-4o, gpt-4o-mini, llama-3.1-70b, claude-3-opus, claude-3-sonnet, claude-3-haiku, claude-3.5-sonnet, command-r-plus, command-r (20)| +| **Authentication** | ❌ | +| **Streaming** | ✔️ | +| **System message** | ❌ | +| **Message history** | ❌ | + +## Auth + +### Airforce +| Provider | `g4f.Provider.Airforce` | +| -------- | ---- | +| **Website** | [llmplayground.net](https://llmplayground.net) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Models** | gpt-4, gpt-4-turbo, gpt-4o, gpt-4o-mini, o1-mini, llama-2-7b, llama-3.1-8b, llama-3.1-70b, hermes-2-dpo, hermes-2-pro, phi-2, openchat-3.5, deepseek-coder, german-7b, openhermes-2.5, lfm-40b, zephyr-7b, neural-7b, evil (40)| +| **Image Models (Image Generation)** | flux, flux-realism, flux-anime, flux-3d, flux-disney, flux-pixel, flux-4o, any-dark, midjourney, dall-e-3, sdxl, flux-pro | +| **Authentication** | ✔️ | +| **Streaming** | ✔️ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### Microsoft Designer in Bing +| Provider | `g4f.Provider.BingCreateImages` | +| -------- | ---- | +| **Website** | [bing.com](https://www.bing.com/images/create) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Image Models (Image Generation)** | dall-e-3 | +| **Authentication** | ✔️ | +| **Streaming** | ✔️ | +| **System message** | ❌ | +| **Message history** | ❌ | +### Cerebras Inference +| Provider | `g4f.Provider.Cerebras` | +| -------- | ---- | +| **Website** | [inference.cerebras.ai](https://inference.cerebras.ai/) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Models** | llama-3.1-8b, llama-3.1-70b (2)| +| **Authentication** | ✔️ | +| **Streaming** | ✔️ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### Microsoft Copilot +| Provider | `g4f.Provider.CopilotAccount` | +| -------- | ---- | +| **Website** | [copilot.microsoft.com](https://copilot.microsoft.com) | +| **Status** | ![Active](https://img.shields.io/badge/Active-brightgreen) | +| **Image Models (Image Generation)** | dall-e-3 | +| **Authentication** | ✔️ | +| **Streaming** | ✔️ | +| **System message** | ❌ | +| **Message history** | ❌ | +### DeepInfra +| Provider | `g4f.Provider.DeepInfra` | +| -------- | ---- | +| **Website** | [deepinfra.com](https://deepinfra.com) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Authentication** | ✔️ | +| **Streaming** | ✔️ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### DeepInfra Chat +| Provider | `g4f.Provider.DeepInfraChat` | +| -------- | ---- | +| **Website** | [deepinfra.com](https://deepinfra.com/chat) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Models** | llama-3.1-8b, llama-3.1-70b, qwen-2-72b, qwen-2.5-coder-32b, qwq-32b, wizardlm-2-8x22b, nemotron-70b (7)| +| **Authentication** | ✔️ | +| **Streaming** | ✔️ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### DeepInfraImage +| Provider | `g4f.Provider.DeepInfraImage` | +| -------- | ---- | +| **Website** | [deepinfra.com](https://deepinfra.com) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Authentication** | ✔️ | +| **Streaming** | ✔️ | +| **System message** | ❌ | +| **Message history** | ❌ | +### Google Gemini +| Provider | `g4f.Provider.Gemini` | +| -------- | ---- | +| **Website** | [gemini.google.com](https://gemini.google.com) | +| **Status** | ![Active](https://img.shields.io/badge/Active-brightgreen) | +| **Models** | gemini-pro, gemini-flash (3)| +| **Image Models (Image Generation)** | gemini | +| **Authentication** | ✔️ | +| **Streaming** | ✔️ | +| **System message** | ❌ | +| **Message history** | ❌ | +### Google Gemini API +| Provider | `g4f.Provider.GeminiPro` | +| -------- | ---- | +| **Website** | [ai.google.dev](https://ai.google.dev) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Models** | gemini-pro, gemini-flash (4)| +| **Authentication** | ✔️ | +| **Streaming** | ✔️ | +| **System message** | ❌ | +| **Message history** | ✔️ | +### GigaChat +| Provider | `g4f.Provider.GigaChat` | +| -------- | ---- | +| **Website** | [developers.sber.ru](https://developers.sber.ru/gigachat) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Models** | GigaChat:latest (3)| +| **Authentication** | ✔️ | +| **Streaming** | ✔️ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### GithubCopilot +| Provider | `g4f.Provider.GithubCopilot` | +| -------- | ---- | +| **Website** | [github.com](https://github.com/copilot) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Models** | gpt-4o, o1-preview, o1-mini, claude-3.5-sonnet (4)| +| **Authentication** | ✔️ | +| **Streaming** | ✔️ | +| **System message** | ❌ | +| **Message history** | ❌ | +### Groq +| Provider | `g4f.Provider.Groq` | +| -------- | ---- | +| **Website** | [console.groq.com](https://console.groq.com/playground) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Models** | mixtral-8x7b (18)| +| **Authentication** | ✔️ | +| **Streaming** | ✔️ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### HuggingChat +| Provider | `g4f.Provider.HuggingChat` | +| -------- | ---- | +| **Website** | [huggingface.co](https://huggingface.co/chat) | +| **Status** | ![Active](https://img.shields.io/badge/Active-brightgreen) | +| **Models** | llama-3.2-11b, llama-3.3-70b, mistral-nemo, hermes-3, phi-3.5-mini, command-r-plus, qwen-2.5-72b, qwen-2.5-coder-32b, qwq-32b, nemotron-70b (11)| +| **Image Models (Image Generation)** | flux-dev | +| **Authentication** | ✔️ | +| **Streaming** | ✔️ | +| **System message** | ❌ | +| **Message history** | ❌ | +### HuggingFace (Inference API) +| Provider | `g4f.Provider.HuggingFaceAPI` | +| -------- | ---- | +| **Website** | [api-inference.huggingface.co](https://api-inference.huggingface.co) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Authentication** | ✔️ | +| **Streaming** | ✔️ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### Meta AI +| Provider | `g4f.Provider.MetaAIAccount` | +| -------- | ---- | +| **Website** | [meta.ai](https://www.meta.ai) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Models** | meta-ai (1)| +| **Authentication** | ✔️ | +| **Streaming** | ✔️ | +| **System message** | ❌ | +| **Message history** | ❌ | +### Microsoft Designer +| Provider | `g4f.Provider.MicrosoftDesigner` | +| -------- | ---- | +| **Website** | [designer.microsoft.com](https://designer.microsoft.com) | +| **Status** | ![Active](https://img.shields.io/badge/Active-brightgreen) | +| **Image Models (Image Generation)** | dall-e-3 | +| **Authentication** | ✔️ | +| **Streaming** | ✔️ | +| **System message** | ❌ | +| **Message history** | ❌ | +### OpenAI API +| Provider | `g4f.Provider.OpenaiAPI` | +| -------- | ---- | +| **Website** | [platform.openai.com](https://platform.openai.com) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Authentication** | ✔️ | +| **Streaming** | ✔️ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### OpenAI ChatGPT +| Provider | `g4f.Provider.OpenaiAccount` | +| -------- | ---- | +| **Website** | [chatgpt.com](https://chatgpt.com) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Models** | gpt-4o-mini, o1-preview, o1-mini (9)| +| **Image Models (Image Generation)** | dall-e-3 | +| **Vision (Image Upload)** | ✔️ | +| **Authentication** | ✔️ | +| **Streaming** | ✔️ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### Perplexity API +| Provider | `g4f.Provider.PerplexityApi` | +| -------- | ---- | +| **Website** | [perplexity.ai](https://www.perplexity.ai) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Authentication** | ✔️ | +| **Streaming** | ✔️ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### Poe +| Provider | `g4f.Provider.Poe` | +| -------- | ---- | +| **Website** | [poe.com](https://poe.com) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Authentication** | ✔️ | +| **Streaming** | ✔️ | +| **System message** | ❌ | +| **Message history** | ❌ | +### Raycast +| Provider | `g4f.Provider.Raycast` | +| -------- | ---- | +| **Website** | [raycast.com](https://raycast.com) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Authentication** | ✔️ | +| **Streaming** | ✔️ | +| **System message** | ❌ | +| **Message history** | ❌ | +### Reka +| Provider | `g4f.Provider.Reka` | +| -------- | ---- | +| **Website** | [chat.reka.ai](https://chat.reka.ai/) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Authentication** | ✔️ | +| **Streaming** | ✔️ | +| **System message** | ❌ | +| **Message history** | ❌ | +### Replicate +| Provider | `g4f.Provider.Replicate` | +| -------- | ---- | +| **Website** | [replicate.com](https://replicate.com) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Authentication** | ✔️ | +| **Streaming** | ✔️ | +| **System message** | ❌ | +| **Message history** | ❌ | +### TheB.AI API +| Provider | `g4f.Provider.ThebApi` | +| -------- | ---- | +| **Website** | [theb.ai](https://theb.ai) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Models** | gpt-3.5-turbo, gpt-4, gpt-4-turbo (21)| +| **Authentication** | ✔️ | +| **Streaming** | ✔️ | +| **System message** | ✔️ | +| **Message history** | ✔️ | +### WhiteRabbitNeo +| Provider | `g4f.Provider.WhiteRabbitNeo` | +| -------- | ---- | +| **Website** | [whiterabbitneo.com](https://www.whiterabbitneo.com) | +| **Status** | ![Unknown](https://img.shields.io/badge/Unknown-grey) | +| **Authentication** | ✔️ | +| **Streaming** | ✔️ | +| **System message** | ❌ | +| **Message history** | ✔️ | +-------------------------------------------------- +| Label | Provider | Image Model | Vision Model | Website | +| ----- | -------- | ----------- | ------------ | ------- | +| Airforce | `g4f.Provider.Airforce` | flux, flux-realism, flux-anime, flux-3d, flux-disney, flux-pixel, flux-4o, any-dark, midjourney, dall-e-3, sdxl, flux-pro| ❌ | [llmplayground.net](https://llmplayground.net) | +| AmigoChat | `g4f.Provider.AmigoChat` | flux-realism, flux-pro, dall-e-3, flux-dev| ❌ | [amigochat.io](https://amigochat.io/chat/) | +| Microsoft Designer in Bing | `g4f.Provider.BingCreateImages` | dall-e-3| ❌ | [bing.com](https://www.bing.com/images/create) | +| Blackbox AI | `g4f.Provider.Blackbox` | flux| ✔️ | [blackbox.ai](https://www.blackbox.ai) | +| Blackbox2 | `g4f.Provider.Blackbox2` | flux| ❌ | [blackbox.ai](https://www.blackbox.ai) | +| Microsoft Copilot | `g4f.Provider.CopilotAccount` | dall-e-3| ❌ | [copilot.microsoft.com](https://copilot.microsoft.com) | +| DeepInfraImage | `g4f.Provider.DeepInfraImage` | | ❌ | [deepinfra.com](https://deepinfra.com) | +| Flux (HuggingSpace) | `g4f.Provider.Flux` | flux-dev| ❌ | [black-forest-labs-flux-1-dev.hf.space](https://black-forest-labs-flux-1-dev.hf.space) | +| Google Gemini | `g4f.Provider.Gemini` | gemini| ❌ | [gemini.google.com](https://gemini.google.com) | +| HuggingChat | `g4f.Provider.HuggingChat` | flux-dev| ❌ | [huggingface.co](https://huggingface.co/chat) | +| HuggingFace | `g4f.Provider.HuggingFace` | flux-dev| ❌ | [huggingface.co](https://huggingface.co/chat) | +| Meta AI | `g4f.Provider.MetaAIAccount` | | ❌ | [meta.ai](https://www.meta.ai) | +| Microsoft Designer | `g4f.Provider.MicrosoftDesigner` | dall-e-3| ❌ | [designer.microsoft.com](https://designer.microsoft.com) | +| OpenAI ChatGPT | `g4f.Provider.OpenaiAccount` | dall-e-3, gpt-4, gpt-4o, dall-e-3| ✔️ | [chatgpt.com](https://chatgpt.com) | +| OpenAI ChatGPT | `g4f.Provider.OpenaiChat` | ❌| ✔️ | [chatgpt.com](https://chatgpt.com) | +| Pollinations AI | `g4f.Provider.PollinationsAI` | flux, flux-realism, flux-cablyai, flux-anime, flux-3d, any-dark, flux-pro, turbo, midjourney, dall-e-3| ❌ | [pollinations.ai](https://pollinations.ai) | +| Prodia | `g4f.Provider.Prodia` | | ❌ | [app.prodia.com](https://app.prodia.com) | +| ReplicateHome | `g4f.Provider.ReplicateHome` | sd-3, sdxl, playground-v2.5| ❌ | [replicate.com](https://replicate.com) | +| You.com | `g4f.Provider.You` | | ❌ | [you.com](https://you.com) | \ No newline at end of file diff --git a/docs/vision.md b/docs/vision.md new file mode 100644 index 00000000..34a8ca39 --- /dev/null +++ b/docs/vision.md @@ -0,0 +1,83 @@ +## Vision Support in Chat Completion + +This documentation provides an overview of how to integrate vision support into chat completions using an API and a client. It includes examples to guide you through the process. + +### Example with the API + +To use vision support in chat completion with the API, follow the example below: + +```python +import requests +import json +from g4f.image import to_data_uri +from g4f.requests.raise_for_status import raise_for_status + +url = "http://localhost:8080/v1/chat/completions" +body = { + "model": "", + "provider": "Copilot", + "messages": [ + {"role": "user", "content": "what are on this image?"} + ], + "images": [ + ["data:image/jpeg;base64,...", "cat.jpeg"] + ] +} +response = requests.post(url, json=body, headers={"g4f-api-key": "secret"}) +raise_for_status(response) +print(response.json()) +``` + +In this example: +- `url` is the endpoint for the chat completion API. +- `body` contains the model, provider, messages, and images. +- `messages` is a list of message objects with roles and content. +- `images` is a list of image data in Data URI format and optional filenames. +- `response` stores the API response. + +### Example with the Client + +To use vision support in chat completion with the client, follow the example below: + +```python +import g4f +import g4f.Provider + +def chat_completion(prompt): + client = g4f.Client(provider=g4f.Provider.Blackbox) + images = [ + [open("docs/images/waterfall.jpeg", "rb"), "waterfall.jpeg"], + [open("docs/images/cat.webp", "rb"), "cat.webp"] + ] + response = client.chat.completions.create([{"content": prompt, "role": "user"}], "", images=images) + print(response.choices[0].message.content) + +prompt = "what are on this images?" +chat_completion(prompt) +``` + +``` +**Image 1** + +* A waterfall with a rainbow +* Lush greenery surrounding the waterfall +* A stream flowing from the waterfall + +**Image 2** + +* A white cat with blue eyes +* A bird perched on a window sill +* Sunlight streaming through the window +``` + +In this example: +- `client` initializes a new client with the specified provider. +- `images` is a list of image data and optional filenames. +- `response` stores the response from the client. +- The `chat_completion` function prints the chat completion output. + +### Notes + +- Multiple images can be sent. Each image has two data parts: image data (in Data URI format for the API) and an optional filename. +- The client supports bytes, IO objects, and PIL images as input. +- Ensure you use a provider that supports vision and multiple images. \ No newline at end of file diff --git a/etc/tool/readme_table.py b/etc/tool/readme_table.py index e89f861b..9f95e680 100644 --- a/etc/tool/readme_table.py +++ b/etc/tool/readme_table.py @@ -4,7 +4,9 @@ import asyncio from g4f import models, ChatCompletion from g4f.providers.types import BaseRetryProvider, ProviderType -from etc.testing._providers import get_providers +from g4f.providers.base_provider import ProviderModelMixin +from g4f.Provider import __providers__ +from g4f.models import _all_models from g4f import debug debug.logging = True @@ -35,53 +37,76 @@ def test_async_list(providers: list[ProviderType]): return responses def print_providers(): - - providers = get_providers() + providers = [provider for provider in __providers__ if provider.working] responses = test_async_list(providers) - - for type in ("GPT-4", "GPT-3.5", "Other"): - lines = [ + lines = [] + for type in ("Free", "Auth"): + lines += [ "", - f"### {type}", + f"## {type}", "", - "| Website | Provider | GPT-3.5 | GPT-4 | Stream | Status | Auth |", - "| ------ | ------- | ------- | ----- | ------ | ------ | ---- |", ] - for is_working in (True, False): - for idx, _provider in enumerate(providers): - if is_working != _provider.working: - continue - do_continue = False - if type == "GPT-4" and _provider.supports_gpt_4: - do_continue = True - elif type == "GPT-3.5" and not _provider.supports_gpt_4 and _provider.supports_gpt_35_turbo: - do_continue = True - elif type == "Other" and not _provider.supports_gpt_4 and not _provider.supports_gpt_35_turbo: - do_continue = True - if not do_continue: - continue + for idx, _provider in enumerate(providers): + do_continue = False + if type == "Auth" and _provider.needs_auth: + do_continue = True + elif type == "Free" and not _provider.needs_auth: + do_continue = True + if not do_continue: + continue + + lines.append( + f"### {getattr(_provider, 'label', _provider.__name__)}", + ) + provider_name = f"`g4f.Provider.{_provider.__name__}`" + lines.append(f"| Provider | {provider_name} |") + lines.append("| -------- | ---- |") + + if _provider.url: netloc = urlparse(_provider.url).netloc.replace("www.", "") website = f"[{netloc}]({_provider.url})" - - provider_name = f"`g4f.Provider.{_provider.__name__}`" - - has_gpt_35 = "✔️" if _provider.supports_gpt_35_turbo else "❌" - has_gpt_4 = "✔️" if _provider.supports_gpt_4 else "❌" - stream = "✔️" if _provider.supports_stream else "❌" - if _provider.working: + else: + website = "❌" + + message_history = "✔️" if _provider.supports_message_history else "❌" + system = "✔️" if _provider.supports_system_message else "❌" + stream = "✔️" if _provider.supports_stream else "❌" + if _provider.working: + status = '![Active](https://img.shields.io/badge/Active-brightgreen)' + if responses[idx]: status = '![Active](https://img.shields.io/badge/Active-brightgreen)' - if responses[idx]: - status = '![Active](https://img.shields.io/badge/Active-brightgreen)' - else: - status = '![Unknown](https://img.shields.io/badge/Unknown-grey)' else: - status = '![Inactive](https://img.shields.io/badge/Inactive-red)' - auth = "✔️" if _provider.needs_auth else "❌" - - lines.append( - f"| {website} | {provider_name} | {has_gpt_35} | {has_gpt_4} | {stream} | {status} | {auth} |" - ) - print("\n".join(lines)) + status = '![Unknown](https://img.shields.io/badge/Unknown-grey)' + else: + status = '![Inactive](https://img.shields.io/badge/Inactive-red)' + auth = "✔️" if _provider.needs_auth else "❌" + + lines.append(f"| **Website** | {website} | \n| **Status** | {status} |") + + if issubclass(_provider, ProviderModelMixin): + try: + all_models = _provider.get_models() + models = [model for model in _all_models if model in all_models or model in _provider.model_aliases] + image_models = _provider.image_models + if image_models: + for alias, name in _provider.model_aliases.items(): + if alias in _all_models and name in image_models: + image_models.append(alias) + image_models = [model for model in image_models if model in _all_models] + if image_models: + models = [model for model in models if model not in image_models] + if models: + lines.append(f"| **Models** | {', '.join(models)} ({len(all_models)})|") + if image_models: + lines.append(f"| **Image Models (Image Generation)** | {', '.join(image_models)} |") + if hasattr(_provider, "vision_models"): + lines.append(f"| **Vision (Image Upload)** | ✔️ |") + except: + pass + + lines.append(f"| **Authentication** | {auth} | \n| **Streaming** | {stream} |") + lines.append(f"| **System message** | {system} | \n| **Message history** | {message_history} |") + return lines def print_models(): base_provider_names = { @@ -123,30 +148,34 @@ def print_models(): lines.append(f"| {name} | {base_provider} | {provider_name} | {website} |") - print("\n".join(lines)) + return lines def print_image_models(): lines = [ "| Label | Provider | Image Model | Vision Model | Website |", "| ----- | -------- | ----------- | ------------ | ------- |", ] - from g4f.gui.server.api import Api - for image_model in Api.get_image_models(): - provider_url = image_model["url"] + for provider in [provider for provider in __providers__ if provider.working and getattr(provider, "image_models", None) or getattr(provider, "vision_models", None)]: + provider_url = provider.url if provider.url else "❌" netloc = urlparse(provider_url).netloc.replace("www.", "") website = f"[{netloc}]({provider_url})" - label = image_model["provider"] if image_model["label"] is None else image_model["label"] - if image_model["image_model"] is None: - image_model["image_model"] = "❌" - if image_model["vision_model"] is None: - image_model["vision_model"] = "❌" - lines.append(f'| {label} | `g4f.Provider.{image_model["provider"]}` | {image_model["image_model"]}| {image_model["vision_model"]} | {website} |') + label = getattr(provider, "label", provider.__name__) + if provider.image_models: + image_models = ", ".join([model for model in provider.image_models if model in _all_models]) + else: + image_models = "❌" + if hasattr(provider, "vision_models"): + vision_models = "✔️" + else: + vision_models = "❌" + lines.append(f'| {label} | `g4f.Provider.{provider.__name__}` | {image_models}| {vision_models} | {website} |') - print("\n".join(lines)) + return lines if __name__ == "__main__": - #print_providers() - #print("\n", "-" * 50, "\n") - #print_models() - print("\n", "-" * 50, "\n") - print_image_models() \ No newline at end of file + with open("docs/providers.md", "w") as f: + f.write("\n".join(print_providers())) + f.write(f"\n{'-' * 50} \n") + #f.write("\n".join(print_models())) + #f.write(f"\n{'-' * 50} \n") + f.write("\n".join(print_image_models())) \ No newline at end of file diff --git a/etc/unittest/models.py b/etc/unittest/models.py index b9b452d7..39fdbb29 100644 --- a/etc/unittest/models.py +++ b/etc/unittest/models.py @@ -4,7 +4,7 @@ import asyncio from g4f.models import __models__ from g4f.providers.base_provider import BaseProvider, ProviderModelMixin -from g4f.models import Model +from g4f.errors import MissingRequirementsError, MissingAuthError class TestProviderHasModel(unittest.IsolatedAsyncioTestCase): cache: dict = {} @@ -13,11 +13,17 @@ class TestProviderHasModel(unittest.IsolatedAsyncioTestCase): for model, providers in __models__.values(): for provider in providers: if issubclass(provider, ProviderModelMixin): - if model.name not in provider.model_aliases: - await asyncio.wait_for(self.provider_has_model(provider, model), 10) + if model.name in provider.model_aliases: + model_name = provider.model_aliases[model.name] + else: + model_name = model.name + await asyncio.wait_for(self.provider_has_model(provider, model_name), 10) - async def provider_has_model(self, provider: Type[BaseProvider], model: Model): + async def provider_has_model(self, provider: Type[BaseProvider], model: str): if provider.__name__ not in self.cache: - self.cache[provider.__name__] = provider.get_models() + try: + self.cache[provider.__name__] = provider.get_models() + except (MissingRequirementsError, MissingAuthError): + return if self.cache[provider.__name__]: - self.assertIn(model.name, self.cache[provider.__name__], provider.__name__) \ No newline at end of file + self.assertIn(model, self.cache[provider.__name__], provider.__name__) \ No newline at end of file diff --git a/g4f/Provider/Blackbox.py b/g4f/Provider/Blackbox.py index fec0a8a9..68ac7852 100644 --- a/g4f/Provider/Blackbox.py +++ b/g4f/Provider/Blackbox.py @@ -10,7 +10,7 @@ import aiohttp import json from pathlib import Path -from ..typing import AsyncResult, Messages, ImageType +from ..typing import AsyncResult, Messages, ImagesType from .base_provider import AsyncGeneratorProvider, ProviderModelMixin from ..image import ImageResponse, to_data_uri from ..cookies import get_cookies_dir @@ -197,8 +197,7 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin): prompt: str = None, proxy: str = None, web_search: bool = False, - image: ImageType = None, - image_name: str = None, + images: ImagesType = None, top_p: float = 0.9, temperature: float = 0.5, max_tokens: int = 1024, @@ -212,13 +211,14 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin): messages = [{"id": message_id, "content": formatted_message, "role": "user"}] - if image is not None: + if images is not None: messages[-1]['data'] = { "imagesData": [ { "filePath": f"MultipleFiles/{image_name}", "contents": to_data_uri(image) } + for image, image_name in images ], "fileText": "", "title": "" diff --git a/g4f/Provider/Blackbox2.py b/g4f/Provider/Blackbox2.py index cae3141e..d09cbf12 100644 --- a/g4f/Provider/Blackbox2.py +++ b/g4f/Provider/Blackbox2.py @@ -3,7 +3,7 @@ from __future__ import annotations import random import asyncio from aiohttp import ClientSession -from typing import Union, AsyncGenerator +from typing import AsyncGenerator from ..typing import AsyncResult, Messages from ..image import ImageResponse @@ -37,12 +37,15 @@ class Blackbox2(AsyncGeneratorProvider, ProviderModelMixin): max_retries: int = 3, delay: int = 1, **kwargs - ) -> AsyncGenerator: + ) -> AsyncResult: + if not model: + model = cls.default_model if model in cls.chat_models: async for result in cls._generate_text(model, messages, proxy, max_retries, delay): yield result elif model in cls.image_models: - async for result in cls._generate_image(model, messages, proxy): + prompt = messages[-1]["content"] if prompt is None else prompt + async for result in cls._generate_image(model, prompt, proxy): yield result else: raise ValueError(f"Unsupported model: {model}") @@ -87,14 +90,13 @@ class Blackbox2(AsyncGeneratorProvider, ProviderModelMixin): async def _generate_image( cls, model: str, - messages: Messages, + prompt: str, proxy: str = None ) -> AsyncGenerator: headers = cls._get_headers() api_endpoint = cls.api_endpoints[model] async with ClientSession(headers=headers) as session: - prompt = messages[-1]["content"] data = { "query": prompt } diff --git a/g4f/Provider/Copilot.py b/g4f/Provider/Copilot.py index 23f175ac..cc0cb6ab 100644 --- a/g4f/Provider/Copilot.py +++ b/g4f/Provider/Copilot.py @@ -18,7 +18,7 @@ except ImportError: from .base_provider import AbstractProvider, ProviderModelMixin, BaseConversation from .helper import format_prompt -from ..typing import CreateResult, Messages, ImageType +from ..typing import CreateResult, Messages, ImagesType from ..errors import MissingRequirementsError, NoValidHarFileError from ..requests.raise_for_status import raise_for_status from ..providers.asyncio import get_running_loop @@ -58,7 +58,7 @@ class Copilot(AbstractProvider, ProviderModelMixin): stream: bool = False, proxy: str = None, timeout: int = 900, - image: ImageType = None, + images: ImagesType = None, conversation: Conversation = None, return_conversation: bool = False, web_search: bool = True, @@ -69,7 +69,7 @@ class Copilot(AbstractProvider, ProviderModelMixin): websocket_url = cls.websocket_url headers = None - if cls.needs_auth or image is not None: + if cls.needs_auth or images is not None: if cls._access_token is None: try: cls._access_token, cls._cookies = readHAR(cls.url) @@ -112,22 +112,23 @@ class Copilot(AbstractProvider, ProviderModelMixin): prompt = messages[-1]["content"] debug.log(f"Copilot: Use conversation: {conversation_id}") - images = [] - if image is not None: - data = to_bytes(image) - response = session.post( - "https://copilot.microsoft.com/c/api/attachments", - headers={"content-type": is_accepted_format(data)}, - data=data - ) - raise_for_status(response) - images.append({"type":"image", "url": response.json().get("url")}) + uploaded_images = [] + if images is not None: + for image, _ in images: + data = to_bytes(image) + response = session.post( + "https://copilot.microsoft.com/c/api/attachments", + headers={"content-type": is_accepted_format(data)}, + data=data + ) + raise_for_status(response) + uploaded_images.append({"type":"image", "url": response.json().get("url")}) wss = session.ws_connect(cls.websocket_url) wss.send(json.dumps({ "event": "send", "conversationId": conversation_id, - "content": [*images, { + "content": [*uploaded_images, { "type": "text", "text": prompt, }], diff --git a/g4f/Provider/DeepInfraChat.py b/g4f/Provider/DeepInfraChat.py index 6874b023..5f3e68e5 100644 --- a/g4f/Provider/DeepInfraChat.py +++ b/g4f/Provider/DeepInfraChat.py @@ -1,19 +1,12 @@ from __future__ import annotations -from aiohttp import ClientSession, ClientResponseError -import json -from ..typing import AsyncResult, Messages, ImageType -from .base_provider import AsyncGeneratorProvider, ProviderModelMixin +from ..typing import AsyncResult, Messages +from .needs_auth import OpenaiAPI - -class DeepInfraChat(AsyncGeneratorProvider, ProviderModelMixin): +class DeepInfraChat(OpenaiAPI): + label = "DeepInfra Chat" url = "https://deepinfra.com/chat" - api_endpoint = "https://api.deepinfra.com/v1/openai/chat/completions" - working = True - supports_stream = True - supports_system_message = True - supports_message_history = True default_model = 'meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo' models = [ @@ -31,16 +24,17 @@ class DeepInfraChat(AsyncGeneratorProvider, ProviderModelMixin): "qwq-32b": "Qwen/QwQ-32B-Preview", "wizardlm-2-8x22b": "microsoft/WizardLM-2-8x22B", "qwen-2-72b": "Qwen/Qwen2.5-72B-Instruct", - "qwen-2.5-coder-32b": "Qwen2.5-Coder-32B-Instruct", + "qwen-2.5-coder-32b": "Qwen/Qwen2.5-Coder-32B-Instruct", "nemotron-70b": "nvidia/Llama-3.1-Nemotron-70B-Instruct", } @classmethod - async def create_async_generator( + def create_async_generator( cls, model: str, messages: Messages, proxy: str = None, + api_base: str = "https://api.deepinfra.com/v1/openai", **kwargs ) -> AsyncResult: headers = { @@ -52,30 +46,4 @@ class DeepInfraChat(AsyncGeneratorProvider, ProviderModelMixin): 'X-Deepinfra-Source': 'web-page', 'accept': 'text/event-stream', } - - data = { - 'model': model, - 'messages': messages, - 'stream': True - } - - async with ClientSession(headers=headers) as session: - async with session.post(cls.api_endpoint, json=data, proxy=proxy) as response: - response.raise_for_status() - async for line in response.content: - if line: - decoded_line = line.decode('utf-8').strip() - if decoded_line.startswith('data:'): - json_part = decoded_line[5:].strip() - if json_part == '[DONE]': - break - try: - data = json.loads(json_part) - choices = data.get('choices', []) - if choices: - delta = choices[0].get('delta', {}) - content = delta.get('content', '') - if content: - yield content - except json.JSONDecodeError: - print(f"JSON decode error: {json_part}") + return super().create_async_generator(model, messages, proxy, api_base=api_base, headers=headers, **kwargs) \ No newline at end of file diff --git a/g4f/Provider/Flux.py b/g4f/Provider/Flux.py index b211ecef..d3949153 100644 --- a/g4f/Provider/Flux.py +++ b/g4f/Provider/Flux.py @@ -8,14 +8,13 @@ from ..image import ImageResponse, ImagePreview from .base_provider import AsyncGeneratorProvider, ProviderModelMixin class Flux(AsyncGeneratorProvider, ProviderModelMixin): - label = "HuggingSpace (black-forest-labs-flux-1-dev)" + label = "Flux (HuggingSpace)" url = "https://black-forest-labs-flux-1-dev.hf.space" api_endpoint = "/gradio_api/call/infer" working = True default_model = 'flux-dev' models = [default_model] image_models = [default_model] - model_aliases = {"flux-dev": "flux-1-dev"} @classmethod async def create_async_generator( diff --git a/g4f/Provider/PerplexityLabs.py b/g4f/Provider/PerplexityLabs.py index 3a6f0d39..af0b8d5b 100644 --- a/g4f/Provider/PerplexityLabs.py +++ b/g4f/Provider/PerplexityLabs.py @@ -21,14 +21,13 @@ class PerplexityLabs(AsyncGeneratorProvider, ProviderModelMixin): "llama-3.1-sonar-small-128k-chat", "llama-3.1-8b-instruct", "llama-3.1-70b-instruct", + "llama-3.3-70b-instruct", "/models/LiquidCloud", ] model_aliases = { "sonar-online": "llama-3.1-sonar-large-128k-online", - "sonar-online": "sonar-small-128k-online", "sonar-chat": "llama-3.1-sonar-large-128k-chat", - "sonar-chat": "llama-3.1-sonar-small-128k-chat", "llama-3.3-70b": "llama-3.3-70b-instruct", "llama-3.1-8b": "llama-3.1-8b-instruct", "llama-3.1-70b": "llama-3.1-70b-instruct", diff --git a/g4f/Provider/RubiksAI.py b/g4f/Provider/RubiksAI.py index 86d61564..43760f9d 100644 --- a/g4f/Provider/RubiksAI.py +++ b/g4f/Provider/RubiksAI.py @@ -23,7 +23,7 @@ class RubiksAI(AsyncGeneratorProvider, ProviderModelMixin): supports_message_history = True default_model = 'gpt-4o-mini' - models = [default_model, 'gpt-4o', 'o1-mini', 'claude-3.5-sonnet', 'grok-beta', 'gemini-1.5-pro', 'nova-pro'] + models = [default_model, 'gpt-4o', 'o1-mini', 'claude-3.5-sonnet', 'grok-beta', 'gemini-1.5-pro', 'nova-pro', "llama-3.1-70b-versatile"] model_aliases = { "llama-3.1-70b": "llama-3.1-70b-versatile", } diff --git a/g4f/Provider/needs_auth/Gemini.py b/g4f/Provider/needs_auth/Gemini.py index b55a604b..3734bbb8 100644 --- a/g4f/Provider/needs_auth/Gemini.py +++ b/g4f/Provider/needs_auth/Gemini.py @@ -5,6 +5,7 @@ import json import random import re import base64 +import asyncio from aiohttp import ClientSession, BaseConnector @@ -15,7 +16,7 @@ except ImportError: has_nodriver = False from ... import debug -from ...typing import Messages, Cookies, ImageType, AsyncResult, AsyncIterator +from ...typing import Messages, Cookies, ImagesType, AsyncResult, AsyncIterator from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin, BaseConversation, SynthesizeData from ..helper import format_prompt, get_cookies from ...requests.raise_for_status import raise_for_status @@ -97,8 +98,7 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin): proxy: str = None, cookies: Cookies = None, connector: BaseConnector = None, - image: ImageType = None, - image_name: str = None, + images: ImagesType = None, return_conversation: bool = False, conversation: Conversation = None, language: str = "en", @@ -128,8 +128,7 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin): raise RuntimeError("Invalid cookies. SNlM0e not found") yield SynthesizeData(cls.__name__, {"text": messages[-1]["content"]}) - image_url = await cls.upload_image(base_connector, to_bytes(image), image_name) if image else None - + images = await cls.upload_images(base_connector, images) if images else None async with ClientSession( cookies=cls._cookies, headers=REQUEST_HEADERS, @@ -148,8 +147,7 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin): prompt, language=language, conversation=conversation, - image_url=image_url, - image_name=image_name + images=images ))]) } async with client.post( @@ -195,7 +193,7 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin): images = [image[0][3][3] for image in response_part[4][0][12][7][0]] image_prompt = image_prompt.replace("a fake image", "") yield ImageResponse(images, image_prompt, {"cookies": cls._cookies}) - except TypeError: + except (TypeError, IndexError, KeyError): pass @classmethod @@ -235,11 +233,10 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin): prompt: str, language: str, conversation: Conversation = None, - image_url: str = None, - image_name: str = None, + images: list[list[str, str]] = None, tools: list[list[str]] = [] ) -> list: - image_list = [[[image_url, 1], image_name]] if image_url else [] + image_list = [[[image_url, 1], image_name] for image_url, image_name in images] if images else [] return [ [prompt, 0, None, image_list, None, None, 0], [language], @@ -262,35 +259,39 @@ class Gemini(AsyncGeneratorProvider, ProviderModelMixin): 0, ] - async def upload_image(connector: BaseConnector, image: bytes, image_name: str = None): - async with ClientSession( - headers=UPLOAD_IMAGE_HEADERS, - connector=connector - ) as session: - async with session.options(UPLOAD_IMAGE_URL) as response: - await raise_for_status(response) + async def upload_images(connector: BaseConnector, images: ImagesType) -> list: + async def upload_image(image: bytes, image_name: str = None): + async with ClientSession( + headers=UPLOAD_IMAGE_HEADERS, + connector=connector + ) as session: + image = to_bytes(image) - headers = { - "size": str(len(image)), - "x-goog-upload-command": "start" - } - data = f"File name: {image_name}" if image_name else None - async with session.post( - UPLOAD_IMAGE_URL, headers=headers, data=data - ) as response: - await raise_for_status(response) - upload_url = response.headers["X-Goog-Upload-Url"] + async with session.options(UPLOAD_IMAGE_URL) as response: + await raise_for_status(response) - async with session.options(upload_url, headers=headers) as response: - await raise_for_status(response) + headers = { + "size": str(len(image)), + "x-goog-upload-command": "start" + } + data = f"File name: {image_name}" if image_name else None + async with session.post( + UPLOAD_IMAGE_URL, headers=headers, data=data + ) as response: + await raise_for_status(response) + upload_url = response.headers["X-Goog-Upload-Url"] - headers["x-goog-upload-command"] = "upload, finalize" - headers["X-Goog-Upload-Offset"] = "0" - async with session.post( - upload_url, headers=headers, data=image - ) as response: - await raise_for_status(response) - return await response.text() + async with session.options(upload_url, headers=headers) as response: + await raise_for_status(response) + + headers["x-goog-upload-command"] = "upload, finalize" + headers["X-Goog-Upload-Offset"] = "0" + async with session.post( + upload_url, headers=headers, data=image + ) as response: + await raise_for_status(response) + return [await response.text(), image_name] + return await asyncio.gather(*[upload_image(image, image_name) for image, image_name in images]) @classmethod async def fetch_snlm0e(cls, session: ClientSession, cookies: Cookies): diff --git a/g4f/Provider/needs_auth/GeminiPro.py b/g4f/Provider/needs_auth/GeminiPro.py index d9204b25..36c90656 100644 --- a/g4f/Provider/needs_auth/GeminiPro.py +++ b/g4f/Provider/needs_auth/GeminiPro.py @@ -4,7 +4,7 @@ import base64 import json from aiohttp import ClientSession, BaseConnector -from ...typing import AsyncResult, Messages, ImageType +from ...typing import AsyncResult, Messages, ImagesType from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin from ...image import to_bytes, is_accepted_format from ...errors import MissingAuthError @@ -36,7 +36,7 @@ class GeminiPro(AsyncGeneratorProvider, ProviderModelMixin): api_key: str = None, api_base: str = "https://generativelanguage.googleapis.com/v1beta", use_auth_header: bool = False, - image: ImageType = None, + images: ImagesType = None, connector: BaseConnector = None, **kwargs ) -> AsyncResult: @@ -62,14 +62,15 @@ class GeminiPro(AsyncGeneratorProvider, ProviderModelMixin): for message in messages if message["role"] != "system" ] - if image is not None: - image = to_bytes(image) - contents[-1]["parts"].append({ - "inline_data": { - "mime_type": is_accepted_format(image), - "data": base64.b64encode(image).decode() - } - }) + if images is not None: + for image, _ in images: + image = to_bytes(image) + contents[-1]["parts"].append({ + "inline_data": { + "mime_type": is_accepted_format(image), + "data": base64.b64encode(image).decode() + } + }) data = { "contents": contents, "generationConfig": { diff --git a/g4f/Provider/needs_auth/HuggingFace.py b/g4f/Provider/needs_auth/HuggingFace.py index fd1da2a7..40db1ee7 100644 --- a/g4f/Provider/needs_auth/HuggingFace.py +++ b/g4f/Provider/needs_auth/HuggingFace.py @@ -18,7 +18,7 @@ class HuggingFace(AsyncGeneratorProvider, ProviderModelMixin): supports_message_history = True default_model = HuggingChat.default_model default_image_model = HuggingChat.default_image_model - models = [*HuggingChat.models, default_image_model] + models = HuggingChat.models image_models = [default_image_model] model_aliases = HuggingChat.model_aliases diff --git a/g4f/Provider/needs_auth/OpenaiAPI.py b/g4f/Provider/needs_auth/OpenaiAPI.py index 83268b6d..e4731ae2 100644 --- a/g4f/Provider/needs_auth/OpenaiAPI.py +++ b/g4f/Provider/needs_auth/OpenaiAPI.py @@ -4,7 +4,7 @@ import json from ..helper import filter_none from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin, FinishReason -from ...typing import Union, Optional, AsyncResult, Messages, ImageType +from ...typing import Union, Optional, AsyncResult, Messages, ImagesType from ...requests import StreamSession, raise_for_status from ...errors import MissingAuthError, ResponseError from ...image import to_data_uri @@ -25,7 +25,7 @@ class OpenaiAPI(AsyncGeneratorProvider, ProviderModelMixin): messages: Messages, proxy: str = None, timeout: int = 120, - image: ImageType = None, + images: ImagesType = None, api_key: str = None, api_base: str = "https://api.openai.com/v1", temperature: float = None, @@ -40,14 +40,14 @@ class OpenaiAPI(AsyncGeneratorProvider, ProviderModelMixin): ) -> AsyncResult: if cls.needs_auth and api_key is None: raise MissingAuthError('Add a "api_key"') - if image is not None: + if images is not None: if not model and hasattr(cls, "default_vision_model"): model = cls.default_vision_model messages[-1]["content"] = [ - { + *[{ "type": "image_url", "image_url": {"url": to_data_uri(image)} - }, + } for image, image_name in images], { "type": "text", "text": messages[-1]["content"] diff --git a/g4f/Provider/needs_auth/OpenaiChat.py b/g4f/Provider/needs_auth/OpenaiChat.py index 3ce5b1a1..01976198 100644 --- a/g4f/Provider/needs_auth/OpenaiChat.py +++ b/g4f/Provider/needs_auth/OpenaiChat.py @@ -18,7 +18,7 @@ except ImportError: has_nodriver = False from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin -from ...typing import AsyncResult, Messages, Cookies, ImageType, AsyncIterator +from ...typing import AsyncResult, Messages, Cookies, ImagesType, AsyncIterator from ...requests.raise_for_status import raise_for_status from ...requests import StreamSession from ...requests import get_nodriver @@ -37,14 +37,14 @@ DEFAULT_HEADERS = { "accept-encoding": "gzip, deflate, br, zstd", 'accept-language': 'en-US,en;q=0.8', "referer": "https://chatgpt.com/", - "sec-ch-ua": "\"Brave\";v=\"123\", \"Not:A-Brand\";v=\"8\", \"Chromium\";v=\"123\"", + "sec-ch-ua": "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"", "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": "\"Windows\"", "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-origin", "sec-gpc": "1", - "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" } INIT_HEADERS = { @@ -53,19 +53,35 @@ INIT_HEADERS = { 'cache-control': 'no-cache', 'pragma': 'no-cache', 'priority': 'u=0, i', - 'sec-ch-ua': '"Not)A;Brand";v="99", "Google Chrome";v="127", "Chromium";v="127"', + "sec-ch-ua": "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"", 'sec-ch-ua-arch': '"arm"', 'sec-ch-ua-bitness': '"64"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-model': '""', - 'sec-ch-ua-platform': '"macOS"', + "sec-ch-ua-platform": "\"Windows\"", 'sec-ch-ua-platform-version': '"14.4.0"', 'sec-fetch-dest': 'document', 'sec-fetch-mode': 'navigate', 'sec-fetch-site': 'none', 'sec-fetch-user': '?1', 'upgrade-insecure-requests': '1', - "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" +} + +UPLOAD_HEADERS = { + "accept": "application/json, text/plain, */*", + 'accept-language': 'en-US,en;q=0.8', + "referer": "https://chatgpt.com/", + "priority": "u=1, i", + "sec-ch-ua": "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\"", + "sec-ch-ua-mobile": "?0", + 'sec-ch-ua-platform': '"macOS"', + "sec-fetch-dest": "empty", + "sec-fetch-mode": "cors", + "sec-fetch-site": "cross-site", + "x-ms-blob-type": "BlockBlob", + "x-ms-version": "2020-04-08", + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" } class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): @@ -78,7 +94,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): supports_message_history = True supports_system_message = True default_model = "auto" - fallback_models = [default_model, "gpt-4", "gpt-4o", "gpt-4o-mini", "gpt-4o-canmore", "o1-preview", "o1-mini"] + fallback_models = [default_model, "gpt-4", "gpt-4o", "gpt-4o-mini", "gpt-4o-canmore", "o1", "o1-preview", "o1-mini"] vision_models = fallback_models synthesize_content_type = "audio/mpeg" @@ -100,12 +116,11 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): return cls.models @classmethod - async def upload_image( + async def upload_images( cls, session: StreamSession, headers: dict, - image: ImageType, - image_name: str = None + images: ImagesType, ) -> ImageRequest: """ Upload an image to the service and get the download URL @@ -113,58 +128,63 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): Args: session: The StreamSession object to use for requests headers: The headers to include in the requests - image: The image to upload, either a PIL Image object or a bytes object + images: The images to upload, either a PIL Image object or a bytes object Returns: An ImageRequest object that contains the download URL, file name, and other data """ - # Convert the image to a PIL Image object and get the extension - data_bytes = to_bytes(image) - image = to_image(data_bytes) - extension = image.format.lower() - data = { - "file_name": "" if image_name is None else image_name, - "file_size": len(data_bytes), - "use_case": "multimodal" - } - # Post the image data to the service and get the image data - async with session.post(f"{cls.url}/backend-api/files", json=data, headers=headers) as response: - cls._update_request_args(session) - await raise_for_status(response, "Create file failed") - image_data = { - **data, - **await response.json(), - "mime_type": is_accepted_format(data_bytes), - "extension": extension, - "height": image.height, - "width": image.width - } - # Put the image bytes to the upload URL and check the status - async with session.put( - image_data["upload_url"], - data=data_bytes, - headers={ - **DEFAULT_HEADERS, - "Content-Type": image_data["mime_type"], - "x-ms-blob-type": "BlockBlob", - "x-ms-version": "2020-04-08", - "Origin": "https://chatgpt.com", + async def upload_image(image, image_name): + # Convert the image to a PIL Image object and get the extension + data_bytes = to_bytes(image) + image = to_image(data_bytes) + extension = image.format.lower() + data = { + "file_name": "" if image_name is None else image_name, + "file_size": len(data_bytes), + "use_case": "multimodal" } - ) as response: - await raise_for_status(response) - # Post the file ID to the service and get the download URL - async with session.post( - f"{cls.url}/backend-api/files/{image_data['file_id']}/uploaded", - json={}, - headers=headers - ) as response: - cls._update_request_args(session) - await raise_for_status(response, "Get download url failed") - image_data["download_url"] = (await response.json())["download_url"] - return ImageRequest(image_data) + # Post the image data to the service and get the image data + async with session.post(f"{cls.url}/backend-api/files", json=data, headers=headers) as response: + cls._update_request_args(session) + await raise_for_status(response, "Create file failed") + image_data = { + **data, + **await response.json(), + "mime_type": is_accepted_format(data_bytes), + "extension": extension, + "height": image.height, + "width": image.width + } + # Put the image bytes to the upload URL and check the status + await asyncio.sleep(1) + async with session.put( + image_data["upload_url"], + data=data_bytes, + headers={ + **UPLOAD_HEADERS, + "Content-Type": image_data["mime_type"], + "x-ms-blob-type": "BlockBlob", + "x-ms-version": "2020-04-08", + "Origin": "https://chatgpt.com", + } + ) as response: + await raise_for_status(response) + # Post the file ID to the service and get the download URL + async with session.post( + f"{cls.url}/backend-api/files/{image_data['file_id']}/uploaded", + json={}, + headers=headers + ) as response: + cls._update_request_args(session) + await raise_for_status(response, "Get download url failed") + image_data["download_url"] = (await response.json())["download_url"] + return ImageRequest(image_data) + if not images: + return + return [await upload_image(image, image_name) for image, image_name in images] @classmethod - def create_messages(cls, messages: Messages, image_request: ImageRequest = None, system_hints: list = None): + def create_messages(cls, messages: Messages, image_requests: ImageRequest = None, system_hints: list = None): """ Create a list of messages for the user input @@ -185,16 +205,18 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): } for message in messages] # Check if there is an image response - if image_request is not None: + if image_requests: # Change content in last user message messages[-1]["content"] = { "content_type": "multimodal_text", - "parts": [{ + "parts": [*[{ "asset_pointer": f"file-service://{image_request.get('file_id')}", "height": image_request.get("height"), "size_bytes": image_request.get("file_size"), "width": image_request.get("width"), - }, messages[-1]["content"]["parts"][0]] + } + for image_request in image_requests], + messages[-1]["content"]["parts"][0]] } # Add the metadata object with the attachments messages[-1]["metadata"] = { @@ -205,7 +227,8 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): "name": image_request.get("file_name"), "size": image_request.get("file_size"), "width": image_request.get("width"), - }] + } + for image_request in image_requests] } return messages @@ -259,8 +282,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): conversation_id: str = None, conversation: Conversation = None, parent_id: str = None, - image: ImageType = None, - image_name: str = None, + images: ImagesType = None, return_conversation: bool = False, max_retries: int = 3, web_search: bool = False, @@ -281,7 +303,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): action (str): Type of action ('next', 'continue', 'variant'). conversation_id (str): ID of the conversation. parent_id (str): ID of the parent message. - image (ImageType): Image to include in the conversation. + images (ImagesType): Images to include in the conversation. return_conversation (bool): Flag to include response fields in the output. **kwargs: Additional keyword arguments. @@ -298,7 +320,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): impersonate="chrome", timeout=timeout ) as session: - image_request = None + image_requests = None if not cls.needs_auth: if cls._headers is None: cls._create_request_args(cookies) @@ -310,7 +332,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): cls._update_request_args(session) await raise_for_status(response) try: - image_request = await cls.upload_image(session, cls._headers, image, image_name) if image else None + image_requests = await cls.upload_images(session, cls._headers, images) if images else None except Exception as e: debug.log("OpenaiChat: Upload image failed") debug.log(f"{e.__class__.__name__}: {e}") @@ -384,7 +406,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): debug.log(f"OpenaiChat: Use conversation: {conversation.conversation_id}") if action != "continue": messages = messages if conversation_id is None else [messages[-1]] - data["messages"] = cls.create_messages(messages, image_request, ["search"] if web_search else None) + data["messages"] = cls.create_messages(messages, image_requests, ["search"] if web_search else None) headers = { **cls._headers, "accept": "text/event-stream", diff --git a/g4f/__init__.py b/g4f/__init__.py index f59a1446..0e13c99e 100644 --- a/g4f/__init__.py +++ b/g4f/__init__.py @@ -2,11 +2,12 @@ from __future__ import annotations import os import logging +from typing import Union, Optional from . import debug, version from .models import Model from .client import Client, AsyncClient -from .typing import Messages, CreateResult, AsyncResult, Union +from .typing import Messages, CreateResult, AsyncResult, ImageType from .errors import StreamNotSupportedError, ModelNotAllowedError from .cookies import get_cookies, set_cookies from .providers.types import ProviderType @@ -27,32 +28,27 @@ class ChatCompletion: messages : Messages, provider : Union[ProviderType, str, None] = None, stream : bool = False, - auth : Union[str, None] = None, - ignored : list[str] = None, + image : ImageType = None, + image_name: Optional[str] = None, + ignored: list[str] = None, ignore_working: bool = False, ignore_stream: bool = False, - patch_provider: callable = None, **kwargs) -> Union[CreateResult, str]: model, provider = get_model_and_provider( model, provider, stream, ignored, ignore_working, ignore_stream or kwargs.get("ignore_stream_and_auth") ) - - if auth is not None: - kwargs['auth'] = auth - + if image is not None: + kwargs["images"] = [(image, image_name)] if "proxy" not in kwargs: proxy = os.environ.get("G4F_PROXY") if proxy: kwargs['proxy'] = proxy - if patch_provider: - provider = patch_provider(provider) - result = provider.create_completion(model, messages, stream=stream, **kwargs) - return result if stream else ''.join([str(chunk) for chunk in result]) + return result if stream else ''.join([str(chunk) for chunk in result if chunk]) @staticmethod def create_async(model : Union[Model, str], @@ -61,40 +57,12 @@ class ChatCompletion: stream : bool = False, ignored : list[str] = None, ignore_working: bool = False, - patch_provider: callable = None, **kwargs) -> Union[AsyncResult, str]: model, provider = get_model_and_provider(model, provider, False, ignored, ignore_working) if stream: - if isinstance(provider, type) and issubclass(provider, AsyncGeneratorProvider): + if hasattr(provider, "create_async_generator"): return provider.create_async_generator(model, messages, **kwargs) raise StreamNotSupportedError(f'{provider.__name__} does not support "stream" argument in "create_async"') - if patch_provider: - provider = patch_provider(provider) - - return provider.create_async(model, messages, **kwargs) - -class Completion: - @staticmethod - def create(model : Union[Model, str], - prompt : str, - provider : Union[ProviderType, None] = None, - stream : bool = False, - ignored : list[str] = None, **kwargs) -> Union[CreateResult, str]: - allowed_models = [ - 'code-davinci-002', - 'text-ada-001', - 'text-babbage-001', - 'text-curie-001', - 'text-davinci-002', - 'text-davinci-003' - ] - if model not in allowed_models: - raise ModelNotAllowedError(f'Can\'t use {model} with Completion.create()') - - model, provider = get_model_and_provider(model, provider, stream, ignored) - - result = provider.create_completion(model, [{"role": "user", "content": prompt}], stream=stream, **kwargs) - - return result if stream else ''.join(result) + return provider.create_async(model, messages, **kwargs) \ No newline at end of file diff --git a/g4f/api/__init__.py b/g4f/api/__init__.py index b036603e..b42c5991 100644 --- a/g4f/api/__init__.py +++ b/g4f/api/__init__.py @@ -102,6 +102,7 @@ class ChatCompletionsConfig(BaseModel): stream: bool = False image: Optional[str] = None image_name: Optional[str] = None + images: Optional[list[tuple[str, str]]] = None temperature: Optional[float] = None max_tokens: Optional[int] = None stop: Union[list[str], str, None] = None @@ -171,7 +172,7 @@ class AppConfig: ignored_providers: Optional[list[str]] = None g4f_api_key: Optional[str] = None ignore_cookie_files: bool = False - model: str = None, + model: str = None provider: str = None image_provider: str = None proxy: str = None @@ -328,8 +329,14 @@ class Api: try: is_data_uri_an_image(config.image) except ValueError as e: - return ErrorResponse.from_message(f"The image you send must be a data URI. Example: data:image/webp;base64,...", status_code=HTTP_422_UNPROCESSABLE_ENTITY) - + return ErrorResponse.from_message(f"The image you send must be a data URI. Example: data:image/jpeg;base64,...", status_code=HTTP_422_UNPROCESSABLE_ENTITY) + if config.images is not None: + for image in config.images: + try: + is_data_uri_an_image(image[0]) + except ValueError as e: + example = json.dumps({"images": [["data:image/jpeg;base64,...", "filename"]]}) + return ErrorResponse.from_message(f'The image you send must be a data URI. Example: {example}', status_code=HTTP_422_UNPROCESSABLE_ENTITY) # Create the completion response response = self.client.chat.completions.create( **filter_none( @@ -522,8 +529,8 @@ def format_exception(e: Union[Exception, str], config: Union[ChatCompletionsConf message = f"{e.__class__.__name__}: {e}" return json.dumps({ "error": {"message": message}, - "model": last_provider.get("model") if model is None else model, **filter_none( + model=last_provider.get("model") if model is None else model, provider=last_provider.get("name") if provider is None else provider ) }) diff --git a/g4f/client/__init__.py b/g4f/client/__init__.py index 52349e72..ec3fec00 100644 --- a/g4f/client/__init__.py +++ b/g4f/client/__init__.py @@ -12,7 +12,7 @@ from ..image import ImageResponse, copy_images, images_dir from ..typing import Messages, ImageType from ..providers.types import ProviderType from ..providers.response import ResponseType, FinishReason, BaseConversation, SynthesizeData -from ..errors import NoImageResponseError, MissingAuthError, NoValidHarFileError +from ..errors import NoImageResponseError from ..providers.retry_provider import IterListProvider from ..providers.asyncio import to_sync_generator, async_generator_to_list from ..Provider.needs_auth import BingCreateImages, OpenaiAccount @@ -192,6 +192,8 @@ class Completions: provider: Optional[ProviderType] = None, stream: Optional[bool] = False, proxy: Optional[str] = None, + image: Optional[ImageType] = None, + image_name: Optional[str] = None, response_format: Optional[dict] = None, max_tokens: Optional[int] = None, stop: Optional[Union[list[str], str]] = None, @@ -210,7 +212,8 @@ class Completions: ignore_stream, ) stop = [stop] if isinstance(stop, str) else stop - + if image is not None: + kwargs["images"] = [(image, image_name)] response = provider.create_completion( model, messages, @@ -390,8 +393,6 @@ class Images: e = None response = None if isinstance(provider_handler, IterListProvider): - # File pointer can be read only once, so we need to convert it to bytes - image = to_bytes(image) for provider in provider_handler.providers: try: response = await self._generate_image_response(provider, provider.__name__, model, prompt, image=image, **kwargs) @@ -471,6 +472,8 @@ class AsyncCompletions: provider: Optional[ProviderType] = None, stream: Optional[bool] = False, proxy: Optional[str] = None, + image: Optional[ImageType] = None, + image_name: Optional[str] = None, response_format: Optional[dict] = None, max_tokens: Optional[int] = None, stop: Optional[Union[list[str], str]] = None, @@ -489,7 +492,8 @@ class AsyncCompletions: ignore_stream, ) stop = [stop] if isinstance(stop, str) else stop - + if image is not None: + kwargs["images"] = [(image, image_name)] if hasattr(provider, "create_async_generator"): create_handler = provider.create_async_generator else: diff --git a/g4f/gui/client/index.html b/g4f/gui/client/index.html index b5fcd280..9c066eea 100644 --- a/g4f/gui/client/index.html +++ b/g4f/gui/client/index.html @@ -240,7 +240,7 @@