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

XFEstudio/gpt4free

Add langchain integration

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

代码差异

11 个文件 +133 -88
Modified g4f/Provider/PollinationsAI.py +6 -6
@@ -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()
Modified g4f/Provider/hf/__init__.py +1 -1
@@ -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):
Modified g4f/Provider/needs_auth/OpenaiChat.py +36 -54
@@ -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 """
Modified g4f/client/__init__.py +14 -11
@@ -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
Modified g4f/client/stubs.py +2 -2
@@ -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
Modified g4f/gui/client/static/css/style.css +5 -2
@@ -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 {
Modified g4f/gui/client/static/js/chat.v1.js +7 -5
@@ -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 {
Modified g4f/providers/base_provider.py +5 -3
@@ -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"])
Modified g4f/requests/raise_for_status.py +13 -3
@@ -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)"
Added g4f/tools/langchain.py +41 -0
@@ -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
Modified g4f/tools/run_tools.py +3 -1
@@ -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