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.
Files changed (221) hide show
  1. beamlit/agents/__init__.py +4 -0
  2. beamlit/agents/chat.py +87 -0
  3. beamlit/agents/decorator.py +147 -0
  4. beamlit/api/agents/create_agent.py +14 -9
  5. beamlit/api/agents/delete_agent_history.py +159 -0
  6. beamlit/api/agents/get_agent_deployment_logs.py +11 -11
  7. beamlit/api/{authentication_providers/list_organizations_for_authentication_provider.py → agents/get_agent_environment_logs.py} +30 -42
  8. beamlit/api/{authentication_providers/get_model_with_repo_for_authentication_provider.py → agents/get_agent_history.py} +43 -68
  9. beamlit/api/agents/list_agent_deployment_history.py +11 -11
  10. beamlit/api/agents/list_agent_deployments.py +11 -11
  11. beamlit/api/agents/list_agent_history.py +151 -0
  12. beamlit/api/agents/list_agents.py +11 -11
  13. beamlit/api/agents/put_agent_history.py +185 -0
  14. beamlit/api/agents/update_agent.py +14 -9
  15. beamlit/api/environments/list_environments.py +11 -11
  16. beamlit/api/functions/create_function.py +14 -9
  17. beamlit/api/functions/get_function_deployment_logs.py +11 -11
  18. beamlit/api/functions/get_function_environment_logs.py +151 -0
  19. beamlit/api/functions/list_function_deployments.py +11 -11
  20. beamlit/api/functions/list_functions.py +11 -11
  21. beamlit/api/functions/update_function.py +14 -9
  22. beamlit/api/history/get_agents_history.py +11 -11
  23. beamlit/api/history/list_agents_history.py +11 -11
  24. beamlit/api/integrations/create_integration_connection.py +167 -0
  25. beamlit/api/integrations/delete_integration_connection.py +158 -0
  26. beamlit/api/integrations/get_integration.py +97 -0
  27. beamlit/api/integrations/get_integration_connection.py +154 -0
  28. beamlit/api/integrations/get_integration_connection_model.py +97 -0
  29. beamlit/api/integrations/get_integration_model.py +97 -0
  30. beamlit/api/integrations/list_integration_connection_models.py +97 -0
  31. beamlit/api/integrations/list_integration_connections.py +139 -0
  32. beamlit/api/integrations/list_integration_models.py +97 -0
  33. beamlit/api/integrations/update_integration_connection.py +180 -0
  34. beamlit/api/invitations/list_all_pending_invitations.py +11 -11
  35. beamlit/api/locations/list_locations.py +11 -11
  36. beamlit/api/model_providers/list_model_providers.py +11 -11
  37. beamlit/api/models/get_model_deployment_logs.py +11 -11
  38. beamlit/api/{authentication_providers/list_models_for_authentication_provider.py → models/get_model_environment_logs.py} +30 -38
  39. beamlit/api/models/list_model_deployments.py +11 -11
  40. beamlit/api/models/list_models.py +11 -11
  41. beamlit/api/policies/list_policies.py +11 -11
  42. beamlit/api/service_accounts/get_workspace_service_accounts.py +11 -11
  43. beamlit/api/service_accounts/list_api_keys_for_service_account.py +11 -11
  44. beamlit/api/store/list_store_agents.py +11 -11
  45. beamlit/api/store/list_store_functions.py +11 -11
  46. beamlit/api/workspaces/list_workspace_users.py +11 -11
  47. beamlit/api/workspaces/list_workspaces.py +11 -11
  48. beamlit/authentication/__init__.py +29 -8
  49. beamlit/authentication/apikey.py +8 -2
  50. beamlit/authentication/authentication.py +66 -3
  51. beamlit/authentication/clientcredentials.py +108 -0
  52. beamlit/authentication/credentials.py +22 -8
  53. beamlit/authentication/device_mode.py +23 -13
  54. beamlit/common/__init__.py +13 -0
  55. beamlit/common/generate.py +183 -0
  56. beamlit/common/logger.py +29 -0
  57. beamlit/common/secrets.py +11 -0
  58. beamlit/common/settings.py +156 -0
  59. beamlit/common/utils.py +15 -0
  60. beamlit/functions/__init__.py +5 -0
  61. beamlit/functions/decorator.py +90 -0
  62. beamlit/functions/github/__init__.py +3 -0
  63. beamlit/functions/github/github.py +21 -0
  64. beamlit/functions/github/kit/__init__.py +7 -0
  65. beamlit/functions/github/kit/pull_request.py +51 -0
  66. beamlit/functions/math/__init__.py +3 -0
  67. beamlit/functions/math/math.py +40 -0
  68. beamlit/functions/search/__init__.py +3 -0
  69. beamlit/functions/search/search.py +15 -0
  70. beamlit/models/__init__.py +24 -0
  71. beamlit/models/acl.py +4 -2
  72. beamlit/models/agent.py +5 -3
  73. beamlit/models/agent_chain.py +4 -2
  74. beamlit/models/agent_configuration.py +4 -2
  75. beamlit/models/agent_deployment.py +41 -28
  76. beamlit/models/agent_deployment_configuration.py +4 -2
  77. beamlit/models/agent_deployment_configuration_type_0.py +43 -0
  78. beamlit/models/agent_deployment_history.py +7 -5
  79. beamlit/models/agent_deployment_history_event.py +11 -9
  80. beamlit/models/agent_deployment_pod_template.py +4 -2
  81. beamlit/models/agent_deployment_pod_template_type_0.py +43 -0
  82. beamlit/models/agent_history.py +165 -0
  83. beamlit/models/agent_history_event.py +131 -0
  84. beamlit/models/agent_metadata.py +144 -0
  85. beamlit/models/agent_release.py +4 -2
  86. beamlit/models/agent_spec.py +248 -0
  87. beamlit/models/agent_with_deployments.py +176 -0
  88. beamlit/models/api_key.py +4 -2
  89. beamlit/models/authentication_provider_model.py +8 -6
  90. beamlit/models/authentication_provider_organization.py +4 -2
  91. beamlit/models/configuration.py +12 -10
  92. beamlit/models/continent.py +4 -2
  93. beamlit/models/core_spec.py +185 -0
  94. beamlit/models/country.py +4 -2
  95. beamlit/models/create_api_key_for_service_account_body.py +4 -2
  96. beamlit/models/create_workspace_service_account_body.py +4 -2
  97. beamlit/models/create_workspace_service_account_response_200.py +4 -2
  98. beamlit/models/delete_workspace_service_account_response_200.py +4 -2
  99. beamlit/models/deployment_configuration.py +4 -2
  100. beamlit/models/deployment_configurations.py +22 -7
  101. beamlit/models/deployment_serverless_config.py +4 -2
  102. beamlit/models/deployment_serverless_config_type_0.py +220 -0
  103. beamlit/models/environment.py +9 -7
  104. beamlit/models/environment_metrics.py +26 -5
  105. beamlit/models/environment_spec.py +61 -0
  106. beamlit/models/flavor.py +11 -9
  107. beamlit/models/function.py +5 -3
  108. beamlit/models/function_configuration.py +4 -2
  109. beamlit/models/function_deployment.py +33 -20
  110. beamlit/models/function_deployment_configuration.py +4 -2
  111. beamlit/models/function_deployment_configuration_type_0.py +43 -0
  112. beamlit/models/function_deployment_pod_template.py +4 -2
  113. beamlit/models/function_deployment_pod_template_type_0.py +43 -0
  114. beamlit/models/function_kit.py +7 -5
  115. beamlit/models/function_metadata.py +144 -0
  116. beamlit/models/function_provider_ref.py +4 -2
  117. beamlit/models/function_release.py +4 -2
  118. beamlit/models/function_spec.py +246 -0
  119. beamlit/models/function_with_deployments.py +176 -0
  120. beamlit/models/get_workspace_service_accounts_response_200_item.py +4 -2
  121. beamlit/models/increase_and_rate_metric.py +104 -0
  122. beamlit/models/integration.py +198 -0
  123. beamlit/models/integration_config.py +45 -0
  124. beamlit/models/integration_connection.py +198 -0
  125. beamlit/models/integration_connection_config.py +45 -0
  126. beamlit/models/integration_connection_secret.py +61 -0
  127. beamlit/models/integration_connection_spec.py +99 -0
  128. beamlit/models/integration_model.py +144 -0
  129. beamlit/models/integration_secret.py +61 -0
  130. beamlit/models/invite_workspace_user_body.py +4 -2
  131. beamlit/models/labels_type_0.py +4 -2
  132. beamlit/models/location.py +7 -5
  133. beamlit/models/location_response.py +7 -5
  134. beamlit/models/metadata.py +135 -0
  135. beamlit/models/{labels.py → metadata_labels.py} +5 -5
  136. beamlit/models/metric.py +4 -2
  137. beamlit/models/metrics.py +68 -25
  138. beamlit/models/model.py +5 -3
  139. beamlit/models/model_deployment.py +27 -14
  140. beamlit/models/model_deployment_log.py +4 -2
  141. beamlit/models/model_deployment_metrics.py +11 -9
  142. beamlit/models/model_deployment_metrics_inference_per_second_per_region.py +7 -5
  143. beamlit/models/model_deployment_metrics_query_per_second_per_region_per_code.py +5 -3
  144. beamlit/models/model_deployment_pod_template.py +4 -2
  145. beamlit/models/model_deployment_pod_template_type_0.py +43 -0
  146. beamlit/models/model_metadata.py +144 -0
  147. beamlit/models/model_metrics.py +8 -6
  148. beamlit/models/model_provider.py +13 -11
  149. beamlit/models/model_provider_ref.py +4 -2
  150. beamlit/models/model_release.py +4 -2
  151. beamlit/models/model_spec.py +194 -0
  152. beamlit/models/model_with_deployments.py +8 -6
  153. beamlit/models/owner_fields.py +68 -0
  154. beamlit/models/pending_invitation.py +4 -2
  155. beamlit/models/pending_invitation_accept.py +5 -3
  156. beamlit/models/pending_invitation_render.py +7 -5
  157. beamlit/models/pending_invitation_render_invited_by.py +4 -2
  158. beamlit/models/pending_invitation_render_workspace.py +4 -2
  159. beamlit/models/pending_invitation_workspace_details.py +8 -6
  160. beamlit/models/pod_template_spec.py +43 -0
  161. beamlit/models/policy.py +22 -20
  162. beamlit/models/policy_location.py +11 -9
  163. beamlit/models/policy_spec.py +125 -0
  164. beamlit/models/provider_config.py +9 -16
  165. beamlit/models/qps.py +4 -2
  166. beamlit/models/resource_deployment_log.py +4 -2
  167. beamlit/models/resource_deployment_metrics.py +119 -9
  168. beamlit/models/resource_deployment_metrics_inference_per_region.py +77 -0
  169. beamlit/models/resource_deployment_metrics_inference_per_region_type_0.py +79 -0
  170. beamlit/models/resource_deployment_metrics_inference_per_second_per_region.py +7 -5
  171. beamlit/models/resource_deployment_metrics_inference_per_second_per_region_type_0.py +79 -0
  172. beamlit/models/resource_deployment_metrics_query_per_region_per_code.py +75 -0
  173. beamlit/models/resource_deployment_metrics_query_per_region_per_code_type_0.py +73 -0
  174. beamlit/models/resource_deployment_metrics_query_per_second_per_region_per_code.py +5 -3
  175. beamlit/models/resource_deployment_metrics_query_per_second_per_region_per_code_type_0.py +73 -0
  176. beamlit/models/resource_environment_metrics.py +210 -0
  177. beamlit/models/resource_environment_metrics_inference_per_second_per_region.py +79 -0
  178. beamlit/models/resource_environment_metrics_query_per_second_per_region_per_code.py +73 -0
  179. beamlit/models/resource_log.py +68 -0
  180. beamlit/models/resource_metrics.py +44 -6
  181. beamlit/models/runtime.py +25 -23
  182. beamlit/models/runtime_readiness_probe.py +4 -2
  183. beamlit/models/runtime_readiness_probe_type_0.py +43 -0
  184. beamlit/models/runtime_resources.py +4 -2
  185. beamlit/models/runtime_type_0.py +111 -0
  186. beamlit/models/runtime_type_0_readiness_probe.py +43 -0
  187. beamlit/models/runtime_type_0_readiness_probe_type_0.py +43 -0
  188. beamlit/models/runtime_type_0_resources.py +59 -0
  189. beamlit/models/serverless_config.py +4 -2
  190. beamlit/models/spec_configuration.py +68 -0
  191. beamlit/models/standard_fields_dynamo_db.py +4 -2
  192. beamlit/models/store_agent.py +8 -6
  193. beamlit/models/store_agent_configuration.py +4 -2
  194. beamlit/models/store_agent_labels.py +4 -2
  195. beamlit/models/store_agent_labels_type_0.py +43 -0
  196. beamlit/models/store_configuration.py +18 -16
  197. beamlit/models/store_configuration_option.py +4 -2
  198. beamlit/models/store_function.py +14 -12
  199. beamlit/models/store_function_configuration.py +4 -2
  200. beamlit/models/store_function_kit.py +7 -5
  201. beamlit/models/store_function_labels.py +4 -2
  202. beamlit/models/store_function_labels_type_0.py +43 -0
  203. beamlit/models/store_function_parameter.py +11 -9
  204. beamlit/models/time_fields.py +68 -0
  205. beamlit/models/update_workspace_service_account_body.py +4 -2
  206. beamlit/models/update_workspace_service_account_response_200.py +4 -2
  207. beamlit/models/update_workspace_user_role_body.py +4 -2
  208. beamlit/models/websocket_channel.py +86 -0
  209. beamlit/models/workspace.py +5 -3
  210. beamlit/models/workspace_labels.py +4 -2
  211. beamlit/models/workspace_user.py +4 -2
  212. beamlit/run.py +49 -0
  213. beamlit/serve/app.py +78 -0
  214. beamlit/serve/middlewares/__init__.py +4 -0
  215. beamlit/serve/middlewares/accesslog.py +14 -0
  216. beamlit/serve/middlewares/processtime.py +12 -0
  217. {beamlit-0.0.18.dist-info → beamlit-0.0.20.dist-info}/METADATA +11 -2
  218. beamlit-0.0.20.dist-info/RECORD +301 -0
  219. beamlit-0.0.18.dist-info/RECORD +0 -211
  220. /beamlit/api/{authentication_providers → integrations}/__init__.py +0 -0
  221. {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 (PublicProvider, RunClientWithCredentials,
3
- new_client_with_credentials)
4
- from .credentials import (Config, ContextConfig, Credentials, WorkspaceConfig,
5
- load_credentials)
6
- from .device_mode import (BearerToken, DeviceLogin, DeviceLoginFinalizeRequest,
7
- DeviceLoginFinalizeResponse, DeviceLoginResponse)
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
+ )
@@ -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['X-Beamlit-Api-Key'] = self.credentials.api_key
13
- request.headers['X-Beamlit-Workspace'] = self.workspace_name
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 .credentials import Credentials
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
- print("Error getting home directory")
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
- print("You are already logged in. Enter a new API key to overwrite it.")
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
- print(f"Error creating credentials directory: {e}")
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
- print("No credentials to save, error")
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(parts[1] + '=' * (-len(parts[1]) % 4))
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['exp'])
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['X-Beamlit-Authorization'] = f'Bearer {self.credentials.access_token}'
73
- request.headers['X-Beamlit-Workspace'] = self.workspace_name
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
@@ -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])