Как работает GPT4Free в Python и почему его лучше не использовать

Страницы:  1

Ответить
 

Professor Seleznov


Привет, Хабр! Сегодня поговорим о проекте, который наделал много шума в сообществе — g4f (GPT4Free). Многие видели его на GitHub, но большинство, наверное, и не задумывались, как он работает.
Как использовать g4f в Python-коде
Сам API g4f выглядит как у настоящего openai. Это сделано специально для того, чтобы программистам не пришлось учить новые команды и методы вызова кода в g4f. Достаточно просто заменить import openai на import g4f. Пример кода:
С использованием официальной библиотеки openai:
from openai import OpenAI
client = OpenAI(api_key="sk-...")
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "ВАШ_ЗАПРОС"}]
)
print(response.choices[0].message.content)
С использованием официальной библиотеки g4f:
from g4f.client import Client
client = Client()
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "ВАШ_ЗАПРОС"}]
)
print(response.choices[0].message.content)
Оба кода отправляют запросы к GPT4, однако первый код требует API-ключ с официального сайта OpenAI, а второй — не требует. Это и есть главная причина существования g4f.
Установка g4f и подготовка к работе
Чтобы установить на свой компьютер g4f, нужно написать вот такую команду в терминале IDE или в командной строке:
python.exe -m pip install g4f
После этого репозиторий проекта установится на ваш компьютер, и его можно сразу использовать в Python-скриптах. Также можно клонировать репозиторий с помощью git. Это наиболее подходящий способ. Для этого в командной строке напишите команду:
git clone https://github.com/xtekky/gpt4free.git
После клонирования репозитория на жёстком диске появится папка "gpt4free".
Что находится внутри g4f
Теперь самое интересное — как работает этот проект. В папке "gpt4free" лежит множество файлов. Нас интересует папка с файлами "g4f". Внутри лежит много различных файлов, но нас интересует папка "Provider". Далее мы видит python-скрипты, где для каждого сайта-провайдера настроена имитация пользовательского взаимодействия с интерфейсом сайта. Посмотрим на примере кода EasyChat.py:
from __future__ import annotations  # Улучшает работу с аннотацией типов
import os # Работа с файлами и переменными окружения
import asyncio # Позволяет выполнять несколько задач одновременно (асинхронно)
import requests # Отправка обычных HTTP-запросов к сайтам
import json # Чтение и запись данных в формате JSON
try:
import zendriver as nodriver # Библиотека для управления настоящим браузером Chromium без окна
except ImportError:
pass # Если библиотека не установлена – ничего страшного, но часть функций будет недоступна
from ..typing import AsyncResult, Messages # Вспомогательные описания типов данных
from ..config import DEFAULT_MODEL # Имя модели, которая используется по умолчанию
from ..requests import get_args_from_nodriver, raise_for_status # Функции: собрать данные из браузера и проверить успешность запроса
from ..providers.base_provider import AuthFileMixin # Добавляет возможность сохранять/загружать авторизационные данные в файл
from .template import OpenaiTemplate # Базовый класс, который копирует поведение API OpenAI
from .helper import get_last_user_message # Достаёт последнее сообщение пользователя из всей переписки
from .. import debug # Простой вывод отладочных сообщений
class EasyChat(OpenaiTemplate, AuthFileMixin): # Этот класс отвечает за доступ к сервису EasyChat, повторяя стиль OpenAI
url = "https://chat3.eqing.tech" # Адрес сайта EasyChat
base_url = f"{url}/api/openai/v1" # Основа для всех запросов к его API
api_endpoint = f"{base_url}/chat/completions" # Конкретный путь, куда отправляется запрос на генерацию ответа
working = False # Говорит, что сейчас этот способ доступа не работает (может меняться)
active_by_default = True # По умолчанию включён в список доступных способов
use_model_names = True # Использовать имена моделей без изменений
default_model = DEFAULT_MODEL.split("/")[-1] # Из полного названия берём только короткое имя модели
model_aliases = {
DEFAULT_MODEL: f"{default_model}-free", # Можно обращаться по короткому имени, внутри добавится "-free"
}
captchaToken: str = None # Сюда попадёт код-пропуск после прохождения проверки «Я не робот»
share_url: str = None # Ссылка для обмена готовой сессией (если хотим поделиться)
looked: bool = False # Флаг, чтобы не пытаться бесконечно загружать сессию по share_url
guestId: str = None # Уникальный номер гостя, который сайт присваивает посетителю
@classmethod
def get_models(cls, **kwargs) -> list[str]: # Возвращает список моделей, которые предлагает сайт
if not cls.models: # Если ещё не спрашивали
models = super().get_models(**kwargs) # Берем список от родительского класса (из API сайта)
models = {m.replace("-free", ""): m for m in models if m.endswith("-free")} # Оставляем только бесплатные модели
cls.model_aliases.update(models) # Запоминаем, что короткое имя соответствует полному
cls.models = list(models) # Сохраняем список моделей для быстрого доступа
return cls.models
@classmethod
async def create_async_generator( # Основной метод: отправляет запрос и отдаёт ответ по частям в реальном времени
cls,
model: str, # Какую модель просим
messages: Messages, # История переписки
stream: bool = True, # Отдавать ли ответ постепенно (обычно да)
proxy: str = None, # Прокси-сервер, если нужно скрыть свой IP
extra_body: dict = None, # Дополнительные данные, которые пойдут вместе с запросом
**kwargs # Любые другие параметры
) -> AsyncResult: # Возвращает генератор кусочков ответа
cls.share_url = os.getenv("G4F_SHARE_URL") # Проверяем, есть ли в настройках ссылка для обмена сессией
model = cls.get_model(model.replace("-free", "")) # Превращаем короткое имя в полное, если нужно
args = None # Здесь будут храниться все технические настройки для запроса (заголовки, куки и т.п.)
cache_file = cls.get_cache_file() # Файл, в который мы спрячем данные сессии, чтобы не проходить капчу каждый раз
async def callback(page): # Эта функция выполнится в браузере сразу после открытия страницы
cls.captchaToken = None # Очищаем старый пропуск
def on_request(event: nodriver.cdp.network.RequestWillBeSent, page=None): # Следим за всеми запросами, которые отправляет сайт
if event.request.url != cls.api_endpoint: # Нас интересует только запрос к API
return
if not event.request.post_data: # Если запрос пустой — пропускаем
return
cls.captchaToken = json.loads(event.request.post_data).get("captchaToken") # Достаём из запроса код-пропуск
await page.send(nodriver.cdp.network.enable()) # Включаем отслеживание сетевых запросов
page.add_handler(nodriver.cdp.network.RequestWillBeSent, on_request) # Прикрепляем перехватчик запросов
button = await page.find("我已知晓") # Ищем на странице кнопку согласия
if button:
await button.click() # Нажимаем её
else:
debug.error("No 'Agree' button found.") # Печатаем ошибку, если кнопка не найдена
for _ in range(3): # Пробуем до трёх раз активировать сессию
await asyncio.sleep(1)
for _ in range(300): # Ждём, пока идёт проверка «Verifying...»
modal = await page.find("Verifying...")
if not modal:
break
debug.log("EasyChat: Waiting for captcha verification...")
await asyncio.sleep(1)
if cls.captchaToken: # Если код-пропуск уже получен
debug.log("EasyChat: Captcha token found, proceeding.")
break
textarea = await page.select("[contenteditable=\"true\"]", 180) # Ищем поле ввода
if textarea is not None:
await textarea.send_keys("Hello") # Печатаем тестовое сообщение
await asyncio.sleep(1)
button = await page.select("button[class*='chat_chat-input-send']") # Ищем кнопку отправки
if button:
await button.click() # Отправляем тестовое сообщение
for _ in range(300): # Ждём появления кода-пропуска, если он ещё не пришёл
await asyncio.sleep(1)
if cls.captchaToken:
break
cls.guestId = await page.evaluate( # Забираем из памяти браузера ID гостя
'"" + JSON.parse(localStorage.getItem("user-info") || "{}")?.state?.guestId'
)
await asyncio.sleep(3) # Небольшая пауза, чтобы всё сохранилось
if cache_file.exists(): # Если сохранённые данные сессии уже есть на диске
with cache_file.open("r") as f:
args = json.load(f) # Читаем их
cls.captchaToken = args.pop("captchaToken") # Извлекаем код-пропуск
cls.guestId = args.pop("guestId", None) # Извлекаем ID гостя
if cls.captchaToken:
debug.log("EasyChat: Using cached captchaToken.") # Используем сохранённые данные
elif not cls.looked and cls.share_url: # Если сохранённой сессии нет и задан URL обмена
cls.looked = True
try:
debug.log("No cache file found, trying to fetch from share URL.")
response = requests.get(cls.share_url, params={ # Обращаемся по ссылке обмена
"prompt": get_last_user_message(messages),
"model": model,
"provider": cls.__name__
})
raise_for_status(response) # Проверяем, что ответ успешный
text, *sub = response.text.split("\n" * 10 + "
 
Loading...
Error