beamlit 0.0.33rc49__py3-none-any.whl → 0.0.34__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. beamlit/agents/__init__.py +2 -1
  2. beamlit/agents/chat.py +16 -4
  3. beamlit/agents/decorator.py +68 -155
  4. beamlit/agents/thread.py +14 -0
  5. beamlit/api/workspaces/workspace_quotas_request.py +97 -0
  6. beamlit/authentication/clientcredentials.py +5 -3
  7. beamlit/authentication/device_mode.py +4 -4
  8. beamlit/common/__init__.py +1 -2
  9. beamlit/common/instrumentation.py +202 -34
  10. beamlit/common/settings.py +7 -64
  11. beamlit/deploy/deploy.py +64 -60
  12. beamlit/deploy/format.py +10 -0
  13. beamlit/functions/__init__.py +2 -2
  14. beamlit/functions/decorator.py +149 -1
  15. beamlit/functions/github/github.py +0 -1
  16. beamlit/models/__init__.py +51 -11
  17. beamlit/models/agent.py +27 -15
  18. beamlit/models/agent_metadata.py +1 -1
  19. beamlit/models/agent_render.py +45 -0
  20. beamlit/models/agent_spec.py +32 -5
  21. beamlit/models/core_event.py +88 -0
  22. beamlit/models/core_spec.py +14 -5
  23. beamlit/models/core_spec_configurations.py +1 -1
  24. beamlit/models/core_status.py +3 -20
  25. beamlit/models/environment.py +2 -2
  26. beamlit/models/environment_metadata.py +1 -1
  27. beamlit/models/function.py +27 -15
  28. beamlit/models/function_metadata.py +1 -1
  29. beamlit/models/function_render.py +45 -0
  30. beamlit/models/function_spec.py +14 -5
  31. beamlit/models/histogram_bucket.py +79 -0
  32. beamlit/models/histogram_stats.py +88 -0
  33. beamlit/models/increase_and_rate_metric.py +0 -9
  34. beamlit/models/integration_connection.py +2 -2
  35. beamlit/models/integration_connection_spec.py +11 -2
  36. beamlit/models/integration_repository.py +88 -0
  37. beamlit/models/last_n_requests_metric.py +88 -0
  38. beamlit/models/latency_metric.py +124 -0
  39. beamlit/models/metadata.py +1 -1
  40. beamlit/models/metric.py +18 -9
  41. beamlit/models/metrics.py +81 -46
  42. beamlit/models/metrics_models.py +45 -0
  43. beamlit/models/metrics_request_total_per_code.py +45 -0
  44. beamlit/models/metrics_rps_per_code.py +45 -0
  45. beamlit/models/model.py +27 -15
  46. beamlit/models/model_metadata.py +1 -1
  47. beamlit/models/model_provider.py +2 -2
  48. beamlit/models/model_render.py +45 -0
  49. beamlit/models/model_spec.py +14 -14
  50. beamlit/models/pending_invitation_accept.py +1 -1
  51. beamlit/models/pending_invitation_render.py +3 -3
  52. beamlit/models/policy.py +2 -2
  53. beamlit/models/provider_config.py +1 -1
  54. beamlit/models/repository.py +70 -0
  55. beamlit/models/repository_type_0.py +70 -0
  56. beamlit/models/request_duration_over_time_metric.py +97 -0
  57. beamlit/models/request_duration_over_time_metrics.py +74 -0
  58. beamlit/models/request_total_by_origin_metric.py +103 -0
  59. beamlit/models/request_total_by_origin_metric_request_total_by_origin.py +45 -0
  60. beamlit/models/request_total_by_origin_metric_request_total_by_origin_and_code.py +45 -0
  61. beamlit/models/request_total_metric.py +115 -0
  62. beamlit/models/request_total_metric_request_total_per_code.py +45 -0
  63. beamlit/models/request_total_metric_rps_per_code.py +45 -0
  64. beamlit/models/resource_deployment_metrics.py +6 -4
  65. beamlit/models/resource_deployment_metrics_query_per_second_per_region_per_code.py +1 -1
  66. beamlit/models/resource_environment_metrics.py +155 -75
  67. beamlit/models/resource_environment_metrics_request_total_per_code.py +45 -0
  68. beamlit/models/resource_environment_metrics_rps_per_code.py +45 -0
  69. beamlit/models/resource_metrics.py +1 -1
  70. beamlit/models/runtime.py +2 -2
  71. beamlit/models/store_agent.py +1 -1
  72. beamlit/models/store_function.py +1 -1
  73. beamlit/models/token_rate_metric.py +88 -0
  74. beamlit/models/token_rate_metrics.py +106 -0
  75. beamlit/models/token_total_metric.py +106 -0
  76. beamlit/models/workspace.py +17 -8
  77. beamlit/serve/app.py +9 -13
  78. {beamlit-0.0.33rc49.dist-info → beamlit-0.0.34.dist-info}/METADATA +21 -3
  79. {beamlit-0.0.33rc49.dist-info → beamlit-0.0.34.dist-info}/RECORD +80 -52
  80. beamlit/common/generate.py +0 -196
  81. {beamlit-0.0.33rc49.dist-info → beamlit-0.0.34.dist-info}/WHEEL +0 -0
