返回提交历史
Modified
g4f/api/__init__.py
+63
-136
Modified
g4f/client.py
+24
-13
Modified
g4f/stubs.py
+72
-13
XFEstudio/gpt4free
Use new client in inter api
74397096
代码差异
3 个文件
+159
-162
@@ -1,21 +1,27 @@
1
import ast
2
1
import logging
3
import time
4
2
import json
5
import random
6
import string
7
3
import uvicorn
8
4
import nest_asyncio
9
5
10
6
from fastapi import FastAPI, Response, Request
11
from fastapi.responses import StreamingResponse
12
from typing import List, Union, Any, Dict, AnyStr
13
#from ._tokenizer import tokenize
7
from fastapi.responses import StreamingResponse, RedirectResponse, HTMLResponse, JSONResponse
8
from pydantic import BaseModel
9
from typing import List
14
10
15
11
import g4f
16
from .. import debug
17
18
debug.logging = True
12
import g4f.debug
13
from g4f.client import Client
14
from g4f.typing import Messages
15
16
class ChatCompletionsConfig(BaseModel):
17
messages: Messages
18
model: str
19
provider: str | None
20
stream: bool = False
21
temperature: float | None
22
max_tokens: int = None
23
stop: list[str] | str | None
24
access_token: str | None
19
25
20
26
class Api:
21
27
def __init__(self, engine: g4f, debug: bool = True, sentry: bool = False,
@@ -25,169 +31,82 @@ class Api:
25
31
self.sentry = sentry
26
32
self.list_ignored_providers = list_ignored_providers
27
33
28
self.app = FastAPI()
34
if debug:
35
g4f.debug.logging = True
36
self.client = Client()
37
29
38
nest_asyncio.apply()
39
self.app = FastAPI()
30
40
31
JSONObject = Dict[AnyStr, Any]
32
JSONArray = List[Any]
33
JSONStructure = Union[JSONArray, JSONObject]
41
self.routes()
34
42
43
def routes(self):
35
44
@self.app.get("/")
36
45
async def read_root():
37
return Response(content=json.dumps({"info": "g4f API"}, indent=4), media_type="application/json")
46
return RedirectResponse("/v1", 302)
38
47
39
48
@self.app.get("/v1")
40
49
async def read_root_v1():
41
return Response(content=json.dumps({"info": "Go to /v1/chat/completions or /v1/models."}, indent=4), media_type="application/json")
50
return HTMLResponse('g4f API: Go to '
51
'<a href="/v1/chat/completions">chat/completions</a> '
52
'or <a href="/v1/models">models</a>.')
42
53
43
54
@self.app.get("/v1/models")
44
55
async def models():
45
model_list = []
46
for model in g4f.Model.__all__():
47
model_info = (g4f.ModelUtils.convert[model])
48
model_list.append({
49
'id': model,
56
model_list = dict(
57
(model, g4f.ModelUtils.convert[model])
58
for model in g4f.Model.__all__()
59
)
60
model_list = [{
61
'id': model_id,
50
62
'object': 'model',
51
63
'created': 0,
52
'owned_by': model_info.base_provider}
53
)
54
return Response(content=json.dumps({
55
'object': 'list',
56
'data': model_list}, indent=4), media_type="application/json")
64
'owned_by': model.base_provider
65
} for model_id, model in model_list.items()]
66
return JSONResponse(model_list)
57
67
58
68
@self.app.get("/v1/models/{model_name}")
59
69
async def model_info(model_name: str):
60
70
try:
61
model_info = (g4f.ModelUtils.convert[model_name])
62
63
return Response(content=json.dumps({
71
model_info = g4f.ModelUtils.convert[model_name]
72
return JSONResponse({
64
73
'id': model_name,
65
74
'object': 'model',
66
75
'created': 0,
67
76
'owned_by': model_info.base_provider
68
}, indent=4), media_type="application/json")
77
})
69
78
except:
70
return Response(content=json.dumps({"error": "The model does not exist."}, indent=4), media_type="application/json")
79
return JSONResponse({"error": "The model does not exist."})
71
80
72
81
@self.app.post("/v1/chat/completions")
73
async def chat_completions(request: Request, item: JSONStructure = None):
74
item_data = {
75
'model': 'gpt-3.5-turbo',
76
'stream': False,
77
}
78
79
# item contains byte keys, and dict.get suppresses error
80
item_data.update({
81
key.decode('utf-8') if isinstance(key, bytes) else key: str(value)
82
for key, value in (item or {}).items()
83
})
84
# messages is str, need dict
85
if isinstance(item_data.get('messages'), str):
86
item_data['messages'] = ast.literal_eval(item_data.get('messages'))
87
88
model = item_data.get('model')
89
stream = True if item_data.get("stream") == "True" else False
90
messages = item_data.get('messages')
91
provider = item_data.get('provider', '').replace('g4f.Provider.', '')
92
provider = provider if provider and provider != "Auto" else None
93
temperature = item_data.get('temperature')
94
82
async def chat_completions(config: ChatCompletionsConfig = None, request: Request = None, provider: str = None):
95
83
try:
96
response = g4f.ChatCompletion.create(
97
model=model,
98
stream=stream,
99
messages=messages,
100
temperature = temperature,
101
provider = provider,
84
config.provider = provider if config.provider is None else config.provider
85
if config.access_token is None and request is not None:
86
auth_header = request.headers.get("Authorization")
87
if auth_header is not None:
88
config.access_token = auth_header.split(None, 1)[-1]
89
90
response = self.client.chat.completions.create(
91
**dict(config),
102
92
ignored=self.list_ignored_providers
103
93
)
104
94
except Exception as e:
105
95
logging.exception(e)
106
content = json.dumps({
107
"error": {"message": f"An error occurred while generating the response:\n{e}"},
108
"model": model,
109
"provider": g4f.get_last_provider(True)
110
})
111
return Response(content=content, status_code=500, media_type="application/json")
112
completion_id = ''.join(random.choices(string.ascii_letters + string.digits, k=28))
113
completion_timestamp = int(time.time())
114
115
if not stream:
116
#prompt_tokens, _ = tokenize(''.join([message['content'] for message in messages]))
117
#completion_tokens, _ = tokenize(response)
118
119
json_data = {
120
'id': f'chatcmpl-{completion_id}',
121
'object': 'chat.completion',
122
'created': completion_timestamp,
123
'model': model,
124
'provider': g4f.get_last_provider(True),
125
'choices': [
126
{
127
'index': 0,
128
'message': {
129
'role': 'assistant',
130
'content': response,
131
},
132
'finish_reason': 'stop',
133
}
134
],
135
'usage': {
136
'prompt_tokens': 0, #prompt_tokens,
137
'completion_tokens': 0, #completion_tokens,
138
'total_tokens': 0, #prompt_tokens + completion_tokens,
139
},
140
}
141
142
return Response(content=json.dumps(json_data, indent=4), media_type="application/json")
96
return Response(content=format_exception(e, config), status_code=500, media_type="application/json")
97
98
if not config.stream:
99
return JSONResponse(response.to_json())
143
100
144
101
def streaming():
145
102
try:
146
103
for chunk in response:
147
completion_data = {
148
'id': f'chatcmpl-{completion_id}',
149
'object': 'chat.completion.chunk',
150
'created': completion_timestamp,
151
'model': model,
152
'provider': g4f.get_last_provider(True),
153
'choices': [
154
{
155
'index': 0,
156
'delta': {
157
'role': 'assistant',
158
'content': chunk,
159
},
160
'finish_reason': None,
161
}
162
],
163
}
164
yield f'data: {json.dumps(completion_data)}\n\n'
165
time.sleep(0.03)
166
end_completion_data = {
167
'id': f'chatcmpl-{completion_id}',
168
'object': 'chat.completion.chunk',
169
'created': completion_timestamp,
170
'model': model,
171
'provider': g4f.get_last_provider(True),
172
'choices': [
173
{
174
'index': 0,
175
'delta': {},
176
'finish_reason': 'stop',
177
}
178
],
179
}
180
yield f'data: {json.dumps(end_completion_data)}\n\n'
104
yield f"data: {json.dumps(chunk.to_json())}\n\n"
181
105
except GeneratorExit:
182
106
pass
183
107
except Exception as e:
184
108
logging.exception(e)
185
content = json.dumps({
186
"error": {"message": f"An error occurred while generating the response:\n{e}"},
187
"model": model,
188
"provider": g4f.get_last_provider(True),
189
})
190
yield f'data: {content}'
109
yield f'data: {format_exception(e, config)}'
191
110
192
111
return StreamingResponse(streaming(), media_type="text/event-stream")
193
112
@@ -198,3 +117,11 @@ class Api:
198
117
def run(self, ip):
199
118
split_ip = ip.split(":")
200
119
uvicorn.run(app=self.app, host=split_ip[0], port=int(split_ip[1]), use_colors=False)
120
121
def format_exception(e: Exception, config: ChatCompletionsConfig) -> str:
122
last_provider = g4f.get_last_provider(True)
123
return json.dumps({
124
"error": {"message": f"ChatCompletionsError: {e.__class__.__name__}: {e}"},
125
"model": last_provider.get("model") if last_provider else config.model,
126
"provider": last_provider.get("name") if last_provider else config.provider
127
})
@@ -2,6 +2,9 @@ from __future__ import annotations
2
2
3
3
import re
4
4
import os
5
import time
6
import random
7
import string
5
8
6
9
from .stubs import ChatCompletion, ChatCompletionChunk, Image, ImagesResponse
7
10
from .typing import Union, Generator, Messages, ImageType
@@ -10,10 +13,11 @@ from .image import ImageResponse as ImageProviderResponse
10
13
from .Provider.BingCreateImages import BingCreateImages
11
14
from .Provider.needs_auth import Gemini, OpenaiChat
12
15
from .errors import NoImageResponseError
13
from . import get_model_and_provider
16
from . import get_model_and_provider, get_last_provider
14
17
15
18
ImageProvider = Union[BaseProvider, object]
16
19
Proxies = Union[dict, str]
20
IterResponse = Generator[ChatCompletion | ChatCompletionChunk, None, None]
17
21
18
22
def read_json(text: str) -> dict:
19
23
"""
@@ -31,18 +35,16 @@ def read_json(text: str) -> dict:
31
35
return text
32
36
33
37
def iter_response(
34
response: iter,
38
response: iter[str],
35
39
stream: bool,
36
40
response_format: dict = None,
37
41
max_tokens: int = None,
38
42
stop: list = None
39
) -> Generator:
43
) -> IterResponse:
40
44
content = ""
41
45
finish_reason = None
42
last_chunk = None
46
completion_id = ''.join(random.choices(string.ascii_letters + string.digits, k=28))
43
47
for idx, chunk in enumerate(response):
44
if last_chunk is not None:
45
yield ChatCompletionChunk(last_chunk, finish_reason)
46
48
content += str(chunk)
47
49
if max_tokens is not None and idx + 1 >= max_tokens:
48
50
finish_reason = "length"
@@ -63,16 +65,25 @@ def iter_response(
63
65
if first != -1:
64
66
finish_reason = "stop"
65
67
if stream:
66
last_chunk = chunk
68
yield ChatCompletionChunk(chunk, None, completion_id, int(time.time()))
67
69
if finish_reason is not None:
68
70
break
69
if last_chunk is not None:
70
yield ChatCompletionChunk(last_chunk, finish_reason)
71
if not stream:
71
finish_reason = "stop" if finish_reason is None else finish_reason
72
if stream:
73
yield ChatCompletionChunk(None, finish_reason, completion_id, int(time.time()))
74
else:
72
75
if response_format is not None and "type" in response_format:
73
76
if response_format["type"] == "json_object":
74
77
content = read_json(content)
75
yield ChatCompletion(content, finish_reason)
78
yield ChatCompletion(content, finish_reason, completion_id, int(time.time()))
79
80
def iter_append_model_and_provider(response: IterResponse) -> IterResponse:
81
last_provider = None
82
for chunk in response:
83
last_provider = get_last_provider(True) if last_provider is None else last_provider
84
chunk.model = last_provider.get("model")
85
chunk.provider = last_provider.get("name")
86
yield chunk
76
87
77
88
class Client():
78
89
proxies: Proxies = None
@@ -113,7 +124,7 @@ class Completions():
113
124
stream: bool = False,
114
125
response_format: dict = None,
115
126
max_tokens: int = None,
116
stop: Union[list. str] = None,
127
stop: list[str] | str = None,
117
128
**kwargs
118
129
) -> Union[ChatCompletion, Generator[ChatCompletionChunk]]:
119
130
if max_tokens is not None:
@@ -128,7 +139,7 @@ class Completions():
128
139
)
129
140
response = provider.create_completion(model, messages, stream=stream, proxy=self.client.get_proxy(), **kwargs)
130
141
stop = [stop] if isinstance(stop, str) else stop
131
response = iter_response(response, stream, response_format, max_tokens, stop)
142
response = iter_append_model_and_provider(iter_response(response, stream, response_format, max_tokens, stop))
132
143
return response if stream else next(response)
133
144
134
145
class Chat():
@@ -2,34 +2,93 @@
2
2
from __future__ import annotations
3
3
4
4
class Model():
5
def __getitem__(self, item):
6
return getattr(self, item)
5
...
7
6
8
7
class ChatCompletion(Model):
9
def __init__(self, content: str, finish_reason: str):
10
self.choices = [ChatCompletionChoice(ChatCompletionMessage(content, finish_reason))]
8
def __init__(
9
self,
10
content: str,
11
finish_reason: str,
12
completion_id: str = None,
13
created: int = None
14
):
15
self.id: str = f"chatcmpl-{completion_id}" if completion_id else None
16
self.object: str = "chat.completion"
17
self.created: int = created
18
self.model: str = None
19
self.provider: str = None
20
self.choices = [ChatCompletionChoice(ChatCompletionMessage(content), finish_reason)]
21
self.usage: dict[str, int] = {
22
"prompt_tokens": 0, #prompt_tokens,
23
"completion_tokens": 0, #completion_tokens,
24
"total_tokens": 0, #prompt_tokens + completion_tokens,
25
}
26
27
def to_json(self):
28
return {
29
**self.__dict__,
30
"choices": [choice.to_json() for choice in self.choices]
31
}
11
32
12
33
class ChatCompletionChunk(Model):
13
def __init__(self, content: str, finish_reason: str):
14
self.choices = [ChatCompletionDeltaChoice(ChatCompletionDelta(content, finish_reason))]
34
def __init__(
35
self,
36
content: str,
37
finish_reason: str,
38
completion_id: str = None,
39
created: int = None
40
):
41
self.id: str = f"chatcmpl-{completion_id}" if completion_id else None
42
self.object: str = "chat.completion.chunk"
43
self.created: int = created
44
self.model: str = None
45
self.provider: str = None
46
self.choices = [ChatCompletionDeltaChoice(ChatCompletionDelta(content), finish_reason)]
47
48
def to_json(self):
49
return {
50
**self.__dict__,
51
"choices": [choice.to_json() for choice in self.choices]
52
}
15
53
16
54
class ChatCompletionMessage(Model):
17
def __init__(self, content: str, finish_reason: str):
55
def __init__(self, content: str | None):
56
self.role = "assistant"
18
57
self.content = content
19
self.finish_reason = finish_reason
58
59
def to_json(self):
60
return self.__dict__
20
61
21
62
class ChatCompletionChoice(Model):
22
def __init__(self, message: ChatCompletionMessage):
63
def __init__(self, message: ChatCompletionMessage, finish_reason: str):
64
self.index = 0
23
65
self.message = message
66
self.finish_reason = finish_reason
67
68
def to_json(self):
69
return {
70
**self.__dict__,
71
"message": self.message.to_json()
72
}
24
73
25
74
class ChatCompletionDelta(Model):
26
def __init__(self, content: str, finish_reason: str):
27
self.content = content
28
self.finish_reason = finish_reason
75
def __init__(self, content: str | None):
76
if content is not None:
77
self.content = content
78
79
def to_json(self):
80
return self.__dict__
29
81
30
82
class ChatCompletionDeltaChoice(Model):
31
def __init__(self, delta: ChatCompletionDelta):
83
def __init__(self, delta: ChatCompletionDelta, finish_reason: str | None):
32
84
self.delta = delta
85
self.finish_reason = finish_reason
86
87
def to_json(self):
88
return {
89
**self.__dict__,
90
"delta": self.delta.to_json()
91
}
33
92
34
93
class Image(Model):
35
94
url: str