返回 VideoClaw
config_model.py
根目录 / video-claw / video-claw / backend / models / config_model.py
1 """Central model registry.
2
3 All backend model metadata is registered in this file, including provider,
4 model type, concurrency, pricing, and API capability tags.
5 """
6
7 import os
8 import sys
9
10 models_dir = os.path.dirname(os.path.abspath(__file__))
11 backend_dir = os.path.dirname(models_dir)
12 if backend_dir not in sys.path:
13 sys.path.insert(0, backend_dir)
14
15 from copy import deepcopy
16 from typing import Any, Optional
17
18 MODEL_CONFIG: dict[str, Any] = {'models': {'deepseek-chat': {'name': 'DeepSeek Chat',
19 'provider': 'deepseek',
20 'type': ['llm'],
21 'concurrency': 10,
22 'price_per_1k_input_token': 0.002,
23 'price_per_1k_output_token': 0.008},
24 'deepseek-reasoner': {'name': 'DeepSeek Reasoner',
25 'provider': 'deepseek',
26 'type': ['llm'],
27 'concurrency': 10,
28 'price_per_1k_input_token': 0.004,
29 'price_per_1k_output_token': 0.016},
30 'deepseek-v4-flash': {'name': 'DeepSeek V4 Flash',
31 'provider': 'deepseek',
32 'type': ['llm'],
33 'concurrency': 10,
34 'price_per_1k_input_token': 0.001,
35 'price_per_1k_output_token': 0.004},
36 'deepseek-v4-pro': {'name': 'DeepSeek V4 Pro',
37 'provider': 'deepseek',
38 'type': ['llm'],
39 'concurrency': 10,
40 'price_per_1k_input_token': 0.002,
41 'price_per_1k_output_token': 0.008},
42 'gpt-4o': {'name': 'GPT-4o',
43 'provider': 'openai',
44 'type': ['llm'],
45 'concurrency': 10,
46 'price_per_1k_input_token': 0.018,
47 'price_per_1k_output_token': 0.072},
48 'gpt-5': {'name': 'GPT-5',
49 'provider': 'openai',
50 'type': ['llm'],
51 'concurrency': 10,
52 'price_per_1k_input_token': 0.02,
53 'price_per_1k_output_token': 0.1},
54 'gpt-5.4': {'name': 'GPT-5.4',
55 'provider': 'openai',
56 'type': ['llm', 'vlm'],
57 'concurrency': 10,
58 'price_per_1k_input_token': 0.03,
59 'price_per_1k_output_token': 0.12},
60 'qwen3.7-max': {'name': 'Qwen 3.7 Max',
61 'provider': 'dashscope',
62 'family': 'qwen',
63 'type': ['llm'],
64 'concurrency': 10,
65 'price_per_1k_input_token': 0.006,
66 'price_per_1k_output_token': 0.024},
67 'qwen3.7-plus': {'name': 'Qwen 3.7 Plus',
68 'provider': 'dashscope',
69 'family': 'qwen',
70 'type': ['vlm'],
71 'concurrency': 10,
72 'price_per_1k_input_token': 0.0008,
73 'price_per_1k_output_token': 0.002},
74 'qwen3.5-plus': {'name': 'Qwen 3.5 Plus',
75 'provider': 'dashscope',
76 'family': 'qwen',
77 'type': ['vlm'],
78 'concurrency': 10,
79 'price_per_1k_input_token': 0.0008,
80 'price_per_1k_output_token': 0.002},
81 'qwen3.6-max-preview': {'name': 'Qwen 3.6 Max Preview',
82 'provider': 'dashscope',
83 'family': 'qwen',
84 'type': ['llm'],
85 'concurrency': 10,
86 'price_per_1k_input_token': 0.006,
87 'price_per_1k_output_token': 0.024},
88 'qwen3-max': {'name': 'Qwen 3 Max',
89 'provider': 'dashscope',
90 'family': 'qwen',
91 'type': ['llm'],
92 'concurrency': 10,
93 'price_per_1k_input_token': 0.006,
94 'price_per_1k_output_token': 0.024},
95 'deepseek-v3.2': {'name': 'DeepSeek V3.2 (DashScope)',
96 'provider': 'dashscope',
97 'family': 'deepseek',
98 'type': ['llm'],
99 'concurrency': 10,
100 'price_per_1k_input_token': 0.002,
101 'price_per_1k_output_token': 0.008},
102 'qwen3.6-plus': {'name': 'Qwen 3.6 Plus',
103 'provider': 'dashscope',
104 'family': 'qwen',
105 'type': ['vlm'],
106 'concurrency': 10,
107 'price_per_1k_input_token': 0.0008,
108 'price_per_1k_output_token': 0.002},
109 'qwen3.6-flash': {'name': 'Qwen 3.6 Flash',
110 'provider': 'dashscope',
111 'family': 'qwen',
112 'type': ['vlm'],
113 'concurrency': 10,
114 'price_per_1k_input_token': 0.0001,
115 'price_per_1k_output_token': 0.0001},
116 'kimi-k2.6': {'name': 'Kimi K2.6',
117 'provider': 'dashscope',
118 'family': 'kimi',
119 'type': ['llm', 'vlm'],
120 'concurrency': 10,
121 'price_per_1k_input_token': 0.001,
122 'price_per_1k_output_token': 0.003},
123 'gemini-2.5-flash': {'name': 'Gemini 2.5 Flash',
124 'provider': 'gemini',
125 'type': ['llm'],
126 'concurrency': 10,
127 'price_per_1k_input_token': 0.002,
128 'price_per_1k_output_token': 0.01},
129 'gemini-2.0-flash': {'name': 'Gemini 2.0 Flash',
130 'provider': 'gemini',
131 'type': ['llm', 'vlm'],
132 'concurrency': 10,
133 'price_per_1k_input_token': 0.002,
134 'price_per_1k_output_token': 0.01},
135 'gemini-2.5-flash-image': {'name': 'Gemini 2.5 Flash Image',
136 'provider': 'gemini',
137 'type': ['vlm'],
138 'concurrency': 10,
139 'price_per_1k_input_token': 0.002,
140 'price_per_1k_output_token': 0.01},
141 'wan2.7-image': {'name': 'Wan 2.7 Image',
142 'provider': 'dashscope',
143 'family': 'wan',
144 'type': ['t2i', 'i2i'],
145 'concurrency': 5,
146 'price_per_image': 0.2,
147 'capabilities': {'ability_type': 'image_generation',
148 'ability_types': ['text_to_image', 'image_to_image', 'reference_image'],
149 'adapter_ability_types': ['text_to_image',
150 'image_to_image',
151 'reference_image'],
152 'input_modalities': ['text', 'image'],
153 'adapter_input_modalities': ['text', 'image'],
154 'api_contract_verified': True,
155 'resolutions': ['720P', '1080P', '2K', '4K'],
156 'ratios': ['16:9', '9:16', '1:1', '4:3', '3:4']}},
157 'wan2.7-image-pro': {'name': 'Wan 2.7 Image Pro',
158 'provider': 'dashscope',
159 'family': 'wan',
160 'type': ['t2i', 'i2i'],
161 'concurrency': 5,
162 'price_per_image': 0.4,
163 'capabilities': {'ability_type': 'image_generation',
164 'ability_types': ['text_to_image',
165 'image_to_image',
166 'reference_image',
167 'high_quality'],
168 'adapter_ability_types': ['text_to_image',
169 'image_to_image',
170 'reference_image'],
171 'input_modalities': ['text', 'image'],
172 'adapter_input_modalities': ['text', 'image'],
173 'api_contract_verified': True,
174 'resolutions': ['720P', '1080P', '2K', '4K'],
175 'ratios': ['16:9', '9:16', '1:1', '4:3', '3:4']}},
176 'wan2.6-t2i': {'name': 'Wan 2.6 T2I',
177 'provider': 'dashscope',
178 'family': 'wan',
179 'type': ['t2i'],
180 'concurrency': 5,
181 'price_per_image': 0.2,
182 'capabilities': {'ability_type': 'image_generation',
183 'ability_types': ['text_to_image'],
184 'adapter_ability_types': ['text_to_image'],
185 'input_modalities': ['text'],
186 'adapter_input_modalities': ['text'],
187 'api_contract_verified': True,
188 'resolutions': ['720P', '1080P', '2K', '4K'],
189 'ratios': ['16:9', '9:16', '1:1', '4:3', '3:4']}},
190 'doubao-seedream-5-0-260128': {'name': 'Seedream 5.0',
191 'provider': 'ark',
192 'family': 'seedream',
193 'type': ['t2i', 'i2i'],
194 'concurrency': 10,
195 'price_per_image': 0.22,
196 'capabilities': {'ability_type': 'image_generation',
197 'ability_types': ['text_to_image',
198 'image_to_image',
199 'reference_image',
200 'high_quality'],
201 'adapter_ability_types': ['text_to_image',
202 'image_to_image',
203 'reference_image'],
204 'input_modalities': ['text', 'image'],
205 'adapter_input_modalities': ['text', 'image'],
206 'api_contract_verified': True,
207 'resolutions': ['720P', '1080P', '2K', '4K'],
208 'ratios': ['16:9', '9:16', '1:1', '4:3', '3:4']}},
209 'doubao-seedream-4-5-251128': {'name': 'Seedream 4.5',
210 'provider': 'ark',
211 'family': 'seedream',
212 'type': ['t2i', 'i2i'],
213 'concurrency': 10,
214 'price_per_image': 0.25,
215 'capabilities': {'ability_type': 'image_generation',
216 'ability_types': ['text_to_image',
217 'image_to_image',
218 'reference_image'],
219 'adapter_ability_types': ['text_to_image',
220 'image_to_image',
221 'reference_image'],
222 'input_modalities': ['text', 'image'],
223 'adapter_input_modalities': ['text', 'image'],
224 'api_contract_verified': True,
225 'resolutions': ['720P', '1080P', '2K', '4K'],
226 'ratios': ['16:9', '9:16', '1:1', '4:3', '3:4']}},
227 'doubao-seedream-4-0-250828': {'name': 'Seedream 4.0',
228 'provider': 'ark',
229 'family': 'seedream',
230 'type': ['t2i', 'i2i'],
231 'concurrency': 10,
232 'price_per_image': 0.2,
233 'capabilities': {'ability_type': 'image_generation',
234 'ability_types': ['text_to_image',
235 'image_to_image',
236 'reference_image'],
237 'adapter_ability_types': ['text_to_image',
238 'image_to_image',
239 'reference_image'],
240 'input_modalities': ['text', 'image'],
241 'adapter_input_modalities': ['text', 'image'],
242 'api_contract_verified': True,
243 'resolutions': ['720P', '1080P', '2K', '4K'],
244 'ratios': ['16:9', '9:16', '1:1', '4:3', '3:4']}},
245 'sora_image': {'name': 'Sora Image',
246 'provider': 'openai',
247 'type': ['t2i'],
248 'concurrency': 3,
249 'price_per_image': 1.45,
250 'capabilities': {'ability_type': 'image_generation',
251 'ability_types': ['text_to_image'],
252 'adapter_ability_types': ['text_to_image'],
253 'input_modalities': ['text'],
254 'adapter_input_modalities': ['text'],
255 'api_contract_verified': True}},
256 'gpt-image-2': {'name': 'GPT Image 2',
257 'provider': 'openai',
258 'type': ['t2i', 'i2i'],
259 'concurrency': 3,
260 'price_per_image': 1.09,
261 'capabilities': {'ability_type': 'image_generation',
262 'ability_types': ['text_to_image', 'image_to_image', 'reference_image'],
263 'adapter_ability_types': ['text_to_image',
264 'image_to_image',
265 'reference_image'],
266 'input_modalities': ['text', 'image'],
267 'adapter_input_modalities': ['text', 'image'],
268 'api_contract_verified': True}},
269 'wan2.6-i2v-flash': {'name': 'Wan 2.6 I2V Flash',
270 'provider': 'dashscope',
271 'family': 'wan',
272 'type': ['video'],
273 'concurrency': 5,
274 'price_per_second': 0.6,
275 'capabilities': {'ability_type': 'image_to_video',
276 'ability_types': ['first_frame_i2v',
277 'audio_driven_i2v',
278 'multi_shot',
279 'fast_generation'],
280 'adapter_ability_types': ['first_frame_i2v'],
281 'input_modalities': ['text', 'image', 'audio'],
282 'adapter_input_modalities': ['text', 'image'],
283 'duration': {'min': 2, 'max': 15, 'integer': True, 'verified': True},
284 'resolutions': ['720P', '1080P'],
285 'ratios': ['16:9', '9:16', '1:1', '4:3', '3:4'],
286 'api_contract_verified': True}},
287 'wan2.7-i2v': {'name': 'Wan 2.7 I2V',
288 'provider': 'dashscope',
289 'family': 'wan',
290 'type': ['video'],
291 'concurrency': 5,
292 'price_per_second': 0.8,
293 'capabilities': {'ability_type': 'image_to_video',
294 'ability_types': ['first_frame_i2v',
295 'start_end_frame_i2v',
296 'video_continuation',
297 'audio_driven_i2v',
298 'multi_shot'],
299 'adapter_ability_types': ['first_frame_i2v',
300 'start_end_frame_i2v',
301 'audio_driven_i2v'],
302 'input_modalities': ['text', 'image', 'audio', 'video'],
303 'adapter_input_modalities': ['text', 'image'],
304 'duration': {'min': 2, 'max': 15, 'integer': True, 'verified': True},
305 'resolutions': ['720P', '1080P'],
306 'ratios': ['16:9', '9:16', '1:1', '4:3', '3:4'],
307 'api_contract_verified': True}},
308 'happyhorse-1.0-i2v': {'name': 'Happy Horse 1.0 I2V',
309 'provider': 'dashscope',
310 'family': 'happyhorse',
311 'type': ['video'],
312 'concurrency': 5,
313 'price_per_second': 1.0,
314 'capabilities': {'ability_type': 'image_to_video',
315 'ability_types': ['first_frame_i2v', 'native_audio'],
316 'adapter_ability_types': ['first_frame_i2v', 'native_audio'],
317 'input_modalities': ['text', 'image'],
318 'adapter_input_modalities': ['text', 'image'],
319 'duration': {'min': 3,
320 'max': 15,
321 'integer': True,
322 'verified': True},
323 'resolutions': ['720P', '1080P'],
324 'api_contract_verified': True}},
325 'happyhorse-1.1-i2v': {'name': 'Happy Horse 1.1 I2V',
326 'provider': 'dashscope',
327 'family': 'happyhorse',
328 'type': ['video'],
329 'concurrency': 5,
330 'price_per_second': 1.0,
331 'capabilities': {'ability_type': 'image_to_video',
332 'ability_types': ['first_frame_i2v', 'native_audio'],
333 'adapter_ability_types': ['first_frame_i2v', 'native_audio'],
334 'input_modalities': ['text', 'image'],
335 'adapter_input_modalities': ['text', 'image'],
336 'duration': {'min': 3,
337 'max': 15,
338 'integer': True,
339 'verified': True},
340 'resolutions': ['720P', '1080P'],
341 'api_contract_verified': True}},
342 'kling-v3': {'name': 'Kling V3',
343 'provider': 'kling',
344 'type': ['video'],
345 'concurrency': 10,
346 'price_per_second': 1.0,
347 'capabilities': {'ability_type': 'image_to_video',
348 'ability_types': ['text_to_video',
349 'image_to_video',
350 'start_end_frame_i2v',
351 'native_audio',
352 'multi_shot',
353 'element_reference'],
354 'adapter_ability_types': ['first_frame_i2v', 'native_audio'],
355 'input_modalities': ['text', 'image'],
356 'adapter_input_modalities': ['text', 'image'],
357 'duration': {'min': 3, 'max': 15, 'integer': True, 'verified': True},
358 'resolutions': ['720P', '1080P'],
359 'api_contract_verified': True}},
360 'kling-v2-6': {'name': 'Kling V2.6',
361 'provider': 'kling',
362 'type': ['video'],
363 'concurrency': 10,
364 'price_per_second': 0.5,
365 'capabilities': {'ability_type': 'image_to_video',
366 'ability_types': ['text_to_video',
367 'image_to_video',
368 'start_end_frame_i2v',
369 'native_audio'],
370 'adapter_ability_types': ['first_frame_i2v', 'native_audio'],
371 'input_modalities': ['text', 'image'],
372 'adapter_input_modalities': ['text', 'image'],
373 'api_contract_verified': True}},
374 'kling-v2-5-turbo': {'name': 'Kling V2.5 Turbo',
375 'provider': 'kling',
376 'type': ['video'],
377 'concurrency': 10,
378 'price_per_second': 0.3,
379 'capabilities': {'ability_type': 'image_to_video',
380 'ability_types': ['image_to_video'],
381 'adapter_ability_types': ['first_frame_i2v', 'native_audio'],
382 'input_modalities': ['text', 'image'],
383 'adapter_input_modalities': ['text', 'image'],
384 'api_contract_verified': True}},
385 'doubao-seedance-2-0-260128': {'name': 'Seedance 2.0',
386 'provider': 'ark',
387 'family': 'seedance',
388 'type': ['video'],
389 'concurrency': 10,
390 'price_per_second': 0.5,
391 'capabilities': {'ability_type': 'image_to_video',
392 'ability_types': ['text_to_video', 'image_to_video'],
393 'adapter_ability_types': ['first_frame_i2v',
394 'native_audio'],
395 'input_modalities': ['text', 'image'],
396 'adapter_input_modalities': ['text', 'image'],
397 'duration': {'min': 2,
398 'max': 12,
399 'integer': True,
400 'verified': True},
401 'resolutions': ['720p', '1080p'],
402 'ratios': ['16:9',
403 '4:3',
404 '1:1',
405 '3:4',
406 '9:16',
407 '21:9',
408 'adaptive'],
409 'api_contract_verified': True}},
410 'doubao-seedance-2-0-fast-260128': {'name': 'Seedance 2.0 Fast',
411 'provider': 'ark',
412 'family': 'seedance',
413 'type': ['video'],
414 'concurrency': 10,
415 'price_per_second': 0.3,
416 'capabilities': {'ability_type': 'image_to_video',
417 'ability_types': ['text_to_video',
418 'image_to_video',
419 'fast_generation'],
420 'adapter_ability_types': ['first_frame_i2v',
421 'native_audio'],
422 'input_modalities': ['text', 'image'],
423 'adapter_input_modalities': ['text', 'image'],
424 'duration': {'min': 2,
425 'max': 12,
426 'integer': True,
427 'verified': True},
428 'resolutions': ['720p', '1080p'],
429 'ratios': ['16:9',
430 '4:3',
431 '1:1',
432 '3:4',
433 '9:16',
434 '21:9',
435 'adaptive'],
436 'api_contract_verified': True}},
437 'doubao-seedance-2-0-mini-260615': {'name': 'Seedance 2.0 Mini',
438 'provider': 'ark',
439 'family': 'seedance',
440 'type': ['video'],
441 'concurrency': 10,
442 'price_per_second': 0.2,
443 'capabilities': {'ability_type': 'image_to_video',
444 'ability_types': ['text_to_video',
445 'image_to_video',
446 'fast_generation'],
447 'adapter_ability_types': ['first_frame_i2v',
448 'native_audio'],
449 'input_modalities': ['text', 'image'],
450 'adapter_input_modalities': ['text', 'image'],
451 'duration': {'min': 2,
452 'max': 12,
453 'integer': True,
454 'verified': True},
455 'resolutions': ['720p', '1080p'],
456 'ratios': ['16:9',
457 '4:3',
458 '1:1',
459 '3:4',
460 '9:16',
461 '21:9',
462 'adaptive'],
463 'api_contract_verified': True}},
464 'wan2.7-r2v': {'name': 'Wan 2.7 R2V',
465 'provider': 'dashscope',
466 'family': 'wan',
467 'type': ['video'],
468 'concurrency': 5,
469 'price_per_second': 0.8,
470 'capabilities': {'ability_type': 'reference_to_video',
471 'ability_types': ['reference_to_video',
472 'digital_human',
473 'multi_character',
474 'native_audio',
475 'voice_reference',
476 'multi_shot'],
477 'adapter_ability_types': ['reference_to_video',
478 'digital_human',
479 'voice_reference'],
480 'input_modalities': ['text', 'image', 'audio', 'video'],
481 'adapter_input_modalities': ['text', 'image', 'audio'],
482 'duration': {'min': 2, 'max': 10, 'integer': True, 'verified': True},
483 'resolutions': ['720P', '1080P'],
484 'ratios': ['16:9', '9:16', '1:1', '4:3', '3:4'],
485 'api_contract_verified': True}},
486 'wan2.7-videoedit': {'name': 'Wan 2.7 Video Edit',
487 'provider': 'dashscope',
488 'family': 'wan',
489 'type': ['video'],
490 'concurrency': 5,
491 'price_per_second': 0.8,
492 'capabilities': {'ability_type': 'video_editing',
493 'ability_types': ['video_editing',
494 'action_transfer',
495 'instruction_editing',
496 'video_transfer'],
497 'adapter_ability_types': ['action_transfer', 'video_editing'],
498 'input_modalities': ['text', 'image', 'video'],
499 'adapter_input_modalities': ['text', 'image', 'video'],
500 'duration': {'min': 2, 'max': 10, 'integer': True, 'verified': True},
501 'resolutions': ['720P', '1080P'],
502 'ratios': ['16:9', '9:16', '1:1', '4:3', '3:4'],
503 'api_contract_verified': True}},
504 'happyhorse-1.0-r2v': {'name': 'Happy Horse 1.0 R2V',
505 'provider': 'dashscope',
506 'family': 'happyhorse',
507 'type': ['video'],
508 'concurrency': 5,
509 'price_per_second': 1.0,
510 'capabilities': {'ability_type': 'reference_to_video',
511 'ability_types': ['reference_to_video',
512 'digital_human',
513 'multi_character',
514 'native_audio',
515 'multi_shot'],
516 'adapter_ability_types': ['reference_to_video', 'digital_human'],
517 'input_modalities': ['text', 'image'],
518 'adapter_input_modalities': ['text', 'image'],
519 'duration': {'min': 3,
520 'max': 15,
521 'integer': True,
522 'verified': True},
523 'resolutions': ['720P', '1080P'],
524 'ratios': ['16:9', '9:16', '3:4', '4:3', '1:1'],
525 'api_contract_verified': True}},
526 'happyhorse-1.1-r2v': {'name': 'Happy Horse 1.1 R2V',
527 'provider': 'dashscope',
528 'family': 'happyhorse',
529 'type': ['video'],
530 'concurrency': 5,
531 'price_per_second': 1.0,
532 'capabilities': {'ability_type': 'reference_to_video',
533 'ability_types': ['reference_to_video',
534 'digital_human',
535 'multi_character',
536 'native_audio',
537 'multi_shot'],
538 'adapter_ability_types': ['reference_to_video', 'digital_human'],
539 'input_modalities': ['text', 'image'],
540 'adapter_input_modalities': ['text', 'image'],
541 'duration': {'min': 3,
542 'max': 15,
543 'integer': True,
544 'verified': True},
545 'resolutions': ['720P', '1080P'],
546 'ratios': ['16:9', '9:16', '3:4', '4:3', '1:1'],
547 'api_contract_verified': True}},
548 'happyhorse-1.0-video-edit': {'name': 'Happy Horse 1.0 Video Edit',
549 'provider': 'dashscope',
550 'family': 'happyhorse',
551 'type': ['video'],
552 'concurrency': 5,
553 'price_per_second': 1.0,
554 'capabilities': {'ability_type': 'video_editing',
555 'ability_types': ['video_editing',
556 'action_transfer',
557 'instruction_editing',
558 'native_audio'],
559 'adapter_ability_types': ['action_transfer',
560 'video_editing'],
561 'input_modalities': ['text', 'image', 'video'],
562 'adapter_input_modalities': ['text', 'image', 'video'],
563 'duration': {'min': 3,
564 'max': 15,
565 'integer': True,
566 'verified': True},
567 'resolutions': ['720P', '1080P'],
568 'api_contract_verified': True}},
569 'seedance-1-0-pro': {'name': 'Seedance 1.0 Pro',
570 'provider': 'ark',
571 'family': 'seedance',
572 'type': ['video'],
573 'concurrency': 10,
574 'price_per_second': 0.5,
575 'capabilities': {'ability_type': 'image_to_video',
576 'ability_types': [],
577 'adapter_ability_types': ['first_frame_i2v'],
578 'api_contract_verified': False}},
579 'seedance-1-0-lite': {'name': 'Seedance 1.0 Lite',
580 'provider': 'ark',
581 'family': 'seedance',
582 'type': ['video'],
583 'concurrency': 10,
584 'price_per_second': 0.3,
585 'capabilities': {'ability_type': 'image_to_video',
586 'ability_types': [],
587 'adapter_ability_types': ['first_frame_i2v'],
588 'api_contract_verified': False}}}}
589
590
591 def load_model_config() -> dict[str, Any]:
592 return MODEL_CONFIG
593
594
595 def get_model_config(model: str) -> dict[str, Any]:
596 """Get metadata for one model, with a loose fallback for provider aliases."""
597 models = MODEL_CONFIG.get("models", {})
598 if model in models:
599 return models[model]
600
601 model_lower = model.lower()
602 for key, value in models.items():
603 if key in model_lower or model_lower in key:
604 return value
605
606 return {
607 "name": model,
608 "provider": "unknown",
609 "type": [],
610 "concurrency": 3,
611 }
612
613
614 def get_max_concurrency(model: str, enable_concurrency: bool = False) -> int:
615 if not enable_concurrency:
616 return 1
617 return int(get_model_config(model).get("concurrency", 3))
618
619
620 def get_models_by_type(model_type: str) -> list[dict[str, Any]]:
621 result = []
622 for model_id, metadata in MODEL_CONFIG.get("models", {}).items():
623 if model_type in (metadata.get("type") or []):
624 result.append({"id": model_id, **metadata})
625 return result
626
627
628 def model_type_capabilities(model_type: str, metadata: Optional[dict[str, Any]] = None) -> dict[str, Any]:
629 """Return normalized capability metadata for non-media model selectors."""
630 metadata = metadata or {}
631 if metadata.get("capabilities"):
632 return deepcopy(metadata["capabilities"])
633 if model_type == "llm":
634 return {
635 "ability_type": "text_generation",
636 "ability_types": ["text_generation"],
637 "adapter_ability_types": ["text_generation"],
638 "input_modalities": ["text"],
639 "adapter_input_modalities": ["text"],
640 "api_contract_verified": bool(metadata.get("api_contract_verified", False)),
641 }
642 if model_type == "vlm":
643 return {
644 "ability_type": "vision_language",
645 "ability_types": ["vision_language", "image_understanding"],
646 "adapter_ability_types": ["vision_language", "image_understanding"],
647 "input_modalities": ["text", "image"],
648 "adapter_input_modalities": ["text", "image"],
649 "api_contract_verified": bool(metadata.get("api_contract_verified", False)),
650 }
651 return media_capabilities(metadata.get("provider", ""), metadata.get("id", ""), "video" if model_type == "video" else "image")
652
653
654 def model_records(media_type: Optional[str] = None) -> list[dict[str, Any]]:
655 records = []
656 for model_id, metadata in MODEL_CONFIG.get("models", {}).items():
657 resolved_media_type = _resolve_media_type(metadata.get("type") or [])
658 if media_type and resolved_media_type != media_type:
659 continue
660 if resolved_media_type not in {"image", "video"}:
661 continue
662 records.append(_workflow_info(model_id, metadata, resolved_media_type))
663 return records
664
665
666 def list_api_models(
667 media_type: Optional[str] = None,
668 required_adapter_abilities: Optional[list[str]] = None,
669 verified_only: bool = False,
670 ) -> list[dict[str, Any]]:
671 records = model_records(media_type=media_type)
672 required = set(required_adapter_abilities or [])
673 if verified_only:
674 records = [record for record in records if record.get("api_contract_verified", True)]
675 if required:
676 records = [record for record in records if required.intersection(model_ability_tags(record))]
677 return records
678
679
680 def parse_api_model(model: str, media_type: str) -> tuple[str, str]:
681 if model and model.startswith("api/"):
682 _, provider, model_id = model.split("/", 2)
683 return provider, model_id
684
685 metadata = MODEL_CONFIG.get("models", {}).get(model)
686 if metadata and _resolve_media_type(metadata.get("type") or []) == media_type:
687 return metadata.get("provider", ""), model
688 return "", model
689
690
691 def media_capabilities(provider: str, model: str, media_type: str) -> dict[str, Any]:
692 metadata = MODEL_CONFIG.get("models", {}).get(model, {})
693 capabilities = metadata.get("capabilities")
694 if capabilities:
695 return deepcopy(capabilities)
696 if media_type == "image":
697 return {
698 "ability_type": "image_generation",
699 "ability_types": ["text_to_image"],
700 "adapter_ability_types": ["text_to_image"],
701 "input_modalities": ["text"],
702 "adapter_input_modalities": ["text"],
703 "api_contract_verified": False,
704 }
705 return {
706 "ability_type": "image_to_video",
707 "ability_types": [],
708 "adapter_ability_types": ["first_frame_i2v"],
709 "input_modalities": ["text", "image"],
710 "adapter_input_modalities": ["text", "image"],
711 "api_contract_verified": False,
712 "duration": {"min": 3, "max": 15, "integer": True, "verified": False},
713 }
714
715
716 def video_capabilities(provider: str, model: str) -> dict[str, Any]:
717 return media_capabilities(provider, model, "video")
718
719
720 def image_capabilities(provider: str, model: str) -> dict[str, Any]:
721 return media_capabilities(provider, model, "image")
722
723
724 def model_ability_tags(record: dict[str, Any]) -> set[str]:
725 tags = set(record.get("adapter_ability_types") or [])
726 tags.update(record.get("ability_types") or [])
727 if record.get("ability_type"):
728 tags.add(record["ability_type"])
729 tags.update(record.get("type") or [])
730 return tags
731
732
733 def _workflow_info(model_id: str, metadata: dict[str, Any], media_type: str) -> dict[str, Any]:
734 provider = metadata.get("provider", "")
735 capabilities = media_capabilities(provider, model_id, media_type)
736 return {
737 "key": f"api/{provider}/{model_id}",
738 "name": model_id,
739 "display_name": f"{metadata.get('name') or model_id} - API {provider.title()}",
740 "source": "api",
741 "provider": provider,
742 "family": metadata.get("family"),
743 "model": model_id,
744 "media_type": media_type,
745 "type": metadata.get("type", []),
746 "capabilities": capabilities,
747 "ability_type": capabilities.get("ability_type"),
748 "ability_types": capabilities.get("ability_types", []),
749 "adapter_ability_types": capabilities.get("adapter_ability_types", []),
750 "input_modalities": capabilities.get("input_modalities", []),
751 "adapter_input_modalities": capabilities.get("adapter_input_modalities", []),
752 "api_contract_verified": capabilities.get("api_contract_verified", False),
753 "concurrency": metadata.get("concurrency"),
754 }
755
756
757 def _resolve_media_type(types: list[str]) -> str:
758 if "video" in types:
759 return "video"
760 if any(item in types for item in ("t2i", "i2i", "image")):
761 return "image"
762 return ""
763
763 lines PYTHON