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

XFEstudio/gpt4free

Fix invalid escape in requests module Add none auth with OpenAI using nodriver Fix missing 1 required positional argument: 'cls' Update count tokens in GUI Fix streaming example in requests guide Remove ChatGptEs as default model

2e531d22
Heiner Lohaus <hlohaus@users.noreply.github.com>
提交于

代码差异

16 个文件 +238 -162
Modified docs/requests.md +16 -25
@@ -73,7 +73,6 @@ For scenarios where you want to receive partial responses or stream data as it's
73 73 ```python
74 74 import requests
75 75 import json
76 from queue import Queue
77 76
78 77 def fetch_response(url, model, messages):
79 78 """
@@ -87,7 +86,7 @@ def fetch_response(url, model, messages):
87 86 Returns:
88 87 requests.Response: The streamed response object.
89 88 """
90 payload = {"model": model, "messages": messages}
89 payload = {"model": model, "messages": messages, "stream": True}
91 90 headers = {
92 91 "Content-Type": "application/json",
93 92 "Accept": "text/event-stream",
@@ -99,7 +98,7 @@ def fetch_response(url, model, messages):
99 98 )
100 99 return response
101 100
102 def process_stream(response, output_queue):
101 def process_stream(response):
103 102 """
104 103 Processes the streamed response and extracts messages.
105 104
@@ -111,37 +110,31 @@ def process_stream(response, output_queue):
111 110 if line:
112 111 line = line.decode("utf-8")
113 112 if line == "data: [DONE]":
113 print("\n\nConversation completed.")
114 114 break
115 115 if line.startswith("data: "):
116 116 try:
117 117 data = json.loads(line[6:])
118 message = data.get("message", "")
118 message = data.get("choices", [{}])[0].get("delta", {}).get("content")
119 119 if message:
120 output_queue.put(message)
121 except json.JSONDecodeError:
120 print(message, end="", flush=True)
121 except json.JSONDecodeError as e:
122 print(f"Error decoding JSON: {e}")
122 123 continue
123 124
124 125 # Define the API endpoint
125 chat_url = "http://localhost/v1/chat/completions"
126 chat_url = "http://localhost:8080/v1/chat/completions"
126 127
127 128 # Define the payload
128 model = "gpt-4o"
129 messages = [{"role": "system", "content": "Hello, how are you?"}]
130
131 # Initialize the queue to store output messages
132 output_queue = Queue()
129 model = ""
130 messages = [{"role": "user", "content": "Hello, how are you?"}]
133 131
134 132 try:
135 133 # Fetch the streamed response
136 134 response = fetch_response(chat_url, model, messages)
137 135
138 136 # Process the streamed response
139 process_stream(response, output_queue)
140
141 # Retrieve messages from the queue
142 while not output_queue.empty():
143 msg = output_queue.get()
144 print(msg)
137 process_stream(response)
145 138
146 139 except Exception as e:
147 140 print(f"An error occurred: {e}")
@@ -150,23 +143,21 @@ except Exception as e:
150 143 **Explanation:**
151 144 - **`fetch_response` Function:**
152 145 - Sends a POST request to the streaming chat completions endpoint with the specified model and messages.
153 - Sets the `Accept` header to `text/event-stream` to enable streaming.
146 - Sets `stream` parameter to `true` to enable streaming.
154 147 - Raises an exception if the request fails.
155 148
156 149 - **`process_stream` Function:**
157 150 - Iterates over each line in the streamed response.
158 151 - Decodes the line and checks for the termination signal `"data: [DONE]"`.
159 152 - Parses lines that start with `"data: "` to extract the message content.
160 - Enqueues the extracted messages into `output_queue` for further processing.
161 153
162 154 - **Main Execution:**
163 155 - Defines the API endpoint, model, and messages.
164 - Initializes a `Queue` to store incoming messages.
165 156 - Fetches and processes the streamed response.
166 - Retrieves and prints messages from the queue.
157 - Retrieves and prints messages.
167 158
168 159 **Usage Tips:**
169 - Ensure your local server supports streaming and the `Accept` header appropriately.
160 - Ensure your local server supports streaming.
170 161 - Adjust the `chat_url` if your local server runs on a different port or path.
171 162 - Use threading or asynchronous programming for handling streams in real-time applications.
172 163
@@ -286,7 +277,7 @@ async def fetch_response_async(url, model, messages, output_queue):
286 277 messages (list): A list of message dictionaries.
287 278 output_queue (Queue): A queue to store the extracted messages.
288 279 """
289 payload = {"model": model, "messages": messages}
280 payload = {"model": model, "messages": messages, "stream": True}
290 281 headers = {
291 282 "Content-Type": "application/json",
292 283 "Accept": "text/event-stream",
@@ -305,7 +296,7 @@ async def fetch_response_async(url, model, messages, output_queue):
305 296 if decoded_line.startswith("data: "):
306 297 try:
307 298 data = json.loads(decoded_line[6:])
308 message = data.get("message", "")
299 message = data.get("choices", [{}])[0].get("delta", {}).get("content")
309 300 if message:
310 301 output_queue.put(message)
311 302 except json.JSONDecodeError:
Modified g4f/Provider/DDG.py +1 -1
@@ -73,7 +73,7 @@ class DDG(AsyncGeneratorProvider, ProviderModelMixin):
73 73 "Content-Type": "application/json",
74 74 },
75 75 cookies: dict = None,
76 max_retries: int = 3,
76 max_retries: int = 0,
77 77 **kwargs
78 78 ) -> AsyncResult:
79 79 if cookies is None and conversation is not None:
Modified g4f/Provider/needs_auth/OpenaiChat.py +22 -20
@@ -106,9 +106,8 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
106 106
107 107 @classmethod
108 108 async def on_auth_async(cls, **kwargs) -> AsyncIterator:
109 if cls.needs_auth:
110 async for chunk in cls.login():
111 yield chunk
109 async for chunk in cls.login():
110 yield chunk
112 111 yield AuthResult(
113 112 api_key=cls._api_key,
114 113 cookies=cls._cookies or RequestConfig.cookies or {},
@@ -335,11 +334,11 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
335 334 cls._update_request_args(auth_result, session)
336 335 await raise_for_status(response)
337 336 else:
338 if cls._headers is None:
337 if cls._headers is None and getattr(auth_result, "cookies", None):
339 338 cls._create_request_args(auth_result.cookies, auth_result.headers)
340 if not cls._set_api_key(auth_result.api_key):
341 raise MissingAuthError("Access token is not valid")
342 async with session.get(cls.url, headers=auth_result.headers) as response:
339 if not cls._set_api_key(getattr(auth_result, "api_key", None)):
340 raise MissingAuthError("Access token is not valid")
341 async with session.get(cls.url, headers=cls._headers) as response:
343 342 cls._update_request_args(auth_result, session)
344 343 await raise_for_status(response)
345 344 try:
@@ -349,9 +348,11 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
349 348 debug.log(f"{e.__class__.__name__}: {e}")
350 349 model = cls.get_model(model)
351 350 if conversation is None:
352 conversation = Conversation(conversation_id, str(uuid.uuid4()))
351 conversation = Conversation(conversation_id, str(uuid.uuid4()), getattr(auth_result, "cookies", {}).get("oai-did"))
353 352 else:
354 353 conversation = copy(conversation)
354 if getattr(auth_result, "cookies", {}).get("oai-did") != conversation.user_id:
355 conversation = Conversation(None, str(uuid.uuid4()))
355 356 if cls._api_key is None:
356 357 auto_continue = False
357 358 conversation.finish_reason = None
@@ -361,11 +362,11 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
361 362 f"{cls.url}/backend-anon/sentinel/chat-requirements"
362 363 if cls._api_key is None else
363 364 f"{cls.url}/backend-api/sentinel/chat-requirements",
364 json={"p": None if not getattr(auth_result, "proof_token") else get_requirements_token(auth_result.proof_token)},
365 json={"p": None if not getattr(auth_result, "proof_token", None) else get_requirements_token(getattr(auth_result, "proof_token", None))},
365 366 headers=cls._headers
366 367 ) as response:
367 if response.status == 401:
368 cls._headers = cls._api_key = None
368 if response.status in (401, 403):
369 auth_result.reset()
369 370 else:
370 371 cls._update_request_args(auth_result, session)
371 372 await raise_for_status(response)
@@ -380,14 +381,13 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
380 381 # cls._set_api_key(auth_result.access_token)
381 382 # if auth_result.arkose_token is None:
382 383 # raise MissingAuthError("No arkose token found in .har file")
383
384 384 if "proofofwork" in chat_requirements:
385 if auth_result.proof_token is None:
385 if getattr(auth_result, "proof_token") is None:
386 386 auth_result.proof_token = get_config(auth_result.headers.get("user-agent"))
387 387 proofofwork = generate_proof_token(
388 388 **chat_requirements["proofofwork"],
389 user_agent=auth_result.headers.get("user-agent"),
390 proof_token=getattr(auth_result, "proof_token")
389 user_agent=getattr(auth_result, "headers", {}).get("user-agent"),
390 proof_token=getattr(auth_result, "proof_token", None)
391 391 )
392 392 [debug.log(text) for text in (
393 393 #f"Arkose: {'False' if not need_arkose else auth_result.arkose_token[:12]+'...'}",
@@ -434,7 +434,7 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
434 434 # headers["openai-sentinel-arkose-token"] = RequestConfig.arkose_token
435 435 if proofofwork is not None:
436 436 headers["openai-sentinel-proof-token"] = proofofwork
437 if need_turnstile and auth_result.turnstile_token is not None:
437 if need_turnstile and getattr(auth_result, "turnstile_token", None) is not None:
438 438 headers['openai-sentinel-turnstile-token'] = auth_result.turnstile_token
439 439 async with session.post(
440 440 f"{cls.url}/backend-anon/conversation"
@@ -653,7 +653,7 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
653 653 await page.evaluate("document.getElementById('prompt-textarea').innerText = 'Hello'")
654 654 await page.evaluate("document.querySelector('[data-testid=\"send-button\"]').click()")
655 655 while True:
656 if cls._api_key is not None:
656 if cls._api_key is not None or not cls.needs_auth:
657 657 break
658 658 body = await page.evaluate("JSON.stringify(window.__remixContext)")
659 659 if body:
@@ -689,8 +689,9 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
689 689
690 690 @classmethod
691 691 def _update_request_args(cls, auth_result: AuthResult, session: StreamSession):
692 for c in session.cookie_jar if hasattr(session, "cookie_jar") else session.cookies.jar:
693 auth_result.cookies[getattr(c, "key", getattr(c, "name", ""))] = c.value
692 if hasattr(auth_result, "cookies"):
693 for c in session.cookie_jar if hasattr(session, "cookie_jar") else session.cookies.jar:
694 auth_result.cookies[getattr(c, "key", getattr(c, "name", ""))] = c.value
694 695 cls._update_cookie_header()
695 696
696 697 @classmethod
@@ -717,12 +718,13 @@ class Conversation(JsonConversation):
717 718 """
718 719 Class to encapsulate response fields.
719 720 """
720 def __init__(self, conversation_id: str = None, message_id: str = None, finish_reason: str = None, parent_message_id: str = None):
721 def __init__(self, conversation_id: str = None, message_id: str = None, user_id: str = None, finish_reason: str = None, parent_message_id: str = None):
721 722 self.conversation_id = conversation_id
722 723 self.message_id = message_id
723 724 self.finish_reason = finish_reason
724 725 self.is_recipient = False
725 726 self.parent_message_id = message_id if parent_message_id is None else parent_message_id
727 self.user_id = user_id
726 728
727 729 def get_cookies(
728 730 urls: Optional[Iterator[str]] = None
Modified g4f/Provider/openai/har_file.py +1 -1
@@ -32,7 +32,7 @@ class RequestConfig:
32 32 arkose_token: str = None
33 33 headers: dict = {}
34 34 cookies: dict = {}
35 data_build: str = "prod-697873d7e78bb14df6e13af3a91fa237cc4db415"
35 data_build: str = "prod-db8e51e8414e068257091cf5003a62d3d4ee6ed0"
36 36
37 37 class arkReq:
38 38 def __init__(self, arkURL, arkBx, arkHeader, arkBody, arkCookies, userAgent):
Modified g4f/api/__init__.py +2 -2
@@ -432,7 +432,7 @@ class Api:
432 432 HTTP_404_NOT_FOUND: {"model": ErrorResponseModel},
433 433 })
434 434 def read_files(request: Request, bucket_id: str, delete_files: bool = True, refine_chunks_with_spacy: bool = False):
435 bucket_dir = os.path.join(get_cookies_dir(), bucket_id)
435 bucket_dir = os.path.join(get_cookies_dir(), "buckets", bucket_id)
436 436 event_stream = "text/event-stream" in request.headers.get("accept", "")
437 437 if not os.path.isdir(bucket_dir):
438 438 return ErrorResponse.from_message("Bucket dir not found", 404)
@@ -443,7 +443,7 @@ class Api:
443 443 HTTP_200_OK: {"model": UploadResponseModel}
444 444 })
445 445 def upload_files(bucket_id: str, files: List[UploadFile]):
446 bucket_dir = os.path.join(get_cookies_dir(), bucket_id)
446 bucket_dir = os.path.join(get_cookies_dir(), "buckets", bucket_id)
447 447 os.makedirs(bucket_dir, exist_ok=True)
448 448 filenames = []
449 449 for file in files:
Modified g4f/client/__init__.py +30 -9
@@ -63,7 +63,7 @@ def iter_response(
63 63 tool_calls = chunk.get_list()
64 64 continue
65 65 elif isinstance(chunk, Usage):
66 usage = chunk.get_dict()
66 usage = chunk
67 67 continue
68 68 elif isinstance(chunk, BaseConversation):
69 69 yield chunk
@@ -90,19 +90,23 @@ def iter_response(
90 90
91 91 idx += 1
92 92 if usage is None:
93 usage = Usage(prompt_tokens=0, completion_tokens=idx, total_tokens=idx).get_dict()
93 usage = Usage(prompt_tokens=0, completion_tokens=idx, total_tokens=idx)
94
94 95 finish_reason = "stop" if finish_reason is None else finish_reason
95 96
96 97 if stream:
97 yield ChatCompletionChunk.model_construct(None, finish_reason, completion_id, int(time.time()))
98 yield ChatCompletionChunk.model_construct(
99 None, finish_reason, completion_id, int(time.time()),
100 usage=usage.get_dict()
101 )
98 102 else:
99 103 if response_format is not None and "type" in response_format:
100 104 if response_format["type"] == "json_object":
101 105 content = filter_json(content)
102 yield ChatCompletion.model_construct(content, finish_reason, completion_id, int(time.time()), **filter_none(
103 tool_calls=tool_calls,
104 usage=usage
105 ))
106 yield ChatCompletion.model_construct(
107 content, finish_reason, completion_id, int(time.time()),
108 usage=usage.get_dict(), **filter_none(tool_calls=tool_calls)
109 )
106 110
107 111 # Synchronous iter_append_model_and_provider function
108 112 def iter_append_model_and_provider(response: ChatCompletionResponseType, last_model: str, last_provider: ProviderType) -> ChatCompletionResponseType:
@@ -126,6 +130,8 @@ async def async_iter_response(
126 130 finish_reason = None
127 131 completion_id = ''.join(random.choices(string.ascii_letters + string.digits, k=28))
128 132 idx = 0
133 tool_calls = None
134 usage = None
129 135
130 136 try:
131 137 async for chunk in response:
@@ -135,6 +141,12 @@ async def async_iter_response(
135 141 elif isinstance(chunk, BaseConversation):
136 142 yield chunk
137 143 continue
144 elif isinstance(chunk, ToolCalls):
145 tool_calls = chunk.get_list()
146 continue
147 elif isinstance(chunk, Usage):
148 usage = chunk
149 continue
138 150 elif isinstance(chunk, SynthesizeData) or not chunk:
139 151 continue
140 152
@@ -158,13 +170,22 @@ async def async_iter_response(
158 170
159 171 finish_reason = "stop" if finish_reason is None else finish_reason
160 172
173 if usage is None:
174 usage = Usage(prompt_tokens=0, completion_tokens=idx, total_tokens=idx)
175
161 176 if stream:
162 yield ChatCompletionChunk.model_construct(None, finish_reason, completion_id, int(time.time()))
177 yield ChatCompletionChunk.model_construct(
178 None, finish_reason, completion_id, int(time.time()),
179 usage=usage.get_dict()
180 )
163 181 else:
164 182 if response_format is not None and "type" in response_format:
165 183 if response_format["type"] == "json_object":
166 184 content = filter_json(content)
167 yield ChatCompletion.model_construct(content, finish_reason, completion_id, int(time.time()))
185 yield ChatCompletion.model_construct(
186 content, finish_reason, completion_id, int(time.time()),
187 usage=usage.get_dict(), **filter_none(tool_calls=tool_calls)
188 )
168 189 finally:
169 190 await safe_aclose(response)
170 191
Modified g4f/client/stubs.py +5 -2
@@ -36,6 +36,7 @@ class ChatCompletionChunk(BaseModel):
36 36 model: str
37 37 provider: Optional[str]
38 38 choices: List[ChatCompletionDeltaChoice]
39 usage: Usage
39 40
40 41 @classmethod
41 42 def model_construct(
@@ -43,7 +44,8 @@ class ChatCompletionChunk(BaseModel):
43 44 content: str,
44 45 finish_reason: str,
45 46 completion_id: str = None,
46 created: int = None
47 created: int = None,
48 usage: Usage = None
47 49 ):
48 50 return super().model_construct(
49 51 id=f"chatcmpl-{completion_id}" if completion_id else None,
@@ -54,7 +56,8 @@ class ChatCompletionChunk(BaseModel):
54 56 choices=[ChatCompletionDeltaChoice.model_construct(
55 57 ChatCompletionDelta.model_construct(content),
56 58 finish_reason
57 )]
59 )],
60 **filter_none(usage=usage)
58 61 )
59 62
60 63 class ChatCompletionMessage(BaseModel):
Modified g4f/gui/client/index.html +2 -1
@@ -36,6 +36,7 @@
36 36 import llamaTokenizer from "llama-tokenizer-js"
37 37 </script>
38 38 <script src="https://cdn.jsdelivr.net/npm/gpt-tokenizer/dist/cl100k_base.js" async></script>
39 <script src="https://cdn.jsdelivr.net/npm/gpt-tokenizer/dist/o200k_base.js" async></script>
39 40 <script type="module" async>
40 41 import PhotoSwipeLightbox from 'https://cdn.jsdelivr.net/npm/photoswipe/dist/photoswipe-lightbox.esm.js';
41 42 const lightbox = new PhotoSwipeLightbox({
@@ -138,7 +139,7 @@
138 139 </div>
139 140 <div class="field">
140 141 <span class="label">Refine files with spaCy</span>
141 <input type="checkbox" id="refine" checked/>
142 <input type="checkbox" id="refine"/>
142 143 <label for="refine" class="toogle" title=""></label>
143 144 </div>
144 145 <div class="field box">
Modified g4f/gui/client/static/js/chat.v1.js +91 -59
@@ -34,6 +34,7 @@ let synthesize_storage = {};
34 34 let title_storage = {};
35 35 let parameters_storage = {};
36 36 let finish_storage = {};
37 let usage_storage = {};
37 38
38 39 messageInput.addEventListener("blur", () => {
39 40 window.scrollTo(0, 0);
@@ -60,7 +61,7 @@ if (window.markdownit) {
60 61 .replaceAll(/<!-- generated images start -->|<!-- generated images end -->/gm, "")
61 62 .replaceAll(/<img data-prompt="[^>]+">/gm, "")
62 63 .replaceAll(/{"bucket_id":"([^"]+)"}/gm, (match, p1) => {
63 size = appStorage.getItem(`bucket:${p1}`);
64 size = parseInt(appStorage.getItem(`bucket:${p1}`), 10);
64 65 return `**Bucket:** [[${p1}]](/backend-api/v2/files/${p1})${size ? ` (${formatFileSize(size)})` : ""}`;
65 66 })
66 67 )
@@ -449,7 +450,7 @@ document.querySelector(".media_player .fa-x").addEventListener("click", ()=>{
449 450 media_player.removeChild(audio);
450 451 });
451 452
452 const prepare_messages = (messages, message_index = -1, do_continue = false) => {
453 const prepare_messages = (messages, message_index = -1, do_continue = false, do_filter = true) => {
453 454 messages = [ ...messages ]
454 455 if (message_index != null) {
455 456 // Removes messages after selected
@@ -493,7 +494,7 @@ const prepare_messages = (messages, message_index = -1, do_continue = false) =>
493 494 }
494 495
495 496 // Remove history, if it's selected
496 if (document.getElementById('history')?.checked) {
497 if (document.getElementById('history')?.checked && do_filter) {
497 498 if (message_index == null) {
498 499 messages = [messages.pop(), messages.pop()];
499 500 } else {
@@ -509,7 +510,7 @@ const prepare_messages = (messages, message_index = -1, do_continue = false) =>
509 510 delete new_message.regenerate;
510 511 }
511 512 // Include only not regenerated messages
512 if (new_message && !new_message.regenerate) {
513 if (new_message) {
513 514 // Remove generated images from history
514 515 if (new_message.content) {
515 516 new_message.content = filter_message(new_message.content);
@@ -518,10 +519,15 @@ const prepare_messages = (messages, message_index = -1, do_continue = false) =>
518 519 delete new_message.provider;
519 520 delete new_message.synthesize;
520 521 delete new_message.finish;
522 delete new_message.usage;
521 523 delete new_message.conversation;
522 524 delete new_message.continue;
523 525 // Append message to new messages
524 new_messages.push(new_message)
526 if (do_filter && !new_message.regenerate) {
527 new_messages.push(new_message)
528 } else if (!do_filter) {
529 new_messages.push(new_message)
530 }
525 531 }
526 532 });
527 533
@@ -714,6 +720,8 @@ async function add_message_chunk(message, message_id, provider, scroll) {
714 720 update_message(content_map, message_id, message.login, scroll);
715 721 } else if (message.type == "finish") {
716 722 finish_storage[message_id] = message.finish;
723 } else if (message.type == "usage") {
724 usage_storage[message_id] = message.usage;
717 725 } else if (message.type == "parameters") {
718 726 if (!parameters_storage[provider]) {
719 727 parameters_storage[provider] = {};
@@ -836,6 +844,7 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
836 844 regenerate,
837 845 title_storage[message_id],
838 846 finish_storage[message_id],
847 usage_storage[message_id],
839 848 action=="continue"
840 849 );
841 850 delete message_storage[message_id];
@@ -983,6 +992,32 @@ const new_conversation = async () => {
983 992 say_hello();
984 993 };
985 994
995 function merge_messages(message1, message2) {
996 let newContent = message2;
997 if (newContent.startsWith("```")) {
998 const index = str.indexOf("\n");
999 newContent = newContent.substring(index);
1000 } else if (newContent.startsWith("...")) {
1001 newContent = " " + newContent.substring(3);
1002 }
1003 // Remove duplicate words
1004 if (newContent.indexOf(" ") > 0) {
1005 let words = message1.trim().split(" ");
1006 let lastWord = words[words.length - 1];
1007 if (newContent.startsWith(lastWord)) {
1008 newContent = newContent.substring(lastWord.length);
1009 }
1010 }
1011 // Remove duplicate lines
1012 let lines = message1.trim().split("\n");
1013 let lastLine = lines[lines.length - 1];
1014 while (newContent && lastLine && newContent.startsWith(lastLine)) {
1015 newContent = newContent.substring(lastLine.length);
1016 lastLine = lines[lines.length - 1];
1017 }
1018 return message1 + newContent;
1019 }
1020
986 1021 const load_conversation = async (conversation_id, scroll=true) => {
987 1022 let conversation = await get_conversation(conversation_id);
988 1023 let messages = conversation?.items || [];
@@ -1004,6 +1039,9 @@ const load_conversation = async (conversation_id, scroll=true) => {
1004 1039 let last_model = null;
1005 1040 let providers = [];
1006 1041 let buffer = "";
1042 let last_usage = null;
1043 let completion_tokens = 0;
1044
1007 1045 messages.forEach((item, i) => {
1008 1046 if (item.continue) {
1009 1047 elements.pop();
@@ -1011,26 +1049,9 @@ const load_conversation = async (conversation_id, scroll=true) => {
1011 1049 buffer = "";
1012 1050 }
1013 1051 buffer = buffer.replace(/ \[aborted\]$/g, "").replace(/ \[error\]$/g, "");
1014 let lines = buffer.trim().split("\n");
1015 let lastLine = lines[lines.length - 1];
1016 let newContent = item.content;
1017 if (newContent.startsWith("```")) {
1018 const index = str.indexOf("\n");
1019 newContent = newContent.substring(index);
1020 } else if (newContent.startsWith("...")) {
1021 newContent = " " + newContent.substring(3);
1022 }
1023 if (newContent.startsWith(lastLine)) {
1024 newContent = newContent.substring(lastLine.length);
1025 } else {
1026 let words = buffer.trim().split(" ");
1027 let lastWord = words[words.length - 1];
1028 if (newContent.startsWith(lastWord)) {
1029 newContent = newContent.substring(lastWord.length);
1030 }
1031 }
1032 buffer += newContent;
1052 buffer += merge_messages(buffer, item.content);
1033 1053 last_model = item.provider?.model;
1054 last_usage = item.usage;
1034 1055 providers.push(item.provider?.name);
1035 1056 let next_i = parseInt(i) + 1;
1036 1057 let next_provider = item.provider ? item.provider : (messages.length > next_i ? messages[next_i].provider : null);
@@ -1055,11 +1076,12 @@ const load_conversation = async (conversation_id, scroll=true) => {
1055 1076 const objectUrl = URL.createObjectURL(file);
1056 1077
1057 1078 let add_buttons = [];
1058 // Add continue button if possible
1079 // Find buttons to add
1059 1080 actions = ["variant"]
1060 1081 if (item.finish && item.finish.actions) {
1061 1082 actions = item.finish.actions
1062 1083 }
1084 // Add continue button if possible
1063 1085 if (item.role == "assistant" && !actions.includes("continue")) {
1064 1086 let reason = "stop";
1065 1087 // Read finish reason from conversation
@@ -1071,20 +1093,9 @@ const load_conversation = async (conversation_id, scroll=true) => {
1071 1093 // Has a stop or error token at the end
1072 1094 if (lastLine.endsWith("[aborted]") || lastLine.endsWith("[error]")) {
1073 1095 reason = "error";
1074 // Has an even number of start or end code tags
1075 } else if (buffer.split("```").length - 1 % 2 === 1) {
1096 // Has an even number of start or end code tags
1097 } else if (reason = "stop" && buffer.split("```").length - 1 % 2 === 1) {
1076 1098 reason = "length";
1077 // Has a end token at the end
1078 } else if (lastLine.endsWith("```") || lastLine.endsWith(".") || lastLine.endsWith("?") || lastLine.endsWith("!")
1079 || lastLine.endsWith('"') || lastLine.endsWith("'") || lastLine.endsWith(")")
1080 || lastLine.endsWith(">") || lastLine.endsWith("]") || lastLine.endsWith("}") ) {
1081 reason = "stop"
1082 } else {
1083 // Has an emoji at the end
1084 const regex = /\p{Emoji}$/u;
1085 if (regex.test(lastLine)) {
1086 reason = "stop"
1087 }
1088 1099 }
1089 1100 if (reason == "length" || reason == "max_tokens" || reason == "error") {
1090 1101 actions.push("continue")
@@ -1117,6 +1128,13 @@ const load_conversation = async (conversation_id, scroll=true) => {
1117 1128 }
1118 1129 }
1119 1130
1131 if (!item.continue) {
1132 completion_tokens = 0;
1133 }
1134 completion_tokens += item.usage?.completion_tokens ? item.usage.completion_tokens : 0;
1135 let next_usage = messages.length > next_i ? messages[next_i].usage : null;
1136 let prompt_tokens = next_usage?.prompt_tokens ? next_usage?.prompt_tokens : 0
1137
1120 1138 elements.push(`
1121 1139 <div class="message${item.regenerate ? " regenerate": ""}" data-index="${i}" data-object_url="${objectUrl}" data-synthesize_url="${synthesize_url}">
1122 1140 <div class="${item.role}">
@@ -1131,7 +1149,7 @@ const load_conversation = async (conversation_id, scroll=true) => {
1131 1149 ${provider}
1132 1150 <div class="content_inner">${markdown_render(buffer)}</div>
1133 1151 <div class="count">
1134 ${count_words_and_tokens(buffer, next_provider?.model)}
1152 ${count_words_and_tokens(buffer, next_provider?.model, completion_tokens, prompt_tokens)}
1135 1153 ${add_buttons.join("")}
1136 1154 </div>
1137 1155 </div>
@@ -1140,8 +1158,11 @@ const load_conversation = async (conversation_id, scroll=true) => {
1140 1158 });
1141 1159
1142 1160 if (window.GPTTokenizer_cl100k_base) {
1143 const filtered = prepare_messages(messages, null);
1161 const filtered = prepare_messages(messages, null, true, false);
1144 1162 if (filtered.length > 0) {
1163 if (GPTTokenizer_o200k_base && last_model?.startsWith("gpt-4o") || last_model?.startsWith("o1")) {
1164 return GPTTokenizer_o200k_base?.encodeChat(filtered, last_model).length;
1165 }
1145 1166 last_model = last_model?.startsWith("gpt-3") ? "gpt-3.5-turbo" : "gpt-4"
1146 1167 let count_total = GPTTokenizer_cl100k_base?.encodeChat(filtered, last_model).length
1147 1168 if (count_total > 0) {
@@ -1259,6 +1280,7 @@ const add_message = async (
1259 1280 regenerate = false,
1260 1281 title = null,
1261 1282 finish = null,
1283 usage = null,
1262 1284 do_continue = false
1263 1285 ) => {
1264 1286 const conversation = await get_conversation(conversation_id);
@@ -1287,6 +1309,9 @@ const add_message = async (
1287 1309 if (finish) {
1288 1310 new_message.finish = finish;
1289 1311 }
1312 if (usage) {
1313 new_message.usage = usage;
1314 }
1290 1315 if (do_continue) {
1291 1316 new_message.continue = true;
1292 1317 }
@@ -1493,7 +1518,7 @@ const say_hello = async () => {
1493 1518 }
1494 1519 }
1495 1520
1496 function count_tokens(model, text) {
1521 function count_tokens(model, text, prompt_tokens = 0) {
1497 1522 if (model) {
1498 1523 if (window.llamaTokenizer)
1499 1524 if (model.startsWith("llama") || model.startsWith("codellama")) {
@@ -1504,10 +1529,16 @@ function count_tokens(model, text) {
1504 1529 return mistralTokenizer.encode(text).length;
1505 1530 }
1506 1531 }
1507 if (window.GPTTokenizer_cl100k_base) {
1508 return GPTTokenizer_cl100k_base.encode(text).length;
1532 if (window.GPTTokenizer_cl100k_base && window.GPTTokenizer_o200k_base) {
1533 if (model?.startsWith("gpt-4o") || model?.startsWith("o1")) {
1534 return GPTTokenizer_o200k_base?.encode(text, model).length;
1535 } else {
1536 model = model?.startsWith("gpt-3") ? "gpt-3.5-turbo" : "gpt-4"
1537 return GPTTokenizer_cl100k_base?.encode(text, model).length;
1538 }
1539 } else {
1540 return prompt_tokens;
1509 1541 }
1510 return 0;
1511 1542 }
1512 1543
1513 1544 function count_words(text) {
@@ -1518,9 +1549,9 @@ function count_chars(text) {
1518 1549 return text.match(/[^\s\p{P}]/gu)?.length || 0;
1519 1550 }
1520 1551
1521 function count_words_and_tokens(text, model) {
1552 function count_words_and_tokens(text, model, completion_tokens, prompt_tokens) {
1522 1553 text = filter_message(text);
1523 return `(${count_words(text)} words, ${count_chars(text)} chars, ${count_tokens(model, text)} tokens)`;
1554 return `(${count_words(text)} words, ${count_chars(text)} chars, ${completion_tokens ? completion_tokens : count_tokens(model, text, prompt_tokens)} tokens)`;
1524 1555 }
1525 1556
1526 1557 function update_message(content_map, message_id, content = null, scroll = true) {
@@ -1553,16 +1584,12 @@ function update_message(content_map, message_id, content = null, scroll = true)
1553 1584 };
1554 1585
1555 1586 let countFocus = messageInput;
1556 let timeoutId;
1557 1587 const count_input = async () => {
1558 if (timeoutId) clearTimeout(timeoutId);
1559 timeoutId = setTimeout(() => {
1560 if (countFocus.value) {
1561 inputCount.innerText = count_words_and_tokens(countFocus.value, get_selected_model()?.value);
1562 } else {
1563 inputCount.innerText = "";
1564 }
1565 }, 100);
1588 if (countFocus.value) {
1589 inputCount.innerText = count_words_and_tokens(countFocus.value, get_selected_model()?.value);
1590 } else {
1591 inputCount.innerText = "";
1592 }
1566 1593 };
1567 1594 messageInput.addEventListener("keyup", count_input);
1568 1595 systemPrompt.addEventListener("keyup", count_input);
@@ -2115,15 +2142,19 @@ if (SpeechRecognition) {
2115 2142 let buffer;
2116 2143 let lastDebounceTranscript;
2117 2144 recognition.onstart = function() {
2118 microLabel.classList.add("recognition");
2119 2145 startValue = messageInput.value;
2120 2146 lastDebounceTranscript = "";
2121 2147 messageInput.readOnly = true;
2148 buffer = "";
2122 2149 };
2123 2150 recognition.onend = function() {
2124 2151 messageInput.value = `${startValue ? startValue + "\n" : ""}${buffer}`;
2125 messageInput.readOnly = false;
2126 messageInput.focus();
2152 if (!microLabel.classList.contains("recognition")) {
2153 recognition.start();
2154 } else {
2155 messageInput.readOnly = false;
2156 messageInput.focus();
2157 }
2127 2158 };
2128 2159 recognition.onresult = function(event) {
2129 2160 if (!event.results) {
@@ -2148,10 +2179,11 @@ if (SpeechRecognition) {
2148 2179
2149 2180 microLabel.addEventListener("click", (e) => {
2150 2181 if (microLabel.classList.contains("recognition")) {
2182 microLabel.classList.remove("recognition");
2151 2183 recognition.stop();
2152 2184 messageInput.value = `${startValue ? startValue + "\n" : ""}${buffer}`;
2153 microLabel.classList.remove("recognition");
2154 2185 } else {
2186 microLabel.classList.add("recognition");
2155 2187 const lang = document.getElementById("recognition-language")?.value;
2156 2188 recognition.lang = lang || navigator.language;
2157 2189 recognition.start();
Modified g4f/gui/server/api.py +3 -1
@@ -13,7 +13,7 @@ from ...tools.run_tools import iter_run_tools
13 13 from ...Provider import ProviderUtils, __providers__
14 14 from ...providers.base_provider import ProviderModelMixin
15 15 from ...providers.retry_provider import IterListProvider
16 from ...providers.response import BaseConversation, JsonConversation, FinishReason
16 from ...providers.response import BaseConversation, JsonConversation, FinishReason, Usage
17 17 from ...providers.response import SynthesizeData, TitleGeneration, RequestLogin, Parameters
18 18 from ... import version, models
19 19 from ... import ChatCompletion, get_model_and_provider
@@ -201,6 +201,8 @@ class Api:
201 201 yield self._format_json("parameters", chunk.get_dict())
202 202 elif isinstance(chunk, FinishReason):
203 203 yield self._format_json("finish", chunk.get_dict())
204 elif isinstance(chunk, Usage):
205 yield self._format_json("usage", chunk.get_dict())
204 206 else:
205 207 yield self._format_json("content", str(chunk))
206 208 if debug.logs:
Modified g4f/models.py +2 -3
@@ -74,7 +74,6 @@ default = Model(
74 74 Airforce,
75 75 Cloudflare,
76 76 PollinationsAI,
77 ChatGptEs,
78 77 OpenaiChat,
79 78 Mhystical,
80 79 ClaudeSon,
@@ -104,13 +103,13 @@ gpt_4 = Model(
104 103 gpt_4o = Model(
105 104 name = 'gpt-4o',
106 105 base_provider = 'OpenAI',
107 best_provider = IterListProvider([Blackbox, ChatGptEs, PollinationsAI, DarkAI, ChatGpt, Liaobots, OpenaiChat])
106 best_provider = IterListProvider([Blackbox, PollinationsAI, DarkAI, ChatGpt, Liaobots, OpenaiChat])
108 107 )
109 108
110 109 gpt_4o_mini = Model(
111 110 name = 'gpt-4o-mini',
112 111 base_provider = 'OpenAI',
113 best_provider = IterListProvider([DDG, ChatGptEs, Pizzagpt, ChatGpt, RubiksAI, Liaobots, OpenaiChat])
112 best_provider = IterListProvider([DDG, Pizzagpt, ChatGpt, RubiksAI, Liaobots, OpenaiChat])
114 113 )
115 114
116 115 # o1
Modified g4f/providers/base_provider.py +43 -15
@@ -385,7 +385,10 @@ class AsyncAuthedProvider(AsyncGeneratorProvider):
385 385
386 386 @classmethod
387 387 def on_auth(cls, **kwargs) -> AuthResult:
388 return asyncio.run(cls.on_auth_async(**kwargs))
388 auth_result = cls.on_auth_async(**kwargs)
389 if hasattr(auth_result, "__aiter__"):
390 return to_sync_generator(auth_result)
391 return asyncio.run(auth_result)
389 392
390 393 @classmethod
391 394 def get_create_function(cls) -> callable:
@@ -414,22 +417,35 @@ class AsyncAuthedProvider(AsyncGeneratorProvider):
414 417 auth_result = AuthResult(**json.load(f))
415 418 else:
416 419 auth_result = cls.on_auth(**kwargs)
417 if hasattr(auth_result, "_iter__"):
418 for chunk in auth_result:
419 if isinstance(chunk, AsyncResult):
420 auth_result = chunk
421 else:
422 yield chunk
420 try:
421 for chunk in auth_result:
422 if hasattr(chunk, "get_dict"):
423 auth_result = chunk
424 else:
425 yield chunk
426 except TypeError:
427 pass
423 428 yield from to_sync_generator(cls.create_authed(model, messages, auth_result, **kwargs))
424 429 except (MissingAuthError, NoValidHarFileError):
425 if cache_file.exists():
426 cache_file.unlink()
427 430 auth_result = cls.on_auth(**kwargs)
431 try:
432 for chunk in auth_result:
433 if hasattr(chunk, "get_dict"):
434 auth_result = chunk
435 else:
436 yield chunk
437 except TypeError:
438 pass
428 439 yield from to_sync_generator(cls.create_authed(model, messages, auth_result, **kwargs))
429 440 finally:
430 cache_file.parent.mkdir(parents=True, exist_ok=True)
431 cache_file.write_text(json.dumps(auth_result.get_dict()))
441 if hasattr(auth_result, "get_dict"):
442 data = auth_result.get_dict()
443 cache_file.parent.mkdir(parents=True, exist_ok=True)
444 cache_file.write_text(json.dumps(data))
445 elif cache_file.exists():
446 cache_file.unlink()
432 447
448 @classmethod
433 449 async def create_async_generator(
434 450 cls,
435 451 model: str,
@@ -443,24 +459,36 @@ class AsyncAuthedProvider(AsyncGeneratorProvider):
443 459 with cache_file.open("r") as f:
444 460 auth_result = AuthResult(**json.load(f))
445 461 else:
446 auth_result = await cls.on_auth_async(**kwargs)
462 auth_result = cls.on_auth_async(**kwargs)
447 463 if hasattr(auth_result, "_aiter__"):
448 464 async for chunk in auth_result:
449 465 if isinstance(chunk, AsyncResult):
450 466 auth_result = chunk
451 467 else:
452 468 yield chunk
469 else:
470 auth_result = await auth_result
453 471 response = to_async_iterator(cls.create_authed(model, messages, **kwargs, auth_result=auth_result))
454 472 async for chunk in response:
455 473 yield chunk
456 474 except (MissingAuthError, NoValidHarFileError):
457 475 if cache_file.exists():
458 476 cache_file.unlink()
459 auth_result = await cls.on_auth_async(**kwargs)
477 auth_result = cls.on_auth_async(**kwargs)
478 if hasattr(auth_result, "_aiter__"):
479 async for chunk in auth_result:
480 if isinstance(chunk, AsyncResult):
481 auth_result = chunk
482 else:
483 yield chunk
484 else:
485 auth_result = await auth_result
460 486 response = to_async_iterator(cls.create_authed(model, messages, **kwargs, auth_result=auth_result))
461 487 async for chunk in response:
462 488 yield chunk
463 489 finally:
464 if auth_result is not None:
490 if hasattr(auth_result, "get_dict"):
465 491 cache_file.parent.mkdir(parents=True, exist_ok=True)
466 cache_file.write_text(json.dumps(auth_result.get_dict()))
492 cache_file.write_text(json.dumps(auth_result.get_dict()))
493 elif cache_file.exists():
494 cache_file.unlink()
Modified g4f/providers/response.py +3 -0
Modified g4f/providers/retry_provider.py +12 -19
Modified g4f/requests/__init__.py +3 -2
Modified g4f/tools/files.py +2 -2