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

XFEstudio/gpt4free

Check request limit in demo only in API Stop recognition in UI on enter request Fix Ratelimt for Ping in GUI Use OpenaiTemplate for OIVSCode Support Reasoning in Blackbox Add error reporting in UI Support Custom Provider in Demo

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

代码差异

9 个文件 +121 -139
Modified g4f/Provider/Blackbox.py +10 -3
@@ -14,7 +14,7 @@ from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
14 14 from ..image import ImageResponse, to_data_uri
15 15 from ..cookies import get_cookies_dir
16 16 from .helper import format_prompt
17 from ..providers.response import FinishReason, JsonConversation
17 from ..providers.response import FinishReason, JsonConversation, Reasoning
18 18
19 19 class Conversation(JsonConversation):
20 20 validated_value: str = None
@@ -310,7 +310,14 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin):
310 310 prompt = messages[-1]["content"]
311 311 yield ImageResponse(images=[image_url], alt=prompt)
312 312 else:
313 if "Generated by BLACKBOX.AI" in text_to_yield:
313 if "<think>" in text_to_yield and "</think>" in chunk_text :
314 chunk_text = text_to_yield.split('<think>', 1)
315 yield chunk_text[0]
316 chunk_text = text_to_yield.split('</think>', 1)
317 yield Reasoning(chunk_text[0])
318 yield chunk_text[1]
319 full_response = text_to_yield
320 elif "Generated by BLACKBOX.AI" in text_to_yield:
314 321 conversation.validated_value = await cls.fetch_validated(force_refresh=True)
315 322 if conversation.validated_value:
316 323 data["validated"] = conversation.validated_value
@@ -337,7 +344,7 @@ class Blackbox(AsyncGeneratorProvider, ProviderModelMixin):
337 344 reason = "length"
338 345 else:
339 346 reason = "stop"
340
347
341 348 if return_conversation:
342 349 conversation.message_history.append({"role": "assistant", "content": full_response})
343 350 yield conversation
Modified g4f/Provider/OIVSCode.py +6 -92
@@ -1,101 +1,15 @@
1 1 from __future__ import annotations
2 2
3 import json
4 from aiohttp import ClientSession
3 from .needs_auth.OpenaiTemplate import OpenaiTemplate
5 4
6 from ..image import to_data_uri
7 from ..typing import AsyncResult, Messages, ImagesType
8 from ..requests.raise_for_status import raise_for_status
9 from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
10 from .helper import format_prompt
11 from ..providers.response import FinishReason
12
13
14 class OIVSCode(AsyncGeneratorProvider, ProviderModelMixin):
5 class OIVSCode(OpenaiTemplate):
15 6 label = "OI VSCode Server"
16 7 url = "https://oi-vscode-server.onrender.com"
17 api_endpoint = "https://oi-vscode-server.onrender.com/v1/chat/completions"
18
8 api_base = "https://oi-vscode-server.onrender.com/v1"
19 9 working = True
20 supports_stream = True
21 supports_system_message = True
22 supports_message_history = True
10 needs_auth = False
23 11
24 12 default_model = "gpt-4o-mini-2024-07-18"
25 13 default_vision_model = default_model
26 vision_models = [default_model, "gpt-4o-mini"]
27 models = vision_models
28
29 model_aliases = {"gpt-4o-mini": "gpt-4o-mini-2024-07-18"}
30
31 @classmethod
32 async def create_async_generator(
33 cls,
34 model: str,
35 messages: Messages,
36 stream: bool = False,
37 images: ImagesType = None,
38 proxy: str = None,
39 **kwargs
40 ) -> AsyncResult:
41 headers = {
42 "accept": "*/*",
43 "accept-language": "en-US,en;q=0.9",
44 "content-type": "application/json",
45 "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
46 }
47
48 async with ClientSession(headers=headers) as session:
49
50 if images is not None:
51 messages[-1]['content'] = [
52 {
53 "type": "text",
54 "text": messages[-1]['content']
55 },
56 *[
57 {
58 "type": "image_url",
59 "image_url": {
60 "url": to_data_uri(image)
61 }
62 }
63 for image, _ in images
64 ]
65 ]
66
67 data = {
68 "model": model,
69 "stream": stream,
70 "messages": messages
71 }
72
73 async with session.post(cls.api_endpoint, json=data, proxy=proxy) as response:
74 await raise_for_status(response)
75
76 full_response = ""
77
78 if stream:
79 async for line in response.content:
80 if line:
81 line = line.decode()
82 if line.startswith("data: "):
83 if line.strip() == "data: [DONE]":
84 break
85 try:
86 data = json.loads(line[6:])
87 if content := data["choices"][0]["delta"].get("content"):
88 yield content
89 full_response += content
90 except:
91 continue
92
93 reason = "length" if len(full_response) > 0 else "stop"
94 yield FinishReason(reason)
95 else:
96 response_data = await response.json()
97 full_response = response_data["choices"][0]["message"]["content"]
98 yield full_response
99
100 reason = "length" if len(full_response) > 0 else "stop"
101 yield FinishReason(reason)
14 vision_models = [default_model, "gpt-4o-mini"]
15 model_aliases = {"gpt-4o-mini": "gpt-4o-mini-2024-07-18"}
Modified g4f/Provider/needs_auth/Custom.py +1 -0
@@ -5,5 +5,6 @@ from .OpenaiTemplate import OpenaiTemplate
5 5 class Custom(OpenaiTemplate):
6 6 label = "Custom Provider"
7 7 working = True
8 needs_auth = False
8 9 api_base = "http://localhost:8080/v1"
9 10 sort_models = False
Modified g4f/Provider/needs_auth/OpenaiTemplate.py +35 -19
@@ -8,7 +8,7 @@ from ..helper import filter_none
8 8 from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin
9 9 from ...typing import Union, Optional, AsyncResult, Messages, ImagesType
10 10 from ...requests import StreamSession, raise_for_status
11 from ...providers.response import FinishReason, ToolCalls, Usage, Reasoning
11 from ...providers.response import FinishReason, ToolCalls, Usage, Reasoning, ImageResponse
12 12 from ...errors import MissingAuthError, ResponseError
13 13 from ...image import to_data_uri
14 14 from ... import debug
@@ -59,6 +59,7 @@ class OpenaiTemplate(AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin
59 59 top_p: float = None,
60 60 stop: Union[str, list[str]] = None,
61 61 stream: bool = False,
62 prompt: str = None,
62 63 headers: dict = None,
63 64 impersonate: str = None,
64 65 tools: Optional[list] = None,
@@ -67,32 +68,47 @@ class OpenaiTemplate(AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin
67 68 ) -> AsyncResult:
68 69 if cls.needs_auth and api_key is None:
69 70 raise MissingAuthError('Add a "api_key"')
70 if api_base is None:
71 api_base = cls.api_base
72 if images is not None and messages:
73 if not model and hasattr(cls, "default_vision_model"):
74 model = cls.default_vision_model
75 last_message = messages[-1].copy()
76 last_message["content"] = [
77 *[{
78 "type": "image_url",
79 "image_url": {"url": to_data_uri(image)}
80 } for image, _ in images],
81 {
82 "type": "text",
83 "text": messages[-1]["content"]
84 }
85 ]
86 messages[-1] = last_message
87 71 async with StreamSession(
88 72 proxy=proxy,
89 73 headers=cls.get_headers(stream, api_key, headers),
90 74 timeout=timeout,
91 75 impersonate=impersonate,
92 76 ) as session:
77 model = cls.get_model(model, api_key=api_key, api_base=api_base)
78 if api_base is None:
79 api_base = cls.api_base
80
81 # Proxy for image generation feature
82 if model in cls.image_models:
83 data = {
84 "prompt": messages[-1]["content"] if prompt is None else prompt,
85 "model": model,
86 }
87 async with session.post(f"{api_base.rstrip('/')}/images/generations", json=data) as response:
88 data = await response.json()
89 cls.raise_error(data)
90 await raise_for_status(response)
91 yield ImageResponse([image["url"] for image in data["data"]], prompt)
92 return
93
94 if images is not None and messages:
95 if not model and hasattr(cls, "default_vision_model"):
96 model = cls.default_vision_model
97 last_message = messages[-1].copy()
98 last_message["content"] = [
99 *[{
100 "type": "image_url",
101 "image_url": {"url": to_data_uri(image)}
102 } for image, _ in images],
103 {
104 "type": "text",
105 "text": messages[-1]["content"]
106 }
107 ]
108 messages[-1] = last_message
93 109 data = filter_none(
94 110 messages=messages,
95 model=cls.get_model(model, api_key=api_key, api_base=api_base),
111 model=model,
96 112 temperature=temperature,
97 113 max_tokens=max_tokens,
98 114 top_p=top_p,
Modified g4f/gui/client/demo.html +1 -0
@@ -216,6 +216,7 @@
216 216 localStorage.setItem("HuggingFace-api_key", accessToken);
217 217 localStorage.setItem("HuggingFace-user", JSON.stringify(user));
218 218 localStorage.setItem("user", user.name);
219 localStorage.setItem("report_error", "true")
219 220 location.href = "/chat/";
220 221 }
221 222 input.addEventListener("input", () => check_access_token());
Modified g4f/gui/client/index.html +5 -3
@@ -151,6 +151,11 @@
151 151 <input type="checkbox" id="track_usage"/>
152 152 <label for="track_usage" class="toogle" title=""></label>
153 153 </div>
154 <div class="field">
155 <span class="label">Report errors</span>
156 <input type="checkbox" id="report_error"/>
157 <label for="report_error" class="toogle" title=""></label>
158 </div>
154 159 <div class="field box">
155 160 <label for="systemPrompt" class="label" title="">System prompt</label>
156 161 <textarea id="systemPrompt" placeholder="You are a helpful assistant."></textarea>
@@ -162,9 +167,6 @@
162 167 <div class="field box">
163 168 <label for="recognition-language" class="label" title="">Speech recognition language</label>
164 169 <input type="text" id="recognition-language" value="" placeholder="navigator.language"/>
165 <script>
166 document.getElementById('recognition-language').placeholder = navigator.language;
167 </script>
168 170 </div>
169 171 <div class="field mem0 hidden">
170 172 <span class="label">Enable Memory with Mem0</span>
Modified g4f/gui/client/static/js/chat.v1.js +50 -20
@@ -732,6 +732,7 @@ async function add_message_chunk(message, message_id, provider, scroll, finish_m
732 732 let p = document.createElement("p");
733 733 p.innerText = message.error;
734 734 log_storage.appendChild(p);
735 await api("log", {...message, provider: provider_storage[message_id]});
735 736 } else if (message.type == "preview") {
736 737 if (content_map.inner.clientHeight > 200)
737 738 content_map.inner.style.height = content_map.inner.clientHeight + "px";
@@ -794,6 +795,9 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
794 795 provider = providerSelect.options[providerSelect.selectedIndex].value;
795 796 }
796 797 let conversation = await get_conversation(window.conversation_id);
798 if (!conversation) {
799 return;
800 }
797 801 messages = prepare_messages(conversation.items, message_index, action=="continue");
798 802 message_storage[message_id] = "";
799 803 stop_generating.classList.remove("stop_generating-hidden");
@@ -1869,12 +1873,15 @@ async function on_api() {
1869 1873 if (prompt_lock) return;
1870 1874 prompt_lock = true;
1871 1875 setTimeout(()=>prompt_lock=false, 3000);
1876 stop_recognition();
1872 1877 await handle_ask();
1873 1878 });
1874 1879 sendButton.querySelector(".fa-square-plus").addEventListener(`click`, async () => {
1880 stop_recognition();
1875 1881 await handle_ask(false);
1876 1882 });
1877 1883 messageInput.focus();
1884
1878 1885 let provider_options = [];
1879 1886 models = await api("models");
1880 1887 models.forEach((model) => {
@@ -1891,7 +1898,8 @@ async function on_api() {
1891 1898 location.href = "/";
1892 1899 return;
1893 1900 }
1894 providerSelect.innerHTML = '<option value="" selected>Demo Mode</option>'
1901 providerSelect.innerHTML = '<option value="">Demo Mode</option><option value="Custom">Custom Provider</option>';
1902 providerSelect.selectedIndex = 0;
1895 1903 document.getElementById("pin").disabled = true;
1896 1904 document.getElementById("refine")?.parentElement.classList.add("hidden")
1897 1905 const track_usage = document.getElementById("track_usage");
@@ -2020,6 +2028,7 @@ async function on_api() {
2020 2028
2021 2029 const method = switchInput.checked ? "add" : "remove";
2022 2030 searchButton.classList[method]("active");
2031 document.getElementById('recognition-language').placeholder = get_navigator_language();
2023 2032 }
2024 2033
2025 2034 async function load_version() {
@@ -2288,6 +2297,12 @@ async function api(ressource, args=null, files=null, message_id=null, scroll=tru
2288 2297 return;
2289 2298 }
2290 2299 } else if (args) {
2300 if (ressource == "log") {
2301 if (appStorage.getItem("report_error") != "true") {
2302 return;
2303 }
2304 url = `https://roxky-g4f-demo.hf.space${url}"`;
2305 }
2291 2306 headers['content-type'] = 'application/json';
2292 2307 response = await fetch(url, {
2293 2308 method: 'POST',
@@ -2456,25 +2471,33 @@ function import_memory() {
2456 2471 }
2457 2472 }
2458 2473 conversations.sort((a, b) => (a.updated||0)-(b.updated||0));
2459 conversations.forEach(async (conversation, i)=>{
2460 setTimeout(async ()=>{
2461 let body = JSON.stringify(conversation);
2462 response = await fetch(`/backend-api/v2/memory/${user_id}`, {
2463 method: 'POST',
2464 body: body,
2465 headers: {
2466 "content-type": "application/json",
2467 "x_api_key": appStorage.getItem("mem0-api_key")
2468 }
2469 });
2470 const result = await response.json();
2471 count += result.count;
2472 inputCount.innerText = `${count} Messages were imported`;
2473 }, (i+1)*1000);
2474 });
2474 async function add_conversation_to_memory(i) {
2475 if (i > conversations.length - 1) {
2476 return;
2477 }
2478 let body = JSON.stringify(conversations[i]);
2479 response = await fetch(`/backend-api/v2/memory/${user_id}`, {
2480 method: 'POST',
2481 body: body,
2482 headers: {
2483 "content-type": "application/json",
2484 "x_api_key": appStorage.getItem("mem0-api_key")
2485 }
2486 });
2487 const result = await response.json();
2488 count += result.count;
2489 inputCount.innerText = `${count} Messages were imported`;
2490 add_conversation_to_memory(i + 1);
2491 }
2492 add_conversation_to_memory(0)
2493 }
2494
2495 function get_navigator_language() {
2496 return navigator.languages.filter((v)=>v.includes("-"))[0] || navigator.language;
2475 2497 }
2476 2498
2477 2499 const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
2500 let stop_recognition = ()=>{};
2478 2501 if (SpeechRecognition) {
2479 2502 const mircoIcon = microLabel.querySelector("i");
2480 2503 mircoIcon.classList.add("fa-microphone");
@@ -2524,15 +2547,22 @@ if (SpeechRecognition) {
2524 2547 }
2525 2548 };
2526 2549
2527 microLabel.addEventListener("click", (e) => {
2550 stop_recognition = ()=>{
2528 2551 if (microLabel.classList.contains("recognition")) {
2529 2552 microLabel.classList.remove("recognition");
2530 2553 recognition.stop();
2531 2554 messageInput.value = `${startValue ? startValue + "\n" : ""}${buffer}`;
2532 } else {
2555 count_input();
2556 return true;
2557 }
2558 return false;
2559 }
2560
2561 microLabel.addEventListener("click", (e) => {
2562 if (!stop_recognition()) {
2533 2563 microLabel.classList.add("recognition");
2534 2564 const lang = document.getElementById("recognition-language")?.value;
2535 recognition.lang = lang || navigator.language;
2565 recognition.lang = lang || get_navigator_language();
2536 2566 recognition.start();
2537 2567 }
2538 2568 });
Modified g4f/gui/server/backend_api.py +12 -1
@@ -68,6 +68,7 @@ class Backend_Api(Api):
68 68 app=app,
69 69 default_limits=["200 per day", "50 per hour"],
70 70 storage_uri="memory://",
71 auto_check=False
71 72 )
72 73
73 74 if has_flask_limiter and app.demo:
@@ -133,7 +134,7 @@ class Backend_Api(Api):
133 134 else:
134 135 json_data = request.json
135 136
136 if app.demo:
137 if app.demo and json_data.get("provider") != "Custom":
137 138 model = json_data.get("model")
138 139 if model != "default" and model in models.demo_models:
139 140 json_data["provider"] = random.choice(models.demo_models[model][1])
@@ -156,6 +157,7 @@ class Backend_Api(Api):
156 157 @app.route('/backend-api/v2/conversation', methods=['POST'])
157 158 @limiter.limit("4 per minute") # 1 request in 15 seconds
158 159 def _handle_conversation():
160 limiter.check()
159 161 return handle_conversation()
160 162 else:
161 163 @app.route('/backend-api/v2/conversation', methods=['POST'])
@@ -171,6 +173,15 @@ class Backend_Api(Api):
171 173 f.write(f"{json.dumps(request.json)}\n")
172 174 return {}
173 175
176 @app.route('/backend-api/v2/log', methods=['POST'])
177 def add_log():
178 cache_dir = Path(get_cookies_dir()) / ".logging"
179 cache_file = cache_dir / f"{datetime.date.today()}.jsonl"
180 cache_dir.mkdir(parents=True, exist_ok=True)
181 with cache_file.open("a" if cache_file.exists() else "w") as f:
182 f.write(f"{json.dumps(request.json)}\n")
183 return {}
184
174 185 @app.route('/backend-api/v2/memory/<user_id>', methods=['POST'])
175 186 def add_memory(user_id: str):
176 187 api_key = request.headers.get("x_api_key")
Modified g4f/providers/response.py +1 -1
@@ -126,7 +126,7 @@ class Reasoning(ResponseType):
126 126 self.status = status
127 127
128 128 def __str__(self) -> str:
129 return "" if self.token is None else self.token
129 return f"{self.status}\n" if self.token is None else self.token
130 130
131 131 class Sources(ResponseType):
132 132 def __init__(self, sources: list[dict[str, str]]) -> None: