model-library-client 0.1.35__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 (324) hide show
  1. model_library_client-0.1.35/PKG-INFO +21 -0
  2. model_library_client-0.1.35/model_gateway/__init__.py +0 -0
  3. model_library_client-0.1.35/model_gateway/app.py +335 -0
  4. model_library_client-0.1.35/model_gateway/asgi_observability.py +145 -0
  5. model_library_client-0.1.35/model_gateway/auth.py +113 -0
  6. model_library_client-0.1.35/model_gateway/benchmark_admission_state.py +423 -0
  7. model_library_client-0.1.35/model_gateway/benchmark_admission_types.py +68 -0
  8. model_library_client-0.1.35/model_gateway/cache.py +58 -0
  9. model_library_client-0.1.35/model_gateway/capacity.py +348 -0
  10. model_library_client-0.1.35/model_gateway/errors.py +116 -0
  11. model_library_client-0.1.35/model_gateway/history.py +30 -0
  12. model_library_client-0.1.35/model_gateway/main.py +5 -0
  13. model_library_client-0.1.35/model_gateway/metrics.py +935 -0
  14. model_library_client-0.1.35/model_gateway/model_helpers.py +179 -0
  15. model_library_client-0.1.35/model_gateway/observability.py +172 -0
  16. model_library_client-0.1.35/model_gateway/rate_limit_monitor/__init__.py +6 -0
  17. model_library_client-0.1.35/model_gateway/rate_limit_monitor/manager.py +514 -0
  18. model_library_client-0.1.35/model_gateway/rate_limit_monitor/routes.py +64 -0
  19. model_library_client-0.1.35/model_gateway/rate_limit_monitor/state.py +678 -0
  20. model_library_client-0.1.35/model_gateway/rate_limit_monitor/types.py +173 -0
  21. model_library_client-0.1.35/model_gateway/route_helpers.py +242 -0
  22. model_library_client-0.1.35/model_gateway/routes/__init__.py +1 -0
  23. model_library_client-0.1.35/model_gateway/routes/benchmark_admission.py +182 -0
  24. model_library_client-0.1.35/model_gateway/routes/health.py +46 -0
  25. model_library_client-0.1.35/model_gateway/routes/models.py +98 -0
  26. model_library_client-0.1.35/model_gateway/routes/provider_ops.py +329 -0
  27. model_library_client-0.1.35/model_gateway/routes/query.py +297 -0
  28. model_library_client-0.1.35/model_gateway/routes/rate_limit.py +154 -0
  29. model_library_client-0.1.35/model_gateway/routes/run_tokens.py +83 -0
  30. model_library_client-0.1.35/model_gateway/routes/token_retry.py +55 -0
  31. model_library_client-0.1.35/model_gateway/run_token_types.py +83 -0
  32. model_library_client-0.1.35/model_gateway/run_tokens.py +153 -0
  33. model_library_client-0.1.35/model_gateway/runtime_config.py +570 -0
  34. model_library_client-0.1.35/model_gateway/startup_canary.py +127 -0
  35. model_library_client-0.1.35/model_gateway/telemetry_helpers.py +125 -0
  36. model_library_client-0.1.35/model_gateway/types.py +216 -0
  37. model_library_client-0.1.35/model_gateway/usage_ledger/__init__.py +1 -0
  38. model_library_client-0.1.35/model_gateway/usage_ledger/cost_alerts/__init__.py +1 -0
  39. model_library_client-0.1.35/model_gateway/usage_ledger/cost_alerts/handler.py +535 -0
  40. model_library_client-0.1.35/model_gateway/usage_ledger/cost_alerts/rules.py +863 -0
  41. model_library_client-0.1.35/model_gateway/usage_ledger/cost_alerts/slack.py +444 -0
  42. model_library_client-0.1.35/model_gateway/usage_ledger/details.py +106 -0
  43. model_library_client-0.1.35/model_gateway/usage_ledger/dynamodb_writer.py +224 -0
  44. model_library_client-0.1.35/model_gateway/usage_ledger/lambdas/__init__.py +1 -0
  45. model_library_client-0.1.35/model_gateway/usage_ledger/lambdas/writer.py +123 -0
  46. model_library_client-0.1.35/model_gateway/usage_ledger/message.py +97 -0
  47. model_library_client-0.1.35/model_gateway/usage_ledger/redshift_export.py +176 -0
  48. model_library_client-0.1.35/model_gateway/usage_ledger/redshift_export_job.py +477 -0
  49. model_library_client-0.1.35/model_gateway/usage_ledger/redshift_load.py +369 -0
  50. model_library_client-0.1.35/model_gateway/usage_ledger/redshift_schema.py +667 -0
  51. model_library_client-0.1.35/model_gateway/usage_ledger/redshift_transform.py +160 -0
  52. model_library_client-0.1.35/model_gateway/usage_ledger/schema.py +205 -0
  53. model_library_client-0.1.35/model_gateway/usage_ledger/store.py +1167 -0
  54. model_library_client-0.1.35/model_library/__init__.py +43 -0
  55. model_library_client-0.1.35/model_library/_licenses/openai-python.txt +201 -0
  56. model_library_client-0.1.35/model_library/agent/__init__.py +112 -0
  57. model_library_client-0.1.35/model_library/agent/__main__.py +3 -0
  58. model_library_client-0.1.35/model_library/agent/agent.py +1021 -0
  59. model_library_client-0.1.35/model_library/agent/cli.py +178 -0
  60. model_library_client-0.1.35/model_library/agent/conductor/__init__.py +15 -0
  61. model_library_client-0.1.35/model_library/agent/conductor/conductor.py +303 -0
  62. model_library_client-0.1.35/model_library/agent/conductor/config.py +9 -0
  63. model_library_client-0.1.35/model_library/agent/conductor/metadata.py +54 -0
  64. model_library_client-0.1.35/model_library/agent/config.py +230 -0
  65. model_library_client-0.1.35/model_library/agent/history_compaction.py +362 -0
  66. model_library_client-0.1.35/model_library/agent/hooks.py +196 -0
  67. model_library_client-0.1.35/model_library/agent/metadata.py +196 -0
  68. model_library_client-0.1.35/model_library/agent/tool.py +177 -0
  69. model_library_client-0.1.35/model_library/agent/tools/__init__.py +16 -0
  70. model_library_client-0.1.35/model_library/agent/tools/bash.py +219 -0
  71. model_library_client-0.1.35/model_library/agent/tools/stop.py +22 -0
  72. model_library_client-0.1.35/model_library/agent/tools/submit.py +37 -0
  73. model_library_client-0.1.35/model_library/agent/tools/web_search.py +138 -0
  74. model_library_client-0.1.35/model_library/atif.py +475 -0
  75. model_library_client-0.1.35/model_library/base/__init__.py +9 -0
  76. model_library_client-0.1.35/model_library/base/base.py +1186 -0
  77. model_library_client-0.1.35/model_library/base/delegate_only.py +200 -0
  78. model_library_client-0.1.35/model_library/base/gateway.py +751 -0
  79. model_library_client-0.1.35/model_library/base/input.py +325 -0
  80. model_library_client-0.1.35/model_library/base/json_schema.py +70 -0
  81. model_library_client-0.1.35/model_library/base/output/__init__.py +53 -0
  82. model_library_client-0.1.35/model_library/base/output/builder.py +419 -0
  83. model_library_client-0.1.35/model_library/base/output/moderation.py +80 -0
  84. model_library_client-0.1.35/model_library/base/output/performance.py +466 -0
  85. model_library_client-0.1.35/model_library/base/output/result.py +424 -0
  86. model_library_client-0.1.35/model_library/base/output/transcription.py +28 -0
  87. model_library_client-0.1.35/model_library/base/query_deadline.py +27 -0
  88. model_library_client-0.1.35/model_library/base/query_ids.py +149 -0
  89. model_library_client-0.1.35/model_library/base/query_logging.py +93 -0
  90. model_library_client-0.1.35/model_library/base/serialize.py +19 -0
  91. model_library_client-0.1.35/model_library/base/transcription.py +285 -0
  92. model_library_client-0.1.35/model_library/base/transcription_only.py +124 -0
  93. model_library_client-0.1.35/model_library/base/utils.py +101 -0
  94. model_library_client-0.1.35/model_library/config/README.md +84 -0
  95. model_library_client-0.1.35/model_library/config/ai21labs_models.yaml +22 -0
  96. model_library_client-0.1.35/model_library/config/alibaba_models.yaml +227 -0
  97. model_library_client-0.1.35/model_library/config/all_models.json +21003 -0
  98. model_library_client-0.1.35/model_library/config/amazon_models.yaml +139 -0
  99. model_library_client-0.1.35/model_library/config/ant_models.yaml +14 -0
  100. model_library_client-0.1.35/model_library/config/anthropic_models.yaml +546 -0
  101. model_library_client-0.1.35/model_library/config/arcee_models.yaml +27 -0
  102. model_library_client-0.1.35/model_library/config/azure_models.yaml +0 -0
  103. model_library_client-0.1.35/model_library/config/baseten_models.yaml +84 -0
  104. model_library_client-0.1.35/model_library/config/cohere_models.yaml +93 -0
  105. model_library_client-0.1.35/model_library/config/cursor_models.yaml +41 -0
  106. model_library_client-0.1.35/model_library/config/decision_models.yaml +80 -0
  107. model_library_client-0.1.35/model_library/config/deepseek_models.yaml +207 -0
  108. model_library_client-0.1.35/model_library/config/deprecated/ai21labs_models.yaml +113 -0
  109. model_library_client-0.1.35/model_library/config/deprecated/alibaba_models.yaml +280 -0
  110. model_library_client-0.1.35/model_library/config/deprecated/amazon_models.yaml +156 -0
  111. model_library_client-0.1.35/model_library/config/deprecated/ant_models.yaml +71 -0
  112. model_library_client-0.1.35/model_library/config/deprecated/anthropic_models.yaml +305 -0
  113. model_library_client-0.1.35/model_library/config/deprecated/arcee_models.yaml +57 -0
  114. model_library_client-0.1.35/model_library/config/deprecated/cohere_models.yaml +108 -0
  115. model_library_client-0.1.35/model_library/config/deprecated/fireworks_models.yaml +453 -0
  116. model_library_client-0.1.35/model_library/config/deprecated/google_models.yaml +537 -0
  117. model_library_client-0.1.35/model_library/config/deprecated/kimi_models.yaml +97 -0
  118. model_library_client-0.1.35/model_library/config/deprecated/mistral_models.yaml +337 -0
  119. model_library_client-0.1.35/model_library/config/deprecated/openai_models.yaml +523 -0
  120. model_library_client-0.1.35/model_library/config/deprecated/perplexity_models.yaml +166 -0
  121. model_library_client-0.1.35/model_library/config/deprecated/poolside_models.yaml +81 -0
  122. model_library_client-0.1.35/model_library/config/deprecated/together_models.yaml +927 -0
  123. model_library_client-0.1.35/model_library/config/deprecated/xai_models.yaml +105 -0
  124. model_library_client-0.1.35/model_library/config/deprecated/zai_models.yaml +403 -0
  125. model_library_client-0.1.35/model_library/config/devin_models.yaml +48 -0
  126. model_library_client-0.1.35/model_library/config/digitalocean_models.yaml +0 -0
  127. model_library_client-0.1.35/model_library/config/dummy_model.yaml +39 -0
  128. model_library_client-0.1.35/model_library/config/factory_models.yaml +44 -0
  129. model_library_client-0.1.35/model_library/config/fireworks_models.yaml +345 -0
  130. model_library_client-0.1.35/model_library/config/google_models.yaml +339 -0
  131. model_library_client-0.1.35/model_library/config/icons/AI21 Labs.svg +7 -0
  132. model_library_client-0.1.35/model_library/config/icons/AWS-dark.svg +8 -0
  133. model_library_client-0.1.35/model_library/config/icons/AWS.svg +8 -0
  134. model_library_client-0.1.35/model_library/config/icons/Alibaba-dark.svg +3 -0
  135. model_library_client-0.1.35/model_library/config/icons/Alibaba.svg +3 -0
  136. model_library_client-0.1.35/model_library/config/icons/Amazon-dark.svg +8 -0
  137. model_library_client-0.1.35/model_library/config/icons/Amazon.svg +8 -0
  138. model_library_client-0.1.35/model_library/config/icons/Ant Group-dark.svg +18 -0
  139. model_library_client-0.1.35/model_library/config/icons/Ant Group.svg +10 -0
  140. model_library_client-0.1.35/model_library/config/icons/Anthropic-dark.svg +4 -0
  141. model_library_client-0.1.35/model_library/config/icons/Anthropic.svg +4 -0
  142. model_library_client-0.1.35/model_library/config/icons/Arcee AI-dark.svg +6 -0
  143. model_library_client-0.1.35/model_library/config/icons/Arcee AI.svg +6 -0
  144. model_library_client-0.1.35/model_library/config/icons/AssemblyAI.svg +4 -0
  145. model_library_client-0.1.35/model_library/config/icons/Cartesia.svg +4 -0
  146. model_library_client-0.1.35/model_library/config/icons/Cohere-dark.svg +10 -0
  147. model_library_client-0.1.35/model_library/config/icons/Cohere.svg +12 -0
  148. model_library_client-0.1.35/model_library/config/icons/Cursor-dark.svg +12 -0
  149. model_library_client-0.1.35/model_library/config/icons/Cursor.svg +12 -0
  150. model_library_client-0.1.35/model_library/config/icons/DeepSeek-dark.svg +8 -0
  151. model_library_client-0.1.35/model_library/config/icons/DeepSeek.svg +10 -0
  152. model_library_client-0.1.35/model_library/config/icons/Deepgram.svg +10 -0
  153. model_library_client-0.1.35/model_library/config/icons/Devin-dark.svg +12 -0
  154. model_library_client-0.1.35/model_library/config/icons/Devin.svg +12 -0
  155. model_library_client-0.1.35/model_library/config/icons/ElevenLabs.svg +4 -0
  156. model_library_client-0.1.35/model_library/config/icons/Fireworks.png +0 -0
  157. model_library_client-0.1.35/model_library/config/icons/Google Cloud-dark.svg +11 -0
  158. model_library_client-0.1.35/model_library/config/icons/Google Cloud.svg +11 -0
  159. model_library_client-0.1.35/model_library/config/icons/Google-dark.svg +11 -0
  160. model_library_client-0.1.35/model_library/config/icons/Google.svg +11 -0
  161. model_library_client-0.1.35/model_library/config/icons/Groq.svg +1 -0
  162. model_library_client-0.1.35/model_library/config/icons/Inception-dark.svg +3 -0
  163. model_library_client-0.1.35/model_library/config/icons/Inception.svg +3 -0
  164. model_library_client-0.1.35/model_library/config/icons/Inworld.png +0 -0
  165. model_library_client-0.1.35/model_library/config/icons/Meta-dark.svg +26 -0
  166. model_library_client-0.1.35/model_library/config/icons/Meta.svg +34 -0
  167. model_library_client-0.1.35/model_library/config/icons/Microsoft.svg +1 -0
  168. model_library_client-0.1.35/model_library/config/icons/MiniMax AI-dark.svg +3 -0
  169. model_library_client-0.1.35/model_library/config/icons/MiniMax AI.svg +9 -0
  170. model_library_client-0.1.35/model_library/config/icons/MiniMax-dark.svg +3 -0
  171. model_library_client-0.1.35/model_library/config/icons/MiniMax.svg +9 -0
  172. model_library_client-0.1.35/model_library/config/icons/Mistral-dark.svg +22 -0
  173. model_library_client-0.1.35/model_library/config/icons/Mistral.svg +22 -0
  174. model_library_client-0.1.35/model_library/config/icons/Moonshot AI-dark.svg +3 -0
  175. model_library_client-0.1.35/model_library/config/icons/Moonshot AI.svg +3 -0
  176. model_library_client-0.1.35/model_library/config/icons/NVIDIA-dark.svg +3 -0
  177. model_library_client-0.1.35/model_library/config/icons/NVIDIA.svg +3 -0
  178. model_library_client-0.1.35/model_library/config/icons/OpenAI-dark.svg +10 -0
  179. model_library_client-0.1.35/model_library/config/icons/OpenAI.svg +10 -0
  180. model_library_client-0.1.35/model_library/config/icons/Poolside-dark.svg +1 -0
  181. model_library_client-0.1.35/model_library/config/icons/Poolside.svg +1 -0
  182. model_library_client-0.1.35/model_library/config/icons/Reson8.png +0 -0
  183. model_library_client-0.1.35/model_library/config/icons/Technology Innovation Institute.svg +4 -0
  184. model_library_client-0.1.35/model_library/config/icons/Tencent-dark.svg +1 -0
  185. model_library_client-0.1.35/model_library/config/icons/Tencent.svg +1 -0
  186. model_library_client-0.1.35/model_library/config/icons/Thinking Machines-dark.svg +4 -0
  187. model_library_client-0.1.35/model_library/config/icons/Thinking Machines.svg +4 -0
  188. model_library_client-0.1.35/model_library/config/icons/Xiaomi-dark.svg +10 -0
  189. model_library_client-0.1.35/model_library/config/icons/Xiaomi.svg +20 -0
  190. model_library_client-0.1.35/model_library/config/icons/xAI.png +0 -0
  191. model_library_client-0.1.35/model_library/config/icons/zAI-dark.svg +3 -0
  192. model_library_client-0.1.35/model_library/config/icons/zAI.svg +3 -0
  193. model_library_client-0.1.35/model_library/config/inception_models.yaml +40 -0
  194. model_library_client-0.1.35/model_library/config/kimi_models.yaml +102 -0
  195. model_library_client-0.1.35/model_library/config/meta_models.yaml +137 -0
  196. model_library_client-0.1.35/model_library/config/minimax_models.yaml +111 -0
  197. model_library_client-0.1.35/model_library/config/mistral_models.yaml +110 -0
  198. model_library_client-0.1.35/model_library/config/nvidia_models.yaml +10 -0
  199. model_library_client-0.1.35/model_library/config/openai_models.yaml +1059 -0
  200. model_library_client-0.1.35/model_library/config/openrouter_models.yaml +0 -0
  201. model_library_client-0.1.35/model_library/config/poolside_models.yaml +39 -0
  202. model_library_client-0.1.35/model_library/config/ramp_models.yaml +0 -0
  203. model_library_client-0.1.35/model_library/config/sakana_models.yaml +0 -0
  204. model_library_client-0.1.35/model_library/config/stepfun_models.yaml +31 -0
  205. model_library_client-0.1.35/model_library/config/tencent_models.yaml +29 -0
  206. model_library_client-0.1.35/model_library/config/thinkingmachines_models.yaml +10 -0
  207. model_library_client-0.1.35/model_library/config/thomsonreuters_models.yaml +21 -0
  208. model_library_client-0.1.35/model_library/config/together_models.yaml +133 -0
  209. model_library_client-0.1.35/model_library/config/vercel_models.yaml +29 -0
  210. model_library_client-0.1.35/model_library/config/voice/assemblyai_models.yaml +23 -0
  211. model_library_client-0.1.35/model_library/config/voice/aws_transcribe_models.yaml +23 -0
  212. model_library_client-0.1.35/model_library/config/voice/azure_speech_models.yaml +19 -0
  213. model_library_client-0.1.35/model_library/config/voice/cartesia_models.yaml +18 -0
  214. model_library_client-0.1.35/model_library/config/voice/deepgram_models.yaml +31 -0
  215. model_library_client-0.1.35/model_library/config/voice/elevenlabs_models.yaml +22 -0
  216. model_library_client-0.1.35/model_library/config/voice/google_cloud_models.yaml +23 -0
  217. model_library_client-0.1.35/model_library/config/voice/groq_models.yaml +30 -0
  218. model_library_client-0.1.35/model_library/config/voice/inworld_models.yaml +22 -0
  219. model_library_client-0.1.35/model_library/config/voice/reson8_models.yaml +33 -0
  220. model_library_client-0.1.35/model_library/config/xai_models.yaml +497 -0
  221. model_library_client-0.1.35/model_library/config/xiaomi_models.yaml +99 -0
  222. model_library_client-0.1.35/model_library/config/zai_models.yaml +68 -0
  223. model_library_client-0.1.35/model_library/custom_register_models.py +77 -0
  224. model_library_client-0.1.35/model_library/exceptions.py +469 -0
  225. model_library_client-0.1.35/model_library/failure_capture/__init__.py +1 -0
  226. model_library_client-0.1.35/model_library/failure_capture/aiohttp.py +133 -0
  227. model_library_client-0.1.35/model_library/failure_capture/botocore.py +94 -0
  228. model_library_client-0.1.35/model_library/failure_capture/core.py +451 -0
  229. model_library_client-0.1.35/model_library/failure_capture/httpx.py +83 -0
  230. model_library_client-0.1.35/model_library/file_utils.py +114 -0
  231. model_library_client-0.1.35/model_library/logging.py +50 -0
  232. model_library_client-0.1.35/model_library/model_utils.py +39 -0
  233. model_library_client-0.1.35/model_library/providers/__init__.py +0 -0
  234. model_library_client-0.1.35/model_library/providers/ai21labs.py +268 -0
  235. model_library_client-0.1.35/model_library/providers/amazon/__init__.py +3 -0
  236. model_library_client-0.1.35/model_library/providers/amazon/_credentials.py +17 -0
  237. model_library_client-0.1.35/model_library/providers/amazon/chat.py +515 -0
  238. model_library_client-0.1.35/model_library/providers/amazon/transcribe.py +156 -0
  239. model_library_client-0.1.35/model_library/providers/anthropic.py +995 -0
  240. model_library_client-0.1.35/model_library/providers/azure.py +98 -0
  241. model_library_client-0.1.35/model_library/providers/delegates/__init__.py +0 -0
  242. model_library_client-0.1.35/model_library/providers/delegates/alibaba.py +203 -0
  243. model_library_client-0.1.35/model_library/providers/delegates/ant.py +57 -0
  244. model_library_client-0.1.35/model_library/providers/delegates/arcee.py +35 -0
  245. model_library_client-0.1.35/model_library/providers/delegates/baseten.py +44 -0
  246. model_library_client-0.1.35/model_library/providers/delegates/cohere.py +45 -0
  247. model_library_client-0.1.35/model_library/providers/delegates/deepseek.py +55 -0
  248. model_library_client-0.1.35/model_library/providers/delegates/digitalocean.py +102 -0
  249. model_library_client-0.1.35/model_library/providers/delegates/fireworks.py +147 -0
  250. model_library_client-0.1.35/model_library/providers/delegates/inception.py +37 -0
  251. model_library_client-0.1.35/model_library/providers/delegates/kimi.py +184 -0
  252. model_library_client-0.1.35/model_library/providers/delegates/meta.py +96 -0
  253. model_library_client-0.1.35/model_library/providers/delegates/minimax.py +53 -0
  254. model_library_client-0.1.35/model_library/providers/delegates/nvidia.py +34 -0
  255. model_library_client-0.1.35/model_library/providers/delegates/openrouter/__init__.py +3 -0
  256. model_library_client-0.1.35/model_library/providers/delegates/openrouter/decision.py +341 -0
  257. model_library_client-0.1.35/model_library/providers/delegates/openrouter/openrouter.py +105 -0
  258. model_library_client-0.1.35/model_library/providers/delegates/perplexity.py +35 -0
  259. model_library_client-0.1.35/model_library/providers/delegates/poolside.py +36 -0
  260. model_library_client-0.1.35/model_library/providers/delegates/ramp.py +116 -0
  261. model_library_client-0.1.35/model_library/providers/delegates/sakana.py +31 -0
  262. model_library_client-0.1.35/model_library/providers/delegates/stepfun.py +68 -0
  263. model_library_client-0.1.35/model_library/providers/delegates/thinkingmachines.py +66 -0
  264. model_library_client-0.1.35/model_library/providers/delegates/thomsonreuters.py +86 -0
  265. model_library_client-0.1.35/model_library/providers/delegates/together.py +60 -0
  266. model_library_client-0.1.35/model_library/providers/delegates/vercel.py +37 -0
  267. model_library_client-0.1.35/model_library/providers/delegates/xiaomi.py +37 -0
  268. model_library_client-0.1.35/model_library/providers/delegates/zai.py +91 -0
  269. model_library_client-0.1.35/model_library/providers/google/__init__.py +3 -0
  270. model_library_client-0.1.35/model_library/providers/google/google.py +792 -0
  271. model_library_client-0.1.35/model_library/providers/google/speech.py +143 -0
  272. model_library_client-0.1.35/model_library/providers/google/transcribe.py +173 -0
  273. model_library_client-0.1.35/model_library/providers/mistral/__init__.py +4 -0
  274. model_library_client-0.1.35/model_library/providers/mistral/chat.py +481 -0
  275. model_library_client-0.1.35/model_library/providers/mistral/transcribe.py +143 -0
  276. model_library_client-0.1.35/model_library/providers/openai/__init__.py +11 -0
  277. model_library_client-0.1.35/model_library/providers/openai/_shared.py +136 -0
  278. model_library_client-0.1.35/model_library/providers/openai/completions.py +617 -0
  279. model_library_client-0.1.35/model_library/providers/openai/openai.py +320 -0
  280. model_library_client-0.1.35/model_library/providers/openai/responses.py +679 -0
  281. model_library_client-0.1.35/model_library/providers/openai/voice.py +286 -0
  282. model_library_client-0.1.35/model_library/providers/vals.py +226 -0
  283. model_library_client-0.1.35/model_library/providers/voice/__init__.py +1 -0
  284. model_library_client-0.1.35/model_library/providers/voice/assemblyai.py +204 -0
  285. model_library_client-0.1.35/model_library/providers/voice/azure_speech.py +156 -0
  286. model_library_client-0.1.35/model_library/providers/voice/cartesia.py +116 -0
  287. model_library_client-0.1.35/model_library/providers/voice/deepgram.py +216 -0
  288. model_library_client-0.1.35/model_library/providers/voice/elevenlabs.py +164 -0
  289. model_library_client-0.1.35/model_library/providers/voice/groq.py +58 -0
  290. model_library_client-0.1.35/model_library/providers/voice/inworld.py +163 -0
  291. model_library_client-0.1.35/model_library/providers/voice/meta.py +98 -0
  292. model_library_client-0.1.35/model_library/providers/voice/reson8.py +145 -0
  293. model_library_client-0.1.35/model_library/providers/voice/xai.py +141 -0
  294. model_library_client-0.1.35/model_library/providers/xai.py +576 -0
  295. model_library_client-0.1.35/model_library/py.typed +0 -0
  296. model_library_client-0.1.35/model_library/query_utils.py +85 -0
  297. model_library_client-0.1.35/model_library/rate_limits/__init__.py +21 -0
  298. model_library_client-0.1.35/model_library/rate_limits/model.py +241 -0
  299. model_library_client-0.1.35/model_library/rate_limits/probe.py +28 -0
  300. model_library_client-0.1.35/model_library/register_models.py +966 -0
  301. model_library_client-0.1.35/model_library/registry_utils.py +429 -0
  302. model_library_client-0.1.35/model_library/retriers/__init__.py +0 -0
  303. model_library_client-0.1.35/model_library/retriers/backoff.py +86 -0
  304. model_library_client-0.1.35/model_library/retriers/base.py +343 -0
  305. model_library_client-0.1.35/model_library/retriers/token/__init__.py +38 -0
  306. model_library_client-0.1.35/model_library/retriers/token/background.py +419 -0
  307. model_library_client-0.1.35/model_library/retriers/token/benchmark_admission.py +225 -0
  308. model_library_client-0.1.35/model_library/retriers/token/benchmark_admission_client.py +130 -0
  309. model_library_client-0.1.35/model_library/retriers/token/benchmark_queue.py +799 -0
  310. model_library_client-0.1.35/model_library/retriers/token/token.py +847 -0
  311. model_library_client-0.1.35/model_library/retriers/token/utils.py +978 -0
  312. model_library_client-0.1.35/model_library/retriers/utils.py +11 -0
  313. model_library_client-0.1.35/model_library/settings.py +68 -0
  314. model_library_client-0.1.35/model_library/telemetry.py +1193 -0
  315. model_library_client-0.1.35/model_library/utils.py +407 -0
  316. model_library_client-0.1.35/model_library_client.egg-info/PKG-INFO +21 -0
  317. model_library_client-0.1.35/model_library_client.egg-info/SOURCES.txt +322 -0
  318. model_library_client-0.1.35/model_library_client.egg-info/dependency_links.txt +1 -0
  319. model_library_client-0.1.35/model_library_client.egg-info/requires.txt +11 -0
  320. model_library_client-0.1.35/model_library_client.egg-info/scm_file_list.json +319 -0
  321. model_library_client-0.1.35/model_library_client.egg-info/scm_version.json +8 -0
  322. model_library_client-0.1.35/model_library_client.egg-info/top_level.txt +2 -0
  323. model_library_client-0.1.35/pyproject.toml +48 -0
  324. model_library_client-0.1.35/setup.cfg +4 -0
