flexllm 0.3.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.
- flexllm/__init__.py +224 -0
- flexllm/__main__.py +1096 -0
- flexllm/async_api/__init__.py +9 -0
- flexllm/async_api/concurrent_call.py +100 -0
- flexllm/async_api/concurrent_executor.py +1036 -0
- flexllm/async_api/core.py +373 -0
- flexllm/async_api/interface.py +12 -0
- flexllm/async_api/progress.py +277 -0
- flexllm/base_client.py +988 -0
- flexllm/batch_tools/__init__.py +16 -0
- flexllm/batch_tools/folder_processor.py +317 -0
- flexllm/batch_tools/table_processor.py +363 -0
- flexllm/cache/__init__.py +10 -0
- flexllm/cache/response_cache.py +293 -0
- flexllm/chain_of_thought_client.py +1120 -0
- flexllm/claudeclient.py +402 -0
- flexllm/client_pool.py +698 -0
- flexllm/geminiclient.py +563 -0
- flexllm/llm_client.py +523 -0
- flexllm/llm_parser.py +60 -0
- flexllm/mllm_client.py +559 -0
- flexllm/msg_processors/__init__.py +174 -0
- flexllm/msg_processors/image_processor.py +729 -0
- flexllm/msg_processors/image_processor_helper.py +485 -0
- flexllm/msg_processors/messages_processor.py +341 -0
- flexllm/msg_processors/unified_processor.py +1404 -0
- flexllm/openaiclient.py +256 -0
- flexllm/pricing/__init__.py +104 -0
- flexllm/pricing/data.json +1201 -0
- flexllm/pricing/updater.py +223 -0
- flexllm/provider_router.py +213 -0
- flexllm/token_counter.py +270 -0
- flexllm/utils/__init__.py +1 -0
- flexllm/utils/core.py +41 -0
- flexllm-0.3.3.dist-info/METADATA +573 -0
- flexllm-0.3.3.dist-info/RECORD +39 -0
- flexllm-0.3.3.dist-info/WHEEL +4 -0
- flexllm-0.3.3.dist-info/entry_points.txt +3 -0
- flexllm-0.3.3.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
批处理工具模块
|
|
3
|
+
|
|
4
|
+
- MllmFolderProcessor: 文件夹批量处理
|
|
5
|
+
- MllmTableProcessor: 表格数据批量处理
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .folder_processor import MllmFolderProcessor
|
|
9
|
+
|
|
10
|
+
# MllmTableProcessor 需要 pandas(可选依赖)
|
|
11
|
+
try:
|
|
12
|
+
from .table_processor import MllmTableProcessor
|
|
13
|
+
except ImportError:
|
|
14
|
+
MllmTableProcessor = None
|
|
15
|
+
|
|
16
|
+
__all__ = ["MllmFolderProcessor", "MllmTableProcessor"]
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Folder processor for MLLM client
|
|
3
|
+
专门处理文件夹图像数据的处理器类
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import List, Callable, Optional, Any, Union, TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
# 使用TYPE_CHECKING避免运行时循环引用
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from ..mllm_client import MllmClientBase, MllmClient
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MllmFolderProcessor:
|
|
16
|
+
"""
|
|
17
|
+
文件夹处理器类
|
|
18
|
+
专门处理文件夹内图像文件与MLLM客户端的交互
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# 支持的图像格式
|
|
22
|
+
SUPPORTED_IMAGE_EXTENSIONS = {
|
|
23
|
+
'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp',
|
|
24
|
+
'.tiff', '.tif', '.svg', '.ico'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
def __init__(self, mllm_client: "MllmClient"):
|
|
28
|
+
"""
|
|
29
|
+
初始化文件夹处理器
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
mllm_client: MLLM客户端实例
|
|
33
|
+
"""
|
|
34
|
+
self.mllm_client = mllm_client
|
|
35
|
+
|
|
36
|
+
# 属性委托:委托给mllm_client的核心方法
|
|
37
|
+
@property
|
|
38
|
+
def call_llm(self):
|
|
39
|
+
"""委托给mllm_client的call_llm方法"""
|
|
40
|
+
return self.mllm_client.call_llm
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def call_llm_with_selection(self):
|
|
44
|
+
"""委托给mllm_client的call_llm_with_selection方法"""
|
|
45
|
+
return self.mllm_client.call_llm_with_selection
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def call_llm_sync(self):
|
|
49
|
+
"""委托"""
|
|
50
|
+
return self.mllm_client.call_llm_sync
|
|
51
|
+
|
|
52
|
+
# 数据预处理工具方法(独立于mllm_client)
|
|
53
|
+
def process_image(self, image_path: str) -> str:
|
|
54
|
+
"""
|
|
55
|
+
预处理图像路径
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
image_path: 图片路径
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
str: 处理后的图片路径
|
|
62
|
+
"""
|
|
63
|
+
# 转换为绝对路径
|
|
64
|
+
return os.path.abspath(image_path)
|
|
65
|
+
|
|
66
|
+
def scan_folder_images(
|
|
67
|
+
self,
|
|
68
|
+
folder_path: str,
|
|
69
|
+
recursive: bool = True,
|
|
70
|
+
max_num: Optional[int] = None,
|
|
71
|
+
extensions: Optional[set] = None
|
|
72
|
+
) -> List[str]:
|
|
73
|
+
"""
|
|
74
|
+
扫描文件夹中的图像文件
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
folder_path: 文件夹路径
|
|
78
|
+
recursive: 是否递归扫描子文件夹,默认为True
|
|
79
|
+
max_num: 最大文件数量限制
|
|
80
|
+
extensions: 支持的文件扩展名集合,默认使用SUPPORTED_IMAGE_EXTENSIONS
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
List[str]: 图像文件路径列表
|
|
84
|
+
|
|
85
|
+
Raises:
|
|
86
|
+
ValueError: 当输入参数无效时
|
|
87
|
+
FileNotFoundError: 当文件夹不存在时
|
|
88
|
+
"""
|
|
89
|
+
# 验证输入参数
|
|
90
|
+
if not folder_path:
|
|
91
|
+
raise ValueError("文件夹路径不能为空")
|
|
92
|
+
|
|
93
|
+
folder_path = Path(folder_path)
|
|
94
|
+
if not folder_path.exists():
|
|
95
|
+
raise FileNotFoundError(f"文件夹不存在: {folder_path}")
|
|
96
|
+
|
|
97
|
+
if not folder_path.is_dir():
|
|
98
|
+
raise ValueError(f"路径不是文件夹: {folder_path}")
|
|
99
|
+
|
|
100
|
+
# 使用默认扩展名或用户指定的扩展名
|
|
101
|
+
if extensions is None:
|
|
102
|
+
extensions = self.SUPPORTED_IMAGE_EXTENSIONS
|
|
103
|
+
|
|
104
|
+
# 转换为小写用于比较
|
|
105
|
+
extensions = {ext.lower() for ext in extensions}
|
|
106
|
+
|
|
107
|
+
image_files = []
|
|
108
|
+
|
|
109
|
+
# 扫描文件
|
|
110
|
+
if recursive:
|
|
111
|
+
# 递归扫描所有子文件夹
|
|
112
|
+
for root, dirs, files in os.walk(folder_path):
|
|
113
|
+
for file in files:
|
|
114
|
+
file_path = os.path.join(root, file)
|
|
115
|
+
if Path(file_path).suffix.lower() in extensions:
|
|
116
|
+
image_files.append(file_path)
|
|
117
|
+
|
|
118
|
+
# 检查数量限制
|
|
119
|
+
if max_num and len(image_files) >= max_num:
|
|
120
|
+
break
|
|
121
|
+
if max_num and len(image_files) >= max_num:
|
|
122
|
+
break
|
|
123
|
+
else:
|
|
124
|
+
# 只扫描当前文件夹
|
|
125
|
+
for file_path in folder_path.iterdir():
|
|
126
|
+
if file_path.is_file() and file_path.suffix.lower() in extensions:
|
|
127
|
+
image_files.append(str(file_path))
|
|
128
|
+
|
|
129
|
+
# 检查数量限制
|
|
130
|
+
if max_num and len(image_files) >= max_num:
|
|
131
|
+
break
|
|
132
|
+
|
|
133
|
+
# 按文件名排序,确保结果的一致性
|
|
134
|
+
image_files.sort()
|
|
135
|
+
|
|
136
|
+
print(f"扫描完成: 发现 {len(image_files)} 个图像文件")
|
|
137
|
+
if image_files:
|
|
138
|
+
print(f"示例文件: {image_files[0]}")
|
|
139
|
+
|
|
140
|
+
return image_files
|
|
141
|
+
|
|
142
|
+
def _build_image_messages_from_files(
|
|
143
|
+
self,
|
|
144
|
+
image_files: List[str],
|
|
145
|
+
system_prompt: str = "",
|
|
146
|
+
text_prompt: str = "请描述这幅图片",
|
|
147
|
+
) -> List[List[dict]]:
|
|
148
|
+
"""
|
|
149
|
+
从图像文件列表构建消息列表
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
image_files: 图像文件路径列表
|
|
153
|
+
system_prompt: 系统提示词
|
|
154
|
+
text_prompt: 文本提示词
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
messages_list: 消息列表
|
|
158
|
+
"""
|
|
159
|
+
messages_list = []
|
|
160
|
+
|
|
161
|
+
for image_path in image_files:
|
|
162
|
+
messages = []
|
|
163
|
+
|
|
164
|
+
# 添加系统提示词(如果提供)
|
|
165
|
+
if system_prompt:
|
|
166
|
+
messages.append({
|
|
167
|
+
"role": "system",
|
|
168
|
+
"content": system_prompt
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
# 处理图像路径
|
|
172
|
+
processed_image_path = self.process_image(image_path)
|
|
173
|
+
|
|
174
|
+
# 添加用户消息(包含文本提示和图像)
|
|
175
|
+
messages.append({
|
|
176
|
+
"role": "user",
|
|
177
|
+
"content": [
|
|
178
|
+
{"type": "text", "text": f"{text_prompt}\n文件路径: {processed_image_path}"},
|
|
179
|
+
{"type": "image_url", "image_url": {"url": f"file://{processed_image_path}"}},
|
|
180
|
+
],
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
messages_list.append(messages)
|
|
184
|
+
|
|
185
|
+
return messages_list
|
|
186
|
+
|
|
187
|
+
async def call_folder_images(
|
|
188
|
+
self,
|
|
189
|
+
folder_path: str,
|
|
190
|
+
system_prompt: str = "",
|
|
191
|
+
text_prompt: str = "请描述这幅图片",
|
|
192
|
+
recursive: bool = True,
|
|
193
|
+
max_num: Optional[int] = None,
|
|
194
|
+
extensions: Optional[set] = None,
|
|
195
|
+
use_selection: bool = False,
|
|
196
|
+
n_predictions: int = 1,
|
|
197
|
+
selector_fn: Optional[Callable[[List[Any]], Any]] = None,
|
|
198
|
+
return_image_files: bool = False,
|
|
199
|
+
**kwargs,
|
|
200
|
+
):
|
|
201
|
+
"""
|
|
202
|
+
对文件夹中的图像进行批量请求大模型
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
folder_path: 文件夹路径
|
|
206
|
+
system_prompt: 系统提示词,默认为空
|
|
207
|
+
text_prompt: 文本提示词,默认为"请描述这幅图片"
|
|
208
|
+
recursive: 是否递归扫描子文件夹,默认为True
|
|
209
|
+
max_num: 最大处理图像数量限制
|
|
210
|
+
extensions: 支持的文件扩展名集合,默认使用SUPPORTED_IMAGE_EXTENSIONS
|
|
211
|
+
use_selection: 是否使用选择模式
|
|
212
|
+
n_predictions: 每条消息预测次数(仅在use_selection=True时有效)
|
|
213
|
+
selector_fn: 选择函数(仅在use_selection=True时有效)
|
|
214
|
+
return_image_files: 是否在返回结果中包含图像文件列表,默认为False
|
|
215
|
+
**kwargs: 其他传递给MLLM的参数
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
如果return_image_files=False: response_list
|
|
219
|
+
如果return_image_files=True: (response_list, image_files)
|
|
220
|
+
|
|
221
|
+
Raises:
|
|
222
|
+
ValueError: 当输入参数无效时
|
|
223
|
+
FileNotFoundError: 当文件夹不存在时
|
|
224
|
+
"""
|
|
225
|
+
# 扫描文件夹获取图像文件
|
|
226
|
+
image_files = self.scan_folder_images(
|
|
227
|
+
folder_path=folder_path,
|
|
228
|
+
recursive=recursive,
|
|
229
|
+
max_num=max_num,
|
|
230
|
+
extensions=extensions
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
if not image_files:
|
|
234
|
+
print("警告: 未找到任何图像文件")
|
|
235
|
+
if return_image_files:
|
|
236
|
+
return [], []
|
|
237
|
+
else:
|
|
238
|
+
return []
|
|
239
|
+
|
|
240
|
+
# 构建消息列表
|
|
241
|
+
messages_list = self._build_image_messages_from_files(
|
|
242
|
+
image_files, system_prompt, text_prompt
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# 调用MLLM
|
|
246
|
+
if use_selection:
|
|
247
|
+
response_list = await self.call_llm_with_selection(
|
|
248
|
+
messages_list,
|
|
249
|
+
n_predictions=n_predictions,
|
|
250
|
+
selector_fn=selector_fn,
|
|
251
|
+
**kwargs
|
|
252
|
+
)
|
|
253
|
+
else:
|
|
254
|
+
response_list = await self.call_llm(messages_list, **kwargs)
|
|
255
|
+
|
|
256
|
+
# 根据参数决定返回格式
|
|
257
|
+
if return_image_files:
|
|
258
|
+
return response_list, image_files
|
|
259
|
+
else:
|
|
260
|
+
return response_list
|
|
261
|
+
|
|
262
|
+
async def call_image_files(
|
|
263
|
+
self,
|
|
264
|
+
image_files: List[str],
|
|
265
|
+
system_prompt: str = "",
|
|
266
|
+
text_prompt: str = "请描述这幅图片",
|
|
267
|
+
use_selection: bool = False,
|
|
268
|
+
n_predictions: int = 1,
|
|
269
|
+
selector_fn: Optional[Callable[[List[Any]], Any]] = None,
|
|
270
|
+
**kwargs,
|
|
271
|
+
):
|
|
272
|
+
"""
|
|
273
|
+
对指定的图像文件列表进行批量请求大模型
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
image_files: 图像文件路径列表
|
|
277
|
+
system_prompt: 系统提示词,默认为空
|
|
278
|
+
text_prompt: 文本提示词,默认为"请描述这幅图片"
|
|
279
|
+
use_selection: 是否使用选择模式
|
|
280
|
+
n_predictions: 每条消息预测次数(仅在use_selection=True时有效)
|
|
281
|
+
selector_fn: 选择函数(仅在use_selection=True时有效)
|
|
282
|
+
**kwargs: 其他传递给MLLM的参数
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
response_list: 响应列表
|
|
286
|
+
"""
|
|
287
|
+
if not image_files:
|
|
288
|
+
print("警告: 图像文件列表为空")
|
|
289
|
+
return []
|
|
290
|
+
|
|
291
|
+
# 验证文件存在性
|
|
292
|
+
valid_files = []
|
|
293
|
+
for file_path in image_files:
|
|
294
|
+
if os.path.exists(file_path):
|
|
295
|
+
valid_files.append(file_path)
|
|
296
|
+
else:
|
|
297
|
+
print(f"警告: 文件不存在,跳过: {file_path}")
|
|
298
|
+
|
|
299
|
+
if not valid_files:
|
|
300
|
+
print("警告: 没有有效的图像文件")
|
|
301
|
+
return []
|
|
302
|
+
|
|
303
|
+
# 构建消息列表
|
|
304
|
+
messages_list = self._build_image_messages_from_files(
|
|
305
|
+
valid_files, system_prompt, text_prompt
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
# 调用MLLM
|
|
309
|
+
if use_selection:
|
|
310
|
+
return await self.call_llm_with_selection(
|
|
311
|
+
messages_list,
|
|
312
|
+
n_predictions=n_predictions,
|
|
313
|
+
selector_fn=selector_fn,
|
|
314
|
+
**kwargs
|
|
315
|
+
)
|
|
316
|
+
else:
|
|
317
|
+
return await self.call_llm(messages_list, **kwargs)
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Table processor for MLLM client
|
|
3
|
+
专门处理表格数据的处理器类
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
from typing import List, Callable, Optional, Any, Union, TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
# 使用TYPE_CHECKING避免运行时循环引用
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from ..mllm_client import MllmClient
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MllmTableProcessor:
|
|
15
|
+
"""
|
|
16
|
+
表格处理器类
|
|
17
|
+
专门处理表格数据与MLLM客户端的交互
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, mllm_client: "MllmClient"):
|
|
21
|
+
"""
|
|
22
|
+
初始化表格处理器
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
mllm_client: MLLM客户端实例
|
|
26
|
+
"""
|
|
27
|
+
self.mllm_client = mllm_client
|
|
28
|
+
|
|
29
|
+
# 属性委托:委托给mllm_client的核心方法
|
|
30
|
+
@property
|
|
31
|
+
def call_llm(self):
|
|
32
|
+
"""委托给mllm_client的call_llm方法"""
|
|
33
|
+
return self.mllm_client.call_llm
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def call_llm_with_selection(self):
|
|
37
|
+
"""委托给mllm_client的call_llm_with_selection方法"""
|
|
38
|
+
return self.mllm_client.call_llm_with_selection
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def call_llm_sync(self):
|
|
42
|
+
"""委托给mllm_client的call_llm_sync方法"""
|
|
43
|
+
return self.mllm_client.call_llm_sync
|
|
44
|
+
|
|
45
|
+
# 数据预处理工具方法(独立于mllm_client)
|
|
46
|
+
def process_text(self, text: str) -> str:
|
|
47
|
+
"""
|
|
48
|
+
预处理文本
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
text: 输入文本
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
str: 处理后的文本
|
|
55
|
+
"""
|
|
56
|
+
return text
|
|
57
|
+
|
|
58
|
+
def process_image(self, image: str) -> str:
|
|
59
|
+
"""
|
|
60
|
+
预处理图像
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
image: 图片路径或URL
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
str: 处理后的图片路径或URL
|
|
67
|
+
"""
|
|
68
|
+
return image
|
|
69
|
+
|
|
70
|
+
def load_dataframe(
|
|
71
|
+
self,
|
|
72
|
+
table_path: str,
|
|
73
|
+
sheet_name: str = 0,
|
|
74
|
+
max_num: int = None,
|
|
75
|
+
) -> pd.DataFrame:
|
|
76
|
+
"""
|
|
77
|
+
加载并过滤Excel数据
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
table_path: 表格文件路径
|
|
81
|
+
sheet_name: 要读取的sheet名称
|
|
82
|
+
max_num: 最大处理数量限制
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
处理后的DataFrame
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
ValueError: 当输入参数无效时
|
|
89
|
+
FileNotFoundError: 当文件不存在时
|
|
90
|
+
"""
|
|
91
|
+
# 验证输入参数
|
|
92
|
+
if not table_path:
|
|
93
|
+
raise ValueError("表格文件路径不能为空")
|
|
94
|
+
|
|
95
|
+
# 读取数据
|
|
96
|
+
try:
|
|
97
|
+
if table_path.endswith(".xlsx"):
|
|
98
|
+
df = pd.read_excel(table_path, sheet_name=sheet_name)
|
|
99
|
+
elif table_path.endswith(".csv"):
|
|
100
|
+
df = pd.read_csv(table_path)
|
|
101
|
+
else:
|
|
102
|
+
raise ValueError(f"不支持的文件格式: {table_path}")
|
|
103
|
+
except Exception as e:
|
|
104
|
+
raise ValueError(f"读取文件失败: {str(e)}")
|
|
105
|
+
|
|
106
|
+
if df.empty:
|
|
107
|
+
print(f"警告: 过滤后数据为空")
|
|
108
|
+
return df
|
|
109
|
+
|
|
110
|
+
# 应用数量限制
|
|
111
|
+
if max_num is not None:
|
|
112
|
+
df = df.head(max_num)
|
|
113
|
+
|
|
114
|
+
print(f"加载数据完成: {len(df)} 行")
|
|
115
|
+
df = df.astype(str)
|
|
116
|
+
print(f"{df.head(2)=}")
|
|
117
|
+
|
|
118
|
+
return df
|
|
119
|
+
|
|
120
|
+
def preprocess_dataframe(
|
|
121
|
+
self,
|
|
122
|
+
df: pd.DataFrame,
|
|
123
|
+
image_col: Optional[str],
|
|
124
|
+
text_col: str,
|
|
125
|
+
):
|
|
126
|
+
"""
|
|
127
|
+
预处理df
|
|
128
|
+
"""
|
|
129
|
+
df[text_col] = df[text_col].apply(self.process_text)
|
|
130
|
+
# 只有当图像列存在时才处理图像列
|
|
131
|
+
if image_col and image_col in df.columns:
|
|
132
|
+
df[image_col] = df[image_col].apply(self.process_image)
|
|
133
|
+
return df
|
|
134
|
+
|
|
135
|
+
def _build_messages_from_dataframe(
|
|
136
|
+
self,
|
|
137
|
+
df: pd.DataFrame,
|
|
138
|
+
image_col: Optional[str],
|
|
139
|
+
text_col: str,
|
|
140
|
+
) -> List[List[dict]]:
|
|
141
|
+
"""
|
|
142
|
+
从dataframe构建消息列表
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
df: 数据框
|
|
146
|
+
image_col: 图片列名,如果为None或列不存在则只使用文本
|
|
147
|
+
text_col: 文本列名
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
messages_list: 消息列表
|
|
151
|
+
"""
|
|
152
|
+
messages_list = []
|
|
153
|
+
# 检查是否有图像列
|
|
154
|
+
has_image_col = image_col and image_col in df.columns
|
|
155
|
+
|
|
156
|
+
for index, row in df.iterrows():
|
|
157
|
+
if has_image_col:
|
|
158
|
+
# 有图像列时,包含图像和文本
|
|
159
|
+
messages_list.append(
|
|
160
|
+
[
|
|
161
|
+
{
|
|
162
|
+
"role": "user",
|
|
163
|
+
"content": [
|
|
164
|
+
{"type": "text", "text": f"{row[text_col]}"},
|
|
165
|
+
{"type": "image_url", "image_url": {"url": f"{str(row[image_col])}"}},
|
|
166
|
+
],
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
)
|
|
170
|
+
else:
|
|
171
|
+
# 没有图像列时,只包含文本
|
|
172
|
+
messages_list.append(
|
|
173
|
+
[
|
|
174
|
+
{
|
|
175
|
+
"role": "user",
|
|
176
|
+
"content": f"{row[text_col]}"
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
)
|
|
180
|
+
return messages_list
|
|
181
|
+
|
|
182
|
+
def _build_image_messages_from_dataframe(
|
|
183
|
+
self,
|
|
184
|
+
df: pd.DataFrame,
|
|
185
|
+
image_col: str,
|
|
186
|
+
system_prompt: str = "",
|
|
187
|
+
text_prompt: str = "请描述这幅图片",
|
|
188
|
+
) -> List[List[dict]]:
|
|
189
|
+
"""
|
|
190
|
+
从dataframe构建图像处理消息列表
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
df: 数据框
|
|
194
|
+
image_col: 图片列名
|
|
195
|
+
system_prompt: 系统提示词
|
|
196
|
+
text_prompt: 文本提示词
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
messages_list: 消息列表
|
|
200
|
+
"""
|
|
201
|
+
messages_list = []
|
|
202
|
+
for index, row in df.iterrows():
|
|
203
|
+
messages = []
|
|
204
|
+
|
|
205
|
+
# 添加系统提示词(如果提供)
|
|
206
|
+
if system_prompt:
|
|
207
|
+
messages.append({
|
|
208
|
+
"role": "system",
|
|
209
|
+
"content": system_prompt
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
# 添加用户消息(包含文本提示和图像)
|
|
213
|
+
messages.append({
|
|
214
|
+
"role": "user",
|
|
215
|
+
"content": [
|
|
216
|
+
{"type": "text", "text": text_prompt},
|
|
217
|
+
{"type": "image_url", "image_url": {"url": f"{str(row[image_col])}"}},
|
|
218
|
+
],
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
messages_list.append(messages)
|
|
222
|
+
return messages_list
|
|
223
|
+
|
|
224
|
+
async def call_dataframe(
|
|
225
|
+
self,
|
|
226
|
+
df: pd.DataFrame,
|
|
227
|
+
text_col: str,
|
|
228
|
+
image_col: Optional[str] = None,
|
|
229
|
+
use_selection: bool = False,
|
|
230
|
+
n_predictions: int = 3,
|
|
231
|
+
selector_fn: Optional[Callable[[List[Any]], Any]] = None,
|
|
232
|
+
**kwargs,
|
|
233
|
+
):
|
|
234
|
+
"""
|
|
235
|
+
调用dataframe
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
df: 数据框
|
|
239
|
+
image_col: 图片列名,如果为None或列不存在则只使用文本
|
|
240
|
+
text_col: 文本列名
|
|
241
|
+
use_selection: 是否使用选择模式
|
|
242
|
+
n_predictions: 每条消息预测次数(仅在use_selection=True时有效)
|
|
243
|
+
selector_fn: 选择函数(仅在use_selection=True时有效)
|
|
244
|
+
**kwargs: 模型请求参数
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
response_list: 响应列表
|
|
248
|
+
|
|
249
|
+
Examples:
|
|
250
|
+
# 纯文本模式(推荐的默认方式)
|
|
251
|
+
await processor.call_dataframe(df, image_col=None, text_col="text")
|
|
252
|
+
|
|
253
|
+
# 图像+文本模式(需要显式指定图像列)
|
|
254
|
+
await processor.call_dataframe(df, image_col="image", text_col="text")
|
|
255
|
+
"""
|
|
256
|
+
df = self.preprocess_dataframe(df, image_col, text_col)
|
|
257
|
+
messages_list = self._build_messages_from_dataframe(df, image_col, text_col)
|
|
258
|
+
|
|
259
|
+
if use_selection:
|
|
260
|
+
return await self.call_llm_with_selection(
|
|
261
|
+
messages_list,
|
|
262
|
+
n_predictions=n_predictions,
|
|
263
|
+
selector_fn=selector_fn,
|
|
264
|
+
**kwargs
|
|
265
|
+
)
|
|
266
|
+
else:
|
|
267
|
+
return await self.call_llm(messages_list, **kwargs)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
async def call_table(
|
|
271
|
+
self,
|
|
272
|
+
table_path: str,
|
|
273
|
+
text_col: str = "text",
|
|
274
|
+
image_col: Optional[str] = None,
|
|
275
|
+
sheet_name: str = 0,
|
|
276
|
+
max_num: int = None,
|
|
277
|
+
use_selection: bool = False,
|
|
278
|
+
n_predictions: int = 1,
|
|
279
|
+
selector_fn: Optional[Callable[[List[Any]], Any]] = None,
|
|
280
|
+
**kwargs,
|
|
281
|
+
):
|
|
282
|
+
"""
|
|
283
|
+
调用table
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
table_path: 表格文件路径
|
|
287
|
+
image_col: 图片列名,默认为None(纯文本模式),指定列名则启用图像+文本模式
|
|
288
|
+
text_col: 文本列名,默认为"text"
|
|
289
|
+
sheet_name: sheet名称,默认为0
|
|
290
|
+
max_num: 最大处理数量限制
|
|
291
|
+
use_selection: 是否使用选择模式
|
|
292
|
+
n_predictions: 每条消息预测次数(仅在use_selection=True时有效)
|
|
293
|
+
selector_fn: 选择函数(仅在use_selection=True时有效)
|
|
294
|
+
**kwargs: 其他参数
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
response_list: 响应列表
|
|
298
|
+
"""
|
|
299
|
+
df = self.load_dataframe(table_path, sheet_name, max_num)
|
|
300
|
+
return await self.call_dataframe(
|
|
301
|
+
df=df,
|
|
302
|
+
text_col=text_col,
|
|
303
|
+
image_col=image_col,
|
|
304
|
+
use_selection=use_selection,
|
|
305
|
+
n_predictions=n_predictions,
|
|
306
|
+
selector_fn=selector_fn,
|
|
307
|
+
**kwargs
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
async def call_table_images(
|
|
312
|
+
self,
|
|
313
|
+
table_path: str,
|
|
314
|
+
image_col: str = "image",
|
|
315
|
+
system_prompt: str = "",
|
|
316
|
+
text_prompt: str = "请描述这幅图片",
|
|
317
|
+
sheet_name: str = 0,
|
|
318
|
+
max_num: int = None,
|
|
319
|
+
use_selection: bool = False,
|
|
320
|
+
n_predictions: int = 1,
|
|
321
|
+
selector_fn: Optional[Callable[[List[Any]], Any]] = None,
|
|
322
|
+
**kwargs,
|
|
323
|
+
):
|
|
324
|
+
"""
|
|
325
|
+
对表格中的图像列进行批量请求大模型
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
table_path: 表格文件路径
|
|
329
|
+
image_col: 图片列名,默认为"image"(此方法专门处理图像,必须指定有效的图像列)
|
|
330
|
+
system_prompt: 系统提示词,默认为空
|
|
331
|
+
text_prompt: 文本提示词,默认为"请描述这幅图片"
|
|
332
|
+
sheet_name: sheet名称,默认为0
|
|
333
|
+
max_num: 最大处理数量限制
|
|
334
|
+
use_selection: 是否使用选择模式
|
|
335
|
+
n_predictions: 每条消息预测次数(仅在use_selection=True时有效)
|
|
336
|
+
selector_fn: 选择函数(仅在use_selection=True时有效)
|
|
337
|
+
**kwargs: 其他参数
|
|
338
|
+
|
|
339
|
+
Returns:
|
|
340
|
+
response_list: 响应列表
|
|
341
|
+
"""
|
|
342
|
+
df = self.load_dataframe(table_path, sheet_name, max_num)
|
|
343
|
+
|
|
344
|
+
# 检查图像列是否存在
|
|
345
|
+
if not image_col or image_col not in df.columns:
|
|
346
|
+
raise ValueError(f"图像列 '{image_col}' 不存在于表格中")
|
|
347
|
+
|
|
348
|
+
# 预处理图像列
|
|
349
|
+
df[image_col] = df[image_col].apply(self.process_image)
|
|
350
|
+
|
|
351
|
+
messages_list = self._build_image_messages_from_dataframe(
|
|
352
|
+
df, image_col, system_prompt, text_prompt
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
if use_selection:
|
|
356
|
+
return await self.call_llm_with_selection(
|
|
357
|
+
messages_list,
|
|
358
|
+
n_predictions=n_predictions,
|
|
359
|
+
selector_fn=selector_fn,
|
|
360
|
+
**kwargs
|
|
361
|
+
)
|
|
362
|
+
else:
|
|
363
|
+
return await self.call_llm(messages_list, **kwargs)
|