MeUtils 2025.4.9.10.39.6__py3-none-any.whl → 2025.4.11.17.37.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {MeUtils-2025.4.9.10.39.6.dist-info → MeUtils-2025.4.11.17.37.3.dist-info}/METADATA +266 -266
- {MeUtils-2025.4.9.10.39.6.dist-info → MeUtils-2025.4.11.17.37.3.dist-info}/RECORD +40 -37
- examples/_openaisdk/openai_google.py +38 -31
- meutils/apis/{google_apis → google}/audios.py +5 -2
- meutils/apis/google/chat.py +372 -0
- meutils/apis/google/files.py +29 -0
- meutils/apis/{google_apis → google}/google2openai.py +8 -4
- meutils/apis/google/images.py +27 -0
- meutils/apis/{google_apis → google}/search.py +7 -5
- meutils/apis/jimeng/images.py +41 -8
- meutils/apis/search/metaso.py +2 -2
- meutils/apis/siliconflow/images.py +5 -3
- meutils/caches/acache.py +1 -1
- meutils/common.py +1 -0
- meutils/data/VERSION +1 -1
- meutils/decorators/catch.py +0 -8
- meutils/decorators/common.py +86 -19
- meutils/decorators/contextmanagers.py +103 -14
- meutils/decorators/demo.py +76 -14
- meutils/io/files_utils.py +2 -3
- meutils/io/openai_files.py +11 -6
- meutils/llm/check_utils.py +20 -1
- meutils/llm/openai_polling/__init__.py +11 -0
- meutils/llm/openai_polling/chat.py +45 -0
- meutils/{apis/google_apis → llm/openai_polling}/images.py +4 -2
- meutils/llm/openai_utils/common.py +1 -1
- meutils/notice/feishu.py +5 -2
- meutils/schemas/image_types.py +26 -3
- meutils/schemas/oneapi/common.py +12 -0
- meutils/schemas/openai_types.py +10 -0
- meutils/serving/fastapi/dependencies/__init__.py +4 -1
- meutils/serving/fastapi/dependencies/auth.py +10 -6
- meutils/serving/fastapi/exceptions/http_error.py +2 -2
- meutils/str_utils/__init__.py +30 -2
- meutils/apis/google_apis/common.py +0 -243
- meutils/apis/google_apis/files.py +0 -19
- {MeUtils-2025.4.9.10.39.6.dist-info → MeUtils-2025.4.11.17.37.3.dist-info}/LICENSE +0 -0
- {MeUtils-2025.4.9.10.39.6.dist-info → MeUtils-2025.4.11.17.37.3.dist-info}/WHEEL +0 -0
- {MeUtils-2025.4.9.10.39.6.dist-info → MeUtils-2025.4.11.17.37.3.dist-info}/entry_points.txt +0 -0
- {MeUtils-2025.4.9.10.39.6.dist-info → MeUtils-2025.4.11.17.37.3.dist-info}/top_level.txt +0 -0
- /meutils/apis/{google_apis → google}/__init__.py +0 -0
- /meutils/apis/{google_apis → google}/gemini_sdk.py +0 -0
meutils/schemas/oneapi/common.py
CHANGED
@@ -386,6 +386,11 @@ MODEL_RATIO = {
|
|
386
386
|
"grok-3-reasoner": 2,
|
387
387
|
"grok-3-deepersearch": 2,
|
388
388
|
|
389
|
+
"grok-3-beta": 1.5,
|
390
|
+
"grok-3-fast-beta": 2.5,
|
391
|
+
"grok-3-mini-beta": 0.15,
|
392
|
+
"grok-3-mini-fast-beta": 0.3,
|
393
|
+
|
389
394
|
# 定制
|
390
395
|
"lingxi-all": 1,
|
391
396
|
|
@@ -681,6 +686,7 @@ MODEL_RATIO = {
|
|
681
686
|
"gemini-2.0-pro": 1.25,
|
682
687
|
"gemini-2.0-pro-exp-02-05": 1.25,
|
683
688
|
"gemini-2.5-pro-exp-03-25": 1.5,
|
689
|
+
"gemini-2.5-pro-preview-03-25": 0.625,
|
684
690
|
|
685
691
|
"gemini-2.0-flash-thinking-exp": 1,
|
686
692
|
"gemini-2.0-flash-thinking-exp-1219": 1,
|
@@ -816,6 +822,11 @@ COMPLETION_RATIO = {
|
|
816
822
|
"grok-3-reasoner": 5,
|
817
823
|
"grok-3-deepersearch": 5,
|
818
824
|
|
825
|
+
"grok-3-beta": 5,
|
826
|
+
"grok-3-fast-beta": 5,
|
827
|
+
"grok-3-mini-beta": 5 / 3,
|
828
|
+
"grok-3-mini-fast-beta": 4 / 0.6,
|
829
|
+
|
819
830
|
"claude-3-5-haiku-20241022": 5,
|
820
831
|
"anthropic/claude-3-5-haiku-20241022:beta": 5,
|
821
832
|
|
@@ -905,6 +916,7 @@ COMPLETION_RATIO = {
|
|
905
916
|
"gemini-2.0-pro": 5,
|
906
917
|
"gemini-2.0-pro-exp-02-05": 5,
|
907
918
|
"gemini-2.5-pro-exp-03-25": 5,
|
919
|
+
"gemini-2.5-pro-preview-03-25": 8,
|
908
920
|
|
909
921
|
"gemma2-9b-it": 4,
|
910
922
|
"gemma2-27b-it": 4,
|
meutils/schemas/openai_types.py
CHANGED
@@ -121,6 +121,14 @@ class CompletionRequest(BaseModel):
|
|
121
121
|
def __init__(self, **kwargs):
|
122
122
|
super().__init__(**kwargs)
|
123
123
|
|
124
|
+
@cached_property
|
125
|
+
def system_instruction(self):
|
126
|
+
# [{'role': 'system', 'content': [{"type": "text", "text": ""}]]
|
127
|
+
|
128
|
+
if message := self.messages and self.messages[0]:
|
129
|
+
if message.get("role") == "system":
|
130
|
+
return str(message.get("content", "")) or None
|
131
|
+
|
124
132
|
@cached_property
|
125
133
|
def last_message(self):
|
126
134
|
return self.messages and self.messages[-1]
|
@@ -159,7 +167,9 @@ class CompletionRequest(BaseModel):
|
|
159
167
|
"""最新一轮的 user url 列表"""
|
160
168
|
content_types = {
|
161
169
|
"image_url",
|
170
|
+
|
162
171
|
"file", "file_url",
|
172
|
+
|
163
173
|
"audio_url", "input_audio",
|
164
174
|
|
165
175
|
"video_url",
|
@@ -33,15 +33,18 @@ async def get_bearer_token(
|
|
33
33
|
return None
|
34
34
|
|
35
35
|
token = auth.credentials
|
36
|
-
if token.startswith('redis:'): #
|
36
|
+
if token.startswith('redis:'): # redis里按序轮询
|
37
37
|
if "feishu.cn" in token:
|
38
38
|
feishu_url = token.removeprefix("redis:")
|
39
|
-
token = await get_next_token(feishu_url) #
|
39
|
+
token = await get_next_token(feishu_url) # todo: 摆脱 feishu
|
40
40
|
|
41
|
-
|
42
|
-
pass
|
41
|
+
# logger.debug(token)
|
43
42
|
|
44
|
-
|
43
|
+
else: # redis:token1,token2
|
44
|
+
tokens = token.removeprefix("redis:").split(',') # todo: 初始化redis
|
45
|
+
token = np.random.choice(tokens)
|
46
|
+
|
47
|
+
elif ',' in token: # 内存里随机轮询
|
45
48
|
token = np.random.choice(token.split(','))
|
46
49
|
|
47
50
|
return token
|
@@ -71,4 +74,5 @@ async def get_bearer_token_for_oneapi(
|
|
71
74
|
if __name__ == '__main__':
|
72
75
|
from meutils.pipe import *
|
73
76
|
|
74
|
-
arun(get_bearer_token(HTTPAuthorizationCredentials(scheme="Bearer",
|
77
|
+
arun(get_bearer_token(HTTPAuthorizationCredentials(scheme="Bearer",
|
78
|
+
credentials="redis:https://xchatllm.feishu.cn/sheets/Bmjtst2f6hfMqFttbhLcdfRJnNf?sheet=rWMtht")))
|
@@ -10,7 +10,7 @@ from starlette.responses import JSONResponse
|
|
10
10
|
|
11
11
|
from fastapi.exceptions import RequestValidationError, HTTPException
|
12
12
|
|
13
|
-
from meutils.notice.feishu import
|
13
|
+
from meutils.notice.feishu import send_message_for_http as send_message
|
14
14
|
|
15
15
|
|
16
16
|
async def http_error_handler(_: Request, exc: HTTPException) -> JSONResponse:
|
@@ -60,7 +60,7 @@ async def chatfire_api_exception_handler(request: Request, exc: Exception):
|
|
60
60
|
)
|
61
61
|
|
62
62
|
# send_message
|
63
|
-
content_detail = f"{traceback.format_exc()}"
|
63
|
+
content_detail = f"{traceback.format_exc()}" # 是不是最后几行就可以了
|
64
64
|
#
|
65
65
|
# from meutils.pipe import logger
|
66
66
|
# logger.debug(content_detail)
|
meutils/str_utils/__init__.py
CHANGED
@@ -11,12 +11,32 @@
|
|
11
11
|
|
12
12
|
from meutils.pipe import *
|
13
13
|
# from meutils.str_utils.translater import translater
|
14
|
+
from meutils.str_utils.regular_expression import parse_url
|
15
|
+
|
14
16
|
from meutils.request_utils.crawler import Crawler
|
15
17
|
from urllib.parse import urlencode, parse_qs, parse_qsl, quote_plus, unquote_plus, urljoin
|
16
18
|
|
17
19
|
query_params2json = lambda q: dict(parse_qsl(q))
|
18
20
|
|
19
21
|
|
22
|
+
def parse_prompt(prompt, only_first_url: bool = True):
|
23
|
+
if not only_first_url and (urls := parse_url(prompt)):
|
24
|
+
prompt = reduce(lambda prompt, url: prompt.replace(url, " "), urls)
|
25
|
+
return urls, prompt
|
26
|
+
|
27
|
+
if prompt.startswith('http') and (prompts := prompt.split(maxsplit=1)): # 单 url
|
28
|
+
if len(prompts) == 2:
|
29
|
+
return prompts
|
30
|
+
else:
|
31
|
+
return prompts + [' ']
|
32
|
+
|
33
|
+
elif "http" in prompt and (urls := parse_url(prompt)):
|
34
|
+
return urls[0], prompt.replace(urls[0], "")
|
35
|
+
|
36
|
+
else:
|
37
|
+
return None, prompt
|
38
|
+
|
39
|
+
|
20
40
|
def json2query_params(js: dict = None, url='') -> str:
|
21
41
|
js = js or {}
|
22
42
|
js = {k: v for k, v in js.items() if v}
|
@@ -200,8 +220,16 @@ if __name__ == '__main__':
|
|
200
220
|
|
201
221
|
# print(query_params2json("act=get&uid=134597&vkey=07A1D82FDDE1E96DB5CEF4EF12C8125F&num=1&time=30&plat=1&re=1&type=0&so=1&ow=1&spl=1&addr=&db=1"))
|
202
222
|
|
203
|
-
print(half2all('⽉月'))
|
204
|
-
print(all2half('⽉月'))
|
223
|
+
# print(half2all('⽉月'))
|
224
|
+
# print(all2half('⽉月'))
|
225
|
+
|
226
|
+
print(parse_prompt(" hi", only_first_url=False))
|
227
|
+
|
228
|
+
print(parse_prompt("https://www.hao123.com/ hi"))
|
229
|
+
print(parse_prompt("https://www.hao123.com/ hi" * 2))
|
230
|
+
print(parse_prompt("hi https://www.hao123.com/ "))
|
231
|
+
|
232
|
+
print(parse_prompt("https://www.hao123.com/ hi" * 2, only_first_url=False))
|
205
233
|
|
206
234
|
# import chardet
|
207
235
|
#
|
@@ -1,243 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python
|
2
|
-
# -*- coding: utf-8 -*-
|
3
|
-
# @Project : AI. @by PyCharm
|
4
|
-
# @File : common
|
5
|
-
# @Time : 2025/4/2 13:03
|
6
|
-
# @Author : betterme
|
7
|
-
# @WeChat : meutils
|
8
|
-
# @Software : PyCharm
|
9
|
-
# @Description : https://ai.google.dev/gemini-api/docs/openai?hl=zh-cn
|
10
|
-
|
11
|
-
# https://googleapis.github.io/python-genai/genai.html#module-genai.models
|
12
|
-
|
13
|
-
|
14
|
-
from meutils.pipe import *
|
15
|
-
from meutils.llm.clients import AsyncOpenAI
|
16
|
-
from meutils.io.files_utils import to_url, to_bytes, guess_mime_type
|
17
|
-
from meutils.str_utils.regular_expression import parse_url
|
18
|
-
|
19
|
-
from meutils.schemas.openai_types import chat_completion, chat_completion_chunk, CompletionRequest, CompletionUsage
|
20
|
-
|
21
|
-
from meutils.config_utils.lark_utils import get_next_token_for_polling
|
22
|
-
from google import genai
|
23
|
-
from google.genai import types
|
24
|
-
from google.genai.types import HttpOptions, GenerateContentConfig, Content, HarmCategory, HarmBlockThreshold, Part
|
25
|
-
from google.genai.types import UserContent, ModelContent
|
26
|
-
|
27
|
-
FEISHU_URL = "https://xchatllm.feishu.cn/sheets/Bmjtst2f6hfMqFttbhLcdfRJnNf?sheet=MJw6hI"
|
28
|
-
|
29
|
-
"""
|
30
|
-
Gemini 1.5 Pro 和 1.5 Flash 最多支持 3,600 个文档页面。文档页面必须采用以下文本数据 MIME 类型之一:
|
31
|
-
|
32
|
-
PDF - application/pdf
|
33
|
-
JavaScript - application/x-javascript、text/javascript
|
34
|
-
Python - application/x-python、text/x-python
|
35
|
-
TXT - text/plain
|
36
|
-
HTML - text/html
|
37
|
-
CSS - text/css
|
38
|
-
Markdown - text/md
|
39
|
-
CSV - text/csv
|
40
|
-
XML - text/xml
|
41
|
-
RTF - text/rtf
|
42
|
-
|
43
|
-
- 小文件
|
44
|
-
- 大文件: 需要等待处理
|
45
|
-
"""
|
46
|
-
|
47
|
-
|
48
|
-
class GeminiClient(object):
|
49
|
-
def __init__(self, api_key: Optional[str] = None):
|
50
|
-
self.api_key = api_key # or await get_next_token_for_polling(feishu_url=FEISHU_URL, from_redis=True)
|
51
|
-
self.base_url = "https://all.chatfire.cc/genai"
|
52
|
-
|
53
|
-
async def create(self, request: CompletionRequest):
|
54
|
-
client = await self.get_client()
|
55
|
-
|
56
|
-
if any(i in request.model for i in ("image",)):
|
57
|
-
messages = await self.to_image_messages(request)
|
58
|
-
|
59
|
-
if len(messages) > 1:
|
60
|
-
history = messages[:-1]
|
61
|
-
message = messages[-1].parts
|
62
|
-
else:
|
63
|
-
history = []
|
64
|
-
message = messages[-1].parts
|
65
|
-
|
66
|
-
chat = client.aio.chats.create( # todo: system_instruction
|
67
|
-
model=request.model,
|
68
|
-
config=GenerateContentConfig(
|
69
|
-
response_modalities=['Text', 'Image'],
|
70
|
-
),
|
71
|
-
history=history
|
72
|
-
)
|
73
|
-
|
74
|
-
logger.debug(message)
|
75
|
-
|
76
|
-
# message = [
|
77
|
-
# Part.from_text(text="画条狗")
|
78
|
-
# ]
|
79
|
-
|
80
|
-
for i in range(5):
|
81
|
-
try:
|
82
|
-
chunks = await chat.send_message_stream(message)
|
83
|
-
async for chunk in chunks:
|
84
|
-
|
85
|
-
if chunk.candidates:
|
86
|
-
parts = chunk.candidates[0].content.parts or []
|
87
|
-
for part in parts:
|
88
|
-
# logger.debug(part)
|
89
|
-
if part.text:
|
90
|
-
yield part.text
|
91
|
-
|
92
|
-
if part.inline_data:
|
93
|
-
image_url = await to_url(
|
94
|
-
part.inline_data.data,
|
95
|
-
mime_type=part.inline_data.mime_type
|
96
|
-
)
|
97
|
-
yield f""
|
98
|
-
break
|
99
|
-
|
100
|
-
except Exception as e:
|
101
|
-
logger.debug(f"重试{i}: {e}")
|
102
|
-
if "The model is overloaded." in str(e):
|
103
|
-
await asyncio.sleep(1)
|
104
|
-
continue
|
105
|
-
else:
|
106
|
-
yield e
|
107
|
-
raise e
|
108
|
-
|
109
|
-
async def to_image_messages(self, request: CompletionRequest):
|
110
|
-
# 两轮即可连续编辑图片
|
111
|
-
|
112
|
-
messages = []
|
113
|
-
for m in request.messages:
|
114
|
-
contents = m.get("content")
|
115
|
-
if m.get("role") == "assistant":
|
116
|
-
assistant_content = str(contents)
|
117
|
-
if urls := parse_url(assistant_content): # assistant
|
118
|
-
datas = await asyncio.gather(*map(to_bytes, urls))
|
119
|
-
|
120
|
-
parts = [
|
121
|
-
Part.from_bytes(
|
122
|
-
data=data,
|
123
|
-
mime_type="image/png"
|
124
|
-
)
|
125
|
-
for data in datas
|
126
|
-
]
|
127
|
-
parts += [
|
128
|
-
Part.from_text(
|
129
|
-
text=request.last_assistant_content
|
130
|
-
),
|
131
|
-
]
|
132
|
-
messages.append(ModelContent(parts=parts))
|
133
|
-
|
134
|
-
elif m.get("role") == "user":
|
135
|
-
if isinstance(contents, list):
|
136
|
-
parts = []
|
137
|
-
for content in contents:
|
138
|
-
if content.get("type") == "image_url":
|
139
|
-
image_url = content.get("image_url", {}).get("url")
|
140
|
-
data = await to_bytes(image_url)
|
141
|
-
|
142
|
-
parts += [
|
143
|
-
Part.from_bytes(data=data, mime_type="image/png")
|
144
|
-
]
|
145
|
-
|
146
|
-
elif content.get("type") == "text":
|
147
|
-
text = content.get("text")
|
148
|
-
if text.startswith('http'):
|
149
|
-
image_url, text = text.split(maxsplit=1)
|
150
|
-
data = await to_bytes(image_url)
|
151
|
-
|
152
|
-
parts += [
|
153
|
-
Part.from_bytes(data=data, mime_type="image/png"),
|
154
|
-
Part.from_text(text=text)
|
155
|
-
]
|
156
|
-
else:
|
157
|
-
parts += [
|
158
|
-
Part.from_text(text=text)
|
159
|
-
]
|
160
|
-
|
161
|
-
messages.append(UserContent(parts=parts))
|
162
|
-
|
163
|
-
else: # str
|
164
|
-
if str(contents).startswith('http'): # 修正提问格式, 兼容 url
|
165
|
-
image_url, text = str(contents).split(maxsplit=1)
|
166
|
-
data = await to_bytes(image_url)
|
167
|
-
parts = [
|
168
|
-
Part.from_bytes(data=data, mime_type="image/png"),
|
169
|
-
Part.from_text(text=text)
|
170
|
-
]
|
171
|
-
else:
|
172
|
-
parts = [
|
173
|
-
Part.from_text(text=str(contents)),
|
174
|
-
]
|
175
|
-
messages.append(UserContent(parts=parts))
|
176
|
-
|
177
|
-
return messages
|
178
|
-
|
179
|
-
async def upload(self, files: Union[str, List[str]]): # => openai files
|
180
|
-
client = await self.get_client()
|
181
|
-
|
182
|
-
if isinstance(files, list):
|
183
|
-
return await asyncio.gather(*map(self.upload, files))
|
184
|
-
|
185
|
-
file_config = {"name": f"{shortuuid.random().lower()}", "mime_type": guess_mime_type(files)}
|
186
|
-
return await client.aio.files.upload(file=io.BytesIO(await to_bytes(files)), config=file_config)
|
187
|
-
|
188
|
-
@alru_cache()
|
189
|
-
async def get_client(self):
|
190
|
-
api_key = self.api_key or await get_next_token_for_polling(feishu_url=FEISHU_URL, from_redis=True)
|
191
|
-
|
192
|
-
logger.info(f"GeminiClient: {api_key}")
|
193
|
-
|
194
|
-
return genai.Client(
|
195
|
-
api_key=api_key,
|
196
|
-
http_options=HttpOptions(
|
197
|
-
base_url=self.base_url
|
198
|
-
)
|
199
|
-
)
|
200
|
-
|
201
|
-
|
202
|
-
if __name__ == '__main__':
|
203
|
-
file = "https://oss.ffire.cc/files/kling_watermark.png"
|
204
|
-
|
205
|
-
api_key = os.getenv("GOOGLE_API_KEY")
|
206
|
-
|
207
|
-
# arun(GeminiClient(api_key=api_key).upload(file))
|
208
|
-
# arun(GeminiClient(api_key=api_key).upload([file] * 2))
|
209
|
-
# arun(GeminiClient(api_key=api_key).create())
|
210
|
-
|
211
|
-
content = [
|
212
|
-
|
213
|
-
{"type": "text", "text": "https://oss.ffire.cc/files/nsfw.jpg 移除右下角的水印"},
|
214
|
-
|
215
|
-
# {"type": "text", "text": "总结下"},
|
216
|
-
# {"type": "image_url", "image_url": {"url": url}},
|
217
|
-
|
218
|
-
# {"type": "video_url", "video_url": {"url": url}}
|
219
|
-
|
220
|
-
]
|
221
|
-
|
222
|
-
# content = "https://oss.ffire.cc/files/nsfw.jpg 移除右下角的水印"
|
223
|
-
|
224
|
-
#
|
225
|
-
request = CompletionRequest(
|
226
|
-
# model="qwen-turbo-2024-11-01",
|
227
|
-
# model="gemini-all",
|
228
|
-
model="gemini-2.0-flash-exp-image-generation",
|
229
|
-
# model="qwen-plus-latest",
|
230
|
-
|
231
|
-
messages=[
|
232
|
-
{
|
233
|
-
'role': 'user',
|
234
|
-
|
235
|
-
'content': content
|
236
|
-
},
|
237
|
-
|
238
|
-
],
|
239
|
-
stream=True,
|
240
|
-
)
|
241
|
-
|
242
|
-
# arun(GeminiClient(api_key=api_key).to_image_messages(request))
|
243
|
-
arun(GeminiClient(api_key=api_key).create(request))
|
@@ -1,19 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python
|
2
|
-
# -*- coding: utf-8 -*-
|
3
|
-
# @Project : AI. @by PyCharm
|
4
|
-
# @File : files
|
5
|
-
# @Time : 2025/4/2 10:40
|
6
|
-
# @Author : betterme
|
7
|
-
# @WeChat : meutils
|
8
|
-
# @Software : PyCharm
|
9
|
-
# @Description :
|
10
|
-
|
11
|
-
from meutils.pipe import *
|
12
|
-
|
13
|
-
|
14
|
-
file = "/Users/betterme/PycharmProjects/AI/QR.png"
|
15
|
-
#
|
16
|
-
# file_object = client.files.upload(file=file)
|
17
|
-
# prompt = "一句话总结"
|
18
|
-
|
19
|
-
file_object = client.aio.files.upload(file=file)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|