beamlit 0.0.18__py3-none-any.whl → 0.0.20__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- beamlit/agents/__init__.py +4 -0
- beamlit/agents/chat.py +87 -0
- beamlit/agents/decorator.py +147 -0
- beamlit/api/agents/create_agent.py +14 -9
- beamlit/api/agents/delete_agent_history.py +159 -0
- beamlit/api/agents/get_agent_deployment_logs.py +11 -11
- beamlit/api/{authentication_providers/list_organizations_for_authentication_provider.py → agents/get_agent_environment_logs.py} +30 -42
- beamlit/api/{authentication_providers/get_model_with_repo_for_authentication_provider.py → agents/get_agent_history.py} +43 -68
- beamlit/api/agents/list_agent_deployment_history.py +11 -11
- beamlit/api/agents/list_agent_deployments.py +11 -11
- beamlit/api/agents/list_agent_history.py +151 -0
- beamlit/api/agents/list_agents.py +11 -11
- beamlit/api/agents/put_agent_history.py +185 -0
- beamlit/api/agents/update_agent.py +14 -9
- beamlit/api/environments/list_environments.py +11 -11
- beamlit/api/functions/create_function.py +14 -9
- beamlit/api/functions/get_function_deployment_logs.py +11 -11
- beamlit/api/functions/get_function_environment_logs.py +151 -0
- beamlit/api/functions/list_function_deployments.py +11 -11
- beamlit/api/functions/list_functions.py +11 -11
- beamlit/api/functions/update_function.py +14 -9
- beamlit/api/history/get_agents_history.py +11 -11
- beamlit/api/history/list_agents_history.py +11 -11
- beamlit/api/integrations/create_integration_connection.py +167 -0
- beamlit/api/integrations/delete_integration_connection.py +158 -0
- beamlit/api/integrations/get_integration.py +97 -0
- beamlit/api/integrations/get_integration_connection.py +154 -0
- beamlit/api/integrations/get_integration_connection_model.py +97 -0
- beamlit/api/integrations/get_integration_model.py +97 -0
- beamlit/api/integrations/list_integration_connection_models.py +97 -0
- beamlit/api/integrations/list_integration_connections.py +139 -0
- beamlit/api/integrations/list_integration_models.py +97 -0
- beamlit/api/integrations/update_integration_connection.py +180 -0
- beamlit/api/invitations/list_all_pending_invitations.py +11 -11
- beamlit/api/locations/list_locations.py +11 -11
- beamlit/api/model_providers/list_model_providers.py +11 -11
- beamlit/api/models/get_model_deployment_logs.py +11 -11
- beamlit/api/{authentication_providers/list_models_for_authentication_provider.py → models/get_model_environment_logs.py} +30 -38
- beamlit/api/models/list_model_deployments.py +11 -11
- beamlit/api/models/list_models.py +11 -11
- beamlit/api/policies/list_policies.py +11 -11
- beamlit/api/service_accounts/get_workspace_service_accounts.py +11 -11
- beamlit/api/service_accounts/list_api_keys_for_service_account.py +11 -11
- beamlit/api/store/list_store_agents.py +11 -11
- beamlit/api/store/list_store_functions.py +11 -11
- beamlit/api/workspaces/list_workspace_users.py +11 -11
- beamlit/api/workspaces/list_workspaces.py +11 -11
- beamlit/authentication/__init__.py +29 -8
- beamlit/authentication/apikey.py +8 -2
- beamlit/authentication/authentication.py +66 -3
- beamlit/authentication/clientcredentials.py +108 -0
- beamlit/authentication/credentials.py +22 -8
- beamlit/authentication/device_mode.py +23 -13
- beamlit/common/__init__.py +13 -0
- beamlit/common/generate.py +183 -0
- beamlit/common/logger.py +29 -0
- beamlit/common/secrets.py +11 -0
- beamlit/common/settings.py +156 -0
- beamlit/common/utils.py +15 -0
- beamlit/functions/__init__.py +5 -0
- beamlit/functions/decorator.py +90 -0
- beamlit/functions/github/__init__.py +3 -0
- beamlit/functions/github/github.py +21 -0
- beamlit/functions/github/kit/__init__.py +7 -0
- beamlit/functions/github/kit/pull_request.py +51 -0
- beamlit/functions/math/__init__.py +3 -0
- beamlit/functions/math/math.py +40 -0
- beamlit/functions/search/__init__.py +3 -0
- beamlit/functions/search/search.py +15 -0
- beamlit/models/__init__.py +24 -0
- beamlit/models/acl.py +4 -2
- beamlit/models/agent.py +5 -3
- beamlit/models/agent_chain.py +4 -2
- beamlit/models/agent_configuration.py +4 -2
- beamlit/models/agent_deployment.py +41 -28
- beamlit/models/agent_deployment_configuration.py +4 -2
- beamlit/models/agent_deployment_configuration_type_0.py +43 -0
- beamlit/models/agent_deployment_history.py +7 -5
- beamlit/models/agent_deployment_history_event.py +11 -9
- beamlit/models/agent_deployment_pod_template.py +4 -2
- beamlit/models/agent_deployment_pod_template_type_0.py +43 -0
- beamlit/models/agent_history.py +165 -0
- beamlit/models/agent_history_event.py +131 -0
- beamlit/models/agent_metadata.py +144 -0
- beamlit/models/agent_release.py +4 -2
- beamlit/models/agent_spec.py +248 -0
- beamlit/models/agent_with_deployments.py +176 -0
- beamlit/models/api_key.py +4 -2
- beamlit/models/authentication_provider_model.py +8 -6
- beamlit/models/authentication_provider_organization.py +4 -2
- beamlit/models/configuration.py +12 -10
- beamlit/models/continent.py +4 -2
- beamlit/models/core_spec.py +185 -0
- beamlit/models/country.py +4 -2
- beamlit/models/create_api_key_for_service_account_body.py +4 -2
- beamlit/models/create_workspace_service_account_body.py +4 -2
- beamlit/models/create_workspace_service_account_response_200.py +4 -2
- beamlit/models/delete_workspace_service_account_response_200.py +4 -2
- beamlit/models/deployment_configuration.py +4 -2
- beamlit/models/deployment_configurations.py +22 -7
- beamlit/models/deployment_serverless_config.py +4 -2
- beamlit/models/deployment_serverless_config_type_0.py +220 -0
- beamlit/models/environment.py +9 -7
- beamlit/models/environment_metrics.py +26 -5
- beamlit/models/environment_spec.py +61 -0
- beamlit/models/flavor.py +11 -9
- beamlit/models/function.py +5 -3
- beamlit/models/function_configuration.py +4 -2
- beamlit/models/function_deployment.py +33 -20
- beamlit/models/function_deployment_configuration.py +4 -2
- beamlit/models/function_deployment_configuration_type_0.py +43 -0
- beamlit/models/function_deployment_pod_template.py +4 -2
- beamlit/models/function_deployment_pod_template_type_0.py +43 -0
- beamlit/models/function_kit.py +7 -5
- beamlit/models/function_metadata.py +144 -0
- beamlit/models/function_provider_ref.py +4 -2
- beamlit/models/function_release.py +4 -2
- beamlit/models/function_spec.py +246 -0
- beamlit/models/function_with_deployments.py +176 -0
- beamlit/models/get_workspace_service_accounts_response_200_item.py +4 -2
- beamlit/models/increase_and_rate_metric.py +104 -0
- beamlit/models/integration.py +198 -0
- beamlit/models/integration_config.py +45 -0
- beamlit/models/integration_connection.py +198 -0
- beamlit/models/integration_connection_config.py +45 -0
- beamlit/models/integration_connection_secret.py +61 -0
- beamlit/models/integration_connection_spec.py +99 -0
- beamlit/models/integration_model.py +144 -0
- beamlit/models/integration_secret.py +61 -0
- beamlit/models/invite_workspace_user_body.py +4 -2
- beamlit/models/labels_type_0.py +4 -2
- beamlit/models/location.py +7 -5
- beamlit/models/location_response.py +7 -5
- beamlit/models/metadata.py +135 -0
- beamlit/models/{labels.py → metadata_labels.py} +5 -5
- beamlit/models/metric.py +4 -2
- beamlit/models/metrics.py +68 -25
- beamlit/models/model.py +5 -3
- beamlit/models/model_deployment.py +27 -14
- beamlit/models/model_deployment_log.py +4 -2
- beamlit/models/model_deployment_metrics.py +11 -9
- beamlit/models/model_deployment_metrics_inference_per_second_per_region.py +7 -5
- beamlit/models/model_deployment_metrics_query_per_second_per_region_per_code.py +5 -3
- beamlit/models/model_deployment_pod_template.py +4 -2
- beamlit/models/model_deployment_pod_template_type_0.py +43 -0
- beamlit/models/model_metadata.py +144 -0
- beamlit/models/model_metrics.py +8 -6
- beamlit/models/model_provider.py +13 -11
- beamlit/models/model_provider_ref.py +4 -2
- beamlit/models/model_release.py +4 -2
- beamlit/models/model_spec.py +194 -0
- beamlit/models/model_with_deployments.py +8 -6
- beamlit/models/owner_fields.py +68 -0
- beamlit/models/pending_invitation.py +4 -2
- beamlit/models/pending_invitation_accept.py +5 -3
- beamlit/models/pending_invitation_render.py +7 -5
- beamlit/models/pending_invitation_render_invited_by.py +4 -2
- beamlit/models/pending_invitation_render_workspace.py +4 -2
- beamlit/models/pending_invitation_workspace_details.py +8 -6
- beamlit/models/pod_template_spec.py +43 -0
- beamlit/models/policy.py +22 -20
- beamlit/models/policy_location.py +11 -9
- beamlit/models/policy_spec.py +125 -0
- beamlit/models/provider_config.py +9 -16
- beamlit/models/qps.py +4 -2
- beamlit/models/resource_deployment_log.py +4 -2
- beamlit/models/resource_deployment_metrics.py +119 -9
- beamlit/models/resource_deployment_metrics_inference_per_region.py +77 -0
- beamlit/models/resource_deployment_metrics_inference_per_region_type_0.py +79 -0
- beamlit/models/resource_deployment_metrics_inference_per_second_per_region.py +7 -5
- beamlit/models/resource_deployment_metrics_inference_per_second_per_region_type_0.py +79 -0
- beamlit/models/resource_deployment_metrics_query_per_region_per_code.py +75 -0
- beamlit/models/resource_deployment_metrics_query_per_region_per_code_type_0.py +73 -0
- beamlit/models/resource_deployment_metrics_query_per_second_per_region_per_code.py +5 -3
- beamlit/models/resource_deployment_metrics_query_per_second_per_region_per_code_type_0.py +73 -0
- beamlit/models/resource_environment_metrics.py +210 -0
- beamlit/models/resource_environment_metrics_inference_per_second_per_region.py +79 -0
- beamlit/models/resource_environment_metrics_query_per_second_per_region_per_code.py +73 -0
- beamlit/models/resource_log.py +68 -0
- beamlit/models/resource_metrics.py +44 -6
- beamlit/models/runtime.py +25 -23
- beamlit/models/runtime_readiness_probe.py +4 -2
- beamlit/models/runtime_readiness_probe_type_0.py +43 -0
- beamlit/models/runtime_resources.py +4 -2
- beamlit/models/runtime_type_0.py +111 -0
- beamlit/models/runtime_type_0_readiness_probe.py +43 -0
- beamlit/models/runtime_type_0_readiness_probe_type_0.py +43 -0
- beamlit/models/runtime_type_0_resources.py +59 -0
- beamlit/models/serverless_config.py +4 -2
- beamlit/models/spec_configuration.py +68 -0
- beamlit/models/standard_fields_dynamo_db.py +4 -2
- beamlit/models/store_agent.py +8 -6
- beamlit/models/store_agent_configuration.py +4 -2
- beamlit/models/store_agent_labels.py +4 -2
- beamlit/models/store_agent_labels_type_0.py +43 -0
- beamlit/models/store_configuration.py +18 -16
- beamlit/models/store_configuration_option.py +4 -2
- beamlit/models/store_function.py +14 -12
- beamlit/models/store_function_configuration.py +4 -2
- beamlit/models/store_function_kit.py +7 -5
- beamlit/models/store_function_labels.py +4 -2
- beamlit/models/store_function_labels_type_0.py +43 -0
- beamlit/models/store_function_parameter.py +11 -9
- beamlit/models/time_fields.py +68 -0
- beamlit/models/update_workspace_service_account_body.py +4 -2
- beamlit/models/update_workspace_service_account_response_200.py +4 -2
- beamlit/models/update_workspace_user_role_body.py +4 -2
- beamlit/models/websocket_channel.py +86 -0
- beamlit/models/workspace.py +5 -3
- beamlit/models/workspace_labels.py +4 -2
- beamlit/models/workspace_user.py +4 -2
- beamlit/run.py +49 -0
- beamlit/serve/app.py +78 -0
- beamlit/serve/middlewares/__init__.py +4 -0
- beamlit/serve/middlewares/accesslog.py +14 -0
- beamlit/serve/middlewares/processtime.py +12 -0
- {beamlit-0.0.18.dist-info → beamlit-0.0.20.dist-info}/METADATA +11 -2
- beamlit-0.0.20.dist-info/RECORD +301 -0
- beamlit-0.0.18.dist-info/RECORD +0 -211
- /beamlit/api/{authentication_providers → integrations}/__init__.py +0 -0
- {beamlit-0.0.18.dist-info → beamlit-0.0.20.dist-info}/WHEEL +0 -0
@@ -1,24 +1,45 @@
|
|
1
1
|
from .apikey import ApiKeyProvider
|
2
|
-
from .authentication import (
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
2
|
+
from .authentication import (
|
3
|
+
PublicProvider,
|
4
|
+
RunClientWithCredentials,
|
5
|
+
get_authentication_headers,
|
6
|
+
new_client,
|
7
|
+
new_client_from_settings,
|
8
|
+
new_client_with_credentials,
|
9
|
+
)
|
10
|
+
from .credentials import (
|
11
|
+
Config,
|
12
|
+
ContextConfig,
|
13
|
+
Credentials,
|
14
|
+
WorkspaceConfig,
|
15
|
+
load_credentials,
|
16
|
+
load_credentials_from_settings,
|
17
|
+
)
|
18
|
+
from .device_mode import (
|
19
|
+
BearerToken,
|
20
|
+
DeviceLogin,
|
21
|
+
DeviceLoginFinalizeRequest,
|
22
|
+
DeviceLoginFinalizeResponse,
|
23
|
+
DeviceLoginResponse,
|
24
|
+
)
|
8
25
|
|
9
26
|
__all__ = (
|
10
27
|
"ApiKeyProvider",
|
11
28
|
"PublicProvider",
|
12
29
|
"RunClientWithCredentials",
|
13
30
|
"new_client_with_credentials",
|
31
|
+
"new_client_from_settings",
|
32
|
+
"new_client",
|
33
|
+
"get_authentication_headers",
|
14
34
|
"Config",
|
15
35
|
"ContextConfig",
|
16
36
|
"Credentials",
|
17
37
|
"WorkspaceConfig",
|
18
38
|
"load_credentials",
|
39
|
+
"load_credentials_from_settings",
|
19
40
|
"BearerToken",
|
20
41
|
"DeviceLogin",
|
21
42
|
"DeviceLoginFinalizeRequest",
|
22
43
|
"DeviceLoginFinalizeResponse",
|
23
|
-
"DeviceLoginResponse"
|
24
|
-
)
|
44
|
+
"DeviceLoginResponse",
|
45
|
+
)
|
beamlit/authentication/apikey.py
CHANGED
@@ -8,7 +8,13 @@ class ApiKeyProvider(Auth):
|
|
8
8
|
self.credentials = credentials
|
9
9
|
self.workspace_name = workspace_name
|
10
10
|
|
11
|
+
def get_headers(self):
|
12
|
+
return {
|
13
|
+
"X-Beamlit-Api-Key": self.credentials.api_key,
|
14
|
+
"X-Beamlit-Workspace": self.workspace_name,
|
15
|
+
}
|
16
|
+
|
11
17
|
def auth_flow(self, request: Request) -> Generator[Request, Response, None]:
|
12
|
-
request.headers[
|
13
|
-
request.headers[
|
18
|
+
request.headers["X-Beamlit-Api-Key"] = self.credentials.api_key
|
19
|
+
request.headers["X-Beamlit-Workspace"] = self.workspace_name
|
14
20
|
yield request
|
@@ -1,11 +1,19 @@
|
|
1
1
|
from dataclasses import dataclass
|
2
|
-
from typing import Generator
|
2
|
+
from typing import Dict, Generator
|
3
3
|
|
4
4
|
from httpx import Auth, Request, Response
|
5
5
|
|
6
|
+
from beamlit.common.settings import Settings, get_settings
|
7
|
+
|
6
8
|
from ..client import AuthenticatedClient
|
7
9
|
from .apikey import ApiKeyProvider
|
8
|
-
from .
|
10
|
+
from .clientcredentials import ClientCredentials
|
11
|
+
from .credentials import (
|
12
|
+
Credentials,
|
13
|
+
current_context,
|
14
|
+
load_credentials,
|
15
|
+
load_credentials_from_settings,
|
16
|
+
)
|
9
17
|
from .device_mode import BearerToken
|
10
18
|
|
11
19
|
|
@@ -14,7 +22,6 @@ class PublicProvider(Auth):
|
|
14
22
|
yield request
|
15
23
|
|
16
24
|
|
17
|
-
|
18
25
|
@dataclass
|
19
26
|
class RunClientWithCredentials:
|
20
27
|
credentials: Credentials
|
@@ -23,13 +30,69 @@ class RunClientWithCredentials:
|
|
23
30
|
run_url: str = "https://run.beamlit.dev/v0"
|
24
31
|
|
25
32
|
|
33
|
+
def new_client_from_settings(settings: Settings):
|
34
|
+
credentials = load_credentials_from_settings(settings)
|
35
|
+
|
36
|
+
client_config = RunClientWithCredentials(
|
37
|
+
credentials=credentials,
|
38
|
+
workspace=settings.workspace,
|
39
|
+
)
|
40
|
+
return new_client_with_credentials(client_config)
|
41
|
+
|
42
|
+
|
43
|
+
def new_client():
|
44
|
+
context = current_context()
|
45
|
+
if context.workspace:
|
46
|
+
credentials = load_credentials(context.workspace)
|
47
|
+
client_config = RunClientWithCredentials(
|
48
|
+
credentials=credentials,
|
49
|
+
workspace=context.workspace,
|
50
|
+
)
|
51
|
+
else:
|
52
|
+
settings = get_settings()
|
53
|
+
credentials = load_credentials_from_settings(settings)
|
54
|
+
|
55
|
+
client_config = RunClientWithCredentials(
|
56
|
+
credentials=credentials,
|
57
|
+
workspace=settings.workspace,
|
58
|
+
)
|
59
|
+
return new_client_with_credentials(client_config)
|
60
|
+
|
61
|
+
|
26
62
|
def new_client_with_credentials(config: RunClientWithCredentials):
|
27
63
|
provider: Auth = None
|
28
64
|
if config.credentials.api_key:
|
29
65
|
provider = ApiKeyProvider(config.credentials, config.workspace)
|
30
66
|
elif config.credentials.access_token:
|
31
67
|
provider = BearerToken(config.credentials, config.workspace, config.api_url)
|
68
|
+
elif config.credentials.client_credentials:
|
69
|
+
provider = ClientCredentials(config.credentials, config.workspace, config.api_url)
|
32
70
|
else:
|
33
71
|
provider = PublicProvider()
|
34
72
|
|
35
73
|
return AuthenticatedClient(base_url=config.api_url, provider=provider)
|
74
|
+
|
75
|
+
|
76
|
+
def get_authentication_headers(settings: Settings) -> Dict[str, str]:
|
77
|
+
context = current_context()
|
78
|
+
if context.workspace:
|
79
|
+
credentials = load_credentials(context.workspace)
|
80
|
+
else:
|
81
|
+
settings = get_settings()
|
82
|
+
credentials = load_credentials_from_settings(settings)
|
83
|
+
|
84
|
+
config = RunClientWithCredentials(
|
85
|
+
credentials=credentials,
|
86
|
+
workspace=settings.workspace,
|
87
|
+
)
|
88
|
+
provider = None
|
89
|
+
if config.credentials.api_key:
|
90
|
+
provider = ApiKeyProvider(config.credentials, config.workspace)
|
91
|
+
elif config.credentials.access_token:
|
92
|
+
provider = BearerToken(config.credentials, config.workspace, config.api_url)
|
93
|
+
elif config.credentials.client_credentials:
|
94
|
+
provider = ClientCredentials(config.credentials, config.workspace, config.api_url)
|
95
|
+
|
96
|
+
if provider is None:
|
97
|
+
return None
|
98
|
+
return provider.get_headers()
|
@@ -0,0 +1,108 @@
|
|
1
|
+
import base64
|
2
|
+
import json
|
3
|
+
import time
|
4
|
+
from dataclasses import dataclass
|
5
|
+
from typing import Generator, Optional
|
6
|
+
|
7
|
+
import requests
|
8
|
+
from httpx import Auth, Request, Response, post
|
9
|
+
|
10
|
+
from beamlit.common.settings import get_settings
|
11
|
+
|
12
|
+
|
13
|
+
@dataclass
|
14
|
+
class DeviceLoginFinalizeResponse:
|
15
|
+
access_token: str
|
16
|
+
expires_in: int
|
17
|
+
refresh_token: str
|
18
|
+
token_type: str
|
19
|
+
|
20
|
+
|
21
|
+
class ClientCredentials(Auth):
|
22
|
+
def __init__(self, credentials, workspace_name: str, base_url: str):
|
23
|
+
self.credentials = credentials
|
24
|
+
self.workspace_name = workspace_name
|
25
|
+
self.base_url = base_url
|
26
|
+
|
27
|
+
def get_headers(self):
|
28
|
+
err = self.refresh_if_needed()
|
29
|
+
if err:
|
30
|
+
raise err
|
31
|
+
|
32
|
+
return {
|
33
|
+
"X-Beamlit-Authorization": f"Bearer {self.credentials.access_token}",
|
34
|
+
"X-Beamlit-Workspace": self.workspace_name,
|
35
|
+
}
|
36
|
+
|
37
|
+
def refresh_if_needed(self) -> Optional[Exception]:
|
38
|
+
settings = get_settings()
|
39
|
+
if self.credentials.client_credentials and not self.credentials.refresh_token:
|
40
|
+
headers = {"Authorization": f"Basic {self.credentials.client_credentials}"}
|
41
|
+
body = {"grant_type": "client_credentials"}
|
42
|
+
response = requests.post(f"{settings.base_url}/oauth/token", headers=headers, json=body)
|
43
|
+
response.raise_for_status()
|
44
|
+
self.credentials.access_token = response.json()["access_token"]
|
45
|
+
self.credentials.refresh_token = response.json()["refresh_token"]
|
46
|
+
self.credentials.expires_in = response.json()["expires_in"]
|
47
|
+
|
48
|
+
# Need to refresh token if expires in less than 10 minutes
|
49
|
+
parts = self.credentials.access_token.split(".")
|
50
|
+
if len(parts) != 3:
|
51
|
+
return Exception("Invalid JWT token format")
|
52
|
+
try:
|
53
|
+
claims_bytes = base64.urlsafe_b64decode(parts[1] + "=" * (-len(parts[1]) % 4))
|
54
|
+
claims = json.loads(claims_bytes)
|
55
|
+
except Exception as e:
|
56
|
+
return Exception(f"Failed to decode/parse JWT claims: {str(e)}")
|
57
|
+
|
58
|
+
exp_time = time.gmtime(claims["exp"])
|
59
|
+
# Refresh if token expires in less than 10 minutes
|
60
|
+
if time.time() + (10 * 60) > time.mktime(exp_time):
|
61
|
+
return self.do_refresh()
|
62
|
+
|
63
|
+
return None
|
64
|
+
|
65
|
+
def auth_flow(self, request: Request) -> Generator[Request, Response, None]:
|
66
|
+
err = self.refresh_if_needed()
|
67
|
+
if err:
|
68
|
+
return err
|
69
|
+
|
70
|
+
request.headers["X-Beamlit-Authorization"] = f"Bearer {self.credentials.access_token}"
|
71
|
+
request.headers["X-Beamlit-Workspace"] = self.workspace_name
|
72
|
+
yield request
|
73
|
+
|
74
|
+
def do_refresh(self) -> Optional[Exception]:
|
75
|
+
if not self.credentials.refresh_token:
|
76
|
+
return Exception("No refresh token to refresh")
|
77
|
+
|
78
|
+
url = f"{self.base_url}/oauth/token"
|
79
|
+
refresh_data = {
|
80
|
+
"grant_type": "refresh_token",
|
81
|
+
"refresh_token": self.credentials.refresh_token,
|
82
|
+
"device_code": self.credentials.device_code,
|
83
|
+
"client_id": "beamlit",
|
84
|
+
}
|
85
|
+
|
86
|
+
try:
|
87
|
+
response = post(url, json=refresh_data, headers={"Content-Type": "application/json"})
|
88
|
+
response.raise_for_status()
|
89
|
+
finalize_response = DeviceLoginFinalizeResponse(**response.json())
|
90
|
+
|
91
|
+
if not finalize_response.refresh_token:
|
92
|
+
finalize_response.refresh_token = self.credentials.refresh_token
|
93
|
+
|
94
|
+
from .credentials import Credentials, save_credentials
|
95
|
+
|
96
|
+
creds = Credentials(
|
97
|
+
access_token=finalize_response.access_token,
|
98
|
+
refresh_token=finalize_response.refresh_token,
|
99
|
+
expires_in=finalize_response.expires_in,
|
100
|
+
device_code=self.credentials.device_code,
|
101
|
+
)
|
102
|
+
|
103
|
+
self.credentials = creds
|
104
|
+
save_credentials(self.workspace_name, creds)
|
105
|
+
return None
|
106
|
+
|
107
|
+
except Exception as e:
|
108
|
+
return Exception(f"Failed to refresh token: {str(e)}")
|
@@ -1,9 +1,14 @@
|
|
1
1
|
from dataclasses import dataclass
|
2
|
+
from logging import getLogger
|
2
3
|
from pathlib import Path
|
3
4
|
from typing import List
|
4
5
|
|
5
6
|
import yaml
|
6
7
|
|
8
|
+
from beamlit.common.settings import Settings
|
9
|
+
|
10
|
+
logger = getLogger(__name__)
|
11
|
+
|
7
12
|
|
8
13
|
@dataclass
|
9
14
|
class Credentials:
|
@@ -12,6 +17,8 @@ class Credentials:
|
|
12
17
|
refresh_token: str = ""
|
13
18
|
expires_in: int = 0
|
14
19
|
device_code: str = ""
|
20
|
+
client_credentials: str = ""
|
21
|
+
|
15
22
|
|
16
23
|
@dataclass
|
17
24
|
class WorkspaceConfig:
|
@@ -67,15 +74,15 @@ def save_config(config: Config):
|
|
67
74
|
"name": ws.name,
|
68
75
|
"credentials": {
|
69
76
|
"access_token": ws.credentials.access_token,
|
70
|
-
"api_key": ws.credentials.api_key
|
71
|
-
}
|
77
|
+
"api_key": ws.credentials.api_key,
|
78
|
+
},
|
72
79
|
}
|
73
80
|
for ws in config.workspaces
|
74
81
|
],
|
75
82
|
"context": {
|
76
83
|
"workspace": config.context.workspace,
|
77
|
-
"environment": config.context.environment
|
78
|
-
}
|
84
|
+
"environment": config.context.environment,
|
85
|
+
},
|
79
86
|
}
|
80
87
|
|
81
88
|
home_dir = Path.home()
|
@@ -116,28 +123,35 @@ def load_credentials(workspace_name: str) -> Credentials:
|
|
116
123
|
return Credentials()
|
117
124
|
|
118
125
|
|
126
|
+
def load_credentials_from_settings(settings: Settings) -> Credentials:
|
127
|
+
return Credentials(
|
128
|
+
api_key=settings.authentication.api_key,
|
129
|
+
client_credentials=settings.authentication.client_credentials,
|
130
|
+
)
|
131
|
+
|
132
|
+
|
119
133
|
def create_home_dir_if_missing():
|
120
134
|
home_dir = Path.home()
|
121
135
|
if not home_dir:
|
122
|
-
|
136
|
+
logger.error("Error getting home directory")
|
123
137
|
return
|
124
138
|
|
125
139
|
credentials_dir = home_dir / ".beamlit"
|
126
140
|
credentials_file = credentials_dir / "credentials.json"
|
127
141
|
|
128
142
|
if credentials_file.exists():
|
129
|
-
|
143
|
+
logger.warning("You are already logged in. Enter a new API key to overwrite it.")
|
130
144
|
else:
|
131
145
|
try:
|
132
146
|
credentials_dir.mkdir(mode=0o700, parents=True, exist_ok=True)
|
133
147
|
except Exception as e:
|
134
|
-
|
148
|
+
logger.error(f"Error creating credentials directory: {e}")
|
135
149
|
|
136
150
|
|
137
151
|
def save_credentials(workspace_name: str, credentials: Credentials):
|
138
152
|
create_home_dir_if_missing()
|
139
153
|
if not credentials.access_token and not credentials.api_key:
|
140
|
-
|
154
|
+
logger.info("No credentials to save, error")
|
141
155
|
return
|
142
156
|
|
143
157
|
config = load_config()
|
@@ -2,7 +2,7 @@ import base64
|
|
2
2
|
import json
|
3
3
|
import time
|
4
4
|
from dataclasses import dataclass
|
5
|
-
from typing import Generator, Optional
|
5
|
+
from typing import Dict, Generator, Optional
|
6
6
|
|
7
7
|
from httpx import Auth, Request, Response, post
|
8
8
|
|
@@ -45,19 +45,30 @@ class BearerToken(Auth):
|
|
45
45
|
self.workspace_name = workspace_name
|
46
46
|
self.base_url = base_url
|
47
47
|
|
48
|
+
def get_headers(self) -> Dict[str, str]:
|
49
|
+
err = self.refresh_if_needed()
|
50
|
+
if err:
|
51
|
+
raise err
|
52
|
+
return {
|
53
|
+
"X-Beamlit-Authorization": f"Bearer {self.credentials.access_token}",
|
54
|
+
"X-Beamlit-Workspace": self.workspace_name,
|
55
|
+
}
|
56
|
+
|
48
57
|
def refresh_if_needed(self) -> Optional[Exception]:
|
49
58
|
# Need to refresh token if expires in less than 10 minutes
|
50
|
-
parts = self.credentials.access_token.split(
|
59
|
+
parts = self.credentials.access_token.split(".")
|
51
60
|
if len(parts) != 3:
|
52
61
|
return Exception("Invalid JWT token format")
|
53
62
|
|
54
63
|
try:
|
55
|
-
claims_bytes = base64.urlsafe_b64decode(
|
64
|
+
claims_bytes = base64.urlsafe_b64decode(
|
65
|
+
parts[1] + "=" * (-len(parts[1]) % 4)
|
66
|
+
)
|
56
67
|
claims = json.loads(claims_bytes)
|
57
68
|
except Exception as e:
|
58
69
|
return Exception(f"Failed to decode/parse JWT claims: {str(e)}")
|
59
70
|
|
60
|
-
exp_time = time.gmtime(claims[
|
71
|
+
exp_time = time.gmtime(claims["exp"])
|
61
72
|
# Refresh if token expires in less than 10 minutes
|
62
73
|
if time.time() + (10 * 60) > time.mktime(exp_time):
|
63
74
|
return self.do_refresh()
|
@@ -69,8 +80,10 @@ class BearerToken(Auth):
|
|
69
80
|
if err:
|
70
81
|
return err
|
71
82
|
|
72
|
-
request.headers[
|
73
|
-
|
83
|
+
request.headers["X-Beamlit-Authorization"] = (
|
84
|
+
f"Bearer {self.credentials.access_token}"
|
85
|
+
)
|
86
|
+
request.headers["X-Beamlit-Workspace"] = self.workspace_name
|
74
87
|
yield request
|
75
88
|
|
76
89
|
def do_refresh(self) -> Optional[Exception]:
|
@@ -82,17 +95,13 @@ class BearerToken(Auth):
|
|
82
95
|
"grant_type": "refresh_token",
|
83
96
|
"refresh_token": self.credentials.refresh_token,
|
84
97
|
"device_code": self.credentials.device_code,
|
85
|
-
"client_id": "beamlit"
|
98
|
+
"client_id": "beamlit",
|
86
99
|
}
|
87
100
|
|
88
101
|
try:
|
89
|
-
print(refresh_data)
|
90
102
|
response = post(
|
91
|
-
url,
|
92
|
-
json=refresh_data,
|
93
|
-
headers={"Content-Type": "application/json"}
|
103
|
+
url, json=refresh_data, headers={"Content-Type": "application/json"}
|
94
104
|
)
|
95
|
-
print(response.text)
|
96
105
|
response.raise_for_status()
|
97
106
|
finalize_response = DeviceLoginFinalizeResponse(**response.json())
|
98
107
|
|
@@ -100,11 +109,12 @@ class BearerToken(Auth):
|
|
100
109
|
finalize_response.refresh_token = self.credentials.refresh_token
|
101
110
|
|
102
111
|
from .credentials import Credentials, save_credentials
|
112
|
+
|
103
113
|
creds = Credentials(
|
104
114
|
access_token=finalize_response.access_token,
|
105
115
|
refresh_token=finalize_response.refresh_token,
|
106
116
|
expires_in=finalize_response.expires_in,
|
107
|
-
device_code=self.credentials.device_code
|
117
|
+
device_code=self.credentials.device_code,
|
108
118
|
)
|
109
119
|
|
110
120
|
self.credentials = creds
|
@@ -0,0 +1,13 @@
|
|
1
|
+
from .logger import init as init_logger
|
2
|
+
from .secrets import Secret
|
3
|
+
from .settings import Settings, get_settings, init_agent
|
4
|
+
from .utils import copy_folder
|
5
|
+
|
6
|
+
__all__ = [
|
7
|
+
"Secret",
|
8
|
+
"Settings",
|
9
|
+
"get_settings",
|
10
|
+
"init_agent",
|
11
|
+
"copy_folder",
|
12
|
+
"init_logger",
|
13
|
+
]
|
@@ -0,0 +1,183 @@
|
|
1
|
+
from typing import Tuple
|
2
|
+
|
3
|
+
from beamlit.common.settings import Settings, get_settings
|
4
|
+
from beamlit.models.agent_deployment import AgentDeployment
|
5
|
+
from beamlit.models.function_deployment import FunctionDeployment
|
6
|
+
from beamlit.models.function_kit import FunctionKit
|
7
|
+
|
8
|
+
|
9
|
+
def get_titles_name(name: str) -> str:
|
10
|
+
return name.title().replace("-", "").replace("_", "")
|
11
|
+
|
12
|
+
|
13
|
+
def generate_kit_function_code(settings: Settings, function: FunctionDeployment, kit: FunctionKit) -> Tuple[str, str]:
|
14
|
+
export_code = ""
|
15
|
+
code = ""
|
16
|
+
for kit in kit:
|
17
|
+
fn = FunctionDeployment(
|
18
|
+
function=kit.name,
|
19
|
+
workspace=settings.workspace,
|
20
|
+
parameters=kit.parameters,
|
21
|
+
description=kit.description,
|
22
|
+
)
|
23
|
+
new_code, export = generate_function_code(settings, fn, force_name_in_endpoint=function.function, kit=True)
|
24
|
+
code += new_code
|
25
|
+
export_code += export
|
26
|
+
return code, export_code
|
27
|
+
|
28
|
+
|
29
|
+
def generate_function_code(
|
30
|
+
settings: Settings, function: FunctionDeployment, force_name_in_endpoint: str = "", kit: bool = False
|
31
|
+
) -> Tuple[str, str]:
|
32
|
+
name = get_titles_name(function.function)
|
33
|
+
if function.parameters and len(function.parameters) > 0:
|
34
|
+
args_list = ", ".join(f"{param.name}: str" for param in function.parameters)
|
35
|
+
args_list += ", "
|
36
|
+
else:
|
37
|
+
args_list = ""
|
38
|
+
args_schema = ""
|
39
|
+
if function.parameters:
|
40
|
+
for param in function.parameters:
|
41
|
+
args_schema += f'{param.name}: str = Field(description="""{param.description}""")\n '
|
42
|
+
if len(args_schema) == 0:
|
43
|
+
args_schema = "pass"
|
44
|
+
|
45
|
+
# TODO: add return direct in function configuration
|
46
|
+
return_direct = False
|
47
|
+
endpoint_name = force_name_in_endpoint or function.function
|
48
|
+
body = "{}"
|
49
|
+
if function.parameters:
|
50
|
+
body = f'{", ".join(f'"{param.name}": {param.name}' for param in function.parameters)}'
|
51
|
+
if kit is True:
|
52
|
+
has_name = False
|
53
|
+
if function.parameters:
|
54
|
+
for param in function.parameters:
|
55
|
+
if param.name == "name":
|
56
|
+
has_name = True
|
57
|
+
break
|
58
|
+
if not has_name:
|
59
|
+
if len(body) > 0:
|
60
|
+
body += ", "
|
61
|
+
body += f'"name": "{function.function}"'
|
62
|
+
return (
|
63
|
+
f'''
|
64
|
+
|
65
|
+
class Beamlit{name}Input(BaseModel):
|
66
|
+
{args_schema}
|
67
|
+
|
68
|
+
class Beamlit{name}(BaseTool):
|
69
|
+
name: str = "beamlit_{function.function.replace("-", "_")}"
|
70
|
+
description: str = """{function.description}"""
|
71
|
+
args_schema: Type[BaseModel] = Beamlit{name}Input
|
72
|
+
|
73
|
+
response_format: Literal["content_and_artifact"] = "content_and_artifact"
|
74
|
+
return_direct: bool = {return_direct}
|
75
|
+
|
76
|
+
def _run(self, {args_list} run_manager: Optional[CallbackManagerForToolRun] = None) -> Tuple[Union[List[Dict[str, str]], str], Dict]:
|
77
|
+
try:
|
78
|
+
params = self.metadata.get("params", {{}})
|
79
|
+
response = run_client.run("function", "{endpoint_name}", settings.environment, "POST", json={{{body}}})
|
80
|
+
if response.status_code >= 400:
|
81
|
+
logger.error(f"Failed to run function {name}, {{response.status_code}}::{{response.text}}")
|
82
|
+
raise Exception(f"Failed to run function {name}, {{response.status_code}}::{{response.text}}")
|
83
|
+
return response.json(), {{}}
|
84
|
+
except Exception as e:
|
85
|
+
return repr(e), {{}}
|
86
|
+
''',
|
87
|
+
f"Beamlit{get_titles_name(function.function)},",
|
88
|
+
)
|
89
|
+
|
90
|
+
|
91
|
+
def generate_chain_code(settings: Settings, agent: AgentDeployment) -> Tuple[str, str]:
|
92
|
+
name = get_titles_name(agent.agent)
|
93
|
+
# TODO: add return direct in agent configuration
|
94
|
+
return_direct = False
|
95
|
+
return (
|
96
|
+
f'''
|
97
|
+
class BeamlitChain{name}Input(BaseModel):
|
98
|
+
input: str = Field(description='{agent.description}')
|
99
|
+
|
100
|
+
class BeamlitChain{name}(BaseTool):
|
101
|
+
name: str = "beamlit_chain_{agent.agent.replace("-", "_")}"
|
102
|
+
description: str = """{agent.description}"""
|
103
|
+
args_schema: Type[BaseModel] = BeamlitChain{name}Input
|
104
|
+
|
105
|
+
response_format: Literal["content_and_artifact"] = "content_and_artifact"
|
106
|
+
return_direct: bool = {return_direct}
|
107
|
+
|
108
|
+
def _run(
|
109
|
+
self,
|
110
|
+
input: str,
|
111
|
+
run_manager: Optional[CallbackManagerForToolRun] = None,
|
112
|
+
) -> Tuple[Union[List[Dict[str, str]], str], Dict]:
|
113
|
+
try:
|
114
|
+
params = self.metadata.get("params", {{}})
|
115
|
+
response = run_client.run("agent", "{agent.agent}", settings.environment, "POST", json={{"input": input}})
|
116
|
+
if response.status_code >= 400:
|
117
|
+
logger.error(f"Failed to run tool {agent.agent}, {{response.status_code}}::{{response.text}}")
|
118
|
+
raise Exception(f"Failed to run tool {agent.agent}, {{response.status_code}}::{{response.text}}")
|
119
|
+
if response.headers.get("Content-Type") == "application/json":
|
120
|
+
return response.json(), {{}}
|
121
|
+
else:
|
122
|
+
return response.text, {{}}
|
123
|
+
except Exception as e:
|
124
|
+
return repr(e), {{}}
|
125
|
+
''',
|
126
|
+
f"BeamlitChain{name},",
|
127
|
+
)
|
128
|
+
|
129
|
+
|
130
|
+
def generate(destination: str, dry_run: bool = False):
|
131
|
+
imports = """from logging import getLogger
|
132
|
+
from typing import Dict, List, Literal, Optional, Tuple, Type, Union
|
133
|
+
|
134
|
+
from langchain_core.callbacks import CallbackManagerForToolRun
|
135
|
+
from langchain_core.tools import BaseTool
|
136
|
+
from pydantic import BaseModel, Field
|
137
|
+
from beamlit.authentication import (RunClientWithCredentials,
|
138
|
+
load_credentials_from_settings,
|
139
|
+
new_client_with_credentials)
|
140
|
+
from beamlit.common.settings import get_settings
|
141
|
+
from beamlit.run import RunClient
|
142
|
+
|
143
|
+
logger = getLogger(__name__)
|
144
|
+
settings = get_settings()
|
145
|
+
credentials = load_credentials_from_settings(settings)
|
146
|
+
|
147
|
+
client_config = RunClientWithCredentials(
|
148
|
+
credentials=credentials,
|
149
|
+
workspace=settings.workspace,
|
150
|
+
)
|
151
|
+
client = new_client_with_credentials(client_config)
|
152
|
+
run_client = RunClient(client=client)
|
153
|
+
"""
|
154
|
+
settings = get_settings()
|
155
|
+
export_code = "\n\nfunctions = ["
|
156
|
+
export_chain = "\n\nchains = ["
|
157
|
+
code = imports
|
158
|
+
if settings.agent.functions and len(settings.agent.functions) > 0:
|
159
|
+
for function_config in settings.agent.functions:
|
160
|
+
if function_config.kit and len(function_config.kit) > 0:
|
161
|
+
new_code, export = generate_kit_function_code(settings, function_config, function_config.kit)
|
162
|
+
code += new_code
|
163
|
+
export_code += export
|
164
|
+
else:
|
165
|
+
new_code, export = generate_function_code(settings, function_config)
|
166
|
+
code += new_code
|
167
|
+
export_code += export
|
168
|
+
if settings.agent.chain and len(settings.agent.chain) > 0:
|
169
|
+
for agent in settings.agent.chain:
|
170
|
+
new_code, export = generate_chain_code(settings, agent)
|
171
|
+
code += new_code
|
172
|
+
export_chain += export
|
173
|
+
if settings.agent.functions and len(settings.agent.functions) > 0:
|
174
|
+
export_code = export_code[:-1]
|
175
|
+
export_code += "]"
|
176
|
+
if settings.agent.chain and len(settings.agent.chain) > 0:
|
177
|
+
export_chain = export_chain[:-1]
|
178
|
+
export_chain += "]"
|
179
|
+
content = code + export_code + export_chain
|
180
|
+
if not dry_run:
|
181
|
+
with open(destination, "w") as f:
|
182
|
+
f.write(content)
|
183
|
+
return content
|
beamlit/common/logger.py
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
import logging
|
2
|
+
|
3
|
+
|
4
|
+
class ColoredFormatter(logging.Formatter):
|
5
|
+
COLORS = {
|
6
|
+
"DEBUG": "\033[1;36m", # Cyan
|
7
|
+
"INFO": "\033[1;32m", # Green
|
8
|
+
"WARNING": "\033[1;33m", # Yellow
|
9
|
+
"ERROR": "\033[1;31m", # Red
|
10
|
+
"CRITICAL": "\033[1;41m", # Red background
|
11
|
+
}
|
12
|
+
|
13
|
+
def format(self, record):
|
14
|
+
color = self.COLORS.get(record.levelname, "\033[0m")
|
15
|
+
record.levelname = f"{color}{record.levelname}\033[0m"
|
16
|
+
return super().format(record)
|
17
|
+
|
18
|
+
|
19
|
+
def init(log_level: str):
|
20
|
+
logging.getLogger("uvicorn.access").handlers.clear()
|
21
|
+
logging.getLogger("uvicorn.access").propagate = False
|
22
|
+
logging.getLogger("uvicorn.error").handlers.clear()
|
23
|
+
logging.getLogger("uvicorn.error").propagate = False
|
24
|
+
logging.getLogger("httpx").handlers.clear()
|
25
|
+
logging.getLogger("httpx").propagate = False
|
26
|
+
|
27
|
+
handler = logging.StreamHandler()
|
28
|
+
handler.setFormatter(ColoredFormatter("%(levelname)s:\t %(name)s - %(message)s"))
|
29
|
+
logging.basicConfig(level=log_level, handlers=[handler])
|