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

XFEstudio/gpt4free

Refactor PA backend conversation endpoint for improved streaming and error handling; adjust session key generation to use 1024 bits for user input compatibility

05a9ac45
hlohaus <hlohaus@users.noreply.github.com>
提交于

代码差异

3 个文件 +81 -91
Modified g4f/api/__init__.py +1 -90
@@ -224,7 +224,7 @@ class Api:
224 224 return (
225 225 path.startswith("/v1")
226 226 or path.startswith("/api/")
227 or path.startswith("/pa/")
227 or (path.startswith("/pa/") and not demo)
228 228 or (demo and path == "/backend-api/v2/upload_cookies")
229 229 )
230 230
@@ -766,95 +766,6 @@ class Api:
766 766 logger.exception(e)
767 767 return ErrorResponse.from_exception(e, config, HTTP_500_INTERNAL_SERVER_ERROR)
768 768
769 @self.app.post("/pa/backend-api/v2/conversation", responses={
770 HTTP_200_OK: {},
771 HTTP_404_NOT_FOUND: {"model": ErrorResponseModel},
772 HTTP_422_UNPROCESSABLE_ENTITY: {"model": ErrorResponseModel},
773 HTTP_500_INTERNAL_SERVER_ERROR: {"model": ErrorResponseModel},
774 })
775 async def pa_backend_conversation(request: Request):
776 """GUI-compatible streaming conversation endpoint for PA providers.
777
778 Accepts the same JSON body as ``/backend-api/v2/conversation`` and
779 streams Server-Sent Events in the same format used by the gpt4free
780 web interface (``{"type": "content", "content": "..."}`` etc.).
781
782 The ``provider`` field should contain the opaque PA provider ID
783 returned by ``GET /pa/providers``. When omitted the first available
784 PA provider is used.
785 """
786 from g4f.mcp.pa_provider import get_pa_registry
787
788 try:
789 body = await request.json()
790 except Exception:
791 return ErrorResponse.from_message(
792 "Invalid JSON body", HTTP_422_UNPROCESSABLE_ENTITY
793 )
794
795 registry = get_pa_registry()
796 pid = body.get("provider")
797 if pid:
798 provider_cls = registry.get_provider_class(pid)
799 if provider_cls is None:
800 return ErrorResponse.from_message(
801 f"PA provider '{pid}' not found", HTTP_404_NOT_FOUND
802 )
803 else:
804 listing = registry.list_providers()
805 if not listing:
806 return ErrorResponse.from_message(
807 "No PA providers found in workspace", HTTP_404_NOT_FOUND
808 )
809 provider_cls = registry.get_provider_class(listing[0]["id"])
810
811 provider_label = getattr(provider_cls, "label", provider_cls.__name__)
812 messages = body.get("messages") or []
813 model = body.get("model") or getattr(provider_cls, "default_model", "") or ""
814
815 async def gen_backend_stream():
816 yield (
817 "data: "
818 + json.dumps({"type": "provider", "provider": provider_label, "model": model})
819 + "\n\n"
820 )
821 try:
822 response = self.client.chat.completions.create(
823 messages=messages,
824 model=model,
825 provider=provider_cls,
826 stream=True,
827 )
828 async for chunk in response:
829 if isinstance(chunk, BaseConversation):
830 continue
831 text = ""
832 if hasattr(chunk, "choices") and chunk.choices:
833 delta = chunk.choices[0].delta
834 text = getattr(delta, "content", "") or ""
835 if text:
836 yield (
837 "data: "
838 + json.dumps({"type": "content", "content": text})
839 + "\n\n"
840 )
841 except GeneratorExit:
842 pass
843 except Exception as e:
844 logger.exception(e)
845 yield (
846 "data: "
847 + json.dumps({"type": "error", "error": f"{type(e).__name__}: {e}"})
848 + "\n\n"
849 )
850 yield (
851 "data: "
852 + json.dumps({"type": "finish", "finish": "stop"})
853 + "\n\n"
854 )
855
856 return StreamingResponse(gen_backend_stream(), media_type="text/event-stream")
857
858 769 # ------------------------------------------------------------------ #
859 770 # PA workspace static file serving (HTML/CSS/JS/images for browser) #
860 771 # ------------------------------------------------------------------ #
Modified g4f/gui/server/backend_api.py +78 -0
@@ -37,6 +37,7 @@ try:
37 37 except ImportError:
38 38 has_crypto = False
39 39
40 from ...client import Client
40 41 from ...client.service import convert_to_provider
41 42 from ...providers.asyncio import to_sync_generator
42 43 from ...providers.response import FinishReason, AudioResponse, MediaResponse, Reasoning, HiddenResponse, JsonResponse
@@ -85,6 +86,7 @@ class Backend_Api(Api):
85 86 """
86 87 self.app: Flask = app
87 88 self.chat_cache = {}
89 self.client = Client()
88 90
89 91 if has_crypto:
90 92 private_key_obj = get_session_key()
@@ -130,6 +132,82 @@ class Backend_Api(Api):
130 132 "user": request.headers.get("x-user", "error")
131 133 })
132 134
135 @app.route('/pa/backend-api/v2/conversation', methods=['POST'])
136 async def pa_backend_conversation():
137 """GUI-compatible streaming conversation endpoint for PA providers.
138
139 Accepts the same JSON body as ``/backend-api/v2/conversation`` and
140 streams Server-Sent Events in the same format used by the gpt4free
141 web interface (``{"type": "content", "content": "..."}`` etc.).
142
143 The ``provider`` field should contain the opaque PA provider ID
144 returned by ``GET /pa/providers``. When omitted the first available
145 PA provider is used.
146 """
147 from g4f.mcp.pa_provider import get_pa_registry
148
149 if app.demo and has_crypto:
150 secret = request.headers.get("x-secret", request.headers.get("x_secret"))
151 if not secret or not validate_secret(secret):
152 return jsonify({"error": {"message": "Invalid or missing secret"}}), 403
153
154 try:
155 body = {**request.json}
156 except Exception:
157 return jsonify({"error": {"message": "Invalid JSON body"}}), 422
158
159 registry = get_pa_registry()
160 pid = body.get("provider")
161 if pid:
162 provider_cls = registry.get_provider_class(pid)
163 if provider_cls is None:
164 return jsonify({"error": {"message": f"PA provider '{pid}' not found"}}), 404
165 else:
166 listing = registry.list_providers()
167 if not listing:
168 return jsonify({"error": {"message": "No PA providers found in workspace"}}), 404
169 provider_cls = registry.get_provider_class(listing[0]["id"])
170
171 provider_label = getattr(provider_cls, "label", provider_cls.__name__)
172 messages = body.get("messages") or []
173 model = body.get("model") or getattr(provider_cls, "default_model", "") or ""
174
175 def gen_backend_stream():
176 yield (
177 "data: "
178 + json.dumps({"type": "provider", "provider": {"name": pid, "label": provider_label, "model": model}})
179 + "\n\n"
180 )
181 try:
182 response = self.client.chat.completions.create(
183 messages=messages,
184 model=model,
185 provider=provider_cls,
186 stream=True,
187 )
188 for chunk in response:
189 if chunk.choices and chunk.choices[0].delta:
190 yield f"data: {json.dumps({'type': 'content', 'content': chunk.choices[0].delta.content})}\n\n"
191 except GeneratorExit:
192 pass
193 except Exception as e:
194 logger.exception(e)
195 yield (
196 "data: "
197 + json.dumps({"type": "error", "error": f"{type(e).__name__}: {e}"})
198 + "\n\n"
199 )
200 yield (
201 "data: "
202 + json.dumps({"type": "finish", "finish": "stop"})
203 + "\n\n"
204 )
205
206 return self.app.response_class(
207 safe_iter_generator(gen_backend_stream()),
208 mimetype='text/event-stream'
209 )
210
133 211 @app.route('/backend-api/v2/models', methods=['GET'])
134 212 @lru_cache(maxsize=1)
135 213 def jsonify_models():
Modified g4f/gui/server/crypto.py +2 -1
@@ -39,7 +39,8 @@ def create_or_read_keys() -> tuple[RSAPrivateKey, RSAPublicKey]:
39 39 return private_key, public_key
40 40
41 41 # Generate keys
42 private_key_obj = rsa.generate_private_key(public_exponent=65537, key_size=4096)
42 # Note: Using 1024 bits for the session key so the user can put it his secret (captcha)
43 private_key_obj = rsa.generate_private_key(public_exponent=65537, key_size=1024)
43 44 public_key_obj = private_key_obj.public_key()
44 45
45 46 # Serialize private key