beamlit 0.0.55rc103__py3-none-any.whl → 0.0.56rc104__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/chain.py +14 -1
- beamlit/agents/decorator.py +6 -3
- beamlit/common/settings.py +2 -0
- beamlit/functions/common.py +16 -0
- beamlit/functions/local/local.py +49 -0
- beamlit/functions/mcp/mcp.py +64 -74
- beamlit/functions/mcp/utils.py +56 -0
- beamlit/functions/remote/remote.py +17 -4
- beamlit/run.py +25 -9
- {beamlit-0.0.55rc103.dist-info → beamlit-0.0.56rc104.dist-info}/METADATA +1 -1
- {beamlit-0.0.55rc103.dist-info → beamlit-0.0.56rc104.dist-info}/RECORD +14 -12
- {beamlit-0.0.55rc103.dist-info → beamlit-0.0.56rc104.dist-info}/WHEEL +0 -0
- {beamlit-0.0.55rc103.dist-info → beamlit-0.0.56rc104.dist-info}/entry_points.txt +0 -0
- {beamlit-0.0.55rc103.dist-info → beamlit-0.0.56rc104.dist-info}/licenses/LICENSE +0 -0
beamlit/agents/chain.py
CHANGED
@@ -2,7 +2,7 @@ import asyncio
|
|
2
2
|
import warnings
|
3
3
|
from dataclasses import dataclass
|
4
4
|
from typing import Callable
|
5
|
-
|
5
|
+
import os
|
6
6
|
import pydantic
|
7
7
|
import typing_extensions as t
|
8
8
|
from langchain_core.tools.base import BaseTool, ToolException
|
@@ -21,6 +21,8 @@ class ChainTool(BaseTool):
|
|
21
21
|
|
22
22
|
client: RunClient
|
23
23
|
handle_tool_error: bool | str | Callable[[ToolException], str] | None = True
|
24
|
+
_cloud: bool = False
|
25
|
+
_service_name: str | None = None
|
24
26
|
|
25
27
|
@t.override
|
26
28
|
def _run(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
|
@@ -58,6 +60,8 @@ class ChainTool(BaseTool):
|
|
58
60
|
self.name,
|
59
61
|
settings.environment,
|
60
62
|
"POST",
|
63
|
+
cloud=self._cloud,
|
64
|
+
service_name=self._service_name,
|
61
65
|
json=kwargs,
|
62
66
|
)
|
63
67
|
return result.text
|
@@ -91,6 +95,8 @@ class ChainToolkit:
|
|
91
95
|
|
92
96
|
client: AuthenticatedClient
|
93
97
|
chain: list[AgentChain]
|
98
|
+
_cloud: bool = False
|
99
|
+
_service_name: str | None = None
|
94
100
|
_chain: list[Agent] | None = None
|
95
101
|
|
96
102
|
model_config = pydantic.ConfigDict(arbitrary_types_allowed=True)
|
@@ -103,6 +109,8 @@ class ChainToolkit:
|
|
103
109
|
RuntimeError: If initialization fails due to missing agents.
|
104
110
|
"""
|
105
111
|
"""Initialize the session and retrieve tools list"""
|
112
|
+
settings = get_settings()
|
113
|
+
self._cloud = settings.cloud
|
106
114
|
if self._chain is None:
|
107
115
|
agents = list_agents.sync_detailed(
|
108
116
|
client=self.client,
|
@@ -111,6 +119,9 @@ class ChainToolkit:
|
|
111
119
|
agents_chain = []
|
112
120
|
for chain in chain_enabled:
|
113
121
|
agent = [agent for agent in agents if agent.metadata.name == chain.name]
|
122
|
+
agent_name = agent[0].metadata.name.upper().replace("-", "_")
|
123
|
+
if os.getenv(f"BL_AGENT_{agent_name}_SERVICE_NAME"):
|
124
|
+
self._service_name = os.getenv(f"BL_AGENT_{agent_name}_SERVICE_NAME")
|
114
125
|
if agent:
|
115
126
|
agent[0].spec.prompt = chain.prompt or agent[0].spec.prompt
|
116
127
|
agent[0].spec.description = chain.description or agent[0].spec.description
|
@@ -136,6 +147,8 @@ class ChainToolkit:
|
|
136
147
|
name=agent.metadata.name,
|
137
148
|
description=agent.spec.description or agent.spec.prompt or "",
|
138
149
|
args_schema=ChainInput,
|
150
|
+
cloud=self._cloud,
|
151
|
+
service_name=self._service_name,
|
139
152
|
)
|
140
153
|
for agent in self._chain
|
141
154
|
]
|
beamlit/agents/decorator.py
CHANGED
@@ -29,6 +29,7 @@ def agent(
|
|
29
29
|
override_agent=None,
|
30
30
|
override_functions=None,
|
31
31
|
remote_functions=None,
|
32
|
+
local_functions=None,
|
32
33
|
) -> Callable:
|
33
34
|
"""
|
34
35
|
A decorator factory that configures and wraps functions to integrate with Beamlit agents.
|
@@ -40,6 +41,7 @@ def agent(
|
|
40
41
|
override_agent (Any, optional): An optional agent instance to override the default agent.
|
41
42
|
mcp_hub (Any, optional): An optional MCP hub configuration.
|
42
43
|
remote_functions (Any, optional): An optional list of remote functions to be integrated.
|
44
|
+
local_functions (Any, optional): An optional list of local functions to be integrated.
|
43
45
|
|
44
46
|
Returns:
|
45
47
|
Callable: A decorator that wraps the target function, injecting agent-related configurations and dependencies.
|
@@ -130,9 +132,10 @@ def agent(
|
|
130
132
|
dir=settings.agent.functions_directory,
|
131
133
|
remote_functions=remote_functions,
|
132
134
|
chain=agent.spec.agent_chain,
|
133
|
-
|
134
|
-
|
135
|
-
|
135
|
+
local_functions=local_functions,
|
136
|
+
remote_functions_empty=not remote_functions,
|
137
|
+
warning=chat_model is not None,
|
138
|
+
)
|
136
139
|
|
137
140
|
settings.agent.functions = functions
|
138
141
|
|
beamlit/common/settings.py
CHANGED
@@ -83,9 +83,11 @@ class Settings(BaseSettings):
|
|
83
83
|
base_url: str = Field(default="https://api.beamlit.com/v0")
|
84
84
|
app_url: str = Field(default="https://app.beamlit.com")
|
85
85
|
run_url: str = Field(default="https://run.beamlit.com")
|
86
|
+
run_internal_hostname: str = Field(default="internal.run.beamlit.com")
|
86
87
|
registry_url: str = Field(default="https://us.registry.beamlit.com")
|
87
88
|
log_level: str = Field(default="INFO")
|
88
89
|
enable_opentelemetry: bool = Field(default=False)
|
90
|
+
cloud: bool = Field(default=False)
|
89
91
|
agent: SettingsAgent = SettingsAgent()
|
90
92
|
server: SettingsServer = SettingsServer()
|
91
93
|
authentication: SettingsAuthentication = SettingsAuthentication()
|
beamlit/functions/common.py
CHANGED
@@ -28,6 +28,7 @@ from beamlit.authentication import new_client
|
|
28
28
|
from beamlit.client import AuthenticatedClient
|
29
29
|
from beamlit.common import slugify
|
30
30
|
from beamlit.common.settings import get_settings
|
31
|
+
from beamlit.functions.local.local import LocalToolKit
|
31
32
|
from beamlit.functions.remote.remote import RemoteToolkit
|
32
33
|
from beamlit.models import AgentChain
|
33
34
|
|
@@ -35,10 +36,12 @@ logger = getLogger(__name__)
|
|
35
36
|
|
36
37
|
def get_functions(
|
37
38
|
remote_functions: Union[list[str], None] = None,
|
39
|
+
local_functions: Union[list[dict], None] = None,
|
38
40
|
client: Union[AuthenticatedClient, None] = None,
|
39
41
|
dir: Union[str, None] = None,
|
40
42
|
chain: Union[list[AgentChain], None] = None,
|
41
43
|
remote_functions_empty: bool = True,
|
44
|
+
local_functions_empty: bool = True,
|
42
45
|
from_decorator: str = "function",
|
43
46
|
warning: bool = True,
|
44
47
|
):
|
@@ -140,6 +143,7 @@ def get_functions(
|
|
140
143
|
client=client,
|
141
144
|
dir=os.path.join(root),
|
142
145
|
remote_functions_empty=remote_functions_empty,
|
146
|
+
local_functions_empty=local_functions_empty,
|
143
147
|
from_decorator="kit",
|
144
148
|
)
|
145
149
|
functions.extend(kit_functions)
|
@@ -187,6 +191,18 @@ def get_functions(
|
|
187
191
|
f"Traceback:\n{traceback.format_exc()}"
|
188
192
|
)
|
189
193
|
logger.warn(f"Failed to initialize remote function {function}: {e!s}")
|
194
|
+
if local_functions:
|
195
|
+
for function in local_functions:
|
196
|
+
try:
|
197
|
+
toolkit = LocalToolKit(client, function)
|
198
|
+
toolkit.initialize()
|
199
|
+
functions.extend(toolkit.get_tools())
|
200
|
+
except Exception as e:
|
201
|
+
logger.debug(
|
202
|
+
f"Failed to initialize local function {function}: {e!s}\n"
|
203
|
+
f"Traceback:\n{traceback.format_exc()}"
|
204
|
+
)
|
205
|
+
logger.warn(f"Failed to initialize local function {function}: {e!s}")
|
190
206
|
|
191
207
|
if chain:
|
192
208
|
toolkit = ChainToolkit(client, chain)
|
@@ -0,0 +1,49 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
|
3
|
+
import pydantic
|
4
|
+
from langchain_core.tools.base import BaseTool
|
5
|
+
|
6
|
+
from beamlit.authentication.authentication import AuthenticatedClient
|
7
|
+
from beamlit.functions.mcp.mcp import MCPClient, MCPToolkit
|
8
|
+
from beamlit.models import Function
|
9
|
+
|
10
|
+
|
11
|
+
@dataclass
|
12
|
+
class LocalToolKit:
|
13
|
+
"""
|
14
|
+
Toolkit for managing local tools.
|
15
|
+
|
16
|
+
Attributes:
|
17
|
+
client (AuthenticatedClient): The authenticated client instance.
|
18
|
+
function (str): The name of the local function to integrate.
|
19
|
+
_function (Function | None): Cached Function object after initialization.
|
20
|
+
"""
|
21
|
+
client: AuthenticatedClient
|
22
|
+
local_function: dict
|
23
|
+
_function: Function | None = None
|
24
|
+
model_config = pydantic.ConfigDict(arbitrary_types_allowed=True)
|
25
|
+
|
26
|
+
def initialize(self) -> None:
|
27
|
+
"""Initialize the session and retrieve the local function details."""
|
28
|
+
if self._function is None:
|
29
|
+
try:
|
30
|
+
# For local functions, we directly create the Function object
|
31
|
+
# based on the local function name
|
32
|
+
self._function = Function(
|
33
|
+
metadata={"name": self.local_function['name']},
|
34
|
+
spec={
|
35
|
+
"configurations": {
|
36
|
+
"url": self.local_function['url'],
|
37
|
+
"sse": self.local_function['sse'],
|
38
|
+
},
|
39
|
+
"description": self.local_function['description'] or "",
|
40
|
+
}
|
41
|
+
)
|
42
|
+
except Exception as e:
|
43
|
+
raise RuntimeError(f"Failed to initialize local function: {e}")
|
44
|
+
|
45
|
+
def get_tools(self) -> list[BaseTool]:
|
46
|
+
mcp_client = MCPClient(self.client, self._function.spec["configurations"]["url"], sse=self._function.spec["configurations"]["sse"])
|
47
|
+
mcp_toolkit = MCPToolkit(client=mcp_client)
|
48
|
+
mcp_toolkit.initialize()
|
49
|
+
return mcp_toolkit.get_tools()
|
beamlit/functions/mcp/mcp.py
CHANGED
@@ -4,91 +4,87 @@ It includes classes for managing MCP clients, creating dynamic schemas, and inte
|
|
4
4
|
"""
|
5
5
|
|
6
6
|
import asyncio
|
7
|
+
import logging
|
7
8
|
import warnings
|
8
|
-
from typing import Any, Callable
|
9
|
+
from typing import Any, AsyncIterator, Callable
|
9
10
|
|
10
11
|
import pydantic
|
11
12
|
import pydantic_core
|
12
13
|
import requests
|
13
14
|
import typing_extensions as t
|
14
15
|
from langchain_core.tools.base import BaseTool, BaseToolkit, ToolException
|
16
|
+
from mcp import ClientSession
|
17
|
+
from mcp.client.sse import sse_client
|
15
18
|
from mcp.types import CallToolResult, ListToolsResult
|
16
|
-
from pydantic.json_schema import JsonSchemaValue
|
17
|
-
from pydantic_core import core_schema as cs
|
18
19
|
|
19
20
|
from beamlit.authentication.authentication import AuthenticatedClient
|
20
21
|
from beamlit.common.settings import get_settings
|
21
22
|
|
22
|
-
|
23
|
+
from .utils import create_schema_model
|
23
24
|
|
24
|
-
|
25
|
-
"integer": int,
|
26
|
-
"number": float,
|
27
|
-
"array": list,
|
28
|
-
"object": dict,
|
29
|
-
"boolean": bool,
|
30
|
-
"string": str,
|
31
|
-
"null": type(None),
|
32
|
-
}
|
33
|
-
|
34
|
-
FIELD_DEFAULTS = {
|
35
|
-
int: 0,
|
36
|
-
float: 0.0,
|
37
|
-
list: [],
|
38
|
-
bool: False,
|
39
|
-
str: "",
|
40
|
-
type(None): None,
|
41
|
-
}
|
42
|
-
|
43
|
-
def configure_field(name: str, type_: dict[str, t.Any], required: list[str]) -> tuple[type, t.Any]:
|
44
|
-
field_type = TYPE_MAP[type_["type"]]
|
45
|
-
default_ = FIELD_DEFAULTS.get(field_type) if name not in required else ...
|
46
|
-
return field_type, default_
|
47
|
-
|
48
|
-
def create_schema_model(name: str, schema: dict[str, t.Any]) -> type[pydantic.BaseModel]:
|
49
|
-
# Create a new model class that returns our JSON schema.
|
50
|
-
# LangChain requires a BaseModel class.
|
51
|
-
class SchemaBase(pydantic.BaseModel):
|
52
|
-
model_config = pydantic.ConfigDict(extra="allow")
|
53
|
-
|
54
|
-
@t.override
|
55
|
-
@classmethod
|
56
|
-
def __get_pydantic_json_schema__(
|
57
|
-
cls, core_schema: cs.CoreSchema, handler: pydantic.GetJsonSchemaHandler
|
58
|
-
) -> JsonSchemaValue:
|
59
|
-
return schema
|
60
|
-
|
61
|
-
# Since this langchain patch, we need to synthesize pydantic fields from the schema
|
62
|
-
# https://github.com/langchain-ai/langchain/commit/033ac417609297369eb0525794d8b48a425b8b33
|
63
|
-
required = schema.get("required", [])
|
64
|
-
fields: dict[str, t.Any] = {
|
65
|
-
name: configure_field(name, type_, required) for name, type_ in schema["properties"].items()
|
66
|
-
}
|
67
|
-
|
68
|
-
return pydantic.create_model(f"{name}Schema", __base__=SchemaBase, **fields)
|
25
|
+
settings = get_settings()
|
69
26
|
|
27
|
+
logger = logging.getLogger(__name__)
|
70
28
|
|
71
29
|
|
72
30
|
class MCPClient:
|
73
31
|
def __init__(self, client: AuthenticatedClient, url: str):
|
74
32
|
self.client = client
|
75
33
|
self.url = url
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
34
|
+
self._sse = False
|
35
|
+
|
36
|
+
async def list_sse_tools(self) -> ListToolsResult:
|
37
|
+
# Create a new context for each SSE connection
|
38
|
+
try:
|
39
|
+
async with sse_client(f"{self.url}/sse") as (read_stream, write_stream):
|
40
|
+
async with ClientSession(read_stream, write_stream) as session:
|
41
|
+
await session.initialize()
|
42
|
+
response = await session.list_tools()
|
43
|
+
return response
|
44
|
+
except Exception:
|
45
|
+
self._sse = False
|
46
|
+
logger.info("SSE not available, trying HTTP")
|
47
|
+
return None # Signal to list_tools() to try HTTP instead
|
48
|
+
|
49
|
+
def list_tools(self) -> ListToolsResult:
|
50
|
+
try:
|
51
|
+
loop = asyncio.get_event_loop()
|
52
|
+
result = loop.run_until_complete(self.list_sse_tools())
|
53
|
+
if result is None: # SSE failed, try HTTP
|
54
|
+
raise Exception("SSE failed")
|
55
|
+
self._sse = True
|
56
|
+
return result
|
57
|
+
except Exception: # Fallback to HTTP
|
58
|
+
client = self.client.get_httpx_client()
|
59
|
+
response = client.request("GET", f"{self.url}/tools/list")
|
60
|
+
response.raise_for_status()
|
61
|
+
return ListToolsResult(**response.json())
|
62
|
+
|
63
|
+
async def call_tool(
|
64
|
+
self,
|
65
|
+
tool_name: str,
|
66
|
+
arguments: dict[str, Any] = None,
|
67
|
+
) -> requests.Response | AsyncIterator[CallToolResult]:
|
68
|
+
if self._sse:
|
69
|
+
async with sse_client(f"{self.url}/sse") as (read_stream, write_stream):
|
70
|
+
async with ClientSession(read_stream, write_stream) as session:
|
71
|
+
await session.initialize()
|
72
|
+
response = await session.call_tool(tool_name, arguments or {})
|
73
|
+
content = pydantic_core.to_json(response).decode()
|
74
|
+
return content
|
75
|
+
else: # Fallback to HTTP
|
76
|
+
client = self.client.get_httpx_client()
|
77
|
+
response = client.request(
|
78
|
+
"POST",
|
79
|
+
f"{self.url}/tools/call",
|
80
|
+
json={"name": tool_name, "arguments": arguments},
|
81
|
+
)
|
82
|
+
response.raise_for_status()
|
83
|
+
result = CallToolResult(response.json())
|
84
|
+
if result.isError:
|
85
|
+
raise ToolException(result.content)
|
86
|
+
content = pydantic_core.to_json(result.content).decode()
|
87
|
+
return content
|
92
88
|
|
93
89
|
class MCPTool(BaseTool):
|
94
90
|
"""
|
@@ -97,6 +93,7 @@ class MCPTool(BaseTool):
|
|
97
93
|
Attributes:
|
98
94
|
client (MCPClient): The MCP client instance.
|
99
95
|
handle_tool_error (bool | str | Callable[[ToolException], str] | None): Error handling strategy.
|
96
|
+
sse (bool): Whether to use SSE streaming for responses.
|
100
97
|
"""
|
101
98
|
|
102
99
|
client: MCPClient
|
@@ -112,13 +109,7 @@ class MCPTool(BaseTool):
|
|
112
109
|
|
113
110
|
@t.override
|
114
111
|
async def _arun(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
|
115
|
-
|
116
|
-
response = result.json()
|
117
|
-
result = CallToolResult(**response)
|
118
|
-
if result.isError:
|
119
|
-
raise ToolException(result.content)
|
120
|
-
content = pydantic_core.to_json(result.content).decode()
|
121
|
-
return content
|
112
|
+
return await self.client.call_tool(self.name, arguments=kwargs)
|
122
113
|
|
123
114
|
@t.override
|
124
115
|
@property
|
@@ -139,14 +130,13 @@ class MCPToolkit(BaseToolkit):
|
|
139
130
|
"""The MCP session used to obtain the tools"""
|
140
131
|
|
141
132
|
_tools: ListToolsResult | None = None
|
142
|
-
|
143
133
|
model_config = pydantic.ConfigDict(arbitrary_types_allowed=True)
|
144
134
|
|
145
135
|
def initialize(self) -> None:
|
146
136
|
"""Initialize the session and retrieve tools list"""
|
147
137
|
if self._tools is None:
|
148
138
|
response = self.client.list_tools()
|
149
|
-
self._tools =
|
139
|
+
self._tools = response
|
150
140
|
|
151
141
|
@t.override
|
152
142
|
def get_tools(self) -> list[BaseTool]:
|
@@ -0,0 +1,56 @@
|
|
1
|
+
"""
|
2
|
+
This module provides functionalities to interact with MCP (Multi-Client Platform) servers.
|
3
|
+
It includes classes for managing MCP clients, creating dynamic schemas, and integrating MCP tools into Beamlit.
|
4
|
+
"""
|
5
|
+
import pydantic
|
6
|
+
import typing_extensions as t
|
7
|
+
from pydantic.json_schema import JsonSchemaValue
|
8
|
+
from pydantic_core import core_schema as cs
|
9
|
+
|
10
|
+
TYPE_MAP = {
|
11
|
+
"integer": int,
|
12
|
+
"number": float,
|
13
|
+
"array": list,
|
14
|
+
"object": dict,
|
15
|
+
"boolean": bool,
|
16
|
+
"string": str,
|
17
|
+
"null": type(None),
|
18
|
+
}
|
19
|
+
|
20
|
+
FIELD_DEFAULTS = {
|
21
|
+
int: 0,
|
22
|
+
float: 0.0,
|
23
|
+
list: [],
|
24
|
+
bool: False,
|
25
|
+
str: "",
|
26
|
+
type(None): None,
|
27
|
+
}
|
28
|
+
|
29
|
+
def configure_field(name: str, type_: dict[str, t.Any], required: list[str]) -> tuple[type, t.Any]:
|
30
|
+
field_type = TYPE_MAP[type_["type"]]
|
31
|
+
default_ = FIELD_DEFAULTS.get(field_type) if name not in required else ...
|
32
|
+
return field_type, default_
|
33
|
+
|
34
|
+
def create_schema_model(name: str, schema: dict[str, t.Any]) -> type[pydantic.BaseModel]:
|
35
|
+
# Create a new model class that returns our JSON schema.
|
36
|
+
# LangChain requires a BaseModel class.
|
37
|
+
class SchemaBase(pydantic.BaseModel):
|
38
|
+
model_config = pydantic.ConfigDict(extra="allow")
|
39
|
+
|
40
|
+
@t.override
|
41
|
+
@classmethod
|
42
|
+
def __get_pydantic_json_schema__(
|
43
|
+
cls, core_schema: cs.CoreSchema, handler: pydantic.GetJsonSchemaHandler
|
44
|
+
) -> JsonSchemaValue:
|
45
|
+
return schema
|
46
|
+
|
47
|
+
# Since this langchain patch, we need to synthesize pydantic fields from the schema
|
48
|
+
# https://github.com/langchain-ai/langchain/commit/033ac417609297369eb0525794d8b48a425b8b33
|
49
|
+
required = schema.get("required", [])
|
50
|
+
fields: dict[str, t.Any] = {
|
51
|
+
name: configure_field(name, type_, required) for name, type_ in schema["properties"].items()
|
52
|
+
}
|
53
|
+
|
54
|
+
return pydantic.create_model(f"{name}Schema", __base__=SchemaBase, **fields)
|
55
|
+
|
56
|
+
|
@@ -7,11 +7,10 @@ import asyncio
|
|
7
7
|
import warnings
|
8
8
|
from dataclasses import dataclass
|
9
9
|
from typing import Callable
|
10
|
+
import os
|
10
11
|
|
11
12
|
import pydantic
|
12
13
|
import typing_extensions as t
|
13
|
-
from langchain_core.tools.base import BaseTool, ToolException
|
14
|
-
|
15
14
|
from beamlit.api.functions import get_function, list_functions
|
16
15
|
from beamlit.authentication.authentication import AuthenticatedClient
|
17
16
|
from beamlit.common.settings import get_settings
|
@@ -19,6 +18,7 @@ from beamlit.errors import UnexpectedStatus
|
|
19
18
|
from beamlit.functions.mcp.mcp import MCPClient, MCPToolkit
|
20
19
|
from beamlit.models import Function, StoreFunctionParameter
|
21
20
|
from beamlit.run import RunClient
|
21
|
+
from langchain_core.tools.base import BaseTool, ToolException
|
22
22
|
|
23
23
|
|
24
24
|
def create_dynamic_schema(name: str, parameters: list[StoreFunctionParameter]) -> type[pydantic.BaseModel]:
|
@@ -67,7 +67,8 @@ class RemoteTool(BaseTool):
|
|
67
67
|
resource_name: str
|
68
68
|
kit: bool = False
|
69
69
|
handle_tool_error: bool | str | Callable[[ToolException], str] | None = True
|
70
|
-
|
70
|
+
service_name: str | None = None
|
71
|
+
cloud: bool = False
|
71
72
|
@t.override
|
72
73
|
def _run(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
|
73
74
|
warnings.warn(
|
@@ -87,7 +88,9 @@ class RemoteTool(BaseTool):
|
|
87
88
|
self.resource_name,
|
88
89
|
settings.environment,
|
89
90
|
"POST",
|
90
|
-
|
91
|
+
cloud=self.cloud,
|
92
|
+
service_name=self.service_name,
|
93
|
+
json=body,
|
91
94
|
)
|
92
95
|
return result.text
|
93
96
|
|
@@ -110,6 +113,7 @@ class RemoteToolkit:
|
|
110
113
|
client: AuthenticatedClient
|
111
114
|
function: str
|
112
115
|
_function: Function | None = None
|
116
|
+
_service_name: str | None = None
|
113
117
|
model_config = pydantic.ConfigDict(arbitrary_types_allowed=True)
|
114
118
|
|
115
119
|
def initialize(self) -> None:
|
@@ -117,6 +121,9 @@ class RemoteToolkit:
|
|
117
121
|
if self._function is None:
|
118
122
|
try:
|
119
123
|
response = get_function.sync_detailed(self.function, client=self.client)
|
124
|
+
function_name = self.function.upper().replace("-", "_")
|
125
|
+
if os.getenv(f"BL_FUNCTION_{function_name}_SERVICE_NAME"):
|
126
|
+
self._service_name = os.getenv(f"BL_FUNCTION_{function_name}_SERVICE_NAME")
|
120
127
|
self._function = response.parsed
|
121
128
|
except UnexpectedStatus as e:
|
122
129
|
settings = get_settings()
|
@@ -139,6 +146,8 @@ class RemoteToolkit:
|
|
139
146
|
|
140
147
|
if self._function.spec.integration_connections:
|
141
148
|
url = f"{settings.run_url}/{settings.workspace}/functions/{self._function.metadata.name}"
|
149
|
+
if self._service_name:
|
150
|
+
url = f"https://{self._service_name}.{settings.run_internal_hostname}"
|
142
151
|
mcp_client = MCPClient(self.client, url)
|
143
152
|
mcp_toolkit = MCPToolkit(client=mcp_client)
|
144
153
|
mcp_toolkit.initialize()
|
@@ -153,6 +162,8 @@ class RemoteToolkit:
|
|
153
162
|
kit=True,
|
154
163
|
description=func.description or "",
|
155
164
|
args_schema=create_dynamic_schema(func.name, func.parameters),
|
165
|
+
cloud=settings.cloud,
|
166
|
+
service_name=self._service_name,
|
156
167
|
)
|
157
168
|
for func in self._function.spec.kit
|
158
169
|
]
|
@@ -167,5 +178,7 @@ class RemoteToolkit:
|
|
167
178
|
self._function.metadata.name,
|
168
179
|
self._function.spec.parameters
|
169
180
|
),
|
181
|
+
cloud=settings.cloud,
|
182
|
+
service_name=self._service_name,
|
170
183
|
)
|
171
184
|
]
|
beamlit/run.py
CHANGED
@@ -48,6 +48,8 @@ class RunClient:
|
|
48
48
|
json: dict[str, Any] | None = None,
|
49
49
|
data: str | None = None,
|
50
50
|
params: dict[str, str] | None = None,
|
51
|
+
cloud: bool = False,
|
52
|
+
service_name: str | None = None,
|
51
53
|
) -> requests.Response:
|
52
54
|
"""Execute an HTTP request against a Beamlit resource.
|
53
55
|
|
@@ -61,6 +63,8 @@ class RunClient:
|
|
61
63
|
json (dict[str, Any] | None, optional): JSON payload to send with the request. Defaults to None.
|
62
64
|
data (str | None, optional): Raw data to send with the request. Defaults to None.
|
63
65
|
params (dict[str, str] | None, optional): Query parameters to include in the URL. Defaults to None.
|
66
|
+
cloud (bool, optional): Whether to use the cloud endpoint. Defaults to False.
|
67
|
+
service_name (str | None, optional): The name of the service to use. Defaults to None.
|
64
68
|
|
65
69
|
Returns:
|
66
70
|
requests.Response: The HTTP response from the server.
|
@@ -72,14 +76,17 @@ class RunClient:
|
|
72
76
|
headers = headers or {}
|
73
77
|
params = params or {}
|
74
78
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
79
|
+
if cloud and path and service_name:
|
80
|
+
url = f"https://{service_name}.{settings.run_internal_hostname}/{path}"
|
81
|
+
|
82
|
+
if cloud and not path and service_name:
|
83
|
+
url = f"https://{service_name}.{settings.run_internal_hostname}"
|
80
84
|
|
81
|
-
|
82
|
-
|
85
|
+
if not cloud and path:
|
86
|
+
url = urllib.parse.urljoin(settings.run_url, f"{settings.workspace}/{resource_type}s/{resource_name}/{path}")
|
87
|
+
|
88
|
+
if not cloud and not path:
|
89
|
+
url = urllib.parse.urljoin(settings.run_url, f"{settings.workspace}/{resource_type}s/{resource_name}")
|
83
90
|
|
84
91
|
kwargs = {
|
85
92
|
"headers": headers,
|
@@ -90,7 +97,16 @@ class RunClient:
|
|
90
97
|
if json:
|
91
98
|
kwargs["json"] = json
|
92
99
|
|
93
|
-
response = client.request(method, url, **kwargs)
|
94
|
-
if response.status_code >= 400:
|
100
|
+
response = self.client.request(method, url, **kwargs)
|
101
|
+
if response.status_code >= 400 and not cloud:
|
95
102
|
raise HTTPError(response.status_code, response.text)
|
103
|
+
if response.status_code >= 400 and cloud: # Redirect to the public endpoint if the resource is in the cloud and the request fails
|
104
|
+
if path:
|
105
|
+
url = urllib.parse.urljoin(settings.run_url, f"{settings.workspace}/{resource_type}s/{resource_name}/{path}")
|
106
|
+
else:
|
107
|
+
url = urllib.parse.urljoin(settings.run_url, f"{settings.workspace}/{resource_type}s/{resource_name}")
|
108
|
+
response = self.client.request(method, url, **kwargs)
|
109
|
+
if response.status_code >= 400:
|
110
|
+
raise HTTPError(response.status_code, response.text)
|
111
|
+
return response
|
96
112
|
return response
|
@@ -2,12 +2,12 @@ beamlit/__init__.py,sha256=545gFC-wLLwUktWcOAjUWe_Glha40tBetRTOYSfHnbI,164
|
|
2
2
|
beamlit/client.py,sha256=PnR6ybZk5dLIJPnDKAf2epHOeQC_7yL0fG4muvphHjA,12695
|
3
3
|
beamlit/errors.py,sha256=gO8GBmKqmSNgAg-E5oT-oOyxztvp7V_6XG7OUTT15q0,546
|
4
4
|
beamlit/py.typed,sha256=8ZJUsxZiuOy1oJeVhsTWQhTG_6pTVHVXk5hJL79ebTk,25
|
5
|
-
beamlit/run.py,sha256=
|
5
|
+
beamlit/run.py,sha256=PNkL1JEd_gZ6BMo_SA0u8TaczRyFz001qcmIOdkw5NM,4645
|
6
6
|
beamlit/types.py,sha256=E1hhDh_zXfsSQ0NCt9-uw90_Mr5iIlsdfnfvxv5HarU,1005
|
7
7
|
beamlit/agents/__init__.py,sha256=bWsFaXUbAps3IsL3Prti89m1s714vICXodbQi77h3vY,206
|
8
|
-
beamlit/agents/chain.py,sha256=
|
8
|
+
beamlit/agents/chain.py,sha256=DrVRdXI8Opa0qfXLAtoNB7oqwtftygN28pxwB1UiWUg,4934
|
9
9
|
beamlit/agents/chat.py,sha256=ItuS9zXkALgXVa6IevNpCVKvsicwg9OcUf4_VqO1x5Q,8603
|
10
|
-
beamlit/agents/decorator.py,sha256=
|
10
|
+
beamlit/agents/decorator.py,sha256=dz0XPS_RQFrCdX6VB_dnFc6f_1z8sszJzgaxlwd9E8Q,8435
|
11
11
|
beamlit/agents/thread.py,sha256=XNqj2WI3W2roQB7J5mpF1wEuGb3VtfvKQvWaEPx-sx8,1014
|
12
12
|
beamlit/agents/voice/openai.py,sha256=-RDBwl16i4TbUhFo5-77Ci3zmI3Y8U2yf2MmvXR2haQ,9479
|
13
13
|
beamlit/agents/voice/utils.py,sha256=tQidyM40Ewuy12wKqpvJLvfJgneQ0sZf50dqnerPGHg,836
|
@@ -136,7 +136,7 @@ beamlit/common/error.py,sha256=eOftZvd6XsAcQIYl9V7B1msGnTZf-HO0hGxBw1uIxaA,763
|
|
136
136
|
beamlit/common/instrumentation.py,sha256=a3wuXMOFx4x_M82I7NkzXwg0-vE5LZBNxV03YD-wJh4,10345
|
137
137
|
beamlit/common/logger.py,sha256=YBsVZXKApr7CCvZdAeNWkUX1lplt1SZVfv9Q4DbgRhQ,2092
|
138
138
|
beamlit/common/secrets.py,sha256=_qsc_HZMsIVVf03yyHUDN6j19Fgd_m1YgFumVS2sgxk,1200
|
139
|
-
beamlit/common/settings.py,sha256=
|
139
|
+
beamlit/common/settings.py,sha256=EX1AYSR-3FL0jFGOBssOWSUhLOfU85c01hkxMy5gJ70,5420
|
140
140
|
beamlit/common/slugify.py,sha256=QPJqa1VrSjRle931RN37t24rUjAbyZEg1UFAp2fLgac,679
|
141
141
|
beamlit/common/utils.py,sha256=eG201z9gMRnhoHkaZGNtfFUbCzfg_Y59JR4ciMgidW8,1465
|
142
142
|
beamlit/deploy/__init__.py,sha256=uRsI_-gTbbki59LlvubeTfG6wfI3o2XqZODW0QXA-Ao,292
|
@@ -144,10 +144,12 @@ beamlit/deploy/deploy.py,sha256=V07m0GKEKvQKjyw5r5UR2IsXQxhgyH6OeSgexj3AAtY,1123
|
|
144
144
|
beamlit/deploy/format.py,sha256=W3ESUHyFv-iZDjVnHOf9YFDDXZSXYIFFbwCoL1GInE0,1162
|
145
145
|
beamlit/deploy/parser.py,sha256=gjRUhOVtfKnc1UNc_FhXsEfj9zrMNuq8W93pNsJBpo0,7586
|
146
146
|
beamlit/functions/__init__.py,sha256=Mnoqpa1dm7TXwjodBbF_40JyD78aXsOYWmqjDSnA1lU,317
|
147
|
-
beamlit/functions/common.py,sha256=
|
147
|
+
beamlit/functions/common.py,sha256=4SK1N0VoQiydUT4frGT9OWMRxgHgMHUcsBhicMkcMmc,9996
|
148
148
|
beamlit/functions/decorator.py,sha256=iQbLwUo0K83DFJ3ub8O5jKtkbSINnku6GZcKJ9h7-5E,2292
|
149
|
-
beamlit/functions/
|
150
|
-
beamlit/functions/
|
149
|
+
beamlit/functions/local/local.py,sha256=KjkHWBxGlG9fliXnOOwZQEop1g2o10IaLiPo4Zu_XAk,1929
|
150
|
+
beamlit/functions/mcp/mcp.py,sha256=6pHWTPbglw1-SlqIztAigN284OyYR7ovn29u0IuF73E,5611
|
151
|
+
beamlit/functions/mcp/utils.py,sha256=V7bah6cymdtjJ_LJUrNcHDeApDHA6uXvaGVeFJGKj2U,1850
|
152
|
+
beamlit/functions/remote/remote.py,sha256=0orVJHpdvXWo18mmk-a_vBwpcClus6bk0kP1lNVoNpg,6716
|
151
153
|
beamlit/models/__init__.py,sha256=2yOQDeO7hQ_85q0WTU7ikAe-IfGr7PwlnHXJnLRSuB0,9036
|
152
154
|
beamlit/models/acl.py,sha256=tH67gsl_BMaviSbTaaIkO1g9cWZgJ6VgAnYVjQSzGZY,3952
|
153
155
|
beamlit/models/agent.py,sha256=pkFemfg0OUAuiqebiT3PurXhjAKvgCa_YOPuyqFVE2o,4264
|
@@ -263,8 +265,8 @@ beamlit/serve/app.py,sha256=lM59fdUtfkfAYNPWSCU9pkXIPBnhgVGvvgfoMkSVtks,4531
|
|
263
265
|
beamlit/serve/middlewares/__init__.py,sha256=O7fyfE1DIYmajFY9WWdzxCgeAQWZzJfeUjzHGbpWaAk,309
|
264
266
|
beamlit/serve/middlewares/accesslog.py,sha256=lcu33j4epFSHRBaeTpyt8deNb3kaM3K91-andw4fp80,1112
|
265
267
|
beamlit/serve/middlewares/processtime.py,sha256=3x5w1yQexB0xFNKK6fgLbINxT-eLLunfZ6UDV0bIIF4,944
|
266
|
-
beamlit-0.0.
|
267
|
-
beamlit-0.0.
|
268
|
-
beamlit-0.0.
|
269
|
-
beamlit-0.0.
|
270
|
-
beamlit-0.0.
|
268
|
+
beamlit-0.0.56rc104.dist-info/METADATA,sha256=uym9lJtWUtDt9LG3E1dzGs8Clj2TGE3B3gIqzCOeJsg,3515
|
269
|
+
beamlit-0.0.56rc104.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
270
|
+
beamlit-0.0.56rc104.dist-info/entry_points.txt,sha256=zxhgdn7SP-Otk4rEv7LMPAAa9w4TUCLbu9TJi9-K3xg,115
|
271
|
+
beamlit-0.0.56rc104.dist-info/licenses/LICENSE,sha256=p5PNQvpvyDT_0aYBDgmV1fFI_vAD2aSV0wWG7VTgRis,1069
|
272
|
+
beamlit-0.0.56rc104.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|