返回提交历史
Modified
docs/requests.md
+16
-25
Modified
g4f/Provider/DDG.py
+1
-1
Modified
g4f/Provider/needs_auth/OpenaiChat.py
+22
-20
Modified
g4f/Provider/openai/har_file.py
+1
-1
Modified
g4f/api/__init__.py
+2
-2
Modified
g4f/client/__init__.py
+30
-9
Modified
g4f/client/stubs.py
+5
-2
Modified
g4f/gui/client/index.html
+2
-1
Modified
g4f/gui/client/static/js/chat.v1.js
+91
-59
Modified
g4f/gui/server/api.py
+3
-1
Modified
g4f/models.py
+2
-3
Modified
g4f/providers/base_provider.py
+43
-15
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
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
代码差异
16 个文件
+238
-162
@@ -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:
@@ -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:
@@ -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
@@ -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):
@@ -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:
@@ -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
@@ -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):
@@ -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">
@@ -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();
@@ -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:
@@ -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
@@ -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()