返回提交历史
Modified
etc/unittest/models.py
+6
-1
Modified
g4f/Provider/Airforce.py
+14
-13
Modified
g4f/Provider/Copilot.py
+30
-21
Modified
g4f/Provider/openai/har_file.py
+2
-0
Modified
g4f/gui/client/static/css/style.css
+1
-1
Modified
g4f/gui/server/api.py
+3
-3
Modified
g4f/models.py
+84
-100
XFEstudio/gpt4free
Add streaming and system messages support in Airforce
315a2f25
代码差异
7 个文件
+140
-139
@@ -26,4 +26,9 @@ class TestProviderHasModel(unittest.IsolatedAsyncioTestCase):
26
26
except (MissingRequirementsError, MissingAuthError):
27
27
return
28
28
if self.cache[provider.__name__]:
29
self.assertIn(model, self.cache[provider.__name__], provider.__name__)
29
self.assertIn(model, self.cache[provider.__name__], provider.__name__)
30
31
async def test_all_providers_working(self):
32
for model, providers in __models__.values():
33
for provider in providers:
34
self.assertTrue(provider.working, f"{provider.__name__} in {model.name}")
@@ -5,8 +5,10 @@ import requests
5
5
from aiohttp import ClientSession
6
6
from typing import List
7
7
from requests.packages.urllib3.exceptions import InsecureRequestWarning
8
8
9
from ..typing import AsyncResult, Messages
9
10
from ..image import ImageResponse
11
from ..requests.raise_for_status import raise_for_status
10
12
from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
11
13
12
14
from .. import debug
@@ -32,7 +34,7 @@ class Airforce(AsyncGeneratorProvider, ProviderModelMixin):
32
34
api_endpoint_imagine2 = "https://api.airforce/imagine2"
33
35
34
36
working = True
35
supports_stream = False
37
supports_stream = True
36
38
supports_system_message = True
37
39
supports_message_history = True
38
40
@@ -87,7 +89,7 @@ class Airforce(AsyncGeneratorProvider, ProviderModelMixin):
87
89
debug.log(f"Error fetching text models: {e}")
88
90
89
91
return cls.models
90
92
91
93
@classmethod
92
94
async def check_api_key(cls, api_key: str) -> bool:
93
95
"""
@@ -95,12 +97,11 @@ class Airforce(AsyncGeneratorProvider, ProviderModelMixin):
95
97
"""
96
98
if not api_key or api_key == "null":
97
99
return True # No restrictions if no key.
98
100
99
101
headers = {
100
102
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
101
103
"Accept": "*/*",
102
104
}
103
104
105
try:
105
106
async with ClientSession(headers=headers) as session:
106
107
async with session.get(f"https://api.airforce/check?key={api_key}") as response:
@@ -195,11 +196,13 @@ class Airforce(AsyncGeneratorProvider, ProviderModelMixin):
195
196
"Content-Type": "application/json",
196
197
"Authorization": f"Bearer {api_key}",
197
198
}
198
full_message = "\n".join([msg['content'] for msg in messages])
199
message_chunks = split_message(full_message, max_length=1000)
200
199
200
final_messages = []
201
for message in messages:
202
message_chunks = split_message(message["content"], max_length=1000)
203
final_messages.extend([{"role": message["role"], "content": chunk} for chunk in message_chunks])
201
204
data = {
202
"messages": [{"role": "user", "content": chunk} for chunk in message_chunks],
205
"messages": final_messages,
203
206
"model": model,
204
207
"max_tokens": max_tokens,
205
208
"temperature": temperature,
@@ -209,10 +212,9 @@ class Airforce(AsyncGeneratorProvider, ProviderModelMixin):
209
212
210
213
async with ClientSession(headers=headers) as session:
211
214
async with session.post(cls.api_endpoint_completions, json=data, proxy=proxy) as response:
212
response.raise_for_status()
215
await raise_for_status(response)
213
216
214
217
if stream:
215
buffer = [] # Buffer to collect partial responses
216
218
async for line in response.content:
217
219
line = line.decode('utf-8').strip()
218
220
if line.startswith('data: '):
@@ -222,12 +224,11 @@ class Airforce(AsyncGeneratorProvider, ProviderModelMixin):
222
224
if 'choices' in chunk and chunk['choices']:
223
225
delta = chunk['choices'][0].get('delta', {})
224
226
if 'content' in delta:
225
buffer.append(delta['content'])
227
chunk = cls._filter_response(delta['content'])
228
if chunk:
229
yield chunk
226
230
except json.JSONDecodeError:
227
231
continue
228
# Combine the buffered response and filter it
229
filtered_response = cls._filter_response(''.join(buffer))
230
yield filtered_response
231
232
else:
232
233
# Non-streaming response
233
234
result = await response.json()
@@ -47,7 +47,7 @@ class Copilot(AbstractProvider, ProviderModelMixin):
47
47
48
48
websocket_url = "wss://copilot.microsoft.com/c/api/chat?api-version=2"
49
49
conversation_url = f"{url}/c/api/conversations"
50
50
51
51
_access_token: str = None
52
52
_cookies: CookieJar = None
53
53
@@ -94,20 +94,20 @@ class Copilot(AbstractProvider, ProviderModelMixin):
94
94
) as session:
95
95
if cls._access_token is not None:
96
96
cls._cookies = session.cookies.jar
97
if cls._access_token is None:
98
try:
99
url = "https://copilot.microsoft.com/cl/eus-sc/collect"
100
headers = {
101
"Accept": "application/x-clarity-gzip",
102
"referrer": "https://copilot.microsoft.com/onboarding"
103
}
104
response = session.post(url, headers=headers, data=get_clarity())
105
clarity_token = json.loads(response.text.split(" ", maxsplit=1)[-1])[0]["value"]
106
debug.log(f"Copilot: Clarity Token: ...{clarity_token[-12:]}")
107
except Exception as e:
108
debug.log(f"Copilot: {e}")
109
else:
110
clarity_token = None
97
# if cls._access_token is None:
98
# try:
99
# url = "https://copilot.microsoft.com/cl/eus-sc/collect"
100
# headers = {
101
# "Accept": "application/x-clarity-gzip",
102
# "referrer": "https://copilot.microsoft.com/onboarding"
103
# }
104
# response = session.post(url, headers=headers, data=get_clarity())
105
# clarity_token = json.loads(response.text.split(" ", maxsplit=1)[-1])[0]["value"]
106
# debug.log(f"Copilot: Clarity Token: ...{clarity_token[-12:]}")
107
# except Exception as e:
108
# debug.log(f"Copilot: {e}")
109
# else:
110
# clarity_token = None
111
111
response = session.get("https://copilot.microsoft.com/c/api/user")
112
112
raise_for_status(response)
113
113
user = response.json().get('firstName')
@@ -121,6 +121,14 @@ class Copilot(AbstractProvider, ProviderModelMixin):
121
121
if return_conversation:
122
122
yield Conversation(conversation_id)
123
123
prompt = format_prompt(messages)
124
if len(prompt) > 10000:
125
if len(messages) > 6:
126
prompt = format_prompt(messages[:3]+messages[-3:])
127
elif len(messages) > 2:
128
prompt = format_prompt(messages[:2]+messages[-1:])
129
if len(prompt) > 10000:
130
prompt = messages[-1]["content"]
131
debug.log(f"Copilot: Trim messages to: {len(prompt)}")
124
132
debug.log(f"Copilot: Created conversation: {conversation_id}")
125
133
else:
126
134
conversation_id = conversation.conversation_id
@@ -138,14 +146,15 @@ class Copilot(AbstractProvider, ProviderModelMixin):
138
146
)
139
147
raise_for_status(response)
140
148
uploaded_images.append({"type":"image", "url": response.json().get("url")})
149
break
141
150
142
151
wss = session.ws_connect(cls.websocket_url)
143
if clarity_token is not None:
144
wss.send(json.dumps({
145
"event": "challengeResponse",
146
"token": clarity_token,
147
"method":"clarity"
148
}).encode(), CurlWsFlag.TEXT)
152
# if clarity_token is not None:
153
# wss.send(json.dumps({
154
# "event": "challengeResponse",
155
# "token": clarity_token,
156
# "method":"clarity"
157
# }).encode(), CurlWsFlag.TEXT)
149
158
wss.send(json.dumps({
150
159
"event": "send",
151
160
"conversationId": conversation_id,
@@ -44,6 +44,8 @@ class arkReq:
44
44
self.userAgent = userAgent
45
45
46
46
def get_har_files():
47
if not os.access(get_cookies_dir(), os.R_OK):
48
raise NoValidHarFileError("har_and_cookies dir is not readable")
47
49
harPath = []
48
50
for root, _, files in os.walk(get_cookies_dir()):
49
51
for file in files:
@@ -1028,7 +1028,7 @@ ul {
1028
1028
.buttons {
1029
1029
align-items: flex-start;
1030
1030
flex-wrap: wrap;
1031
gap: 15px;
1031
gap: 12px;
1032
1032
}
1033
1033
1034
1034
.mobile-sidebar {
@@ -120,11 +120,11 @@ class Api:
120
120
}
121
121
122
122
def _create_response_stream(self, kwargs: dict, conversation_id: str, provider: str, download_images: bool = True) -> Iterator:
123
def log_handler(text: str):
123
def decorated_log(text: str):
124
124
debug.logs.append(text)
125
125
if debug.logging:
126
print(text)
127
debug.log_handler = log_handler
126
debug.log_handler(text)
127
debug.log = decorated_log
128
128
proxy = os.environ.get("G4F_PROXY")
129
129
provider = kwargs.get("provider")
130
130
model, provider_handler = get_model_and_provider(
@@ -4,7 +4,6 @@ from dataclasses import dataclass
4
4
5
5
from .Provider import IterListProvider, ProviderType
6
6
from .Provider import (
7
AIChatFree,
8
7
Blackbox,
9
8
Blackbox2,
10
9
BingCreateImages,
@@ -72,10 +71,10 @@ default = Model(
72
71
ReplicateHome,
73
72
Blackbox2,
74
73
Blackbox,
74
Copilot,
75
75
Free2GPT,
76
76
DeepInfraChat,
77
77
Airforce,
78
ChatGptEs,
79
78
Cloudflare,
80
79
Mhystical,
81
80
PollinationsAI,
@@ -256,7 +255,7 @@ phi_3_5_mini = Model(
256
255
gemini_pro = Model(
257
256
name = 'gemini-pro',
258
257
base_provider = 'Google DeepMind',
259
best_provider = IterListProvider([Blackbox, AIChatFree, Gemini, GeminiPro, Liaobots])
258
best_provider = IterListProvider([Blackbox, Gemini, GeminiPro, Liaobots])
260
259
)
261
260
262
261
gemini_flash = Model(
@@ -408,7 +407,6 @@ grok_beta = Model(
408
407
best_provider = Liaobots
409
408
)
410
409
411
412
410
### Perplexity AI ###
413
411
sonar_online = Model(
414
412
name = 'sonar-online',
@@ -429,7 +427,6 @@ nemotron_70b = Model(
429
427
best_provider = IterListProvider([DeepInfraChat, HuggingChat, HuggingFace])
430
428
)
431
429
432
433
430
### Teknium ###
434
431
openhermes_2_5 = Model(
435
432
name = 'openhermes-2.5',
@@ -466,13 +463,6 @@ neural_7b = Model(
466
463
best_provider = Airforce
467
464
)
468
465
469
### PollinationsAI ###
470
p1 = Model(
471
name = 'p1',
472
base_provider = 'PollinationsAI',
473
best_provider = PollinationsAI
474
)
475
476
466
### Uncensored AI ###
477
467
evil = Model(
478
468
name = 'evil',
@@ -529,7 +519,6 @@ playground_v2_5 = ImageModel(
529
519
best_provider = ReplicateHome
530
520
)
531
521
532
533
522
### Flux AI ###
534
523
flux = ImageModel(
535
524
name = 'flux',
@@ -629,184 +618,181 @@ class ModelUtils:
629
618
'gpt-3': gpt_35_turbo,
630
619
631
620
# gpt-3.5
632
'gpt-3.5-turbo': gpt_35_turbo,
621
gpt_35_turbo.name: gpt_35_turbo,
633
622
634
623
# gpt-4
635
'gpt-4': gpt_4,
636
'gpt-4-turbo': gpt_4_turbo,
624
gpt_4.name: gpt_4,
625
gpt_4_turbo.name: gpt_4_turbo,
637
626
638
627
# gpt-4o
639
'gpt-4o': gpt_4o,
640
'gpt-4o-mini': gpt_4o_mini,
628
gpt_4o.name: gpt_4o,
629
gpt_4o_mini.name: gpt_4o_mini,
641
630
642
631
# o1
643
'o1-preview': o1_preview,
644
'o1-mini': o1_mini,
632
o1_preview.name: o1_preview,
633
o1_mini.name: o1_mini,
645
634
646
635
### Meta ###
647
"meta-ai": meta,
636
meta.name: meta,
648
637
649
638
# llama-2
650
'llama-2-7b': llama_2_7b,
639
llama_2_7b.name: llama_2_7b,
651
640
652
641
# llama-3
653
'llama-3-8b': llama_3_8b,
642
llama_3_8b.name: llama_3_8b,
654
643
655
644
# llama-3.1
656
'llama-3.1-8b': llama_3_1_8b,
657
'llama-3.1-70b': llama_3_1_70b,
658
'llama-3.1-405b': llama_3_1_405b,
645
llama_3_1_8b.name: llama_3_1_8b,
646
llama_3_1_70b.name: llama_3_1_70b,
647
llama_3_1_405b.name: llama_3_1_405b,
659
648
660
649
# llama-3.2
661
'llama-3.2-1b': llama_3_2_1b,
662
'llama-3.2-11b': llama_3_2_11b,
650
llama_3_2_1b.name: llama_3_2_1b,
651
llama_3_2_11b.name: llama_3_2_11b,
663
652
664
653
# llama-3.3
665
'llama-3.3-70b': llama_3_3_70b,
654
llama_3_3_70b.name: llama_3_3_70b,
666
655
667
656
### Mistral ###
668
'mixtral-8x7b': mixtral_8x7b,
669
'mistral-nemo': mistral_nemo,
670
'mistral-large': mistral_large,
657
mixtral_8x7b.name: mixtral_8x7b,
658
mistral_nemo.name: mistral_nemo,
659
mistral_large.name: mistral_large,
671
660
672
661
### NousResearch ###
673
'hermes-2-dpo': hermes_2_dpo,
674
'hermes-2-pro': hermes_2_pro,
675
'hermes-3': hermes_3,
662
hermes_2_dpo.name: hermes_2_dpo,
663
hermes_2_pro.name: hermes_2_pro,
664
hermes_3.name: hermes_3,
676
665
677
666
### Microsoft ###
678
'phi-2': phi_2,
679
'phi-3.5-mini': phi_3_5_mini,
667
phi_2.name: phi_2,
668
phi_3_5_mini.name: phi_3_5_mini,
680
669
681
670
### Google ###
682
671
# gemini
683
'gemini': gemini,
684
'gemini-pro': gemini_pro,
685
'gemini-flash': gemini_flash,
672
gemini.name: gemini,
673
gemini_pro.name: gemini_pro,
674
gemini_flash.name: gemini_flash,
686
675
687
676
# gemma
688
'gemma-2b': gemma_2b,
677
gemma_2b.name: gemma_2b,
689
678
690
679
### Anthropic ###
691
680
# claude 3
692
'claude-3-opus': claude_3_opus,
693
'claude-3-sonnet': claude_3_sonnet,
694
'claude-3-haiku': claude_3_haiku,
681
claude_3_opus.name: claude_3_opus,
682
claude_3_sonnet.name: claude_3_sonnet,
683
claude_3_haiku.name: claude_3_haiku,
695
684
696
685
# claude 3.5
697
'claude-3.5-sonnet': claude_3_5_sonnet,
686
claude_3_5_sonnet.name: claude_3_5_sonnet,
698
687
699
688
### Reka AI ###
700
'reka-core': reka_core,
689
reka_core.name: reka_core,
701
690
702
691
### Blackbox AI ###
703
'blackboxai': blackboxai,
704
'blackboxai-pro': blackboxai_pro,
692
blackboxai.name: blackboxai,
693
blackboxai_pro.name: blackboxai_pro,
705
694
706
695
### CohereForAI ###
707
'command-r+': command_r_plus,
708
'command-r': command_r,
696
command_r_plus.name: command_r_plus,
697
command_r.name: command_r,
709
698
710
699
### GigaChat ###
711
'gigachat': gigachat,
700
gigachat.name: gigachat,
712
701
713
702
### Qwen ###
714
703
# qwen 1_5
715
'qwen-1.5-7b': qwen_1_5_7b,
704
qwen_1_5_7b.name: qwen_1_5_7b,
716
705
717
706
# qwen 2
718
'qwen-2-72b': qwen_2_72b,
707
qwen_2_72b.name: qwen_2_72b,
719
708
720
709
# qwen 2.5
721
'qwen-2.5-72b': qwen_2_5_72b,
722
'qwen-2.5-coder-32b': qwen_2_5_coder_32b,
723
'qwq-32b': qwq_32b,
710
qwen_2_5_72b.name: qwen_2_5_72b,
711
qwen_2_5_coder_32b.name: qwen_2_5_coder_32b,
712
qwq_32b.name: qwq_32b,
724
713
725
714
### Inflection ###
726
'pi': pi,
715
pi.name: pi,
727
716
728
717
### WizardLM ###
729
'wizardlm-2-8x22b': wizardlm_2_8x22b,
718
wizardlm_2_8x22b.name: wizardlm_2_8x22b,
730
719
731
720
### OpenChat ###
732
'openchat-3.5': openchat_3_5,
721
openchat_3_5.name: openchat_3_5,
733
722
734
723
### x.ai ###
735
'grok-beta': grok_beta,
724
grok_beta.name: grok_beta,
736
725
737
726
### Perplexity AI ###
738
'sonar-online': sonar_online,
739
'sonar-chat': sonar_chat,
727
sonar_online.name: sonar_online,
728
sonar_chat.name: sonar_chat,
740
729
741
730
### DeepSeek ###
742
'deepseek-coder': deepseek_coder,
731
deepseek_coder.name: deepseek_coder,
743
732
744
733
### TheBloke ###
745
'german-7b': german_7b,
734
german_7b.name: german_7b,
746
735
747
736
### Nvidia ###
748
'nemotron-70b': nemotron_70b,
737
nemotron_70b.name: nemotron_70b,
749
738
750
739
### Teknium ###
751
'openhermes-2.5': openhermes_2_5,
740
openhermes_2_5.name: openhermes_2_5,
752
741
753
742
### Liquid ###
754
'lfm-40b': lfm_40b,
743
lfm_40b.name: lfm_40b,
755
744
756
745
### HuggingFaceH4 ###
757
'zephyr-7b': zephyr_7b,
746
zephyr_7b.name: zephyr_7b,
758
747
759
748
### Inferless ###
760
'neural-7b': neural_7b,
761
762
### PollinationsAI ###
763
'p1': p1,
749
neural_7b.name: neural_7b,
764
750
765
751
### Uncensored AI ###
766
'evil': evil,
752
evil.name: evil,
767
753
768
754
### Other ###
769
'midijourney': midijourney,
770
'turbo': turbo,
771
'unity': unity,
772
'rtist': rtist,
755
midijourney.name: midijourney,
756
turbo.name: turbo,
757
unity.name: unity,
758
rtist.name: rtist,
773
759
774
760
#############
775
761
### Image ###
776
762
#############
777
763
778
764
### Stability AI ###
779
'sdxl': sdxl,
780
'sd-3': sd_3,
765
sdxl.name: sdxl,
766
sd_3.name: sd_3,
781
767
782
768
### Playground ###
783
'playground-v2.5': playground_v2_5,
769
playground_v2_5.name: playground_v2_5,
784
770
785
771
### Flux AI ###
786
'flux': flux,
787
'flux-pro': flux_pro,
788
'flux-dev': flux_dev,
789
'flux-realism': flux_realism,
790
'flux-cablyai': flux_cablyai,
791
'flux-anime': flux_anime,
792
'flux-3d': flux_3d,
793
'flux-disney': flux_disney,
794
'flux-pixel': flux_pixel,
795
'flux-4o': flux_4o,
772
flux.name: flux,
773
flux_pro.name: flux_pro,
774
flux_dev.name: flux_dev,
775
flux_realism.name: flux_realism,
776
flux_cablyai.name: flux_cablyai,
777
flux_anime.name: flux_anime,
778
flux_3d.name: flux_3d,
779
flux_disney.name: flux_disney,
780
flux_pixel.name: flux_pixel,
781
flux_4o.name: flux_4o,
796
782
797
783
### OpenAI ###
798
'dall-e-3': dall_e_3,
784
dall_e_3.name: dall_e_3,
799
785
800
786
### Midjourney ###
801
'midjourney': midjourney,
787
midjourney.name: midjourney,
802
788
803
789
### Other ###
804
'any-dark': any_dark,
790
any_dark.name: any_dark,
805
791
}
806
792
807
# Create a list of all working models
808
__models__ = {model.name: (model, providers) for model, providers in [
809
(model, [provider for provider in providers if provider.working])
793
# Create a list of all models and his providers
794
__models__ = {
795
model.name: (model, providers)
810
796
for model, providers in [
811
797
(model, model.best_provider.providers
812
798
if isinstance(model.best_provider, IterListProvider)
@@ -814,7 +800,5 @@ __models__ = {model.name: (model, providers) for model, providers in [
814
800
if model.best_provider is not None
815
801
else [])
816
802
for model in ModelUtils.convert.values()]
817
] if providers}
818
# Update the ModelUtils.convert with the working models
819
ModelUtils.convert = {model.name: model for model, _ in __models__.values()}
820
_all_models = list(ModelUtils.convert.keys())
803
}
804
_all_models = list(__models__.keys())