beamlit 0.0.28rc25__py3-none-any.whl → 0.0.29rc27__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.
@@ -0,0 +1,93 @@
1
+ import asyncio
2
+ from dataclasses import dataclass
3
+ import warnings
4
+ from typing import Any, Callable
5
+
6
+ import pydantic
7
+ import typing_extensions as t
8
+ from beamlit.api.agents import list_agents
9
+ from beamlit.authentication.authentication import AuthenticatedClient
10
+ from beamlit.models import Agent, AgentChain
11
+ from beamlit.run import RunClient
12
+ from beamlit.common.settings import get_settings
13
+ from langchain_core.tools.base import BaseTool, ToolException
14
+
15
+
16
+
17
+ class ChainTool(BaseTool):
18
+ """
19
+ Chain tool
20
+ """
21
+
22
+ client: RunClient
23
+ handle_tool_error: bool | str | Callable[[ToolException], str] | None = True
24
+
25
+ @t.override
26
+ def _run(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
27
+ warnings.warn(
28
+ "Invoke this tool asynchronousely using `ainvoke`. This method exists only to satisfy standard tests.",
29
+ stacklevel=1,
30
+ )
31
+ return asyncio.run(self._arun(*args, **kwargs))
32
+
33
+ @t.override
34
+ async def _arun(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
35
+ settings = get_settings()
36
+ result = self.client.run(
37
+ "agent",
38
+ self.name,
39
+ settings.environment,
40
+ "POST",
41
+ json=kwargs,
42
+ )
43
+ return result.text
44
+
45
+ @t.override
46
+ @property
47
+ def tool_call_schema(self) -> type[pydantic.BaseModel]:
48
+ assert self.args_schema is not None # noqa: S101
49
+ return self.args_schema
50
+
51
+ class ChainInput(pydantic.BaseModel):
52
+ inputs: str
53
+
54
+ @dataclass
55
+ class ChainToolkit:
56
+ """
57
+ Remote toolkit
58
+ """
59
+ client: AuthenticatedClient
60
+ chain: list[AgentChain]
61
+ _chain: list[Agent] | None = None
62
+
63
+ model_config = pydantic.ConfigDict(arbitrary_types_allowed=True)
64
+
65
+ def initialize(self) -> None:
66
+ """Initialize the session and retrieve tools list"""
67
+ if self._chain is None:
68
+ agents = list_agents.sync_detailed(
69
+ client=self.client,
70
+ ).parsed
71
+ chain_enabled = [chain for chain in self.chain if chain.enabled]
72
+ agents_chain = []
73
+ for chain in chain_enabled:
74
+ agent = [agent for agent in agents if agent.metadata.name == chain.name]
75
+ if agent:
76
+ agent[0].spec.description = chain.description or agent[0].spec.description
77
+ agents_chain.append(agent[0])
78
+ self._chain = agents_chain
79
+
80
+ @t.override
81
+ def get_tools(self) -> list[BaseTool]:
82
+ if self._chain is None:
83
+ raise RuntimeError("Must initialize the toolkit first")
84
+
85
+ return [
86
+ ChainTool(
87
+ client=RunClient(self.client),
88
+ name=agent.metadata.name,
89
+ description=agent.spec.description or "",
90
+ args_schema=ChainInput,
91
+ )
92
+ for agent in self._chain
93
+ ]
@@ -18,7 +18,7 @@ from langgraph.checkpoint.memory import MemorySaver
18
18
  from langgraph.prebuilt import create_react_agent
19
19
 
20
20
  from .chat import get_chat_model
21
-
21
+ from .chain import ChainToolkit
22
22
 
23
23
  def get_functions(dir="src/functions", from_decorator="function"):
24
24
  functions = []
@@ -178,7 +178,6 @@ def agent(
178
178
  logger.warn(f"Failed to initialize MCP server {server}: {e!s}")
179
179
 
180
180
  if remote_functions:
181
-
182
181
  for function in remote_functions:
183
182
  try:
184
183
  toolkit = RemoteToolkit(client, function)
@@ -187,6 +186,11 @@ def agent(
187
186
  except Exception as e:
188
187
  logger.warn(f"Failed to initialize remote function {function}: {e!s}")
189
188
 
189
+ if agent.spec.agent_chain:
190
+ toolkit = ChainToolkit(client, agent.spec.agent_chain)
191
+ toolkit.initialize()
192
+ functions.extend(toolkit.get_tools())
193
+
190
194
  if override_agent is None and len(functions) == 0:
191
195
  raise ValueError(
192
196
  "You must define at least one function, you can define this function in directory "
@@ -47,15 +47,15 @@ def new_client_from_settings(settings: Settings):
47
47
 
48
48
 
49
49
  def new_client():
50
+ settings = get_settings()
50
51
  context = current_context()
51
- if context.workspace:
52
+ if context.workspace and not settings.authentication.client.credentials:
52
53
  credentials = load_credentials(context.workspace)
53
54
  client_config = RunClientWithCredentials(
54
55
  credentials=credentials,
55
56
  workspace=context.workspace,
56
57
  )
57
58
  else:
58
- settings = get_settings()
59
59
  credentials = load_credentials_from_settings(settings)
60
60
  client_config = RunClientWithCredentials(
61
61
  credentials=credentials,
@@ -80,7 +80,7 @@ def new_client_with_credentials(config: RunClientWithCredentials):
80
80
 
81
81
  def get_authentication_headers(settings: Settings) -> Dict[str, str]:
82
82
  context = current_context()
83
- if context.workspace:
83
+ if context.workspace and not settings.authentication.client.credentials:
84
84
  credentials = load_credentials(context.workspace)
85
85
  else:
86
86
  settings = get_settings()
@@ -37,7 +37,7 @@ class ClientCredentials(Auth):
37
37
  def refresh_if_needed(self) -> Optional[Exception]:
38
38
  settings = get_settings()
39
39
  if self.credentials.client_credentials and not self.credentials.refresh_token:
40
- headers = {"Authorization": f"Basic {self.credentials.client_credentials}"}
40
+ headers = {"Authorization": f"Basic {self.credentials.client_credentials}", "Content-Type": "application/json"}
41
41
  body = {"grant_type": "client_credentials"}
42
42
  response = requests.post(f"{settings.base_url}/oauth/token", headers=headers, json=body)
43
43
  response.raise_for_status()
@@ -18,11 +18,6 @@ from pydantic_settings import (BaseSettings, PydanticBaseSettingsSource,
18
18
  global SETTINGS
19
19
  SETTINGS = None
20
20
 
21
-
22
- def get_settings():
23
- return SETTINGS
24
-
25
-
26
21
  class SettingsAgent(BaseSettings):
27
22
  agent: Union[None, CompiledGraph, BaseChatModel] = None
28
23
  chain: Union[Unset, list[Agent]] = UNSET
@@ -96,6 +91,9 @@ class Settings(BaseSettings):
96
91
  init_settings,
97
92
  )
98
93
 
94
+ def get_settings() -> Settings:
95
+ return SETTINGS
96
+
99
97
 
100
98
  def init_agent(
101
99
  client: AuthenticatedClient,
@@ -11,26 +11,35 @@ from beamlit.authentication.authentication import AuthenticatedClient
11
11
  from beamlit.common.settings import get_settings
12
12
  from langchain_core.tools.base import BaseTool, BaseToolkit, ToolException
13
13
  from mcp import ListToolsResult
14
- from pydantic.json_schema import JsonSchemaValue
15
- from pydantic_core import core_schema as cs
16
14
 
17
15
  settings = get_settings()
18
16
 
17
+ def create_dynamic_schema(name: str, schema: dict[str, t.Any]) -> type[pydantic.BaseModel]:
18
+ field_definitions = {}
19
+ for k, v in schema["properties"].items():
20
+ field_type = str
21
+ if v["type"] == "number":
22
+ field_type = float
23
+ elif v["type"] == "integer":
24
+ field_type = int
25
+ elif v["type"] == "boolean":
26
+ field_type = bool
27
+ description = v.get("description") or ""
28
+ default_ = v.get("default")
29
+ fields = {}
30
+ if default_ is not None:
31
+ fields["default"] = default_
32
+ if description is not None:
33
+ fields["description"] = description
34
+ field_definitions[k] = (
35
+ field_type,
36
+ pydantic.Field(**fields)
37
+ )
38
+ return pydantic.create_model(
39
+ f"{name}Schema",
40
+ **field_definitions
41
+ )
19
42
 
20
- def create_schema_model(schema: dict[str, t.Any]) -> type[pydantic.BaseModel]:
21
- # Create a new model class that returns our JSON schema.
22
- # LangChain requires a BaseModel class.
23
- class Schema(pydantic.BaseModel):
24
- model_config = pydantic.ConfigDict(extra="allow")
25
-
26
- @t.override
27
- @classmethod
28
- def __get_pydantic_json_schema__(
29
- cls, core_schema: cs.CoreSchema, handler: pydantic.GetJsonSchemaHandler
30
- ) -> JsonSchemaValue:
31
- return schema
32
-
33
- return Schema
34
43
 
35
44
 
36
45
  class MCPClient:
@@ -42,7 +51,6 @@ class MCPClient:
42
51
  def list_tools(self) -> requests.Response:
43
52
  client = self.client.get_httpx_client()
44
53
  url = urllib.parse.urljoin(settings.mcp_hub_url, f"{self.server_name}/tools/list")
45
-
46
54
  response = client.request("GET", url, headers=self.headers)
47
55
  response.raise_for_status()
48
56
  return response
@@ -118,7 +126,7 @@ class MCPToolkit(BaseToolkit):
118
126
  client=self.client,
119
127
  name=tool.name,
120
128
  description=tool.description or "",
121
- args_schema=create_schema_model(tool.inputSchema),
129
+ args_schema=create_dynamic_schema(tool.name, tool.inputSchema),
122
130
  )
123
131
  # list_tools returns a PaginatedResult, but I don't see a way to pass the cursor to retrieve more tools
124
132
  for tool in self._tools.tools
@@ -1,33 +1,37 @@
1
1
  import asyncio
2
- import urllib.parse
3
2
  import warnings
4
- from typing import Any, Callable
3
+ from dataclasses import dataclass
4
+ from typing import Callable, Literal
5
5
 
6
6
  import pydantic
7
- import pydantic_core
8
7
  import typing_extensions as t
9
8
  from beamlit.api.functions import get_function
10
9
  from beamlit.authentication.authentication import AuthenticatedClient
11
- from beamlit.models.function import Function
12
- from langchain_core.tools.base import BaseTool, BaseToolkit, ToolException
13
- from pydantic.json_schema import JsonSchemaValue
14
- from pydantic_core import core_schema as cs
15
-
16
-
17
- def create_schema_model(schema: dict[str, t.Any]) -> type[pydantic.BaseModel]:
18
- # Create a new model class that returns our JSON schema.
19
- # LangChain requires a BaseModel class.
20
- class Schema(pydantic.BaseModel):
21
- model_config = pydantic.ConfigDict(extra="allow")
22
-
23
- @t.override
24
- @classmethod
25
- def __get_pydantic_json_schema__(
26
- cls, core_schema: cs.CoreSchema, handler: pydantic.GetJsonSchemaHandler
27
- ) -> JsonSchemaValue:
28
- return schema
29
-
30
- return Schema
10
+ from beamlit.common.settings import get_settings
11
+ from beamlit.models import Function, StoreFunctionParameter
12
+ from beamlit.run import RunClient
13
+ from langchain_core.tools.base import BaseTool, ToolException
14
+
15
+
16
+ def create_dynamic_schema(name: str, parameters: list[StoreFunctionParameter]) -> type[pydantic.BaseModel]:
17
+ field_definitions = {}
18
+ for param in parameters:
19
+ field_type = str
20
+ if param.type_ == "number":
21
+ field_type = float
22
+ elif param.type_ == "integer":
23
+ field_type = int
24
+ elif param.type_ == "boolean":
25
+ field_type = bool
26
+
27
+ field_definitions[param.name] = (
28
+ field_type,
29
+ pydantic.Field(description=param.description or "")
30
+ )
31
+ return pydantic.create_model(
32
+ f"{name}Schema",
33
+ **field_definitions
34
+ )
31
35
 
32
36
 
33
37
  class RemoteTool(BaseTool):
@@ -35,7 +39,9 @@ class RemoteTool(BaseTool):
35
39
  Remote tool
36
40
  """
37
41
 
38
- client: AuthenticatedClient
42
+ client: RunClient
43
+ resource_name: str
44
+ kit: bool = False
39
45
  handle_tool_error: bool | str | Callable[[ToolException], str] | None = True
40
46
 
41
47
  @t.override
@@ -48,12 +54,18 @@ class RemoteTool(BaseTool):
48
54
 
49
55
  @t.override
50
56
  async def _arun(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
51
- result = self.client.call_tool(self.name, arguments=kwargs)
52
- response = result.json()
53
- content = pydantic_core.to_json(response["content"]).decode()
54
- if response["isError"]:
55
- raise ToolException(content)
56
- return content
57
+ settings = get_settings()
58
+ body = {**kwargs}
59
+ if self.kit:
60
+ body["name"] = self.name
61
+ result = self.client.run(
62
+ "function",
63
+ self.resource_name,
64
+ settings.environment,
65
+ "POST",
66
+ json=body
67
+ )
68
+ return result.text
57
69
 
58
70
  @t.override
59
71
  @property
@@ -61,7 +73,8 @@ class RemoteTool(BaseTool):
61
73
  assert self.args_schema is not None # noqa: S101
62
74
  return self.args_schema
63
75
 
64
- class RemoteToolkit(BaseToolkit):
76
+ @dataclass
77
+ class RemoteToolkit:
65
78
  """
66
79
  Remote toolkit
67
80
  """
@@ -75,29 +88,35 @@ class RemoteToolkit(BaseToolkit):
75
88
  def initialize(self) -> None:
76
89
  """Initialize the session and retrieve tools list"""
77
90
  if self._function is None:
78
- self._function = get_function(self.function, client=self.client)
91
+ self._function = get_function.sync_detailed(self.function, client=self.client).parsed
79
92
 
80
93
  @t.override
81
94
  def get_tools(self) -> list[BaseTool]:
82
- if self._tools is None:
95
+ if self._function is None:
83
96
  raise RuntimeError("Must initialize the toolkit first")
84
97
 
85
98
  if self._function.spec.kit:
86
99
  return [
87
100
  RemoteTool(
88
- client=self.client,
89
- name=func.name,
90
- description=func.description or "",
91
- args_schema=create_schema_model(func.parameters),
101
+ client=RunClient(self.client),
102
+ name=func.name,
103
+ resource_name=self._function.metadata.name,
104
+ kit=True,
105
+ description=func.description or "",
106
+ args_schema=create_dynamic_schema(func.name, func.parameters),
92
107
  )
93
108
  for func in self._function.spec.kit
94
109
  ]
95
110
 
96
111
  return [
97
112
  RemoteTool(
98
- client=self.client,
113
+ client=RunClient(self.client),
99
114
  name=self._function.metadata.name,
115
+ resource_name=self._function.metadata.name,
100
116
  description=self._function.spec.description or "",
101
- args_schema=create_schema_model(self._function.spec.parameters),
117
+ args_schema=create_dynamic_schema(
118
+ self._function.metadata.name,
119
+ self._function.spec.parameters
120
+ ),
102
121
  )
103
122
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: beamlit
3
- Version: 0.0.28rc25
3
+ Version: 0.0.29rc27
4
4
  Summary: Add your description here
5
5
  Author-email: cploujoux <ch.ploujoux@gmail.com>
6
6
  Requires-Python: >=3.12
@@ -5,8 +5,9 @@ beamlit/py.typed,sha256=8ZJUsxZiuOy1oJeVhsTWQhTG_6pTVHVXk5hJL79ebTk,25
5
5
  beamlit/run.py,sha256=RLwMv5f_S8lw7Wq7O6DvEcOl0nGYh769qjGl_SmR9Z0,1338
6
6
  beamlit/types.py,sha256=E1hhDh_zXfsSQ0NCt9-uw90_Mr5iIlsdfnfvxv5HarU,1005
7
7
  beamlit/agents/__init__.py,sha256=nf1iwQwGtCG6nDqyVhxfWoqR6dv6X3bvSpCeqkTCFaM,101
8
+ beamlit/agents/chain.py,sha256=HzBs3nI4xaH86I_r-M-HGGp6roXsJxMx-qXl_GrJaY0,2831
8
9
  beamlit/agents/chat.py,sha256=gVyv4FGBdQTDhdutX8l64OUNa6Fdqaw4eCfEDRH0IPQ,3558
9
- beamlit/agents/decorator.py,sha256=WiE6zfRv4Ywf1wH-rUaGjBNGjIDRdgoEH_8Yl26IM6U,9263
10
+ beamlit/agents/decorator.py,sha256=4JFITRjRLuNpaaSTFkj1ZL5B2PQRXY-a5VBoZ_2NSPw,9479
10
11
  beamlit/api/__init__.py,sha256=zTSiG_ujSjAqWPyc435YXaX9XTlpMjiJWBbV-f-YtdA,45
11
12
  beamlit/api/agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
13
  beamlit/api/agents/create_agent.py,sha256=t5Pr62My2EhQlcIY71MrI73-0_q5Djr3a_Ybt9MIiQQ,3587
@@ -123,8 +124,8 @@ beamlit/api/workspaces/update_workspace.py,sha256=qa5DV2UJSUYuB_ibALb4E9ghKpT1Ha
123
124
  beamlit/api/workspaces/update_workspace_user_role.py,sha256=Yn9iuJ4tKtauzBiJyU4-wYUMS9g98X2Om8zs7UkzrY8,4917
124
125
  beamlit/authentication/__init__.py,sha256=wiXqRbc7E-ulrH_ueA9duOGFvXeo7-RvhSD1XbFogMo,1020
125
126
  beamlit/authentication/apikey.py,sha256=KNBTgdi0VBzBAAmSwU2X1QoB58vRbg8wkXb8-GTZCQo,657
126
- beamlit/authentication/authentication.py,sha256=a29-iD92T8_q8Kpx25VXEyyASlCaFsSg0s8y-SWPtns,3311
127
- beamlit/authentication/clientcredentials.py,sha256=6kbfTjwUkXUArJX8XZLe9ZzbEicQc19tSXBvsTpiXMk,3954
127
+ beamlit/authentication/authentication.py,sha256=ZH3zS7PVa9kw8szZTplwZvCbhBJ-oXHsxEtnAhKjxzM,3409
128
+ beamlit/authentication/clientcredentials.py,sha256=cxZPPu--CgizwqX0pdfFQ91gJt1EFKwyy-aBB_dXX7I,3990
128
129
  beamlit/authentication/credentials.py,sha256=p_1xenabCbQuRz7BiFk7oTK4uCxAt_zoyku5o-jcKGE,5343
129
130
  beamlit/authentication/device_mode.py,sha256=tmr22gllKOZwBRub_QjF5pYa425x-nE8tQNpZ_EGR6g,3644
130
131
  beamlit/common/__init__.py,sha256=yDoMJDKj-xjTGl7U1YI59KpWxiOV65HSiUulgO8xdTA,277
@@ -132,7 +133,7 @@ beamlit/common/generate.py,sha256=LtdCju_QayRS4lZrrb_0VHqWWvTcv4Mbf-iV1TB_Qko,75
132
133
  beamlit/common/instrumentation.py,sha256=MsBDfFcMYqGDiHHj4j5hLHE4EWxZExkhmCeFS3SKzJY,3181
133
134
  beamlit/common/logger.py,sha256=VFRbaZh93n8ZGugeeYKe88IP2nI3g2JNa7XN4j8wVJE,1116
134
135
  beamlit/common/secrets.py,sha256=sid81bOe3LflkMKDHwBsBs9nIju8bp5-v9qU9gkyNMc,212
135
- beamlit/common/settings.py,sha256=Z-UBDQKp_pAusWgwmoIwK5nl_z9xfOYYEeCCuYafr0M,5821
136
+ beamlit/common/settings.py,sha256=uge6-VTioSILA9lgPpKc5V7MaSEMuHYHfCQhwdqHteE,5831
136
137
  beamlit/common/utils.py,sha256=jouz5igBvT37Xn_e94-foCHyQczVim-UzVcoIF6RWJ4,657
137
138
  beamlit/deploy/__init__.py,sha256=GS7l7Jtm2yKs7iNLKcfjYO-rAhUzggQ3xiYSf3oxLBY,91
138
139
  beamlit/deploy/deploy.py,sha256=rXpiAqlO36Ul1eadBEiwiil-6R2DkbaYHzw-qcqZ0g4,9183
@@ -146,8 +147,8 @@ beamlit/functions/github/kit/__init__.py,sha256=jBwPqZv6C23_utukohxqXZwrlicNlI7P
146
147
  beamlit/functions/github/kit/pull_request.py,sha256=wQVeRBakiqu-2ouflO8p1z7D5u07KNsitwyNRrp0KjM,1357
147
148
  beamlit/functions/math/__init__.py,sha256=wie4WME8jT-WpFRrtu-lDlHW31Mg6K2cwstjkUdLF3o,43
148
149
  beamlit/functions/math/math.py,sha256=CpoLJGwuvwCPGnVC8k9GYuIyvfUYPDQHKlZg3cx-z-A,1049
149
- beamlit/functions/mcp/mcp.py,sha256=uGqNOwEgIKdvtVPmCf9Nf_YI_k7zioLvc6jVOpszrg4,4104
150
- beamlit/functions/remote/remote.py,sha256=W60VNSNfBcVed8HVifOjcWN20StmhDjSV06AT35STaY,3276
150
+ beamlit/functions/mcp/mcp.py,sha256=gXzvUPAKmvGQPHUKTVzMiINukxpw1BV6H8M2iOWvIGU,4348
151
+ beamlit/functions/remote/remote.py,sha256=x2eHh4oYkWwHuZWwg5XeylY-E9Opa6SzfN_0396ePZw,3775
151
152
  beamlit/functions/search/__init__.py,sha256=5NAthQ9PBwrkNg1FpLRx4flauvv0HyWuwaVS589c1Pw,49
152
153
  beamlit/functions/search/search.py,sha256=8s9ECltq7YE17j6rTxb12uY2EQY4_eTLHmwlIMThI0w,515
153
154
  beamlit/models/__init__.py,sha256=O16oX1-oBJqB5LZEHAfu_hZZZh6_PX2LqZpVl4fNQRs,7730
@@ -253,6 +254,6 @@ beamlit/serve/app.py,sha256=OpwPjRdyHZK6J-ziPwhiRDGGa2mvCrFVcBFE6alJVOM,3071
253
254
  beamlit/serve/middlewares/__init__.py,sha256=1dVmnOmhAQWvWktqHkKSIX-YoF6fmMU8xkUQuhg_rJU,148
254
255
  beamlit/serve/middlewares/accesslog.py,sha256=Mu4T4_9OvHybjA0ApzZFpgi2C8f3X1NbUk-76v634XM,631
255
256
  beamlit/serve/middlewares/processtime.py,sha256=lDAaIasZ4bwvN-HKHvZpaD9r-yrkVNZYx4abvbjbrCg,411
256
- beamlit-0.0.28rc25.dist-info/METADATA,sha256=JLFUlrvUKeIJWwQT1lX7St9EBkt7-xRZwQu_UGKUotg,2405
257
- beamlit-0.0.28rc25.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
258
- beamlit-0.0.28rc25.dist-info/RECORD,,
257
+ beamlit-0.0.29rc27.dist-info/METADATA,sha256=cG70rj7h-Axm61rRZWHeKjHwb18SayvRGmbOjxnczAs,2405
258
+ beamlit-0.0.29rc27.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
259
+ beamlit-0.0.29rc27.dist-info/RECORD,,