@@ -1,4 +1,5 @@
1
1
  from .chat import get_chat_model
2
2
  from .decorator import agent
3
+ from .thread import get_default_thread
3
4
 
4
- __all__ = ["agent", "get_chat_model"]
5
+ __all__ = ["agent", "get_chat_model", "get_default_thread"]
beamlit/agents/chat.py CHANGED
@@ -1,5 +1,9 @@
1
1
  from logging import getLogger
2
+ from typing import Tuple, Union
2
3
 
4
+ from langchain_core.language_models import BaseChatModel
5
+
6
+ from beamlit.api.models import get_model
3
7
  from beamlit.authentication import get_authentication_headers, new_client
4
8
  from beamlit.common.settings import get_settings
5
9
  from beamlit.models import Model
@@ -39,11 +43,17 @@ def get_cohere_chat_model(**kwargs):
39
43
 
40
44
  return ChatCohere(**kwargs)
41
45
 
42
- def get_chat_model(name: str, agent_model: Model):
46
+ def get_chat_model(name: str, agent_model: Union[Model, None] = None) -> Tuple[BaseChatModel, str, str]:
43
47
  settings = get_settings()
44
48
  client = new_client()
45
49
 
46
- environment = (agent_model.metadata and agent_model.metadata.environment) or settings.environment
50
+ if agent_model is None:
51
+ try:
52
+ agent_model = get_model.sync(name, client=client, environment=settings.environment)
53
+ except Exception:
54
+ logger.warning(f"Model {name} not found, defaulting to gpt-4o-mini")
55
+
56
+ environment = (agent_model and agent_model.metadata and agent_model.metadata.environment) or settings.environment
47
57
  headers = get_authentication_headers(settings)
48
58
  headers["X-Beamlit-Environment"] = environment
49
59
 
@@ -84,7 +94,8 @@ def get_chat_model(name: str, agent_model: Model):
84
94
  }
85
95
 
86
96
  provider = (
87
- agent_model.spec
97
+ agent_model
98
+ and agent_model.spec
88
99
  and agent_model.spec.runtime
89
100
  and agent_model.spec.runtime.type_
90
101
  )
@@ -93,7 +104,8 @@ def get_chat_model(name: str, agent_model: Model):
93
104
  provider = "openai"
94
105
 
95
106
  model = (
96
- agent_model.spec
107
+ agent_model
108
+ and agent_model.spec
97
109
  and agent_model.spec.runtime
98
110
  and agent_model.spec.runtime.model
99
111
  )
@@ -1,122 +1,24 @@
1
1
  # Import necessary modules
2
- import ast
3
- import asyncio
4
2
  import functools
5
- import importlib
6
- import os
3
+ import inspect
7
4
  from logging import getLogger
