返回提交历史
Modified
docs/interference-api.md
+21
-11
Modified
g4f/Provider/ARTA.py
+1
-1
Modified
g4f/Provider/hf/HuggingFaceAPI.py
+11
-11
Modified
g4f/Provider/hf/__init__.py
+1
-1
Modified
g4f/Provider/needs_auth/OpenaiChat.py
+0
-2
Modified
g4f/Provider/template/OpenaiTemplate.py
+16
-22
Modified
g4f/api/__init__.py
+5
-10
Modified
g4f/api/stubs.py
+7
-0
Modified
g4f/cli.py
+3
-1
Modified
g4f/client/__init__.py
+5
-4
Modified
g4f/client/stubs.py
+4
-2
Modified
g4f/gui/client/demo.html
+4
-2
Added
g4f/gui/client/qrcode.html
+127
-0
Modified
g4f/gui/client/static/js/chat.v1.js
+33
-14
Modified
g4f/gui/server/backend_api.py
+28
-5
Modified
g4f/requests/aiohttp.py
+13
-0
Modified
g4f/requests/curl_cffi.py
+12
-0
XFEstudio/gpt4free
Update docs: Using the OpenAI Library Add sse function to requests sessions Small improvments in OpenaiChat and ARTA provider
8f6efd53
代码差异
17 个文件
+291
-86
@@ -8,7 +8,7 @@
8
8
- [From Repository](#from-repository)
9
9
- [Using the Interference API](#using-the-interference-api)
10
10
- [Basic Usage](#basic-usage)
11
- [With OpenAI Library](#with-openai-library)
11
- [Using the OpenAI Library](#using-the-openai-library)
12
12
- [With Requests Library](#with-requests-library)
13
13
- [Selecting a Provider](#selecting-a-provider)
14
14
- [Key Points](#key-points)
@@ -95,35 +95,45 @@ curl -X POST "http://localhost:1337/v1/images/generate" \
95
95
}'
96
96
```
97
97
98
---
99
100
### Using the OpenAI Library
98
101
99
### With OpenAI Library
102
**To utilize the Inference API with the OpenAI Python library, you can specify the `base_url` to point to your endpoint:**
100
103
101
**You can use the Interference API with the OpenAI Python library by changing the `base_url`:**
102
104
```python
103
105
from openai import OpenAI
104
106
107
# Initialize the OpenAI client
105
108
client = OpenAI(
106
api_key="secret",
107
base_url="http://localhost:1337/v1"
109
api_key="secret", # Set an API key (use "secret" if your provider doesn't require one)
110
base_url="http://localhost:1337/v1" # Point to your local or custom API endpoint
108
111
)
109
112
113
# Create a chat completion request
110
114
response = client.chat.completions.create(
111
model="gpt-4o-mini",
112
messages=[{"role": "user", "content": "Write a poem about a tree"}],
113
stream=True,
115
model="gpt-4o-mini", # Specify the model to use
116
messages=[{"role": "user", "content": "Write a poem about a tree"}], # Define the input message
117
stream=True, # Enable streaming for real-time responses
114
118
)
115
119
120
# Handle the response
116
121
if isinstance(response, dict):
117
# Not streaming
122
# Non-streaming response
118
123
print(response.choices[0].message.content)
119
124
else:
120
# Streaming
125
# Streaming response
121
126
for token in response:
122
127
content = token.choices[0].delta.content
123
128
if content is not None:
124
129
print(content, end="", flush=True)
125
126
130
```
131
132
**Notes:**
133
- The `api_key` is required by the OpenAI Python library. If your provider does not require an API key, you can set it to `"secret"`. This value will be ignored by providers in G4F.
134
- Replace `"http://localhost:1337/v1"` with the appropriate URL for your custom or local inference API.
135
136
---
127
137
128
138
129
139
### With Requests Library
@@ -16,7 +16,7 @@ from .base_provider import AsyncGeneratorProvider, ProviderModelMixin
16
16
from .helper import format_image_prompt
17
17
18
18
class ARTA(AsyncGeneratorProvider, ProviderModelMixin):
19
url = "https://img-gen-prod.ai-arta.com"
19
url = "https://ai-arta.com"
20
20
auth_url = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/signupNewUser?key=AIzaSyB3-71wG0fIt0shj0ee4fvx1shcjJHGrrQ"
21
21
token_refresh_url = "https://securetoken.googleapis.com/v1/token?key=AIzaSyB3-71wG0fIt0shj0ee4fvx1shcjJHGrrQ"
22
22
image_generation_url = "https://img-gen-prod.ai-arta.com/api/v1/text2image"
@@ -92,17 +92,17 @@ class HuggingFaceAPI(OpenaiTemplate):
92
92
model = provider_mapping[provider_key]["providerId"]
93
93
yield ProviderInfo(**{**cls.get_dict(), "label": f"HuggingFace ({provider_key})"})
94
94
break
95
start = calculate_lenght(messages)
96
if start > max_inputs_lenght:
97
if len(messages) > 6:
98
messages = messages[:3] + messages[-3:]
99
if calculate_lenght(messages) > max_inputs_lenght:
100
last_user_message = [{"role": "user", "content": get_last_user_message(messages)}]
101
if len(messages) > 2:
102
messages = [m for m in messages if m["role"] == "system"] + last_user_message
103
if len(messages) > 1 and calculate_lenght(messages) > max_inputs_lenght:
104
messages = last_user_message
105
debug.log(f"Messages trimmed from: {start} to: {calculate_lenght(messages)}")
95
# start = calculate_lenght(messages)
96
# if start > max_inputs_lenght:
97
# if len(messages) > 6:
98
# messages = messages[:3] + messages[-3:]
99
# if calculate_lenght(messages) > max_inputs_lenght:
100
# last_user_message = [{"role": "user", "content": get_last_user_message(messages)}]
101
# if len(messages) > 2:
102
# messages = [m for m in messages if m["role"] == "system"] + last_user_message
103
# if len(messages) > 1 and calculate_lenght(messages) > max_inputs_lenght:
104
# messages = last_user_message
105
# debug.log(f"Messages trimmed from: {start} to: {calculate_lenght(messages)}")
106
106
async for chunk in super().create_async_generator(model, messages, api_base=api_base, api_key=api_key, max_tokens=max_tokens, media=media, **kwargs):
107
107
yield chunk
108
108
@@ -36,7 +36,7 @@ class HuggingFace(AsyncGeneratorProvider, ProviderModelMixin):
36
36
messages: Messages,
37
37
**kwargs
38
38
) -> AsyncResult:
39
if "tools" not in kwargs and "images" not in kwargs and random.random() >= 0.5:
39
if "tools" not in kwargs and "media" not in kwargs and 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):
@@ -465,8 +465,6 @@ class OpenaiChat(AsyncAuthedProvider, ProviderModelMixin):
465
465
if not line.startswith(b"data: "):
466
466
return
467
467
elif line.startswith(b"data: [DONE]"):
468
if fields.finish_reason is None:
469
fields.finish_reason = "error"
470
468
return
471
469
try:
472
470
line = json.loads(line[6:])
@@ -1,6 +1,5 @@
1
1
from __future__ import annotations
2
2
3
import json
4
3
import requests
5
4
6
5
from ..helper import filter_none, format_image_prompt
@@ -141,7 +140,7 @@ class OpenaiTemplate(AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin
141
140
choice = data["choices"][0]
142
141
if "content" in choice["message"] and choice["message"]["content"]:
143
142
yield choice["message"]["content"].strip()
144
elif "tool_calls" in choice["message"]:
143
if "tool_calls" in choice["message"]:
145
144
yield ToolCalls(choice["message"]["tool_calls"])
146
145
if "usage" in data:
147
146
yield Usage(**data["usage"])
@@ -151,26 +150,21 @@ class OpenaiTemplate(AsyncGeneratorProvider, ProviderModelMixin, RaiseErrorMixin
151
150
elif content_type.startswith("text/event-stream"):
152
151
await raise_for_status(response)
153
152
first = True
154
async for line in response.iter_lines():
155
if line.startswith(b"data: "):
156
chunk = line[6:]
157
if chunk == b"[DONE]":
158
break
159
data = json.loads(chunk)
160
cls.raise_error(data)
161
choice = data["choices"][0]
162
if "content" in choice["delta"] and choice["delta"]["content"]:
163
delta = choice["delta"]["content"]
164
if first:
165
delta = delta.lstrip()
166
if delta:
167
first = False
168
yield delta
169
if "usage" in data and data["usage"]:
170
yield Usage(**data["usage"])
171
if "finish_reason" in choice and choice["finish_reason"] is not None:
172
yield FinishReason(choice["finish_reason"])
173
break
153
async for data in response.sse():
154
cls.raise_error(data)
155
choice = data["choices"][0]
156
if "content" in choice["delta"] and choice["delta"]["content"]:
157
delta = choice["delta"]["content"]
158
if first:
159
delta = delta.lstrip()
160
if delta:
161
first = False
162
yield delta
163
if "usage" in data and data["usage"]:
164
yield Usage(**data["usage"])
165
if "finish_reason" in choice and choice["finish_reason"] is not None:
166
yield FinishReason(choice["finish_reason"])
167
break
174
168
else:
175
169
await raise_for_status(response)
176
170
raise ResponseError(f"Not supported content-type: {content_type}")
@@ -308,7 +308,8 @@ class Api:
308
308
if credentials is not None and credentials.credentials != "secret":
309
309
config.api_key = credentials.credentials
310
310
311
conversation = return_conversation = None
311
conversation = None
312
return_conversation = config.return_conversation
312
313
if conversation is not None:
313
314
conversation = JsonConversation(**conversation)
314
315
return_conversation = True
@@ -637,11 +638,8 @@ def run_api(
637
638
port: int = None,
638
639
bind: str = None,
639
640
debug: bool = False,
640
workers: int = None,
641
641
use_colors: bool = None,
642
reload: bool = False,
643
ssl_keyfile: str = None,
644
ssl_certfile: str = None
642
**kwargs
645
643
) -> None:
646
644
print(f'Starting server... [g4f v-{g4f.version.utils.current_version}]' + (" (debug)" if debug else ""))
647
645
@@ -665,10 +663,7 @@ def run_api(
665
663
f"g4f.api:{method}",
666
664
host=host,
667
665
port=int(port),
668
workers=workers,
669
use_colors=use_colors,
670
666
factory=True,
671
reload=reload,
672
ssl_keyfile=ssl_keyfile,
673
ssl_certfile=ssl_certfile
667
use_colors=use_colors,
668
**filter_none(**kwargs)
674
669
)
@@ -31,6 +31,7 @@ class ChatCompletionsConfig(BaseModel):
31
31
proxy: Optional[str] = None
32
32
conversation_id: Optional[str] = None
33
33
conversation: Optional[dict] = None
34
return_conversation: Optional[bool] = None
34
35
history_disabled: Optional[bool] = None
35
36
timeout: Optional[int] = None
36
37
tool_calls: list = Field(default=[], examples=[[
@@ -43,6 +44,12 @@ class ChatCompletionsConfig(BaseModel):
43
44
}
44
45
]])
45
46
tools: list = None
47
parallel_tool_calls: bool = None
48
tool_choice: Optional[str] = None
49
reasoning_effort: Optional[str] = None
50
logit_bias: Optional[dict] = None
51
modalities: Optional[list[str]] = None
52
audio: Optional[dict] = None
46
53
response_format: Optional[dict] = None
47
54
48
55
class ImageGenerationConfig(BaseModel):
@@ -32,6 +32,7 @@ def get_api_parser():
32
32
33
33
api_parser.add_argument("--ssl-keyfile", type=str, default=None, help="Path to SSL key file for HTTPS.")
34
34
api_parser.add_argument("--ssl-certfile", type=str, default=None, help="Path to SSL certificate file for HTTPS.")
35
api_parser.add_argument("--log-config", type=str, default=None, help="Custom log config.")
35
36
36
37
return api_parser
37
38
@@ -74,7 +75,8 @@ def run_api_args(args):
74
75
use_colors=not args.disable_colors,
75
76
reload=args.reload,
76
77
ssl_keyfile=args.ssl_keyfile,
77
ssl_certfile=args.ssl_certfile
78
ssl_certfile=args.ssl_certfile,
79
log_config=args.log_config,
78
80
)
79
81
80
82
if __name__ == "__main__":
@@ -162,14 +162,15 @@ async def async_iter_response(
162
162
tool_calls = None
163
163
usage = None
164
164
provider: ProviderInfo = None
165
conversation: JsonConversation = None
165
166
166
167
try:
167
168
async for chunk in response:
168
169
if isinstance(chunk, FinishReason):
169
170
finish_reason = chunk.reason
170
171
break
171
elif isinstance(chunk, BaseConversation):
172
yield chunk
172
elif isinstance(chunk, JsonConversation):
173
conversation = chunk
173
174
continue
174
175
elif isinstance(chunk, ToolCalls):
175
176
tool_calls = chunk.get_list()
@@ -228,7 +229,8 @@ async def async_iter_response(
228
229
content, finish_reason, completion_id, int(time.time()), usage=usage,
229
230
**filter_none(
230
231
tool_calls=[ToolCallModel.model_construct(**tool_call) for tool_call in tool_calls]
231
) if tool_calls is not None else {}
232
) if tool_calls is not None else {},
233
conversation=None if conversation is None else conversation.get_dict()
232
234
)
233
235
if provider is not None:
234
236
chat_completion.provider = provider.name
@@ -242,7 +244,6 @@ async def async_iter_append_model_and_provider(
242
244
last_model: str,
243
245
last_provider: ProviderType
244
246
) -> AsyncChatCompletionResponseType:
245
last_provider = None
246
247
try:
247
248
if isinstance(last_provider, BaseRetryProvider):
248
249
async for chunk in response:
@@ -132,6 +132,7 @@ class ChatCompletion(BaseModel):
132
132
provider: Optional[str]
133
133
choices: list[ChatCompletionChoice]
134
134
usage: UsageModel
135
conversation: dict
135
136
136
137
@classmethod
137
138
def model_construct(
@@ -141,7 +142,8 @@ class ChatCompletion(BaseModel):
141
142
completion_id: str = None,
142
143
created: int = None,
143
144
tool_calls: list[ToolCallModel] = None,
144
usage: UsageModel = None
145
usage: UsageModel = None,
146
conversation: dict = None
145
147
):
146
148
return super().model_construct(
147
149
id=f"chatcmpl-{completion_id}" if completion_id else None,
@@ -153,7 +155,7 @@ class ChatCompletion(BaseModel):
153
155
ChatCompletionMessage.model_construct(content, tool_calls),
154
156
finish_reason,
155
157
)],
156
**filter_none(usage=usage)
158
**filter_none(usage=usage, conversation=conversation)
157
159
)
158
160
159
161
class ChatCompletionDelta(BaseModel):
@@ -298,10 +298,12 @@
298
298
299
299
let oauthResult = localStorage.getItem("oauth");
300
300
if (oauthResult) {
301
let user;
301
302
try {
302
303
oauthResult = JSON.parse(oauthResult);
303
304
user = await hub.whoAmI({accessToken: oauthResult.accessToken});
304
} catch {
305
} catch (e) {
306
console.error(e);
305
307
oauthResult = null;
306
308
localStorage.removeItem("oauth");
307
309
localStorage.removeItem("HuggingFace-api_key");
@@ -365,7 +367,7 @@
365
367
return;
366
368
}
367
369
const lower = data.prompt.toLowerCase();
368
const tags = ["nsfw", "timeline", "feet", "blood", "soap", "orally", "heel", "latex", "bathroom", "boobs", "charts", " text ", "gel", "logo", "infographic", "warts", " bra ", "prostitute", "curvy", "breasts", "written", "bodies", "naked", "classroom", "malone", "dirty", "shoes", "shower", "banner", "fat", "nipples", "couple", "sexual", "sandal", "supplier", "overlord", "succubus", "platinum", "cracy", "crazy", "hemale", "oprah", "lamic", "ropes", "cables", "wires", "dirty", "messy", "cluttered", "chaotic", "disorganized", "disorderly", "untidy", "unorganized", "unorderly", "unsystematic", "disarranged", "disarrayed", "disheveled", "disordered", "jumbled", "muddled", "scattered", "shambolic", "sloppy", "unkept", "unruly"];
370
const tags = ["nsfw", "timeline", "feet", "blood", "soap", "orally", "heel", "latex", "bathroom", "boobs", "charts", "gel", "logo", "infographic", "warts", " bra ", "prostitute", "curvy", "breasts", "written", "bodies", "naked", "classroom", "malone", "dirty", "shoes", "shower", "banner", "fat", "nipples", "couple", "sexual", "sandal", "supplier", "overlord", "succubus", "platinum", "cracy", "crazy", "hemale", "oprah", "lamic", "ropes", "cables", "wires", "dirty", "messy", "cluttered", "chaotic", "disorganized", "disorderly", "untidy", "unorganized", "unorderly", "unsystematic", "disarranged", "disarrayed", "disheveled", "disordered", "jumbled", "muddled", "scattered", "shambolic", "sloppy", "unkept", "unruly"];
369
371
for (i in tags) {
370
372
if (lower.indexOf(tags[i]) != -1) {
371
373
console.log("Skipping image with tag: " + tags[i]);