kiapi 0.1.0__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.
- kiapi/__init__.py +3 -0
- kiapi/__main__.py +4 -0
- kiapi/api/__init__.py +26 -0
- kiapi/api/_constants/require_auth.py +14 -0
- kiapi/api/_helpers/build_job_responses.py +73 -0
- kiapi/api/_helpers/build_openapi.py +55 -0
- kiapi/api/_helpers/build_train_responses.py +21 -0
- kiapi/api/_helpers/get_accept.py +20 -0
- kiapi/api/_helpers/get_ctx.py +7 -0
- kiapi/api/_helpers/get_worker.py +7 -0
- kiapi/api/_helpers/register_capability_endpoints.py +70 -0
- kiapi/api/_helpers/submit_and_maybe_wait.py +113 -0
- kiapi/api/_settings.py +50 -0
- kiapi/api/_views/async_job_response.py +21 -0
- kiapi/api/_views/capability_model_spec.py +49 -0
- kiapi/api/app.py +150 -0
- kiapi/api/audio/acestep/_views/extract_response.py +57 -0
- kiapi/api/audio/acestep/_views/track_response.py +57 -0
- kiapi/api/audio/acestep/router.py +190 -0
- kiapi/api/audio/audiogen/_views/audio_response.py +46 -0
- kiapi/api/audio/audiogen/router.py +77 -0
- kiapi/api/chat/_views/chat_completion_response.py +71 -0
- kiapi/api/chat/router.py +253 -0
- kiapi/api/embedding/_views/embedding_response.py +35 -0
- kiapi/api/embedding/router.py +82 -0
- kiapi/api/files/_operations/get_file.py +14 -0
- kiapi/api/files/_operations/get_file_id.py +18 -0
- kiapi/api/files/_views/file_delete_response.py +14 -0
- kiapi/api/files/_views/file_list_response.py +15 -0
- kiapi/api/files/router.py +105 -0
- kiapi/api/health/_views/health_response.py +26 -0
- kiapi/api/health/router.py +28 -0
- kiapi/api/image/depthpro/_views/estimate_response.py +70 -0
- kiapi/api/image/depthpro/router.py +81 -0
- kiapi/api/image/ernie/_views/image_response.py +50 -0
- kiapi/api/image/ernie/router.py +181 -0
- kiapi/api/image/flux2/_views/image_response.py +53 -0
- kiapi/api/image/flux2/router.py +197 -0
- kiapi/api/image/ideogram4/_views/image_response.py +55 -0
- kiapi/api/image/ideogram4/router.py +87 -0
- kiapi/api/image/qwen/_views/image_response.py +47 -0
- kiapi/api/image/qwen/router.py +141 -0
- kiapi/api/image/seedvr2/_views/image_response.py +50 -0
- kiapi/api/image/seedvr2/router.py +83 -0
- kiapi/api/image/zimage/_views/image_response.py +49 -0
- kiapi/api/image/zimage/router.py +131 -0
- kiapi/api/jobs/_operations/get_job_id.py +18 -0
- kiapi/api/jobs/_views/job_delete_response.py +19 -0
- kiapi/api/jobs/_views/job_list_response.py +15 -0
- kiapi/api/jobs/router.py +62 -0
- kiapi/api/models/_schemas/openai_model_spec.py +26 -0
- kiapi/api/models/_views/model_list_response.py +15 -0
- kiapi/api/models/router.py +29 -0
- kiapi/api/video/ltx2/_views/__init__.py +1 -0
- kiapi/api/video/ltx2/_views/video_response.py +59 -0
- kiapi/api/video/ltx2/router.py +89 -0
- kiapi/api/web/_views/__init__.py +7 -0
- kiapi/api/web/_views/fetch_api_request.py +26 -0
- kiapi/api/web/_views/fetch_error_response.py +25 -0
- kiapi/api/web/router.py +302 -0
- kiapi/capabilities/__init__.py +24 -0
- kiapi/capabilities/_exceptions/CapabilityError.py +2 -0
- kiapi/capabilities/_exceptions/ValidationError.py +2 -0
- kiapi/capabilities/_helpers/attach_mflux_progress.py +50 -0
- kiapi/capabilities/_helpers/get_file_path.py +20 -0
- kiapi/capabilities/_helpers/resolve_file_ref.py +16 -0
- kiapi/capabilities/acestep/README.ja.md +174 -0
- kiapi/capabilities/acestep/README.md +167 -0
- kiapi/capabilities/acestep/__init__.py +37 -0
- kiapi/capabilities/acestep/_constants/description.py +122 -0
- kiapi/capabilities/acestep/_exceptions/ace_step_engine_error.py +2 -0
- kiapi/capabilities/acestep/_helpers/register.py +96 -0
- kiapi/capabilities/acestep/_models/acestep.py +105 -0
- kiapi/capabilities/acestep/_operations/handle_cover.py +23 -0
- kiapi/capabilities/acestep/_operations/handle_extract.py +38 -0
- kiapi/capabilities/acestep/_operations/handle_generate.py +18 -0
- kiapi/capabilities/acestep/_operations/handle_repaint.py +25 -0
- kiapi/capabilities/acestep/_operations/resolve_ace_step_paths.py +30 -0
- kiapi/capabilities/acestep/_operations/resolve_cover_params.py +21 -0
- kiapi/capabilities/acestep/_operations/resolve_engine.py +10 -0
- kiapi/capabilities/acestep/_operations/resolve_extract_params.py +24 -0
- kiapi/capabilities/acestep/_operations/resolve_generate_params.py +18 -0
- kiapi/capabilities/acestep/_operations/resolve_repaint_params.py +22 -0
- kiapi/capabilities/acestep/_schemas/ace_step_paths.py +29 -0
- kiapi/capabilities/acestep/_services/ace_step_engine.py +143 -0
- kiapi/capabilities/acestep/_settings.py +82 -0
- kiapi/capabilities/acestep/_views/ace_step_base.py +61 -0
- kiapi/capabilities/acestep/_views/cover_params.py +26 -0
- kiapi/capabilities/acestep/_views/cover_request.py +49 -0
- kiapi/capabilities/acestep/_views/extract_params.py +24 -0
- kiapi/capabilities/acestep/_views/extract_request.py +33 -0
- kiapi/capabilities/acestep/_views/generate_params.py +25 -0
- kiapi/capabilities/acestep/_views/generate_request.py +54 -0
- kiapi/capabilities/acestep/_views/repaint_params.py +27 -0
- kiapi/capabilities/acestep/_views/repaint_request.py +51 -0
- kiapi/capabilities/acestep/_views/track_params.py +7 -0
- kiapi/capabilities/acestep/worker_subprocess.py +247 -0
- kiapi/capabilities/audiogen/README.ja.md +49 -0
- kiapi/capabilities/audiogen/README.md +48 -0
- kiapi/capabilities/audiogen/__init__.py +25 -0
- kiapi/capabilities/audiogen/_constants/description.py +60 -0
- kiapi/capabilities/audiogen/_helpers/attach_audiogen_progress.py +27 -0
- kiapi/capabilities/audiogen/_helpers/register.py +42 -0
- kiapi/capabilities/audiogen/_helpers/validate_generate.py +14 -0
- kiapi/capabilities/audiogen/_models/audiogen.py +98 -0
- kiapi/capabilities/audiogen/_operations/handle_generate.py +27 -0
- kiapi/capabilities/audiogen/_operations/resolve_generate_params.py +25 -0
- kiapi/capabilities/audiogen/_settings.py +34 -0
- kiapi/capabilities/audiogen/_views/generate_params.py +14 -0
- kiapi/capabilities/audiogen/_views/generate_request.py +96 -0
- kiapi/capabilities/chat/README.ja.md +822 -0
- kiapi/capabilities/chat/README.md +806 -0
- kiapi/capabilities/chat/__init__.py +25 -0
- kiapi/capabilities/chat/_constants/description.py +163 -0
- kiapi/capabilities/chat/_helpers/register.py +67 -0
- kiapi/capabilities/chat/_models/qwen3_5.py +183 -0
- kiapi/capabilities/chat/_models/qwen3_omni.py +242 -0
- kiapi/capabilities/chat/_operations/apply_template.py +74 -0
- kiapi/capabilities/chat/_operations/completed_hermes_tool_call_text.py +9 -0
- kiapi/capabilities/chat/_operations/emit_streaming_response.py +297 -0
- kiapi/capabilities/chat/_operations/ensure_streaming_detokenizer_compat.py +49 -0
- kiapi/capabilities/chat/_operations/format_response.py +72 -0
- kiapi/capabilities/chat/_operations/handle_chat.py +25 -0
- kiapi/capabilities/chat/_operations/parse_hermes_tool_calls.py +63 -0
- kiapi/capabilities/chat/_operations/parse_json_tool_calls.py +66 -0
- kiapi/capabilities/chat/_operations/parse_messages.py +326 -0
- kiapi/capabilities/chat/_operations/resolve_chat_params.py +50 -0
- kiapi/capabilities/chat/_operations/stream_text_from_tokens.py +32 -0
- kiapi/capabilities/chat/_operations/strip_tool_calls.py +15 -0
- kiapi/capabilities/chat/_settings.py +74 -0
- kiapi/capabilities/chat/_utils/apply_parallel_tool_call_policy.py +17 -0
- kiapi/capabilities/chat/_utils/apply_seed.py +8 -0
- kiapi/capabilities/chat/_utils/load_audio_mono.py +31 -0
- kiapi/capabilities/chat/_utils/load_mlx_vlm.py +12 -0
- kiapi/capabilities/chat/_utils/warmup_params.py +26 -0
- kiapi/capabilities/chat/_views/chat_params.py +34 -0
- kiapi/capabilities/chat/_views/chat_request.py +153 -0
- kiapi/capabilities/depthpro/README.ja.md +131 -0
- kiapi/capabilities/depthpro/README.md +124 -0
- kiapi/capabilities/depthpro/__init__.py +23 -0
- kiapi/capabilities/depthpro/_constants/description.py +39 -0
- kiapi/capabilities/depthpro/_helpers/register.py +46 -0
- kiapi/capabilities/depthpro/_helpers/validate_estimate.py +32 -0
- kiapi/capabilities/depthpro/_models/depthpro.py +144 -0
- kiapi/capabilities/depthpro/_operations/handle_estimate.py +50 -0
- kiapi/capabilities/depthpro/_operations/is_quantize_override.py +12 -0
- kiapi/capabilities/depthpro/_operations/resolve_estimate_params.py +24 -0
- kiapi/capabilities/depthpro/_settings.py +46 -0
- kiapi/capabilities/depthpro/_views/estimate_params.py +21 -0
- kiapi/capabilities/depthpro/_views/estimate_request.py +63 -0
- kiapi/capabilities/embedding/README.ja.md +137 -0
- kiapi/capabilities/embedding/README.md +128 -0
- kiapi/capabilities/embedding/__init__.py +24 -0
- kiapi/capabilities/embedding/_constants/description.py +47 -0
- kiapi/capabilities/embedding/_helpers/register.py +66 -0
- kiapi/capabilities/embedding/_models/qwen3_text.py +53 -0
- kiapi/capabilities/embedding/_models/qwen3_vl.py +97 -0
- kiapi/capabilities/embedding/_operations/format_response.py +24 -0
- kiapi/capabilities/embedding/_operations/handle_embed.py +24 -0
- kiapi/capabilities/embedding/_operations/parse_inputs.py +108 -0
- kiapi/capabilities/embedding/_operations/resolve_embed_params.py +18 -0
- kiapi/capabilities/embedding/_settings.py +27 -0
- kiapi/capabilities/embedding/_utils/to_vector.py +20 -0
- kiapi/capabilities/embedding/_views/embed_params.py +17 -0
- kiapi/capabilities/embedding/_views/embed_request.py +39 -0
- kiapi/capabilities/ernie/README.ja.md +242 -0
- kiapi/capabilities/ernie/README.md +223 -0
- kiapi/capabilities/ernie/__init__.py +34 -0
- kiapi/capabilities/ernie/_constants/description.py +71 -0
- kiapi/capabilities/ernie/_helpers/register.py +60 -0
- kiapi/capabilities/ernie/_helpers/validate_edit.py +19 -0
- kiapi/capabilities/ernie/_helpers/validate_generate.py +8 -0
- kiapi/capabilities/ernie/_models/ernie.py +356 -0
- kiapi/capabilities/ernie/_operations/handle_edit.py +39 -0
- kiapi/capabilities/ernie/_operations/handle_generate.py +38 -0
- kiapi/capabilities/ernie/_operations/handle_train.py +77 -0
- kiapi/capabilities/ernie/_operations/is_quantize_override.py +16 -0
- kiapi/capabilities/ernie/_operations/resolve_edit_params.py +44 -0
- kiapi/capabilities/ernie/_operations/resolve_generate_params.py +40 -0
- kiapi/capabilities/ernie/_operations/resolve_lora_params.py +23 -0
- kiapi/capabilities/ernie/_operations/resolve_train_params.py +43 -0
- kiapi/capabilities/ernie/_operations/validate_common.py +33 -0
- kiapi/capabilities/ernie/_schemas/lora_ref.py +27 -0
- kiapi/capabilities/ernie/_settings.py +151 -0
- kiapi/capabilities/ernie/_views/edit_params.py +36 -0
- kiapi/capabilities/ernie/_views/edit_request.py +142 -0
- kiapi/capabilities/ernie/_views/generate_params.py +32 -0
- kiapi/capabilities/ernie/_views/generate_request.py +127 -0
- kiapi/capabilities/ernie/_views/train_params.py +28 -0
- kiapi/capabilities/ernie/_views/train_request.py +96 -0
- kiapi/capabilities/flux2/README.ja.md +260 -0
- kiapi/capabilities/flux2/README.md +241 -0
- kiapi/capabilities/flux2/__init__.py +36 -0
- kiapi/capabilities/flux2/_constants/description.py +81 -0
- kiapi/capabilities/flux2/_helpers/register.py +98 -0
- kiapi/capabilities/flux2/_helpers/validate_edit.py +12 -0
- kiapi/capabilities/flux2/_helpers/validate_generate.py +8 -0
- kiapi/capabilities/flux2/_models/flux2.py +396 -0
- kiapi/capabilities/flux2/_operations/handle_edit.py +44 -0
- kiapi/capabilities/flux2/_operations/handle_generate.py +46 -0
- kiapi/capabilities/flux2/_operations/handle_train.py +105 -0
- kiapi/capabilities/flux2/_operations/is_quantize_override.py +16 -0
- kiapi/capabilities/flux2/_operations/resolve_edit_params.py +43 -0
- kiapi/capabilities/flux2/_operations/resolve_generate_params.py +44 -0
- kiapi/capabilities/flux2/_operations/resolve_lora_params.py +23 -0
- kiapi/capabilities/flux2/_operations/resolve_train_params.py +42 -0
- kiapi/capabilities/flux2/_operations/role_spec.py +13 -0
- kiapi/capabilities/flux2/_operations/split_repo.py +11 -0
- kiapi/capabilities/flux2/_operations/validate_common.py +35 -0
- kiapi/capabilities/flux2/_schemas/lora_ref.py +27 -0
- kiapi/capabilities/flux2/_settings.py +181 -0
- kiapi/capabilities/flux2/_views/edit_params.py +36 -0
- kiapi/capabilities/flux2/_views/edit_request.py +145 -0
- kiapi/capabilities/flux2/_views/generate_params.py +36 -0
- kiapi/capabilities/flux2/_views/generate_request.py +150 -0
- kiapi/capabilities/flux2/_views/train_params.py +32 -0
- kiapi/capabilities/flux2/_views/train_request.py +102 -0
- kiapi/capabilities/ideogram4/README.ja.md +230 -0
- kiapi/capabilities/ideogram4/README.md +219 -0
- kiapi/capabilities/ideogram4/__init__.py +24 -0
- kiapi/capabilities/ideogram4/_constants/description.py +65 -0
- kiapi/capabilities/ideogram4/_helpers/register.py +44 -0
- kiapi/capabilities/ideogram4/_helpers/validate_generate.py +31 -0
- kiapi/capabilities/ideogram4/_models/ideogram4.py +120 -0
- kiapi/capabilities/ideogram4/_operations/handle_generate.py +30 -0
- kiapi/capabilities/ideogram4/_operations/is_quantize_override.py +12 -0
- kiapi/capabilities/ideogram4/_operations/resolve_generate_params.py +30 -0
- kiapi/capabilities/ideogram4/_settings.py +89 -0
- kiapi/capabilities/ideogram4/_views/generate_params.py +27 -0
- kiapi/capabilities/ideogram4/_views/generate_request.py +118 -0
- kiapi/capabilities/ltx2/README.ja.md +287 -0
- kiapi/capabilities/ltx2/README.md +274 -0
- kiapi/capabilities/ltx2/__init__.py +26 -0
- kiapi/capabilities/ltx2/_constants/description.py +81 -0
- kiapi/capabilities/ltx2/_helpers/register.py +58 -0
- kiapi/capabilities/ltx2/_helpers/validate_generate.py +42 -0
- kiapi/capabilities/ltx2/_models/ltx2.py +106 -0
- kiapi/capabilities/ltx2/_operations/detect_mode.py +19 -0
- kiapi/capabilities/ltx2/_operations/handle_generate.py +52 -0
- kiapi/capabilities/ltx2/_operations/resolve_generate_params.py +31 -0
- kiapi/capabilities/ltx2/_settings.py +93 -0
- kiapi/capabilities/ltx2/_views/generate_params.py +34 -0
- kiapi/capabilities/ltx2/_views/generate_request.py +151 -0
- kiapi/capabilities/qwen/README.ja.md +251 -0
- kiapi/capabilities/qwen/README.md +235 -0
- kiapi/capabilities/qwen/__init__.py +30 -0
- kiapi/capabilities/qwen/_constants/description.py +53 -0
- kiapi/capabilities/qwen/_helpers/register.py +60 -0
- kiapi/capabilities/qwen/_helpers/validate_edit.py +12 -0
- kiapi/capabilities/qwen/_helpers/validate_generate.py +12 -0
- kiapi/capabilities/qwen/_models/qwen.py +224 -0
- kiapi/capabilities/qwen/_operations/handle_edit.py +44 -0
- kiapi/capabilities/qwen/_operations/handle_generate.py +46 -0
- kiapi/capabilities/qwen/_operations/is_quantize_override.py +15 -0
- kiapi/capabilities/qwen/_operations/resolve_edit_params.py +35 -0
- kiapi/capabilities/qwen/_operations/resolve_generate_params.py +39 -0
- kiapi/capabilities/qwen/_operations/resolve_lora_params.py +23 -0
- kiapi/capabilities/qwen/_operations/validate_common.py +32 -0
- kiapi/capabilities/qwen/_schemas/lora_ref.py +27 -0
- kiapi/capabilities/qwen/_settings.py +107 -0
- kiapi/capabilities/qwen/_views/edit_params.py +36 -0
- kiapi/capabilities/qwen/_views/edit_request.py +138 -0
- kiapi/capabilities/qwen/_views/generate_params.py +37 -0
- kiapi/capabilities/qwen/_views/generate_request.py +149 -0
- kiapi/capabilities/seedvr2/README.ja.md +234 -0
- kiapi/capabilities/seedvr2/README.md +219 -0
- kiapi/capabilities/seedvr2/__init__.py +24 -0
- kiapi/capabilities/seedvr2/_constants/description.py +46 -0
- kiapi/capabilities/seedvr2/_helpers/register.py +60 -0
- kiapi/capabilities/seedvr2/_helpers/validate_upscale.py +52 -0
- kiapi/capabilities/seedvr2/_models/seedvr2.py +140 -0
- kiapi/capabilities/seedvr2/_operations/handle_upscale.py +42 -0
- kiapi/capabilities/seedvr2/_operations/is_quantize_override.py +12 -0
- kiapi/capabilities/seedvr2/_operations/resolve_upscale_params.py +30 -0
- kiapi/capabilities/seedvr2/_settings.py +74 -0
- kiapi/capabilities/seedvr2/_views/upscale_params.py +27 -0
- kiapi/capabilities/seedvr2/_views/upscale_request.py +94 -0
- kiapi/capabilities/web/README.ja.md +187 -0
- kiapi/capabilities/web/README.md +171 -0
- kiapi/capabilities/web/__init__.py +30 -0
- kiapi/capabilities/web/_constants/description.py +38 -0
- kiapi/capabilities/web/_exceptions/FetchError.py +24 -0
- kiapi/capabilities/web/_exceptions/SearchBackendError.py +11 -0
- kiapi/capabilities/web/_helpers/register.py +59 -0
- kiapi/capabilities/web/_models/crawl4ai.py +32 -0
- kiapi/capabilities/web/_models/searxng.py +44 -0
- kiapi/capabilities/web/_operations/handle_fetch.py +145 -0
- kiapi/capabilities/web/_operations/handle_search.py +56 -0
- kiapi/capabilities/web/_operations/resolve_fetch_params.py +19 -0
- kiapi/capabilities/web/_operations/resolve_search_params.py +34 -0
- kiapi/capabilities/web/_operations/resolve_searxng_config_dir.py +21 -0
- kiapi/capabilities/web/_services/__init__.py +1 -0
- kiapi/capabilities/web/_services/web_backend_process.py +144 -0
- kiapi/capabilities/web/_settings.py +138 -0
- kiapi/capabilities/web/_views/fetch_params.py +27 -0
- kiapi/capabilities/web/_views/fetch_request.py +29 -0
- kiapi/capabilities/web/_views/fetch_result.py +20 -0
- kiapi/capabilities/web/_views/search_params.py +43 -0
- kiapi/capabilities/web/_views/search_request.py +93 -0
- kiapi/capabilities/web/_views/search_response.py +49 -0
- kiapi/capabilities/web/resources/__init__.py +0 -0
- kiapi/capabilities/web/resources/searxng/__init__.py +0 -0
- kiapi/capabilities/web/resources/searxng/settings.yml +11 -0
- kiapi/capabilities/zimage/README.ja.md +249 -0
- kiapi/capabilities/zimage/README.md +231 -0
- kiapi/capabilities/zimage/__init__.py +34 -0
- kiapi/capabilities/zimage/_constants/description.py +73 -0
- kiapi/capabilities/zimage/_helpers/register.py +62 -0
- kiapi/capabilities/zimage/_helpers/validate_generate.py +37 -0
- kiapi/capabilities/zimage/_models/zimage.py +278 -0
- kiapi/capabilities/zimage/_operations/handle_generate.py +38 -0
- kiapi/capabilities/zimage/_operations/handle_train.py +78 -0
- kiapi/capabilities/zimage/_operations/is_quantize_override.py +11 -0
- kiapi/capabilities/zimage/_operations/resolve_generate_params.py +39 -0
- kiapi/capabilities/zimage/_operations/resolve_lora_params.py +23 -0
- kiapi/capabilities/zimage/_operations/resolve_train_params.py +38 -0
- kiapi/capabilities/zimage/_schemas/lora_ref.py +27 -0
- kiapi/capabilities/zimage/_settings.py +166 -0
- kiapi/capabilities/zimage/_views/generate_params.py +23 -0
- kiapi/capabilities/zimage/_views/generate_request.py +127 -0
- kiapi/capabilities/zimage/_views/train_params.py +22 -0
- kiapi/capabilities/zimage/_views/train_request.py +101 -0
- kiapi/cli/__init__.py +11 -0
- kiapi/cli/_helpers/dedupe_resources.py +12 -0
- kiapi/cli/_helpers/register_all_capabilities.py +36 -0
- kiapi/cli/_helpers/run_resources.py +24 -0
- kiapi/cli/_helpers/select_specs.py +67 -0
- kiapi/cli/activate/_operations/preflight_hf_access.py +129 -0
- kiapi/cli/activate/cli.py +37 -0
- kiapi/cli/check/_helpers/check_audio_acestep_turbo.py +22 -0
- kiapi/cli/check/_helpers/check_audio_acestep_xl_base.py +22 -0
- kiapi/cli/check/_helpers/check_audio_audiogen_medium.py +24 -0
- kiapi/cli/check/_helpers/check_chat_chat_qwen3_6_27b.py +19 -0
- kiapi/cli/check/_helpers/check_chat_chat_qwen3_omni.py +19 -0
- kiapi/cli/check/_helpers/check_embedding_embedding_qwen3_embedding_8b.py +14 -0
- kiapi/cli/check/_helpers/check_embedding_embedding_qwen3_vl_embedding_2b.py +19 -0
- kiapi/cli/check/_helpers/check_image_depthpro_base.py +21 -0
- kiapi/cli/check/_helpers/check_image_ernie_base.py +23 -0
- kiapi/cli/check/_helpers/check_image_ernie_turbo.py +23 -0
- kiapi/cli/check/_helpers/check_image_flux2_klein_9b.py +23 -0
- kiapi/cli/check/_helpers/check_image_flux2_klein_base_4b.py +23 -0
- kiapi/cli/check/_helpers/check_image_flux2_klein_base_9b.py +23 -0
- kiapi/cli/check/_helpers/check_image_ideogram4_fp8.py +23 -0
- kiapi/cli/check/_helpers/check_image_qwen_edit_2509.py +27 -0
- kiapi/cli/check/_helpers/check_image_qwen_image.py +23 -0
- kiapi/cli/check/_helpers/check_image_seedvr2_3b.py +23 -0
- kiapi/cli/check/_helpers/check_image_seedvr2_7b.py +23 -0
- kiapi/cli/check/_helpers/check_image_zimage_base.py +23 -0
- kiapi/cli/check/_helpers/check_image_zimage_turbo.py +23 -0
- kiapi/cli/check/_helpers/check_video_ltx2_distilled.py +26 -0
- kiapi/cli/check/_helpers/check_web_web_fetch.py +14 -0
- kiapi/cli/check/_helpers/check_web_web_search.py +14 -0
- kiapi/cli/check/_operations/build_check_result.py +20 -0
- kiapi/cli/check/_operations/check_context.py +10 -0
- kiapi/cli/check/_operations/create_sample_image.py +46 -0
- kiapi/cli/check/_schemas/check_result.py +12 -0
- kiapi/cli/check/cli.py +89 -0
- kiapi/cli/cli.py +29 -0
- kiapi/cli/config/cli.py +17 -0
- kiapi/cli/config/edit/cli.py +32 -0
- kiapi/cli/config/init/cli.py +33 -0
- kiapi/cli/config/show/cli.py +13 -0
- kiapi/cli/config/template/cli.py +36 -0
- kiapi/cli/deactivate/cli.py +49 -0
- kiapi/cli/run/asgi.py +16 -0
- kiapi/cli/run/cli.py +59 -0
- kiapi/cli/service/__init__.py +5 -0
- kiapi/cli/service/_services/service_manager.py +107 -0
- kiapi/cli/service/cli.py +19 -0
- kiapi/cli/service/install/cli.py +17 -0
- kiapi/cli/service/start/cli.py +22 -0
- kiapi/cli/service/status/cli.py +81 -0
- kiapi/cli/service/stop/cli.py +19 -0
- kiapi/cli/service/uninstall/cli.py +16 -0
- kiapi/cli/status/cli.py +226 -0
- kiapi/core/app/__init__.py +16 -0
- kiapi/core/app/_schemas/app_context.py +28 -0
- kiapi/core/app/_services/user_directory.py +59 -0
- kiapi/core/app/_settings.py +46 -0
- kiapi/core/capability/__init__.py +11 -0
- kiapi/core/capability/_schemas/capability_spec.py +17 -0
- kiapi/core/capability/_services/capability_spec_registry.py +21 -0
- kiapi/core/config/__init__.py +11 -0
- kiapi/core/config/_exceptions/user_config_error.py +2 -0
- kiapi/core/config/_helpers/get_user_settings_path.py +7 -0
- kiapi/core/config/_helpers/load_user_settings.py +54 -0
- kiapi/core/file/__init__.py +37 -0
- kiapi/core/file/_helpers/create_file_store.py +7 -0
- kiapi/core/file/_helpers/resolve_file_ref.py +90 -0
- kiapi/core/file/_schemas/file_data_url_ref.py +19 -0
- kiapi/core/file/_schemas/file_id_ref.py +16 -0
- kiapi/core/file/_schemas/file_record.py +46 -0
- kiapi/core/file/_schemas/file_url_ref.py +16 -0
- kiapi/core/file/_services/file_store.py +138 -0
- kiapi/core/file/_settings.py +22 -0
- kiapi/core/file/_types/file_id.py +2 -0
- kiapi/core/file/_types/file_ref.py +12 -0
- kiapi/core/file/_views/resolved_file_ref.py +16 -0
- kiapi/core/job/__init__.py +24 -0
- kiapi/core/job/_enums/job_status.py +9 -0
- kiapi/core/job/_helpers/creep_progress.py +123 -0
- kiapi/core/job/_models/job.py +121 -0
- kiapi/core/job/_services/job_store.py +33 -0
- kiapi/core/job/_services/progress_reporter.py +79 -0
- kiapi/core/job/_types/job_id.py +3 -0
- kiapi/core/job/_types/job_result.py +5 -0
- kiapi/core/job/_types/job_type.py +4 -0
- kiapi/core/logging/__init__.py +14 -0
- kiapi/core/logging/_helpers/get_log_level.py +8 -0
- kiapi/core/logging/_helpers/setup_logger.py +14 -0
- kiapi/core/logging/_settings.py +34 -0
- kiapi/core/logging/_types/log_level.py +3 -0
- kiapi/core/memory/__init__.py +21 -0
- kiapi/core/memory/_exceptions/memory_budget_error.py +2 -0
- kiapi/core/memory/_helpers/create_memory_manager.py +17 -0
- kiapi/core/memory/_operations/log_memory_limit.py +29 -0
- kiapi/core/memory/_operations/resolve_effective_memory_limit_gb.py +13 -0
- kiapi/core/memory/_operations/resolve_framework_strategy.py +42 -0
- kiapi/core/memory/_schemas/framework_strategy.py +14 -0
- kiapi/core/memory/_schemas/memory_stats.py +24 -0
- kiapi/core/memory/_schemas/resident_model.py +14 -0
- kiapi/core/memory/_schemas/resident_model_stats.py +49 -0
- kiapi/core/memory/_services/memory_manager.py +257 -0
- kiapi/core/memory/_settings.py +36 -0
- kiapi/core/memory/_types/framework_name.py +4 -0
- kiapi/core/memory/_types/resident_key.py +4 -0
- kiapi/core/model/__init__.py +36 -0
- kiapi/core/model/_exceptions/unknown_model_error.py +2 -0
- kiapi/core/model/_schemas/model_spec.py +145 -0
- kiapi/core/model/_services/model_registry.py +85 -0
- kiapi/core/model/_types/model_alias.py +2 -0
- kiapi/core/model/_types/model_domain.py +6 -0
- kiapi/core/model/_types/model_family.py +5 -0
- kiapi/core/model/_types/model_key.py +8 -0
- kiapi/core/model/_types/model_name.py +4 -0
- kiapi/core/model/_types/model_repo.py +3 -0
- kiapi/core/net/__init__.py +16 -0
- kiapi/core/net/_exceptions/unsafe_url_error.py +7 -0
- kiapi/core/net/_helpers/verify_public_url.py +68 -0
- kiapi/core/net/_settings.py +28 -0
- kiapi/core/setup/__init__.py +25 -0
- kiapi/core/setup/_exceptions/setup_required_error.py +2 -0
- kiapi/core/setup/_schemas/docker_image_resource.py +37 -0
- kiapi/core/setup/_schemas/hf_snapshot_resource.py +50 -0
- kiapi/core/setup/_schemas/local_path_resource.py +43 -0
- kiapi/core/setup/_schemas/python_package_resource.py +58 -0
- kiapi/core/setup/_schemas/python_venv_resource.py +63 -0
- kiapi/core/setup/_schemas/setup_status.py +7 -0
- kiapi/core/setup/_schemas/url_file_resource.py +43 -0
- kiapi/core/setup/_services/setup_manager.py +331 -0
- kiapi/core/setup/_types/setup_resource.py +21 -0
- kiapi/core/setup/_types/setup_target.py +11 -0
- kiapi/core/workdir/__init__.py +8 -0
- kiapi/core/workdir/_helpers/create_work_dir.py +11 -0
- kiapi/core/workdir/_settings.py +21 -0
- kiapi/core/worker/__init__.py +23 -0
- kiapi/core/worker/_helpers/create_worker.py +9 -0
- kiapi/core/worker/_services/worker.py +165 -0
- kiapi/core/worker/_settings.py +36 -0
- kiapi/core/worker/_types/job_thunk.py +6 -0
- kiapi-0.1.0.dist-info/METADATA +337 -0
- kiapi-0.1.0.dist-info/RECORD +465 -0
- kiapi-0.1.0.dist-info/WHEEL +4 -0
- kiapi-0.1.0.dist-info/entry_points.txt +2 -0
- kiapi-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"""Shared helper: parse OpenAI-style multimodal ``messages`` into the Qwen
|
|
2
|
+
chat-template shape. Model handlers call this with the set of modalities they
|
|
3
|
+
support (``allow=``); anything outside that set is rejected with 400.
|
|
4
|
+
|
|
5
|
+
Recognised content parts (lenient — aliases accepted):
|
|
6
|
+
|
|
7
|
+
text : {"type": "text", "text": "..."}
|
|
8
|
+
image : {"type": "image_url", "image_url": {"url": "data:image/...;base64,..." | "http..." }}
|
|
9
|
+
aliases: {"type": "image", "image": <url|data|path>}, {"type": "input_image", ...}
|
|
10
|
+
audio : {"type": "input_audio", "input_audio": {"data": "<base64>", "format": "wav"}}
|
|
11
|
+
aliases: {"type": "audio", "audio": <url|data|path>}, {"type": "audio_url", ...}
|
|
12
|
+
video : {"type": "video_url", "video_url": {"url": "data:video/...;base64,..." | "http..." }}
|
|
13
|
+
aliases: {"type": "video", "video": <url|data|path>}, {"type": "input_video", ...}
|
|
14
|
+
|
|
15
|
+
Each media source (data URL, http(s) URL, or bare base64) is materialized to a
|
|
16
|
+
file under ``tmp_dir``. Server-side local paths are intentionally not accepted,
|
|
17
|
+
so a chat message cannot read arbitrary files off the host. The function returns:
|
|
18
|
+
|
|
19
|
+
- ``template_messages``: same conversation, but every media part rewritten to
|
|
20
|
+
the Qwen-native form ({"type": "image"/"audio"/"video", <key>: <path>}) so the
|
|
21
|
+
processor's chat_template inserts placeholders in document order.
|
|
22
|
+
- ``images`` / ``audios`` / ``videos``: ordered lists of file paths, collected in
|
|
23
|
+
the SAME walk order, to hand to ``generate(image=, audio=, video=)``.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import base64
|
|
27
|
+
import binascii
|
|
28
|
+
import json
|
|
29
|
+
import re
|
|
30
|
+
import subprocess
|
|
31
|
+
import urllib.request
|
|
32
|
+
import uuid
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
from typing import Any
|
|
35
|
+
|
|
36
|
+
from kiapi.core.net import UnsafeURLError, verify_public_url
|
|
37
|
+
|
|
38
|
+
_DATA_URL = re.compile(r"^data:(?P<mime>[\w./+-]+)?;base64,(?P<data>.*)$", re.DOTALL)
|
|
39
|
+
|
|
40
|
+
_EXT_BY_MIME = {
|
|
41
|
+
"image/png": ".png",
|
|
42
|
+
"image/jpeg": ".jpg",
|
|
43
|
+
"image/jpg": ".jpg",
|
|
44
|
+
"image/webp": ".webp",
|
|
45
|
+
"image/gif": ".gif",
|
|
46
|
+
"audio/wav": ".wav",
|
|
47
|
+
"audio/x-wav": ".wav",
|
|
48
|
+
"audio/mpeg": ".mp3",
|
|
49
|
+
"audio/mp3": ".mp3",
|
|
50
|
+
"audio/mp4": ".m4a",
|
|
51
|
+
"audio/ogg": ".ogg",
|
|
52
|
+
"audio/flac": ".flac",
|
|
53
|
+
"video/mp4": ".mp4",
|
|
54
|
+
"video/quicktime": ".mov",
|
|
55
|
+
"video/webm": ".webm",
|
|
56
|
+
}
|
|
57
|
+
_DEFAULT_EXT = {"image": ".png", "audio": ".wav", "video": ".mp4"}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class MediaError(ValueError):
|
|
61
|
+
"""Bad/undecodable media in a message; mapped to HTTP 400 by the API layer."""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class CapabilityError(MediaError):
|
|
65
|
+
"""A modality the selected model does not support; mapped to HTTP 400."""
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def parse_messages(
|
|
69
|
+
messages: list[dict[str, Any]],
|
|
70
|
+
tmp_dir: Path,
|
|
71
|
+
*,
|
|
72
|
+
allow: set[str] | None = None,
|
|
73
|
+
use_audio_in_video: bool = True,
|
|
74
|
+
) -> tuple[list[dict[str, Any]], list[str], list[str], list[str]]:
|
|
75
|
+
"""Parse messages into (template_messages, images, audios, videos).
|
|
76
|
+
|
|
77
|
+
``allow`` is the set of modalities the selected model supports
|
|
78
|
+
(e.g. ``{"text", "image"}``). A part outside it raises :class:`CapabilityError`.
|
|
79
|
+
``None`` allows everything (used by tests).
|
|
80
|
+
"""
|
|
81
|
+
template_messages: list[dict[str, Any]] = []
|
|
82
|
+
images: list[str] = []
|
|
83
|
+
audios: list[str] = []
|
|
84
|
+
videos: list[str] = []
|
|
85
|
+
|
|
86
|
+
def _check(kind: str) -> None:
|
|
87
|
+
if allow is not None and kind not in allow:
|
|
88
|
+
raise CapabilityError(
|
|
89
|
+
f"{kind} input is not supported by the selected model"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
for raw_msg in messages:
|
|
93
|
+
msg = _normalize_tool_calls_for_template(raw_msg)
|
|
94
|
+
role = msg.get("role", "user")
|
|
95
|
+
content = msg.get("content")
|
|
96
|
+
|
|
97
|
+
if content is None or isinstance(content, str):
|
|
98
|
+
# Pass plain text / tool fields (tool_calls, tool_call_id, name) through.
|
|
99
|
+
template_messages.append(dict(msg))
|
|
100
|
+
continue
|
|
101
|
+
|
|
102
|
+
if not isinstance(content, list):
|
|
103
|
+
raise MediaError(f"message.content must be a string or list (role={role})")
|
|
104
|
+
|
|
105
|
+
new_content: list[dict[str, Any]] = []
|
|
106
|
+
for part in content:
|
|
107
|
+
if not isinstance(part, dict):
|
|
108
|
+
raise MediaError("each content part must be an object")
|
|
109
|
+
kind, source = _extract_part(part)
|
|
110
|
+
|
|
111
|
+
if kind == "text":
|
|
112
|
+
new_content.append({"type": "text", "text": source or ""})
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
_check(kind)
|
|
116
|
+
|
|
117
|
+
fmt = None
|
|
118
|
+
if isinstance(source, tuple): # (data, format) from input_audio/input_video
|
|
119
|
+
source, fmt = source
|
|
120
|
+
path = _materialize(source, kind, tmp_dir, fmt=fmt)
|
|
121
|
+
|
|
122
|
+
if kind == "image":
|
|
123
|
+
images.append(path)
|
|
124
|
+
new_content.append({"type": "image", "image": path})
|
|
125
|
+
elif kind == "audio":
|
|
126
|
+
audios.append(path)
|
|
127
|
+
new_content.append({"type": "audio", "audio": path})
|
|
128
|
+
else: # video
|
|
129
|
+
videos.append(path)
|
|
130
|
+
new_content.append({"type": "video", "video": path})
|
|
131
|
+
# A video that carries sound: demux its audio track and feed it as
|
|
132
|
+
# an audio input too (placeholder right after the video), so the
|
|
133
|
+
# model hears the clip, not just sees it.
|
|
134
|
+
if use_audio_in_video and (allow is None or "audio" in allow):
|
|
135
|
+
apath = _extract_audio(path, tmp_dir)
|
|
136
|
+
if apath is not None:
|
|
137
|
+
audios.append(apath)
|
|
138
|
+
new_content.append({"type": "audio", "audio": apath})
|
|
139
|
+
|
|
140
|
+
out = dict(msg)
|
|
141
|
+
out["content"] = new_content
|
|
142
|
+
template_messages.append(out)
|
|
143
|
+
|
|
144
|
+
return template_messages, images, audios, videos
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _template_tool_arguments(arguments: Any) -> dict[str, Any]:
|
|
148
|
+
"""Convert OpenAI's JSON-string tool args to the mapping Qwen templates expect."""
|
|
149
|
+
if isinstance(arguments, dict):
|
|
150
|
+
return arguments
|
|
151
|
+
if isinstance(arguments, str):
|
|
152
|
+
try:
|
|
153
|
+
parsed = json.loads(arguments)
|
|
154
|
+
except json.JSONDecodeError:
|
|
155
|
+
return {"_raw": arguments}
|
|
156
|
+
if isinstance(parsed, dict):
|
|
157
|
+
return parsed
|
|
158
|
+
return {"value": parsed}
|
|
159
|
+
if arguments is None:
|
|
160
|
+
return {}
|
|
161
|
+
return {"value": arguments}
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _normalize_tool_calls_for_template(msg: dict[str, Any]) -> dict[str, Any]:
|
|
165
|
+
"""Return a copy of msg with tool-call history shaped for HF chat templates."""
|
|
166
|
+
out = dict(msg)
|
|
167
|
+
calls = out.get("tool_calls")
|
|
168
|
+
if not isinstance(calls, list):
|
|
169
|
+
return out
|
|
170
|
+
|
|
171
|
+
if out.get("content") is None:
|
|
172
|
+
out["content"] = ""
|
|
173
|
+
|
|
174
|
+
normalized = []
|
|
175
|
+
for call in calls:
|
|
176
|
+
if not isinstance(call, dict):
|
|
177
|
+
normalized.append(call)
|
|
178
|
+
continue
|
|
179
|
+
ncall = dict(call)
|
|
180
|
+
fn = ncall.get("function")
|
|
181
|
+
if isinstance(fn, dict):
|
|
182
|
+
nfn = dict(fn)
|
|
183
|
+
if "arguments" in nfn:
|
|
184
|
+
nfn["arguments"] = _template_tool_arguments(nfn["arguments"])
|
|
185
|
+
ncall["function"] = nfn
|
|
186
|
+
elif "arguments" in ncall:
|
|
187
|
+
ncall["arguments"] = _template_tool_arguments(ncall["arguments"])
|
|
188
|
+
normalized.append(ncall)
|
|
189
|
+
|
|
190
|
+
out["tool_calls"] = normalized
|
|
191
|
+
return out
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _write_bytes(data: bytes, tmp_dir: Path, kind: str, ext: str) -> str:
|
|
195
|
+
path = tmp_dir / f"{kind}_{uuid.uuid4().hex}{ext}"
|
|
196
|
+
path.write_bytes(data)
|
|
197
|
+
return str(path)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _materialize(
|
|
201
|
+
source: str, kind: str, tmp_dir: Path, *, fmt: str | None = None
|
|
202
|
+
) -> str:
|
|
203
|
+
"""Turn a data URL / http(s) URL / bare base64 into a local file path."""
|
|
204
|
+
if not isinstance(source, str) or not source:
|
|
205
|
+
raise MediaError(f"{kind}: empty or non-string source")
|
|
206
|
+
|
|
207
|
+
m = _DATA_URL.match(source)
|
|
208
|
+
if m:
|
|
209
|
+
mime = m.group("mime") or ""
|
|
210
|
+
ext = _EXT_BY_MIME.get(mime, _DEFAULT_EXT[kind])
|
|
211
|
+
try:
|
|
212
|
+
return _write_bytes(base64.b64decode(m.group("data")), tmp_dir, kind, ext)
|
|
213
|
+
except (binascii.Error, ValueError) as exc:
|
|
214
|
+
raise MediaError(f"{kind}: invalid base64 data URL ({exc})") # noqa: B904
|
|
215
|
+
|
|
216
|
+
if source.startswith(("http://", "https://")):
|
|
217
|
+
try:
|
|
218
|
+
verify_public_url(source, kind=kind)
|
|
219
|
+
except UnsafeURLError as exc:
|
|
220
|
+
raise MediaError(f"{kind}: {exc}") # noqa: B904
|
|
221
|
+
try:
|
|
222
|
+
with urllib.request.urlopen(source, timeout=30) as resp:
|
|
223
|
+
data = resp.read()
|
|
224
|
+
except Exception as exc:
|
|
225
|
+
raise MediaError(f"{kind}: failed to fetch {source!r} ({exc})") # noqa: B904
|
|
226
|
+
ext = Path(source.split("?")[0]).suffix or _DEFAULT_EXT[kind]
|
|
227
|
+
return _write_bytes(data, tmp_dir, kind, ext)
|
|
228
|
+
|
|
229
|
+
# Bare base64 (e.g. input_audio.data without a data: prefix). Server-side
|
|
230
|
+
# local paths are intentionally NOT accepted: a chat message must not be able
|
|
231
|
+
# to read arbitrary files off the host.
|
|
232
|
+
try:
|
|
233
|
+
data = base64.b64decode(source, validate=True)
|
|
234
|
+
except (binascii.Error, ValueError):
|
|
235
|
+
raise MediaError(f"{kind}: source is not a data URL, URL, or base64") # noqa: B904
|
|
236
|
+
ext = (
|
|
237
|
+
_EXT_BY_MIME.get(f"{kind}/{fmt}", _DEFAULT_EXT[kind])
|
|
238
|
+
if fmt
|
|
239
|
+
else _DEFAULT_EXT[kind]
|
|
240
|
+
)
|
|
241
|
+
return _write_bytes(data, tmp_dir, kind, ext)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _extract_part(part: dict[str, Any]) -> tuple[str, Any]:
|
|
245
|
+
"""Return (kind, source) for one content part. kind ∈ text|image|audio|video."""
|
|
246
|
+
t = part.get("type")
|
|
247
|
+
|
|
248
|
+
if t == "text":
|
|
249
|
+
return "text", part.get("text", "")
|
|
250
|
+
|
|
251
|
+
if (
|
|
252
|
+
t in ("image_url", "image", "input_image")
|
|
253
|
+
or "image_url" in part
|
|
254
|
+
or "image" in part
|
|
255
|
+
):
|
|
256
|
+
if "image_url" in part:
|
|
257
|
+
iu = part["image_url"]
|
|
258
|
+
return "image", iu.get("url") if isinstance(iu, dict) else iu
|
|
259
|
+
return "image", part.get("image") or part.get("input_image")
|
|
260
|
+
|
|
261
|
+
if (
|
|
262
|
+
t in ("input_audio", "audio", "audio_url")
|
|
263
|
+
or "input_audio" in part
|
|
264
|
+
or "audio_url" in part
|
|
265
|
+
):
|
|
266
|
+
if "input_audio" in part:
|
|
267
|
+
ia = part["input_audio"]
|
|
268
|
+
if isinstance(ia, dict):
|
|
269
|
+
return "audio", (ia.get("data"), ia.get("format"))
|
|
270
|
+
return "audio", ia
|
|
271
|
+
if "audio_url" in part:
|
|
272
|
+
au = part["audio_url"]
|
|
273
|
+
return "audio", au.get("url") if isinstance(au, dict) else au
|
|
274
|
+
return "audio", part.get("audio")
|
|
275
|
+
|
|
276
|
+
if (
|
|
277
|
+
t in ("video_url", "video", "input_video")
|
|
278
|
+
or "video_url" in part
|
|
279
|
+
or "video" in part
|
|
280
|
+
):
|
|
281
|
+
if "video_url" in part:
|
|
282
|
+
vu = part["video_url"]
|
|
283
|
+
return "video", vu.get("url") if isinstance(vu, dict) else vu
|
|
284
|
+
if "input_video" in part:
|
|
285
|
+
iv = part["input_video"]
|
|
286
|
+
if isinstance(iv, dict):
|
|
287
|
+
return "video", (iv.get("data"), iv.get("format"))
|
|
288
|
+
return "video", iv
|
|
289
|
+
return "video", part.get("video")
|
|
290
|
+
|
|
291
|
+
raise MediaError(f"unsupported content part type: {t!r}")
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _extract_audio(video_path: str, tmp_dir: Path) -> str | None:
|
|
295
|
+
"""Demux a video's audio track to mono 16 kHz wav. None if it has no audio."""
|
|
296
|
+
wav = tmp_dir / f"audio_{uuid.uuid4().hex}.wav"
|
|
297
|
+
try:
|
|
298
|
+
# If the video has no audio stream, ffmpeg will fail/exit with an error,
|
|
299
|
+
# and either not create the file or leave it empty.
|
|
300
|
+
subprocess.run(
|
|
301
|
+
[
|
|
302
|
+
"ffmpeg",
|
|
303
|
+
"-v",
|
|
304
|
+
"error",
|
|
305
|
+
"-y",
|
|
306
|
+
"-i",
|
|
307
|
+
video_path,
|
|
308
|
+
"-vn",
|
|
309
|
+
"-ac",
|
|
310
|
+
"1",
|
|
311
|
+
"-ar",
|
|
312
|
+
"16000",
|
|
313
|
+
str(wav),
|
|
314
|
+
],
|
|
315
|
+
capture_output=True,
|
|
316
|
+
timeout=120,
|
|
317
|
+
)
|
|
318
|
+
except (OSError, subprocess.SubprocessError):
|
|
319
|
+
return None
|
|
320
|
+
|
|
321
|
+
if wav.exists() and wav.stat().st_size > 0:
|
|
322
|
+
return str(wav)
|
|
323
|
+
|
|
324
|
+
if wav.exists():
|
|
325
|
+
wav.unlink(missing_ok=True)
|
|
326
|
+
return None
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Merge a chat request with settings defaults into the complete ChatParams.
|
|
2
|
+
|
|
3
|
+
Resolves and caps the sampling knobs and fills multimodal defaults once, here,
|
|
4
|
+
so the per-model ``run`` works purely from :class:`ChatParams`. Model-specific
|
|
5
|
+
template switches (e.g. Qwen3.6's ``enable_thinking`` default) stay in the model;
|
|
6
|
+
this only passes ``chat_template_kwargs`` through verbatim.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .._settings import ChatSettings
|
|
10
|
+
from .._views.chat_params import ChatParams
|
|
11
|
+
from .._views.chat_request import ChatRequest
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def resolve_chat_params(
|
|
15
|
+
settings: ChatSettings,
|
|
16
|
+
req: ChatRequest,
|
|
17
|
+
*,
|
|
18
|
+
variant: str,
|
|
19
|
+
) -> ChatParams:
|
|
20
|
+
requested_max_tokens = req.max_completion_tokens
|
|
21
|
+
if requested_max_tokens is None:
|
|
22
|
+
requested_max_tokens = settings.default_max_tokens
|
|
23
|
+
max_tokens = min(requested_max_tokens, settings.max_tokens_cap)
|
|
24
|
+
|
|
25
|
+
temperature = (
|
|
26
|
+
req.temperature if req.temperature is not None else settings.default_temperature
|
|
27
|
+
)
|
|
28
|
+
top_p = req.top_p if req.top_p is not None else settings.default_top_p
|
|
29
|
+
fps = req.fps if req.fps is not None else settings.default_fps
|
|
30
|
+
use_audio_in_video = (
|
|
31
|
+
req.use_audio_in_video
|
|
32
|
+
if req.use_audio_in_video is not None
|
|
33
|
+
else settings.use_audio_in_video
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
return ChatParams(
|
|
37
|
+
model=variant,
|
|
38
|
+
messages=req.messages,
|
|
39
|
+
tools=req.tools,
|
|
40
|
+
tool_choice=req.tool_choice,
|
|
41
|
+
parallel_tool_calls=req.parallel_tool_calls,
|
|
42
|
+
max_tokens=max_tokens,
|
|
43
|
+
temperature=temperature,
|
|
44
|
+
top_p=top_p,
|
|
45
|
+
seed=req.seed,
|
|
46
|
+
fps=fps,
|
|
47
|
+
use_audio_in_video=use_audio_in_video,
|
|
48
|
+
chat_template_kwargs=req.chat_template_kwargs,
|
|
49
|
+
stream=req.stream,
|
|
50
|
+
)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from collections.abc import Iterable, Iterator
|
|
2
|
+
from copy import copy
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def stream_text_from_tokens(processor, chunks: Iterable) -> Iterator: # type: ignore
|
|
6
|
+
"""Yield chunks whose text is decoded from token ids when mlx-vlm supports it."""
|
|
7
|
+
try:
|
|
8
|
+
from mlx_vlm.tokenizer_utils import ( # type: ignore
|
|
9
|
+
_ServerTokenStreamer,
|
|
10
|
+
make_streaming_detokenizer,
|
|
11
|
+
)
|
|
12
|
+
except Exception:
|
|
13
|
+
yield from chunks
|
|
14
|
+
return
|
|
15
|
+
|
|
16
|
+
tokenizer = processor.tokenizer if hasattr(processor, "tokenizer") else processor
|
|
17
|
+
streamer = _ServerTokenStreamer(tokenizer, make_streaming_detokenizer(processor))
|
|
18
|
+
|
|
19
|
+
for chunk in chunks:
|
|
20
|
+
token = getattr(chunk, "token", None)
|
|
21
|
+
finish_reason = getattr(chunk, "finish_reason", None)
|
|
22
|
+
|
|
23
|
+
if token is None:
|
|
24
|
+
text = str(getattr(chunk, "text", "") or "")
|
|
25
|
+
elif finish_reason is None:
|
|
26
|
+
text = streamer.advance(token, None)
|
|
27
|
+
else:
|
|
28
|
+
text = streamer.finalize()
|
|
29
|
+
|
|
30
|
+
out = copy(chunk)
|
|
31
|
+
out.text = text
|
|
32
|
+
yield out
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Shared helper: the assistant preamble before any tool call.
|
|
2
|
+
|
|
3
|
+
Tool calls are emitted at the end of the turn, so everything before the first
|
|
4
|
+
``<tool_call>`` tag is the natural-language preamble (often empty). Close-tag
|
|
5
|
+
independent and format-independent — both the JSON (Qwen3-Omni) and Hermes/XML
|
|
6
|
+
(Qwen3.6) formats use the same ``<tool_call>`` opening tag — so it is shared.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
_TAG = "<tool_call>"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def strip_tool_calls(full_text: str) -> str:
|
|
13
|
+
"""Return the text before the first ``<tool_call>`` tag (stripped)."""
|
|
14
|
+
tag = full_text.find(_TAG)
|
|
15
|
+
return (full_text if tag == -1 else full_text[:tag]).strip()
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Chat-capability generation defaults, read from the environment (``KIAPI_CHAT_``).
|
|
2
|
+
|
|
3
|
+
Separate from the global infra settings: each capability owns its own knobs. A
|
|
4
|
+
request may override these per call.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pydantic import Field
|
|
8
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
9
|
+
from pydantic_settings_manager import SettingsManager
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ChatSettings(BaseSettings):
|
|
13
|
+
"""Settings for Chat Completions generation defaults and input-processing limits."""
|
|
14
|
+
|
|
15
|
+
model_config = SettingsConfigDict(
|
|
16
|
+
env_prefix="KIAPI_CHAT_",
|
|
17
|
+
extra="ignore",
|
|
18
|
+
protected_namespaces=(),
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
default_max_tokens: int = Field(
|
|
22
|
+
default=512,
|
|
23
|
+
title="Default maximum generated tokens",
|
|
24
|
+
description="Generation token limit used when a request omits max_tokens.",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
default_temperature: float = Field(
|
|
28
|
+
default=0.7,
|
|
29
|
+
title="Default temperature",
|
|
30
|
+
description=(
|
|
31
|
+
"Sampling temperature used when a request omits temperature.\n"
|
|
32
|
+
"Lower values are more stable; higher values are more diverse."
|
|
33
|
+
),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
default_top_p: float = Field(
|
|
37
|
+
default=1.0,
|
|
38
|
+
title="Default top_p",
|
|
39
|
+
description=(
|
|
40
|
+
"Nucleus sampling threshold used when a request omits top_p.\n"
|
|
41
|
+
"Use a value greater than 0 and less than or equal to 1.0."
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
default_fps: float = Field(
|
|
46
|
+
default=1.0,
|
|
47
|
+
title="Default video input sampling FPS",
|
|
48
|
+
description=(
|
|
49
|
+
"Default FPS for extracting frames from video input before passing them to the model.\n"
|
|
50
|
+
"Higher values provide finer video understanding, but increase "
|
|
51
|
+
"processing time and input size."
|
|
52
|
+
),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
use_audio_in_video: bool = Field(
|
|
56
|
+
default=True,
|
|
57
|
+
title="Use audio in video",
|
|
58
|
+
description=(
|
|
59
|
+
"When true, audio tracks in video files are demuxed and passed to "
|
|
60
|
+
"the model as audio input."
|
|
61
|
+
),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
max_tokens_cap: int = Field(
|
|
65
|
+
default=4096,
|
|
66
|
+
title="Maximum generated token cap",
|
|
67
|
+
description=(
|
|
68
|
+
"Server-side upper limit for max_tokens accepted in a request.\n"
|
|
69
|
+
"This protects the single worker queue from being blocked for a long time."
|
|
70
|
+
),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
settings_manager = SettingsManager(ChatSettings)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Apply OpenAI's ``parallel_tool_calls`` response contract.
|
|
2
|
+
|
|
3
|
+
Qwen templates do not expose a native switch for this, so each model enforces it
|
|
4
|
+
after parsing the generated tool calls: when ``parallel_tool_calls`` is false, at
|
|
5
|
+
most one tool call is returned.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def apply_parallel_tool_call_policy(
|
|
12
|
+
tool_calls: list[dict[str, Any]],
|
|
13
|
+
parallel_tool_calls: bool,
|
|
14
|
+
) -> list[dict[str, Any]]:
|
|
15
|
+
if parallel_tool_calls is False:
|
|
16
|
+
return tool_calls[:1]
|
|
17
|
+
return tool_calls
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Load an audio file as a mono float32 array at the target sampling rate.
|
|
2
|
+
|
|
3
|
+
Replaces ``mlx_vlm.utils.load_audio`` for the Qwen3-Omni audio path. That helper
|
|
4
|
+
has a bug: ``read_audio`` returns audio channel-last ``(samples, channels)``, but
|
|
5
|
+
``load_audio`` then resamples with ``resample_audio(..., axis=-1)`` (the *channel*
|
|
6
|
+
axis) while downmixing with ``mean(axis=1)`` (also the channel axis). The two are
|
|
7
|
+
inconsistent, so a stereo clip whose rate differs from the target (e.g. a 48 kHz
|
|
8
|
+
stereo wav) is never actually resampled on the time axis — its 48 kHz samples are
|
|
9
|
+
handed to the model as if they were 16 kHz, and Omni can't recognize the speech.
|
|
10
|
+
Mono clips happen to work because their only axis is the time axis.
|
|
11
|
+
|
|
12
|
+
Here we downmix to mono *first* (so the array is 1-D) and only then resample, so
|
|
13
|
+
``resample_audio``'s default ``axis=-1`` is the time axis and the rate conversion
|
|
14
|
+
is correct. Drop this once mlx-vlm fixes ``load_audio``.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def load_audio_mono(path: str, sr: int) -> np.ndarray:
|
|
21
|
+
"""Read ``path`` and return a mono float32 array resampled to ``sr`` Hz."""
|
|
22
|
+
from mlx_audio.audio_io import read as read_audio # type: ignore
|
|
23
|
+
from mlx_audio.utils import resample_audio # type: ignore
|
|
24
|
+
|
|
25
|
+
audio, orig_sr = read_audio(path, dtype="float32")
|
|
26
|
+
audio = np.asarray(audio, dtype=np.float32)
|
|
27
|
+
if audio.ndim > 1: # (samples, channels) → mono, before any resample
|
|
28
|
+
audio = audio.mean(axis=1)
|
|
29
|
+
if orig_sr != sr:
|
|
30
|
+
audio = resample_audio(audio, orig_sr, sr) # 1-D, so axis=-1 is time
|
|
31
|
+
return np.asarray(audio, dtype=np.float32)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Load an mlx-vlm model + processor. Shared by every chat model's ``load()``."""
|
|
2
|
+
|
|
3
|
+
from types import SimpleNamespace
|
|
4
|
+
|
|
5
|
+
from kiapi.core.model import ModelSpec
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def load_mlx_vlm(spec: ModelSpec) -> SimpleNamespace:
|
|
9
|
+
from mlx_vlm import load # type: ignore
|
|
10
|
+
|
|
11
|
+
model, processor = load(spec.repo)
|
|
12
|
+
return SimpleNamespace(model=model, processor=processor)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Build the tiny :class:`ChatParams` used to prime MLX kernels at startup.
|
|
2
|
+
|
|
3
|
+
A 1-token, deterministic request for the given model variant — each model's
|
|
4
|
+
``warmup(payload)`` feeds it to ``run`` so the first real request isn't slowed by
|
|
5
|
+
kernel compilation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .._views.chat_params import ChatParams
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def warmup_params(model: str) -> ChatParams:
|
|
12
|
+
return ChatParams(
|
|
13
|
+
model=model,
|
|
14
|
+
messages=[{"role": "user", "content": "hi"}],
|
|
15
|
+
tools=None,
|
|
16
|
+
tool_choice=None,
|
|
17
|
+
parallel_tool_calls=True,
|
|
18
|
+
max_tokens=1,
|
|
19
|
+
temperature=0.0,
|
|
20
|
+
top_p=1.0,
|
|
21
|
+
seed=None,
|
|
22
|
+
fps=1.0,
|
|
23
|
+
use_audio_in_video=False,
|
|
24
|
+
chat_template_kwargs=None,
|
|
25
|
+
stream=False,
|
|
26
|
+
)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""The complete contract for one chat completion run.
|
|
2
|
+
|
|
3
|
+
Built from settings + request by ``resolve_chat_params``; a model's ``run`` needs
|
|
4
|
+
nothing else (no ``req`` / ``settings``) to generate the completion. ``model`` is
|
|
5
|
+
the canonical model name to echo back; sampling knobs are already resolved and
|
|
6
|
+
capped. ``messages`` / ``tools`` / ``tool_choice`` are the request's raw OpenAI
|
|
7
|
+
shapes (parsed per-model via ``parse_messages`` + each model's tool format).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from pydantic import BaseModel
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ChatParams(BaseModel):
|
|
16
|
+
model: str
|
|
17
|
+
|
|
18
|
+
messages: list[dict[str, Any]]
|
|
19
|
+
tools: list[dict[str, Any]] | None
|
|
20
|
+
tool_choice: Any | None
|
|
21
|
+
parallel_tool_calls: bool
|
|
22
|
+
|
|
23
|
+
# Resolved + capped sampling knobs.
|
|
24
|
+
max_tokens: int
|
|
25
|
+
temperature: float
|
|
26
|
+
top_p: float
|
|
27
|
+
seed: int | None
|
|
28
|
+
|
|
29
|
+
# Multimodal knobs (resolved defaults).
|
|
30
|
+
fps: float
|
|
31
|
+
use_audio_in_video: bool
|
|
32
|
+
|
|
33
|
+
chat_template_kwargs: dict[str, Any] | None
|
|
34
|
+
stream: bool
|