XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 1
返回提交历史

XFEstudio/gpt4free

feat: add EasyChat and GLM providers, update HuggingFace and SSE parsing

- Added new `EasyChat` provider (`g4f/Provider/EasyChat.py`) with captcha handling, nodriver callback, and token caching - Added new `GLM` provider (`g4f/Provider/GLM.py`) with model retrieval, auth token fetch, and SSE streaming support - Updated `g4f/Provider/__init__.py` to import `EasyChat` and `GLM` - Modified `LMArenaBeta` in `g4f/Provider/needs_auth/LMArenaBeta.py` to remove nodriver availability check and always use `get_args_from_nodriver` with callback - Updated `HuggingFaceAPI` in `g4f/Provider/needs_auth/hf/HuggingFaceAPI.py` to use `default_model` from `models` instead of `default_llama_model` and removed commented `max_inputs_lenght` param - Updated `HuggingFace` in `g4f/Provider/needs_auth/hf/__init__.py` to import `default_model` instead of `default_vision_model`, set `default_model` class attribute, and commented out HuggingFaceInference and image model handling logic - Modified `OpenaiTemplate` in `g4f/Provider/template/OpenaiTemplate.py` to prefer `"name"` over `"id"` when populating `vision_models`, `models`, and `models_count` - Enhanced `sse_stream` in `g4f/requests/__init__.py` to strip and skip empty `data:` lines, handle JSON decode errors, and raise `ValueError` on invalid JSON

b9dfe1a4
hlohaus <983577+hlohaus@users.noreply.github.com>
提交于

代码差异

