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

XFEstudio/gpt4free

feat: add Qwen Qwen-3 provider and update audio/media handling

- Introduce `Qwen_Qwen_3` provider in `g4f/Provider/hf_space/Qwen_Qwen_3.py` - Register Qwen_Qwen_3 in `g4f/Provider/hf_space/__init__.py` and add it to `HuggingSpace` - Update `MarkItDown` in `g4f/Provider/audio/MarkItDown.py` to accept and forward `llm_client` and `llm_model` kwargs; add async handling for `text_content` - Modify audio route in `g4f/api/__init__.py` to pass `llm_client` for MarkItDown and set `modalities` only for other providers - Adjust `OpenaiChat` (needs_auth) to merge media for upload and check for media presence before requesting images - Change `get_tempfile` in `g4f/tools/files.py` to determine suffix from file extension using `os.path.splitext` - Refactor provider listing and model mapping in `AnyProvider.get_models()` (g4f/providers/any_provider.py) to update provider order, support new `HarProvider`, initialize attributes, and guard against model_aliases being None - Ensure `AnyProvider.create_async_generator` calls `get_models` before working with providers

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

代码差异

7 个文件 +250 -96
Modified g4f/Provider/audio/MarkItDown.py +22 -2
@@ -1,6 +1,8 @@
1 1 from __future__ import annotations
2 2
3 3 import os
4 import asyncio
5 from typing import Any
4 6
5 7 try:
6 8 from markitdown import MarkItDown as MaItDo, StreamInfo
@@ -21,6 +23,7 @@ class MarkItDown(AsyncGeneratorProvider, ProviderModelMixin):
21 23 model: str,
22 24 messages: Messages,
23 25 media: MediaListType = None,
26 llm_client: Any = None,
24 27 **kwargs
25 28 ) -> AsyncResult:
26 29 if media is None:
@@ -31,11 +34,28 @@ class MarkItDown(AsyncGeneratorProvider, ProviderModelMixin):
31 34 for file, filename in media:
32 35 text = None
33 36 try:
34 text = md.convert(file, stream_info=StreamInfo(filename=filename) if filename else None).text_content
37 result = md.convert(
38 file,
39 stream_info=StreamInfo(filename=filename) if filename else None,
40 llm_client=llm_client,
41 llm_model=model
42 )
43 if asyncio.iscoroutine(result.text_content):
44 text = await result.text_content
45 else:
46 text = result.text_content
35 47 except TypeError:
36 48 copyfile = get_tempfile(file, filename)
37 49 try:
38 text = md.convert(copyfile).text_content
50 result = md.convert(
51 copyfile,
52 llm_client=llm_client,
53 llm_model=model
54 )
55 if asyncio.iscoroutine(result.text_content):
56 text = await result.text_content
57 else:
58 text = result.text_content
39 59 finally:
40 60 os.remove(copyfile)
41 61 text = text.split("### Audio Transcript:\n")[-1]
Added g4f/Provider/hf_space/Qwen_Qwen_3.py +122 -0
@@ -0,0 +1,122 @@
1 from __future__ import annotations
2
3 import aiohttp
4 import json
5 import uuid
6
7 from ...typing import AsyncResult, Messages
8 from ...providers.response import Reasoning, JsonConversation
9 from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin
10 from ..helper import get_last_user_message
11 from ... import debug
12
13 class Qwen_Qwen_3(AsyncGeneratorProvider, ProviderModelMixin):
14 label = "Qwen Qwen-3"
15 url = "https://qwen-qwen3-demo.hf.space"
16 api_endpoint = "https://qwen-qwen3-demo.hf.space/gradio_api/queue/join?__theme=system"
17
18 working = True
19 supports_stream = True
20 supports_system_message = True
21
22 default_model = "qwen3-235b-a22b"
23 models = {
24 default_model,
25 "qwen3-32b",
26 "qwen3-30b-a3b",
27 "qwen3-14b",
28 "qwen3-8b",
29 "qwen3-4b",
30 "qwen3-1.7b",
31 "qwen3-0.6b",
32 }
33 model_aliases = {model: model for model in models}
34
35 @classmethod
36 async def create_async_generator(
37 cls,
38 model: str,
39 messages: Messages,
40 proxy: str = None,
41 conversation: JsonConversation = None,
42 thinking_budget: int = 38,
43 **kwargs
44 ) -> AsyncResult:
45 if conversation is None:
46 conversation = JsonConversation(session_hash=str(uuid.uuid4()).replace('-', ''))
47
48 headers_join = {
49 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0',
50 'Accept': '*/*',
51 'Accept-Language': 'en-US,en;q=0.5',
52 'Accept-Encoding': 'gzip, deflate, br, zstd',
53 'Referer': f'{cls.url}/?__theme=system',
54 'content-type': 'application/json',
55 'Origin': cls.url,
56 'Connection': 'keep-alive',
57 'Sec-Fetch-Dest': 'empty',
58 'Sec-Fetch-Mode': 'cors',
59 'Sec-Fetch-Site': 'same-origin',
60 'Pragma': 'no-cache',
61 'Cache-Control': 'no-cache',
62 }
63
64 sys_prompt = "\n".join([message['content'] for message in messages if message['role'] == 'system'])
65 sys_prompt = sys_prompt if sys_prompt else "You are a helpful and harmless assistant."
66
67 payload_join = {"data":[
68 get_last_user_message(messages),
69 {"thinking_budget": thinking_budget, "model": cls.get_model(model), "sys_prompt": sys_prompt}, None, None],
70 "event_data":None,"fn_index":13,"trigger_id":31,"session_hash":conversation.session_hash
71 }
72
73 async with aiohttp.ClientSession() as session:
74 # Send join request
75 async with session.post(cls.api_endpoint, headers=headers_join, json=payload_join) as response:
76 (await response.json())['event_id']
77
78 # Prepare data stream request
79 url_data = f'{cls.url}/gradio_api/queue/data'
80
81 headers_data = {
82 'Accept': 'text/event-stream',
83 'Accept-Language': 'en-US,en;q=0.5',
84 'Referer': f'{cls.url}/?__theme=system',
85 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0',
86 }
87
88 params_data = {
89 'session_hash': conversation.session_hash,
90 }
91
92 # Send data stream request
93 async with session.get(url_data, headers=headers_data, params=params_data) as response:
94 is_thinking = False
95 async for line in response.content:
96 decoded_line = line.decode('utf-8')
97 if decoded_line.startswith('data: '):
98 try:
99 json_data = json.loads(decoded_line[6:])
100
101 # Look for generation stages
102 if json_data.get('msg') == 'process_generating':
103 if 'output' in json_data and 'data' in json_data['output'] and len(json_data['output']['data']) > 5:
104 updates = json_data['output']['data'][5]
105 for update in updates:
106 if isinstance(update[2], dict):
107 if update[2].get('type') == 'tool':
108 yield Reasoning(update[2].get('content'), status=update[2].get('options', {}).get('title'))
109 is_thinking = True
110 elif isinstance(update, list) and isinstance(update[1], list) and len(update[1]) > 4:
111 if update[1][4] == "content":
112 yield Reasoning(update[2]) if is_thinking else update[2]
113 elif update[1][4] == "options":
114 if update[2] != "done":
115 yield Reasoning(status=update[2])
116 is_thinking = False
117 # Check for completion
118 if json_data.get('msg') == 'process_completed':
119 break
120
121 except json.JSONDecodeError:
122 debug.log("Could not parse JSON:", decoded_line)
Modified g4f/Provider/hf_space/__init__.py +2 -0
@@ -16,6 +16,7 @@ from .Qwen_Qwen_2_5 import Qwen_Qwen_2_5
16 16 from .Qwen_Qwen_2_5M import Qwen_Qwen_2_5M
17 17 from .Qwen_Qwen_2_5_Max import Qwen_Qwen_2_5_Max
18 18 from .Qwen_Qwen_2_72B import Qwen_Qwen_2_72B
19 from .Qwen_Qwen_3 import Qwen_Qwen_3
19 20 from .StabilityAI_SD35Large import StabilityAI_SD35Large
20 21 from .Voodoohop_Flux1Schnell import Voodoohop_Flux1Schnell
21 22
@@ -38,6 +39,7 @@ class HuggingSpace(AsyncGeneratorProvider, ProviderModelMixin):
38 39 Qwen_Qwen_2_5M,
39 40 Qwen_Qwen_2_5_Max,
40 41 Qwen_Qwen_2_72B,
42 Qwen_Qwen_3,
41 43 StabilityAI_SD35Large,
42 44 Voodoohop_Flux1Schnell,
43 45 ]
Modified g4f/Provider/needs_auth/OpenaiChat.py +3 -2
@@ -337,7 +337,8 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
337 337 timeout=timeout
338 338 ) as session:
339 339 image_requests = None
340 if not cls.needs_auth:
340 media = merge_media(media, messages)
341 if not cls.needs_auth and not media:
341 342 if cls._headers is None:
342 343 cls._create_request_args(cls._cookies)
343 344 async with session.get(cls.url, headers=INIT_HEADERS) as response:
@@ -352,7 +353,7 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
352 353 cls._update_request_args(auth_result, session)
353 354 await raise_for_status(response)
354 355 try:
355 image_requests = await cls.upload_images(session, auth_result, merge_media(media, messages))
356 image_requests = await cls.upload_images(session, auth_result, media)
356 357 except Exception as e:
357 358 debug.error("OpenaiChat: Upload image failed")
358 359 debug.error(e)
Modified g4f/api/__init__.py +8 -2
@@ -508,13 +508,19 @@ class Api:
508 508 provider: Annotated[Optional[str], Form()] = None,
509 509 prompt: Annotated[Optional[str], Form()] = "Transcribe this audio"
510 510 ):
511 provider = provider if path_provider is None else path_provider
512 kwargs = {"modalities": ["text"]}
513 if provider == "MarkItDown":
514 kwargs = {
515 "llm_client": self.client,
516 }
511 517 try:
512 518 response = await self.client.chat.completions.create(
513 519 messages=prompt,
514 520 model=model,
515 provider=provider if path_provider is None else path_provider,
521 provider=provider,
516 522 media=[[file.file, file.filename]],
517 modalities=["text"]
523 **kwargs
518 524 )
519 525 return {"text": response.choices[0].message.content, "model": response.model, "provider": response.provider}
520 526 except (ModelNotFoundError, ProviderNotFoundError) as e:
Modified g4f/providers/any_provider.py +92 -89
@@ -6,12 +6,11 @@ from ..providers.retry_provider import IterListProvider
6 6 from ..image import is_data_an_audio
7 7 from ..providers.response import JsonConversation, ProviderInfo
8 8 from ..Provider.needs_auth import OpenaiChat, CopilotAccount
9 from ..Provider.hf import HuggingFace, HuggingFaceMedia
10 9 from ..Provider.hf_space import HuggingSpace
11 10 from .. import Provider
12 11 from .. import models
13 from ..Provider import Cloudflare, LMArenaProvider, Gemini, Grok, DeepSeekAPI, PerplexityLabs, LambdaChat, PollinationsAI, FreeRouter
14 from ..Provider import Microsoft_Phi_4, DeepInfraChat, Blackbox, EdgeTTS, gTTS, MarkItDown
12 from ..Provider import Cloudflare, Gemini, Grok, DeepSeekAPI, PerplexityLabs, LambdaChat, PollinationsAI, FreeRouter
13 from ..Provider import Microsoft_Phi_4, DeepInfraChat, Blackbox, EdgeTTS, gTTS, MarkItDown, HarProvider
15 14 from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
16 15
17 16 class AnyProvider(AsyncGeneratorProvider, ProviderModelMixin):
@@ -20,90 +19,93 @@ class AnyProvider(AsyncGeneratorProvider, ProviderModelMixin):
20 19
21 20 @classmethod
22 21 def get_models(cls, ignored: list[str] = []) -> list[str]:
23 cls.audio_models = {}
24 cls.image_models = []
25 cls.vision_models = []
26 cls.video_models = []
27 model_with_providers = {
28 model: [
29 provider for provider in providers
30 if provider.working and getattr(provider, "parent", provider.__name__) not in ignored
31 ] for model, (_, providers) in models.__models__.items()
32 }
33 model_with_providers = {
34 model: providers for model, providers in model_with_providers.items()
35 if providers
36 }
37 cls.models_count = {
38 model: len(providers) for model, providers in model_with_providers.items() if len(providers) > 1
39 }
40 all_models = [cls.default_model] + list(model_with_providers.keys())
41 for provider in [OpenaiChat, PollinationsAI, HuggingSpace, Cloudflare, PerplexityLabs, Gemini, Grok]:
42 if not provider.working or getattr(provider, "parent", provider.__name__) in ignored:
43 continue
44 if provider == PollinationsAI:
45 all_models.extend([f"{provider.__name__}:{model}" for model in provider.get_models() if model not in all_models])
46 cls.audio_models.update({f"{provider.__name__}:{model}": [] for model in provider.get_models() if model in provider.audio_models})
47 cls.image_models.extend([f"{provider.__name__}:{model}" for model in provider.get_models() if model in provider.image_models])
48 cls.vision_models.extend([f"{provider.__name__}:{model}" for model in provider.get_models() if model in provider.vision_models])
49 else:
50 all_models.extend(provider.get_models())
51 cls.image_models.extend(provider.image_models)
52 cls.vision_models.extend(provider.vision_models)
53 cls.video_models.extend(provider.video_models)
54 if CopilotAccount.working and CopilotAccount.parent not in ignored:
55 all_models.extend(list(CopilotAccount.model_aliases.keys()))
56 if PollinationsAI.working and PollinationsAI.__name__ not in ignored:
57 all_models.extend(list(PollinationsAI.model_aliases.keys()))
58 def clean_name(name: str) -> str:
59 return name.split("/")[-1].split(":")[0].lower(
60 ).replace("-instruct", ""
61 ).replace("-chat", ""
62 ).replace("-08-2024", ""
63 ).replace("-03-2025", ""
64 ).replace("-20250219", ""
65 ).replace("-20241022", ""
66 ).replace("-20240904", ""
67 ).replace("-2025-04-16", ""
68 ).replace("-2025-04-14", ""
69 ).replace("-0125", ""
70 ).replace("-2407", ""
71 ).replace("-2501", ""
72 ).replace("-0324", ""
73 ).replace("-2409", ""
74 ).replace("-2410", ""
75 ).replace("-2411", ""
76 ).replace("-1119", ""
77 ).replace("-0919", ""
78 ).replace("-02-24", ""
79 ).replace("-03-25", ""
80 ).replace("-03-26", ""
81 ).replace("-01-21", ""
82 ).replace("-002", ""
83 ).replace(".1-", "-"
84 ).replace("_", "."
85 ).replace("c4ai-", ""
86 ).replace("-preview", ""
87 ).replace("-experimental", ""
88 ).replace("-v1", ""
89 ).replace("-fp8", ""
90 ).replace("-bf16", ""
91 ).replace("-hf", ""
92 ).replace("llama3", "llama-3")
93 for provider in [HuggingFace, HuggingFaceMedia, LMArenaProvider, LambdaChat, DeepInfraChat]:
94 if not provider.working or getattr(provider, "parent", provider.__name__) in ignored:
95 continue
96 model_map = {clean_name(model): model for model in provider.get_models()}
97 provider.model_aliases.update(model_map)
98 all_models.extend(list(model_map.keys()))
99 cls.image_models.extend([clean_name(model) for model in provider.image_models])
100 cls.vision_models.extend([clean_name(model) for model in provider.vision_models])
101 cls.video_models.extend([clean_name(model) for model in provider.video_models])
102 for provider in [Microsoft_Phi_4, PollinationsAI]:
103 if provider.working and getattr(provider, "parent", provider.__name__) not in ignored:
104 cls.audio_models.update(provider.audio_models)
105 cls.models_count.update({model: all_models.count(model) for model in all_models if all_models.count(model) > cls.models_count.get(model, 0)})
106 return list(dict.fromkeys([model if model else cls.default_model for model in all_models]))
22 if not cls.models:
23 cls.audio_models = {}
24 cls.image_models = []
25 cls.vision_models = []
26 cls.video_models = []
27 model_with_providers = {
28 model: [
29 provider for provider in providers
30 if provider.working and getattr(provider, "parent", provider.__name__) not in ignored
31 ] for model, (_, providers) in models.__models__.items()
32 }
33 model_with_providers = {
34 model: providers for model, providers in model_with_providers.items()
35 if providers
36 }
37 cls.models_count = {
38 model: len(providers) for model, providers in model_with_providers.items() if len(providers) > 1
39 }
40 all_models = [cls.default_model] + list(model_with_providers.keys())
41 for provider in [OpenaiChat, PollinationsAI, HuggingSpace, Cloudflare, PerplexityLabs, Gemini, Grok]:
42 if not provider.working or getattr(provider, "parent", provider.__name__) in ignored:
43 continue
44 if provider == PollinationsAI:
45 all_models.extend([f"{provider.__name__}:{model}" for model in provider.get_models() if model not in all_models])
46 cls.audio_models.update({f"{provider.__name__}:{model}": [] for model in provider.get_models() if model in provider.audio_models})
47 cls.image_models.extend([f"{provider.__name__}:{model}" for model in provider.get_models() if model in provider.image_models])
48 cls.vision_models.extend([f"{provider.__name__}:{model}" for model in provider.get_models() if model in provider.vision_models])
49 else:
50 all_models.extend(provider.get_models())
51 cls.image_models.extend(provider.image_models)
52 cls.vision_models.extend(provider.vision_models)
53 cls.video_models.extend(provider.video_models)
54 if CopilotAccount.working and CopilotAccount.parent not in ignored:
55 all_models.extend(list(CopilotAccount.model_aliases.keys()))
56 if PollinationsAI.working and PollinationsAI.__name__ not in ignored:
57 all_models.extend(list(PollinationsAI.model_aliases.keys()))
58 def clean_name(name: str) -> str:
59 return name.split("/")[-1].split(":")[0].lower(
60 ).replace("-instruct", ""
61 ).replace("-chat", ""
62 ).replace("-08-2024", ""
63 ).replace("-03-2025", ""
64 ).replace("-20250219", ""
65 ).replace("-20241022", ""
66 ).replace("-20240904", ""
67 ).replace("-2025-04-16", ""
68 ).replace("-2025-04-14", ""
69 ).replace("-0125", ""
70 ).replace("-2407", ""
71 ).replace("-2501", ""
72 ).replace("-0324", ""
73 ).replace("-2409", ""
74 ).replace("-2410", ""
75 ).replace("-2411", ""
76 ).replace("-1119", ""
77 ).replace("-0919", ""
78 ).replace("-02-24", ""
79 ).replace("-03-25", ""
80 ).replace("-03-26", ""
81 ).replace("-01-21", ""
82 ).replace("-002", ""
83 ).replace("_", "."
84 ).replace("c4ai-", ""
85 ).replace("-preview", ""
86 ).replace("-experimental", ""
87 ).replace("-v1", ""
88 ).replace("-fp8", ""
89 ).replace("-bf16", ""
90 ).replace("-hf", ""
91 ).replace("llama3", "llama-3")
92 for provider in [HarProvider, LambdaChat, DeepInfraChat]:
93 if not provider.working or getattr(provider, "parent", provider.__name__) in ignored:
94 continue
95 model_map = {clean_name(model): model for model in provider.get_models()}
96 if not provider.model_aliases:
97 provider.model_aliases = {}
98 provider.model_aliases.update(model_map)
99 all_models.extend(list(model_map.keys()))
100 cls.image_models.extend([clean_name(model) for model in provider.image_models])
101 cls.vision_models.extend([clean_name(model) for model in provider.vision_models])
102 cls.video_models.extend([clean_name(model) for model in provider.video_models])
103 for provider in [Microsoft_Phi_4, PollinationsAI]:
104 if provider.working and getattr(provider, "parent", provider.__name__) not in ignored:
105 cls.audio_models.update(provider.audio_models)
106 cls.models_count.update({model: all_models.count(model) for model in all_models if all_models.count(model) > cls.models_count.get(model, 0)})
107 cls.models = list(dict.fromkeys([model if model else cls.default_model for model in all_models]))
108 return cls.models
107 109
108 110 @classmethod
109 111 async def create_async_generator(
@@ -116,6 +118,7 @@ class AnyProvider(AsyncGeneratorProvider, ProviderModelMixin):
116 118 conversation: JsonConversation = None,
117 119 **kwargs
118 120 ) -> AsyncResult:
121 cls.get_models(ignored=ignored)
119 122 providers = []
120 123 if model and ":" in model:
121 124 providers = model.split(":")
@@ -146,8 +149,8 @@ class AnyProvider(AsyncGeneratorProvider, ProviderModelMixin):
146 149 providers.append(provider)
147 150 else:
148 151 for provider in [
149 OpenaiChat, Cloudflare, LMArenaProvider, PerplexityLabs, Gemini, Grok, DeepSeekAPI, FreeRouter, Blackbox,
150 HuggingFace, HuggingFaceMedia, HuggingSpace, LambdaChat, CopilotAccount, PollinationsAI, DeepInfraChat
152 OpenaiChat, Cloudflare, HarProvider, PerplexityLabs, Gemini, Grok, DeepSeekAPI, FreeRouter, Blackbox,
153 HuggingSpace, LambdaChat, CopilotAccount, PollinationsAI, DeepInfraChat
151 154 ]:
152 155 if provider.working:
153 156 if not model or model in provider.get_models() or model in provider.model_aliases:
Modified g4f/tools/files.py +1 -1
@@ -583,7 +583,7 @@ async def get_async_streaming(bucket_dir: str, delete_files = False, refine_chun
583 583 raise e
584 584
585 585 def get_tempfile(file, suffix):
586 copyfile = tempfile.NamedTemporaryFile(suffix=suffix, delete=False)
586 copyfile = tempfile.NamedTemporaryFile(suffix=os.path.splitext(suffix)[-1], delete=False)
587 587 shutil.copyfileobj(file, copyfile)
588 588 copyfile.close()
589 589 file.close()