@@ -0,0 +1,21 @@
1
+ Metadata-Version: 2.4
2
+ Name: model-library-client
3
+ Version: 0.1.35
4
+ Summary: SDK-free Gateway client for model-library
5
+ Author-email: "Vals AI, Inc." <contact@vals.ai>
6
+ License: MIT
7
+ Requires-Python: >=3.11
8
+ Description-Content-Type: text/plain
9
+ Requires-Dist: typing-extensions<5.0,>=4.15.0
10
+ Requires-Dist: pydantic<3.0,>=2.12.5
11
+ Requires-Dist: pyyaml<7.0,>=6.0.3
12
+ Requires-Dist: rich<15.0,>=14.3.3
13
+ Requires-Dist: httpx[http2]<1.0,>=0.28.0
14
+ Requires-Dist: backoff<3.0,>=2.2.1
15
+ Requires-Dist: redis<7.0,>=6.4.0
16
+ Requires-Dist: tiktoken<1.0,>=0.12.0
17
+ Requires-Dist: pillow<13.0,>=12.1.1
18
+ Requires-Dist: python-dotenv<2.0,>=1.0.0
19
+ Requires-Dist: aiohttp<4.0,>=3.13.5
20
+
21
+ Install model-library-client to use the model_library Gateway client without provider SDK dependencies. Install model-library for the full provider distribution.
File without changes
@@ -0,0 +1,335 @@
1
+ """FastAPI app factory for the model gateway."""
2
+
3
+ import asyncio
4
+ import json
5
+ import logging
6
+ import os
7
+ from collections.abc import Awaitable, Callable
8
+ from contextlib import asynccontextmanager, suppress
9
+ from functools import partial
10
+ from typing import Literal, cast
11
+
12
+ import redis.asyncio as async_redis
13
+ from dotenv import load_dotenv
14
+ from redis.exceptions import TimeoutError as RedisTimeoutError
15
+ from fastapi import FastAPI, Request
16
+ from fastapi.exceptions import RequestValidationError
17
+ from fastapi.responses import JSONResponse
18
+ from starlette.responses import Response
19
+
20
+ import model_library.telemetry as telemetry
21
+ from model_library import model_library_settings
22
+ from model_library.registry_utils import get_model_names
23
+ from model_library.retriers.token import set_redis_client
24
+
25
+ from model_gateway.asgi_observability import GatewayObservabilityMiddleware
26
+ from model_gateway.auth import create_auth_middleware
27
+ from model_gateway.cache import ModelCache
28
+ from model_gateway.capacity import GatewayCapacityLimiter, create_capacity_middleware
29
+ from model_gateway.errors import ErrorBody, ErrorResponse, RunTokenAuthorizationError
30
+ from model_gateway.metrics import (
31
+ MetricPublisher,
32
+ create_metrics_middleware,
33
+ install_telemetry_delivery_metric_handler,
34
+ publish_metrics_periodically,
35
+ record_runtime,
36
+ remove_telemetry_delivery_metric_handler,
37
+ )
38
+ from model_gateway.observability import (
39
+ install_loop_exception_handler,
40
+ log_process_lifecycle,
41
+ runtime_snapshot,
42
+ )
43
+ from model_gateway.rate_limit_monitor import RateLimitMonitor
44
+ from model_gateway.rate_limit_monitor.routes import register_rate_limit_monitor_routes
45
+ from model_gateway.rate_limit_monitor.state import MonitorRedis, RateLimitMonitorStore
46
+ from model_gateway.routes.benchmark_admission import register_benchmark_admission_routes
47
+ from model_gateway.routes.health import register_health_routes
48
+ from model_gateway.routes.models import register_model_routes
49
+ from model_gateway.routes.provider_ops import register_provider_ops_routes
50
+ from model_gateway.routes.rate_limit import (
51
+ RateLimitProbeService,
52
+ register_rate_limit_route,
53
+ )
54
+ from model_gateway.routes.query import register_query_routes
55
+ from model_gateway.routes.run_tokens import register_run_token_routes
56
+ from model_gateway.routes.token_retry import register_token_retry_routes
57
+ from model_gateway.runtime_config import (
58
+ RuntimeConfigState,
59
+ runtime_config_manager_from_env,
60
+ )
61
+ from model_gateway.startup_canary import run_startup_canary, startup_canary_state
62
+ from model_gateway.telemetry_helpers import error_telemetry_attributes
63
+ from model_gateway.usage_ledger.store import (
64
+ NoopUsageLedger,
65
+ create_usage_ledger_from_env,
66
+ )
67
+
68
+ logger = logging.getLogger("model_proxy_server")
69
+
70
+ load_dotenv()
71
+
72
+ # The server must never proxy to itself — clear the client-side gateway URL.
73
+ model_library_settings.unset("MODEL_GATEWAY_URL")
74
+
75
+
76
+ GatewayRuntimeRole = Literal["combined", "query", "control"]
77
+
78
+
79
+ def env_flag(name: str, *, default: bool = False) -> bool:
80
+ value = os.environ.get(name)
81
+ if value is None:
82
+ return default
83
+ return value.strip().lower() in {"1", "true", "yes", "on"}
84
+
85
+
86
+ def gateway_runtime_role() -> GatewayRuntimeRole:
87
+ value = os.environ.get("GATEWAY_RUNTIME_ROLE", "combined").strip().lower()
88
+ if value not in {"combined", "query", "control"}:
89
+ raise ValueError("GATEWAY_RUNTIME_ROLE must be combined, query, or control")
90
+ return cast(GatewayRuntimeRole, value)
91
+
92
+
93
+ async def _record_runtime_current(loop: asyncio.AbstractEventLoop) -> None:
94
+ lag_start = loop.time()
95
+ await asyncio.sleep(0)
96
+ event_loop_lag_ms = int((loop.time() - lag_start) * 1000)
97
+ snapshot = runtime_snapshot()
98
+ snapshot["event_loop_lag_ms"] = event_loop_lag_ms
99
+ record_runtime(snapshot)
100
+
101
+
102
+ def create_app() -> FastAPI:
103
+ runtime_role = gateway_runtime_role()
104
+ query_enabled = runtime_role in {"combined", "query"}
105
+ control_enabled = runtime_role in {"combined", "control"}
106
+ api_keys = model_library_settings.get("MODEL_GATEWAY_API_KEYS", None)
107
+ if not isinstance(api_keys, str) or not api_keys:
108
+ raise RuntimeError("MODEL_GATEWAY_API_KEYS must be set")
109
+ api_keys_by_name = cast(dict[str, str], json.loads(api_keys))
110
+ valid_keys = set(api_keys_by_name.values())
111
+
112
+ hmac_secret_value = model_library_settings.get("MODEL_GATEWAY_HMAC_SECRET", None)
113
+ if not isinstance(hmac_secret_value, str) or not hmac_secret_value:
114
+ raise RuntimeError("MODEL_GATEWAY_HMAC_SECRET must be set")
115
+ hmac_secret = hmac_secret_value.encode()
116
+ cache = ModelCache()
117
+ runtime_config_state = RuntimeConfigState.bootstrap()
118
+ runtime_config_manager = runtime_config_manager_from_env(runtime_config_state)
119
+ capacity_limiter = GatewayCapacityLimiter()
120
+ startup_canary_enabled = query_enabled and env_flag(
121
+ "GATEWAY_STARTUP_CANARY_ENABLED"
122
+ )
123
+ usage_ledger = (
124
+ create_usage_ledger_from_env() if query_enabled else NoopUsageLedger()
125
+ )
126
+ rate_limit_probe_service: RateLimitProbeService | None = None
127
+
128
+ @asynccontextmanager
129
+ async def lifespan(app: FastAPI):
130
+ redis_url = os.environ.get("REDIS_URL", "")
131
+ if control_enabled and not redis_url:
132
+ raise RuntimeError("REDIS_URL must be set for a control-enabled runtime")
133
+
134
+ loop = asyncio.get_running_loop()
135
+ previous_exception_handler = install_loop_exception_handler(loop)
136
+ redis_client: async_redis.Redis | None = None
137
+ if redis_url:
138
+ redis_client = async_redis.from_url( # pyright: ignore[reportUnknownMemberType]
139
+ redis_url,
140
+ decode_responses=True,
141
+ socket_timeout=30,
142
+ socket_connect_timeout=10,
143
+ socket_keepalive=True,
144
+ health_check_interval=30,
145
+ retry_on_error=[RedisTimeoutError],
146
+ max_connections=200,
147
+ )
148
+ set_redis_client(redis_client)
149
+
150
+ metrics_stop = asyncio.Event()
151
+ metrics_publishers: list[MetricPublisher] = [
152
+ partial(_record_runtime_current, loop)
153
+ ]
154
+ if query_enabled:
155
+ metrics_publishers.insert(0, capacity_limiter.record_current)
156
+ metrics_task: asyncio.Task[None] | None = None
157
+ usage_ledger_started = False
158
+ canary_task: asyncio.Task[None] | None = None
159
+ rate_limit_monitor: RateLimitMonitor | None = None
160
+ runtime_refresh_stop: asyncio.Event | None = None
161
+ runtime_refresh_task: asyncio.Task[None] | None = None
162
+ delivery_metric_handler = install_telemetry_delivery_metric_handler()
163
+ try:
164
+ if runtime_config_manager is not None:
165
+ initial_refresh = await runtime_config_manager.refresh_once()
166
+ if initial_refresh.failed or initial_refresh.rejected:
167
+ raise RuntimeError("Initial runtime configuration refresh failed")
168
+ runtime_refresh_stop = asyncio.Event()
169
+ runtime_refresh_task = asyncio.create_task(
170
+ runtime_config_manager.run(runtime_refresh_stop)
171
+ )
172
+ with runtime_config_state.capture():
173
+ model_count = len(get_model_names())
174
+ logger.info("Loaded gateway model registry with %s models", model_count)
175
+ log_process_lifecycle("gateway.process.startup")
176
+ telemetry.configure_telemetry(app)
177
+ if control_enabled:
178
+ assert redis_client is not None
179
+ rate_limit_monitor = RateLimitMonitor(
180
+ RateLimitMonitorStore(
181
+ cast(MonitorRedis, redis_client),
182
+ stage=os.environ.get("GATEWAY_STAGE", "local"),
183
+ ),
184
+ runtime_capture=runtime_config_state.capture,
185
+ )
186
+ app.state.rate_limit_monitor = rate_limit_monitor
187
+ rate_limit_monitor.start()
188
+ metrics_task = asyncio.create_task(
189
+ publish_metrics_periodically(
190
+ metrics_stop,
191
+ publishers=metrics_publishers,
192
+ )
193
+ )
194
+ if startup_canary_enabled:
195
+ canary_task = asyncio.create_task(
196
+ run_startup_canary(app, next(iter(api_keys_by_name.values())))
197
+ )
198
+ await usage_ledger.start()
199
+ usage_ledger_started = True
200
+ yield
201
+ finally:
202
+ log_process_lifecycle("gateway.process.shutdown_start")
203
+ if runtime_refresh_stop is not None:
204
+ runtime_refresh_stop.set()
205
+ if runtime_refresh_task is not None:
206
+ await runtime_refresh_task
207
+ if runtime_config_manager is not None:
208
+ await runtime_config_manager.aclose()
209
+ if canary_task is not None and not canary_task.done():
210
+ canary_task.cancel()
211
+ with suppress(asyncio.CancelledError):
212
+ await canary_task
213
+ if rate_limit_probe_service is not None:
214
+ try:
215
+ await rate_limit_probe_service.close()
216
+ except Exception:
217
+ logger.exception(
218
+ "Gateway rate-limit probe service close failed during shutdown"
219
+ )
220
+ if usage_ledger_started:
221
+ try:
222
+ await usage_ledger.close()
223
+ except Exception:
224
+ logger.exception(
225
+ "Gateway usage ledger close failed during shutdown"
226
+ )
227
+ if rate_limit_monitor is not None:
228
+ try:
229
+ await rate_limit_monitor.close()
230
+ except Exception:
231
+ logger.exception(
232
+ "Gateway rate-limit monitor close failed during shutdown"
233
+ )
234
+ finally:
235
+ app.state.rate_limit_monitor = None
236
+ if redis_client is not None:
237
+ try:
238
+ await redis_client.aclose()
239
+ except Exception:
240
+ logger.exception("Gateway Redis close failed during shutdown")
241
+ try:
242
+ telemetry.shutdown_telemetry()
243
+ finally:
244
+ try:
245
+ remove_telemetry_delivery_metric_handler(delivery_metric_handler)
246
+ finally:
247
+ metrics_stop.set()
248
+ try:
249
+ if metrics_task is not None:
250
+ await metrics_task
251
+ finally:
252
+ loop.set_exception_handler(previous_exception_handler)
253
+ log_process_lifecycle("gateway.process.shutdown_done")
254
+
255
+ app = FastAPI(
256
+ title="Model Proxy",
257
+ lifespan=lifespan,
258
+ redirect_slashes=False,
259
+ docs_url=None if runtime_role == "control" else "/docs",
260
+ redoc_url=None if runtime_role == "control" else "/redoc",
261
+ openapi_url=None if runtime_role == "control" else "/openapi.json",
262
+ )
263
+ app.state.cache = cache
264
+ app.state.runtime_config_state = runtime_config_state
265
+ app.state.hmac_secret = hmac_secret
266
+ app.state.runtime_role = runtime_role
267
+ app.state.capacity_limiter = capacity_limiter
268
+ app.state.usage_ledger = usage_ledger
269
+ app.state.rate_limit_monitor = None
270
+ app.state.startup_canary = startup_canary_state(startup_canary_enabled)
271
+
272
+ @app.middleware("http")
273
+ async def capture_runtime_configuration(
274
+ request: Request,
275
+ call_next: Callable[[Request], Awaitable[Response]],
276
+ ) -> Response:
277
+ with runtime_config_state.capture():
278
+ return await call_next(request)
279
+
280
+ if query_enabled:
281
+ app.middleware("http")(create_capacity_middleware())
282
+ app.middleware("http")(create_metrics_middleware())
283
+ app.middleware("http")(create_auth_middleware(api_keys_by_name))
284
+ app.add_middleware(GatewayObservabilityMiddleware)
285
+
286
+ @app.exception_handler(RunTokenAuthorizationError)
287
+ async def run_token_authorization_handler(
288
+ request: Request, exc: RunTokenAuthorizationError
289
+ ) -> JSONResponse:
290
+ claims = request.state.run_token_claims
291
+ telemetry.add_event(
292
+ "gateway.run_token.violation",
293
+ {
294
+ "gateway.run_token.run_id": claims.run_id,
295
+ "gateway.run_token.task_id": claims.task_id,
296
+ "gateway.error.message": str(exc),
297
+ },
298
+ )
299
+ return JSONResponse(
300
+ status_code=403,
301
+ content={"code": "model_not_authorized", "message": str(exc)},
302
+ )
303
+
304
+ @app.exception_handler(RequestValidationError)
305
+ async def validation_exception_handler(
306
+ request: Request, exc: RequestValidationError
307
+ ) -> JSONResponse:
308
+ err = ErrorResponse(
309
+ 400,
310
+ ErrorBody(code="invalid_request", message=str(exc)),
311
+ )
312
+ error_attrs = error_telemetry_attributes(err, phase="request_validation")
313
+ telemetry.set_attributes(error_attrs)
314
+ telemetry.set_status_error(err.body.code)
315
+ telemetry.add_event("gateway.request_validation.error", error_attrs)
316
+ return JSONResponse(status_code=err.status_code, content=err.body.model_dump())
317
+
318
+ register_health_routes(
319
+ app,
320
+ valid_keys=valid_keys,
321
+ hmac_secret=hmac_secret,
322
+ control_enabled=control_enabled,
323
+ )
324
+ if control_enabled:
325
+ register_benchmark_admission_routes(app, cache=cache)
326
+ register_run_token_routes(app)
327
+ register_rate_limit_monitor_routes(app)
328
+ if control_enabled:
329
+ register_model_routes(app)
330
+ if query_enabled:
331
+ register_token_retry_routes(app)
332
+ register_query_routes(app, cache=cache)
333
+ rate_limit_probe_service = register_rate_limit_route(app, cache=cache)
334
+ register_provider_ops_routes(app, cache=cache)
335
+ return app
@@ -0,0 +1,145 @@
1
+ """ASGI-level gateway request/response boundary diagnostics."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import time
7
+ from typing import cast
8
+
9
+ from starlette.types import ASGIApp, Message, Receive, Scope, Send
10
+
11
+ from model_gateway.observability import (
12
+ log_gateway_event,
13
+ request_log_fields_from_scope,
14
+ runtime_snapshot,
15
+ )
16
+
17
+
18
+ class GatewayObservabilityMiddleware:
19
+ """Capture request and ASGI response-boundary events without payload data."""
20
+
21
+ def __init__(self, app: ASGIApp) -> None:
22
+ self.app = app
23
+
24
+ async def __call__(
25
+ self,
26
+ scope: Scope,
27
+ receive: Receive,
28
+ send: Send,
29
+ ) -> None:
30
+ if scope.get("type") != "http":
31
+ await self.app(scope, receive, send)
32
+ return
33
+
34
+ path = cast(str, scope["path"])
35
+ method = cast(str, scope["method"])
36
+ request_fields = request_log_fields_from_scope(scope)
37
+ state = _RequestBoundaryState(start=time.perf_counter())
38
+
39
+ def log_anomaly(
40
+ event: str, *, level: int = logging.INFO, **fields: object
41
+ ) -> None:
42
+ log_gateway_event(
43
+ event,
44
+ level=level,
45
+ route=path,
46
+ method=method,
47
+ runtime=_runtime_snapshot_for_anomaly(state),
48
+ **request_fields,
49
+ **fields,
50
+ )
51
+
52
+ async def wrapped_receive() -> Message:
53
+ message = await receive()
54
+ if message["type"] == "http.disconnect" and not state.client_disconnected:
55
+ state.client_disconnected = True
56
+ log_anomaly(
57
+ "gateway.request.client_disconnect",
58
+ response_started=state.response_started,
59
+ )
60
+ return message
61
+
62
+ async def wrapped_send(message: Message) -> None:
63
+ message_type = message["type"]
64
+ try:
65
+ await send(message)
66
+ except Exception as exc:
67
+ state.send_error = True
68
+ log_anomaly(
69
+ "gateway.response.send_error",
70
+ level=logging.WARNING,
71
+ exception_type=type(exc).__name__,
72
+ response_started=state.response_started,
73
+ first_body_sent=state.first_body_sent,
74
+ status_code=state.status_code,
75
+ )
76
+ raise
77
+ if message_type == "http.response.start":
78
+ state.response_started = True
79
+ state.status_code = cast(int, message["status"])
80
+ elif message_type == "http.response.body":
81
+ if not state.first_body_sent:
82
+ state.first_body_sent = True
83
+ more_body = bool(message.get("more_body"))
84
+ if not more_body:
85
+ state.response_done = True
86
+
87
+ try:
88
+ await self.app(scope, wrapped_receive, wrapped_send)
89
+ except Exception as exc:
90
+ if not state.send_error:
91
+ log_anomaly(
92
+ "gateway.request.error",
93
+ level=logging.WARNING,
94
+ exception_type=type(exc).__name__,
95
+ response_started=state.response_started,
96
+ first_body_sent=state.first_body_sent,
97
+ status_code=state.status_code,
98
+ )
99
+ raise
100
+ finally:
101
+ duration_ms = (time.perf_counter() - state.start) * 1000
102
+ if not state.response_started:
103
+ log_anomaly(
104
+ "gateway.response.missing_start",
105
+ level=logging.WARNING,
106
+ client_disconnected=state.client_disconnected,
107
+ )
108
+ if _is_anomaly(state):
109
+ log_anomaly(
110
+ "gateway.request.done",
111
+ status_code=state.status_code,
112
+ duration_ms=duration_ms,
113
+ response_started=state.response_started,
114
+ first_body_sent=state.first_body_sent,
115
+ response_done=state.response_done,
116
+ send_error=state.send_error,
117
+ client_disconnected=state.client_disconnected,
118
+ )
119
+
120
+
121
+ def _runtime_snapshot_for_anomaly(state: "_RequestBoundaryState") -> dict[str, int]:
122
+ if state.anomaly_runtime is None:
123
+ state.anomaly_runtime = runtime_snapshot()
124
+ return state.anomaly_runtime
125
+
126
+
127
+ def _is_anomaly(state: "_RequestBoundaryState") -> bool:
128
+ return (
129
+ state.send_error
130
+ or state.client_disconnected
131
+ or not state.response_done
132
+ or (state.status_code is not None and not 200 <= state.status_code < 300)
133
+ )
134
+
135
+
136
+ class _RequestBoundaryState:
137
+ def __init__(self, *, start: float):
138
+ self.start = start
139
+ self.response_started = False
140
+ self.first_body_sent = False
141
+ self.response_done = False
142
+ self.send_error = False
143
+ self.client_disconnected = False
144
+ self.status_code: int | None = None
145
+ self.anomaly_runtime: dict[str, int] | None = None
@@ -0,0 +1,113 @@
1
+ """Bearer token auth middleware."""
2
+
3
+ import hashlib
4
+ import hmac
5
+ from collections.abc import Mapping
6
+
7
+ from fastapi import Request
8
+ from fastapi.responses import JSONResponse
9
+
10
+ import model_library.telemetry as telemetry
11
+ from starlette.middleware.base import RequestResponseEndpoint
12
+ from starlette.responses import Response
13
+
14
+ from model_gateway.run_token_types import RUN_TOKEN_OPERATION_PATHS
15
+ from model_gateway.run_tokens import RUN_TOKEN_PREFIX, get_run_token, sha256_hex
16
+
17
+ EXEMPT_PATHS = {"/health/live", "/health/ready"}
18
+ TRACE_AUTH_FAILURE_PATHS = telemetry.HTTP_TRACE_ALLOWED_ROUTES
19
+
20
+ RUN_TOKEN_ALLOWED_PATHS = frozenset({*RUN_TOKEN_OPERATION_PATHS.values(), "/registry"})
21
+
22
+
23
+ def create_auth_middleware(api_keys_by_name: Mapping[str, str]):
24
+ # Pre-hash keys for constant-time comparison (prevents timing side-channel)
25
+ hashed_keys = tuple(
26
+ (name, hashlib.sha256(api_key.encode()).digest())
27
+ for name, api_key in api_keys_by_name.items()
28
+ )
29
+ accepted_digests = {digest.hex() for _, digest in hashed_keys}
30
+
31
+ async def auth_middleware(
32
+ request: Request, call_next: RequestResponseEndpoint
33
+ ) -> Response:
34
+ if request.url.path in EXEMPT_PATHS:
35
+ return await call_next(request)
36
+
37
+ auth_header = request.headers.get("Authorization", "")
38
+ if not auth_header.startswith("Bearer "):
39
+ return _unauthorized_response(
40
+ request,
41
+ "Missing or malformed Authorization header",
42
+ )
43
+
44
+ presented = auth_header[7:]
45
+ request.state.run_token_claims = None
46
+
47
+ if presented.startswith(RUN_TOKEN_PREFIX):
48
+ # Before the lookup, so a denied path cannot leak token validity.
49
+ if request.url.path not in RUN_TOKEN_ALLOWED_PATHS:
50
+ return _auth_error_response(
51
+ request, 403, "forbidden", "Run tokens cannot call this route"
52
+ )
53
+ claims = await get_run_token(sha256_hex(presented))
54
+ # Dev and prod share Redis but not keys.
55
+ if claims is None or claims.minted_with not in accepted_digests:
56
+ return _unauthorized_response(request, "Invalid or expired run token")
57
+ if claims.allowed_operations is not None and request.url.path not in {
58
+ "/registry",
59
+ *(RUN_TOKEN_OPERATION_PATHS[op] for op in claims.allowed_operations),
60
+ }:
61
+ return _auth_error_response(
62
+ request, 403, "forbidden", "Run token does not allow this operation"
63
+ )
64
+ request.state.gateway_api_key_name = f"run-token:{claims.minted_by}"
65
+ request.state.gateway_api_key_digest = claims.minted_with
66
+ request.state.run_token_claims = claims
67
+ return await call_next(request)
68
+
69
+ token_hash = hashlib.sha256(presented.encode()).digest()
70
+ matched_name: str | None = None
71
+ for name, expected_hash in hashed_keys:
72
+ if hmac.compare_digest(token_hash, expected_hash):
73
+ matched_name = name
74
+ if matched_name is None:
75
+ return _unauthorized_response(request, "Invalid API key")
76
+
77
+ request.state.gateway_api_key_name = matched_name
78
+ request.state.gateway_api_key_digest = token_hash.hex()
79
+ return await call_next(request)
80
+
81
+ return auth_middleware
82
+
83
+
84
+ def _unauthorized_response(request: Request, message: str) -> JSONResponse:
85
+ return _auth_error_response(request, 401, "unauthorized", message)
86
+
87
+
88
+ def _auth_error_response(
89
+ request: Request, status_code: int, code: str, message: str
90
+ ) -> JSONResponse:
91
+ attrs = {
92
+ "gateway.route": request.url.path,
93
+ "gateway.operation": "access_check",
94
+ "gateway.error.code": "access_denied",
95
+ "gateway.error.phase": "access_control",
96
+ "gateway.status_code": status_code,
97
+ "http.request.method": request.method,
98
+ "http.status_code": status_code,
99
+ "http.response.status_code": status_code,
100
+ }
101
+ if request.url.path in TRACE_AUTH_FAILURE_PATHS:
102
+ with telemetry.start_span(
103
+ telemetry.http_server_span_name(request.method, request.url.path),
104
+ attrs,
105
+ kind="server",
106
+ ):
107
+ telemetry.set_attributes(attrs)
108
+ telemetry.set_status_error("access_denied")
109
+ telemetry.add_event("gateway.auth.error", attrs)
110
+ return JSONResponse(
111
+ status_code=status_code,
112
+ content={"code": code, "message": message},
113
+ )