From 7b32f89eca5b11032d72464717d346c96b34c262 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 29 Nov 2025 04:17:42 +0000 Subject: [PATCH 1/6] Initial plan From f0ea4c5b95d67312e0c183e859ef7bfcecd0fdf6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 29 Nov 2025 04:22:02 +0000 Subject: [PATCH 2/6] Add GradientNetwork provider for chat.gradient.network Co-authored-by: hlohaus <983577+hlohaus@users.noreply.github.com> --- g4f/Provider/GradientNetwork.py | 131 ++++++++++++++++++++++++++++++++ g4f/Provider/__init__.py | 1 + 2 files changed, 132 insertions(+) create mode 100644 g4f/Provider/GradientNetwork.py diff --git a/g4f/Provider/GradientNetwork.py b/g4f/Provider/GradientNetwork.py new file mode 100644 index 00000000..b9286d0b --- /dev/null +++ b/g4f/Provider/GradientNetwork.py @@ -0,0 +1,131 @@ +from __future__ import annotations + +import json + +from aiohttp import ClientSession + +from ..typing import AsyncResult, Messages +from ..providers.response import Reasoning +from .base_provider import AsyncGeneratorProvider, ProviderModelMixin + + +class GradientNetwork(AsyncGeneratorProvider, ProviderModelMixin): + """ + Provider for chat.gradient.network + Supports streaming text generation with various Qwen models. + """ + label = "Gradient Network" + url = "https://chat.gradient.network" + api_endpoint = "https://chat.gradient.network/api/generate" + + working = True + needs_auth = False + supports_stream = True + supports_system_message = True + supports_message_history = True + + default_model = "qwen3-235b" + models = [ + default_model, + "qwen3-32b", + "deepseek-r1-0528", + "deepseek-v3-0324", + "llama-4-maverick", + ] + model_aliases = { + "qwen-3-235b": "qwen3-235b", + "deepseek-r1": "deepseek-r1-0528", + "deepseek-v3": "deepseek-v3-0324", + } + + @classmethod + async def create_async_generator( + cls, + model: str, + messages: Messages, + proxy: str = None, + temperature: float = None, + max_tokens: int = None, + enable_thinking: bool = False, + **kwargs + ) -> AsyncResult: + """ + Create an async generator for streaming chat responses. + + Args: + model: The model name to use + messages: List of message dictionaries + proxy: Optional proxy URL + temperature: Optional temperature parameter + max_tokens: Optional max tokens parameter + enable_thinking: Enable the thinking/analysis channel + **kwargs: Additional arguments + + Yields: + str: Content chunks from the response + Reasoning: Thinking content when enable_thinking is True + """ + model = cls.get_model(model) + + headers = { + "Accept": "application/x-ndjson", + "Content-Type": "application/json", + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36", + "Origin": cls.url, + "Referer": f"{cls.url}/", + } + + payload = { + "model": model, + "messages": messages, + } + + if temperature is not None: + payload["temperature"] = temperature + if max_tokens is not None: + payload["max_tokens"] = max_tokens + if enable_thinking: + payload["enableThinking"] = True + + async with ClientSession(headers=headers) as session: + async with session.post( + cls.api_endpoint, + json=payload, + proxy=proxy + ) as response: + response.raise_for_status() + + async for line_bytes in response.content: + if not line_bytes: + continue + + line = line_bytes.decode("utf-8").strip() + if not line: + continue + + try: + data = json.loads(line) + msg_type = data.get("type") + + if msg_type == "text": + # Regular text content + content = data.get("data") + if content: + yield content + + elif msg_type == "thinking": + # Thinking/reasoning content + content = data.get("data") + if content: + yield Reasoning(content) + + elif msg_type == "done": + # Stream complete + break + + # Ignore clusterInfo and blockUpdate messages + # as they are for GPU cluster visualization only + + except json.JSONDecodeError: + # Skip non-JSON lines + continue diff --git a/g4f/Provider/__init__.py b/g4f/Provider/__init__.py index 826ac4c4..ea51e380 100644 --- a/g4f/Provider/__init__.py +++ b/g4f/Provider/__init__.py @@ -48,6 +48,7 @@ from .Copilot import Copilot from .DeepInfra import DeepInfra from .EasyChat import EasyChat from .GLM import GLM +from .GradientNetwork import GradientNetwork from .LambdaChat import LambdaChat from .Mintlify import Mintlify from .OIVSCodeSer import OIVSCodeSer2, OIVSCodeSer0501 From da4d7d118dcffbf59a5a7c2a7f495e04bdfd9319 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 29 Nov 2025 04:24:09 +0000 Subject: [PATCH 3/6] Use StreamSession for proper line-by-line NDJSON parsing Co-authored-by: hlohaus <983577+hlohaus@users.noreply.github.com> --- g4f/Provider/GradientNetwork.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/g4f/Provider/GradientNetwork.py b/g4f/Provider/GradientNetwork.py index b9286d0b..d5dc1919 100644 --- a/g4f/Provider/GradientNetwork.py +++ b/g4f/Provider/GradientNetwork.py @@ -2,10 +2,9 @@ from __future__ import annotations import json -from aiohttp import ClientSession - from ..typing import AsyncResult, Messages from ..providers.response import Reasoning +from ..requests import StreamSession from .base_provider import AsyncGeneratorProvider, ProviderModelMixin @@ -58,7 +57,7 @@ class GradientNetwork(AsyncGeneratorProvider, ProviderModelMixin): proxy: Optional proxy URL temperature: Optional temperature parameter max_tokens: Optional max tokens parameter - enable_thinking: Enable the thinking/analysis channel + enable_thinking: Enable the thinking/analysis channel (maps to enableThinking in API) **kwargs: Additional arguments Yields: @@ -87,19 +86,14 @@ class GradientNetwork(AsyncGeneratorProvider, ProviderModelMixin): if enable_thinking: payload["enableThinking"] = True - async with ClientSession(headers=headers) as session: + async with StreamSession(headers=headers, proxy=proxy) as session: async with session.post( cls.api_endpoint, json=payload, - proxy=proxy ) as response: response.raise_for_status() - async for line_bytes in response.content: - if not line_bytes: - continue - - line = line_bytes.decode("utf-8").strip() + async for line in response.iter_lines(): if not line: continue @@ -127,5 +121,5 @@ class GradientNetwork(AsyncGeneratorProvider, ProviderModelMixin): # as they are for GPU cluster visualization only except json.JSONDecodeError: - # Skip non-JSON lines + # Skip non-JSON lines (may be partial data or empty) continue From f57663cbe81b9e93399aeb29bdfa39929f532146 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 29 Nov 2025 04:25:22 +0000 Subject: [PATCH 4/6] Address code review: pass enable_thinking value directly, explicit skip for cluster messages Co-authored-by: hlohaus <983577+hlohaus@users.noreply.github.com> --- g4f/Provider/GradientNetwork.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/g4f/Provider/GradientNetwork.py b/g4f/Provider/GradientNetwork.py index d5dc1919..b6aec90c 100644 --- a/g4f/Provider/GradientNetwork.py +++ b/g4f/Provider/GradientNetwork.py @@ -84,7 +84,7 @@ class GradientNetwork(AsyncGeneratorProvider, ProviderModelMixin): if max_tokens is not None: payload["max_tokens"] = max_tokens if enable_thinking: - payload["enableThinking"] = True + payload["enableThinking"] = enable_thinking async with StreamSession(headers=headers, proxy=proxy) as session: async with session.post( @@ -117,8 +117,9 @@ class GradientNetwork(AsyncGeneratorProvider, ProviderModelMixin): # Stream complete break - # Ignore clusterInfo and blockUpdate messages - # as they are for GPU cluster visualization only + elif msg_type in ("clusterInfo", "blockUpdate"): + # Skip GPU cluster visualization messages + continue except json.JSONDecodeError: # Skip non-JSON lines (may be partial data or empty) From 098b2401eae4afe654a3934fd80e1c5996433b48 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 29 Nov 2025 04:36:25 +0000 Subject: [PATCH 5/6] Fix response parsing: use type "reply" with data.content/reasoningContent, update models Co-authored-by: hlohaus <983577+hlohaus@users.noreply.github.com> --- g4f/Provider/GradientNetwork.py | 38 +++++++++++++-------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/g4f/Provider/GradientNetwork.py b/g4f/Provider/GradientNetwork.py index b6aec90c..e253a4bb 100644 --- a/g4f/Provider/GradientNetwork.py +++ b/g4f/Provider/GradientNetwork.py @@ -11,7 +11,7 @@ from .base_provider import AsyncGeneratorProvider, ProviderModelMixin class GradientNetwork(AsyncGeneratorProvider, ProviderModelMixin): """ Provider for chat.gradient.network - Supports streaming text generation with various Qwen models. + Supports streaming text generation with Qwen and GPT OSS models. """ label = "Gradient Network" url = "https://chat.gradient.network" @@ -23,18 +23,15 @@ class GradientNetwork(AsyncGeneratorProvider, ProviderModelMixin): supports_system_message = True supports_message_history = True - default_model = "qwen3-235b" + default_model = "Qwen3 235B" models = [ default_model, - "qwen3-32b", - "deepseek-r1-0528", - "deepseek-v3-0324", - "llama-4-maverick", + "GPT OSS 120B", ] model_aliases = { - "qwen-3-235b": "qwen3-235b", - "deepseek-r1": "deepseek-r1-0528", - "deepseek-v3": "deepseek-v3-0324", + "qwen-3-235b": "Qwen3 235B", + "qwen3-235b": "Qwen3 235B", + "gpt-oss-120b": "GPT OSS 120B", } @classmethod @@ -62,7 +59,7 @@ class GradientNetwork(AsyncGeneratorProvider, ProviderModelMixin): Yields: str: Content chunks from the response - Reasoning: Thinking content when enable_thinking is True + Reasoning: Reasoning content when enable_thinking is True """ model = cls.get_model(model) @@ -101,22 +98,17 @@ class GradientNetwork(AsyncGeneratorProvider, ProviderModelMixin): data = json.loads(line) msg_type = data.get("type") - if msg_type == "text": - # Regular text content - content = data.get("data") + if msg_type == "reply": + # Response chunks with content or reasoningContent + reply_data = data.get("data", {}) + content = reply_data.get("content") + reasoning_content = reply_data.get("reasoningContent") + + if reasoning_content: + yield Reasoning(reasoning_content) if content: yield content - elif msg_type == "thinking": - # Thinking/reasoning content - content = data.get("data") - if content: - yield Reasoning(content) - - elif msg_type == "done": - # Stream complete - break - elif msg_type in ("clusterInfo", "blockUpdate"): # Skip GPU cluster visualization messages continue From 21113c51a64d2e6804cd787e1dd918c2c68e33cf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 29 Nov 2025 04:39:45 +0000 Subject: [PATCH 6/6] Remove redundant continue statement for cluster message handling Co-authored-by: hlohaus <983577+hlohaus@users.noreply.github.com> --- g4f/Provider/GradientNetwork.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/g4f/Provider/GradientNetwork.py b/g4f/Provider/GradientNetwork.py index e253a4bb..2a7f20a8 100644 --- a/g4f/Provider/GradientNetwork.py +++ b/g4f/Provider/GradientNetwork.py @@ -109,9 +109,7 @@ class GradientNetwork(AsyncGeneratorProvider, ProviderModelMixin): if content: yield content - elif msg_type in ("clusterInfo", "blockUpdate"): - # Skip GPU cluster visualization messages - continue + # Skip clusterInfo and blockUpdate GPU visualization messages except json.JSONDecodeError: # Skip non-JSON lines (may be partial data or empty)