Привет, Хабр! Сегодня поговорим о проекте, который наделал много шума в сообществе — 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 + "<!--", 1)  # Отделяем текст ответа от спрятанных технических данных
                if sub:  # Если есть технические данные
                    debug.log("Save args to cache file:", str(cache_file))
                    with cache_file.open("w") as f:
                        f.write(sub[0].strip())  # Сохраняем их для будущих запросов
                yield text  # Отдаём полученный текст как результат
            finally:
                cls.looked = False
            return

        for _ in range(2):  # Делаем до двух попыток на случай, если первая не удалась
            if not args:  # Если технических настроек ещё нет
                args = await get_args_from_nodriver(  # Открываем сайт в браузере и собираем все необходимые данные
                    cls.url, proxy=proxy, callback=callback, user_data_dir=None
                )
            if extra_body is None:
                extra_body = {}
            extra_body.setdefault("captchaToken", cls.captchaToken)  # Добавляем код-пропуск в тело запроса
            try:
                last_chunk = None
                async for chunk in super().create_async_generator(  # Отправляем запрос через родительский класс (шаблон OpenAI)
                    model=model,
                    messages=messages,
                    stream=True,
                    extra_body=extra_body,
                    **{
                        **args,
                        "headers": {
                            "X-Guest-Id": cls.guestId,  # Добавляем ID гостя в заголовки
                            **args.get("headers", {})
                        }
                    },
                    **kwargs
                ):
                    if last_chunk == "\n" and chunk == "\n":  # Убираем рекламную строку в конце («Предоставлено сервисом...»)
                        break
                    last_chunk = chunk
                    yield chunk  # Отдаём кусочек ответа
            except Exception as e:
                if "CLEAR-CAPTCHA-TOKEN" in str(e):  # Сервер сказал, что наш код-пропуск устарел
                    debug.log("EasyChat: Captcha token expired, clearing cache file.")
                    cache_file.unlink(missing_ok=True)  # Удаляем файл с устаревшими данными
                    args = None  # Готовимся собрать новые данные через браузер
                    continue
                raise e
            break

        if not args:  # Если после всех попыток данные не добыты
            raise ValueError("Failed to retrieve arguments for EasyChat.")
        if os.getenv("G4F_SHARE_AUTH"):  # Если разрешено делиться техническими данными сессии
            yield "\n" * 10
            yield "<!--"
            yield json.dumps({**args, "captchaToken": cls.captchaToken, "guestId": cls.guestId})
        with cache_file.open("w") as f:  # Сохраняем технические данные в файл для быстрого входа в следующий раз
            json.dump({**args, "captchaToken": cls.captchaToken, "guestId": cls.guestId}, f)

Здесь я наглядно показал, как работает этот кусочек кода в g4f. Основной принцип работы — реверс-инжиниринг. Это процесс анализа готового продукта для понимания того, как он устроен внутри, без доступа к его исходному коду. Программа от имени пользователя взаимодействует с AI-сервисами, проходит капчи, сама разбирается с интерфейсом сайта, структурирует ответ под заданную модель. Пользователь лишь запускает код, а он отправляет автоматические запросы, и, обходя защиту сервисов от ботов, выводит ответ.

Насколько безопасно этим пользоваться

Самый опасный миф — «раз я не плачу, значит, никто не знает, что я спрашиваю». На деле всё наоборот. Если при использовании обычного API OpenAI цепочка такая:
Ваш код → Сервер OpenAI → Ответ от GPT-модели высокого качества

то при использовании g4f цепочка будет примерно такая:
Ваш код → g4f Client → Сайт 1 (ошибка: обнаружен бот) → Сайт 2 (долгое ожидание) → Сайт 3 (доступ запрещён) → Использование прокси → Сайт 3 → Сервер неизвестной LLM → Ответ сомнительного качества

При этом g4f, пока вы даже не знаете, может подключаться через случайные прокси-серверы, подменять заголовки и притворяться браузером, чтобы обойти большинство стандартных проверок на робота. Если сайт не доступен — g4f может проделать длительную цепочку перебора множества сайтов, отправляя ваш запрос на первый попавшийся рабочий. А если сайт и вовсе запретит доступ из-за слишком частых запросов — g4f замаскирует ваш IP без вашего ведома.

А самое опасное — это огромный риск утечки данных. Ваш запрос проходит через множество случайных серверов, которые g4f подбирает на лету. Любой из этих сайтов может логировать всё, что через него проходит: исходный код, коммерческую информацию, личные данные. В результате вы теряете контроль над тем, где окажется ваша информация. И самое страшное: доказать, что утечка произошла именно через g4f, будет невозможно — библиотека уже замела следы, и никто никогда не узнает, в чьём распоряжении оказались ваши данные и когда они сработают против вас.

Наглядное сравнение g4f с официальным OpenAI API

Критерий

OpenAI (официальный API)

g4f (GPT4Free)

Модель

Вы получаете именно ту модель, которую запросили

Модель в большинстве случаев никогда не будет именно той, которую вы указали в коде

Конфиденциальность

Прямой защищённый канал, данные не покидают сервер OpenAI

Запрос гуляет через десятки чужих серверов

Стабильность

Гарантированная, 99%

Может упасть в любой момент из-за изменения алгоритмов безопасности сайтов, к которым обращается g4f

Юридическая чистота

Полностью легально, не нарушает Пользовательское Соглашение OpenAI

Нарушает условия использования как самого OpenAI, так и участвующих в цепочке перебора сайтов, через которые идёт запрос

Контроль над IP

Вы работаете со своего IP

Трафик идёт через случайные прокси без вашего ведома

Качество ответа

Предсказуемое и воспроизводимое. Если ответ вас не устраивает, достаточно просто поменять модель в коде и получить соответствующее качество ответа

Хаотичное, может прийти ответ от неизвестной LLM. G4F просто изменит стиль ответа под выбранную модель. Менять модель в коде бессмысленно — качество ответа от этого не изменится

Хранение сессий

API-ключ — единственный идентификатор

Кэшируются токены капч, чужие куки

Риск утечки

Минимален, трафик зашифрован

Критический: данные могут осесть на любом узле

Цена

Можно найти на официальном сайте OpenAI

«Бесплатно» ценой вашей приватности и безопасности

Скорость ответа

Почти мгновенная, 1-5 секунд

Может занимать несколько минут. Всё зависит от того, как долго g4f будет перебирать сайты и решать капчи

Техподдержка

Официальная документация, есть техподдержка, статус-страница, отчёты об инцидентах

Полностью отсутствует. Можно написать issue на GitHub, однако гарантия того, что оно останется замеченным отсутствует

Как итог, вы видите сами: официальный API OpenAI даёт вам предсказуемость, безопасность и полный контроль над данными за цену, которая для большинства задач измеряется копейками. G4F же предлагает «бесплатный сыр», который в любой момент может обернуться утечкой, блокировкой или просто неработающим кодом — и без единой возможности получить помощь. Выбор между этими подходами сводится не к экономии денег, а к простому вопросу: готовы ли вы доверить свои личные данные и стабильность продукта непрозрачной цепочке случайных серверов и ничьей ответственности?

Надеюсь, это статья поможет вам в дальнейшем сделать правильный выбор между g4f и OpenAI API. Спасибо, что дочитали до конца, и пусть ваш код всегда работает предсказуемо, а данные остаются только в ваших руках!

Комментарии (0)