8
5
 
9
- from langchain_core.tools import Tool
10
6
  from langgraph.checkpoint.memory import MemorySaver
11
7
  from langgraph.prebuilt import create_react_agent
12
8
 
13
- from beamlit.api.models import get_model
9
+ from beamlit.api.models import get_model, list_models
14
10
  from beamlit.authentication import new_client
15
- from beamlit.common import slugify
16
- from beamlit.common.settings import get_settings, init
11
+ from beamlit.common.settings import init
17
12
  from beamlit.errors import UnexpectedStatus
18
- from beamlit.functions.mcp.mcp import MCPClient, MCPToolkit
19
- from beamlit.functions.remote.remote import RemoteToolkit
13
+ from beamlit.functions import get_functions
20
14
  from beamlit.models import Agent, AgentMetadata, AgentSpec
21
15
 
22
- from .chain import ChainToolkit
23
16
  from .chat import get_chat_model
24
17
 
25
18
 
26
- def get_functions(client, dir="src/functions", from_decorator="function", remote_functions_empty=True):
27
- functions = []
28
- logger = getLogger(__name__)
29
- settings = get_settings()
30
-
31
- # Walk through all Python files in functions directory and subdirectories
32
- if not os.path.exists(dir):
33
- if remote_functions_empty:
34
- logger.warn(f"Functions directory {dir} not found")
35
- return []
36
- for root, _, files in os.walk(dir):
37
- for file in files:
38
- if file.endswith(".py"):
39
- file_path = os.path.join(root, file)
40
- # Read and compile the file content
41
- with open(file_path) as f:
42
- try:
43
- file_content = f.read()
44
- # Parse the file content to find decorated functions
45
- tree = ast.parse(file_content)
46
-
47
- # Look for function definitions with decorators
48
- for node in ast.walk(tree):
49
- if (
50
- not isinstance(node, ast.FunctionDef)
51
- and not isinstance(node, ast.AsyncFunctionDef)
52
- ) or len(node.decorator_list) == 0:
53
- continue
54
- decorator = node.decorator_list[0]
55
-
56
- decorator_name = ""
57
- if isinstance(decorator, ast.Call):
58
- decorator_name = decorator.func.id
59
- if isinstance(decorator, ast.Name):
60
- decorator_name = decorator.id
61
-
62
- if decorator_name == from_decorator:
63
- # Get the function name and decorator name
64
- func_name = node.name
65
-
66
- # Import the module to get the actual function
67
- spec = importlib.util.spec_from_file_location(func_name, file_path)
68
- module = importlib.util.module_from_spec(spec)
69
- spec.loader.exec_module(module)
70
- # Check if kit=True in the decorator arguments
71
- is_kit = False
72
- if isinstance(decorator, ast.Call):
73
- for keyword in decorator.keywords:
74
- if keyword.arg == "kit" and isinstance(
75
- keyword.value, ast.Constant
76
- ):
77
- is_kit = keyword.value.value
78
- if is_kit and not settings.remote:
79
- kit_functions = get_functions(
80
- client,
81
- dir=os.path.join(root),
82
- from_decorator="kit",
83
- remote_functions_empty=remote_functions_empty,
84
- )
85
- functions.extend(kit_functions)
86
-
87
- # Get the decorated function
88
- if not is_kit and hasattr(module, func_name):
89
- func = getattr(module, func_name)
90
- if settings.remote:
91
- toolkit = RemoteToolkit(client, slugify(func.__name__))
92
- toolkit.initialize()
93
- functions.extend(toolkit.get_tools())
94
- else:
95
- if asyncio.iscoroutinefunction(func):
96
- functions.append(
97
- Tool(
98
- name=func.__name__,
99
- description=func.__doc__,
100
- func=func,
101
- coroutine=func,
102
- )
103
- )
104
- else:
105
- functions.append(
106
- Tool(
107
- name=func.__name__,
108
- description=func.__doc__,
109
- func=func,
110
- )
111
- )
112
- except Exception as e:
113
- logger.warning(f"Error processing {file_path}: {e!s}")
114
- return functions
115
-
116
-
117
19
  def agent(
118
20
  agent: Agent | dict = None,
119
- override_chat_model=None,
21
+ override_model=None,
120
22
  override_agent=None,
121
23
  mcp_hub=None,
122
24
  remote_functions=None,
@@ -129,31 +31,34 @@ def agent(
129
31
  )
130
32
 
131
33
  client = new_client()
132
- chat_model = override_chat_model or None
34
+ chat_model = override_model or None
133
35
  settings = init()
134
36
 
135
37
  def wrapper(func):
38
+ agent_kwargs = any(
39
+ param.name == "agent"
40
+ for param in inspect.signature(func).parameters.values()
41
+ )
42
+ model_kwargs = any(
43
+ param.name == "model"
44
+ for param in inspect.signature(func).parameters.values()
45
+ )
46
+ functions_kwargs = any(
47
+ param.name == "functions"
48
+ for param in inspect.signature(func).parameters.values()
49
+ )
136
50
  @functools.wraps(func)
137
51
  def wrapped(*args, **kwargs):
138
- return func(
139
- settings.agent.agent,
140
- settings.agent.chat_model,
141
- settings.agent.functions,
142
- *args,
143
- **kwargs,
144
- )
52
+ if agent_kwargs:
53
+ kwargs["agent"] = settings.agent.agent
54
+ if model_kwargs:
55
+ kwargs["model"] = settings.agent.chat_model
56
+ if functions_kwargs:
57
+ kwargs["functions"] = settings.agent.functions
58
+ return func(*args, **kwargs)
145
59
 
146
60
  return wrapped
147
61
 
148
- # Initialize functions array to store decorated functions
149
- functions = get_functions(
150
- client,
151
- dir=settings.agent.functions_directory,
152
- remote_functions_empty=not remote_functions,
153
- )
154
-
155
- settings.agent.functions = functions
156
-
157
62
  if agent is not None:
158
63
  metadata = AgentMetadata(**agent.get("metadata", {}))
159
64
  spec = AgentSpec(**agent.get("spec", {}))
@@ -184,44 +89,52 @@ def agent(
184
89
  settings.agent.chat_model = chat_model
185
90
  logger.info(f"Chat model configured, using: {provider}:{model}")
186
91
 
187
- if mcp_hub:
188
- for server in mcp_hub:
189
- try:
190
- mcp_client = MCPClient(client, server)
191
- toolkit = MCPToolkit(client=mcp_client)
192
- toolkit.initialize()
193
- functions.extend(toolkit.get_tools())
194
- except Exception as e:
195
- logger.warn(f"Failed to initialize MCP server {server}: {e!s}")
92
+ functions = get_functions(
93
+ client=client,
94
+ dir=settings.agent.functions_directory,
95
+ mcp_hub=mcp_hub,
96
+ remote_functions=remote_functions,
97
+ chain=agent.spec.agent_chain,
98
+ remote_functions_empty=not remote_functions,
99
+ warning=chat_model is not None,
100
+ )
101
+ settings.agent.functions = functions
196
102
 
197
- if remote_functions:
198
- for function in remote_functions:
103
+ if override_agent is None:
104
+ if chat_model is None:
105
+ models_select = ""
199
106
  try:
200
- toolkit = RemoteToolkit(client, function)
201
- toolkit.initialize()
202
- functions.extend(toolkit.get_tools())
203
- except Exception as e:
204
- logger.warn(f"Failed to initialize remote function {function}: {e!s}")
205
-
206
- if agent.spec.agent_chain:
207
- toolkit = ChainToolkit(client, agent.spec.agent_chain)
208
- toolkit.initialize()
209
- functions.extend(toolkit.get_tools())
210
-
211
- if override_agent is None and len(functions) == 0:
212
- raise ValueError(
213
- "You must define at least one function, you can define this function in directory "
214
- f'"{settings.agent.functions_directory}". Here is a sample function you can use:\n\n'
215
- "from beamlit.functions import function\n\n"
216
- "@function()\n"
217
- "def hello_world(query: str):\n"
218
- " return 'Hello, world!'\n"
219
- )
220
-
221
- if override_agent is None and chat_model is not None:
107
+ models = list_models.sync_detailed(
108
+ environment=settings.environment, client=client
109
+ )
110
+ models = ", ".join([model.metadata.name for model in models.parsed])
111
+ models_select = f"You can select one from the your models: {models}"
112
+ except Exception:
113
+ pass
114
+
115
+ raise ValueError(f"You must provide a model.\n"
116
+ f"{models_select}\n"
117
+ f"You can create one at {settings.app_url}/{settings.workspace}/global-inference-network/models/create\n"
118
+ "Add it to your agent spec\n"
119
+ "agent={\n"
120
+ " \"metadata\": {\n"
121
+ f" \"name\": \"{agent.metadata.name}\",\n"
122
+ " },\n"
123
+ " \"spec\": {\n"
124
+ " \"model\": \"MODEL_NAME\",\n"
125
+ f" \"description\": \"{agent.spec.description}\",\n"
126
+ " },\n"
127
+ "}")
222
128
  memory = MemorySaver()
223
- agent = create_react_agent(chat_model, functions, checkpointer=memory)
224
- settings.agent.agent = agent
129
+ if len(functions) == 0:
130
+ raise ValueError("You can define this function in directory "
131
+ f'"{settings.agent.functions_directory}". Here is a sample function you can use:\n\n'
132
+ "from beamlit.functions import function\n\n"
133
+ "@function()\n"
134
+ "def hello_world(query: str):\n"
135
+ " return 'Hello, world!'\n")
136
+ _agent = create_react_agent(chat_model, functions, checkpointer=memory)
137
+ settings.agent.agent = _agent
225
138
  else:
226
139
  settings.agent.agent = override_agent
227
140
  return wrapper
@@ -0,0 +1,14 @@
1
+
2
+ import jwt
3
+ from fastapi import Request
4
+
5
+
6
+ def get_default_thread(request: Request) -> str:
7
+ if request.headers.get("X-Beamlit-Sub"):
8
+ return request.headers.get("X-Beamlit-Sub")
9
+ authorization = request.headers.get("Authorization", request.headers.get("X-Beamlit-Authorization"))
10
+ if authorization and len(authorization.split("Bearer ")) > 1:
11
+ token = authorization.split(" ")[1]
12
+ decoded = jwt.decode(token, options={"verify_signature": False})
13
+ return decoded["sub"]
14
+ return ""
@@ -0,0 +1,97 @@
1
+ from http import HTTPStatus
2
+ from typing import Any, Optional, Union
3
+
4
+ import httpx
5
+
6
+ from ... import errors
7
+ from ...client import AuthenticatedClient, Client
8
+ from ...types import Response
9
+
10
+
11
+ def _get_kwargs(
12
+ workspace_name: str,
13
+ ) -> dict[str, Any]:
14
+ _kwargs: dict[str, Any] = {
15
+ "method": "put",
16
+ "url": f"/workspaces/{workspace_name}/quotas",
17
+ }
18
+
19
+ return _kwargs
20
+
21
+
22
+ def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]:
23
+ if response.status_code == 200:
24
+ return None
25
+ if client.raise_on_unexpected_status:
26
+ raise errors.UnexpectedStatus(response.status_code, response.content)
27
+ else:
28
+ return None
29
+
30
+
31
+ def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Any]:
32
+ return Response(
33
+ status_code=HTTPStatus(response.status_code),
34
+ content=response.content,
35
+ headers=response.headers,
36
+ parsed=_parse_response(client=client, response=response),
37
+ )
38
+
39
+
40
+ def sync_detailed(
41
+ workspace_name: str,
42
+ *,
43
+ client: AuthenticatedClient,
44
+ ) -> Response[Any]:
45
+ """Request workspace quotas
46
+
47
+ Send a request to update quotas for a workspace.
48
+
49
+ Args:
50
+ workspace_name (str):
51
+
52
+ Raises:
53
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
54
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
55
+
56
+ Returns:
57
+ Response[Any]
58
+ """
59
+
60
+ kwargs = _get_kwargs(
61
+ workspace_name=workspace_name,
62
+ )
63
+
64
+ response = client.get_httpx_client().request(
65
+ **kwargs,
66
+ )
67
+
68
+ return _build_response(client=client, response=response)
69
+
70
+
71
+ async def asyncio_detailed(
72
+ workspace_name: str,
73
+ *,
74
+ client: AuthenticatedClient,
75
+ ) -> Response[Any]:
76
+ """Request workspace quotas
77
+
78
+ Send a request to update quotas for a workspace.
79
+
80
+ Args:
81
+ workspace_name (str):
82
+
83
+ Raises:
84
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
85
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
86
+
87
+ Returns:
88
+ Response[Any]
89
+ """
90
+
91
+ kwargs = _get_kwargs(
92
+ workspace_name=workspace_name,
93
+ )
94
+
95
+ response = await client.get_async_httpx_client().request(**kwargs)
96
+
97
+ return _build_response(client=client, response=response)
@@ -1,7 +1,7 @@
1
1
  import base64