9 个文件 +234 -46
Modified g4f/Provider/Blackbox.py +4 -3
@@ -300,9 +300,10 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin):
300 300 async for chunk in response.content.iter_any():
301 301 if chunk:
302 302 chunk_text = chunk.decode()
303 full_response.append(chunk_text)
304 yield chunk_text
305
303 if chunk_text != "Login to continue using":
304 full_response.append(chunk_text)
305 yield chunk_text
306
306 307 full_response_text = ''.join(full_response)
307 308
308 309 # Handle conversation history
Added g4f/Provider/EasyChat.py +108 -0
@@ -0,0 +1,108 @@
1 from __future__ import annotations
2
3 import asyncio
4 import json
5 try:
6 import nodriver
7 except ImportError:
8 pass
9
10 from ..typing import AsyncResult, Messages
11 from ..config import DEFAULT_MODEL
12 from ..requests import get_args_from_nodriver
13 from ..providers.base_provider import AuthFileMixin
14 from .template import OpenaiTemplate
15 from .. import debug
16
17 class EasyChat(OpenaiTemplate, AuthFileMixin):
18 url = "https://chat3.eqing.tech"
19 api_base = f"{url}/api/openai/v1"
20 api_endpoint = f"{api_base}/chat/completions"
21 working = True
22 active_by_default = True
23
24 default_model = "gpt-oss-120b-free"
25 model_aliases = {
26 DEFAULT_MODEL: default_model,
27 }
28
29 captchaToken: dict = None
30
31 @classmethod
32 async def create_async_generator(
33 cls,
34 model: str,
35 messages: Messages,
36 proxy: str = None,
37 extra_body: dict = None,
38 **kwargs
39 ) -> AsyncResult:
40 args = None
41 auth_file = cls.get_cache_file()
42 if auth_file.exists():
43 with auth_file.open("r") as f:
44 args = json.load(f)
45 cls.captchaToken = args.pop("captchaToken")
46 if cls.captchaToken:
47 debug.log("EasyChat: Using cached captchaToken.")
48 def on_request(event: nodriver.cdp.network.RequestWillBeSent, page=None):
49 if event.request.url != cls.api_endpoint:
50 return
51 if not event.request.post_data:
52 return
53 cls.captchaToken = json.loads(event.request.post_data).get("captchaToken")
54 async def callback(page):
55 await page.send(nodriver.cdp.network.enable())
56 page.add_handler(nodriver.cdp.network.RequestWillBeSent, on_request)
57 button = await page.find("我已知晓")
58 if button:
59 await button.click()
60 else:
61 debug.error("No 'Agree' button found.")
62 for _ in range(3):
63 for _ in range(300):
64 modal = await page.find("Verifying...")
65 if not modal:
66 break
67 debug.log("EasyChaat: Waiting for captcha verification...")
68 if cls.captchaToken:
69 debug.log("EasyChat: Captcha token found, proceeding.")
70 break
71 textarea = await page.select("textarea", 180)
72 await textarea.send_keys("Hello")
73 await asyncio.sleep(1)
74 button = await page.select("button[class*='chat_chat-input-send']")
75 if button:
76 await button.click()
77 for _ in range(300):
78 await asyncio.sleep(1)
79 if cls.captchaToken:
80 break
81 await asyncio.sleep(3)
82 if not args:
83 args = await get_args_from_nodriver(cls.url, proxy=proxy, callback=callback)
84 if extra_body is None:
85 extra_body = {}
86 extra_body.setdefault("captchaToken", cls.captchaToken)
87 try:
88 last_chunk = None
89 async for chunk in super().create_async_generator(
90 model=model,
91 messages=messages,
92 extra_body=extra_body,
93 **args
94 ):
95 # Remove provided by
96 if last_chunk == "\n" and chunk == "\n":
97 break
98 last_chunk = chunk
99 yield chunk
100 except Exception as e:
101 if "CLEAR-CAPTCHA-TOKEN" in str(e):
102 auth_file.unlink(missing_ok=True)
103 cls.captchaToken = None
104 debug.log("EasyChat: Captcha token cleared, please try again.")
105 raise e
106 with auth_file.open("w") as f:
107 json.dump({**args, "captchaToken": cls.captchaToken}, f)
108
Added g4f/Provider/GLM.py +78 -0
@@ -0,0 +1,78 @@
1 from __future__ import annotations
2
3 import uuid
4 import requests
5
6 from ..typing import AsyncResult, Messages
7 from ..providers.response import Usage, Reasoning
8 from ..requests import StreamSession, raise_for_status
9 from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
10
11 class GLM(AsyncGeneratorProvider, ProviderModelMixin):
12 url = "https://chat.z.ai"
13 api_endpoint = "https://chat.z.ai/api/chat/completions"
14 working = True
15 active_by_default = True
16 default_model = "GLM-4.5"
17 api_key = None
18
19 @classmethod
20 def get_models(cls, **kwargs) -> str:
21 if not cls.models:
22 response = requests.get(f"{cls.url}/api/v1/auths/")
23 cls.api_key = response.json().get("token")
24 response = requests.get(f"{cls.url}/api/models", headers={"Authorization": f"Bearer {cls.api_key}"})
25 data = response.json().get("data", [])
26 cls.model_aliases = {data.get("name"): data.get("id") for data in data}
27 cls.models = list(cls.model_aliases.keys())
28 return cls.models
29
30 @classmethod
31 async def create_async_generator(
32 cls,
33 model: str,
34 messages: Messages,
35 proxy: str = None,
36 **kwargs
37 ) -> AsyncResult:
38 cls.get_models()
39 model = cls.get_model(model)
40 data = {
41 "chat_id": "local",
42 "id": str(uuid.uuid4()),
43 "stream": True,
44 "model": model,
45 "messages": messages,
46 "params": {},
47 "tool_servers": [],
48 }
49 async with StreamSession(
50 impersonate="chrome",
51 proxy=proxy,
52 ) as session:
53 async with session.post(
54 cls.api_endpoint,
55 json=data,
56 headers={"Authorization": f"Bearer {cls.api_key}", "x-fe-version": "prod-fe-1.0.57"},
57 ) as response:
58 await raise_for_status(response)
59 usage = None
60 async for chunk in response.sse():
61 if chunk.get("type") == "chat:completion":
62 if not usage:
63 usage = chunk.get("data", {}).get("usage")
64 if usage:
65 yield Usage(**usage)
66 if chunk.get("data", {}).get("phase") == "thinking":
67 delta_content = chunk.get("data", {}).get("delta_content")
68 delta_content = delta_content.split("</summary>\n>")[-1] if delta_content else ""
69 if delta_content:
70 yield Reasoning(delta_content)
71 else:
72 edit_content = chunk.get("data", {}).get("edit_content")
73 if edit_content:
74 yield edit_content.split("\n</details>\n")[-1]
75 else:
76 delta_content = chunk.get("data", {}).get("delta_content")
77 if delta_content:
78 yield delta_content
Modified g4f/Provider/__init__.py +2 -0
@@ -39,7 +39,9 @@ from .Cloudflare import Cloudflare
39 39 from .Copilot import Copilot
40 40 from .DeepInfraChat import DeepInfraChat
41 41 from .DuckDuckGo import DuckDuckGo
42 from .EasyChat import EasyChat
42 43 from .Free2GPT import Free2GPT
44 from .GLM import GLM
43 45 from .GptOss import GptOss
44 46 from .ImageLabs import ImageLabs
45 47 from .Kimi import Kimi
Modified g4f/Provider/needs_auth/LMArenaBeta.py +6 -12
@@ -147,19 +147,13 @@ class LMArenaBeta(AsyncGeneratorProvider, ProviderModelMixin, AuthFileMixin):
147 147 if cache_file.exists() and cache_file.stat().st_mtime > time.time() - 60 * 30:
148 148 with cache_file.open("r") as f:
149 149 args = json.load(f)
150 elif has_nodriver:
151 try:
152 async def callback(page):
153 while not await page.evaluate('document.cookie.indexOf("arena-auth-prod-v1") >= 0'):
154 await asyncio.sleep(1)
155 while not await page.evaluate('document.querySelector(\'textarea\')'):
156 await asyncio.sleep(1)
157 args = await get_args_from_nodriver(cls.url, proxy=proxy, callback=callback)
158 except (RuntimeError, FileNotFoundError) as e:
159 debug.log(f"Nodriver is not available:", e)
160 args = {"headers": DEFAULT_HEADERS, "cookies": {}, "impersonate": "chrome"}
161 150 else:
162 args = {"headers": DEFAULT_HEADERS, "cookies": {}, "impersonate": "chrome"}
151 async def callback(page):
152 while not await page.evaluate('document.cookie.indexOf("arena-auth-prod-v1") >= 0'):
153 await asyncio.sleep(1)
154 while not await page.evaluate('document.querySelector(\'textarea\')'):
155 await asyncio.sleep(1)
156 args = await get_args_from_nodriver(cls.url, proxy=proxy, callback=callback)
163 157
164 158 # Build the JSON payload
165 159 is_image_model = model in image_models
Modified g4f/Provider/needs_auth/hf/HuggingFaceAPI.py +2 -3
@@ -8,7 +8,7 @@ from ....requests import StreamSession, raise_for_status
8 8 from ....errors import ModelNotFoundError, PaymentRequiredError
9 9 from ....providers.response import ProviderInfo
10 10 from ...template.OpenaiTemplate import OpenaiTemplate
11 from .models import model_aliases, vision_models, default_llama_model, default_vision_model, text_models
11 from .models import model_aliases, vision_models, default_model, default_vision_model, text_models
12 12
13 13 class HuggingFaceAPI(OpenaiTemplate):
14 14 label = "HuggingFace (Text Generation)"
@@ -18,7 +18,7 @@ class HuggingFaceAPI(OpenaiTemplate):
18 18 working = True
19 19 needs_auth = True
20 20
21 default_model = default_llama_model
21 default_model = default_model
22 22 default_vision_model = default_vision_model
23 23 vision_models = vision_models
24 24 model_aliases = model_aliases
@@ -78,7 +78,6 @@ class HuggingFaceAPI(OpenaiTemplate):
78 78 api_base: str = None,
79 79 api_key: str = None,
80 80 max_tokens: int = 2048,
81 # max_inputs_lenght: int = 10000,
82 81 media: MediaListType = None,
83 82 **kwargs
84 83 ):
Modified g4f/Provider/needs_auth/hf/__init__.py +23 -23
@@ -10,7 +10,7 @@ from .HuggingChat import HuggingChat
10 10 from .HuggingFaceAPI import HuggingFaceAPI
11 11 from .HuggingFaceInference import HuggingFaceInference
12 12 from .HuggingFaceMedia import HuggingFaceMedia
13 from .models import model_aliases, image_model_aliases, vision_models, default_vision_model
13 from .models import model_aliases, image_model_aliases, vision_models, default_model
14 14 from .... import debug
15 15
16 16 class HuggingFace(AsyncGeneratorProvider, ProviderModelMixin):
@@ -28,7 +28,7 @@ class HuggingFace(AsyncGeneratorProvider, ProviderModelMixin):
28 28
29 29 model_aliases = {**model_aliases, **image_model_aliases}
30 30 vision_models = vision_models
31 default_vision_model = default_vision_model
31 default_model = default_model
32 32
33 33 @classmethod
34 34 async def create_async_generator(
@@ -39,19 +39,19 @@ class HuggingFace(AsyncGeneratorProvider, ProviderModelMixin):
39 39 ) -> AsyncResult:
40 40 if model in cls.model_aliases:
41 41 model = cls.model_aliases[model]
42 if "tools" not in kwargs and "media" not in kwargs and random.random() >= 0.5:
43 try:
44 is_started = False
45 async for chunk in HuggingFaceInference.create_async_generator(model, messages, **kwargs):
46 if isinstance(chunk, (str, ImageResponse)):
47 is_started = True
48 yield chunk
49 if is_started:
50 return
51 except Exception as e:
52 if is_started:
53 raise e
54 debug.error(f"{cls.__name__} {type(e).__name__}; {e}")
42 # if "tools" not in kwargs and "media" not in kwargs and random.random() >= 0.5:
43 # try:
44 # is_started = False
45 # async for chunk in HuggingFaceInference.create_async_generator(model, messages, **kwargs):
46 # if isinstance(chunk, (str, ImageResponse)):
47 # is_started = True
48 # yield chunk
49 # if is_started:
50 # return
51 # except Exception as e:
52 # if is_started:
53 # raise e
54 # debug.error(f"{cls.__name__} {type(e).__name__}; {e}")
55 55 if not cls.image_models:
56 56 cls.get_models()
57 57 try:
@@ -60,14 +60,14 @@ class HuggingFace(AsyncGeneratorProvider, ProviderModelMixin):
60 60 return
61 61 except ModelNotFoundError:
62 62 pass
63 if model in cls.image_models:
64 if "api_key" not in kwargs:
65 async for chunk in HuggingChat.create_async_generator(model, messages, **kwargs):
66 yield chunk
67 else:
68 async for chunk in HuggingFaceInference.create_async_generator(model, messages, **kwargs):
69 yield chunk
70 return
63 # if model in cls.image_models:
64 # if "api_key" not in kwargs:
65 # async for chunk in HuggingChat.create_async_generator(model, messages, **kwargs):
66 # yield chunk
67 # else:
68 # async for chunk in HuggingFaceInference.create_async_generator(model, messages, **kwargs):
69 # yield chunk
70 # return
71 71 try:
72 72 async for chunk in HuggingFaceAPI.create_async_generator(model, messages, **kwargs):
73 73 yield chunk
Modified g4f/Provider/template/OpenaiTemplate.py +3 -3
@@ -47,9 +47,9 @@ class OpenaiTemplate(AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin
47 47 data = data.get("data") if isinstance(data, dict) else data
48 48 cls.image_models = [model.get("id", model.get("name")) for model in data if model.get("image") or model.get("type") == "image"]
49 49 cls.vision_models = cls.vision_models.copy()
50 cls.vision_models += [model.get("id", model.get("name")) for model in data if model.get("vision")]
51 cls.models = [model.get("id", model.get("name")) for model in data]
52 cls.models_count = {model.get("id", model.get("name")): len(model.get("providers", [])) for model in data if len(model.get("providers", [])) > 1}
50 cls.vision_models += [model.get("name", model.get("id")) for model in data if model.get("vision")]
51 cls.models = [model.get("name", model.get("id")) for model in data]
52 cls.models_count = {model.get("name", model.get("id")): len(model.get("providers", [])) for model in data if len(model.get("providers", [])) > 1}
53 53 if cls.sort_models:
54 54 cls.models.sort()
55 55 except Exception as e:
Modified g4f/requests/__init__.py +8 -2
@@ -222,9 +222,15 @@ async def sse_stream(iter_lines: Iterator[bytes]) -> AsyncIterator[dict]:
222 222 iter_lines = iter_lines.iter_lines()
223 223 async for line in iter_lines:
224 224 if line.startswith(b"data: "):
225 if line[6:].startswith(b"[DONE]"):
225 rest = line[6:].strip()
226 if not rest:
227 continue
228 if rest.startswith(b"[DONE]"):
226 229 break
227 yield json.loads(line[6:])
230 try:
231 yield json.loads(rest)
232 except json.JSONDecodeError:
233 raise ValueError(f"Invalid JSON data: {rest}")
228 234
229 235 async def iter_lines(iter_response: AsyncIterator[bytes], delimiter=None):
230 236 """