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.
- blaxel/__init__.py +8 -0
- blaxel/agents/__init__.py +5 -0
- blaxel/agents/chain.py +153 -0
- blaxel/agents/chat.py +286 -0
- blaxel/agents/decorator.py +208 -0
- blaxel/agents/thread.py +24 -0
- blaxel/agents/voice/openai.py +255 -0
- blaxel/agents/voice/utils.py +25 -0
- blaxel/api/__init__.py +1 -0
- blaxel/api/agents/__init__.py +0 -0
- blaxel/api/agents/create_agent.py +155 -0
- blaxel/api/agents/delete_agent.py +146 -0
- blaxel/api/agents/get_agent.py +146 -0
- blaxel/api/agents/get_agent_logs.py +151 -0
- blaxel/api/agents/get_agent_metrics.py +150 -0
- blaxel/api/agents/get_agent_trace_ids.py +201 -0
- blaxel/api/agents/list_agent_revisions.py +155 -0
- blaxel/api/agents/list_agents.py +127 -0
- blaxel/api/agents/update_agent.py +168 -0
- blaxel/api/configurations/__init__.py +0 -0
- blaxel/api/configurations/get_configuration.py +122 -0
- blaxel/api/default/__init__.py +0 -0
- blaxel/api/default/get_trace.py +150 -0
- blaxel/api/default/get_trace_ids.py +218 -0
- blaxel/api/default/get_trace_logs.py +186 -0
- blaxel/api/default/list_mcp_hub_definitions.py +127 -0
- blaxel/api/functions/__init__.py +0 -0
- blaxel/api/functions/create_function.py +155 -0
- blaxel/api/functions/delete_function.py +146 -0
- blaxel/api/functions/get_function.py +146 -0
- blaxel/api/functions/get_function_logs.py +151 -0
- blaxel/api/functions/get_function_metrics.py +150 -0
- blaxel/api/functions/get_function_trace_ids.py +201 -0
- blaxel/api/functions/list_function_revisions.py +158 -0
- blaxel/api/functions/list_functions.py +131 -0
- blaxel/api/functions/update_function.py +168 -0
- blaxel/api/integrations/__init__.py +0 -0
- blaxel/api/integrations/create_integration_connection.py +167 -0
- blaxel/api/integrations/delete_integration_connection.py +158 -0
- blaxel/api/integrations/get_integration.py +97 -0
- blaxel/api/integrations/get_integration_connection.py +158 -0
- blaxel/api/integrations/get_integration_connection_model.py +104 -0
- blaxel/api/integrations/get_integration_connection_model_endpoint_configurations.py +97 -0
- blaxel/api/integrations/list_integration_connection_models.py +97 -0
- blaxel/api/integrations/list_integration_connections.py +139 -0
- blaxel/api/integrations/update_integration_connection.py +180 -0
- blaxel/api/invitations/__init__.py +0 -0
- blaxel/api/invitations/list_all_pending_invitations.py +142 -0
- blaxel/api/knowledgebases/__init__.py +0 -0
- blaxel/api/knowledgebases/create_knowledgebase.py +163 -0
- blaxel/api/knowledgebases/delete_knowledgebase.py +154 -0
- blaxel/api/knowledgebases/get_knowledgebase.py +154 -0
- blaxel/api/knowledgebases/list_knowledgebase_revisions.py +158 -0
- blaxel/api/knowledgebases/list_knowledgebases.py +139 -0
- blaxel/api/knowledgebases/update_knowledgebase.py +176 -0
- blaxel/api/locations/__init__.py +0 -0
- blaxel/api/locations/list_locations.py +139 -0
- blaxel/api/metrics/__init__.py +0 -0
- blaxel/api/metrics/get_metrics.py +130 -0
- blaxel/api/models/__init__.py +0 -0
- blaxel/api/models/create_model.py +163 -0
- blaxel/api/models/delete_model.py +154 -0
- blaxel/api/models/get_model.py +154 -0
- blaxel/api/models/get_model_logs.py +155 -0
- blaxel/api/models/get_model_metrics.py +158 -0
- blaxel/api/models/get_model_trace_ids.py +201 -0
- blaxel/api/models/list_model_revisions.py +158 -0
- blaxel/api/models/list_models.py +135 -0
- blaxel/api/models/update_model.py +176 -0
- blaxel/api/policies/__init__.py +0 -0
- blaxel/api/policies/create_policy.py +167 -0
- blaxel/api/policies/delete_policy.py +154 -0
- blaxel/api/policies/get_policy.py +154 -0
- blaxel/api/policies/list_policies.py +139 -0
- blaxel/api/policies/update_policy.py +180 -0
- blaxel/api/privateclusters/__init__.py +0 -0
- blaxel/api/privateclusters/create_private_cluster.py +132 -0
- blaxel/api/privateclusters/delete_private_cluster.py +156 -0
- blaxel/api/privateclusters/get_private_cluster.py +159 -0
- blaxel/api/privateclusters/get_private_cluster_health.py +97 -0
- blaxel/api/privateclusters/list_private_clusters.py +140 -0
- blaxel/api/privateclusters/update_private_cluster.py +156 -0
- blaxel/api/privateclusters/update_private_cluster_health.py +97 -0
- blaxel/api/service_accounts/__init__.py +0 -0
- blaxel/api/service_accounts/create_api_key_for_service_account.py +177 -0
- blaxel/api/service_accounts/create_workspace_service_account.py +170 -0
- blaxel/api/service_accounts/delete_api_key_for_service_account.py +104 -0
- blaxel/api/service_accounts/delete_workspace_service_account.py +160 -0
- blaxel/api/service_accounts/get_workspace_service_accounts.py +141 -0
- blaxel/api/service_accounts/list_api_keys_for_service_account.py +163 -0
- blaxel/api/service_accounts/update_workspace_service_account.py +183 -0
- blaxel/api/store/__init__.py +0 -0
- blaxel/api/store/get_store_agent.py +146 -0
- blaxel/api/store/get_store_function.py +146 -0
- blaxel/api/store/list_store_agents.py +131 -0
- blaxel/api/store/list_store_functions.py +131 -0
- blaxel/api/workspaces/__init__.py +0 -0
- blaxel/api/workspaces/accept_workspace_invitation.py +161 -0
- blaxel/api/workspaces/create_worspace.py +163 -0
- blaxel/api/workspaces/decline_workspace_invitation.py +158 -0
- blaxel/api/workspaces/delete_workspace.py +154 -0
- blaxel/api/workspaces/get_workspace.py +154 -0
- blaxel/api/workspaces/invite_workspace_user.py +174 -0
- blaxel/api/workspaces/leave_workspace.py +161 -0
- blaxel/api/workspaces/list_workspace_users.py +139 -0
- blaxel/api/workspaces/list_workspaces.py +139 -0
- blaxel/api/workspaces/remove_workspace_user.py +101 -0
- blaxel/api/workspaces/update_workspace.py +176 -0
- blaxel/api/workspaces/update_workspace_user_role.py +187 -0
- blaxel/authentication/__init__.py +45 -0
- blaxel/authentication/apikey.py +50 -0
- blaxel/authentication/authentication.py +176 -0
- blaxel/authentication/clientcredentials.py +103 -0
- blaxel/authentication/credentials.py +295 -0
- blaxel/authentication/device_mode.py +197 -0
- blaxel/client.py +281 -0
- blaxel/common/__init__.py +17 -0
- blaxel/common/error.py +27 -0
- blaxel/common/instrumentation.py +317 -0
- blaxel/common/logger.py +60 -0
- blaxel/common/secrets.py +39 -0
- blaxel/common/settings.py +150 -0
- blaxel/common/slugify.py +18 -0
- blaxel/common/utils.py +34 -0
- blaxel/deploy/__init__.py +8 -0
- blaxel/deploy/deploy.py +316 -0
- blaxel/deploy/format.py +46 -0
- blaxel/deploy/parser.py +192 -0
- blaxel/errors.py +16 -0
- blaxel/functions/__init__.py +7 -0
- blaxel/functions/common.py +228 -0
- blaxel/functions/decorator.py +64 -0
- blaxel/functions/local/local.py +48 -0
- blaxel/functions/mcp/client.py +96 -0
- blaxel/functions/mcp/mcp.py +168 -0
- blaxel/functions/mcp/utils.py +56 -0
- blaxel/functions/remote/remote.py +183 -0
- blaxel/models/__init__.py +233 -0
- blaxel/models/acl.py +133 -0
- blaxel/models/agent.py +126 -0
- blaxel/models/agent_chain.py +88 -0
- blaxel/models/agent_spec.py +346 -0
- blaxel/models/api_key.py +142 -0
- blaxel/models/configuration.py +85 -0
- blaxel/models/continent.py +70 -0
- blaxel/models/core_event.py +97 -0
- blaxel/models/core_spec.py +249 -0
- blaxel/models/core_spec_configurations.py +77 -0
- blaxel/models/country.py +70 -0
- blaxel/models/create_api_key_for_service_account_body.py +69 -0
- blaxel/models/create_workspace_service_account_body.py +71 -0
- blaxel/models/create_workspace_service_account_response_200.py +105 -0
- blaxel/models/delete_workspace_service_account_response_200.py +96 -0
- blaxel/models/entrypoint.py +96 -0
- blaxel/models/entrypoint_env.py +45 -0
- blaxel/models/flavor.py +70 -0
- blaxel/models/form.py +120 -0
- blaxel/models/form_config.py +45 -0
- blaxel/models/form_oauthomitempty.py +45 -0
- blaxel/models/form_secrets.py +45 -0
- blaxel/models/function.py +126 -0
- blaxel/models/function_kit.py +97 -0
- blaxel/models/function_spec.py +310 -0
- blaxel/models/get_trace_ids_response_200.py +45 -0
- blaxel/models/get_trace_logs_response_200.py +45 -0
- blaxel/models/get_trace_response_200.py +45 -0
- blaxel/models/get_workspace_service_accounts_response_200_item.py +96 -0
- blaxel/models/histogram_bucket.py +79 -0
- blaxel/models/histogram_stats.py +88 -0
- blaxel/models/integration_connection.py +96 -0
- blaxel/models/integration_connection_spec.py +114 -0
- blaxel/models/integration_connection_spec_config.py +45 -0
- blaxel/models/integration_connection_spec_secret.py +45 -0
- blaxel/models/integration_model.py +162 -0
- blaxel/models/integration_repository.py +88 -0
- blaxel/models/invite_workspace_user_body.py +60 -0
- blaxel/models/knowledgebase.py +126 -0
- blaxel/models/knowledgebase_spec.py +163 -0
- blaxel/models/knowledgebase_spec_options.py +45 -0
- blaxel/models/last_n_requests_metric.py +79 -0
- blaxel/models/latency_metric.py +144 -0
- blaxel/models/location_response.py +113 -0
- blaxel/models/mcp_definition.py +188 -0
- blaxel/models/mcp_definition_entrypoint.py +45 -0
- blaxel/models/mcp_definition_form.py +45 -0
- blaxel/models/metadata.py +139 -0
- blaxel/models/metadata_labels.py +45 -0
- blaxel/models/metric.py +79 -0
- blaxel/models/metrics.py +169 -0
- blaxel/models/metrics_models.py +45 -0
- blaxel/models/metrics_request_total_per_code.py +45 -0
- blaxel/models/metrics_rps_per_code.py +45 -0
- blaxel/models/model.py +126 -0
- blaxel/models/model_private_cluster.py +79 -0
- blaxel/models/model_spec.py +249 -0
- blaxel/models/o_auth.py +72 -0
- blaxel/models/owner_fields.py +70 -0
- blaxel/models/pending_invitation.py +124 -0
- blaxel/models/pending_invitation_accept.py +85 -0
- blaxel/models/pending_invitation_render.py +147 -0
- blaxel/models/pending_invitation_render_invited_by.py +88 -0
- blaxel/models/pending_invitation_render_workspace.py +70 -0
- blaxel/models/pending_invitation_workspace_details.py +72 -0
- blaxel/models/pod_template_spec.py +45 -0
- blaxel/models/policy.py +96 -0
- blaxel/models/policy_location.py +70 -0
- blaxel/models/policy_max_tokens.py +106 -0
- blaxel/models/policy_spec.py +151 -0
- blaxel/models/private_cluster.py +183 -0
- blaxel/models/private_location.py +61 -0
- blaxel/models/repository.py +70 -0
- blaxel/models/request_duration_over_time_metric.py +97 -0
- blaxel/models/request_duration_over_time_metrics.py +80 -0
- blaxel/models/request_total_by_origin_metric.py +115 -0
- blaxel/models/request_total_by_origin_metric_request_total_by_origin.py +45 -0
- blaxel/models/request_total_by_origin_metric_request_total_by_origin_and_code.py +45 -0
- blaxel/models/request_total_metric.py +123 -0
- blaxel/models/request_total_metric_request_total_per_code.py +45 -0
- blaxel/models/request_total_metric_rps_per_code.py +45 -0
- blaxel/models/resource_log.py +79 -0
- blaxel/models/resource_metrics.py +270 -0
- blaxel/models/resource_metrics_request_total_per_code.py +45 -0
- blaxel/models/resource_metrics_rps_per_code.py +45 -0
- blaxel/models/revision_configuration.py +97 -0
- blaxel/models/revision_metadata.py +124 -0
- blaxel/models/runtime.py +196 -0
- blaxel/models/runtime_startup_probe.py +45 -0
- blaxel/models/serverless_config.py +80 -0
- blaxel/models/spec_configuration.py +70 -0
- blaxel/models/store_agent.py +178 -0
- blaxel/models/store_agent_labels.py +45 -0
- blaxel/models/store_configuration.py +151 -0
- blaxel/models/store_configuration_option.py +79 -0
- blaxel/models/store_function.py +211 -0
- blaxel/models/store_function_kit.py +97 -0
- blaxel/models/store_function_labels.py +45 -0
- blaxel/models/store_function_parameter.py +88 -0
- blaxel/models/time_fields.py +70 -0
- blaxel/models/token_rate_metric.py +88 -0
- blaxel/models/token_rate_metrics.py +120 -0
- blaxel/models/token_total_metric.py +106 -0
- blaxel/models/trace_ids_response.py +45 -0
- blaxel/models/update_workspace_service_account_body.py +69 -0
- blaxel/models/update_workspace_service_account_response_200.py +96 -0
- blaxel/models/update_workspace_user_role_body.py +60 -0
- blaxel/models/websocket_channel.py +88 -0
- blaxel/models/workspace.py +148 -0
- blaxel/models/workspace_labels.py +45 -0
- blaxel/models/workspace_user.py +115 -0
- blaxel/py.typed +1 -0
- blaxel/run.py +108 -0
- blaxel/serve/app.py +131 -0
- blaxel/serve/middlewares/__init__.py +10 -0
- blaxel/serve/middlewares/accesslog.py +32 -0
- blaxel/serve/middlewares/processtime.py +28 -0
- blaxel/types.py +46 -0
- blaxel-0.64.0.dist-info/METADATA +96 -0
- blaxel-0.64.0.dist-info/RECORD +261 -0
- blaxel-0.64.0.dist-info/WHEEL +4 -0
- blaxel-0.64.0.dist-info/entry_points.txt +2 -0
- 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()
|