返回提交历史
Modified
g4f/Provider/PollinationsAI.py
+6
-6
Modified
g4f/Provider/hf/__init__.py
+1
-1
Modified
g4f/Provider/needs_auth/OpenaiChat.py
+36
-54
Modified
g4f/client/__init__.py
+14
-11
Modified
g4f/client/stubs.py
+2
-2
Modified
g4f/gui/client/static/css/style.css
+5
-2
Modified
g4f/gui/client/static/js/chat.v1.js
+7
-5
Modified
g4f/providers/base_provider.py
+5
-3
Modified
g4f/requests/raise_for_status.py
+13
-3
Added
g4f/tools/langchain.py
+41
-0
Modified
g4f/tools/run_tools.py
+3
-1
XFEstudio/gpt4free
Add langchain integration
4e12f048
代码差异
11 个文件
+133
-88
@@ -14,7 +14,7 @@ from ..image import to_data_uri
14
14
from ..errors import ModelNotFoundError
15
15
from ..requests.raise_for_status import raise_for_status
16
16
from ..requests.aiohttp import get_connector
17
from ..providers.response import ImageResponse, ImagePreview, FinishReason, Usage, Reasoning
17
from ..providers.response import ImageResponse, ImagePreview, FinishReason, Usage
18
18
19
19
DEFAULT_HEADERS = {
20
20
'Accept': '*/*',
@@ -63,6 +63,7 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
63
63
"gpt-4o-mini": "claude",
64
64
"deepseek-chat": "claude-email",
65
65
"deepseek-r1": "deepseek-reasoner",
66
"gemini-2.0": "gemini",
66
67
"gemini-2.0-flash": "gemini",
67
68
"gemini-2.0-flash-thinking": "gemini-thinking",
68
69
@@ -208,10 +209,8 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
208
209
"enhance": str(enhance).lower(),
209
210
"safe": str(safe).lower()
210
211
}
211
params = {k: v for k, v in params.items() if v is not None}
212
query = "&".join(f"{k}={quote_plus(v)}" for k, v in params.items())
213
prefix = f"{model}_{seed}" if seed is not None else model
214
url = f"{cls.image_api_endpoint}prompt/{prefix}_{quote_plus(prompt)}?{query}"
212
query = "&".join(f"{k}={quote_plus(v)}" for k, v in params.items() if v is not None)
213
url = f"{cls.image_api_endpoint}prompt/{quote_plus(prompt)}?{query}"
215
214
yield ImagePreview(url, prompt)
216
215
217
216
async with ClientSession(headers=DEFAULT_HEADERS, connector=get_connector(proxy=proxy)) as session:
@@ -266,7 +265,8 @@ class PollinationsAI(AsyncGeneratorProvider, ProviderModelMixin):
266
265
"seed": seed,
267
266
"cache": cache
268
267
})
269
268
if "gemimi" in model:
269
data.pop("seed")
270
270
async with session.post(cls.text_api_endpoint, json=data) as response:
271
271
await raise_for_status(response)
272
272
result = await response.json()
@@ -36,7 +36,7 @@ class HuggingFace(AsyncGeneratorProvider, ProviderModelMixin):
36
36
messages: Messages,
37
37
**kwargs
38
38
) -> AsyncResult:
39
if "images" not in kwargs and "deepseek" in model or random.random() >= 0.5:
39
if "tools" not in kwargs and "images" not in kwargs and "deepseek" in model or random.random() >= 0.5:
40
40
try:
41
41
is_started = False
42
42
async for chunk in HuggingFaceInference.create_async_generator(model, messages, **kwargs):
@@ -8,7 +8,7 @@ import json
8
8
import base64
9
9
import time
10
10
import random
11
from typing import AsyncIterator, Iterator, Optional, Generator, Dict, List
11
from typing import AsyncIterator, Iterator, Optional, Generator, Dict
12
12
from copy import copy
13
13
14
14
try:
@@ -104,19 +104,16 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
104
104
synthesize_content_type = "audio/mpeg"
105
105
request_config = RequestConfig()
106
106
107
_api_key: str = None
108
_headers: dict = None
109
_cookies: Cookies = None
110
107
_expires: int = None
111
108
112
109
@classmethod
113
async def on_auth_async(cls, **kwargs) -> AsyncIterator:
114
async for chunk in cls.login():
110
async def on_auth_async(cls, proxy: str = None, **kwargs) -> AsyncIterator:
111
async for chunk in cls.login(proxy=proxy):
115
112
yield chunk
116
113
yield AuthResult(
117
api_key=cls._api_key,
118
cookies=cls._cookies or cls.request_config.cookies or {},
119
headers=cls._headers or cls.request_config.headers or cls.get_default_headers(),
114
api_key=cls.request_config.access_token,
115
cookies=cls.request_config.cookies or {},
116
headers=cls.request_config.headers or cls.get_default_headers(),
120
117
expires=cls._expires,
121
118
proof_token=cls.request_config.proof_token,
122
119
turnstile_token=cls.request_config.turnstile_token
@@ -306,17 +303,17 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
306
303
) as session:
307
304
image_requests = None
308
305
if not cls.needs_auth:
309
if cls._headers is None:
310
cls._create_request_args(cls._cookies)
306
if not cls.request_config.headers:
307
cls._create_request_args(cls.request_config.cookies)
311
308
async with session.get(cls.url, headers=INIT_HEADERS) as response:
312
309
cls._update_request_args(auth_result, session)
313
310
await raise_for_status(response)
314
311
else:
315
if cls._headers is None and getattr(auth_result, "cookies", None):
312
if not cls.request_config.headers and getattr(auth_result, "cookies", None):
316
313
cls._create_request_args(auth_result.cookies, auth_result.headers)
317
314
if not cls._set_api_key(getattr(auth_result, "api_key", None)):
318
315
raise MissingAuthError("Access token is not valid")
319
async with session.get(cls.url, headers=cls._headers) as response:
316
async with session.get(cls.url, headers=cls.request_config.headers) as response:
320
317
cls._update_request_args(auth_result, session)
321
318
await raise_for_status(response)
322
319
try:
@@ -331,17 +328,17 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
331
328
conversation = copy(conversation)
332
329
if getattr(auth_result, "cookies", {}).get("oai-did") != getattr(conversation, "user_id", None):
333
330
conversation = Conversation(None, str(uuid.uuid4()))
334
if cls._api_key is None:
331
if cls.request_config.access_token is None:
335
332
auto_continue = False
336
333
conversation.finish_reason = None
337
334
sources = Sources([])
338
335
while conversation.finish_reason is None:
339
336
async with session.post(
340
337
f"{cls.url}/backend-anon/sentinel/chat-requirements"
341
if cls._api_key is None else
338
if cls.request_config.access_token is None else
342
339
f"{cls.url}/backend-api/sentinel/chat-requirements",
343
340
json={"p": None if not getattr(auth_result, "proof_token", None) else get_requirements_token(getattr(auth_result, "proof_token", None))},
344
headers=cls._headers
341
headers=cls.request_config.headers
345
342
) as response:
346
343
if response.status in (401, 403):
347
344
auth_result.reset()
@@ -407,7 +404,7 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
407
404
messages = messages if conversation_id is None else [messages[-1]]
408
405
data["messages"] = cls.create_messages(messages, image_requests, ["search"] if web_search else None)
409
406
headers = {
410
**cls._headers,
407
**cls.request_config.headers,
411
408
"accept": "text/event-stream",
412
409
"content-type": "application/json",
413
410
"openai-sentinel-chat-requirements-token": chat_token,
@@ -420,7 +417,7 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
420
417
headers['openai-sentinel-turnstile-token'] = auth_result.turnstile_token
421
418
async with session.post(
422
419
f"{cls.url}/backend-anon/conversation"
423
if cls._api_key is None else
420
if cls.request_config.access_token is None else
424
421
f"{cls.url}/backend-api/conversation",
425
422
json=data,
426
423
headers=headers
@@ -550,7 +547,7 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
550
547
async with session.get(
551
548
f"{cls.url}/backend-api/synthesize",
552
549
params=params,
553
headers=cls._headers
550
headers=cls.request_config.headers
554
551
) as response:
555
552
await raise_for_status(response)
556
553
async for chunk in response.iter_content():
@@ -560,44 +557,29 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
560
557
async def login(
561
558
cls,
562
559
proxy: str = None,
563
api_key: str = None,
564
proof_token: str = None,
565
cookies: Cookies = None,
566
headers: dict = None,
567
560
**kwargs
568
561
) -> AsyncIterator:
569
562
if cls._expires is not None and (cls._expires - 60*10) < time.time():
570
cls._headers = cls._api_key = None
571
if cls._headers is None or headers is not None:
572
cls._headers = {} if headers is None else headers
573
if proof_token is not None:
574
cls.request_config.proof_token = proof_token
575
if cookies is not None:
576
cls.request_config.cookies = cookies
577
if api_key is not None:
563
cls.request_config.headers = cls.request_config.access_token = None
564
if cls.request_config.headers is None:
565
cls.request_config.headers = {}
566
if cls.request_config.access_token is not None:
578
567
cls._create_request_args(cls.request_config.cookies, cls.request_config.headers)
579
cls._set_api_key(api_key)
568
cls._set_api_key(cls.request_config.access_token)
580
569
else:
581
570
try:
582
await get_request_config(cls.request_config, proxy)
571
cls.request_config = await get_request_config(cls.request_config, proxy)
583
572
cls._create_request_args(cls.request_config.cookies, cls.request_config.headers)
584
573
if cls.request_config.access_token is not None or cls.needs_auth:
585
574
if not cls._set_api_key(cls.request_config.access_token):
586
575
raise NoValidHarFileError(f"Access token is not valid: {cls.request_config.access_token}")
587
576
except NoValidHarFileError:
588
577
if has_nodriver:
589
if cls._api_key is None:
590
login_url = os.environ.get("G4F_LOGIN_URL")
591
if login_url:
592
yield RequestLogin(cls.label, login_url)
578
if cls.request_config.access_token is None:
579
yield RequestLogin(cls.label, os.environ.get("G4F_LOGIN_URL", ""))
593
580
await cls.nodriver_auth(proxy)
594
581
else:
595
582
raise
596
yield Parameters(**{
597
"api_key": cls._api_key,
598
"proof_token": cls.request_config.proof_token,
599
"cookies": cls.request_config.cookies,
600
})
601
583
602
584
@classmethod
603
585
async def nodriver_auth(cls, proxy: str = None):
@@ -615,7 +597,7 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
615
597
if "OpenAI-Sentinel-Turnstile-Token" in event.request.headers:
616
598
cls.request_config.turnstile_token = event.request.headers["OpenAI-Sentinel-Turnstile-Token"]
617
599
if "Authorization" in event.request.headers:
618
cls._api_key = event.request.headers["Authorization"].split()[-1]
600
cls.request_config.access_token = event.request.headers["Authorization"].split()[-1]
619
601
elif event.request.url == arkose_url:
620
602
cls.request_config.arkose_request = arkReq(
621
603
arkURL=event.request.url,
@@ -632,13 +614,13 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
632
614
await page.evaluate("document.getElementById('prompt-textarea').innerText = 'Hello'")
633
615
await page.evaluate("document.querySelector('[data-testid=\"send-button\"]').click()")
634
616
while True:
635
if cls._api_key is not None or not cls.needs_auth:
617
if cls.request_config.access_token is not None or not cls.needs_auth:
636
618
break
637
619
body = await page.evaluate("JSON.stringify(window.__remixContext)")
638
620
if body:
639
621
match = re.search(r'"accessToken":"(.*?)"', body)
640
622
if match:
641
cls._api_key = match.group(1)
623
cls.request_config.access_token = match.group(1)
642
624
break
643
625
await asyncio.sleep(1)
644
626
while True:
@@ -649,7 +631,7 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
649
631
cls.request_config.cookies = await page.send(get_cookies([cls.url]))
650
632
await page.close()
651
633
cls._create_request_args(cls.request_config.cookies, cls.request_config.headers, user_agent=user_agent)
652
cls._set_api_key(cls._api_key)
634
cls._set_api_key(cls.request_config.access_token)
653
635
finally:
654
636
stop_browser()
655
637
@@ -662,10 +644,10 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
662
644
663
645
@classmethod
664
646
def _create_request_args(cls, cookies: Cookies = None, headers: dict = None, user_agent: str = None):
665
cls._headers = cls.get_default_headers() if headers is None else headers
647
cls.request_config.headers = cls.get_default_headers() if headers is None else headers
666
648
if user_agent is not None:
667
cls._headers["user-agent"] = user_agent
668
cls._cookies = {} if cookies is None else cookies
649
cls.request_config.headers["user-agent"] = user_agent
650
cls.request_config.cookies = {} if cookies is None else cookies
669
651
cls._update_cookie_header()
670
652
671
653
@classmethod
@@ -673,7 +655,7 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
673
655
if hasattr(auth_result, "cookies"):
674
656
for c in session.cookie_jar if hasattr(session, "cookie_jar") else session.cookies.jar:
675
657
auth_result.cookies[getattr(c, "key", getattr(c, "name", ""))] = c.value
676
cls._cookies = auth_result.cookies
658
cls.request_config.cookies = auth_result.cookies
677
659
cls._update_cookie_header()
678
660
679
661
@classmethod
@@ -686,15 +668,15 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
686
668
if time.time() > cls._expires:
687
669
debug.log(f"OpenaiChat: API key is expired")
688
670
else:
689
cls._api_key = api_key
690
cls._headers["authorization"] = f"Bearer {api_key}"
671
cls.request_config.access_token = api_key
672
cls.request_config.headers["authorization"] = f"Bearer {api_key}"
691
673
return True
692
674
return False
693
675
694
676
@classmethod
695
677
def _update_cookie_header(cls):
696
if cls._cookies:
697
cls._headers["cookie"] = format_cookies(cls._cookies)
678
if cls.request_config.cookies:
679
cls.request_config.headers["cookie"] = format_cookies(cls.request_config.cookies)
698
680
699
681
class Conversation(JsonConversation):
700
682
"""
@@ -111,23 +111,24 @@ def iter_response(
111
111
break
112
112
113
113
idx += 1
114
114
115
if usage is None:
115
usage = Usage(completion_tokens=idx, total_tokens=idx)
116
usage = UsageModel.model_construct(completion_tokens=idx, total_tokens=idx)
117
else:
118
usage = UsageModel.model_construct(**usage.get_dict())
116
119
117
120
finish_reason = "stop" if finish_reason is None else finish_reason
118
121
119
122
if stream:
120
123
chat_completion = ChatCompletionChunk.model_construct(
121
None, finish_reason, completion_id, int(time.time()),
122
usage=usage
124
None, finish_reason, completion_id, int(time.time()), usage=usage
123
125
)
124
126
else:
125
127
if response_format is not None and "type" in response_format:
126
128
if response_format["type"] == "json_object":
127
129
content = filter_json(content)
128
130
chat_completion = ChatCompletion.model_construct(
129
content, finish_reason, completion_id, int(time.time()),
130
usage=UsageModel.model_construct(**usage.get_dict()),
131
content, finish_reason, completion_id, int(time.time()), usage=usage,
131
132
**filter_none(tool_calls=[ToolCallModel.model_construct(**tool_call) for tool_call in tool_calls]) if tool_calls is not None else {}
132
133
)
133
134
if provider is not None:
@@ -211,21 +212,23 @@ async def async_iter_response(
211
212
finish_reason = "stop" if finish_reason is None else finish_reason
212
213
213
214
if usage is None:
214
usage = Usage(completion_tokens=idx, total_tokens=idx)
215
usage = UsageModel.model_construct(completion_tokens=idx, total_tokens=idx)
216
else:
217
usage = UsageModel.model_construct(**usage.get_dict())
215
218
216
219
if stream:
217
220
chat_completion = ChatCompletionChunk.model_construct(
218
None, finish_reason, completion_id, int(time.time()),
219
usage=usage.get_dict()
221
None, finish_reason, completion_id, int(time.time()), usage=usage
220
222
)
221
223
else:
222
224
if response_format is not None and "type" in response_format:
223
225
if response_format["type"] == "json_object":
224
226
content = filter_json(content)
225
227
chat_completion = ChatCompletion.model_construct(
226
content, finish_reason, completion_id, int(time.time()),
227
usage=UsageModel.model_construct(**usage.get_dict()),
228
**filter_none(tool_calls=[ToolCallModel.model_construct(**tool_call) for tool_call in tool_calls]) if tool_calls is not None else {}
228
content, finish_reason, completion_id, int(time.time()), usage=usage,
229
**filter_none(
230
tool_calls=[ToolCallModel.model_construct(**tool_call) for tool_call in tool_calls]
231
) if tool_calls is not None else {}
229
232
)
230
233
if provider is not None:
231
234
chat_completion.provider = provider.name
@@ -42,8 +42,8 @@ class UsageModel(BaseModel):
42
42
prompt_tokens=prompt_tokens,
43
43
completion_tokens=completion_tokens,
44
44
total_tokens=total_tokens,
45
prompt_tokens_details=TokenDetails.model_construct(**prompt_tokens_details) if prompt_tokens_details else None,
46
completion_tokens_details=TokenDetails.model_construct(**completion_tokens_details) if completion_tokens_details else None,
45
prompt_tokens_details=TokenDetails.model_construct(**prompt_tokens_details if prompt_tokens_details else {}),
46
completion_tokens_details=TokenDetails.model_construct(**completion_tokens_details if completion_tokens_details else {}),
47
47
**kwargs
48
48
)
49
49
@@ -32,6 +32,7 @@
32
32
--scrollbar: var(--colour-3);
33
33
--scrollbar-thumb: var(--blur-bg);
34
34
--button-hover: var(--colour-5);
35
--media-select: var(--colour-4);
35
36
--top: 50%;
36
37
--size: 70vw;
37
38
--blur: 35vw; /* Half of 70vw */
@@ -520,7 +521,7 @@ body:not(.white) a:visited{
520
521
cursor: pointer;
521
522
user-select: none;
522
523
color: var(--colour-1);
523
background: var(--colour-4);
524
background: var(--media-select);
524
525
border: 1px solid var(--colour-1);
525
526
transition: all 0.2s ease;
526
527
width: auto;
@@ -529,13 +530,14 @@ body:not(.white) a:visited{
529
530
}
530
531
531
532
.media-select label, .media-select button {
532
padding: 8px 12px;
533
padding: 20px 12px;
533
534
border-radius: var(--border-radius-1);
534
535
}
535
536
536
537
.media-select button.close {
537
538
order: 1000;
538
539
height: 32px;
540
padding: 8px 12px;
539
541
}
540
542
541
543
.count_total {
@@ -1357,6 +1359,7 @@ ul {
1357
1359
--scrollbar-thumb: #ccc;
1358
1360
--button-hover: var(--colour-4);
1359
1361
--background: transparent;
1362
--media-select: var(--colour-3);
1360
1363
}
1361
1364
1362
1365
.white .message .assistant .fa-xmark {
@@ -844,7 +844,7 @@ function is_stopped() {
844
844
return false;
845
845
}
846
846
847
const requestWakeLock = async (onVisibilityChange = false) => {
847
const requestWakeLock = async () => {
848
848
try {
849
849
wakeLock = await navigator.wakeLock.request('screen');
850
850
}
@@ -890,7 +890,7 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
890
890
<i class="fa-solid fa-xmark"></i>
891
891
<i class="fa-regular fa-phone-arrow-down-left"></i>
892
892
</div>
893
<div class="content" id="gpt_${message_id}">
893
<div class="content">
894
894
<div class="provider" data-provider="${provider}"></div>
895
895
<div class="content_inner"><span class="cursor"></span></div>
896
896
<div class="count"></div>
@@ -908,7 +908,7 @@ const ask_gpt = async (message_id, message_index = -1, regenerate = false, provi
908
908
909
909
controller_storage[message_id] = new AbortController();
910
910
911
let content_el = document.getElementById(`gpt_${message_id}`)
911
let content_el = message_el.querySelector('.content');
912
912
let content_map = content_storage[message_id] = {
913
913
container: message_el,
914
914
content: content_el,
@@ -1986,7 +1986,7 @@ async function on_api() {
1986
1986
console.log("pressed enter");
1987
1987
prompt_lock = true;
1988
1988
setTimeout(()=>prompt_lock=false, 3000);
1989
await handle_ask();
1989
await handle_ask(!do_enter);
1990
1990
} else {
1991
1991
messageInput.style.height = messageInput.scrollHeight + "px";
1992
1992
}
@@ -2777,7 +2777,9 @@ if (SpeechRecognition) {
2777
2777
buffer = "";
2778
2778
};
2779
2779
recognition.onend = function() {
2780
messageInput.value = `${startValue ? startValue + "\n" : ""}${buffer}`;
2780
if (buffer) {
2781
messageInput.value = `${startValue ? startValue + "\n" : ""}${buffer}`;
2782
}
2781
2783
if (microLabel.classList.contains("recognition")) {
2782
2784
recognition.start();
2783
2785
} else {
@@ -361,7 +361,7 @@ class ProviderModelMixin:
361
361
model = cls.model_aliases[model]
362
362
else:
363
363
if model not in cls.get_models(**kwargs) and cls.models:
364
raise ModelNotSupportedError(f"Model is not supported: {model} in: {cls.__name__}")
364
raise ModelNotSupportedError(f"Model is not supported: {model} in: {cls.__name__} Valid models: {cls.models}")
365
365
cls.last_model = model
366
366
debug.last_model = model
367
367
return model
@@ -373,9 +373,11 @@ class RaiseErrorMixin():
373
373
if "error_message" in data:
374
374
raise ResponseError(data["error_message"])
375
375
elif "error" in data:
376
if "code" in data["error"]:
376
if isinstance(data["error"], str):
377
raise ResponseError(data["error"])
378
elif "code" in data["error"]:
377
379
raise ResponseError("\n".join(
378
[e for e in [f'Error {data["error"]["code"]}: {data["error"]["message"]}', data["error"].get("failed_generation")] if e is not None]
380
[e for e in [f'Error {data["error"]["code"]}:{data["error"]["message"]}', data["error"].get("failed_generation")] if e is not None]
379
381
))
380
382
elif "message" in data["error"]:
381
383
raise ResponseError(data["error"]["message"])
@@ -23,10 +23,20 @@ def is_openai(text: str) -> bool:
23
23
async def raise_for_status_async(response: Union[StreamResponse, ClientResponse], message: str = None):
24
24
if response.ok:
25
25
return
26
text = await response.text()
26
text = (await response.text()).strip()
27
27
if message is None:
28
is_html = response.headers.get("content-type", "").startswith("text/html") or text.startswith("<!DOCTYPE")
29
message = "HTML content" if is_html else text
28
content_type = response.headers.get("content-type", "")
29
if content_type.startswith("application/json"):
30
try:
31
data = await response.json()
32
message = data.get("error")
33
if isinstance(message, dict):
34
message = data.get("message")
35
except Exception:
36
pass
37
else:
38
is_html = content_type.startswith("text/html") or text.startswith("<!DOCTYPE")
39
message = "HTML content" if is_html else text
30
40
if message == "HTML content":
31
41
if response.status == 520:
32
42
message = "Unknown error (Cloudflare)"
@@ -0,0 +1,41 @@
1
from __future__ import annotations
2
3
from typing import Any, Dict
4
from langchain_community.chat_models import openai
5
from langchain_community.chat_models.openai import convert_message_to_dict
6
from pydantic import Field
7
from g4f.client import AsyncClient, Client
8
from g4f.client.stubs import ChatCompletionMessage
9
10
def new_convert_message_to_dict(message: openai.BaseMessage) -> dict:
11
message_dict: Dict[str, Any]
12
if isinstance(message, ChatCompletionMessage):
13
message_dict = {"role": message.role, "content": message.content}
14
if message.tool_calls is not None:
15
message_dict["tool_calls"] = [{
16
"id": tool_call.id,
17
"type": tool_call.type,
18
"function": tool_call.function
19
} for tool_call in message.tool_calls]
20
if message_dict["content"] == "":
21
message_dict["content"] = None
22
else:
23
message_dict = convert_message_to_dict(message)
24
return message_dict
25
26
openai.convert_message_to_dict = new_convert_message_to_dict
27
28
class ChatAI(openai.ChatOpenAI):
29
model_name: str = Field(default="gpt-4o", alias="model")
30
31
@classmethod
32
def validate_environment(cls, values: dict) -> dict:
33
client_params = {
34
"api_key": values["g4f_api_key"] if "g4f_api_key" in values else None,
35
"provider": values["provider"] if "provider" in values else None,
36
}
37
values["client"] = Client(**client_params).chat.completions
38
values["async_client"] = AsyncClient(
39
**client_params
40
).chat.completions
41
return values
@@ -10,7 +10,7 @@ from typing import Optional, Callable, AsyncIterator
10
10
from ..typing import Messages
11
11
from ..providers.helper import filter_none
12
12
from ..providers.asyncio import to_async_iterator
13
from ..providers.response import Reasoning, FinishReason
13
from ..providers.response import Reasoning, FinishReason, Sources
14
14
from ..providers.types import ProviderType
15
15
from ..cookies import get_cookies_dir
16
16
from .web_search import do_search, get_search_message
@@ -208,6 +208,8 @@ def iter_run_tools(
208
208
sources = None
209
209
yield chunk
210
210
continue
211
elif isinstance(chunk, Sources):
212
sources = None
211
213
if not isinstance(chunk, str):
212
214
yield chunk
213
215
continue