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.
Files changed (465) hide show
  1. kiapi/__init__.py +3 -0
  2. kiapi/__main__.py +4 -0
  3. kiapi/api/__init__.py +26 -0
  4. kiapi/api/_constants/require_auth.py +14 -0
  5. kiapi/api/_helpers/build_job_responses.py +73 -0
  6. kiapi/api/_helpers/build_openapi.py +55 -0
  7. kiapi/api/_helpers/build_train_responses.py +21 -0
  8. kiapi/api/_helpers/get_accept.py +20 -0
  9. kiapi/api/_helpers/get_ctx.py +7 -0
  10. kiapi/api/_helpers/get_worker.py +7 -0
  11. kiapi/api/_helpers/register_capability_endpoints.py +70 -0
  12. kiapi/api/_helpers/submit_and_maybe_wait.py +113 -0
  13. kiapi/api/_settings.py +50 -0
  14. kiapi/api/_views/async_job_response.py +21 -0
  15. kiapi/api/_views/capability_model_spec.py +49 -0
  16. kiapi/api/app.py +150 -0
  17. kiapi/api/audio/acestep/_views/extract_response.py +57 -0
  18. kiapi/api/audio/acestep/_views/track_response.py +57 -0
  19. kiapi/api/audio/acestep/router.py +190 -0
  20. kiapi/api/audio/audiogen/_views/audio_response.py +46 -0
  21. kiapi/api/audio/audiogen/router.py +77 -0
  22. kiapi/api/chat/_views/chat_completion_response.py +71 -0
  23. kiapi/api/chat/router.py +253 -0
  24. kiapi/api/embedding/_views/embedding_response.py +35 -0
  25. kiapi/api/embedding/router.py +82 -0
  26. kiapi/api/files/_operations/get_file.py +14 -0
  27. kiapi/api/files/_operations/get_file_id.py +18 -0
  28. kiapi/api/files/_views/file_delete_response.py +14 -0
  29. kiapi/api/files/_views/file_list_response.py +15 -0
  30. kiapi/api/files/router.py +105 -0
  31. kiapi/api/health/_views/health_response.py +26 -0
  32. kiapi/api/health/router.py +28 -0
  33. kiapi/api/image/depthpro/_views/estimate_response.py +70 -0
  34. kiapi/api/image/depthpro/router.py +81 -0
  35. kiapi/api/image/ernie/_views/image_response.py +50 -0
  36. kiapi/api/image/ernie/router.py +181 -0
  37. kiapi/api/image/flux2/_views/image_response.py +53 -0
  38. kiapi/api/image/flux2/router.py +197 -0
  39. kiapi/api/image/ideogram4/_views/image_response.py +55 -0
  40. kiapi/api/image/ideogram4/router.py +87 -0
  41. kiapi/api/image/qwen/_views/image_response.py +47 -0
  42. kiapi/api/image/qwen/router.py +141 -0
  43. kiapi/api/image/seedvr2/_views/image_response.py +50 -0
  44. kiapi/api/image/seedvr2/router.py +83 -0
  45. kiapi/api/image/zimage/_views/image_response.py +49 -0
  46. kiapi/api/image/zimage/router.py +131 -0
  47. kiapi/api/jobs/_operations/get_job_id.py +18 -0
  48. kiapi/api/jobs/_views/job_delete_response.py +19 -0
  49. kiapi/api/jobs/_views/job_list_response.py +15 -0
  50. kiapi/api/jobs/router.py +62 -0
  51. kiapi/api/models/_schemas/openai_model_spec.py +26 -0
  52. kiapi/api/models/_views/model_list_response.py +15 -0
  53. kiapi/api/models/router.py +29 -0
  54. kiapi/api/video/ltx2/_views/__init__.py +1 -0
  55. kiapi/api/video/ltx2/_views/video_response.py +59 -0
  56. kiapi/api/video/ltx2/router.py +89 -0
  57. kiapi/api/web/_views/__init__.py +7 -0
  58. kiapi/api/web/_views/fetch_api_request.py +26 -0
  59. kiapi/api/web/_views/fetch_error_response.py +25 -0
  60. kiapi/api/web/router.py +302 -0
  61. kiapi/capabilities/__init__.py +24 -0
  62. kiapi/capabilities/_exceptions/CapabilityError.py +2 -0
  63. kiapi/capabilities/_exceptions/ValidationError.py +2 -0
  64. kiapi/capabilities/_helpers/attach_mflux_progress.py +50 -0
  65. kiapi/capabilities/_helpers/get_file_path.py +20 -0
  66. kiapi/capabilities/_helpers/resolve_file_ref.py +16 -0
  67. kiapi/capabilities/acestep/README.ja.md +174 -0
  68. kiapi/capabilities/acestep/README.md +167 -0
  69. kiapi/capabilities/acestep/__init__.py +37 -0
  70. kiapi/capabilities/acestep/_constants/description.py +122 -0
  71. kiapi/capabilities/acestep/_exceptions/ace_step_engine_error.py +2 -0
  72. kiapi/capabilities/acestep/_helpers/register.py +96 -0
  73. kiapi/capabilities/acestep/_models/acestep.py +105 -0
  74. kiapi/capabilities/acestep/_operations/handle_cover.py +23 -0
  75. kiapi/capabilities/acestep/_operations/handle_extract.py +38 -0
  76. kiapi/capabilities/acestep/_operations/handle_generate.py +18 -0
  77. kiapi/capabilities/acestep/_operations/handle_repaint.py +25 -0
  78. kiapi/capabilities/acestep/_operations/resolve_ace_step_paths.py +30 -0
  79. kiapi/capabilities/acestep/_operations/resolve_cover_params.py +21 -0
  80. kiapi/capabilities/acestep/_operations/resolve_engine.py +10 -0
  81. kiapi/capabilities/acestep/_operations/resolve_extract_params.py +24 -0
  82. kiapi/capabilities/acestep/_operations/resolve_generate_params.py +18 -0
  83. kiapi/capabilities/acestep/_operations/resolve_repaint_params.py +22 -0
  84. kiapi/capabilities/acestep/_schemas/ace_step_paths.py +29 -0
  85. kiapi/capabilities/acestep/_services/ace_step_engine.py +143 -0
  86. kiapi/capabilities/acestep/_settings.py +82 -0
  87. kiapi/capabilities/acestep/_views/ace_step_base.py +61 -0
  88. kiapi/capabilities/acestep/_views/cover_params.py +26 -0
  89. kiapi/capabilities/acestep/_views/cover_request.py +49 -0
  90. kiapi/capabilities/acestep/_views/extract_params.py +24 -0
  91. kiapi/capabilities/acestep/_views/extract_request.py +33 -0
  92. kiapi/capabilities/acestep/_views/generate_params.py +25 -0
  93. kiapi/capabilities/acestep/_views/generate_request.py +54 -0
  94. kiapi/capabilities/acestep/_views/repaint_params.py +27 -0
  95. kiapi/capabilities/acestep/_views/repaint_request.py +51 -0
  96. kiapi/capabilities/acestep/_views/track_params.py +7 -0
  97. kiapi/capabilities/acestep/worker_subprocess.py +247 -0
  98. kiapi/capabilities/audiogen/README.ja.md +49 -0
  99. kiapi/capabilities/audiogen/README.md +48 -0
  100. kiapi/capabilities/audiogen/__init__.py +25 -0
  101. kiapi/capabilities/audiogen/_constants/description.py +60 -0
  102. kiapi/capabilities/audiogen/_helpers/attach_audiogen_progress.py +27 -0
  103. kiapi/capabilities/audiogen/_helpers/register.py +42 -0
  104. kiapi/capabilities/audiogen/_helpers/validate_generate.py +14 -0
  105. kiapi/capabilities/audiogen/_models/audiogen.py +98 -0
  106. kiapi/capabilities/audiogen/_operations/handle_generate.py +27 -0
  107. kiapi/capabilities/audiogen/_operations/resolve_generate_params.py +25 -0
  108. kiapi/capabilities/audiogen/_settings.py +34 -0
  109. kiapi/capabilities/audiogen/_views/generate_params.py +14 -0
  110. kiapi/capabilities/audiogen/_views/generate_request.py +96 -0
  111. kiapi/capabilities/chat/README.ja.md +822 -0
  112. kiapi/capabilities/chat/README.md +806 -0
  113. kiapi/capabilities/chat/__init__.py +25 -0
  114. kiapi/capabilities/chat/_constants/description.py +163 -0
  115. kiapi/capabilities/chat/_helpers/register.py +67 -0
  116. kiapi/capabilities/chat/_models/qwen3_5.py +183 -0
  117. kiapi/capabilities/chat/_models/qwen3_omni.py +242 -0
  118. kiapi/capabilities/chat/_operations/apply_template.py +74 -0
  119. kiapi/capabilities/chat/_operations/completed_hermes_tool_call_text.py +9 -0
  120. kiapi/capabilities/chat/_operations/emit_streaming_response.py +297 -0
  121. kiapi/capabilities/chat/_operations/ensure_streaming_detokenizer_compat.py +49 -0
  122. kiapi/capabilities/chat/_operations/format_response.py +72 -0
  123. kiapi/capabilities/chat/_operations/handle_chat.py +25 -0
  124. kiapi/capabilities/chat/_operations/parse_hermes_tool_calls.py +63 -0
  125. kiapi/capabilities/chat/_operations/parse_json_tool_calls.py +66 -0
  126. kiapi/capabilities/chat/_operations/parse_messages.py +326 -0
  127. kiapi/capabilities/chat/_operations/resolve_chat_params.py +50 -0
  128. kiapi/capabilities/chat/_operations/stream_text_from_tokens.py +32 -0
  129. kiapi/capabilities/chat/_operations/strip_tool_calls.py +15 -0
  130. kiapi/capabilities/chat/_settings.py +74 -0
  131. kiapi/capabilities/chat/_utils/apply_parallel_tool_call_policy.py +17 -0
  132. kiapi/capabilities/chat/_utils/apply_seed.py +8 -0
  133. kiapi/capabilities/chat/_utils/load_audio_mono.py +31 -0
  134. kiapi/capabilities/chat/_utils/load_mlx_vlm.py +12 -0
  135. kiapi/capabilities/chat/_utils/warmup_params.py +26 -0
  136. kiapi/capabilities/chat/_views/chat_params.py +34 -0
  137. kiapi/capabilities/chat/_views/chat_request.py +153 -0
  138. kiapi/capabilities/depthpro/README.ja.md +131 -0
  139. kiapi/capabilities/depthpro/README.md +124 -0
  140. kiapi/capabilities/depthpro/__init__.py +23 -0
  141. kiapi/capabilities/depthpro/_constants/description.py +39 -0
  142. kiapi/capabilities/depthpro/_helpers/register.py +46 -0
  143. kiapi/capabilities/depthpro/_helpers/validate_estimate.py +32 -0
  144. kiapi/capabilities/depthpro/_models/depthpro.py +144 -0
  145. kiapi/capabilities/depthpro/_operations/handle_estimate.py +50 -0
  146. kiapi/capabilities/depthpro/_operations/is_quantize_override.py +12 -0
  147. kiapi/capabilities/depthpro/_operations/resolve_estimate_params.py +24 -0
  148. kiapi/capabilities/depthpro/_settings.py +46 -0
  149. kiapi/capabilities/depthpro/_views/estimate_params.py +21 -0
  150. kiapi/capabilities/depthpro/_views/estimate_request.py +63 -0
  151. kiapi/capabilities/embedding/README.ja.md +137 -0
  152. kiapi/capabilities/embedding/README.md +128 -0
  153. kiapi/capabilities/embedding/__init__.py +24 -0
  154. kiapi/capabilities/embedding/_constants/description.py +47 -0
  155. kiapi/capabilities/embedding/_helpers/register.py +66 -0
  156. kiapi/capabilities/embedding/_models/qwen3_text.py +53 -0
  157. kiapi/capabilities/embedding/_models/qwen3_vl.py +97 -0
  158. kiapi/capabilities/embedding/_operations/format_response.py +24 -0
  159. kiapi/capabilities/embedding/_operations/handle_embed.py +24 -0
  160. kiapi/capabilities/embedding/_operations/parse_inputs.py +108 -0
  161. kiapi/capabilities/embedding/_operations/resolve_embed_params.py +18 -0
  162. kiapi/capabilities/embedding/_settings.py +27 -0
  163. kiapi/capabilities/embedding/_utils/to_vector.py +20 -0
  164. kiapi/capabilities/embedding/_views/embed_params.py +17 -0
  165. kiapi/capabilities/embedding/_views/embed_request.py +39 -0
  166. kiapi/capabilities/ernie/README.ja.md +242 -0
  167. kiapi/capabilities/ernie/README.md +223 -0
  168. kiapi/capabilities/ernie/__init__.py +34 -0
  169. kiapi/capabilities/ernie/_constants/description.py +71 -0
  170. kiapi/capabilities/ernie/_helpers/register.py +60 -0
  171. kiapi/capabilities/ernie/_helpers/validate_edit.py +19 -0
  172. kiapi/capabilities/ernie/_helpers/validate_generate.py +8 -0
  173. kiapi/capabilities/ernie/_models/ernie.py +356 -0
  174. kiapi/capabilities/ernie/_operations/handle_edit.py +39 -0
  175. kiapi/capabilities/ernie/_operations/handle_generate.py +38 -0
  176. kiapi/capabilities/ernie/_operations/handle_train.py +77 -0
  177. kiapi/capabilities/ernie/_operations/is_quantize_override.py +16 -0
  178. kiapi/capabilities/ernie/_operations/resolve_edit_params.py +44 -0
  179. kiapi/capabilities/ernie/_operations/resolve_generate_params.py +40 -0
  180. kiapi/capabilities/ernie/_operations/resolve_lora_params.py +23 -0
  181. kiapi/capabilities/ernie/_operations/resolve_train_params.py +43 -0
  182. kiapi/capabilities/ernie/_operations/validate_common.py +33 -0
  183. kiapi/capabilities/ernie/_schemas/lora_ref.py +27 -0
  184. kiapi/capabilities/ernie/_settings.py +151 -0
  185. kiapi/capabilities/ernie/_views/edit_params.py +36 -0
  186. kiapi/capabilities/ernie/_views/edit_request.py +142 -0
  187. kiapi/capabilities/ernie/_views/generate_params.py +32 -0
  188. kiapi/capabilities/ernie/_views/generate_request.py +127 -0
  189. kiapi/capabilities/ernie/_views/train_params.py +28 -0
  190. kiapi/capabilities/ernie/_views/train_request.py +96 -0
  191. kiapi/capabilities/flux2/README.ja.md +260 -0
  192. kiapi/capabilities/flux2/README.md +241 -0
  193. kiapi/capabilities/flux2/__init__.py +36 -0
  194. kiapi/capabilities/flux2/_constants/description.py +81 -0
  195. kiapi/capabilities/flux2/_helpers/register.py +98 -0
  196. kiapi/capabilities/flux2/_helpers/validate_edit.py +12 -0
  197. kiapi/capabilities/flux2/_helpers/validate_generate.py +8 -0
  198. kiapi/capabilities/flux2/_models/flux2.py +396 -0
  199. kiapi/capabilities/flux2/_operations/handle_edit.py +44 -0
  200. kiapi/capabilities/flux2/_operations/handle_generate.py +46 -0
  201. kiapi/capabilities/flux2/_operations/handle_train.py +105 -0
  202. kiapi/capabilities/flux2/_operations/is_quantize_override.py +16 -0
  203. kiapi/capabilities/flux2/_operations/resolve_edit_params.py +43 -0
  204. kiapi/capabilities/flux2/_operations/resolve_generate_params.py +44 -0
  205. kiapi/capabilities/flux2/_operations/resolve_lora_params.py +23 -0
  206. kiapi/capabilities/flux2/_operations/resolve_train_params.py +42 -0
  207. kiapi/capabilities/flux2/_operations/role_spec.py +13 -0
  208. kiapi/capabilities/flux2/_operations/split_repo.py +11 -0
  209. kiapi/capabilities/flux2/_operations/validate_common.py +35 -0
  210. kiapi/capabilities/flux2/_schemas/lora_ref.py +27 -0
  211. kiapi/capabilities/flux2/_settings.py +181 -0
  212. kiapi/capabilities/flux2/_views/edit_params.py +36 -0
  213. kiapi/capabilities/flux2/_views/edit_request.py +145 -0
  214. kiapi/capabilities/flux2/_views/generate_params.py +36 -0
  215. kiapi/capabilities/flux2/_views/generate_request.py +150 -0
  216. kiapi/capabilities/flux2/_views/train_params.py +32 -0
  217. kiapi/capabilities/flux2/_views/train_request.py +102 -0
  218. kiapi/capabilities/ideogram4/README.ja.md +230 -0
  219. kiapi/capabilities/ideogram4/README.md +219 -0
  220. kiapi/capabilities/ideogram4/__init__.py +24 -0
  221. kiapi/capabilities/ideogram4/_constants/description.py +65 -0
  222. kiapi/capabilities/ideogram4/_helpers/register.py +44 -0
  223. kiapi/capabilities/ideogram4/_helpers/validate_generate.py +31 -0
  224. kiapi/capabilities/ideogram4/_models/ideogram4.py +120 -0
  225. kiapi/capabilities/ideogram4/_operations/handle_generate.py +30 -0
  226. kiapi/capabilities/ideogram4/_operations/is_quantize_override.py +12 -0
  227. kiapi/capabilities/ideogram4/_operations/resolve_generate_params.py +30 -0
  228. kiapi/capabilities/ideogram4/_settings.py +89 -0
  229. kiapi/capabilities/ideogram4/_views/generate_params.py +27 -0
  230. kiapi/capabilities/ideogram4/_views/generate_request.py +118 -0
  231. kiapi/capabilities/ltx2/README.ja.md +287 -0
  232. kiapi/capabilities/ltx2/README.md +274 -0
  233. kiapi/capabilities/ltx2/__init__.py +26 -0
  234. kiapi/capabilities/ltx2/_constants/description.py +81 -0
  235. kiapi/capabilities/ltx2/_helpers/register.py +58 -0
  236. kiapi/capabilities/ltx2/_helpers/validate_generate.py +42 -0
  237. kiapi/capabilities/ltx2/_models/ltx2.py +106 -0
  238. kiapi/capabilities/ltx2/_operations/detect_mode.py +19 -0
  239. kiapi/capabilities/ltx2/_operations/handle_generate.py +52 -0
  240. kiapi/capabilities/ltx2/_operations/resolve_generate_params.py +31 -0
  241. kiapi/capabilities/ltx2/_settings.py +93 -0
  242. kiapi/capabilities/ltx2/_views/generate_params.py +34 -0
  243. kiapi/capabilities/ltx2/_views/generate_request.py +151 -0
  244. kiapi/capabilities/qwen/README.ja.md +251 -0
  245. kiapi/capabilities/qwen/README.md +235 -0
  246. kiapi/capabilities/qwen/__init__.py +30 -0
  247. kiapi/capabilities/qwen/_constants/description.py +53 -0
  248. kiapi/capabilities/qwen/_helpers/register.py +60 -0
  249. kiapi/capabilities/qwen/_helpers/validate_edit.py +12 -0
  250. kiapi/capabilities/qwen/_helpers/validate_generate.py +12 -0
  251. kiapi/capabilities/qwen/_models/qwen.py +224 -0
  252. kiapi/capabilities/qwen/_operations/handle_edit.py +44 -0
  253. kiapi/capabilities/qwen/_operations/handle_generate.py +46 -0
  254. kiapi/capabilities/qwen/_operations/is_quantize_override.py +15 -0
  255. kiapi/capabilities/qwen/_operations/resolve_edit_params.py +35 -0
  256. kiapi/capabilities/qwen/_operations/resolve_generate_params.py +39 -0
  257. kiapi/capabilities/qwen/_operations/resolve_lora_params.py +23 -0
  258. kiapi/capabilities/qwen/_operations/validate_common.py +32 -0
  259. kiapi/capabilities/qwen/_schemas/lora_ref.py +27 -0
  260. kiapi/capabilities/qwen/_settings.py +107 -0
  261. kiapi/capabilities/qwen/_views/edit_params.py +36 -0
  262. kiapi/capabilities/qwen/_views/edit_request.py +138 -0
  263. kiapi/capabilities/qwen/_views/generate_params.py +37 -0
  264. kiapi/capabilities/qwen/_views/generate_request.py +149 -0
  265. kiapi/capabilities/seedvr2/README.ja.md +234 -0
  266. kiapi/capabilities/seedvr2/README.md +219 -0
  267. kiapi/capabilities/seedvr2/__init__.py +24 -0
  268. kiapi/capabilities/seedvr2/_constants/description.py +46 -0
  269. kiapi/capabilities/seedvr2/_helpers/register.py +60 -0
  270. kiapi/capabilities/seedvr2/_helpers/validate_upscale.py +52 -0
  271. kiapi/capabilities/seedvr2/_models/seedvr2.py +140 -0
  272. kiapi/capabilities/seedvr2/_operations/handle_upscale.py +42 -0
  273. kiapi/capabilities/seedvr2/_operations/is_quantize_override.py +12 -0
  274. kiapi/capabilities/seedvr2/_operations/resolve_upscale_params.py +30 -0
  275. kiapi/capabilities/seedvr2/_settings.py +74 -0
  276. kiapi/capabilities/seedvr2/_views/upscale_params.py +27 -0
  277. kiapi/capabilities/seedvr2/_views/upscale_request.py +94 -0
  278. kiapi/capabilities/web/README.ja.md +187 -0
  279. kiapi/capabilities/web/README.md +171 -0
  280. kiapi/capabilities/web/__init__.py +30 -0
  281. kiapi/capabilities/web/_constants/description.py +38 -0
  282. kiapi/capabilities/web/_exceptions/FetchError.py +24 -0
  283. kiapi/capabilities/web/_exceptions/SearchBackendError.py +11 -0
  284. kiapi/capabilities/web/_helpers/register.py +59 -0
  285. kiapi/capabilities/web/_models/crawl4ai.py +32 -0
  286. kiapi/capabilities/web/_models/searxng.py +44 -0
  287. kiapi/capabilities/web/_operations/handle_fetch.py +145 -0
  288. kiapi/capabilities/web/_operations/handle_search.py +56 -0
  289. kiapi/capabilities/web/_operations/resolve_fetch_params.py +19 -0
  290. kiapi/capabilities/web/_operations/resolve_search_params.py +34 -0
  291. kiapi/capabilities/web/_operations/resolve_searxng_config_dir.py +21 -0
  292. kiapi/capabilities/web/_services/__init__.py +1 -0
  293. kiapi/capabilities/web/_services/web_backend_process.py +144 -0
  294. kiapi/capabilities/web/_settings.py +138 -0
  295. kiapi/capabilities/web/_views/fetch_params.py +27 -0
  296. kiapi/capabilities/web/_views/fetch_request.py +29 -0
  297. kiapi/capabilities/web/_views/fetch_result.py +20 -0
  298. kiapi/capabilities/web/_views/search_params.py +43 -0
  299. kiapi/capabilities/web/_views/search_request.py +93 -0
  300. kiapi/capabilities/web/_views/search_response.py +49 -0
  301. kiapi/capabilities/web/resources/__init__.py +0 -0
  302. kiapi/capabilities/web/resources/searxng/__init__.py +0 -0
  303. kiapi/capabilities/web/resources/searxng/settings.yml +11 -0
  304. kiapi/capabilities/zimage/README.ja.md +249 -0
  305. kiapi/capabilities/zimage/README.md +231 -0
  306. kiapi/capabilities/zimage/__init__.py +34 -0
  307. kiapi/capabilities/zimage/_constants/description.py +73 -0
  308. kiapi/capabilities/zimage/_helpers/register.py +62 -0
  309. kiapi/capabilities/zimage/_helpers/validate_generate.py +37 -0
  310. kiapi/capabilities/zimage/_models/zimage.py +278 -0
  311. kiapi/capabilities/zimage/_operations/handle_generate.py +38 -0
  312. kiapi/capabilities/zimage/_operations/handle_train.py +78 -0
  313. kiapi/capabilities/zimage/_operations/is_quantize_override.py +11 -0
  314. kiapi/capabilities/zimage/_operations/resolve_generate_params.py +39 -0
  315. kiapi/capabilities/zimage/_operations/resolve_lora_params.py +23 -0
  316. kiapi/capabilities/zimage/_operations/resolve_train_params.py +38 -0
  317. kiapi/capabilities/zimage/_schemas/lora_ref.py +27 -0
  318. kiapi/capabilities/zimage/_settings.py +166 -0
  319. kiapi/capabilities/zimage/_views/generate_params.py +23 -0
  320. kiapi/capabilities/zimage/_views/generate_request.py +127 -0
  321. kiapi/capabilities/zimage/_views/train_params.py +22 -0
  322. kiapi/capabilities/zimage/_views/train_request.py +101 -0
  323. kiapi/cli/__init__.py +11 -0
  324. kiapi/cli/_helpers/dedupe_resources.py +12 -0
  325. kiapi/cli/_helpers/register_all_capabilities.py +36 -0
  326. kiapi/cli/_helpers/run_resources.py +24 -0
  327. kiapi/cli/_helpers/select_specs.py +67 -0
  328. kiapi/cli/activate/_operations/preflight_hf_access.py +129 -0
  329. kiapi/cli/activate/cli.py +37 -0
  330. kiapi/cli/check/_helpers/check_audio_acestep_turbo.py +22 -0
  331. kiapi/cli/check/_helpers/check_audio_acestep_xl_base.py +22 -0
  332. kiapi/cli/check/_helpers/check_audio_audiogen_medium.py +24 -0
  333. kiapi/cli/check/_helpers/check_chat_chat_qwen3_6_27b.py +19 -0
  334. kiapi/cli/check/_helpers/check_chat_chat_qwen3_omni.py +19 -0
  335. kiapi/cli/check/_helpers/check_embedding_embedding_qwen3_embedding_8b.py +14 -0
  336. kiapi/cli/check/_helpers/check_embedding_embedding_qwen3_vl_embedding_2b.py +19 -0
  337. kiapi/cli/check/_helpers/check_image_depthpro_base.py +21 -0
  338. kiapi/cli/check/_helpers/check_image_ernie_base.py +23 -0
  339. kiapi/cli/check/_helpers/check_image_ernie_turbo.py +23 -0
  340. kiapi/cli/check/_helpers/check_image_flux2_klein_9b.py +23 -0
  341. kiapi/cli/check/_helpers/check_image_flux2_klein_base_4b.py +23 -0
  342. kiapi/cli/check/_helpers/check_image_flux2_klein_base_9b.py +23 -0
  343. kiapi/cli/check/_helpers/check_image_ideogram4_fp8.py +23 -0
  344. kiapi/cli/check/_helpers/check_image_qwen_edit_2509.py +27 -0
  345. kiapi/cli/check/_helpers/check_image_qwen_image.py +23 -0
  346. kiapi/cli/check/_helpers/check_image_seedvr2_3b.py +23 -0
  347. kiapi/cli/check/_helpers/check_image_seedvr2_7b.py +23 -0
  348. kiapi/cli/check/_helpers/check_image_zimage_base.py +23 -0
  349. kiapi/cli/check/_helpers/check_image_zimage_turbo.py +23 -0
  350. kiapi/cli/check/_helpers/check_video_ltx2_distilled.py +26 -0
  351. kiapi/cli/check/_helpers/check_web_web_fetch.py +14 -0
  352. kiapi/cli/check/_helpers/check_web_web_search.py +14 -0
  353. kiapi/cli/check/_operations/build_check_result.py +20 -0
  354. kiapi/cli/check/_operations/check_context.py +10 -0
  355. kiapi/cli/check/_operations/create_sample_image.py +46 -0
  356. kiapi/cli/check/_schemas/check_result.py +12 -0
  357. kiapi/cli/check/cli.py +89 -0
  358. kiapi/cli/cli.py +29 -0
  359. kiapi/cli/config/cli.py +17 -0
  360. kiapi/cli/config/edit/cli.py +32 -0
  361. kiapi/cli/config/init/cli.py +33 -0
  362. kiapi/cli/config/show/cli.py +13 -0
  363. kiapi/cli/config/template/cli.py +36 -0
  364. kiapi/cli/deactivate/cli.py +49 -0
  365. kiapi/cli/run/asgi.py +16 -0
  366. kiapi/cli/run/cli.py +59 -0
  367. kiapi/cli/service/__init__.py +5 -0
  368. kiapi/cli/service/_services/service_manager.py +107 -0
  369. kiapi/cli/service/cli.py +19 -0
  370. kiapi/cli/service/install/cli.py +17 -0
  371. kiapi/cli/service/start/cli.py +22 -0
  372. kiapi/cli/service/status/cli.py +81 -0
  373. kiapi/cli/service/stop/cli.py +19 -0
  374. kiapi/cli/service/uninstall/cli.py +16 -0
  375. kiapi/cli/status/cli.py +226 -0
  376. kiapi/core/app/__init__.py +16 -0
  377. kiapi/core/app/_schemas/app_context.py +28 -0
  378. kiapi/core/app/_services/user_directory.py +59 -0
  379. kiapi/core/app/_settings.py +46 -0
  380. kiapi/core/capability/__init__.py +11 -0
  381. kiapi/core/capability/_schemas/capability_spec.py +17 -0
  382. kiapi/core/capability/_services/capability_spec_registry.py +21 -0
  383. kiapi/core/config/__init__.py +11 -0
  384. kiapi/core/config/_exceptions/user_config_error.py +2 -0
  385. kiapi/core/config/_helpers/get_user_settings_path.py +7 -0
  386. kiapi/core/config/_helpers/load_user_settings.py +54 -0
  387. kiapi/core/file/__init__.py +37 -0
  388. kiapi/core/file/_helpers/create_file_store.py +7 -0
  389. kiapi/core/file/_helpers/resolve_file_ref.py +90 -0
  390. kiapi/core/file/_schemas/file_data_url_ref.py +19 -0
  391. kiapi/core/file/_schemas/file_id_ref.py +16 -0
  392. kiapi/core/file/_schemas/file_record.py +46 -0
  393. kiapi/core/file/_schemas/file_url_ref.py +16 -0
  394. kiapi/core/file/_services/file_store.py +138 -0
  395. kiapi/core/file/_settings.py +22 -0
  396. kiapi/core/file/_types/file_id.py +2 -0
  397. kiapi/core/file/_types/file_ref.py +12 -0
  398. kiapi/core/file/_views/resolved_file_ref.py +16 -0
  399. kiapi/core/job/__init__.py +24 -0
  400. kiapi/core/job/_enums/job_status.py +9 -0
  401. kiapi/core/job/_helpers/creep_progress.py +123 -0
  402. kiapi/core/job/_models/job.py +121 -0
  403. kiapi/core/job/_services/job_store.py +33 -0
  404. kiapi/core/job/_services/progress_reporter.py +79 -0
  405. kiapi/core/job/_types/job_id.py +3 -0
  406. kiapi/core/job/_types/job_result.py +5 -0
  407. kiapi/core/job/_types/job_type.py +4 -0
  408. kiapi/core/logging/__init__.py +14 -0
  409. kiapi/core/logging/_helpers/get_log_level.py +8 -0
  410. kiapi/core/logging/_helpers/setup_logger.py +14 -0
  411. kiapi/core/logging/_settings.py +34 -0
  412. kiapi/core/logging/_types/log_level.py +3 -0
  413. kiapi/core/memory/__init__.py +21 -0
  414. kiapi/core/memory/_exceptions/memory_budget_error.py +2 -0
  415. kiapi/core/memory/_helpers/create_memory_manager.py +17 -0
  416. kiapi/core/memory/_operations/log_memory_limit.py +29 -0
  417. kiapi/core/memory/_operations/resolve_effective_memory_limit_gb.py +13 -0
  418. kiapi/core/memory/_operations/resolve_framework_strategy.py +42 -0
  419. kiapi/core/memory/_schemas/framework_strategy.py +14 -0
  420. kiapi/core/memory/_schemas/memory_stats.py +24 -0
  421. kiapi/core/memory/_schemas/resident_model.py +14 -0
  422. kiapi/core/memory/_schemas/resident_model_stats.py +49 -0
  423. kiapi/core/memory/_services/memory_manager.py +257 -0
  424. kiapi/core/memory/_settings.py +36 -0
  425. kiapi/core/memory/_types/framework_name.py +4 -0
  426. kiapi/core/memory/_types/resident_key.py +4 -0
  427. kiapi/core/model/__init__.py +36 -0
  428. kiapi/core/model/_exceptions/unknown_model_error.py +2 -0
  429. kiapi/core/model/_schemas/model_spec.py +145 -0
  430. kiapi/core/model/_services/model_registry.py +85 -0
  431. kiapi/core/model/_types/model_alias.py +2 -0
  432. kiapi/core/model/_types/model_domain.py +6 -0
  433. kiapi/core/model/_types/model_family.py +5 -0
  434. kiapi/core/model/_types/model_key.py +8 -0
  435. kiapi/core/model/_types/model_name.py +4 -0
  436. kiapi/core/model/_types/model_repo.py +3 -0
  437. kiapi/core/net/__init__.py +16 -0
  438. kiapi/core/net/_exceptions/unsafe_url_error.py +7 -0
  439. kiapi/core/net/_helpers/verify_public_url.py +68 -0
  440. kiapi/core/net/_settings.py +28 -0
  441. kiapi/core/setup/__init__.py +25 -0
  442. kiapi/core/setup/_exceptions/setup_required_error.py +2 -0
  443. kiapi/core/setup/_schemas/docker_image_resource.py +37 -0
  444. kiapi/core/setup/_schemas/hf_snapshot_resource.py +50 -0
  445. kiapi/core/setup/_schemas/local_path_resource.py +43 -0
  446. kiapi/core/setup/_schemas/python_package_resource.py +58 -0
  447. kiapi/core/setup/_schemas/python_venv_resource.py +63 -0
  448. kiapi/core/setup/_schemas/setup_status.py +7 -0
  449. kiapi/core/setup/_schemas/url_file_resource.py +43 -0
  450. kiapi/core/setup/_services/setup_manager.py +331 -0
  451. kiapi/core/setup/_types/setup_resource.py +21 -0
  452. kiapi/core/setup/_types/setup_target.py +11 -0
  453. kiapi/core/workdir/__init__.py +8 -0
  454. kiapi/core/workdir/_helpers/create_work_dir.py +11 -0
  455. kiapi/core/workdir/_settings.py +21 -0
  456. kiapi/core/worker/__init__.py +23 -0
  457. kiapi/core/worker/_helpers/create_worker.py +9 -0
  458. kiapi/core/worker/_services/worker.py +165 -0
  459. kiapi/core/worker/_settings.py +36 -0
  460. kiapi/core/worker/_types/job_thunk.py +6 -0
  461. kiapi-0.1.0.dist-info/METADATA +337 -0
  462. kiapi-0.1.0.dist-info/RECORD +465 -0
  463. kiapi-0.1.0.dist-info/WHEEL +4 -0
  464. kiapi-0.1.0.dist-info/entry_points.txt +2 -0
  465. 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,8 @@
1
+ """Seed MLX's RNG for a reproducible generation (no-op when ``seed`` is None)."""
2
+
3
+
4
+ def apply_seed(seed: int | None) -> None:
5
+ if seed is not None:
6
+ import mlx.core as mx
7
+
8
+ mx.random.seed(seed)
@@ -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