hugpy-engine 0.2.6__tar.gz → 0.3.1__tar.gz

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 (234) hide show
  1. hugpy_engine-0.3.1/PKG-INFO +22 -0
  2. hugpy_engine-0.3.1/README.md +3 -0
  3. hugpy_engine-0.3.1/pyproject.toml +41 -0
  4. hugpy_engine-0.3.1/src/hugpy_engine/__init__.py +49 -0
  5. hugpy_engine-0.3.1/src/hugpy_engine/config/main.py +84 -0
  6. hugpy_engine-0.3.1/src/hugpy_engine/config/models/models_config.py +190 -0
  7. hugpy_engine-0.3.1/src/hugpy_engine/config/models/models_default.py +150 -0
  8. {hugpy_engine-0.2.6 → hugpy_engine-0.3.1}/src/hugpy_engine/dispatch/__init__.py +1 -2
  9. {hugpy_engine-0.2.6 → hugpy_engine-0.3.1}/src/hugpy_engine/dispatch/dispatch.py +43 -1
  10. hugpy_engine-0.3.1/src/hugpy_engine/llama/runners/fitevict_runner.py +197 -0
  11. {hugpy_engine-0.2.6 → hugpy_engine-0.3.1}/src/hugpy_engine/placement.py +15 -1
  12. hugpy_engine-0.3.1/src/hugpy_engine/resolvers/__init__.py +22 -0
  13. {hugpy_engine-0.2.6 → hugpy_engine-0.3.1}/src/hugpy_engine/resolvers/categories/builders.py +7 -1
  14. {hugpy_engine-0.2.6 → hugpy_engine-0.3.1}/src/hugpy_engine/resolvers/categories/frameworks.py +11 -3
  15. {hugpy_engine-0.2.6 → hugpy_engine-0.3.1}/src/hugpy_engine/resolvers/model_resolver.py +58 -176
  16. {hugpy_engine-0.2.6 → hugpy_engine-0.3.1}/src/hugpy_engine/schemas/chat_schemas.py +1 -0
  17. {hugpy_engine-0.2.6 → hugpy_engine-0.3.1}/src/hugpy_engine/schemas/event_schemas.py +1 -0
  18. {hugpy_engine-0.2.6 → hugpy_engine-0.3.1}/src/hugpy_engine/schemas/runner_schemas.py +1 -1
  19. {hugpy_engine-0.2.6 → hugpy_engine-0.3.1}/src/hugpy_engine/schemas/task_schemas.py +1 -0
  20. hugpy_engine-0.3.1/src/hugpy_engine.egg-info/PKG-INFO +22 -0
  21. hugpy_engine-0.3.1/src/hugpy_engine.egg-info/SOURCES.txt +38 -0
  22. hugpy_engine-0.3.1/src/hugpy_engine.egg-info/requires.txt +3 -0
  23. hugpy_engine-0.2.6/LICENSE +0 -41
  24. hugpy_engine-0.2.6/PKG-INFO +0 -116
  25. hugpy_engine-0.2.6/README.md +0 -68
  26. hugpy_engine-0.2.6/docs/ALLOCATION-MODES.md +0 -174
  27. hugpy_engine-0.2.6/pyproject.toml +0 -82
  28. hugpy_engine-0.2.6/src/hugpy_engine/__init__.py +0 -163
  29. hugpy_engine-0.2.6/src/hugpy_engine/alloc_modes.py +0 -1342
  30. hugpy_engine-0.2.6/src/hugpy_engine/allocation.py +0 -68
  31. hugpy_engine-0.2.6/src/hugpy_engine/apis/call_api.py +0 -149
  32. hugpy_engine-0.2.6/src/hugpy_engine/apis/get_module.py +0 -649
  33. hugpy_engine-0.2.6/src/hugpy_engine/apis/reclassify.py +0 -238
  34. hugpy_engine-0.2.6/src/hugpy_engine/apis/reconcile.py +0 -628
  35. hugpy_engine-0.2.6/src/hugpy_engine/apis/serve/__init__.py +0 -22
  36. hugpy_engine-0.2.6/src/hugpy_engine/apis/serve/serve.py +0 -33
  37. hugpy_engine-0.2.6/src/hugpy_engine/apis/serve/serve_cli.py +0 -9
  38. hugpy_engine-0.2.6/src/hugpy_engine/apis/systemd_units.py +0 -9
  39. hugpy_engine-0.2.6/src/hugpy_engine/backend.py +0 -86
  40. hugpy_engine-0.2.6/src/hugpy_engine/backends/__init__.py +0 -5
  41. hugpy_engine-0.2.6/src/hugpy_engine/backends/local.py +0 -163
  42. hugpy_engine-0.2.6/src/hugpy_engine/catalog.py +0 -99
  43. hugpy_engine-0.2.6/src/hugpy_engine/catalog_bridge.py +0 -230
  44. hugpy_engine-0.2.6/src/hugpy_engine/chat_context/__init__.py +0 -27
  45. hugpy_engine-0.2.6/src/hugpy_engine/chat_context/chat_context.py +0 -223
  46. hugpy_engine-0.2.6/src/hugpy_engine/chat_context/context_budget.py +0 -223
  47. hugpy_engine-0.2.6/src/hugpy_engine/chat_context/unbounded.py +0 -55
  48. hugpy_engine-0.2.6/src/hugpy_engine/config/main.py +0 -286
  49. hugpy_engine-0.2.6/src/hugpy_engine/config/models/model_meta.py +0 -206
  50. hugpy_engine-0.2.6/src/hugpy_engine/config/models/models_config.py +0 -1838
  51. hugpy_engine-0.2.6/src/hugpy_engine/config/models/models_default.py +0 -196
  52. hugpy_engine-0.2.6/src/hugpy_engine/config/models/models_dict.py +0 -73
  53. hugpy_engine-0.2.6/src/hugpy_engine/contracts.py +0 -44
  54. hugpy_engine-0.2.6/src/hugpy_engine/dispatch/acquire.py +0 -59
  55. hugpy_engine-0.2.6/src/hugpy_engine/dispatch/activity.py +0 -122
  56. hugpy_engine-0.2.6/src/hugpy_engine/draft_models.py +0 -72
  57. hugpy_engine-0.2.6/src/hugpy_engine/engines.py +0 -44
  58. hugpy_engine-0.2.6/src/hugpy_engine/eviction.py +0 -662
  59. hugpy_engine-0.2.6/src/hugpy_engine/fit/__init__.py +0 -28
  60. hugpy_engine-0.2.6/src/hugpy_engine/fit/flex.py +0 -709
  61. hugpy_engine-0.2.6/src/hugpy_engine/fit/gguf_need.py +0 -203
  62. hugpy_engine-0.2.6/src/hugpy_engine/fit/plan.py +0 -769
  63. hugpy_engine-0.2.6/src/hugpy_engine/generate/__init__.py +0 -16
  64. hugpy_engine-0.2.6/src/hugpy_engine/generate/coder.py +0 -694
  65. hugpy_engine-0.2.6/src/hugpy_engine/generate/config.py +0 -258
  66. hugpy_engine-0.2.6/src/hugpy_engine/generate/generate_runner.py +0 -323
  67. hugpy_engine-0.2.6/src/hugpy_engine/gguf_election.py +0 -259
  68. hugpy_engine-0.2.6/src/hugpy_engine/llama/__init__.py +0 -23
  69. hugpy_engine-0.2.6/src/hugpy_engine/llama/runners/__init__.py +0 -23
  70. hugpy_engine-0.2.6/src/hugpy_engine/llama/runners/chat_runner.py +0 -266
  71. hugpy_engine-0.2.6/src/hugpy_engine/llama/runners/get.py +0 -852
  72. hugpy_engine-0.2.6/src/hugpy_engine/llama/runners/src/__init__.py +0 -9
  73. hugpy_engine-0.2.6/src/hugpy_engine/llama/runners/src/base_runner.py +0 -660
  74. hugpy_engine-0.2.6/src/hugpy_engine/llama/runners/src/ccp_runner.py +0 -351
  75. hugpy_engine-0.2.6/src/hugpy_engine/llama/runners/src/imports/__init__.py +0 -25
  76. hugpy_engine-0.2.6/src/hugpy_engine/llama/runners/src/imports/config.py +0 -16
  77. hugpy_engine-0.2.6/src/hugpy_engine/llama/runners/src/imports/constants.py +0 -38
  78. hugpy_engine-0.2.6/src/hugpy_engine/llama/runners/src/imports/init_imports.py +0 -6
  79. hugpy_engine-0.2.6/src/hugpy_engine/llama/runners/src/imports/utils.py +0 -64
  80. hugpy_engine-0.2.6/src/hugpy_engine/llama/runners/src/python_runner.py +0 -511
  81. hugpy_engine-0.2.6/src/hugpy_engine/llama/runners/src/shard_server.py +0 -341
  82. hugpy_engine-0.2.6/src/hugpy_engine/llama/serve.py +0 -9
  83. hugpy_engine-0.2.6/src/hugpy_engine/manifest.py +0 -53
  84. hugpy_engine-0.2.6/src/hugpy_engine/model_classifier.py +0 -382
  85. hugpy_engine-0.2.6/src/hugpy_engine/model_index/__init__.py +0 -108
  86. hugpy_engine-0.2.6/src/hugpy_engine/model_index/client.py +0 -101
  87. hugpy_engine-0.2.6/src/hugpy_engine/model_index/planner/__init__.py +0 -5
  88. hugpy_engine-0.2.6/src/hugpy_engine/model_index/planner/compute.py +0 -714
  89. hugpy_engine-0.2.6/src/hugpy_engine/model_index/planner/store.py +0 -466
  90. hugpy_engine-0.2.6/src/hugpy_engine/model_index/query_registry.py +0 -1052
  91. hugpy_engine-0.2.6/src/hugpy_engine/model_index/repositories.py +0 -302
  92. hugpy_engine-0.2.6/src/hugpy_engine/model_index/service.py +0 -1025
  93. hugpy_engine-0.2.6/src/hugpy_engine/native/__init__.py +0 -19
  94. hugpy_engine-0.2.6/src/hugpy_engine/native/build.py +0 -98
  95. hugpy_engine-0.2.6/src/hugpy_engine/native/fetch.py +0 -194
  96. hugpy_engine-0.2.6/src/hugpy_engine/native/resolve.py +0 -352
  97. hugpy_engine-0.2.6/src/hugpy_engine/output_repair.py +0 -212
  98. hugpy_engine-0.2.6/src/hugpy_engine/peft_adapters.py +0 -293
  99. hugpy_engine-0.2.6/src/hugpy_engine/query.py +0 -191
  100. hugpy_engine-0.2.6/src/hugpy_engine/resolvers/__init__.py +0 -26
  101. hugpy_engine-0.2.6/src/hugpy_engine/resolvers/allocator.py +0 -126
  102. hugpy_engine-0.2.6/src/hugpy_engine/resolvers/device_placement.py +0 -212
  103. hugpy_engine-0.2.6/src/hugpy_engine/resolvers/groups.py +0 -651
  104. hugpy_engine-0.2.6/src/hugpy_engine/resolvers/model_dict_resolver.py +0 -23
  105. hugpy_engine-0.2.6/src/hugpy_engine/resolvers/remote.py +0 -4606
  106. hugpy_engine-0.2.6/src/hugpy_engine/routing_diagnostics.py +0 -265
  107. hugpy_engine-0.2.6/src/hugpy_engine/runtime.py +0 -84
  108. hugpy_engine-0.2.6/src/hugpy_engine/serve/__init__.py +0 -57
  109. hugpy_engine-0.2.6/src/hugpy_engine/serve/chunksum_verify.py +0 -213
  110. hugpy_engine-0.2.6/src/hugpy_engine/serve/hot_cache.py +0 -1014
  111. hugpy_engine-0.2.6/src/hugpy_engine/serve/load_failure.py +0 -271
  112. hugpy_engine-0.2.6/src/hugpy_engine/serve/model_cache.py +0 -218
  113. hugpy_engine-0.2.6/src/hugpy_engine/serve/overrides.py +0 -1156
  114. hugpy_engine-0.2.6/src/hugpy_engine/serve/policy.py +0 -45
  115. hugpy_engine-0.2.6/src/hugpy_engine/serve/profiles.py +0 -417
  116. hugpy_engine-0.2.6/src/hugpy_engine/serve/serve.py +0 -930
  117. hugpy_engine-0.2.6/src/hugpy_engine/serve/serve_cli.py +0 -79
  118. hugpy_engine-0.2.6/src/hugpy_engine/serve/slot_agent.py +0 -2626
  119. hugpy_engine-0.2.6/src/hugpy_engine/serve/slots.py +0 -882
  120. hugpy_engine-0.2.6/src/hugpy_engine/serve/supervisor.py +0 -148
  121. hugpy_engine-0.2.6/src/hugpy_engine/serve/tf_child.py +0 -553
  122. hugpy_engine-0.2.6/src/hugpy_engine/spill.py +0 -1749
  123. hugpy_engine-0.2.6/src/hugpy_engine/task_deps.py +0 -67
  124. hugpy_engine-0.2.6/src/hugpy_engine/utils/json_scavenge.py +0 -111
  125. hugpy_engine-0.2.6/src/hugpy_engine/utils/no_think.py +0 -325
  126. hugpy_engine-0.2.6/src/hugpy_engine/wire/chat_schemas.py +0 -73
  127. hugpy_engine-0.2.6/src/hugpy_engine/wire/config_schemas.py +0 -31
  128. hugpy_engine-0.2.6/src/hugpy_engine/wire/install_schemas.py +0 -17
  129. hugpy_engine-0.2.6/src/hugpy_engine/wire/model_schemas.py +0 -43
  130. hugpy_engine-0.2.6/src/hugpy_engine/wire/request_schemas.py +0 -13
  131. hugpy_engine-0.2.6/src/hugpy_engine/wire/specs_schemas.py +0 -9
  132. hugpy_engine-0.2.6/src/hugpy_engine.egg-info/PKG-INFO +0 -116
  133. hugpy_engine-0.2.6/src/hugpy_engine.egg-info/SOURCES.txt +0 -220
  134. hugpy_engine-0.2.6/src/hugpy_engine.egg-info/entry_points.txt +0 -2
  135. hugpy_engine-0.2.6/src/hugpy_engine.egg-info/requires.txt +0 -28
  136. hugpy_engine-0.2.6/src/hugpy_engine.egg-info/scm_file_list.json +0 -216
  137. hugpy_engine-0.2.6/src/hugpy_engine.egg-info/scm_version.json +0 -8
  138. hugpy_engine-0.2.6/tests/conftest.py +0 -41
  139. hugpy_engine-0.2.6/tests/known_good/test_call_queue_contract.py +0 -357
  140. hugpy_engine-0.2.6/tests/known_good/test_dependency_acquisition_contract.py +0 -252
  141. hugpy_engine-0.2.6/tests/known_good/test_evict_fit_contract.py +0 -198
  142. hugpy_engine-0.2.6/tests/known_good/test_key_equivalence_contract.py +0 -99
  143. hugpy_engine-0.2.6/tests/known_good/test_missing_dependency_contract.py +0 -116
  144. hugpy_engine-0.2.6/tests/known_good/test_model_format_contract.py +0 -193
  145. hugpy_engine-0.2.6/tests/known_good/test_model_key_contract.py +0 -318
  146. hugpy_engine-0.2.6/tests/known_good/test_plan_fit_contract.py +0 -765
  147. hugpy_engine-0.2.6/tests/known_good/test_politeness_retired_contract.py +0 -65
  148. hugpy_engine-0.2.6/tests/known_good/test_prefill_progress_contract.py +0 -99
  149. hugpy_engine-0.2.6/tests/known_good/test_relay_gate_allocation_contract.py +0 -111
  150. hugpy_engine-0.2.6/tests/known_good/test_relay_stream_stall_contract.py +0 -279
  151. hugpy_engine-0.2.6/tests/known_good/test_remote_error_structure_contract.py +0 -133
  152. hugpy_engine-0.2.6/tests/known_good/test_response_format_contract.py +0 -104
  153. hugpy_engine-0.2.6/tests/known_good/test_slot_seat_admission_contract.py +0 -196
  154. hugpy_engine-0.2.6/tests/known_good/test_slot_serialization_contract.py +0 -159
  155. hugpy_engine-0.2.6/tests/test_admission_gate_and_diagnostics.py +0 -178
  156. hugpy_engine-0.2.6/tests/test_allocation_accuracy.py +0 -257
  157. hugpy_engine-0.2.6/tests/test_budget_bar_spec.py +0 -179
  158. hugpy_engine-0.2.6/tests/test_catalog_bridge.py +0 -100
  159. hugpy_engine-0.2.6/tests/test_central_cap_gating.py +0 -295
  160. hugpy_engine-0.2.6/tests/test_chat_request_caller.py +0 -40
  161. hugpy_engine-0.2.6/tests/test_chat_role_alternation.py +0 -563
  162. hugpy_engine-0.2.6/tests/test_chat_tool_messages_schema.py +0 -113
  163. hugpy_engine-0.2.6/tests/test_chunksum_verify.py +0 -231
  164. hugpy_engine-0.2.6/tests/test_comms_foundations.py +0 -113
  165. hugpy_engine-0.2.6/tests/test_ctx_fit.py +0 -296
  166. hugpy_engine-0.2.6/tests/test_ctx_range_flex.py +0 -31
  167. hugpy_engine-0.2.6/tests/test_device_placement.py +0 -151
  168. hugpy_engine-0.2.6/tests/test_engine_boundary.py +0 -98
  169. hugpy_engine-0.2.6/tests/test_engine_persisted_resolve.py +0 -295
  170. hugpy_engine-0.2.6/tests/test_evict_releases_transformers_registry.py +0 -51
  171. hugpy_engine-0.2.6/tests/test_eviction_flow.py +0 -411
  172. hugpy_engine-0.2.6/tests/test_feasible_distribution_engine.py +0 -89
  173. hugpy_engine-0.2.6/tests/test_gguf_election.py +0 -301
  174. hugpy_engine-0.2.6/tests/test_gguf_sharded_effective.py +0 -162
  175. hugpy_engine-0.2.6/tests/test_hot_cache.py +0 -357
  176. hugpy_engine-0.2.6/tests/test_image_task_classification.py +0 -405
  177. hugpy_engine-0.2.6/tests/test_import_policy.py +0 -107
  178. hugpy_engine-0.2.6/tests/test_load_failure_reason.py +0 -188
  179. hugpy_engine-0.2.6/tests/test_mmproj_effective_bytes.py +0 -79
  180. hugpy_engine-0.2.6/tests/test_model_aliases.py +0 -86
  181. hugpy_engine-0.2.6/tests/test_no_makeroom.py +0 -316
  182. hugpy_engine-0.2.6/tests/test_no_think.py +0 -329
  183. hugpy_engine-0.2.6/tests/test_peft_adapter_load.py +0 -379
  184. hugpy_engine-0.2.6/tests/test_per_device_slot.py +0 -125
  185. hugpy_engine-0.2.6/tests/test_per_request_alloc.py +0 -91
  186. hugpy_engine-0.2.6/tests/test_placement_defaults.py +0 -104
  187. hugpy_engine-0.2.6/tests/test_planner_encroachment.py +0 -39
  188. hugpy_engine-0.2.6/tests/test_planner_worker_totals.py +0 -20
  189. hugpy_engine-0.2.6/tests/test_profile_worker_base.py +0 -25
  190. hugpy_engine-0.2.6/tests/test_provision_failure_honesty.py +0 -120
  191. hugpy_engine-0.2.6/tests/test_ram_residents_plan.py +0 -50
  192. hugpy_engine-0.2.6/tests/test_registry_attribution.py +0 -144
  193. hugpy_engine-0.2.6/tests/test_registry_symmetry.py +0 -172
  194. hugpy_engine-0.2.6/tests/test_resolve_routing_key_reconcile.py +0 -92
  195. hugpy_engine-0.2.6/tests/test_runner_registry_heartbeat_starvation.py +0 -100
  196. hugpy_engine-0.2.6/tests/test_slot_agent_loading_state.py +0 -75
  197. hugpy_engine-0.2.6/tests/test_slot_load_stall_health.py +0 -228
  198. hugpy_engine-0.2.6/tests/test_slot_moe_vram_preflight.py +0 -169
  199. hugpy_engine-0.2.6/tests/test_slot_parallel_kv.py +0 -37
  200. hugpy_engine-0.2.6/tests/test_slot_placement_projection.py +0 -37
  201. hugpy_engine-0.2.6/tests/test_slot_port_guard.py +0 -50
  202. hugpy_engine-0.2.6/tests/test_slot_relaunch.py +0 -211
  203. hugpy_engine-0.2.6/tests/test_slot_served_ctx.py +0 -24
  204. hugpy_engine-0.2.6/tests/test_spill_ctx_reserve.py +0 -414
  205. hugpy_engine-0.2.6/tests/test_spill_vision_fit.py +0 -233
  206. hugpy_engine-0.2.6/tests/test_tasks_registry.py +0 -186
  207. hugpy_engine-0.2.6/tests/test_tf_child.py +0 -111
  208. hugpy_engine-0.2.6/tests/test_tf_slot_and_routing.py +0 -57
  209. hugpy_engine-0.2.6/tests/test_tok_s_plausible_ceiling.py +0 -51
  210. hugpy_engine-0.2.6/tests/test_transformers_kv_geometry_hybrid_vl.py +0 -30
  211. hugpy_engine-0.2.6/tests/test_transformers_placement.py +0 -130
  212. hugpy_engine-0.2.6/tests/test_video_model_classification.py +0 -289
  213. hugpy_engine-0.2.6/tests/test_vision_needs_slot.py +0 -242
  214. hugpy_engine-0.2.6/tests/test_vram_cliff.py +0 -766
  215. hugpy_engine-0.2.6/tests/test_worker_error_attribution.py +0 -66
  216. hugpy_engine-0.2.6/tests/test_worker_serve_path_central_only.py +0 -104
  217. {hugpy_engine-0.2.6 → hugpy_engine-0.3.1}/setup.cfg +0 -0
  218. {hugpy_engine-0.2.6 → hugpy_engine-0.3.1}/src/hugpy_engine/categories.py +0 -0
  219. {hugpy_engine-0.2.6/src/hugpy_engine/apis → hugpy_engine-0.3.1/src/hugpy_engine/config}/__init__.py +0 -0
  220. {hugpy_engine-0.2.6/src/hugpy_engine/config → hugpy_engine-0.3.1/src/hugpy_engine/config/models}/__init__.py +0 -0
  221. {hugpy_engine-0.2.6/src/hugpy_engine/config/models → hugpy_engine-0.3.1/src/hugpy_engine/fit}/__init__.py +0 -0
  222. {hugpy_engine-0.2.6 → hugpy_engine-0.3.1}/src/hugpy_engine/fit/types.py +0 -0
  223. {hugpy_engine-0.2.6/src/hugpy_engine/schemas → hugpy_engine-0.3.1/src/hugpy_engine/llama}/__init__.py +0 -0
  224. {hugpy_engine-0.2.6/src/hugpy_engine/utils → hugpy_engine-0.3.1/src/hugpy_engine/llama/runners}/__init__.py +0 -0
  225. {hugpy_engine-0.2.6 → hugpy_engine-0.3.1}/src/hugpy_engine/name_match.py +0 -0
  226. {hugpy_engine-0.2.6 → hugpy_engine-0.3.1}/src/hugpy_engine/py.typed +0 -0
  227. {hugpy_engine-0.2.6 → hugpy_engine-0.3.1}/src/hugpy_engine/resolvers/assure_model_key.py +0 -0
  228. {hugpy_engine-0.2.6 → hugpy_engine-0.3.1}/src/hugpy_engine/resolvers/categories/__init__.py +0 -0
  229. {hugpy_engine-0.2.6/src/hugpy_engine/wire → hugpy_engine-0.3.1/src/hugpy_engine/schemas}/__init__.py +0 -0
  230. {hugpy_engine-0.2.6 → hugpy_engine-0.3.1}/src/hugpy_engine/schemas/metadata_schemas.py +0 -0
  231. {hugpy_engine-0.2.6 → hugpy_engine-0.3.1}/src/hugpy_engine/schemas/model_schemas.py +0 -0
  232. {hugpy_engine-0.2.6 → hugpy_engine-0.3.1}/src/hugpy_engine/tasks.py +0 -0
  233. {hugpy_engine-0.2.6 → hugpy_engine-0.3.1}/src/hugpy_engine.egg-info/dependency_links.txt +0 -0
  234. {hugpy_engine-0.2.6 → hugpy_engine-0.3.1}/src/hugpy_engine.egg-info/top_level.txt +0 -0
