mirror of
https://github.com/xtekky/gpt4free.git
synced 2025-12-06 02:30:41 -08:00
540 lines
20 KiB
Python
540 lines
20 KiB
Python
from __future__ import annotations
|
|
|
|
import uuid
|
|
import json
|
|
from aiohttp import ClientSession, BaseConnector
|
|
|
|
from ..typing import AsyncResult, Messages
|
|
from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
|
|
from .helper import get_connector
|
|
from ..requests import raise_for_status
|
|
from ..errors import RateLimitError
|
|
|
|
models = {
|
|
"claude-3-5-sonnet-20241022": {
|
|
"id": "claude-3-5-sonnet-20241022",
|
|
"name": "claude-3-5-sonnet-20241022",
|
|
"model": "claude-3-5-sonnet-20241022",
|
|
"provider": "Anthropic",
|
|
"maxLength": 0,
|
|
"tokenLimit": 0,
|
|
"context": 0,
|
|
"success_rate": 100,
|
|
"tps": 25.366666666666667,
|
|
},
|
|
"claude-3-5-sonnet-20241022-t": {
|
|
"id": "claude-3-5-sonnet-20241022-t",
|
|
"name": "claude-3-5-sonnet-20241022-t",
|
|
"model": "claude-3-5-sonnet-20241022-t",
|
|
"provider": "Anthropic",
|
|
"maxLength": 0,
|
|
"tokenLimit": 0,
|
|
"context": 0,
|
|
"success_rate": 100,
|
|
"tps": 39.820754716981135,
|
|
},
|
|
"claude-3-7-sonnet-20250219": {
|
|
"id": "claude-3-7-sonnet-20250219",
|
|
"name": "claude-3-7-sonnet-20250219",
|
|
"model": "claude-3-7-sonnet-20250219",
|
|
"provider": "Anthropic",
|
|
"maxLength": 0,
|
|
"tokenLimit": 0,
|
|
"context": 0,
|
|
"success_rate": 100,
|
|
"tps": 47.02970297029703,
|
|
},
|
|
"claude-3-7-sonnet-20250219-t": {
|
|
"id": "claude-3-7-sonnet-20250219-t",
|
|
"name": "claude-3-7-sonnet-20250219-t",
|
|
"model": "claude-3-7-sonnet-20250219-t",
|
|
"provider": "Anthropic",
|
|
"maxLength": 0,
|
|
"tokenLimit": 0,
|
|
"context": 0,
|
|
"success_rate": 100,
|
|
"tps": 39.04289693593315,
|
|
},
|
|
"deepseek-v3": {
|
|
"id": "deepseek-v3",
|
|
"name": "deepseek-v3",
|
|
"model": "deepseek-v3",
|
|
"provider": "DeepSeek",
|
|
"maxLength": 0,
|
|
"tokenLimit": 0,
|
|
"context": 0,
|
|
"success_rate": 100,
|
|
"tps": 40.484657419083646,
|
|
},
|
|
"gemini-1.0-pro-latest-123": {
|
|
"id": "gemini-1.0-pro-latest-123",
|
|
"name": "gemini-1.0-pro-latest-123",
|
|
"model": "gemini-1.0-pro-latest-123",
|
|
"provider": "Google",
|
|
"maxLength": 0,
|
|
"tokenLimit": 0,
|
|
"context": 0,
|
|
"success_rate": 100,
|
|
"tps": 10,
|
|
},
|
|
"gemini-2.0-flash": {
|
|
"id": "gemini-2.0-flash",
|
|
"name": "gemini-2.0-flash",
|
|
"model": "gemini-2.0-flash",
|
|
"provider": "Google",
|
|
"maxLength": 0,
|
|
"tokenLimit": 0,
|
|
"context": 0,
|
|
"success_rate": 100,
|
|
"tps": 216.44162436548223,
|
|
},
|
|
"gemini-2.0-flash-exp": {
|
|
"id": "gemini-2.0-flash-exp",
|
|
"name": "gemini-2.0-flash-exp",
|
|
"model": "gemini-2.0-flash-exp",
|
|
"provider": "Google",
|
|
"maxLength": 0,
|
|
"tokenLimit": 0,
|
|
"context": 0,
|
|
"success_rate": 0,
|
|
"tps": 0,
|
|
},
|
|
"gemini-2.0-flash-thinking-exp": {
|
|
"id": "gemini-2.0-flash-thinking-exp",
|
|
"name": "gemini-2.0-flash-thinking-exp",
|
|
"model": "gemini-2.0-flash-thinking-exp",
|
|
"provider": "Google",
|
|
"maxLength": 0,
|
|
"tokenLimit": 0,
|
|
"context": 0,
|
|
"success_rate": 0,
|
|
"tps": 0,
|
|
},
|
|
"gemini-2.5-flash-preview-04-17": {
|
|
"id": "gemini-2.5-flash-preview-04-17",
|
|
"name": "gemini-2.5-flash-preview-04-17",
|
|
"model": "gemini-2.5-flash-preview-04-17",
|
|
"provider": "Google",
|
|
"maxLength": 0,
|
|
"tokenLimit": 0,
|
|
"context": 0,
|
|
"success_rate": 100,
|
|
"tps": 189.84010840108402,
|
|
},
|
|
"gemini-2.5-pro-official": {
|
|
"id": "gemini-2.5-pro-official",
|
|
"name": "gemini-2.5-pro-official",
|
|
"model": "gemini-2.5-pro-official",
|
|
"provider": "Google",
|
|
"maxLength": 0,
|
|
"tokenLimit": 0,
|
|
"context": 0,
|
|
"success_rate": 100,
|
|
"tps": 91.00613496932516,
|
|
},
|
|
"gemini-2.5-pro-preview-03-25": {
|
|
"id": "gemini-2.5-pro-preview-03-25",
|
|
"name": "gemini-2.5-pro-preview-03-25",
|
|
"model": "gemini-2.5-pro-preview-03-25",
|
|
"provider": "Google",
|
|
"maxLength": 0,
|
|
"tokenLimit": 0,
|
|
"context": 0,
|
|
"success_rate": 99.05660377358491,
|
|
"tps": 45.050511247443765,
|
|
},
|
|
"gemini-2.5-pro-preview-05-06": {
|
|
"id": "gemini-2.5-pro-preview-05-06",
|
|
"name": "gemini-2.5-pro-preview-05-06",
|
|
"model": "gemini-2.5-pro-preview-05-06",
|
|
"provider": "Google",
|
|
"maxLength": 0,
|
|
"tokenLimit": 0,
|
|
"context": 0,
|
|
"success_rate": 100,
|
|
"tps": 99.29617834394904,
|
|
},
|
|
"gpt-4-turbo-2024-04-09": {
|
|
"id": "gpt-4-turbo-2024-04-09",
|
|
"name": "gpt-4-turbo-2024-04-09",
|
|
"model": "gpt-4-turbo-2024-04-09",
|
|
"provider": "OpenAI",
|
|
"maxLength": 0,
|
|
"tokenLimit": 0,
|
|
"context": 0,
|
|
"success_rate": 100,
|
|
"tps": 1,
|
|
},
|
|
"gpt-4.1": {
|
|
"id": "gpt-4.1",
|
|
"name": "gpt-4.1",
|
|
"model": "gpt-4.1",
|
|
"provider": "OpenAI",
|
|
"maxLength": 0,
|
|
"tokenLimit": 0,
|
|
"context": 0,
|
|
"success_rate": 42.857142857142854,
|
|
"tps": 19.58032786885246,
|
|
},
|
|
"gpt-4.1-mini": {
|
|
"id": "gpt-4.1-mini",
|
|
"name": "gpt-4.1-mini",
|
|
"model": "gpt-4.1-mini",
|
|
"provider": "OpenAI",
|
|
"maxLength": 0,
|
|
"tokenLimit": 0,
|
|
"context": 0,
|
|
"success_rate": 68.75,
|
|
"tps": 12.677576601671309,
|
|
},
|
|
"gpt-4.1-mini-2025-04-14": {
|
|
"id": "gpt-4.1-mini-2025-04-14",
|
|
"name": "gpt-4.1-mini-2025-04-14",
|
|
"model": "gpt-4.1-mini-2025-04-14",
|
|
"provider": "OpenAI",
|
|
"maxLength": 0,
|
|
"tokenLimit": 0,
|
|
"context": 0,
|
|
"success_rate": 94.23076923076923,
|
|
"tps": 8.297687861271676,
|
|
},
|
|
"gpt-4o-2024-11-20": {
|
|
"id": "gpt-4o-2024-11-20",
|
|
"name": "gpt-4o-2024-11-20",
|
|
"model": "gpt-4o-2024-11-20",
|
|
"provider": "OpenAI",
|
|
"maxLength": 0,
|
|
"tokenLimit": 0,
|
|
"context": 0,
|
|
"success_rate": 100,
|
|
"tps": 73.3955223880597,
|
|
},
|
|
"gpt-4o-mini-2024-07-18": {
|
|
"id": "gpt-4o-mini-2024-07-18",
|
|
"name": "gpt-4o-mini-2024-07-18",
|
|
"model": "gpt-4o-mini-2024-07-18",
|
|
"provider": "OpenAI",
|
|
"maxLength": 0,
|
|
"tokenLimit": 0,
|
|
"context": 0,
|
|
"success_rate": 100,
|
|
"tps": 26.874455100261553,
|
|
},
|
|
"grok-3": {
|
|
"id": "grok-3",
|
|
"name": "grok-3",
|
|
"model": "grok-3",
|
|
"provider": "xAI",
|
|
"maxLength": 0,
|
|
"tokenLimit": 0,
|
|
"context": 0,
|
|
"success_rate": 100,
|
|
"tps": 51.110652663165794,
|
|
},
|
|
"grok-3-reason": {
|
|
"id": "grok-3-reason",
|
|
"name": "grok-3-reason",
|
|
"model": "grok-3-reason",
|
|
"provider": "xAI",
|
|
"maxLength": 0,
|
|
"tokenLimit": 0,
|
|
"context": 0,
|
|
"success_rate": 100,
|
|
"tps": 62.81976744186046,
|
|
},
|
|
"o3-mini-2025-01-31": {
|
|
"id": "o3-mini-2025-01-31",
|
|
"name": "o3-mini-2025-01-31",
|
|
"model": "o3-mini-2025-01-31",
|
|
"provider": "Unknown",
|
|
"maxLength": 0,
|
|
"tokenLimit": 0,
|
|
"context": 0,
|
|
"success_rate": 100,
|
|
"tps": 125.31410256410257,
|
|
},
|
|
"qwen3-235b-a22b": {
|
|
"id": "qwen3-235b-a22b",
|
|
"name": "qwen3-235b-a22b",
|
|
"model": "qwen3-235b-a22b",
|
|
"provider": "Alibaba",
|
|
"maxLength": 0,
|
|
"tokenLimit": 0,
|
|
"context": 0,
|
|
"success_rate": 100,
|
|
"tps": 25.846153846153847,
|
|
},
|
|
}
|
|
|
|
class Liaobots(AsyncGeneratorProvider, ProviderModelMixin):
|
|
url = "https://liaobots.work"
|
|
working = True
|
|
supports_message_history = True
|
|
supports_system_message = True
|
|
|
|
default_model = "grok-3"
|
|
models = list(models.keys())
|
|
model_aliases = {
|
|
# Anthropic
|
|
"claude-3.5-sonnet": "claude-3-5-sonnet-20241022",
|
|
"claude-3.5-sonnet": "claude-3-5-sonnet-20241022-t",
|
|
"claude-3.7-sonnet": "claude-3-7-sonnet-20250219",
|
|
"claude-3.7-sonnet": "claude-3-7-sonnet-20250219-t",
|
|
|
|
# DeepSeek
|
|
#"deepseek-v3": "deepseek-v3",
|
|
|
|
# Google
|
|
"gemini-1.0-pro": "gemini-1.0-pro-latest-123",
|
|
"gemini-2.0-flash": "gemini-2.0-flash-exp",
|
|
"gemini-2.0-flash-thinking": "gemini-2.0-flash-thinking-exp",
|
|
"gemini-2.5-flash": "gemini-2.5-flash-preview-04-17",
|
|
"gemini-2.5-pro": "gemini-2.5-pro-official",
|
|
"gemini-2.5-pro": "gemini-2.5-pro-preview-03-25",
|
|
"gemini-2.5-pro": "gemini-2.5-pro-preview-05-06",
|
|
|
|
# OpenAI
|
|
"gpt-4-turbo": "gpt-4-turbo-2024-04-09",
|
|
"gpt-4.1-mini": "gpt-4.1-mini-2025-04-14",
|
|
"gpt-4": "gpt-4o-2024-11-20",
|
|
"gpt-4o": "gpt-4o-2024-11-20",
|
|
"gpt-4o-mini": "gpt-4o-mini-2024-07-18",
|
|
|
|
# xAI
|
|
"grok-3-reason": "grok-3-reason",
|
|
"o3-mini": "o3-mini-2025-01-31",
|
|
"qwen3-235b": "qwen3-235b-a22b",
|
|
}
|
|
|
|
_auth_code = None
|
|
_cookie_jar = None
|
|
|
|
@classmethod
|
|
def is_supported(cls, model: str) -> bool:
|
|
"""
|
|
Check if the given model is supported.
|
|
"""
|
|
return model in models or model in cls.model_aliases
|
|
|
|
@classmethod
|
|
async def create_async_generator(
|
|
cls,
|
|
model: str,
|
|
messages: Messages,
|
|
proxy: str = None,
|
|
connector: BaseConnector = None,
|
|
**kwargs
|
|
) -> AsyncResult:
|
|
model = cls.get_model(model)
|
|
|
|
headers = {
|
|
"accept": "*/*",
|
|
"accept-language": "en-US,en;q=0.9",
|
|
"content-type": "application/json",
|
|
"dnt": "1",
|
|
"origin": "https://liaobots.work",
|
|
"priority": "u=1, i",
|
|
"referer": "https://liaobots.work/en",
|
|
"sec-ch-ua": "\"Chromium\";v=\"135\", \"Not-A.Brand\";v=\"8\"",
|
|
"sec-ch-ua-mobile": "?0",
|
|
"sec-ch-ua-platform": "\"Linux\"",
|
|
"sec-fetch-dest": "empty",
|
|
"sec-fetch-mode": "cors",
|
|
"sec-fetch-site": "same-origin",
|
|
"user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36"
|
|
}
|
|
|
|
async with ClientSession(
|
|
headers=headers,
|
|
cookie_jar=cls._cookie_jar,
|
|
connector=get_connector(connector, proxy, True)
|
|
) as session:
|
|
# First, get a valid auth code
|
|
await cls.get_auth_code(session)
|
|
|
|
# Create conversation ID
|
|
conversation_id = str(uuid.uuid4())
|
|
|
|
# Prepare request data
|
|
data = {
|
|
"conversationId": conversation_id,
|
|
"models": [{
|
|
"modelId": model,
|
|
"provider": models[model]["provider"]
|
|
}],
|
|
"search": "false",
|
|
"messages": messages,
|
|
"key": "",
|
|
"prompt": kwargs.get("system_message", "你是 {{model}},一个由 {{provider}} 训练的大型语言模型,请仔细遵循用户的指示。")
|
|
}
|
|
|
|
# Try to make the chat request
|
|
try:
|
|
# Make the chat request with the current auth code
|
|
async with session.post(
|
|
f"{cls.url}/api/chat",
|
|
json=data,
|
|
headers={"x-auth-code": cls._auth_code},
|
|
ssl=False
|
|
) as response:
|
|
# Check if we got a streaming response
|
|
content_type = response.headers.get("Content-Type", "")
|
|
if "text/event-stream" in content_type:
|
|
async for line in response.content:
|
|
if line.startswith(b"data: "):
|
|
try:
|
|
response_data = json.loads(line[6:])
|
|
|
|
# Check for error response
|
|
if response_data.get("error") is True:
|
|
# Raise RateLimitError for payment required or other errors
|
|
if "402" in str(response_data.get("res_status", "")):
|
|
raise RateLimitError("This model requires payment or credits")
|
|
else:
|
|
error_msg = response_data.get('message', 'Unknown error')
|
|
raise RateLimitError(f"Error: {error_msg}")
|
|
|
|
# Process normal response
|
|
if response_data.get("role") == "assistant" and "content" in response_data:
|
|
content = response_data.get("content")
|
|
yield content
|
|
except json.JSONDecodeError:
|
|
continue
|
|
else:
|
|
# Not a streaming response, might be an error or HTML
|
|
response_text = await response.text()
|
|
|
|
# If we got HTML, we need to bypass CAPTCHA
|
|
if response_text.startswith("<!DOCTYPE html>"):
|
|
await cls.bypass_captcha(session)
|
|
|
|
# Get a fresh auth code
|
|
await cls.get_auth_code(session)
|
|
|
|
# Try the request again
|
|
async with session.post(
|
|
f"{cls.url}/api/chat",
|
|
json=data,
|
|
headers={"x-auth-code": cls._auth_code},
|
|
ssl=False
|
|
) as response2:
|
|
# Check if we got a streaming response
|
|
content_type = response2.headers.get("Content-Type", "")
|
|
if "text/event-stream" in content_type:
|
|
async for line in response2.content:
|
|
if line.startswith(b"data: "):
|
|
try:
|
|
response_data = json.loads(line[6:])
|
|
|
|
# Check for error response
|
|
if response_data.get("error") is True:
|
|
# Raise RateLimitError for payment required or other errors
|
|
if "402" in str(response_data.get("res_status", "")):
|
|
raise RateLimitError("This model requires payment or credits")
|
|
else:
|
|
error_msg = response_data.get('message', 'Unknown error')
|
|
raise RateLimitError(f"Error: {error_msg}")
|
|
|
|
# Process normal response
|
|
if response_data.get("role") == "assistant" and "content" in response_data:
|
|
content = response_data.get("content")
|
|
yield content
|
|
except json.JSONDecodeError:
|
|
continue
|
|
else:
|
|
raise RateLimitError("Failed to get streaming response")
|
|
else:
|
|
raise RateLimitError("Failed to connect to the service")
|
|
except Exception as e:
|
|
# If it's already a RateLimitError, re-raise it
|
|
if isinstance(e, RateLimitError):
|
|
raise
|
|
# Otherwise, wrap it in a RateLimitError
|
|
raise RateLimitError(f"Error processing request: {str(e)}")
|
|
|
|
@classmethod
|
|
async def bypass_captcha(cls, session: ClientSession) -> None:
|
|
"""
|
|
Bypass the CAPTCHA verification by directly making the recaptcha API request.
|
|
"""
|
|
try:
|
|
# First, try the direct recaptcha API request
|
|
async with session.post(
|
|
f"{cls.url}/recaptcha/api/login",
|
|
json={"token": "abcdefghijklmnopqrst"},
|
|
ssl=False
|
|
) as response:
|
|
if response.status == 200:
|
|
try:
|
|
response_text = await response.text()
|
|
|
|
# Try to parse as JSON
|
|
try:
|
|
response_data = json.loads(response_text)
|
|
|
|
# Check if we got a successful response
|
|
if response_data.get("code") == 200:
|
|
cls._cookie_jar = session.cookie_jar
|
|
except json.JSONDecodeError:
|
|
pass
|
|
except Exception:
|
|
pass
|
|
except Exception:
|
|
pass
|
|
|
|
@classmethod
|
|
async def get_auth_code(cls, session: ClientSession) -> None:
|
|
"""
|
|
Get a valid auth code by sending a request with an empty authcode.
|
|
"""
|
|
try:
|
|
# Send request with empty authcode to get a new one
|
|
auth_request_data = {
|
|
"authcode": "",
|
|
"recommendUrl": "https://liaobots.work/zh"
|
|
}
|
|
|
|
async with session.post(
|
|
f"{cls.url}/api/user",
|
|
json=auth_request_data,
|
|
ssl=False
|
|
) as response:
|
|
if response.status == 200:
|
|
response_text = await response.text()
|
|
|
|
try:
|
|
response_data = json.loads(response_text)
|
|
|
|
if "authCode" in response_data:
|
|
cls._auth_code = response_data["authCode"]
|
|
cls._cookie_jar = session.cookie_jar
|
|
return
|
|
except json.JSONDecodeError:
|
|
# If we got HTML, it might be the CAPTCHA page
|
|
if response_text.startswith("<!DOCTYPE html>"):
|
|
await cls.bypass_captcha(session)
|
|
|
|
# Try again after bypassing CAPTCHA
|
|
async with session.post(
|
|
f"{cls.url}/api/user",
|
|
json=auth_request_data,
|
|
ssl=False
|
|
) as response2:
|
|
if response2.status == 200:
|
|
response_text2 = await response2.text()
|
|
|
|
try:
|
|
response_data2 = json.loads(response_text2)
|
|
|
|
if "authCode" in response_data2:
|
|
cls._auth_code = response_data2["authCode"]
|
|
cls._cookie_jar = session.cookie_jar
|
|
return
|
|
except json.JSONDecodeError:
|
|
pass
|
|
except Exception:
|
|
pass
|
|
|
|
# If we're here, we couldn't get a valid auth code
|
|
# Set a default one as a fallback
|
|
cls._auth_code = "DvS3A5GTE9f0D" # Fallback to one of the provided auth codes
|