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

XFEstudio/gpt4free

Add streaming and system messages support in Airforce

315a2f25
Heiner Lohaus <hlohaus@users.noreply.github.com>
提交于

代码差异

7 个文件 +140 -139
Modified etc/unittest/models.py +6 -1
@@ -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}")
Modified g4f/Provider/Airforce.py +14 -13
@@ -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()
Modified g4f/Provider/Copilot.py +30 -21
@@ -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,
Modified g4f/Provider/openai/har_file.py +2 -0
@@ -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:
Modified g4f/gui/client/static/css/style.css +1 -1
@@ -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 {
Modified g4f/gui/server/api.py +3 -3
@@ -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(
Modified g4f/models.py +84 -100
@@ -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())