blaxel 0.64.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. blaxel/__init__.py +8 -0
  2. blaxel/agents/__init__.py +5 -0
  3. blaxel/agents/chain.py +153 -0
  4. blaxel/agents/chat.py +286 -0
  5. blaxel/agents/decorator.py +208 -0
  6. blaxel/agents/thread.py +24 -0
  7. blaxel/agents/voice/openai.py +255 -0
  8. blaxel/agents/voice/utils.py +25 -0
  9. blaxel/api/__init__.py +1 -0
  10. blaxel/api/agents/__init__.py +0 -0
  11. blaxel/api/agents/create_agent.py +155 -0
  12. blaxel/api/agents/delete_agent.py +146 -0
  13. blaxel/api/agents/get_agent.py +146 -0
  14. blaxel/api/agents/get_agent_logs.py +151 -0
  15. blaxel/api/agents/get_agent_metrics.py +150 -0
  16. blaxel/api/agents/get_agent_trace_ids.py +201 -0
  17. blaxel/api/agents/list_agent_revisions.py +155 -0
  18. blaxel/api/agents/list_agents.py +127 -0
  19. blaxel/api/agents/update_agent.py +168 -0
  20. blaxel/api/configurations/__init__.py +0 -0
  21. blaxel/api/configurations/get_configuration.py +122 -0
  22. blaxel/api/default/__init__.py +0 -0
  23. blaxel/api/default/get_trace.py +150 -0
  24. blaxel/api/default/get_trace_ids.py +218 -0
  25. blaxel/api/default/get_trace_logs.py +186 -0
  26. blaxel/api/default/list_mcp_hub_definitions.py +127 -0
  27. blaxel/api/functions/__init__.py +0 -0
  28. blaxel/api/functions/create_function.py +155 -0
  29. blaxel/api/functions/delete_function.py +146 -0
  30. blaxel/api/functions/get_function.py +146 -0
  31. blaxel/api/functions/get_function_logs.py +151 -0
  32. blaxel/api/functions/get_function_metrics.py +150 -0
  33. blaxel/api/functions/get_function_trace_ids.py +201 -0
  34. blaxel/api/functions/list_function_revisions.py +158 -0
  35. blaxel/api/functions/list_functions.py +131 -0
  36. blaxel/api/functions/update_function.py +168 -0
  37. blaxel/api/integrations/__init__.py +0 -0
  38. blaxel/api/integrations/create_integration_connection.py +167 -0
  39. blaxel/api/integrations/delete_integration_connection.py +158 -0
  40. blaxel/api/integrations/get_integration.py +97 -0
  41. blaxel/api/integrations/get_integration_connection.py +158 -0
  42. blaxel/api/integrations/get_integration_connection_model.py +104 -0
  43. blaxel/api/integrations/get_integration_connection_model_endpoint_configurations.py +97 -0
  44. blaxel/api/integrations/list_integration_connection_models.py +97 -0
  45. blaxel/api/integrations/list_integration_connections.py +139 -0
  46. blaxel/api/integrations/update_integration_connection.py +180 -0
  47. blaxel/api/invitations/__init__.py +0 -0
  48. blaxel/api/invitations/list_all_pending_invitations.py +142 -0
  49. blaxel/api/knowledgebases/__init__.py +0 -0
  50. blaxel/api/knowledgebases/create_knowledgebase.py +163 -0
  51. blaxel/api/knowledgebases/delete_knowledgebase.py +154 -0
  52. blaxel/api/knowledgebases/get_knowledgebase.py +154 -0
  53. blaxel/api/knowledgebases/list_knowledgebase_revisions.py +158 -0
  54. blaxel/api/knowledgebases/list_knowledgebases.py +139 -0
  55. blaxel/api/knowledgebases/update_knowledgebase.py +176 -0
  56. blaxel/api/locations/__init__.py +0 -0
  57. blaxel/api/locations/list_locations.py +139 -0
  58. blaxel/api/metrics/__init__.py +0 -0
  59. blaxel/api/metrics/get_metrics.py +130 -0
  60. blaxel/api/models/__init__.py +0 -0
  61. blaxel/api/models/create_model.py +163 -0
  62. blaxel/api/models/delete_model.py +154 -0
  63. blaxel/api/models/get_model.py +154 -0
  64. blaxel/api/models/get_model_logs.py +155 -0
  65. blaxel/api/models/get_model_metrics.py +158 -0
  66. blaxel/api/models/get_model_trace_ids.py +201 -0
  67. blaxel/api/models/list_model_revisions.py +158 -0
  68. blaxel/api/models/list_models.py +135 -0
  69. blaxel/api/models/update_model.py +176 -0
  70. blaxel/api/policies/__init__.py +0 -0
  71. blaxel/api/policies/create_policy.py +167 -0
  72. blaxel/api/policies/delete_policy.py +154 -0
  73. blaxel/api/policies/get_policy.py +154 -0
  74. blaxel/api/policies/list_policies.py +139 -0
  75. blaxel/api/policies/update_policy.py +180 -0
  76. blaxel/api/privateclusters/__init__.py +0 -0
  77. blaxel/api/privateclusters/create_private_cluster.py +132 -0
  78. blaxel/api/privateclusters/delete_private_cluster.py +156 -0
  79. blaxel/api/privateclusters/get_private_cluster.py +159 -0
  80. blaxel/api/privateclusters/get_private_cluster_health.py +97 -0
  81. blaxel/api/privateclusters/list_private_clusters.py +140 -0
  82. blaxel/api/privateclusters/update_private_cluster.py +156 -0
  83. blaxel/api/privateclusters/update_private_cluster_health.py +97 -0
  84. blaxel/api/service_accounts/__init__.py +0 -0
  85. blaxel/api/service_accounts/create_api_key_for_service_account.py +177 -0
  86. blaxel/api/service_accounts/create_workspace_service_account.py +170 -0
  87. blaxel/api/service_accounts/delete_api_key_for_service_account.py +104 -0
  88. blaxel/api/service_accounts/delete_workspace_service_account.py +160 -0
  89. blaxel/api/service_accounts/get_workspace_service_accounts.py +141 -0
  90. blaxel/api/service_accounts/list_api_keys_for_service_account.py +163 -0
  91. blaxel/api/service_accounts/update_workspace_service_account.py +183 -0
  92. blaxel/api/store/__init__.py +0 -0
  93. blaxel/api/store/get_store_agent.py +146 -0
  94. blaxel/api/store/get_store_function.py +146 -0
  95. blaxel/api/store/list_store_agents.py +131 -0
  96. blaxel/api/store/list_store_functions.py +131 -0
  97. blaxel/api/workspaces/__init__.py +0 -0
  98. blaxel/api/workspaces/accept_workspace_invitation.py +161 -0
  99. blaxel/api/workspaces/create_worspace.py +163 -0
  100. blaxel/api/workspaces/decline_workspace_invitation.py +158 -0
  101. blaxel/api/workspaces/delete_workspace.py +154 -0
  102. blaxel/api/workspaces/get_workspace.py +154 -0
  103. blaxel/api/workspaces/invite_workspace_user.py +174 -0
  104. blaxel/api/workspaces/leave_workspace.py +161 -0
  105. blaxel/api/workspaces/list_workspace_users.py +139 -0
  106. blaxel/api/workspaces/list_workspaces.py +139 -0
  107. blaxel/api/workspaces/remove_workspace_user.py +101 -0
  108. blaxel/api/workspaces/update_workspace.py +176 -0
  109. blaxel/api/workspaces/update_workspace_user_role.py +187 -0
  110. blaxel/authentication/__init__.py +45 -0
  111. blaxel/authentication/apikey.py +50 -0
  112. blaxel/authentication/authentication.py +176 -0
  113. blaxel/authentication/clientcredentials.py +103 -0
  114. blaxel/authentication/credentials.py +295 -0
  115. blaxel/authentication/device_mode.py +197 -0
  116. blaxel/client.py +281 -0
  117. blaxel/common/__init__.py +17 -0
  118. blaxel/common/error.py +27 -0
  119. blaxel/common/instrumentation.py +317 -0
  120. blaxel/common/logger.py +60 -0
  121. blaxel/common/secrets.py +39 -0
  122. blaxel/common/settings.py +150 -0
  123. blaxel/common/slugify.py +18 -0
  124. blaxel/common/utils.py +34 -0
  125. blaxel/deploy/__init__.py +8 -0
  126. blaxel/deploy/deploy.py +316 -0
  127. blaxel/deploy/format.py +46 -0
  128. blaxel/deploy/parser.py +192 -0
  129. blaxel/errors.py +16 -0
  130. blaxel/functions/__init__.py +7 -0
  131. blaxel/functions/common.py +228 -0
  132. blaxel/functions/decorator.py +64 -0
  133. blaxel/functions/local/local.py +48 -0
  134. blaxel/functions/mcp/client.py +96 -0
  135. blaxel/functions/mcp/mcp.py +168 -0
  136. blaxel/functions/mcp/utils.py +56 -0
  137. blaxel/functions/remote/remote.py +183 -0
  138. blaxel/models/__init__.py +233 -0
  139. blaxel/models/acl.py +133 -0
  140. blaxel/models/agent.py +126 -0
  141. blaxel/models/agent_chain.py +88 -0
  142. blaxel/models/agent_spec.py +346 -0
  143. blaxel/models/api_key.py +142 -0
  144. blaxel/models/configuration.py +85 -0
  145. blaxel/models/continent.py +70 -0
  146. blaxel/models/core_event.py +97 -0
  147. blaxel/models/core_spec.py +249 -0
  148. blaxel/models/core_spec_configurations.py +77 -0
  149. blaxel/models/country.py +70 -0
  150. blaxel/models/create_api_key_for_service_account_body.py +69 -0
  151. blaxel/models/create_workspace_service_account_body.py +71 -0
  152. blaxel/models/create_workspace_service_account_response_200.py +105 -0
  153. blaxel/models/delete_workspace_service_account_response_200.py +96 -0
  154. blaxel/models/entrypoint.py +96 -0
  155. blaxel/models/entrypoint_env.py +45 -0
  156. blaxel/models/flavor.py +70 -0
  157. blaxel/models/form.py +120 -0
  158. blaxel/models/form_config.py +45 -0
  159. blaxel/models/form_oauthomitempty.py +45 -0
  160. blaxel/models/form_secrets.py +45 -0
  161. blaxel/models/function.py +126 -0
  162. blaxel/models/function_kit.py +97 -0
  163. blaxel/models/function_spec.py +310 -0
  164. blaxel/models/get_trace_ids_response_200.py +45 -0
  165. blaxel/models/get_trace_logs_response_200.py +45 -0
  166. blaxel/models/get_trace_response_200.py +45 -0
  167. blaxel/models/get_workspace_service_accounts_response_200_item.py +96 -0
  168. blaxel/models/histogram_bucket.py +79 -0
  169. blaxel/models/histogram_stats.py +88 -0
  170. blaxel/models/integration_connection.py +96 -0
  171. blaxel/models/integration_connection_spec.py +114 -0
  172. blaxel/models/integration_connection_spec_config.py +45 -0
  173. blaxel/models/integration_connection_spec_secret.py +45 -0
  174. blaxel/models/integration_model.py +162 -0
  175. blaxel/models/integration_repository.py +88 -0
  176. blaxel/models/invite_workspace_user_body.py +60 -0
  177. blaxel/models/knowledgebase.py +126 -0
  178. blaxel/models/knowledgebase_spec.py +163 -0
  179. blaxel/models/knowledgebase_spec_options.py +45 -0
  180. blaxel/models/last_n_requests_metric.py +79 -0
  181. blaxel/models/latency_metric.py +144 -0
  182. blaxel/models/location_response.py +113 -0
  183. blaxel/models/mcp_definition.py +188 -0
  184. blaxel/models/mcp_definition_entrypoint.py +45 -0
  185. blaxel/models/mcp_definition_form.py +45 -0
  186. blaxel/models/metadata.py +139 -0
  187. blaxel/models/metadata_labels.py +45 -0
  188. blaxel/models/metric.py +79 -0
  189. blaxel/models/metrics.py +169 -0
  190. blaxel/models/metrics_models.py +45 -0
  191. blaxel/models/metrics_request_total_per_code.py +45 -0
  192. blaxel/models/metrics_rps_per_code.py +45 -0
  193. blaxel/models/model.py +126 -0
  194. blaxel/models/model_private_cluster.py +79 -0
  195. blaxel/models/model_spec.py +249 -0
  196. blaxel/models/o_auth.py +72 -0
  197. blaxel/models/owner_fields.py +70 -0
  198. blaxel/models/pending_invitation.py +124 -0
  199. blaxel/models/pending_invitation_accept.py +85 -0
  200. blaxel/models/pending_invitation_render.py +147 -0
  201. blaxel/models/pending_invitation_render_invited_by.py +88 -0
  202. blaxel/models/pending_invitation_render_workspace.py +70 -0
  203. blaxel/models/pending_invitation_workspace_details.py +72 -0
  204. blaxel/models/pod_template_spec.py +45 -0
  205. blaxel/models/policy.py +96 -0
  206. blaxel/models/policy_location.py +70 -0
  207. blaxel/models/policy_max_tokens.py +106 -0
  208. blaxel/models/policy_spec.py +151 -0
  209. blaxel/models/private_cluster.py +183 -0
  210. blaxel/models/private_location.py +61 -0
  211. blaxel/models/repository.py +70 -0
  212. blaxel/models/request_duration_over_time_metric.py +97 -0
  213. blaxel/models/request_duration_over_time_metrics.py +80 -0
  214. blaxel/models/request_total_by_origin_metric.py +115 -0
  215. blaxel/models/request_total_by_origin_metric_request_total_by_origin.py +45 -0
  216. blaxel/models/request_total_by_origin_metric_request_total_by_origin_and_code.py +45 -0
  217. blaxel/models/request_total_metric.py +123 -0
  218. blaxel/models/request_total_metric_request_total_per_code.py +45 -0
  219. blaxel/models/request_total_metric_rps_per_code.py +45 -0
  220. blaxel/models/resource_log.py +79 -0
  221. blaxel/models/resource_metrics.py +270 -0
  222. blaxel/models/resource_metrics_request_total_per_code.py +45 -0
  223. blaxel/models/resource_metrics_rps_per_code.py +45 -0
  224. blaxel/models/revision_configuration.py +97 -0
  225. blaxel/models/revision_metadata.py +124 -0
  226. blaxel/models/runtime.py +196 -0
  227. blaxel/models/runtime_startup_probe.py +45 -0
  228. blaxel/models/serverless_config.py +80 -0
  229. blaxel/models/spec_configuration.py +70 -0
  230. blaxel/models/store_agent.py +178 -0
  231. blaxel/models/store_agent_labels.py +45 -0
  232. blaxel/models/store_configuration.py +151 -0
  233. blaxel/models/store_configuration_option.py +79 -0
  234. blaxel/models/store_function.py +211 -0
  235. blaxel/models/store_function_kit.py +97 -0
  236. blaxel/models/store_function_labels.py +45 -0
  237. blaxel/models/store_function_parameter.py +88 -0
  238. blaxel/models/time_fields.py +70 -0
  239. blaxel/models/token_rate_metric.py +88 -0
  240. blaxel/models/token_rate_metrics.py +120 -0
  241. blaxel/models/token_total_metric.py +106 -0
  242. blaxel/models/trace_ids_response.py +45 -0
  243. blaxel/models/update_workspace_service_account_body.py +69 -0
  244. blaxel/models/update_workspace_service_account_response_200.py +96 -0
  245. blaxel/models/update_workspace_user_role_body.py +60 -0
  246. blaxel/models/websocket_channel.py +88 -0
  247. blaxel/models/workspace.py +148 -0
  248. blaxel/models/workspace_labels.py +45 -0
  249. blaxel/models/workspace_user.py +115 -0
  250. blaxel/py.typed +1 -0
  251. blaxel/run.py +108 -0
  252. blaxel/serve/app.py +131 -0
  253. blaxel/serve/middlewares/__init__.py +10 -0
  254. blaxel/serve/middlewares/accesslog.py +32 -0
  255. blaxel/serve/middlewares/processtime.py +28 -0
  256. blaxel/types.py +46 -0
  257. blaxel-0.64.0.dist-info/METADATA +96 -0
  258. blaxel-0.64.0.dist-info/RECORD +261 -0
  259. blaxel-0.64.0.dist-info/WHEEL +4 -0
  260. blaxel-0.64.0.dist-info/entry_points.txt +2 -0
  261. blaxel-0.64.0.dist-info/licenses/LICENSE +21 -0
