beamlit 0.0.49rc96__py3-none-any.whl → 0.0.50__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.
beamlit/agents/chat.py CHANGED
@@ -7,6 +7,7 @@ from beamlit.api.models import get_model
7
7
  from beamlit.authentication import get_authentication_headers, new_client
8
8
  from beamlit.common.settings import get_settings
9
9
  from beamlit.models import Model
10
+ from .voice.openai import OpenAIVoiceReactAgent
10
11
 
11
12
  logger = getLogger(__name__)
12
13
 
@@ -57,7 +58,7 @@ def get_azure_ai_inference_chat_model(**kwargs):
57
58
 
58
59
  def get_azure_marketplace_chat_model(**kwargs):
59
60
  from langchain_openai import OpenAI # type: ignore
60
-
61
+
61
62
  return OpenAI(
62
63
  **kwargs
63
64
  ) # It seems to use a compatible endpoint, so we can use the classic OpenAI interface
@@ -156,6 +157,17 @@ def get_chat_model_full(name: str, agent_model: Union[Model, None] = None) -> Tu
156
157
  logger.warning("Model not found in agent model, defaulting to gpt-4o-mini")
157
158
  model = "gpt-4o-mini"
158
159
 
160
+ if provider == "openai" and "realtime" in model:
161
+ logger.info("Starting OpenAI Realtime Agent")
162
+ return (
163
+ OpenAIVoiceReactAgent(
164
+ url=get_base_url(name),
165
+ model=model,
166
+ headers=headers
167
+ ),
168
+ provider,
169
+ model
170
+ )
159
171
  kwargs = {
160
172
  "model": model,
161
173
  "base_url": get_base_url(name),
@@ -12,7 +12,7 @@ from beamlit.common.settings import init
12
12
  from beamlit.errors import UnexpectedStatus
13
13
  from beamlit.functions import get_functions
14
14
  from beamlit.models import Agent, AgentSpec, EnvironmentMetadata
15
-
15
+ from .voice.openai import OpenAIVoiceReactAgent
16
16
  from .chat import get_chat_model_full
17
17
 
18
18
 
@@ -128,19 +128,22 @@ def agent(
128
128
  f" \"description\": \"{agent.spec.description}\",\n"
129
129
  " },\n"
130
130
  "}")
131
- memory = MemorySaver()
132
- if len(functions) == 0:
133
- raise ValueError("You can define this function in directory "
134
- f'"{settings.agent.functions_directory}". Here is a sample function you can use:\n\n'
135
- "from beamlit.functions import function\n\n"
136
- "@function()\n"
137
- "def hello_world(query: str):\n"
138
- " return 'Hello, world!'\n")
139
- try:
140
- _agent = create_react_agent(chat_model, functions, checkpointer=memory)
141
- except AttributeError: # special case for azure-marketplace where it uses the old OpenAI interface (no tools)
142
- logger.warning("Using the old OpenAI interface for Azure Marketplace, no tools available")
143
- _agent = create_react_agent(chat_model, [], checkpointer=memory)
131
+ if isinstance(chat_model, OpenAIVoiceReactAgent):
132
+ _agent = chat_model
133
+ else:
134
+ memory = MemorySaver()
135
+ if len(functions) == 0:
136
+ raise ValueError("You can define this function in directory "
137
+ f'"{settings.agent.functions_directory}". Here is a sample function you can use:\n\n'
138
+ "from beamlit.functions import function\n\n"
139
+ "@function()\n"
140
+ "def hello_world(query: str):\n"
141
+ " return 'Hello, world!'\n")
142
+ try:
143
+ _agent = create_react_agent(chat_model, functions, checkpointer=memory)
144
+ except AttributeError: # special case for azure-marketplace where it uses the old OpenAI interface (no tools)
145
+ logger.warning("Using the old OpenAI interface for Azure Marketplace, no tools available")
146
+ _agent = create_react_agent(chat_model, [], checkpointer=memory)
144
147
 
145
148
  settings.agent.agent = _agent
146
149
  else:
@@ -0,0 +1,257 @@
1
+ import asyncio
2
+ import json
3
+ import websockets
4
+
5
+ from contextlib import asynccontextmanager
6
+ from typing import AsyncGenerator, AsyncIterator, Any, Callable, Coroutine
7
+ from .utils import amerge
8
+
9
+ from langchain_core.tools import BaseTool
10
+ from langchain_core._api import beta
11
+ from langchain_core.utils import secret_from_env
12
+
13
+ from pydantic import BaseModel, Field, SecretStr, PrivateAttr
14
+
15
+ EVENTS_TO_IGNORE = {
16
+ "response.function_call_arguments.delta",
17
+ "rate_limits.updated",
18
+ "response.audio_transcript.delta",
19
+ "response.created",
20
+ "response.content_part.added",
21
+ "response.content_part.done",
22
+ "conversation.item.created",
23
+ "response.audio.done",
24
+ "session.created",
25
+ "session.updated",
26
+ "response.done",
27
+ "response.output_item.done",
28
+ }
29
+
30
+
31
+ @asynccontextmanager
32
+ async def connect(*, headers: dict[str, Any], model: str, url: str) -> AsyncGenerator[
33
+ tuple[
34
+ Callable[[dict[str, Any] | str], Coroutine[Any, Any, None]],
35
+ AsyncIterator[dict[str, Any]],
36
+ ],
37
+ None,
38
+ ]:
39
+ """
40
+ async with connect(model="gpt-4o-realtime-preview-2024-10-01") as websocket:
41
+ await websocket.send("Hello, world!")
42
+ async for message in websocket:
43
+ print(message)
44
+ """
45
+ url += f"?model={model}"
46
+
47
+ websocket = await websockets.connect(url, extra_headers={**headers, "OpenAI-Beta": "realtime=v1"})
48
+
49
+ try:
50
+
51
+ async def send_event(event: dict[str, Any] | str) -> None:
52
+ formatted_event = json.dumps(event) if isinstance(event, dict) else event
53
+ await websocket.send(formatted_event)
54
+
55
+ async def event_stream() -> AsyncIterator[dict[str, Any]]:
56
+ async for raw_event in websocket:
57
+ yield json.loads(raw_event)
58
+
59
+ stream: AsyncIterator[dict[str, Any]] = event_stream()
60
+
61
+ yield send_event, stream
62
+ finally:
63
+ await websocket.close()
64
+
65
+
66
+ class VoiceToolExecutor(BaseModel):
67
+ """
68
+ Can accept function calls and emits function call outputs to a stream.
69
+ """
70
+
71
+ tools_by_name: dict[str, BaseTool]
72
+ _trigger_future: asyncio.Future = PrivateAttr(default_factory=asyncio.Future)
73
+ _lock: asyncio.Lock = PrivateAttr(default_factory=asyncio.Lock)
74
+
75
+ async def _trigger_func(self) -> dict: # returns a tool call
76
+ return await self._trigger_future
77
+
78
+ async def add_tool_call(self, tool_call: dict) -> None:
79
+ # lock to avoid simultaneous tool calls racing and missing
80
+ # _trigger_future being
81
+ async with self._lock:
82
+ if self._trigger_future.done():
83
+ # TODO: handle simultaneous tool calls better
84
+ raise ValueError("Tool call adding already in progress")
85
+
86
+ self._trigger_future.set_result(tool_call)
87
+
88
+ async def _create_tool_call_task(self, tool_call: dict) -> asyncio.Task[dict]:
89
+ tool = self.tools_by_name.get(tool_call["name"])
90
+ if tool is None:
91
+ # immediately yield error, do not add task
92
+ raise ValueError(
93
+ f"tool {tool_call['name']} not found. "
94
+ f"Must be one of {list(self.tools_by_name.keys())}"
95
+ )
96
+
97
+ # try to parse args
98
+ try:
99
+ args = json.loads(tool_call["arguments"])
100
+ except json.JSONDecodeError:
101
+ raise ValueError(
102
+ f"failed to parse arguments `{tool_call['arguments']}`. Must be valid JSON."
103
+ )
104
+
105
+ async def run_tool() -> dict:
106
+ result = await tool.ainvoke(args)
107
+ try:
108
+ result_str = json.dumps(result)
109
+ except TypeError:
110
+ # not json serializable, use str
111
+ result_str = str(result)
112
+ return {
113
+ "type": "conversation.item.create",
114
+ "item": {
115
+ "id": tool_call["call_id"],
116
+ "call_id": tool_call["call_id"],
117
+ "type": "function_call_output",
118
+ "output": result_str,
119
+ },
120
+ }
121
+
122
+ task = asyncio.create_task(run_tool())
123
+ return task
124
+
125
+ async def output_iterator(self) -> AsyncIterator[dict]: # yield events
126
+ trigger_task = asyncio.create_task(self._trigger_func())
127
+ tasks = set([trigger_task])
128
+ while True:
129
+ done, _ = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
130
+ for task in done:
131
+ tasks.remove(task)
132
+ if task == trigger_task:
133
+ async with self._lock:
134
+ self._trigger_future = asyncio.Future()
135
+ trigger_task = asyncio.create_task(self._trigger_func())
136
+ tasks.add(trigger_task)
137
+ tool_call = task.result()
138
+ try:
139
+ new_task = await self._create_tool_call_task(tool_call)
140
+ tasks.add(new_task)
141
+ except ValueError as e:
142
+ yield {
143
+ "type": "conversation.item.create",
144
+ "item": {
145
+ "id": tool_call["call_id"],
146
+ "call_id": tool_call["call_id"],
147
+ "type": "function_call_output",
148
+ "output": (f"Error: {str(e)}"),
149
+ },
150
+ }
151
+ else:
152
+ yield task.result()
153
+
154
+
155
+ @beta()
156
+ class OpenAIVoiceReactAgent(BaseModel):
157
+ model: str
158
+ headers: dict[str, Any] = Field()
159
+ instructions: str | None = None
160
+ tools: list[BaseTool] | None = None
161
+ url: str = Field()
162
+
163
+ def bind_tools(self, tools: list[BaseTool]) -> None:
164
+ self.tools = tools
165
+
166
+ async def aconnect(
167
+ self,
168
+ input_stream: AsyncIterator[str],
169
+ send_output_chunk: Callable[[str], Coroutine[Any, Any, None]],
170
+ ) -> None:
171
+ """
172
+ Connect to the OpenAI API and send and receive messages.
173
+
174
+ input_stream: AsyncIterator[str]
175
+ Stream of input events to send to the model. Usually transports input_audio_buffer.append events from the microphone.
176
+ output: Callable[[str], None]
177
+ Callback to receive output events from the model. Usually sends response.audio.delta events to the speaker.
178
+
179
+ """
180
+ # formatted_tools: list[BaseTool] = [
181
+ # tool if isinstance(tool, BaseTool) else tool_converter.wr(tool) # type: ignore
182
+ # for tool in self.tools or []
183
+ # ]
184
+ tools_by_name = {tool.name: tool for tool in self.tools}
185
+ tool_executor = VoiceToolExecutor(tools_by_name=tools_by_name)
186
+
187
+ async with connect(
188
+ model=self.model, headers=self.headers, url=self.url
189
+ ) as (
190
+ model_send,
191
+ model_receive_stream,
192
+ ):
193
+ # sent tools and instructions with initial chunk
194
+ tool_defs = [
195
+ {
196
+ "type": "function",
197
+ "name": tool.name,
198
+ "description": tool.description,
199
+ "parameters": {"type": "object", "properties": tool.args},
200
+ }
201
+ for tool in tools_by_name.values()
202
+ ]
203
+ await model_send(
204
+ {
205
+ "type": "session.update",
206
+ "session": {
207
+ "instructions": self.instructions,
208
+ "input_audio_transcription": {
209
+ "model": "whisper-1",
210
+ },
211
+ "tools": tool_defs,
212
+ },
213
+ }
214
+ )
215
+ async for stream_key, data_raw in amerge(
216
+ input_mic=input_stream,
217
+ output_speaker=model_receive_stream,
218
+ tool_outputs=tool_executor.output_iterator(),
219
+ ):
220
+ try:
221
+ data = (
222
+ json.loads(data_raw) if isinstance(data_raw, str) else data_raw
223
+ )
224
+ except json.JSONDecodeError:
225
+ print("error decoding data:", data_raw)
226
+ continue
227
+
228
+ if stream_key == "input_mic":
229
+ await model_send(data)
230
+ elif stream_key == "tool_outputs":
231
+ print("tool output", data)
232
+ await model_send(data)
233
+ await model_send({"type": "response.create", "response": {}})
234
+ elif stream_key == "output_speaker":
235
+
236
+ t = data["type"]
237
+ if t == "response.audio.delta":
238
+ await send_output_chunk(json.dumps(data))
239
+ elif t == "input_audio_buffer.speech_started":
240
+ print("interrupt")
241
+ send_output_chunk(json.dumps(data))
242
+ elif t == "error":
243
+ print("error:", data)
244
+ elif t == "response.function_call_arguments.done":
245
+ print("tool call", data)
246
+ await tool_executor.add_tool_call(data)
247
+ elif t == "response.audio_transcript.done":
248
+ print("model:", data["transcript"])
249
+ elif t == "conversation.item.input_audio_transcription.completed":
250
+ print("user:", data["transcript"])
251
+ elif t in EVENTS_TO_IGNORE:
252
+ pass
253
+ else:
254
+ print(t)
255
+
256
+
257
+ __all__ = ["OpenAIVoiceReactAgent"]
@@ -0,0 +1,25 @@
1
+ import asyncio
2
+ from typing import AsyncIterator, TypeVar
3
+
4
+ T = TypeVar("T")
5
+
6
+
7
+ async def amerge(**streams: AsyncIterator[T]) -> AsyncIterator[tuple[str, T]]:
8
+ """Merge multiple streams into one stream."""
9
+ nexts: dict[asyncio.Task, str] = {
10
+ asyncio.create_task(anext(stream)): key for key, stream in streams.items()
11
+ }
12
+ while nexts:
13
+ done, _ = await asyncio.wait(nexts, return_when=asyncio.FIRST_COMPLETED)
14
+ for task in done:
15
+ key = nexts.pop(task)
16
+ stream = streams[key]
17
+ try:
18
+ yield key, task.result()
19
+ nexts[asyncio.create_task(anext(stream))] = key
20
+ except StopAsyncIteration:
21
+ pass
22
+ except Exception as e:
23
+ for task in nexts:
24
+ task.cancel()
25
+ raise e
beamlit/deploy/deploy.py CHANGED
@@ -191,7 +191,7 @@ COPY README.m[d] /beamlit/README.md
191
191
  COPY LICENS[E] /beamlit/LICENSE
192
192
  COPY {settings.server.directory} /beamlit/src
193
193
 
194
- RUN uv sync --no-cache
194
+ RUN uv sync --no-cache --no-dev
195
195
 
196
196
  ENV PATH="/beamlit/.venv/bin:$PATH"
197
197
 
beamlit/serve/app.py CHANGED
@@ -3,12 +3,13 @@ import importlib
3
3
  import os
4
4
  import sys
5
5
  import traceback
6
+ import inspect
6
7
  from contextlib import asynccontextmanager
7
8
  from logging import getLogger
8
9
  from uuid import uuid4
9
10
 
10
11
  from asgi_correlation_id import CorrelationIdMiddleware
11
- from fastapi import FastAPI, Request, Response
12
+ from fastapi import FastAPI, Request, Response, WebSocket
12
13
  from fastapi.responses import JSONResponse
13
14
 
14
15
  from beamlit.common import HTTPError, get_settings, init
@@ -38,7 +39,10 @@ logger.info(
38
39
  f"Running server with environment {settings.environment}"
39
40
  f" on {settings.server.host}:{settings.server.port}"
40
41
  )
41
-
42
+ func_params = inspect.signature(func).parameters
43
+ websocket_detected = False
44
+ if "websocket" in func_params:
45
+ websocket_detected = True
42
46
 
43
47
  @asynccontextmanager
44
48
  async def lifespan(app: FastAPI):
@@ -56,48 +60,58 @@ app.add_middleware(AddProcessTimeHeader)
56
60
  app.add_middleware(AccessLogMiddleware)
57
61
  instrument_app(app)
58
62
 
59
-
60
63
  @app.get("/health")
61
64
  async def health():
62
65
  return {"status": "ok"}
63
66
 
67
+ if websocket_detected:
68
+ @app.websocket("/ws")
69
+ async def websocket_endpoint(websocket: WebSocket):
70
+ await websocket.accept()
64
71
 
65
- @app.post("/")
66
- async def root(request: Request):
67
- settings = get_settings()
68
- logger = getLogger(__name__)
69
- try:
70
72
  original_func = getattr(func, "__wrapped__", func)
71
73
  if asyncio.iscoroutinefunction(func) or asyncio.iscoroutinefunction(original_func):
72
- response = await func(request)
74
+ await func(websocket)
73
75
  else:
74
- response = func(request)
75
-
76
- if isinstance(response, Response):
77
- return response
78
- if type(response) is str:
79
- return Response(
80
- content=response,
81
- headers={"Content-Type": "text/plain"},
82
- media_type="text/plain",
83
- status_code=200,
84
- )
85
- return JSONResponse(status_code=200, content=response)
86
- except ValueError as e:
87
- content = {"error": str(e)}
88
- if settings.environment == "development":
89
- content["traceback"] = str(traceback.format_exc())
90
- logger.error(str(traceback.format_exc()))
91
- return JSONResponse(status_code=400, content=content)
92
- except HTTPError as e:
93
- content = {"error": e.message, "status_code": e.status_code}
94
- if settings.environment == "development":
95
- content["traceback"] = str(traceback.format_exc())
96
- logger.error(f"{e.status_code} {str(traceback.format_exc())}")
97
- return JSONResponse(status_code=e.status_code, content=content)
98
- except Exception as e:
99
- content = {"error": f"Internal server error, {e}"}
100
- if settings.environment == "development":
101
- content["traceback"] = str(traceback.format_exc())
102
- logger.error(str(traceback.format_exc()))
103
- return JSONResponse(status_code=500, content=content)
76
+ func(websocket)
77
+
78
+ else:
79
+ @app.post("/")
80
+ async def root(request: Request):
81
+ settings = get_settings()
82
+ logger = getLogger(__name__)
83
+ try:
84
+ original_func = getattr(func, "__wrapped__", func)
85
+ if asyncio.iscoroutinefunction(func) or asyncio.iscoroutinefunction(original_func):
86
+ response = await func(request)
87
+ else:
88
+ response = func(request)
89
+
90
+ if isinstance(response, Response):
91
+ return response
92
+ if type(response) is str:
93
+ return Response(
94
+ content=response,
95
+ headers={"Content-Type": "text/plain"},
96
+ media_type="text/plain",
97
+ status_code=200,
98
+ )
99
+ return JSONResponse(status_code=200, content=response)
100
+ except ValueError as e:
101
+ content = {"error": str(e)}
102
+ if settings.environment == "development":
103
+ content["traceback"] = str(traceback.format_exc())
104
+ logger.error(str(traceback.format_exc()))
105
+ return JSONResponse(status_code=400, content=content)
106
+ except HTTPError as e:
107
+ content = {"error": e.message, "status_code": e.status_code}
108
+ if settings.environment == "development":
109
+ content["traceback"] = str(traceback.format_exc())
110
+ logger.error(f"{e.status_code} {str(traceback.format_exc())}")
111
+ return JSONResponse(status_code=e.status_code, content=content)
112
+ except Exception as e:
113
+ content = {"error": f"Internal server error, {e}"}
114
+ if settings.environment == "development":
115
+ content["traceback"] = str(traceback.format_exc())
116
+ logger.error(str(traceback.format_exc()))
117
+ return JSONResponse(status_code=500, content=content)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: beamlit
3
- Version: 0.0.49rc96
3
+ Version: 0.0.50
4
4
  Summary: Add your description here
5
5
  Author-email: cploujoux <ch.ploujoux@gmail.com>
6
6
  License-File: LICENSE
@@ -43,6 +43,7 @@ Requires-Dist: pyjwt>=2.10.1
43
43
  Requires-Dist: python-dateutil>=2.8.0
44
44
  Requires-Dist: pyyaml<6.1.0,>=6.0.2
45
45
  Requires-Dist: requests<2.33.0,>=2.32.3
46
+ Requires-Dist: websockets>=14.1
46
47
  Description-Content-Type: text/markdown
47
48
 
48
49
  # beamlit
@@ -6,9 +6,11 @@ beamlit/run.py,sha256=HtDYDjD7oVfQ8r3T5_t4qN5UDJOJfsQILi45Z21ArAg,1446
6
6
  beamlit/types.py,sha256=E1hhDh_zXfsSQ0NCt9-uw90_Mr5iIlsdfnfvxv5HarU,1005
7
7
  beamlit/agents/__init__.py,sha256=bWsFaXUbAps3IsL3Prti89m1s714vICXodbQi77h3vY,206
8
8
  beamlit/agents/chain.py,sha256=vfCjiFHuu02uTTGicxMlFzjyICQkIjpXrBGs-7uJEsg,2826
9
- beamlit/agents/chat.py,sha256=4R_mKeGvVXluQ5r-pnN3YaqhE3lFuetyos4zUBLnLnE,5510
10
- beamlit/agents/decorator.py,sha256=3-89dnk23l7M0Hxh---3pqVkiVDcAeFw9FLgBgSGDVQ,6358
9
+ beamlit/agents/chat.py,sha256=wkzhRNWeKmNlaKZQVRLwdjTEtdOTiUnpj0fTeRe2ePk,5877
10
+ beamlit/agents/decorator.py,sha256=5bEnVYEYTiFmCyQkFtN4XamrfTli6KYr98nFukPgOZE,6573
11
11
  beamlit/agents/thread.py,sha256=LN5Ss-uOf5_hdB0WV1dqpn-N-pDJB3C2hUvlCzdqtdk,519
12
+ beamlit/agents/voice/openai.py,sha256=hmZQlAGQCQtR2MsXX1kC1DMUeiFctb-FiYqKDG7NGOo,9540
13
+ beamlit/agents/voice/utils.py,sha256=tQidyM40Ewuy12wKqpvJLvfJgneQ0sZf50dqnerPGHg,836
12
14
  beamlit/api/__init__.py,sha256=zTSiG_ujSjAqWPyc435YXaX9XTlpMjiJWBbV-f-YtdA,45
13
15
  beamlit/api/accounts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
16
  beamlit/api/accounts/delete_account.py,sha256=Mrbhuxl1IF1GDclSixIAvj3YHa7trlFg3JNmllqpBRA,3864
@@ -139,7 +141,7 @@ beamlit/common/settings.py,sha256=C7iZrLLvl6ap5VCVCYOoK20WdSFqacmd6HtaeZ7SF6s,37
139
141
  beamlit/common/slugify.py,sha256=nR29r37IdWS2i44ZC6ZsXRgqKPYmvMGtFQ7BuIQUTlc,90
140
142
  beamlit/common/utils.py,sha256=jouz5igBvT37Xn_e94-foCHyQczVim-UzVcoIF6RWJ4,657
141
143
  beamlit/deploy/__init__.py,sha256=GS7l7Jtm2yKs7iNLKcfjYO-rAhUzggQ3xiYSf3oxLBY,91
142
- beamlit/deploy/deploy.py,sha256=YqmaNJSa5faFpkUjBBX6ueAQxdmljdTdZBITMuaxY8U,10371
144
+ beamlit/deploy/deploy.py,sha256=0snAN62iZGVYdH4Nh1H2-QTQKSN_-WpaEUCkwTo6USc,10380
143
145
  beamlit/deploy/format.py,sha256=U6UZEFAYLnGJJ7O2YmSdlUUFhnWNGAv6NZ-DW4KTgvI,2049
144
146
  beamlit/deploy/parser.py,sha256=Ga0poCZkoRnuTw082QnTcNGCBJncoRAnVsn8-1FsaJE,6907
145
147
  beamlit/functions/__init__.py,sha256=oejZ6GVd4-2kGKsOwRUq1u2AuSyrrn9kWIuH6WxMqhE,189
@@ -259,11 +261,11 @@ beamlit/models/websocket_channel.py,sha256=jg3vN7yS_oOIwGtndtIUr1LsyEA58RXLXahqS
259
261
  beamlit/models/workspace.py,sha256=nsck1BNzNoF_SalMqyVtkRoC_P-6-b4gzxKgKu3HwDw,5065
260
262
  beamlit/models/workspace_labels.py,sha256=WbnUY6eCTkUNdY7hhhSF-KQCl8fWFfkCf7hzCTiNp4A,1246
261
263
  beamlit/models/workspace_user.py,sha256=70CcifQWYbeWG7TDui4pblTzUe5sVK0AS19vNCzKE8g,3423
262
- beamlit/serve/app.py,sha256=ROS_tb9cO4GvOQKCwloyAzpYraTdIb3oG6sChXikeNw,3285
264
+ beamlit/serve/app.py,sha256=BhEFQx8l2pv4eeVRGnvaC6el0tcgrzu-YiXpyL8oY6Q,3970
263
265
  beamlit/serve/middlewares/__init__.py,sha256=1dVmnOmhAQWvWktqHkKSIX-YoF6fmMU8xkUQuhg_rJU,148
264
266
  beamlit/serve/middlewares/accesslog.py,sha256=Mu4T4_9OvHybjA0ApzZFpgi2C8f3X1NbUk-76v634XM,631
265
267
  beamlit/serve/middlewares/processtime.py,sha256=lDAaIasZ4bwvN-HKHvZpaD9r-yrkVNZYx4abvbjbrCg,411
266
- beamlit-0.0.49rc96.dist-info/METADATA,sha256=6Eiz03TKilXs5O6NzHacJFjI-0Us6i8cZhClheKKT7E,3482
267
- beamlit-0.0.49rc96.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
268
- beamlit-0.0.49rc96.dist-info/licenses/LICENSE,sha256=p5PNQvpvyDT_0aYBDgmV1fFI_vAD2aSV0wWG7VTgRis,1069
269
- beamlit-0.0.49rc96.dist-info/RECORD,,
268
+ beamlit-0.0.50.dist-info/METADATA,sha256=KkTnNDFwFZBA-iLgkc7mA7cXD3hdkeFixQYwTMhZuHs,3510
269
+ beamlit-0.0.50.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
270
+ beamlit-0.0.50.dist-info/licenses/LICENSE,sha256=p5PNQvpvyDT_0aYBDgmV1fFI_vAD2aSV0wWG7VTgRis,1069
271
+ beamlit-0.0.50.dist-info/RECORD,,