2
2
  import json
3
- import time
4
3
  from dataclasses import dataclass
4
+ from datetime import datetime, timedelta
5
5
  from typing import Generator, Optional
6
6
 
7
7
  import requests
@@ -55,11 +55,13 @@ class ClientCredentials(Auth):
55
55
  except Exception as e:
56
56
  return Exception(f"Failed to decode/parse JWT claims: {str(e)}")
57
57
 
58
- exp_time = time.gmtime(claims["exp"])
58
+ exp_time = datetime.fromtimestamp(claims["exp"])
59
+ current_time = datetime.now()
59
60
  # Refresh if token expires in less than 10 minutes
60
- if time.time() + (10 * 60) > time.mktime(exp_time):
61
+ if current_time + timedelta(minutes=10) > exp_time:
61
62
  return self.do_refresh()
62
63
 
64
+
63
65
  return None
64
66
 
65
67
  def auth_flow(self, request: Request) -> Generator[Request, Response, None]:
@@ -1,7 +1,7 @@
1
1
  import base64
2
2
  import json
3
- import time
4
3
  from dataclasses import dataclass
4
+ from datetime import datetime, timedelta
5
5
  from typing import Dict, Generator, Optional
6
6
 
7
7
  from httpx import Auth, Request, Response, post
@@ -65,10 +65,10 @@ class BearerToken(Auth):
65
65
  claims = json.loads(claims_bytes)
66
66
  except Exception as e:
67
67
  return Exception(f"Failed to decode/parse JWT claims: {str(e)}")
68
-
69
- exp_time = time.gmtime(claims["exp"])
68
+ exp_time = datetime.fromtimestamp(claims["exp"])
69
+ current_time = datetime.now()
70
70
  # Refresh if token expires in less than 10 minutes
71
- if time.time() + (10 * 60) > time.mktime(exp_time):
71
+ if current_time + timedelta(minutes=10) > exp_time:
72
72
  return self.do_refresh()
73
73
 
74
74
  return None
@@ -1,7 +1,7 @@
1
1
  from .error import HTTPError
2
2
  from .logger import init as init_logger
3
3
  from .secrets import Secret
4
- from .settings import Settings, get_settings, init, init_agent
4
+ from .settings import Settings, get_settings, init
5
5
  from .slugify import slugify
6
6
  from .utils import copy_folder
7
7
 
@@ -9,7 +9,6 @@ __all__ = [
9
9
  "Secret",
10
10
  "Settings",
11
11
  "get_settings",
12
- "init_agent",
13
12
  "init",
14
13
  "copy_folder",
15
14
  "init_logger",