@@ -0,0 +1,22 @@
1
+ Metadata-Version: 2.4
2
+ Name: hugpy-engine
3
+ Version: 0.3.1
4
+ Summary: Hugpy engine: model discovery and classification, allocation and eviction, native llama.cpp and GGUF runners, and the prompt-to-reply inference path with task and placement seams
5
+ Author-email: putkoff <support@hugpy.ai>
6
+ License-Expression: LicenseRef-Proprietary
7
+ Keywords: hugpy,llm,self-hosted,inference,llama.cpp,gguf,transformers,model-serving
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: hugpy-platform
17
+ Requires-Dist: abstract-essentials
18
+ Requires-Dist: pydantic>=2
19
+
20
+ # hugpy-engine
21
+
22
+ lean 0.3.0 — shared helpers for hugpy arms; everything else retired.
@@ -0,0 +1,3 @@
1
+ # hugpy-engine
2
+
3
+ lean 0.3.0 — shared helpers for hugpy arms; everything else retired.
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "hugpy-engine"
7
+ version = "0.3.1"
8
+ description = "Hugpy engine: model discovery and classification, allocation and eviction, native llama.cpp and GGUF runners, and the prompt-to-reply inference path with task and placement seams"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "LicenseRef-Proprietary"
12
+ authors = [{ name = "putkoff", email = "support@hugpy.ai" }]
13
+ keywords = [
14
+ "hugpy",
15
+ "llm",
16
+ "self-hosted",
17
+ "inference",
18
+ "llama.cpp",
19
+ "gguf",
20
+ "transformers",
21
+ "model-serving",
22
+ ]
23
+ classifiers = [
24
+ "Development Status :: 3 - Alpha",
25
+ "Intended Audience :: Developers",
26
+ "Operating System :: OS Independent",
27
+ "Programming Language :: Python :: 3",
28
+ "Programming Language :: Python :: 3 :: Only",
29
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
30
+ ]
31
+ dependencies = [
32
+ "hugpy-platform",
33
+ "abstract-essentials",
34
+ "pydantic>=2",
35
+ ]
36
+
37
+ [tool.setuptools.packages.find]
38
+ where = ["src"]
39
+
40
+ [tool.setuptools.package-data]
41
+ hugpy_engine = ["py.typed"]
@@ -0,0 +1,49 @@
1
+ """Hugpy inference engine — the one-call prompt API, revived.
2
+
3
+ ``execute_prompt(*args, **kwargs)`` returns the output you expected for a task:
4
+ positional args are inferred by type (str -> a model id or a prompt, int ->
5
+ max_new_tokens, float -> temperature then top_p, bool -> do_sample, list ->
6
+ messages), the request is resolved to a model + task, and the call is handed to
7
+ the box's fitevict front door through hugpy_core's relay (which picks the
8
+ worker and owns evict-to-fit, load, serving and the call log).
9
+
10
+ Chat/vision run today; a task whose runner has no front-door endpoint yet
11
+ resolves to a clean, named error (fail-as-data).
12
+ """
13
+ from __future__ import annotations
14
+
15
+ try:
16
+ from importlib.metadata import version as _package_version
17
+
18
+ __version__ = _package_version("hugpy-engine")
19
+ except Exception: # pragma: no cover - source tree without metadata
20
+ __version__ = "0.0.0+unknown"
21
+
22
+ from hugpy_engine.dispatch.dispatch import (
23
+ execute_chat_stream,
24
+ execute_prompt,
25
+ execute_prompt_stream,
26
+ infer_arg_name,
27
+ normalize_prompt_kwargs,
28
+ runner_for,
29
+ )
30
+ from hugpy_engine.resolvers import resolve, resolve_model_key
31
+ from hugpy_engine.config.models.models_config import (
32
+ MODEL_REGISTRY,
33
+ get_model_registry,
34
+ refresh_registry,
35
+ )
36
+
37
+ __all__ = [
38
+ "MODEL_REGISTRY",
39
+ "execute_chat_stream",
40
+ "execute_prompt",
41
+ "execute_prompt_stream",
42
+ "get_model_registry",
43
+ "infer_arg_name",
44
+ "normalize_prompt_kwargs",
45
+ "refresh_registry",
46
+ "resolve",
47
+ "resolve_model_key",
48
+ "runner_for",
49
+ ]
@@ -0,0 +1,84 @@
1
+ """Registry lookups over the live MODEL_REGISTRY (hugpy_next cut 2, lean).
2
+
3
+ The heavy disk/path resolution of the legacy config.main is DROPPED — hugpy
4
+ hosts all weights and the box's fitevict front door owns every load, so the
5
+ engine never resolves a model directory. These helpers answer "what are the
6
+ facts of model X" from the registry (sourced from hugpy_core's catalog).
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from typing import Optional
11
+
12
+ from hugpy_engine.config.models.models_config import (
13
+ MODEL_REGISTRY,
14
+ get_model_registry,
15
+ refresh_registry,
16
+ )
17
+
18
+
19
+ def list_models():
20
+ refresh_registry()
21
+ return list(MODEL_REGISTRY.keys())
22
+
23
+
24
+ def _resolve_model_key(model_key, registry, prefer=None):
25
+ """Map a possibly-bare model_key to a concrete registry key (exact, then a
26
+ unique ``owner~name`` tail, then the ``-GGUF`` format equivalent)."""
27
+ if model_key in registry:
28
+ return model_key
29
+
30
+ def _bare(k):
31
+ return k.split("~", 1)[1] if "~" in k else k
32
+
33
+ candidates = sorted(k for k in registry if _bare(k) == model_key)
34
+ if not candidates:
35
+ try:
36
+ from hugpy_engine.fit.types import key_equivalent
37
+ candidates = sorted(k for k in registry if key_equivalent(_bare(k), model_key))
38
+ except Exception: # noqa: BLE001
39
+ candidates = []
40
+ if not candidates:
41
+ return None
42
+ if len(candidates) == 1:
43
+ return candidates[0]
44
+ for p in (prefer or []):
45
+ if p in candidates:
46
+ return p
47
+ for c in candidates:
48
+ if _bare(c) == _bare(p):
49
+ return c
50
+ return candidates[0]
51
+
52
+
53
+ def get_model_config(model_key: str = None, dict_return=False, return_dict=False,
54
+ key: str = None, prefer=None):
55
+ model_key = model_key or key
56
+ refresh_registry()
57
+ registry = get_model_registry(dict_return=dict_return, return_dict=return_dict)
58
+ resolved = _resolve_model_key(model_key, MODEL_REGISTRY, prefer=prefer)
59
+ if resolved is None:
60
+ raise KeyError(f"Unknown model: {model_key}")
61
+ return registry[resolved]
62
+
63
+
64
+ def get_model_path(key: str) -> Optional[str]:
65
+ """The model's folder as recorded on its registry row. Not load-bearing (the
66
+ front door routes by model_key); best-effort for callers that still ask."""
67
+ cfg = get_model_config(key)
68
+ return getattr(cfg, "folder", None)
69
+
70
+
71
+ def list_model_options():
72
+ refresh_registry()
73
+ return {
74
+ key: {
75
+ "name": cfg.name,
76
+ "hub_id": cfg.hub_id,
77
+ "folder": cfg.folder,
78
+ "tasks": cfg.tasks,
79
+ "framework": cfg.framework,
80
+ "filename": cfg.filename,
81
+ "port": cfg.port,
82
+ }
83
+ for key, cfg in MODEL_REGISTRY.items()
84
+ }
@@ -0,0 +1,190 @@
1
+ """models_config.py — the registry SOURCE (hugpy_next cut 2 seam).
2
+
3
+ Operator ruling "hugpy.json is the model init": per-model facts (framework,
4
+ tasks, primary_task, hub_id, name) are stamped in each model's hugpy.json. The
5
+ worker stamps them, derives the model KEY per box (``owner~repo`` on an owner
6
+ collision, else bare ``repo`` — fitevict/adapters/llama_cpp.discover_models),
7
+ and relays both the key and the parsed hugpy.json to hugpy_core, which exposes
8
+ them at ``GET {core}/models`` (catalog_rows). That endpoint is the ONE place
9
+ that carries BOTH the core id (the id ``/v1/models`` serves and ``pick()``
10
+ routes on) AND each model's hugpy.json facts — the core id is not derivable from
11
+ a single hugpy.json on disk (the collision qualifier is per-box state), so the
12
+ registry is keyed from core, never re-derived from disk.
13
+
14
+ So: MODEL_REGISTRY is built by reading hugpy_core's catalog, keyed by the core
15
+ id, mapping each row's hugpy.json facts through ``derive_model_config_row`` into
16
+ ``ModelConfig``. Read lazily, TTL-cached, fail-open: core unreachable / no
17
+ workers online -> the registry keeps whatever it last had (empty at worst), and
18
+ resolution then refuses cleanly ("no online worker has it" / "no chat default").
19
+
20
+ There is NO curated ``MODELS`` fleet and NO disk discovery/classification here
21
+ (operator: no hardcoded model names; every model already carries its task).
22
+ """
23
+ from __future__ import annotations
24
+
25
+ import json
26
+ import logging
27
+ import os
28
+ import threading
29
+ import time
30
+ import urllib.request
31
+ from typing import Dict, Optional, Tuple
32
+
33
+ from hugpy_engine.schemas.model_schemas import ModelConfig
34
+
35
+ logger = logging.getLogger(__name__)
36
+
37
+ # No curated staples (operator: no hardcoded model names as defaults). Kept as an
38
+ # empty dict because a few lifted call sites still read ``MODELS`` (e.g.
39
+ # validate_registry's "is this a code-declared staple" check, media defaults).
40
+ MODELS: Dict[str, dict] = {}
41
+
42
+ # The live registry, keyed by the core id. Updated IN PLACE by refresh_registry
43
+ # so modules that imported these names by reference always see the current set.
44
+ MODEL_REGISTRY: Dict[str, ModelConfig] = {}
45
+ MODEL_REGISTRY_DICT: Dict[str, dict] = {}
46
+
47
+ _CORE_URL = (os.environ.get("HUGPY_CORE_URL") or "http://127.0.0.1:7020").rstrip("/")
48
+ _TTL_S = float(os.environ.get("HUGPY_REGISTRY_TTL_S", "30"))
49
+ _TIMEOUT_S = float(os.environ.get("HUGPY_REGISTRY_TIMEOUT_S", "10"))
50
+ _lock = threading.RLock()
51
+ _last_fetch = 0.0
52
+
53
+
54
+ def _dedup(seq) -> list:
55
+ """Order-preserving de-dup, dropping falsy entries."""
56
+ out: list = []
57
+ for x in seq or []:
58
+ if x and x not in out:
59
+ out.append(x)
60
+ return out
61
+
62
+
63
+ def derive_model_config_row(name, row) -> "Tuple[Optional[dict], Optional[str]]":
64
+ """One hugpy_core catalog row -> a ModelConfig-ready dict, or (None, reason).
65
+
66
+ ``name`` is the core id (the registry key). ``row`` is a catalog_rows entry;
67
+ its ``hugpy_json`` sub-dict is authoritative for framework/tasks/
68
+ primary_task/hub_id/name (operator: hugpy.json is the model init), with the
69
+ row's own top-level fields as the fallback the catalog already flattened.
70
+
71
+ framework == the wrapper's engine (gguf, transformers, comfy, ...): one
72
+ concept, kept verbatim. tasks are the plain HF pipeline-tag strings straight
73
+ from hugpy.json, no renaming. A null ``tasks`` (seen on some GGUF markers)
74
+ falls back to ``[primary_task]`` so every model has at least its own task.
75
+ """
76
+ hj = row.get("hugpy_json") if isinstance(row.get("hugpy_json"), dict) else {}
77
+
78
+ framework = hj.get("framework") or row.get("framework")
79
+ if isinstance(framework, (list, tuple)):
80
+ framework = framework[0] if framework else None
81
+ framework = str(framework or "").strip().lower() or None
82
+ if not framework:
83
+ return None, f"{name}: no framework/engine recorded"
84
+
85
+ tasks = hj.get("tasks") if isinstance(hj.get("tasks"), list) else None
86
+ if not tasks:
87
+ tasks = row.get("tasks") if isinstance(row.get("tasks"), list) else None
88
+ primary = hj.get("primary_task") or row.get("primary_task") or row.get("task")
89
+ tasks = _dedup(tasks or ([primary] if primary else []))
90
+ if not tasks:
91
+ return None, f"{name}: no tasks and no primary_task recorded"
92
+ if not primary or primary not in tasks:
93
+ primary = tasks[0]
94
+ else:
95
+ # primary must be present; already is. Keep order with primary first is
96
+ # not required (ModelConfig only checks membership).
97
+ pass
98
+
99
+ hub_id = hj.get("hub_id") or row.get("hub_id") or name
100
+ disp_name = hj.get("name") or row.get("name") or name
101
+ # folder is a required ModelConfig field; it is not load-bearing here (the
102
+ # fitevict front door routes by model_key, not folder), so the hub_id is a
103
+ # fine stable value.
104
+ folder = str(hub_id or name)
105
+
106
+ return {
107
+ "name": disp_name,
108
+ "model_key": name,
109
+ "hub_id": str(hub_id),
110
+ "folder": folder,
111
+ "framework": framework,
112
+ "tasks": tasks,
113
+ "primary_task": primary,
114
+ "filename": hj.get("filename") or row.get("file"),
115
+ "include": hj.get("include"),
116
+ "model_max_length": hj.get("model_max_length") or row.get("model_max_length"),
117
+ }, None
118
+
119
+
120
+ def _fetch_catalog() -> "Optional[list]":
121
+ """GET {core}/models (catalog_rows). None on any failure (fail-open)."""
122
+ url = _CORE_URL + "/models"
123
+ try:
124
+ req = urllib.request.Request(url, headers={"Accept": "application/json"})
125
+ with urllib.request.urlopen(req, timeout=_TIMEOUT_S) as r:
126
+ data = json.loads(r.read().decode("utf-8") or "null")
127
+ except Exception as exc: # noqa: BLE001 — the registry read never raises
128
+ logger.warning("registry: could not read %s (%s); keeping the last "
129
+ "known model set (%d models)", url, exc, len(MODEL_REGISTRY))
130
+ return None
131
+ if isinstance(data, dict):
132
+ data = data.get("data") or data.get("models") or []
133
+ return data if isinstance(data, list) else []
134
+
135
+
136
+ def refresh_registry(force: bool = False) -> None:
137
+ """Rebuild MODEL_REGISTRY / MODEL_REGISTRY_DICT from hugpy_core's catalog.
138
+
139
+ TTL-cached (HUGPY_REGISTRY_TTL_S) so a burst of calls hits core once. Updates
140
+ the dicts IN PLACE. Fail-open: a failed/empty fetch leaves the current set
141
+ untouched rather than emptying a working registry."""
142
+ global _last_fetch
143
+ now = time.time()
144
+ with _lock:
145
+ if not force and MODEL_REGISTRY and (now - _last_fetch) < _TTL_S:
146
+ return
147
+ rows = _fetch_catalog()
148
+ _last_fetch = now
149
+ if rows is None:
150
+ return # keep the last known set
151
+ fresh: Dict[str, ModelConfig] = {}
152
+ fresh_dict: Dict[str, dict] = {}
153
+ dropped = []
154
+ for row in rows:
155
+ if not isinstance(row, dict):
156
+ continue
157
+ key = row.get("model_key") or row.get("id") or row.get("name")
158
+ if not key:
159
+ continue
160
+ values, why = derive_model_config_row(key, row)
161
+ if values is None:
162
+ dropped.append((key, why))
163
+ continue
164
+ try:
165
+ cfg = ModelConfig(**values)
166
+ except Exception as exc: # noqa: BLE001 — a bad row never breaks the set
167
+ dropped.append((key, f"ModelConfig rejected: {exc}"))
168
+ continue
169
+ fresh[key] = cfg
170
+ fresh_dict[key] = cfg.to_dict()
171
+ if not fresh and MODEL_REGISTRY:
172
+ # Core answered but yielded nothing usable (all workers offline mid
173
+ # refresh): keep the last good set rather than blanking resolution.
174
+ logger.info("registry: catalog refresh yielded 0 usable rows; "
175
+ "keeping %d previous models", len(MODEL_REGISTRY))
176
+ return
177
+ MODEL_REGISTRY.clear(); MODEL_REGISTRY.update(fresh)
178
+ MODEL_REGISTRY_DICT.clear(); MODEL_REGISTRY_DICT.update(fresh_dict)
179
+ if dropped:
180
+ logger.info("registry: %d/%d catalog rows not servable here: %s",
181
+ len(dropped), len(rows),
182
+ ", ".join(f"{k} ({w})" for k, w in dropped[:6]))
183
+ logger.info("registry: refreshed from %s — %d models",
184
+ _CORE_URL, len(MODEL_REGISTRY))
185
+
186
+
187
+ def get_model_registry(dict_return: bool = False, return_dict: bool = False):
188
+ if dict_return or return_dict:
189
+ return MODEL_REGISTRY_DICT
190
+ return MODEL_REGISTRY
@@ -0,0 +1,150 @@
1
+ """Default-model reconciliation (hugpy_next cut 2, lean).
2
+
3
+ TASK_DEFAULTS / MEDIA_DEFAULTS are seeded in categories.py from the configured
4
+ ``DEFAULT_*`` constants. Those seeds are names a fresh box may not actually
5
+ serve, so they are RECONCILED against the live registry (sourced from
6
+ hugpy_core's catalog, keyed by core id): keep the configured default when the
7
+ registry holds it, else stand in a registry model that serves the task group.
8
+
9
+ Every registry row here is a model a worker is serving RIGHT NOW (it came from
10
+ the live catalog), so "installed" is implicit — there is no disk check and no
11
+ ``route_destination`` import. ``reconcile_defaults()`` and
12
+ ``refresh_task_registries()`` are re-callable so resolve() heals the dicts from
13
+ the current model set on every request.
14
+ """
15
+ from typing import Dict
16
+
17
+ from abstract_essentials import make_list
18
+ from hugpy_engine.categories import MEDIA_DEFAULTS, TASK_DEFAULTS
19
+ from hugpy_engine.config.models.models_config import MODEL_REGISTRY
20
+ from hugpy_engine.schemas.model_schemas import ModelConfig
21
+ from hugpy_platform.constants import (
22
+ DEFAULT_AGENT_BRAIN,
23
+ DEFAULT_CHAT_MODEL,
24
+ DEFAULT_EMBED_MODEL,
25
+ DEFAULT_VISION_MODEL,
26
+ DEFAULT_WHISPER_MODEL,
27
+ )
28
+
29
+
30
+ def get_context_tokens():
31
+ return {k: v.model_max_length for k, v in MODEL_REGISTRY.items()}
32
+
33
+
34
+ def default_context_tokens_for_model(model_key: str) -> int:
35
+ return get_context_tokens().get(model_key, 8192)
36
+
37
+
38
+ def get_models_dict_by_tasks(tasks=None):
39
+ tasks = make_list(tasks or [])
40
+ return {k: v for k, v in MODEL_REGISTRY.items()
41
+ if any(t in tasks for t in v.tasks)}
42
+
43
+
44
+ def get_models_dict_by_names(names=None):
45
+ names = make_list(names or [])
46
+ out = {}
47
+ for k, v in MODEL_REGISTRY.items():
48
+ for name in names:
49
+ if name in v.name:
50
+ out[k] = v
51
+ break
52
+ return out
53
+
54
+
55
+ def _reconcile_default(configured: str, registry: Dict[str, ModelConfig],
56
+ prefer_task: str | None = None) -> str:
57
+ """Keep ``configured`` when the registry holds it; otherwise stand in a
58
+ registry model that serves ``prefer_task`` (then key order for determinism).
59
+ Returns ``configured`` unchanged when the registry is empty, so the honest
60
+ "not in MODEL_REGISTRY" refusal downstream stays accurate."""
61
+ if not registry:
62
+ return configured
63
+ if configured in registry:
64
+ return configured
65
+ pool = registry
66
+ if prefer_task:
67
+ preferred = {k: v for k, v in registry.items() if prefer_task in v.tasks}
68
+ pool = preferred or registry
69
+ if not pool:
70
+ return configured
71
+ return min(pool)
72
+
73
+
74
+ # Seeds from constants.py; healed by reconcile_defaults() against the live set.
75
+ _SEED = {
76
+ "chat": DEFAULT_CHAT_MODEL,
77
+ "vision": DEFAULT_VISION_MODEL,
78
+ "whisper": DEFAULT_WHISPER_MODEL,
79
+ "embed": DEFAULT_EMBED_MODEL,
80
+ "brain": DEFAULT_AGENT_BRAIN,
81
+ }
82
+
83
+ # Task-filtered views (updated IN PLACE — other modules hold them by reference).
84
+ CHAT_MODELS_REGISTRY: Dict[str, ModelConfig] = {}
85
+ VISION_MODELS_REGISTRY: Dict[str, ModelConfig] = {}
86
+ WHISPER_MODELS_REGISTRY: Dict[str, ModelConfig] = {}
87
+ EMBED_MODELS_REGISTRY: Dict[str, ModelConfig] = {}
88
+
89
+ _TASK_REGISTRY_GROUPS = (
90
+ (CHAT_MODELS_REGISTRY, ["text-generation", "text-generation-inference", "text2text-generation"]),
91
+ (VISION_MODELS_REGISTRY, ["image-text-to-text", "text-to-image"]),
92
+ (WHISPER_MODELS_REGISTRY, ["automatic-speech-recognition", "speech-recognition"]),
93
+ (EMBED_MODELS_REGISTRY, ["feature-extraction", "sentence-similarity", "sentence-transformers"]),
94
+ )
95
+
96
+
97
+ def refresh_task_registries() -> None:
98
+ """Rebuild the task-filtered registries in place from the live MODEL_REGISTRY
99
+ (update-then-prune so a concurrent reader never sees an empty dict)."""
100
+ for registry, tasks in _TASK_REGISTRY_GROUPS:
101
+ fresh = get_models_dict_by_tasks(tasks=tasks)
102
+ registry.update(fresh)
103
+ for stale in [k for k in registry if k not in fresh]:
104
+ registry.pop(stale, None)
105
+
106
+
107
+ # The reconciled defaults (module globals, rebound by reconcile_defaults()).
108
+ DEFAULT_CHAT_MODEL = _SEED["chat"]
109
+ DEFAULT_VISION_MODEL = _SEED["vision"]
110
+ DEFAULT_WHISPER_MODEL = _SEED["whisper"]
111
+ DEFAULT_EMBED_MODEL = _SEED["embed"]
112
+ DEFAULT_AGENT_BRAIN = _SEED["brain"]
113
+
114
+
115
+ def reconcile_defaults() -> None:
116
+ """Recompute the DEFAULT_* model keys from the live registry and HEAL
117
+ TASK_DEFAULTS / MEDIA_DEFAULTS in place so a task-only / media-only / bare
118
+ request resolves to a real core id instead of a dangling configured name."""
119
+ global DEFAULT_CHAT_MODEL, DEFAULT_VISION_MODEL, DEFAULT_WHISPER_MODEL
120
+ global DEFAULT_EMBED_MODEL, DEFAULT_AGENT_BRAIN
121
+ refresh_task_registries()
122
+ DEFAULT_CHAT_MODEL = _reconcile_default(_SEED["chat"], CHAT_MODELS_REGISTRY,
123
+ prefer_task="text-generation")
124
+ DEFAULT_AGENT_BRAIN = _reconcile_default(_SEED["brain"], CHAT_MODELS_REGISTRY,
125
+ prefer_task="text-generation")
126
+ DEFAULT_VISION_MODEL = _reconcile_default(_SEED["vision"], VISION_MODELS_REGISTRY,
127
+ prefer_task="image-text-to-text")
128
+ DEFAULT_WHISPER_MODEL = _reconcile_default(_SEED["whisper"], WHISPER_MODELS_REGISTRY,
129
+ prefer_task="automatic-speech-recognition")
130
+ DEFAULT_EMBED_MODEL = _reconcile_default(_SEED["embed"], EMBED_MODELS_REGISTRY,
131
+ prefer_task="feature-extraction")
132
+ reconciled = {
133
+ _SEED["chat"]: DEFAULT_CHAT_MODEL,
134
+ _SEED["vision"]: DEFAULT_VISION_MODEL,
135
+ _SEED["whisper"]: DEFAULT_WHISPER_MODEL,
136
+ _SEED["embed"]: DEFAULT_EMBED_MODEL,
137
+ }
138
+ for defaults, keys_are_tasks in ((TASK_DEFAULTS, True), (MEDIA_DEFAULTS, False)):
139
+ for key, model in list(defaults.items()):
140
+ replacement = reconciled.get(model)
141
+ if not replacement or replacement == model:
142
+ continue
143
+ if keys_are_tasks and replacement in MODEL_REGISTRY \
144
+ and key not in MODEL_REGISTRY[replacement].tasks:
145
+ continue # the stand-in serves the group but not this exact task
146
+ defaults[key] = replacement
147
+
148
+
149
+ # Heal once at import (registry may be empty here -> seeds, re-healed per request).
150
+ reconcile_defaults()
@@ -1,5 +1,4 @@
1
1
  """Request dispatch: runner cache, execution, streaming, eviction."""
2
- from hugpy_engine.dispatch.acquire import acquire
3
2
  from hugpy_engine.dispatch.dispatch import (
4
3
  LoadRefusal,
5
4
  clear,
@@ -27,7 +26,7 @@ from hugpy_engine.dispatch.dispatch import (
27
26
  )
28
27
 
29
28
  __all__ = [
30
- "LoadRefusal", "acquire", "clear", "ensure_headroom_for_load", "evict",
29
+ "LoadRefusal", "clear", "ensure_headroom_for_load", "evict",
31
30
  "execute_chat_stream", "execute_prompt", "execute_prompt_stream",
32
31
  "infer_arg_name", "last_used_snapshot", "loaded_disk_detail",
33
32
  "loaded_model_keys", "loading_model_keys", "no_makeroom_active",
@@ -562,11 +562,31 @@ def infer_arg_name(arg: Any) -> Optional[str]:
562
562
  "/" in arg
563
563
  or "_gguf" in lowered
564
564
  or any(tag in lowered for tag in ("qwen", "llama", "mistral", "gpt"))
565
+ # Any live registry key is a model_key — core ids like
566
+ # ``AtomicChat~Qwen3.8-27B-GGUF`` / ``Echo-Mini`` carry none of the
567
+ # name tags above, so the positional model id must be recognised by
568
+ # membership (the one allowed addition, operator 2026-10-04). Guarded
569
+ # + fail-open: an unreachable/empty registry just leaves it "messages".
570
+ or _is_registry_key(arg)
565
571
  )
566
572
  return "model_key" if looks_like_model else "messages"
567
573
  return None
568
574
 
569
575
 
576
+ def _is_registry_key(arg: str) -> bool:
577
+ """True when ``arg`` names a model the registry holds (exact or resolvable).
578
+ Fail-open to False so a registry/import hiccup never misclassifies a prompt."""
579
+ try:
580
+ from hugpy_engine.config.models.models_config import MODEL_REGISTRY, refresh_registry
581
+ refresh_registry()
582
+ if arg in MODEL_REGISTRY:
583
+ return True
584
+ from hugpy_engine.resolvers.assure_model_key import assure_model_key
585
+ return assure_model_key(arg, fuzzy=False) is not None
586
+ except Exception: # noqa: BLE001
587
+ return False
588
+
589
+
570
590
  def normalize_prompt_kwargs(*args: Any, **kwargs: Any) -> Dict[str, Any]:
571
591
  """Convert flexible input into builder-compatible kwargs.
572
592
 
@@ -612,6 +632,20 @@ def runner_for(
612
632
  return _get_or_build_runner(res)
613
633
 
614
634
 
635
+ def _run_coro_sync(coro):
636
+ """Drive an awaitable to its result from a SYNC caller. Uses asyncio.run when
637
+ no loop is running; if one is (rare for this entrypoint), runs it on a short
638
+ worker thread so execute_prompt still returns a concrete result rather than a
639
+ coroutine the sync caller would drop on the floor."""
640
+ try:
641
+ asyncio.get_running_loop()
642
+ except RuntimeError:
643
+ return asyncio.run(coro)
644
+ import concurrent.futures
645
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as ex:
646
+ return ex.submit(lambda: asyncio.run(coro)).result()
647
+
648
+
615
649
  def execute_prompt(*args: Any, **kwargs: Any):
616
650
  """One-shot request -> result. Sync entrypoint; awaits inside if needed.
617
651
 
@@ -625,7 +659,15 @@ def execute_prompt(*args: Any, **kwargs: Any):
625
659
  req = res.builder(prompt_kwargs, res.model_key)
626
660
  runner = _get_or_build_runner(res)
627
661
  touch_model(res.model_key) # residency idle clock
628
- return runner.run(req=req)
662
+ out = runner.run(req=req)
663
+ # Sync one-call contract (the shape the operator loves): a sync caller
664
+ # gets the RESULT, not a coroutine. Runners are async now (the fitevict
665
+ # front door), so await-to-completion here when the caller isn't already
666
+ # in an event loop; under a running loop, hand back the awaitable so the
667
+ # async caller (the HTTP route via await_sync) drives it.
668
+ if inspect.isawaitable(out):
669
+ return _run_coro_sync(out)
670
+ return out
629
671
  finally:
630
672
  # The try body can resume in a DIFFERENT Context (async/greenlet hop),
631
673
  # where reset(token) raises "created in a different Context" and 500s