blaxel/client.py ADDED
@@ -0,0 +1,281 @@
1
+ import ssl
2
+ from typing import Any, Optional, Union
3
+
4
+ import httpx
5
+ from attrs import define, evolve, field
6
+
7
+
8
+ @define
9
+ class Client:
10
+ """A class for keeping track of data related to the API
11
+
12
+ The following are accepted as keyword arguments and will be used to construct httpx Clients internally:
13
+
14
+ ``base_url``: The base URL for the API, all requests are made to a relative path to this URL
15
+
16
+ ``cookies``: A dictionary of cookies to be sent with every request
17
+
18
+ ``headers``: A dictionary of headers to be sent with every request
19
+
20
+ ``provider``: An implementation of httpx.Auth to use for authentication
21
+
22
+ ``timeout``: The maximum amount of a time a request can take. API functions will raise
23
+ httpx.TimeoutException if this is exceeded.
24
+
25
+ ``verify_ssl``: Whether or not to verify the SSL certificate of the API server. This should be True in production,
26
+ but can be set to False for testing purposes.
27
+
28
+ ``follow_redirects``: Whether or not to follow redirects. Default value is False.
29
+
30
+ ``httpx_args``: A dictionary of additional arguments to be passed to the ``httpx.Client`` and ``httpx.AsyncClient`` constructor.
31
+
32
+
33
+ Attributes:
34
+ raise_on_unexpected_status: Whether or not to raise an errors.UnexpectedStatus if the API returns a
35
+ status code that was not documented in the source OpenAPI document. Can also be provided as a keyword
36
+ argument to the constructor.
37
+
38
+ """
39
+
40
+ raise_on_unexpected_status: bool = field(default=True, kw_only=True)
41
+ _base_url: str = field(alias="base_url", default="")
42
+ _cookies: dict[str, str] = field(factory=dict, kw_only=True, alias="cookies")
43
+ _headers: dict[str, str] = field(factory=dict, kw_only=True, alias="headers")
44
+ _provider: httpx.Auth = field(default=None, alias="provider")
45
+ _timeout: Optional[httpx.Timeout] = field(default=None, kw_only=True, alias="timeout")
46
+ _verify_ssl: Union[str, bool, ssl.SSLContext] = field(default=True, kw_only=True, alias="verify_ssl")
47
+ _follow_redirects: bool = field(default=False, kw_only=True, alias="follow_redirects")
48
+ _httpx_args: dict[str, Any] = field(factory=dict, kw_only=True, alias="httpx_args")
49
+ _client: Optional[httpx.Client] = field(default=None, init=False)
50
+ _async_client: Optional[httpx.AsyncClient] = field(default=None, init=False)
51
+
52
+ def __post_init__(self):
53
+ from .common.settings import get_settings
54
+
55
+ settings = get_settings()
56
+ self._base_url = settings.base_url
57
+
58
+ def with_headers(self, headers: dict[str, str]) -> "Client":
59
+ """Get a new client matching this one with additional headers"""
60
+ if self._client is not None:
61
+ self._client.headers.update(headers)
62
+ if self._async_client is not None:
63
+ self._async_client.headers.update(headers)
64
+ return evolve(self, headers={**self._headers, **headers})
65
+
66
+ def with_cookies(self, cookies: dict[str, str]) -> "Client":
67
+ """Get a new client matching this one with additional cookies"""
68
+ if self._client is not None:
69
+ self._client.cookies.update(cookies)
70
+ if self._async_client is not None:
71
+ self._async_client.cookies.update(cookies)
72
+ return evolve(self, cookies={**self._cookies, **cookies})
73
+
74
+ def with_timeout(self, timeout: httpx.Timeout) -> "Client":
75
+ """Get a new client matching this one with a new timeout (in seconds)"""
76
+ if self._client is not None:
77
+ self._client.timeout = timeout
78
+ if self._async_client is not None:
79
+ self._async_client.timeout = timeout
80
+ return evolve(self, timeout=timeout)
81
+
82
+ def set_httpx_client(self, client: httpx.Client) -> "Client":
83
+ """Manually set the underlying httpx.Client
84
+
85
+ **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
86
+ """
87
+ self._client = client
88
+ return self
89
+
90
+ def get_httpx_client(self) -> httpx.Client:
91
+ """Get the underlying httpx.Client, constructing a new one if not previously set"""
92
+ if self._client is None:
93
+ self._client = httpx.Client(
94
+ base_url=self._base_url,
95
+ cookies=self._cookies,
96
+ headers=self._headers,
97
+ timeout=self._timeout,
98
+ verify=self._verify_ssl,
99
+ follow_redirects=self._follow_redirects,
100
+ auth=self._provider,
101
+ **self._httpx_args,
102
+ )
103
+ return self._client
104
+
105
+ def __enter__(self) -> "Client":
106
+ """Enter a context manager for self.client—you cannot enter twice (see httpx docs)"""
107
+ self.get_httpx_client().__enter__()
108
+ return self
109
+
110
+ def __exit__(self, *args: Any, **kwargs: Any) -> None:
111
+ """Exit a context manager for internal httpx.Client (see httpx docs)"""
112
+ self.get_httpx_client().__exit__(*args, **kwargs)
113
+
114
+ def set_async_httpx_client(self, async_client: httpx.AsyncClient) -> "Client":
115
+ """Manually the underlying httpx.AsyncClient
116
+
117
+ **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
118
+ """
119
+ self._async_client = async_client
120
+ return self
121
+
122
+ def get_async_httpx_client(self) -> httpx.AsyncClient:
123
+ """Get the underlying httpx.AsyncClient, constructing a new one if not previously set"""
124
+ if self._async_client is None:
125
+ self._async_client = httpx.AsyncClient(
126
+ base_url=self._base_url,
127
+ cookies=self._cookies,
128
+ headers=self._headers,
129
+ timeout=self._timeout,
130
+ verify=self._verify_ssl,
131
+ follow_redirects=self._follow_redirects,
132
+ **self._httpx_args,
133
+ )
134
+ return self._async_client
135
+
136
+ async def __aenter__(self) -> "Client":
137
+ """Enter a context manager for underlying httpx.AsyncClient—you cannot enter twice (see httpx docs)"""
138
+ await self.get_async_httpx_client().__aenter__()
139
+ return self
140
+
141
+ async def __aexit__(self, *args: Any, **kwargs: Any) -> None:
142
+ """Exit a context manager for underlying httpx.AsyncClient (see httpx docs)"""
143
+ await self.get_async_httpx_client().__aexit__(*args, **kwargs)
144
+
145
+
146
+ @define
147
+ class AuthenticatedClient:
148
+ """A Client which has been authenticated for use on secured endpoints
149
+
150
+ The following are accepted as keyword arguments and will be used to construct httpx Clients internally:
151
+
152
+ ``base_url``: The base URL for the API, all requests are made to a relative path to this URL
153
+
154
+ ``cookies``: A dictionary of cookies to be sent with every request
155
+
156
+ ``headers``: A dictionary of headers to be sent with every request
157
+
158
+ ``provider``: An implementation of httpx.Auth to use for authentication
159
+
160
+ ``timeout``: The maximum amount of a time a request can take. API functions will raise
161
+ httpx.TimeoutException if this is exceeded.
162
+
163
+ ``verify_ssl``: Whether or not to verify the SSL certificate of the API server. This should be True in production,
164
+ but can be set to False for testing purposes.
165
+
166
+ ``follow_redirects``: Whether or not to follow redirects. Default value is False.
167
+
168
+ ``httpx_args``: A dictionary of additional arguments to be passed to the ``httpx.Client`` and ``httpx.AsyncClient`` constructor.
169
+
170
+
171
+ Attributes:
172
+ raise_on_unexpected_status: Whether or not to raise an errors.UnexpectedStatus if the API returns a
173
+ status code that was not documented in the source OpenAPI document. Can also be provided as a keyword
174
+ argument to the constructor.
175
+ provider: AuthProvider to use for authentication
176
+ """
177
+
178
+ raise_on_unexpected_status: bool = field(default=True, kw_only=True)
179
+ _base_url: str = field(alias="base_url", default="")
180
+ _cookies: dict[str, str] = field(factory=dict, kw_only=True, alias="cookies")
181
+ _headers: dict[str, str] = field(factory=dict, kw_only=True, alias="headers")
182
+ _provider: httpx.Auth = field(default=None, alias="provider")
183
+ _timeout: Optional[httpx.Timeout] = field(default=None, kw_only=True, alias="timeout")
184
+ _verify_ssl: Union[str, bool, ssl.SSLContext] = field(default=True, kw_only=True, alias="verify_ssl")
185
+ _follow_redirects: bool = field(default=False, kw_only=True, alias="follow_redirects")
186
+ _httpx_args: dict[str, Any] = field(factory=dict, kw_only=True, alias="httpx_args")
187
+ _client: Optional[httpx.Client] = field(default=None, init=False)
188
+ _async_client: Optional[httpx.AsyncClient] = field(default=None, init=False)
189
+
190
+ def __post_init__(self):
191
+ from .common.settings import get_settings
192
+
193
+ settings = get_settings()
194
+ self._base_url = settings.base_url
195
+
196
+ def with_headers(self, headers: dict[str, str]) -> "AuthenticatedClient":
197
+ """Get a new client matching this one with additional headers"""
198
+ if self._client is not None:
199
+ self._client.headers.update(headers)
200
+ if self._async_client is not None:
201
+ self._async_client.headers.update(headers)
202
+ return evolve(self, headers={**self._headers, **headers})
203
+
204
+ def with_cookies(self, cookies: dict[str, str]) -> "AuthenticatedClient":
205
+ """Get a new client matching this one with additional cookies"""
206
+ if self._client is not None:
207
+ self._client.cookies.update(cookies)
208
+ if self._async_client is not None:
209
+ self._async_client.cookies.update(cookies)
210
+ return evolve(self, cookies={**self._cookies, **cookies})
211
+
212
+ def with_timeout(self, timeout: httpx.Timeout) -> "AuthenticatedClient":
213
+ """Get a new client matching this one with a new timeout (in seconds)"""
214
+ if self._client is not None:
215
+ self._client.timeout = timeout
216
+ if self._async_client is not None:
217
+ self._async_client.timeout = timeout
218
+ return evolve(self, timeout=timeout)
219
+
220
+ def set_httpx_client(self, client: httpx.Client) -> "AuthenticatedClient":
221
+ """Manually set the underlying httpx.Client
222
+
223
+ **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
224
+ """
225
+ self._client = client
226
+ return self
227
+
228
+ def get_httpx_client(self) -> httpx.Client:
229
+ """Get the underlying httpx.Client, constructing a new one if not previously set"""
230
+ if self._client is None:
231
+ self._client = httpx.Client(
232
+ base_url=self._base_url,
233
+ cookies=self._cookies,
234
+ headers=self._headers,
235
+ timeout=self._timeout,
236
+ verify=self._verify_ssl,
237
+ follow_redirects=self._follow_redirects,
238
+ auth=self._provider,
239
+ **self._httpx_args,
240
+ )
241
+ return self._client
242
+
243
+ def __enter__(self) -> "AuthenticatedClient":
244
+ """Enter a context manager for self.client—you cannot enter twice (see httpx docs)"""
245
+ self.get_httpx_client().__enter__()
246
+ return self
247
+
248
+ def __exit__(self, *args: Any, **kwargs: Any) -> None:
249
+ """Exit a context manager for internal httpx.Client (see httpx docs)"""
250
+ self.get_httpx_client().__exit__(*args, **kwargs)
251
+
252
+ def set_async_httpx_client(self, async_client: httpx.AsyncClient) -> "AuthenticatedClient":
253
+ """Manually the underlying httpx.AsyncClient
254
+
255
+ **NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
256
+ """
257
+ self._async_client = async_client
258
+ return self
259
+
260
+ def get_async_httpx_client(self) -> httpx.AsyncClient:
261
+ """Get the underlying httpx.AsyncClient, constructing a new one if not previously set"""
262
+ if self._async_client is None:
263
+ self._async_client = httpx.AsyncClient(
264
+ base_url=self._base_url,
265
+ cookies=self._cookies,
266
+ headers=self._headers,
267
+ timeout=self._timeout,
268
+ verify=self._verify_ssl,
269
+ follow_redirects=self._follow_redirects,
270
+ **self._httpx_args,
271
+ )
272
+ return self._async_client
273
+
274
+ async def __aenter__(self) -> "AuthenticatedClient":
275
+ """Enter a context manager for underlying httpx.AsyncClient—you cannot enter twice (see httpx docs)"""
276
+ await self.get_async_httpx_client().__aenter__()
277
+ return self
278
+
279
+ async def __aexit__(self, *args: Any, **kwargs: Any) -> None:
280
+ """Exit a context manager for underlying httpx.AsyncClient (see httpx docs)"""
281
+ await self.get_async_httpx_client().__aexit__(*args, **kwargs)
@@ -0,0 +1,17 @@
1
+ from .error import HTTPError
2
+ from .logger import init as init_logger
3
+ from .secrets import Secret
4
+ from .settings import Settings, get_settings, init
5
+ from .slugify import slugify
6
+ from .utils import copy_folder
7
+
8
+ __all__ = [
9
+ "Secret",
10
+ "Settings",
11
+ "get_settings",
12
+ "init",
13
+ "copy_folder",
14
+ "init_logger",
15
+ "HTTPError",
16
+ "slugify"
17
+ ]
blaxel/common/error.py ADDED
@@ -0,0 +1,27 @@
1
+ """
2
+ This module defines custom exception classes used for handling HTTP-related errors within Blaxel.
3
+ """
4
+
5
+ class HTTPError(Exception):
6
+ """
7
+ A custom exception class for HTTP errors.
8
+
9
+ Attributes:
10
+ status_code (int): The HTTP status code associated with the error.
11
+ message (str): A descriptive message explaining the error.
12
+ """
13
+
14
+ def __init__(self, status_code: int, message: str):
15
+ self.status_code = status_code
16
+ self.message = message
17
+ super().__init__(self.message)
18
+
19
+ def __str__(self):
20
+ """
21
+ Returns a string representation of the HTTPError.
22
+
23
+ Returns:
24
+ str: A string in the format "status_code message".
25
+ """
26
+ return f"{self.status_code} {self.message}"
27
+
@@ -0,0 +1,317 @@
1
+ """
2
+ This module provides utilities for setting up and managing OpenTelemetry instrumentation within Blaxel.
3
+ It includes classes and functions for configuring tracers, meters, loggers, and integrating with FastAPI applications.
4
+ """
5
+
6
+ import importlib
7
+ import logging
8
+ from typing import Any, Optional, Type
9
+
10
+ from fastapi import FastAPI
11
+ from opentelemetry import _logs, metrics, trace
12
+ from opentelemetry._logs import set_logger_provider
13
+ from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
14
+ from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
15
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
16
+ from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor # type: ignore
17
+ from opentelemetry.metrics import NoOpMeterProvider
18
+ from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
19
+ from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
20
+ from opentelemetry.sdk.metrics import MeterProvider
21
+ from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
22
+ from opentelemetry.sdk.resources import Resource
23
+ from opentelemetry.sdk.trace import TracerProvider
24
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
25
+ from opentelemetry.trace import NoOpTracerProvider
26
+ from typing_extensions import Dict
27
+
28
+ from blaxel.authentication import get_authentication_headers
29
+
30
+ from .settings import get_settings
31
+
32
+ tracer: trace.Tracer | None = None
33
+ meter: metrics.Meter | None = None
34
+ logger: LoggerProvider | None = None
35
+
36
+ log = logging.getLogger(__name__)
37
+
38
+
39
+ def auth_headers() -> Dict[str, str]:
40
+ """
41
+ Retrieves authentication headers based on the current settings.
42
+
43
+ Returns:
44
+ Dict[str, str]: A dictionary containing authentication headers.
45
+ """
46
+ settings = get_settings()
47
+ headers = get_authentication_headers(settings)
48
+ return {
49
+ "x-blaxel-authorization": headers.get("X-Blaxel-Authorization", ""),
50
+ "x-blaxel-workspace": headers.get("X-Blaxel-Workspace", ""),
51
+ }
52
+
53
+
54
+ def get_logger() -> LoggerProvider:
55
+ """
56
+ Retrieves the current logger provider.
57
+
58
+ Returns:
59
+ LoggerProvider: The active logger provider.
60
+
61
+ Raises:
62
+ Exception: If the logger has not been initialized.
63
+ """
64
+ if logger is None:
65
+ raise Exception("Logger is not initialized")
66
+ return logger
67
+
68
+
69
+ def get_resource_attributes() -> Dict[str, Any]:
70
+ resources = Resource.create()
71
+ resources_dict: Dict[str, Any] = {}
72
+ for key in resources.attributes:
73
+ resources_dict[key] = resources.attributes[key]
74
+ settings = get_settings()
75
+ resources_dict["workspace"] = settings.workspace
76
+ resources_dict["service.name"] = settings.name
77
+ return resources_dict
78
+
79
+
80
+ def get_metrics_exporter() -> OTLPMetricExporter | None:
81
+ settings = get_settings()
82
+ if not settings.enable_opentelemetry:
83
+ return None
84
+ return OTLPMetricExporter(headers=auth_headers())
85
+
86
+
87
+ def get_span_exporter() -> OTLPSpanExporter | None:
88
+ settings = get_settings()
89
+ if not settings.enable_opentelemetry:
90
+ return None
91
+ return OTLPSpanExporter(headers=auth_headers())
92
+
93
+
94
+ def get_log_exporter() -> OTLPLogExporter | None:
95
+ settings = get_settings()
96
+ if not settings.enable_opentelemetry:
97
+ return None
98
+ return OTLPLogExporter(headers=auth_headers())
99
+
100
+
101
+ def _import_class(module_path: str, class_name: str) -> Optional[Type]: # type: ignore
102
+ """Dynamically import a class from a module path."""
103
+ try:
104
+ module = importlib.import_module(module_path)
105
+ return getattr(module, class_name)
106
+ except (ImportError, AttributeError) as e:
107
+ log.error(f"Could not import {class_name} from {module_path}: {str(e)}")
108
+ return None
109
+
110
+
111
+ # Define mapping of instrumentor info: (module path, class name, required package)
112
+ INSTRUMENTOR_CONFIGS = {
113
+ "httpx": (
114
+ "opentelemetry.instrumentation.httpx",
115
+ "HTTPXClientInstrumentor",
116
+ "httpx",
117
+ ),
118
+ "anthropic": (
119
+ "opentelemetry.instrumentation.anthropic",
120
+ "AnthropicInstrumentor",
121
+ "anthropic",
122
+ ),
123
+ "chroma": (
124
+ "opentelemetry.instrumentation.chroma",
125
+ "ChromaInstrumentor",
126
+ "chromadb",
127
+ ),
128
+ "cohere": (
129
+ "opentelemetry.instrumentation.cohere",
130
+ "CohereInstrumentor",
131
+ "cohere",
132
+ ),
133
+ "groq": ("opentelemetry.instrumentation.groq", "GroqInstrumentor", "groq"),
134
+ "lance": (
135
+ "opentelemetry.instrumentation.lance",
136
+ "LanceInstrumentor",
137
+ "pylance",
138
+ ),
139
+ "langchain": (
140
+ "opentelemetry.instrumentation.langchain",
141
+ "LangchainInstrumentor",
142
+ "langchain",
143
+ ),
144
+ "llama_index": (
145
+ "opentelemetry.instrumentation.llama_index",
146
+ "LlamaIndexInstrumentor",
147
+ "llama_index",
148
+ ),
149
+ "marqo": (
150
+ "opentelemetry.instrumentation.marqo",
151
+ "MarqoInstrumentor",
152
+ "marqo",
153
+ ),
154
+ "milvus": (
155
+ "opentelemetry.instrumentation.milvus",
156
+ "MilvusInstrumentor",
157
+ "pymilvus",
158
+ ),
159
+ "mistralai": (
160
+ "opentelemetry.instrumentation.mistralai",
161
+ "MistralAiInstrumentor",
162
+ "mistralai",
163
+ ),
164
+ "ollama": (
165
+ "opentelemetry.instrumentation.ollama",
166
+ "OllamaInstrumentor",
167
+ "ollama",
168
+ ),
169
+ "openai": (
170
+ "opentelemetry.instrumentation.openai",
171
+ "OpenAIInstrumentor",
172
+ "openai",
173
+ ),
174
+ "pinecone": (
175
+ "opentelemetry.instrumentation.pinecone",
176
+ "PineconeInstrumentor",
177
+ "pinecone",
178
+ ),
179
+ "qdrant": (
180
+ "opentelemetry.instrumentation.qdrant",
181
+ "QdrantInstrumentor",
182
+ "qdrant_client",
183
+ ),
184
+ "replicate": (
185
+ "opentelemetry.instrumentation.replicate",
186
+ "ReplicateInstrumentor",
187
+ "replicate",
188
+ ),
189
+ "together": (
190
+ "opentelemetry.instrumentation.together",
191
+ "TogetherAiInstrumentor",
192
+ "together",
193
+ ),
194
+ "watsonx": (
195
+ "opentelemetry.instrumentation.watsonx",
196
+ "WatsonxInstrumentor",
197
+ "ibm_watson_machine_learning",
198
+ ),
199
+ "weaviate": (
200
+ "opentelemetry.instrumentation.weaviate",
201
+ "WeaviateInstrumentor",
202
+ "weaviate",
203
+ ),
204
+ }
205
+
206
+
207
+ def _is_package_installed(package_name: str) -> bool:
208
+ """Check if a package is installed."""
209
+ try:
210
+ importlib.import_module(package_name)
211
+ return True
212
+ except (ImportError, ModuleNotFoundError):
213
+ return False
214
+
215
+
216
+ def instrument_app(app: FastAPI):
217
+ """
218
+ Instruments the given FastAPI application with OpenTelemetry.
219
+
220
+ This includes setting up tracer and meter providers, configuring exporters, and instrumenting
221
+ various modules based on available packages.
222
+
223
+ Parameters:
224
+ app (FastAPI): The FastAPI application to instrument.
225
+ """
226
+ global tracer
227
+ global meter
228
+ settings = get_settings()
229
+ if not settings.enable_opentelemetry:
230
+ # Use NoOp implementations to stub tracing and metrics
231
+ trace.set_tracer_provider(NoOpTracerProvider())
232
+ tracer = trace.get_tracer(__name__)
233
+
234
+ metrics.set_meter_provider(NoOpMeterProvider())
235
+ meter = metrics.get_meter(__name__)
236
+ return
237
+
238
+ resource = Resource.create(
239
+ {
240
+ "service.name": settings.name,
241
+ "service.namespace": settings.workspace,
242
+ "service.workspace": settings.workspace,
243
+ }
244
+ )
245
+
246
+ # Set up the TracerProvider if not already set
247
+ if not isinstance(trace.get_tracer_provider(), TracerProvider):
248
+ trace_provider = TracerProvider(resource=resource)
249
+ span_processor = BatchSpanProcessor(get_span_exporter()) # type: ignore
250
+ trace_provider.add_span_processor(span_processor)
251
+ trace.set_tracer_provider(trace_provider)
252
+ tracer = trace_provider.get_tracer(__name__)
253
+ else:
254
+ tracer = trace.get_tracer(__name__)
255
+
256
+ # Set up the MeterProvider if not already set
257
+ if not isinstance(metrics.get_meter_provider(), MeterProvider):
258
+ metrics_exporter = PeriodicExportingMetricReader(get_metrics_exporter()) # type: ignore
259
+ meter_provider = MeterProvider(
260
+ resource=resource, metric_readers=[metrics_exporter]
261
+ )
262
+ metrics.set_meter_provider(meter_provider)
263
+ meter = meter_provider.get_meter(__name__)
264
+ else:
265
+ meter = metrics.get_meter(__name__)
266
+
267
+ if not isinstance(_logs.get_logger_provider(), LoggerProvider):
268
+ logger_provider = LoggerProvider(resource=resource)
269
+ set_logger_provider(logger_provider)
270
+ logger_provider.add_log_record_processor(
271
+ BatchLogRecordProcessor(get_log_exporter()) # type: ignore
272
+ )
273
+ handler = LoggingHandler(
274
+ level=logging.NOTSET, logger_provider=logger_provider
275
+ )
276
+ logging.getLogger().addHandler(handler)
277
+ else:
278
+ logger_provider = _logs.get_logger_provider()
279
+
280
+ # Only instrument the app when OpenTelemetry is enabled
281
+ FastAPIInstrumentor.instrument_app(app) # type: ignore
282
+
283
+ for name, (
284
+ module_path,
285
+ class_name,
286
+ required_package,
287
+ ) in INSTRUMENTOR_CONFIGS.items():
288
+ if _is_package_installed(required_package):
289
+ instrumentor_class = _import_class(module_path, class_name) # type: ignore
290
+ if instrumentor_class:
291
+ try:
292
+ instrumentor_class().instrument()
293
+ log.debug(f"Successfully instrumented {name}")
294
+ except Exception as e:
295
+ log.debug(f"Failed to instrument {name}: {str(e)}")
296
+ else:
297
+ log.debug(f"Could not load instrumentor for {name}")
298
+ else:
299
+ log.debug(
300
+ f"Skipping {name} instrumentation - required package '{required_package}' not installed"
301
+ )
302
+
303
+
304
+ def shutdown_instrumentation():
305
+ """
306
+ Shuts down the OpenTelemetry instrumentation providers gracefully.
307
+
308
+ This ensures that all spans and metrics are properly exported before the application exits.
309
+ """
310
+ if tracer is not None:
311
+ trace_provider = trace.get_tracer_provider()
312
+ if isinstance(trace_provider, TracerProvider):
313
+ trace_provider.shutdown()
314
+ if meter is not None:
315
+ meter_provider = metrics.get_meter_provider()
316
+ if isinstance(meter_provider, MeterProvider):
317
+ meter_provider.shutdown()