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
kiapi/api/jobs/router.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Jobs API: list / get / delete. Every generation (sync or async) is a job."""
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, Depends, HTTPException
|
|
4
|
+
|
|
5
|
+
from kiapi.api import REQUIRE_AUTH, get_ctx
|
|
6
|
+
from kiapi.core.app import AppContext
|
|
7
|
+
from kiapi.core.job import Job, JobID, JobStatus
|
|
8
|
+
|
|
9
|
+
from ._operations.get_job_id import get_job_id
|
|
10
|
+
from ._views.job_delete_response import JobDeleteResponse
|
|
11
|
+
from ._views.job_list_response import JobListResponse
|
|
12
|
+
|
|
13
|
+
router = APIRouter(prefix="/v1/jobs", dependencies=REQUIRE_AUTH)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@router.get("", response_model=JobListResponse)
|
|
17
|
+
async def list_jobs(ctx: AppContext = Depends(get_ctx)) -> dict:
|
|
18
|
+
"""List in-memory jobs.
|
|
19
|
+
|
|
20
|
+
Every generation request creates a job, including sync requests. Jobs are
|
|
21
|
+
useful for progress polling, result inspection, and discovering artifact
|
|
22
|
+
file_ids. They are not persisted across process restarts.
|
|
23
|
+
"""
|
|
24
|
+
return {"object": "list", "data": [j.to_dict() for j in ctx.job_store.list_all()]}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@router.get("/{job_id}", response_model=Job)
|
|
28
|
+
async def get_job(
|
|
29
|
+
job_id: JobID = Depends(get_job_id),
|
|
30
|
+
ctx: AppContext = Depends(get_ctx),
|
|
31
|
+
) -> dict:
|
|
32
|
+
"""Get a job's status, progress, result, and artifacts.
|
|
33
|
+
|
|
34
|
+
Poll this endpoint after an async response or after a sync request times
|
|
35
|
+
out. When status is succeeded, artifact file ids can be downloaded through
|
|
36
|
+
the Files API.
|
|
37
|
+
"""
|
|
38
|
+
job = ctx.job_store.get(job_id)
|
|
39
|
+
if job is None:
|
|
40
|
+
raise HTTPException(status_code=404, detail=f"unknown job {job_id!r}")
|
|
41
|
+
return job.to_dict()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@router.delete("/{job_id}", response_model=JobDeleteResponse)
|
|
45
|
+
async def delete_job(
|
|
46
|
+
job_id: JobID = Depends(get_job_id),
|
|
47
|
+
ctx: AppContext = Depends(get_ctx),
|
|
48
|
+
) -> dict:
|
|
49
|
+
"""Remove a job record from the in-memory store.
|
|
50
|
+
|
|
51
|
+
Queued jobs are forgotten and effectively canceled. A running job cannot be
|
|
52
|
+
interrupted; deleting it only removes the record, and generated files are
|
|
53
|
+
managed separately by the Files API.
|
|
54
|
+
"""
|
|
55
|
+
job = ctx.job_store.get(job_id)
|
|
56
|
+
if job is None:
|
|
57
|
+
raise HTTPException(status_code=404, detail=f"unknown job {job_id!r}")
|
|
58
|
+
# A running job cannot be interrupted (MLX/generation is not preemptible);
|
|
59
|
+
# deleting it just forgets it. Queued jobs are effectively canceled.
|
|
60
|
+
running = job.status == JobStatus.RUNNING
|
|
61
|
+
ctx.job_store.delete(job_id)
|
|
62
|
+
return {"deleted": True, "job_id": job_id, "was_running": running}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class OpenAIModelSpec(BaseModel):
|
|
5
|
+
"""An OpenAI-compatible model spec (one entry in a model list)."""
|
|
6
|
+
|
|
7
|
+
id: str = Field(
|
|
8
|
+
...,
|
|
9
|
+
description="Model name to pass in the chat request model field.",
|
|
10
|
+
examples=["qwen3-omni"],
|
|
11
|
+
)
|
|
12
|
+
object: str = Field(
|
|
13
|
+
default="model",
|
|
14
|
+
description="OpenAI-compatible object marker.",
|
|
15
|
+
examples=["model"],
|
|
16
|
+
)
|
|
17
|
+
created: int = Field(
|
|
18
|
+
...,
|
|
19
|
+
description="Unix timestamp (seconds) for when the model became available.",
|
|
20
|
+
examples=[1735689600],
|
|
21
|
+
)
|
|
22
|
+
owned_by: str = Field(
|
|
23
|
+
default="kiapi",
|
|
24
|
+
description="Owner marker for OpenAI-compatible model lists.",
|
|
25
|
+
examples=["kiapi"],
|
|
26
|
+
)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
|
|
3
|
+
from .._schemas.openai_model_spec import OpenAIModelSpec
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ModelListResponse(BaseModel):
|
|
7
|
+
object: str = Field(
|
|
8
|
+
default="list",
|
|
9
|
+
description="OpenAI-style list envelope marker.",
|
|
10
|
+
examples=["list"],
|
|
11
|
+
)
|
|
12
|
+
data: list[OpenAIModelSpec] = Field(
|
|
13
|
+
default_factory=list,
|
|
14
|
+
description="Chat models available via the OpenAI-compatible /v1/models endpoint.",
|
|
15
|
+
)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter
|
|
4
|
+
|
|
5
|
+
from kiapi.core.model import model_registry
|
|
6
|
+
|
|
7
|
+
from ._schemas.openai_model_spec import OpenAIModelSpec
|
|
8
|
+
from ._views.model_list_response import ModelListResponse
|
|
9
|
+
|
|
10
|
+
router = APIRouter()
|
|
11
|
+
|
|
12
|
+
# Stable per-process timestamp used for the OpenAI-compatible ``created`` field;
|
|
13
|
+
# kiapi models don't carry a real creation date, so we report server start.
|
|
14
|
+
_CREATED = int(time.time())
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@router.get("/v1/models", response_model=ModelListResponse)
|
|
18
|
+
async def list_openai_compatible_chat_models() -> ModelListResponse:
|
|
19
|
+
"""List OpenAI-compatible chat models.
|
|
20
|
+
|
|
21
|
+
Returns the OpenAI /v1/models shape so OpenAI chat clients work unchanged.
|
|
22
|
+
Other capabilities expose richer, family-specific model lists at
|
|
23
|
+
/v1/{domain}/{family}/models.
|
|
24
|
+
"""
|
|
25
|
+
data = [
|
|
26
|
+
OpenAIModelSpec(id=spec.name, created=_CREATED)
|
|
27
|
+
for spec in model_registry.list_specs("chat")
|
|
28
|
+
]
|
|
29
|
+
return ModelListResponse(data=data)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""API response views for the LTX-2 video endpoints."""
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""``/v1/video/ltx2/generate`` result payload (the Job ``result`` shape).
|
|
2
|
+
|
|
3
|
+
Mirrors the dict returned by ``capabilities/ltx2/_models/ltx2.run_generate`` so
|
|
4
|
+
the MP4 result is self-describing in OpenAPI. It is an output-side projection of
|
|
5
|
+
the model result, so it lives under ``api/video/ltx2/_views``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Literal
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, Field
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class _Timings(BaseModel):
|
|
14
|
+
total_s: float = Field(
|
|
15
|
+
description="Wall-clock generation time in seconds (model run only)."
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class VideoResponse(BaseModel):
|
|
20
|
+
"""Capability-specific ``result`` for a succeeded LTX-2 generation job."""
|
|
21
|
+
|
|
22
|
+
file_id: str = Field(
|
|
23
|
+
description=(
|
|
24
|
+
"Files-API id of the produced MP4. Fetch metadata at GET /v1/files/{id} "
|
|
25
|
+
"or bytes at /download. This is also the artifact returned as raw "
|
|
26
|
+
"bytes by a single-artifact sync call."
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
video_bytes: int = Field(description="Size of the produced MP4 in bytes.")
|
|
30
|
+
|
|
31
|
+
mode: Literal[
|
|
32
|
+
"T2V",
|
|
33
|
+
"I2V",
|
|
34
|
+
"I2V(first+last)",
|
|
35
|
+
"I2V(last)",
|
|
36
|
+
"A2V",
|
|
37
|
+
"A2V+I2V",
|
|
38
|
+
"T2V+Audio",
|
|
39
|
+
] = Field(
|
|
40
|
+
description=(
|
|
41
|
+
"Detected generation mode, inferred from image/end_image/audio inputs "
|
|
42
|
+
"and `generate_audio`."
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
prompt: str = Field(description="Prompt used for the generation.")
|
|
46
|
+
params: dict[str, Any] = Field(
|
|
47
|
+
description=(
|
|
48
|
+
"Resolved parameters actually used for the run (prompt, dimensions, "
|
|
49
|
+
"frame count, fps, seed, conditioning strengths, and generate_audio), "
|
|
50
|
+
"so the result is reproducible."
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
has_audio: bool = Field(
|
|
54
|
+
description=(
|
|
55
|
+
"Whether an auxiliary audio track was produced or supplied and muxed "
|
|
56
|
+
"into the MP4."
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
timings: _Timings = Field(description="kiapi extension: server-side timing.")
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Video API (ltx2): ``POST /v1/video/ltx2/generate`` (JSON; sync or async)."""
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, Depends, HTTPException
|
|
4
|
+
from fastapi.responses import Response
|
|
5
|
+
|
|
6
|
+
from kiapi.api import (
|
|
7
|
+
REQUIRE_AUTH,
|
|
8
|
+
build_job_responses,
|
|
9
|
+
get_accept,
|
|
10
|
+
get_ctx,
|
|
11
|
+
get_worker,
|
|
12
|
+
register_capability_endpoints,
|
|
13
|
+
submit_and_maybe_wait,
|
|
14
|
+
)
|
|
15
|
+
from kiapi.capabilities.ltx2 import (
|
|
16
|
+
GenerateRequest,
|
|
17
|
+
ValidationError,
|
|
18
|
+
detect_mode,
|
|
19
|
+
handle_generate,
|
|
20
|
+
validate_generate,
|
|
21
|
+
)
|
|
22
|
+
from kiapi.core.app import AppContext
|
|
23
|
+
from kiapi.core.model import UnknownModelError, model_registry
|
|
24
|
+
from kiapi.core.worker import Worker
|
|
25
|
+
|
|
26
|
+
from ._views.video_response import VideoResponse
|
|
27
|
+
|
|
28
|
+
router = APIRouter(dependencies=REQUIRE_AUTH)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@router.post(
|
|
32
|
+
"/v1/video/ltx2/generate",
|
|
33
|
+
responses=build_job_responses("video/mp4", result_model=VideoResponse),
|
|
34
|
+
)
|
|
35
|
+
async def generate(
|
|
36
|
+
req: GenerateRequest,
|
|
37
|
+
accept: str | None = Depends(get_accept),
|
|
38
|
+
ctx: AppContext = Depends(get_ctx),
|
|
39
|
+
worker: Worker = Depends(get_worker),
|
|
40
|
+
) -> Response:
|
|
41
|
+
"""Generate a short MP4 with LTX-2 distilled (text/image/audio to video).
|
|
42
|
+
|
|
43
|
+
The mode is inferred from supplied inputs: prompt only is T2V; `image` adds
|
|
44
|
+
first-frame I2V; `end_image` adds last-frame conditioning; `audio` drives A2V
|
|
45
|
+
timing/motion; and `generate_audio=true` asks LTX-2 to synthesize audio. The
|
|
46
|
+
same endpoint serves both `sync` and `async` via `mode`.
|
|
47
|
+
|
|
48
|
+
Sync content negotiation: the job produces one MP4 artifact, so unless the
|
|
49
|
+
client asks for JSON the raw video bytes are returned with
|
|
50
|
+
`X-Kiapi-File-Id` / `X-Kiapi-Job-Id` headers. With
|
|
51
|
+
`Accept: application/json` (or async), the Job JSON is returned, whose
|
|
52
|
+
`result` follows VideoResponse.
|
|
53
|
+
|
|
54
|
+
LTX-2 is a transient model: each call reserves the configured memory budget,
|
|
55
|
+
loads the pipeline, generates, and frees it instead of keeping a resident
|
|
56
|
+
model in `/health`. Async returns 202 immediately; poll
|
|
57
|
+
GET /v1/jobs/{job_id} and fetch the artifact via GET /v1/files/{file_id}.
|
|
58
|
+
"""
|
|
59
|
+
try:
|
|
60
|
+
spec = model_registry.resolve("ltx2", req.model)
|
|
61
|
+
except UnknownModelError as exc:
|
|
62
|
+
raise HTTPException(status_code=400, detail=str(exc)) # noqa: B904
|
|
63
|
+
try:
|
|
64
|
+
validate_generate(req, variant=spec.name, has_audio=req.audio is not None)
|
|
65
|
+
except ValidationError as exc:
|
|
66
|
+
raise HTTPException(status_code=422, detail=str(exc)) # noqa: B904
|
|
67
|
+
|
|
68
|
+
mode = detect_mode(
|
|
69
|
+
has_image=req.image is not None,
|
|
70
|
+
has_end_image=req.end_image is not None,
|
|
71
|
+
has_audio=req.audio is not None,
|
|
72
|
+
generate_audio=req.generate_audio,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def thunk(): # type: ignore
|
|
76
|
+
return handle_generate(ctx, req, mode)
|
|
77
|
+
|
|
78
|
+
return await submit_and_maybe_wait(
|
|
79
|
+
ctx,
|
|
80
|
+
worker,
|
|
81
|
+
type="ltx2",
|
|
82
|
+
params=req.gen_params() | {"model": spec.name, "mode_detected": mode},
|
|
83
|
+
thunk=thunk,
|
|
84
|
+
mode=req.mode,
|
|
85
|
+
accept=accept,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
register_capability_endpoints(router, name="ltx2", base_path="/v1/video/ltx2")
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class FetchAPIRequest(BaseModel):
|
|
5
|
+
model_config = ConfigDict(extra="forbid")
|
|
6
|
+
|
|
7
|
+
url: str = Field(
|
|
8
|
+
...,
|
|
9
|
+
min_length=1,
|
|
10
|
+
description=(
|
|
11
|
+
"Absolute `http://` or `https://` URL of an HTML page to render. "
|
|
12
|
+
"Binary resources such as images, audio, video, archives, and PDFs "
|
|
13
|
+
"are rejected with `not_html`; use the Files API for stored artifacts."
|
|
14
|
+
),
|
|
15
|
+
examples=["https://example.com/"],
|
|
16
|
+
)
|
|
17
|
+
accept: str | None = Field(
|
|
18
|
+
default=None,
|
|
19
|
+
description=(
|
|
20
|
+
"Requested output media type. `application/pdf` returns a PDF. "
|
|
21
|
+
"`text/markdown`, `text/plain`, `text/*`, `*/*`, browser default "
|
|
22
|
+
"headers, or an omitted header return Markdown. Other concrete media "
|
|
23
|
+
"types such as `application/json` are rejected with HTTP 406."
|
|
24
|
+
),
|
|
25
|
+
examples=["text/markdown", "application/pdf"],
|
|
26
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class _FetchErrorDetail(BaseModel):
|
|
5
|
+
error: str = Field(
|
|
6
|
+
description=(
|
|
7
|
+
"Stable error code such as `unsupported_accept`, `invalid_request`, "
|
|
8
|
+
"`not_html`, `empty_content`, or `fetch_failed`."
|
|
9
|
+
)
|
|
10
|
+
)
|
|
11
|
+
message: str = Field(description="Human-readable error detail.")
|
|
12
|
+
url: str = Field(description="URL submitted to `/v1/web/fetch`.")
|
|
13
|
+
content_type: str | None = Field(
|
|
14
|
+
default=None,
|
|
15
|
+
description=(
|
|
16
|
+
"Detected upstream Content-Type when known. Present for non-HTML "
|
|
17
|
+
"resources and some fetch failures."
|
|
18
|
+
),
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class FetchErrorResponse(BaseModel):
|
|
23
|
+
detail: _FetchErrorDetail = Field(
|
|
24
|
+
description="Structured fetch error payload returned by FastAPI."
|
|
25
|
+
)
|
kiapi/api/web/router.py
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"""Web API (web): ``POST /v1/web/search`` + ``GET /v1/web/fetch``.
|
|
2
|
+
|
|
3
|
+
``fetch`` selects its output format from the ``Accept`` header (``text/markdown``
|
|
4
|
+
default, ``application/pdf``) and returns the rendered page as a raw body, in the
|
|
5
|
+
generation families' raw-bytes-by-default style; errors come back as JSON.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
from typing import Annotated
|
|
10
|
+
|
|
11
|
+
from fastapi import APIRouter, Depends, Header, HTTPException, Query, Response
|
|
12
|
+
from fastapi.responses import FileResponse
|
|
13
|
+
from pydantic import ValidationError as PydanticValidationError
|
|
14
|
+
|
|
15
|
+
from kiapi.api import (
|
|
16
|
+
REQUIRE_AUTH,
|
|
17
|
+
get_ctx,
|
|
18
|
+
get_worker,
|
|
19
|
+
register_capability_endpoints,
|
|
20
|
+
)
|
|
21
|
+
from kiapi.api._settings import settings_manager
|
|
22
|
+
from kiapi.api.web._views import FetchAPIRequest, FetchErrorResponse
|
|
23
|
+
from kiapi.capabilities.web import (
|
|
24
|
+
FetchError,
|
|
25
|
+
FetchRequest,
|
|
26
|
+
SearchBackendError,
|
|
27
|
+
SearchRequest,
|
|
28
|
+
SearchResponse,
|
|
29
|
+
handle_fetch,
|
|
30
|
+
handle_search,
|
|
31
|
+
)
|
|
32
|
+
from kiapi.core.app import AppContext
|
|
33
|
+
from kiapi.core.memory import MemoryBudgetError
|
|
34
|
+
from kiapi.core.model import UnknownModelError
|
|
35
|
+
from kiapi.core.setup import SetupRequiredError
|
|
36
|
+
from kiapi.core.worker import Worker
|
|
37
|
+
|
|
38
|
+
router = APIRouter(dependencies=REQUIRE_AUTH)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def fetch_api_request(
|
|
42
|
+
url: Annotated[
|
|
43
|
+
str,
|
|
44
|
+
Query(
|
|
45
|
+
min_length=1,
|
|
46
|
+
description=(
|
|
47
|
+
"Absolute http:// or https:// URL of an HTML page to fetch. "
|
|
48
|
+
"Non-HTML resources such as images, audio, video, archives, and "
|
|
49
|
+
"PDFs are rejected with `not_html`."
|
|
50
|
+
),
|
|
51
|
+
examples=["https://example.com/"],
|
|
52
|
+
),
|
|
53
|
+
],
|
|
54
|
+
accept: Annotated[
|
|
55
|
+
str | None,
|
|
56
|
+
Header(
|
|
57
|
+
alias="Accept",
|
|
58
|
+
description=(
|
|
59
|
+
"Requested output media type. application/pdf returns PDF; "
|
|
60
|
+
"text/markdown, text/plain, text/*, */*, browser default "
|
|
61
|
+
"headers, or an omitted header return Markdown. application/json "
|
|
62
|
+
"and other incompatible concrete media types return HTTP 406."
|
|
63
|
+
),
|
|
64
|
+
examples=["text/markdown", "application/pdf"],
|
|
65
|
+
),
|
|
66
|
+
] = None,
|
|
67
|
+
) -> FetchAPIRequest:
|
|
68
|
+
return FetchAPIRequest(url=url, accept=accept)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@router.post(
|
|
72
|
+
"/v1/web/search",
|
|
73
|
+
response_model=SearchResponse,
|
|
74
|
+
responses={
|
|
75
|
+
400: {"description": "Unknown web model or invalid model selection."},
|
|
76
|
+
422: {"description": "Request schema or validation error."},
|
|
77
|
+
502: {
|
|
78
|
+
"description": "SearXNG backend was unreachable or returned an HTTP error."
|
|
79
|
+
},
|
|
80
|
+
503: {
|
|
81
|
+
"description": "Backend Docker image is not activated or memory budget is exhausted."
|
|
82
|
+
},
|
|
83
|
+
504: {
|
|
84
|
+
"description": "SearXNG or the sync job exceeded the configured timeout."
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
)
|
|
88
|
+
async def search_web(
|
|
89
|
+
req: SearchRequest,
|
|
90
|
+
ctx: AppContext = Depends(get_ctx),
|
|
91
|
+
worker: Worker = Depends(get_worker),
|
|
92
|
+
) -> dict:
|
|
93
|
+
"""Search the live web with SearXNG and return JSON results.
|
|
94
|
+
|
|
95
|
+
The request body maps to SearXNG's search parameters. `query` is passed
|
|
96
|
+
through verbatim, so SearXNG inline operators such as `site:`, `!wp`,
|
|
97
|
+
`!images`, and language operators like `:ja` work normally. `format=json` is
|
|
98
|
+
always added by kiapi.
|
|
99
|
+
|
|
100
|
+
This endpoint is synchronous from the client's perspective: it waits up to
|
|
101
|
+
`KIAPI_SYNC_TIMEOUT_S` and returns `SearchResponse`. Internally it still
|
|
102
|
+
creates a Job and runs through the single-flight worker so web backends share
|
|
103
|
+
the same queue, setup checks, and resident subprocess lifecycle as other
|
|
104
|
+
capabilities.
|
|
105
|
+
|
|
106
|
+
Results are live and non-deterministic. `results` is optionally truncated by
|
|
107
|
+
`max_results`, while `answers`, `infoboxes`, `suggestions`, and
|
|
108
|
+
`unresponsive_engines` are forwarded from SearXNG without truncation.
|
|
109
|
+
"""
|
|
110
|
+
settings = settings_manager.get_settings()
|
|
111
|
+
|
|
112
|
+
job = ctx.job_store.create(type="web.search", params=req.model_dump(mode="json"))
|
|
113
|
+
fut = await worker.submit(job, lambda: handle_search(ctx, req))
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
return await asyncio.wait_for(fut, timeout=settings.sync_timeout_s)
|
|
117
|
+
except TimeoutError:
|
|
118
|
+
raise HTTPException( # noqa: B904
|
|
119
|
+
status_code=504,
|
|
120
|
+
detail=f"web.search job {job.id} exceeded sync timeout "
|
|
121
|
+
f"({settings.sync_timeout_s}s); it keeps running - poll /v1/jobs/{job.id}",
|
|
122
|
+
)
|
|
123
|
+
except SearchBackendError as exc:
|
|
124
|
+
raise HTTPException(status_code=exc.status_code, detail=str(exc)) # noqa: B904
|
|
125
|
+
except UnknownModelError as exc:
|
|
126
|
+
raise HTTPException(status_code=400, detail=str(exc)) # noqa: B904
|
|
127
|
+
except SetupRequiredError as exc:
|
|
128
|
+
raise HTTPException(status_code=503, detail=str(exc)) # noqa: B904
|
|
129
|
+
except MemoryBudgetError as exc:
|
|
130
|
+
raise HTTPException(status_code=503, detail=str(exc)) # noqa: B904
|
|
131
|
+
except Exception as exc:
|
|
132
|
+
raise HTTPException(status_code=500, detail=f"web.search failed: {exc}") # noqa: B904
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _negotiate_format(accept: str | None) -> str | None:
|
|
136
|
+
"""Map the Accept header to a fetch format, or None if unacceptable.
|
|
137
|
+
|
|
138
|
+
``application/pdf`` → ``pdf``; markdown/text/wildcard/absent → ``markdown``
|
|
139
|
+
(the default); a concrete incompatible type (e.g. ``application/json``) → None.
|
|
140
|
+
"""
|
|
141
|
+
if accept is None:
|
|
142
|
+
return "markdown"
|
|
143
|
+
a = accept.lower()
|
|
144
|
+
if "application/pdf" in a:
|
|
145
|
+
return "pdf"
|
|
146
|
+
if any(t in a for t in ("text/markdown", "text/plain", "text/*", "*/*")):
|
|
147
|
+
return "markdown"
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@router.get(
|
|
152
|
+
"/v1/web/fetch",
|
|
153
|
+
responses={
|
|
154
|
+
200: {
|
|
155
|
+
"description": (
|
|
156
|
+
"Rendered page body. Markdown is returned by default; PDF is "
|
|
157
|
+
"returned when the Accept header allows application/pdf."
|
|
158
|
+
),
|
|
159
|
+
"content": {
|
|
160
|
+
"text/markdown": {"schema": {"type": "string"}},
|
|
161
|
+
"application/pdf": {"schema": {"type": "string", "format": "binary"}},
|
|
162
|
+
},
|
|
163
|
+
"headers": {
|
|
164
|
+
"X-Kiapi-Url": {
|
|
165
|
+
"description": "Fetched URL recorded on the stored artifact.",
|
|
166
|
+
"schema": {"type": "string"},
|
|
167
|
+
},
|
|
168
|
+
"X-Kiapi-File-Id": {
|
|
169
|
+
"description": "Files-API id of the rendered Markdown or PDF artifact.",
|
|
170
|
+
"schema": {"type": "string"},
|
|
171
|
+
},
|
|
172
|
+
"X-Kiapi-Job-Id": {
|
|
173
|
+
"description": "Job id created for the fetch operation.",
|
|
174
|
+
"schema": {"type": "string"},
|
|
175
|
+
},
|
|
176
|
+
"X-Kiapi-Content-Type": {
|
|
177
|
+
"description": "Detected upstream page Content-Type, when known.",
|
|
178
|
+
"schema": {"type": "string"},
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
406: {
|
|
183
|
+
"model": FetchErrorResponse,
|
|
184
|
+
"description": "Accept header does not allow Markdown or PDF.",
|
|
185
|
+
},
|
|
186
|
+
422: {
|
|
187
|
+
"model": FetchErrorResponse,
|
|
188
|
+
"description": "Invalid URL, non-HTML resource, or empty rendered content.",
|
|
189
|
+
},
|
|
190
|
+
502: {
|
|
191
|
+
"model": FetchErrorResponse,
|
|
192
|
+
"description": "Target URL or Crawl4AI backend was unreachable or returned an HTTP error.",
|
|
193
|
+
},
|
|
194
|
+
503: {
|
|
195
|
+
"description": "Backend Docker image is not activated or memory budget is exhausted."
|
|
196
|
+
},
|
|
197
|
+
504: {
|
|
198
|
+
"description": "Crawl4AI or the sync job exceeded the configured timeout."
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
)
|
|
202
|
+
async def fetch_web(
|
|
203
|
+
api_req: FetchAPIRequest = Depends(fetch_api_request),
|
|
204
|
+
ctx: AppContext = Depends(get_ctx),
|
|
205
|
+
worker: Worker = Depends(get_worker),
|
|
206
|
+
) -> Response:
|
|
207
|
+
"""Render one HTML page to Markdown or PDF and stream the raw body.
|
|
208
|
+
|
|
209
|
+
`url` is supplied as a query parameter. The router resolves the `Accept`
|
|
210
|
+
header to the capability request's internal `format`: `application/pdf`
|
|
211
|
+
selects PDF; Markdown-compatible, text-compatible, wildcard, browser default,
|
|
212
|
+
or omitted Accept headers select Markdown. Incompatible concrete media types
|
|
213
|
+
return HTTP 406 with a structured JSON error.
|
|
214
|
+
|
|
215
|
+
Fetch performs a cheap preflight check before rendering. Non-HTML resources
|
|
216
|
+
are rejected with `not_html`; pages that render to no extractable text are
|
|
217
|
+
rejected with `empty_content`. Successful bodies are stored in the Files API
|
|
218
|
+
and streamed back directly with `X-Kiapi-File-Id`, `X-Kiapi-Job-Id`, and
|
|
219
|
+
upstream `X-Kiapi-Content-Type` headers when available.
|
|
220
|
+
"""
|
|
221
|
+
settings = settings_manager.get_settings()
|
|
222
|
+
fmt = _negotiate_format(api_req.accept)
|
|
223
|
+
if fmt is None:
|
|
224
|
+
raise HTTPException(
|
|
225
|
+
status_code=406,
|
|
226
|
+
detail={
|
|
227
|
+
"error": "unsupported_accept",
|
|
228
|
+
"message": "Accept must allow text/markdown or application/pdf.",
|
|
229
|
+
"url": api_req.url,
|
|
230
|
+
},
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
try:
|
|
234
|
+
req = FetchRequest(url=api_req.url, format=fmt) # type: ignore[arg-type]
|
|
235
|
+
except PydanticValidationError as exc:
|
|
236
|
+
# Keep the fetch error envelope and stay JSON-serializable: a custom
|
|
237
|
+
# field_validator raises ValueError, whose object pydantic stashes in
|
|
238
|
+
# each error's ``ctx`` — passing exc.errors() straight through would
|
|
239
|
+
# 500 on encoding. Surface the human-readable messages only.
|
|
240
|
+
raise HTTPException( # noqa: B904
|
|
241
|
+
status_code=422,
|
|
242
|
+
detail={
|
|
243
|
+
"error": "invalid_request",
|
|
244
|
+
"message": "; ".join(e["msg"] for e in exc.errors()),
|
|
245
|
+
"url": api_req.url,
|
|
246
|
+
},
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
job = ctx.job_store.create(type="web.fetch", params=req.model_dump(mode="json"))
|
|
250
|
+
fut = await worker.submit(job, lambda: handle_fetch(ctx, req))
|
|
251
|
+
|
|
252
|
+
try:
|
|
253
|
+
result = await asyncio.wait_for(fut, timeout=settings.sync_timeout_s)
|
|
254
|
+
except FetchError as exc:
|
|
255
|
+
raise HTTPException( # noqa: B904
|
|
256
|
+
status_code=exc.status_code,
|
|
257
|
+
detail={
|
|
258
|
+
"error": exc.code,
|
|
259
|
+
"message": str(exc),
|
|
260
|
+
"url": api_req.url,
|
|
261
|
+
"content_type": exc.content_type,
|
|
262
|
+
},
|
|
263
|
+
)
|
|
264
|
+
except TimeoutError:
|
|
265
|
+
raise HTTPException( # noqa: B904
|
|
266
|
+
status_code=504,
|
|
267
|
+
detail=f"web.fetch job {job.id} exceeded sync timeout "
|
|
268
|
+
f"({settings.sync_timeout_s}s); it keeps running - poll /v1/jobs/{job.id}",
|
|
269
|
+
)
|
|
270
|
+
except UnknownModelError as exc:
|
|
271
|
+
raise HTTPException(status_code=400, detail=str(exc)) # noqa: B904
|
|
272
|
+
except SetupRequiredError as exc:
|
|
273
|
+
raise HTTPException(status_code=503, detail=str(exc)) # noqa: B904
|
|
274
|
+
except MemoryBudgetError as exc:
|
|
275
|
+
raise HTTPException(status_code=503, detail=str(exc)) # noqa: B904
|
|
276
|
+
except Exception as exc:
|
|
277
|
+
raise HTTPException(status_code=500, detail=f"web.fetch failed: {exc}") # noqa: B904
|
|
278
|
+
|
|
279
|
+
file_id = result["file_id"]
|
|
280
|
+
rec = ctx.file_store.get(file_id)
|
|
281
|
+
if rec is None:
|
|
282
|
+
raise HTTPException(
|
|
283
|
+
status_code=500,
|
|
284
|
+
detail=f"web.fetch produced missing artifact {file_id!r}",
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
headers = {
|
|
288
|
+
"X-Kiapi-Url": result["url"],
|
|
289
|
+
"X-Kiapi-File-Id": file_id,
|
|
290
|
+
"X-Kiapi-Job-Id": job.id,
|
|
291
|
+
}
|
|
292
|
+
if result.get("content_type"):
|
|
293
|
+
headers["X-Kiapi-Content-Type"] = result["content_type"]
|
|
294
|
+
return FileResponse(
|
|
295
|
+
rec.path,
|
|
296
|
+
media_type=rec.content_type,
|
|
297
|
+
filename=rec.filename,
|
|
298
|
+
headers=headers,
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
register_capability_endpoints(router, name="web", base_path="/v1/web")
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Family packages — one subpackage per family (dir == family).
|
|
2
|
+
|
|
3
|
+
Each family exposes a ``register()`` that populates the global model + help
|
|
4
|
+
registries (see core/model, core/help). Registration is explicit, not an import
|
|
5
|
+
side effect: the CLI bootstrap calls each family's ``register`` before starting
|
|
6
|
+
the API server or running setup/check commands.
|
|
7
|
+
|
|
8
|
+
Shared building blocks reused across families live here too: the common
|
|
9
|
+
request-error types and the Files-API path resolver.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from ._exceptions.CapabilityError import CapabilityError
|
|
13
|
+
from ._exceptions.ValidationError import ValidationError
|
|
14
|
+
from ._helpers.attach_mflux_progress import attach_mflux_progress
|
|
15
|
+
from ._helpers.get_file_path import get_file_path
|
|
16
|
+
from ._helpers.resolve_file_ref import resolve_file_ref
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"CapabilityError",
|
|
20
|
+
"ValidationError",
|
|
21
|
+
"attach_mflux_progress",
|
|
22
|
+
"get_file_path",
|
|
23
|
+
"resolve_file_ref",
|
|
24
|
+
]
|