blaxel 0.64.0__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 (261) hide show
  1. blaxel/__init__.py +8 -0
  2. blaxel/agents/__init__.py +5 -0
  3. blaxel/agents/chain.py +153 -0
  4. blaxel/agents/chat.py +286 -0
  5. blaxel/agents/decorator.py +208 -0
  6. blaxel/agents/thread.py +24 -0
  7. blaxel/agents/voice/openai.py +255 -0
  8. blaxel/agents/voice/utils.py +25 -0
  9. blaxel/api/__init__.py +1 -0
  10. blaxel/api/agents/__init__.py +0 -0
  11. blaxel/api/agents/create_agent.py +155 -0
  12. blaxel/api/agents/delete_agent.py +146 -0
  13. blaxel/api/agents/get_agent.py +146 -0
  14. blaxel/api/agents/get_agent_logs.py +151 -0
  15. blaxel/api/agents/get_agent_metrics.py +150 -0
  16. blaxel/api/agents/get_agent_trace_ids.py +201 -0
  17. blaxel/api/agents/list_agent_revisions.py +155 -0
  18. blaxel/api/agents/list_agents.py +127 -0
  19. blaxel/api/agents/update_agent.py +168 -0
  20. blaxel/api/configurations/__init__.py +0 -0
  21. blaxel/api/configurations/get_configuration.py +122 -0
  22. blaxel/api/default/__init__.py +0 -0
  23. blaxel/api/default/get_trace.py +150 -0
  24. blaxel/api/default/get_trace_ids.py +218 -0
  25. blaxel/api/default/get_trace_logs.py +186 -0
  26. blaxel/api/default/list_mcp_hub_definitions.py +127 -0
  27. blaxel/api/functions/__init__.py +0 -0
  28. blaxel/api/functions/create_function.py +155 -0
  29. blaxel/api/functions/delete_function.py +146 -0
  30. blaxel/api/functions/get_function.py +146 -0
  31. blaxel/api/functions/get_function_logs.py +151 -0
  32. blaxel/api/functions/get_function_metrics.py +150 -0
  33. blaxel/api/functions/get_function_trace_ids.py +201 -0
  34. blaxel/api/functions/list_function_revisions.py +158 -0
  35. blaxel/api/functions/list_functions.py +131 -0
  36. blaxel/api/functions/update_function.py +168 -0
  37. blaxel/api/integrations/__init__.py +0 -0
  38. blaxel/api/integrations/create_integration_connection.py +167 -0
  39. blaxel/api/integrations/delete_integration_connection.py +158 -0
  40. blaxel/api/integrations/get_integration.py +97 -0
  41. blaxel/api/integrations/get_integration_connection.py +158 -0
  42. blaxel/api/integrations/get_integration_connection_model.py +104 -0
  43. blaxel/api/integrations/get_integration_connection_model_endpoint_configurations.py +97 -0
  44. blaxel/api/integrations/list_integration_connection_models.py +97 -0
  45. blaxel/api/integrations/list_integration_connections.py +139 -0
  46. blaxel/api/integrations/update_integration_connection.py +180 -0
  47. blaxel/api/invitations/__init__.py +0 -0
  48. blaxel/api/invitations/list_all_pending_invitations.py +142 -0
  49. blaxel/api/knowledgebases/__init__.py +0 -0
  50. blaxel/api/knowledgebases/create_knowledgebase.py +163 -0
  51. blaxel/api/knowledgebases/delete_knowledgebase.py +154 -0
  52. blaxel/api/knowledgebases/get_knowledgebase.py +154 -0
  53. blaxel/api/knowledgebases/list_knowledgebase_revisions.py +158 -0
  54. blaxel/api/knowledgebases/list_knowledgebases.py +139 -0
  55. blaxel/api/knowledgebases/update_knowledgebase.py +176 -0
  56. blaxel/api/locations/__init__.py +0 -0
  57. blaxel/api/locations/list_locations.py +139 -0
  58. blaxel/api/metrics/__init__.py +0 -0
  59. blaxel/api/metrics/get_metrics.py +130 -0
  60. blaxel/api/models/__init__.py +0 -0
  61. blaxel/api/models/create_model.py +163 -0
  62. blaxel/api/models/delete_model.py +154 -0
  63. blaxel/api/models/get_model.py +154 -0
  64. blaxel/api/models/get_model_logs.py +155 -0
  65. blaxel/api/models/get_model_metrics.py +158 -0
  66. blaxel/api/models/get_model_trace_ids.py +201 -0
  67. blaxel/api/models/list_model_revisions.py +158 -0
  68. blaxel/api/models/list_models.py +135 -0
  69. blaxel/api/models/update_model.py +176 -0
  70. blaxel/api/policies/__init__.py +0 -0
  71. blaxel/api/policies/create_policy.py +167 -0
  72. blaxel/api/policies/delete_policy.py +154 -0
  73. blaxel/api/policies/get_policy.py +154 -0
  74. blaxel/api/policies/list_policies.py +139 -0
  75. blaxel/api/policies/update_policy.py +180 -0
  76. blaxel/api/privateclusters/__init__.py +0 -0
  77. blaxel/api/privateclusters/create_private_cluster.py +132 -0
  78. blaxel/api/privateclusters/delete_private_cluster.py +156 -0
  79. blaxel/api/privateclusters/get_private_cluster.py +159 -0
  80. blaxel/api/privateclusters/get_private_cluster_health.py +97 -0
  81. blaxel/api/privateclusters/list_private_clusters.py +140 -0
  82. blaxel/api/privateclusters/update_private_cluster.py +156 -0
  83. blaxel/api/privateclusters/update_private_cluster_health.py +97 -0
  84. blaxel/api/service_accounts/__init__.py +0 -0
  85. blaxel/api/service_accounts/create_api_key_for_service_account.py +177 -0
  86. blaxel/api/service_accounts/create_workspace_service_account.py +170 -0
  87. blaxel/api/service_accounts/delete_api_key_for_service_account.py +104 -0
  88. blaxel/api/service_accounts/delete_workspace_service_account.py +160 -0
  89. blaxel/api/service_accounts/get_workspace_service_accounts.py +141 -0
  90. blaxel/api/service_accounts/list_api_keys_for_service_account.py +163 -0
  91. blaxel/api/service_accounts/update_workspace_service_account.py +183 -0
  92. blaxel/api/store/__init__.py +0 -0
  93. blaxel/api/store/get_store_agent.py +146 -0
  94. blaxel/api/store/get_store_function.py +146 -0
  95. blaxel/api/store/list_store_agents.py +131 -0
  96. blaxel/api/store/list_store_functions.py +131 -0
  97. blaxel/api/workspaces/__init__.py +0 -0
  98. blaxel/api/workspaces/accept_workspace_invitation.py +161 -0
  99. blaxel/api/workspaces/create_worspace.py +163 -0
  100. blaxel/api/workspaces/decline_workspace_invitation.py +158 -0
  101. blaxel/api/workspaces/delete_workspace.py +154 -0
  102. blaxel/api/workspaces/get_workspace.py +154 -0
  103. blaxel/api/workspaces/invite_workspace_user.py +174 -0
  104. blaxel/api/workspaces/leave_workspace.py +161 -0
  105. blaxel/api/workspaces/list_workspace_users.py +139 -0
  106. blaxel/api/workspaces/list_workspaces.py +139 -0
  107. blaxel/api/workspaces/remove_workspace_user.py +101 -0
  108. blaxel/api/workspaces/update_workspace.py +176 -0
  109. blaxel/api/workspaces/update_workspace_user_role.py +187 -0
  110. blaxel/authentication/__init__.py +45 -0
  111. blaxel/authentication/apikey.py +50 -0
  112. blaxel/authentication/authentication.py +176 -0
  113. blaxel/authentication/clientcredentials.py +103 -0
  114. blaxel/authentication/credentials.py +295 -0
  115. blaxel/authentication/device_mode.py +197 -0
  116. blaxel/client.py +281 -0
  117. blaxel/common/__init__.py +17 -0
  118. blaxel/common/error.py +27 -0
  119. blaxel/common/instrumentation.py +317 -0
  120. blaxel/common/logger.py +60 -0
  121. blaxel/common/secrets.py +39 -0
  122. blaxel/common/settings.py +150 -0
  123. blaxel/common/slugify.py +18 -0
  124. blaxel/common/utils.py +34 -0
  125. blaxel/deploy/__init__.py +8 -0
  126. blaxel/deploy/deploy.py +316 -0
  127. blaxel/deploy/format.py +46 -0
  128. blaxel/deploy/parser.py +192 -0
  129. blaxel/errors.py +16 -0
  130. blaxel/functions/__init__.py +7 -0
  131. blaxel/functions/common.py +228 -0
  132. blaxel/functions/decorator.py +64 -0
  133. blaxel/functions/local/local.py +48 -0
  134. blaxel/functions/mcp/client.py +96 -0
  135. blaxel/functions/mcp/mcp.py +168 -0
  136. blaxel/functions/mcp/utils.py +56 -0
  137. blaxel/functions/remote/remote.py +183 -0
  138. blaxel/models/__init__.py +233 -0
  139. blaxel/models/acl.py +133 -0
  140. blaxel/models/agent.py +126 -0
  141. blaxel/models/agent_chain.py +88 -0
  142. blaxel/models/agent_spec.py +346 -0
  143. blaxel/models/api_key.py +142 -0
  144. blaxel/models/configuration.py +85 -0
  145. blaxel/models/continent.py +70 -0
  146. blaxel/models/core_event.py +97 -0
  147. blaxel/models/core_spec.py +249 -0
  148. blaxel/models/core_spec_configurations.py +77 -0
  149. blaxel/models/country.py +70 -0
  150. blaxel/models/create_api_key_for_service_account_body.py +69 -0
  151. blaxel/models/create_workspace_service_account_body.py +71 -0
  152. blaxel/models/create_workspace_service_account_response_200.py +105 -0
  153. blaxel/models/delete_workspace_service_account_response_200.py +96 -0
  154. blaxel/models/entrypoint.py +96 -0
  155. blaxel/models/entrypoint_env.py +45 -0
  156. blaxel/models/flavor.py +70 -0
  157. blaxel/models/form.py +120 -0
  158. blaxel/models/form_config.py +45 -0
  159. blaxel/models/form_oauthomitempty.py +45 -0
  160. blaxel/models/form_secrets.py +45 -0
  161. blaxel/models/function.py +126 -0
  162. blaxel/models/function_kit.py +97 -0
  163. blaxel/models/function_spec.py +310 -0
  164. blaxel/models/get_trace_ids_response_200.py +45 -0
  165. blaxel/models/get_trace_logs_response_200.py +45 -0
  166. blaxel/models/get_trace_response_200.py +45 -0
  167. blaxel/models/get_workspace_service_accounts_response_200_item.py +96 -0
  168. blaxel/models/histogram_bucket.py +79 -0
  169. blaxel/models/histogram_stats.py +88 -0
  170. blaxel/models/integration_connection.py +96 -0
  171. blaxel/models/integration_connection_spec.py +114 -0
  172. blaxel/models/integration_connection_spec_config.py +45 -0
  173. blaxel/models/integration_connection_spec_secret.py +45 -0
  174. blaxel/models/integration_model.py +162 -0
  175. blaxel/models/integration_repository.py +88 -0
  176. blaxel/models/invite_workspace_user_body.py +60 -0
  177. blaxel/models/knowledgebase.py +126 -0
  178. blaxel/models/knowledgebase_spec.py +163 -0
  179. blaxel/models/knowledgebase_spec_options.py +45 -0
  180. blaxel/models/last_n_requests_metric.py +79 -0
  181. blaxel/models/latency_metric.py +144 -0
  182. blaxel/models/location_response.py +113 -0
  183. blaxel/models/mcp_definition.py +188 -0
  184. blaxel/models/mcp_definition_entrypoint.py +45 -0
  185. blaxel/models/mcp_definition_form.py +45 -0
  186. blaxel/models/metadata.py +139 -0
  187. blaxel/models/metadata_labels.py +45 -0
  188. blaxel/models/metric.py +79 -0
  189. blaxel/models/metrics.py +169 -0
  190. blaxel/models/metrics_models.py +45 -0
  191. blaxel/models/metrics_request_total_per_code.py +45 -0
  192. blaxel/models/metrics_rps_per_code.py +45 -0
  193. blaxel/models/model.py +126 -0
  194. blaxel/models/model_private_cluster.py +79 -0
  195. blaxel/models/model_spec.py +249 -0
  196. blaxel/models/o_auth.py +72 -0
  197. blaxel/models/owner_fields.py +70 -0
  198. blaxel/models/pending_invitation.py +124 -0
  199. blaxel/models/pending_invitation_accept.py +85 -0
  200. blaxel/models/pending_invitation_render.py +147 -0
  201. blaxel/models/pending_invitation_render_invited_by.py +88 -0
  202. blaxel/models/pending_invitation_render_workspace.py +70 -0
  203. blaxel/models/pending_invitation_workspace_details.py +72 -0
  204. blaxel/models/pod_template_spec.py +45 -0
  205. blaxel/models/policy.py +96 -0
  206. blaxel/models/policy_location.py +70 -0
  207. blaxel/models/policy_max_tokens.py +106 -0
  208. blaxel/models/policy_spec.py +151 -0
  209. blaxel/models/private_cluster.py +183 -0
  210. blaxel/models/private_location.py +61 -0
  211. blaxel/models/repository.py +70 -0
  212. blaxel/models/request_duration_over_time_metric.py +97 -0
  213. blaxel/models/request_duration_over_time_metrics.py +80 -0
  214. blaxel/models/request_total_by_origin_metric.py +115 -0
  215. blaxel/models/request_total_by_origin_metric_request_total_by_origin.py +45 -0
  216. blaxel/models/request_total_by_origin_metric_request_total_by_origin_and_code.py +45 -0
  217. blaxel/models/request_total_metric.py +123 -0
  218. blaxel/models/request_total_metric_request_total_per_code.py +45 -0
  219. blaxel/models/request_total_metric_rps_per_code.py +45 -0
  220. blaxel/models/resource_log.py +79 -0
  221. blaxel/models/resource_metrics.py +270 -0
  222. blaxel/models/resource_metrics_request_total_per_code.py +45 -0
  223. blaxel/models/resource_metrics_rps_per_code.py +45 -0
  224. blaxel/models/revision_configuration.py +97 -0
  225. blaxel/models/revision_metadata.py +124 -0
  226. blaxel/models/runtime.py +196 -0
  227. blaxel/models/runtime_startup_probe.py +45 -0
  228. blaxel/models/serverless_config.py +80 -0
  229. blaxel/models/spec_configuration.py +70 -0
  230. blaxel/models/store_agent.py +178 -0
  231. blaxel/models/store_agent_labels.py +45 -0
  232. blaxel/models/store_configuration.py +151 -0
  233. blaxel/models/store_configuration_option.py +79 -0
  234. blaxel/models/store_function.py +211 -0
  235. blaxel/models/store_function_kit.py +97 -0
  236. blaxel/models/store_function_labels.py +45 -0
  237. blaxel/models/store_function_parameter.py +88 -0
  238. blaxel/models/time_fields.py +70 -0
  239. blaxel/models/token_rate_metric.py +88 -0
  240. blaxel/models/token_rate_metrics.py +120 -0
  241. blaxel/models/token_total_metric.py +106 -0
  242. blaxel/models/trace_ids_response.py +45 -0
  243. blaxel/models/update_workspace_service_account_body.py +69 -0
  244. blaxel/models/update_workspace_service_account_response_200.py +96 -0
  245. blaxel/models/update_workspace_user_role_body.py +60 -0
  246. blaxel/models/websocket_channel.py +88 -0
  247. blaxel/models/workspace.py +148 -0
  248. blaxel/models/workspace_labels.py +45 -0
  249. blaxel/models/workspace_user.py +115 -0
  250. blaxel/py.typed +1 -0
  251. blaxel/run.py +108 -0
  252. blaxel/serve/app.py +131 -0
  253. blaxel/serve/middlewares/__init__.py +10 -0
  254. blaxel/serve/middlewares/accesslog.py +32 -0
  255. blaxel/serve/middlewares/processtime.py +28 -0
  256. blaxel/types.py +46 -0
  257. blaxel-0.64.0.dist-info/METADATA +96 -0
  258. blaxel-0.64.0.dist-info/RECORD +261 -0
  259. blaxel-0.64.0.dist-info/WHEEL +4 -0
  260. blaxel-0.64.0.dist-info/entry_points.txt +2 -0
  261. blaxel-0.64.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,192 @@
1
+ """
2
+ This module provides classes and functions for parsing deployment resources within Blaxel.
3
+ It includes the Resource dataclass for representing deployment resources and functions to extract and process resources
4
+ decorated within Python files.
5
+ """
6
+
7
+ import ast
8
+ import importlib
9
+ import os
10
+ from dataclasses import dataclass
11
+ from logging import getLogger
12
+ from typing import Callable, Literal
13
+
14
+ from blaxel.models import StoreFunctionParameter
15
+
16
+
17
+ @dataclass
18
+ class Resource:
19
+ """
20
+ A dataclass representing a deployment resource.
21
+
22
+ Attributes:
23
+ type (Literal["agent", "function"]): The type of deployment ("agent" or "function").
24
+ module (Callable): The module containing the deployment.
25
+ name (str): The name of the deployment.
26
+ decorator (ast.Call): The decorator AST node used on the deployment function.
27
+ func (Callable): The deployment function.
28
+ """
29
+ type: Literal["agent", "function"]
30
+ module: Callable
31
+ name: str
32
+ decorator: ast.Call
33
+ func: Callable
34
+
35
+
36
+ def get_resources(from_decorator, dir) -> list[Resource]:
37
+ """
38
+ Scans through Python files in a directory to find functions decorated with a specific decorator.
39
+
40
+ Args:
41
+ from_decorator (str): The name of the decorator to search for
42
+ dir (str): The directory to scan, defaults to "src"
43
+
44
+ Returns:
45
+ list[Resource]: List of Resource objects containing information about decorated functions
46
+ """
47
+ resources = []
48
+ logger = getLogger(__name__)
49
+
50
+ # Walk through all Python files in resources directory and subdirectories
51
+ for root, _, files in os.walk(dir):
52
+ for file in files:
53
+ if file.endswith(".py"):
54
+ file_path = os.path.join(root, file)
55
+ # Read and compile the file content
56
+ with open(file_path) as f:
57
+ try:
58
+ file_content = f.read()
59
+ # Parse the file content to find decorated resources
60
+ tree = ast.parse(file_content)
61
+
62
+ # Look for function definitions with decorators
63
+ for node in ast.walk(tree):
64
+ if (
65
+ not isinstance(node, ast.FunctionDef)
66
+ and not isinstance(node, ast.AsyncFunctionDef)
67
+ ) or len(node.decorator_list) == 0:
68
+ continue
69
+ decorator = node.decorator_list[0]
70
+
71
+ decorator_name = ""
72
+ if isinstance(decorator, ast.Call):
73
+ decorator_name = decorator.func.id
74
+ if isinstance(decorator, ast.Name):
75
+ decorator_name = decorator.id
76
+ if decorator_name == from_decorator:
77
+ # Get the function name and decorator name
78
+ func_name = node.name
79
+
80
+ # Import the module to get the actual function
81
+ spec = importlib.util.spec_from_file_location(func_name, file_path)
82
+ module = importlib.util.module_from_spec(spec)
83
+ spec.loader.exec_module(module)
84
+
85
+ # Check if kit=True in the decorator arguments
86
+
87
+ # Get the decorated function
88
+ if hasattr(module, func_name) and isinstance(decorator, ast.Call):
89
+ resources.append(
90
+ Resource(
91
+ type=decorator_name,
92
+ module=module,
93
+ name=func_name,
94
+ func=getattr(module, func_name),
95
+ decorator=decorator,
96
+ )
97
+ )
98
+ except Exception as e:
99
+ logger.warning(f"Error processing {file_path}: {e!s} at line {e.__traceback__.tb_lineno}")
100
+ return resources
101
+
102
+
103
+ def get_parameters(resource: Resource) -> list[StoreFunctionParameter]:
104
+ """
105
+ Extracts parameter information from a function's signature and docstring.
106
+
107
+ Args:
108
+ resource (Resource): The resource object containing the function to analyze
109
+
110
+ Returns:
111
+ list[StoreFunctionParameter]: List of parameter objects with name, type, required status, and description
112
+ """
113
+ parameters = []
114
+ # Get function signature
115
+ import inspect
116
+
117
+ sig = inspect.signature(resource.func)
118
+ # Get docstring for parameter descriptions
119
+ docstring = inspect.getdoc(resource.func)
120
+ param_descriptions = {}
121
+ if docstring:
122
+ # Parse docstring for parameter descriptions
123
+ lines = docstring.split("\n")
124
+ for line in lines:
125
+ line = line.strip().lower()
126
+ if line.startswith(":param "):
127
+ # Extract parameter name and description
128
+ param_line = line[7:].split(":", 1)
129
+ if len(param_line) == 2:
130
+ param_name = param_line[0].strip()
131
+ param_desc = param_line[1].strip()
132
+ param_descriptions[param_name] = param_desc
133
+ for name, param in sig.parameters.items():
134
+ # Skip *args and **kwargs parameters
135
+ if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
136
+ continue
137
+
138
+ param_type = "string" # Default type
139
+ type_mapping = {
140
+ "str": "string",
141
+ "int": "integer",
142
+ "float": "number",
143
+ "bool": "boolean",
144
+ "list": "array",
145
+ "dict": "object",
146
+ "none": "null",
147
+ }
148
+ if param.annotation != inspect.Parameter.empty:
149
+ # Map Python types to OpenAPI types
150
+ if hasattr(param.annotation, "__name__"):
151
+ param_type = param.annotation.__name__.lower()
152
+ else:
153
+ # Handle special types like Union, Optional etc
154
+ param_type = str(param.annotation).lower()
155
+ parameter = StoreFunctionParameter(
156
+ name=name,
157
+ type_=type_mapping.get(param_type, "string"),
158
+ required=param.default == inspect.Parameter.empty,
159
+ description=param_descriptions.get(name, f"Parameter {name}"),
160
+ )
161
+ parameters.append(parameter)
162
+
163
+ return parameters
164
+
165
+
166
+ def get_description(description: str | None, resource: Resource) -> str:
167
+ """
168
+ Gets the description of a function from either a provided description or the function's docstring.
169
+
170
+ Args:
171
+ description (str | None): Optional explicit description
172
+ resource (Resource): The resource object containing the function
173
+
174
+ Returns:
175
+ str: The function description
176
+ """
177
+ if description:
178
+ return description
179
+ doc = resource.func.__doc__
180
+ if doc:
181
+ # Split docstring into sections and get only the description part
182
+ doc_lines = doc.split("\n")
183
+ description_lines = []
184
+ for line in doc_lines:
185
+ line = line.strip()
186
+ # Stop when we hit param/return sections
187
+ if line.startswith(":param") or line.startswith(":return"):
188
+ break
189
+ if line:
190
+ description_lines.append(line)
191
+ return " ".join(description_lines).strip()
192
+ return ""
blaxel/errors.py ADDED
@@ -0,0 +1,16 @@
1
+ """Contains shared errors types that can be raised from API functions"""
2
+
3
+
4
+ class UnexpectedStatus(Exception):
5
+ """Raised by api functions when the response status an undocumented status and Client.raise_on_unexpected_status is True"""
6
+
7
+ def __init__(self, status_code: int, content: bytes):
8
+ self.status_code = status_code
9
+ self.content = content
10
+
11
+ super().__init__(
12
+ f"Unexpected status code: {status_code}\n\nResponse content:\n{content.decode(errors='ignore')}"
13
+ )
14
+
15
+
16
+ __all__ = ["UnexpectedStatus"]
@@ -0,0 +1,7 @@
1
+ """Functions package providing function decorators and utilities for Blaxel integration.
2
+ It includes decorators for creating function tools and utilities for managing and retrieving functions."""
3
+
4
+ from .common import get_functions
5
+ from .decorator import function, kit
6
+
7
+ __all__ = ["function", "kit", "get_functions"]
@@ -0,0 +1,228 @@
1
+ """Decorators for creating function tools with Blaxel and LangChain integration.
2
+
3
+ This module provides functionality to discover and load function tools from Python files,
4
+ supporting both local and remote function execution.
5
+
6
+ Key Features:
7
+ - Automatic function discovery in specified directories
8
+ - Support for both synchronous and asynchronous functions
9
+ - Integration with LangChain's StructuredTool system
10
+ - Remote function toolkit handling
11
+ - Chain toolkit integration
12
+
13
+ Main Components:
14
+ - get_functions(): Core function that discovers and loads function tools
15
+ """
16
+ import ast
17
+ import asyncio
18
+ import importlib.util
19
+ import os
20
+ import traceback
21
+ from logging import getLogger
22
+ from typing import Union
23
+
24
+ from langchain_core.tools import StructuredTool
25
+ from langchain_core.tools.base import create_schema_from_function
26
+
27
+ from blaxel.aimon import slugify
28
+ from blaxel.aimon.settings import get_settings
29
+ from blaxel.authentication import new_client
30
+ from blaxel.client import AuthenticatedClient
31
+ from blaxel.functions.local.local import LocalToolKit
32
+ from blaxel.functions.remote.remote import RemoteToolkit
33
+ from blaxel.models import AgentChain
34
+
35
+ logger = getLogger(__name__)
36
+
37
+ MAX_RETRIES = 10
38
+ RETRY_DELAY = 1 # 1 second delay between retries
39
+
40
+ async def initialize_with_retry(toolkit, function_name: str, max_retries: int):
41
+ for attempt in range(1, max_retries + 1):
42
+ try:
43
+ await toolkit.initialize()
44
+ return await toolkit.get_tools()
45
+ except Exception as e:
46
+ if attempt == max_retries:
47
+ logger.warn(f"Failed to initialize function {function_name} after {max_retries} attempts: {e!s}")
48
+ raise
49
+ logger.info(f"Attempt {attempt} failed for {function_name}, retrying...")
50
+ await asyncio.sleep(RETRY_DELAY)
51
+ return []
52
+
53
+ async def get_functions(
54
+ remote_functions: Union[list[str], None] = None,
55
+ local_functions: Union[list[dict], None] = None,
56
+ client: Union[AuthenticatedClient, None] = None,
57
+ dir: Union[str, None] = None,
58
+ chain: Union[list[AgentChain], None] = None,
59
+ remote_functions_empty: bool = True,
60
+ local_functions_empty: bool = True,
61
+ from_decorator: str = "function",
62
+ warning: bool = True,
63
+ ):
64
+ """Discovers and loads function tools from Python files and remote sources.
65
+
66
+ This function walks through Python files in a specified directory, looking for
67
+ decorated functions to convert into LangChain tools. It also handles remote
68
+ functions and chain toolkits.
69
+
70
+ Args:
71
+ remote_functions (Union[list[str], None]): List of remote function names to load
72
+ client (Union[AuthenticatedClient, None]): Authenticated client instance for API calls
73
+ dir (Union[str, None]): Directory to search for Python files containing functions
74
+ chain (Union[list[AgentChain], None]): List of agent chains to include
75
+ remote_functions_empty (bool): Whether to allow empty remote functions
76
+ from_decorator (str): Name of the decorator to look for (default: "function")
77
+ warning (bool): Whether to show warning messages
78
+
79
+ Returns:
80
+ list: List of discovered and loaded function tools
81
+
82
+ The function performs the following steps:
83
+ 1. Walks through Python files in the specified directory
84
+ 2. Parses each file to find decorated functions
85
+ 3. Converts found functions into LangChain StructuredTools
86
+ 4. Handles both synchronous and asynchronous functions
87
+ 5. Processes remote functions if specified
88
+ 6. Integrates chain toolkits if provided
89
+
90
+ Example:
91
+ ```python
92
+ tools = get_functions(
93
+ dir="./functions",
94
+ from_decorator="function",
95
+ warning=True
96
+ )
97
+ ```
98
+ """
99
+ from blaxel.agents.chain import ChainToolkit
100
+
101
+ settings = get_settings()
102
+ if client is None:
103
+ client = new_client()
104
+ if dir is None:
105
+ dir = settings.agent.functions_directory
106
+
107
+ functions = []
108
+ logger = getLogger(__name__)
109
+ settings = get_settings()
110
+
111
+ # Walk through all Python files in functions directory and subdirectories
112
+ if not os.path.exists(dir):
113
+ if remote_functions_empty and warning:
114
+ logger.warn(f"Functions directory {dir} not found")
115
+ if os.path.exists(dir):
116
+ for root, _, files in os.walk(dir):
117
+ for file in files:
118
+ if file.endswith(".py"):
119
+ file_path = os.path.join(root, file)
120
+ # Read and compile the file content
121
+ with open(file_path) as f:
122
+ try:
123
+ file_content = f.read()
124
+ # Parse the file content to find decorated functions
125
+ tree = ast.parse(file_content)
126
+
127
+ # Look for function definitions with decorators
128
+ for node in ast.walk(tree):
129
+ if (
130
+ not isinstance(node, ast.FunctionDef)
131
+ and not isinstance(node, ast.AsyncFunctionDef)
132
+ ) or len(node.decorator_list) == 0:
133
+ continue
134
+ decorator = node.decorator_list[0]
135
+
136
+ decorator_name = ""
137
+ if isinstance(decorator, ast.Call):
138
+ decorator_name = decorator.func.id
139
+ if isinstance(decorator, ast.Name):
140
+ decorator_name = decorator.id
141
+ if decorator_name == from_decorator:
142
+ # Get the function name and decorator name
143
+ func_name = node.name
144
+
145
+ # Import the module to get the actual function
146
+ spec = importlib.util.spec_from_file_location(func_name, file_path)
147
+ module = importlib.util.module_from_spec(spec)
148
+ spec.loader.exec_module(module)
149
+ # Check if kit=True in the decorator arguments
150
+ is_kit = False
151
+ if isinstance(decorator, ast.Call):
152
+ for keyword in decorator.keywords:
153
+ if keyword.arg == "kit" and isinstance(
154
+ keyword.value, ast.Constant
155
+ ):
156
+ is_kit = keyword.value.value
157
+ if is_kit and not settings.remote:
158
+ kit_functions = await get_functions(
159
+ client=client,
160
+ dir=os.path.join(root),
161
+ remote_functions_empty=remote_functions_empty,
162
+ local_functions_empty=local_functions_empty,
163
+ from_decorator="kit",
164
+ )
165
+ functions.extend(kit_functions)
166
+
167
+ # Get the decorated function
168
+ if not is_kit and hasattr(module, func_name):
169
+ func = getattr(module, func_name)
170
+ if settings.remote:
171
+ toolkit = RemoteToolkit(client, slugify(func.__name__))
172
+ await toolkit.initialize()
173
+ functions.extend(await toolkit.get_tools())
174
+ else:
175
+ if asyncio.iscoroutinefunction(func):
176
+ functions.append(
177
+ StructuredTool(
178
+ name=func.__name__,
179
+ description=func.__doc__,
180
+ func=func,
181
+ coroutine=func,
182
+ args_schema=create_schema_from_function(func.__name__, func)
183
+ )
184
+ )
185
+ else:
186
+
187
+ functions.append(
188
+ StructuredTool(
189
+ name=func.__name__,
190
+ description=func.__doc__,
191
+ func=func,
192
+ args_schema=create_schema_from_function(func.__name__, func)
193
+ )
194
+ )
195
+ except Exception as e:
196
+ logger.warning(f"Error processing {file_path}: {e!s}")
197
+ if remote_functions:
198
+ for function in remote_functions:
199
+ try:
200
+ toolkit = RemoteToolkit(client, function)
201
+ tools = await initialize_with_retry(toolkit, function, MAX_RETRIES)
202
+ functions.extend(tools)
203
+ except Exception as e:
204
+ if not isinstance(e, RuntimeError):
205
+ logger.debug(
206
+ f"Failed to initialize remote function {function}: {e!s}\n"
207
+ f"Traceback:\n{traceback.format_exc()}"
208
+ )
209
+ logger.warn(f"Failed to initialize remote function {function}: {e!s}")
210
+ if local_functions:
211
+ for function in local_functions:
212
+ try:
213
+ toolkit = LocalToolKit(client, function)
214
+ tools = await initialize_with_retry(toolkit, function["name"], MAX_RETRIES)
215
+ functions.extend(tools)
216
+ except Exception as e:
217
+ logger.debug(
218
+ f"Failed to initialize local function {function}: {e!s}\n"
219
+ f"Traceback:\n{traceback.format_exc()}"
220
+ )
221
+ logger.warn(f"Failed to initialize local function {function}: {e!s}")
222
+
223
+ if chain:
224
+ toolkit = ChainToolkit(client, chain)
225
+ await toolkit.initialize()
226
+ functions.extend(await toolkit.get_tools())
227
+ return functions
228
+
@@ -0,0 +1,64 @@
1
+ """Decorators for creating function tools with Blaxel and LangChain integration."""
2
+ import asyncio
3
+ import functools
4
+ from collections.abc import Callable
5
+ from logging import getLogger
6
+
7
+ from fastapi import Request
8
+
9
+ from blaxel.models import Function, FunctionKit
10
+
11
+ logger = getLogger(__name__)
12
+
13
+ def kit(bl_kit: FunctionKit = None, **kwargs: dict) -> Callable:
14
+ """
15
+ Decorator to create function tools with Blaxel and LangChain integration.
16
+
17
+ Args:
18
+ bl_kit (FunctionKit | None): Optional FunctionKit to associate with the function.
19
+ **kwargs (dict): Additional keyword arguments for function configuration.
20
+
21
+ Returns:
22
+ Callable: The decorated function.
23
+ """
24
+
25
+ def wrapper(func: Callable) -> Callable:
26
+ if bl_kit and not func.__doc__ and bl_kit.description:
27
+ func.__doc__ = bl_kit.description
28
+ return func
29
+
30
+ return wrapper
31
+
32
+
33
+ def function(*args, function: Function | dict = None, kit=False, **kwargs: dict) -> Callable:
34
+ """
35
+ Decorator to create function tools with Blaxel and LangChain integration.
36
+
37
+ Args:
38
+ function (Function | dict): Function metadata or a dictionary representing it.
39
+ kit (bool): Whether to associate a function kit.
40
+ **kwargs (dict): Additional keyword arguments for function configuration.
41
+
42
+ Returns:
43
+ Callable: The decorated function.
44
+ """
45
+ if function is not None and not isinstance(function, dict):
46
+ raise Exception(
47
+ 'function must be a dictionary, example: @function(function={"metadata": {"name": "my_function"}})'
48
+ )
49
+ if isinstance(function, dict):
50
+ function = Function(**function)
51
+
52
+ def wrapper(func: Callable) -> Callable:
53
+ if function and not func.__doc__ and function.spec and function.spec.description:
54
+ func.__doc__ = function.spec.description
55
+
56
+ @functools.wraps(func)
57
+ async def wrapped(*args, **kwargs):
58
+ if len(args) > 0 and isinstance(args[0], Request):
59
+ body = await args[0].json()
60
+ args = [body.get(param) for param in func.__code__.co_varnames[:func.__code__.co_argcount]]
61
+ return await func(*args, **kwargs) if asyncio.iscoroutinefunction(func) else func(*args, **kwargs)
62
+ return wrapped
63
+
64
+ return wrapper
@@ -0,0 +1,48 @@
1
+ from dataclasses import dataclass
2
+
3
+ import pydantic
4
+ from langchain_core.tools.base import BaseTool
5
+
6
+ from blaxel.authentication.authentication import AuthenticatedClient
7
+ from blaxel.functions.mcp.mcp import MCPClient, MCPToolkit
8
+ from blaxel.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
+ async 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
+ },
38
+ "description": self.local_function['description'] or "",
39
+ }
40
+ )
41
+ except Exception as e:
42
+ raise RuntimeError(f"Failed to initialize local function: {e}")
43
+
44
+ async def get_tools(self) -> list[BaseTool]:
45
+ mcp_client = MCPClient(self.client, self._function.spec["configurations"]["url"], None)
46
+ mcp_toolkit = MCPToolkit(client=mcp_client, url=self._function.spec["configurations"]["url"])
47
+ await mcp_toolkit.initialize()
48
+ return await mcp_toolkit.get_tools()
@@ -0,0 +1,96 @@
1
+ import logging
2
+ from contextlib import asynccontextmanager
3
+ from typing import Any
4
+ from urllib.parse import urljoin, urlparse
5
+
6
+ import anyio
7
+ import mcp.types as types
8
+ from anyio.abc import TaskStatus
9
+ from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
10
+ from websockets.client import WebSocketClientProtocol
11
+ from websockets.client import connect as ws_connect
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ def remove_request_params(url: str) -> str:
17
+ return urljoin(url, urlparse(url).path)
18
+
19
+
20
+ @asynccontextmanager
21
+ async def websocket_client(
22
+ url: str,
23
+ headers: dict[str, Any] | None = None,
24
+ timeout: float = 5,
25
+ ):
26
+ """
27
+ Client transport for WebSocket.
28
+
29
+ The `timeout` parameter controls connection timeout.
30
+ """
31
+ read_stream: MemoryObjectReceiveStream[types.JSONRPCMessage | Exception]
32
+ read_stream_writer: MemoryObjectSendStream[types.JSONRPCMessage | Exception]
33
+
34
+ write_stream: MemoryObjectSendStream[types.JSONRPCMessage]
35
+ write_stream_reader: MemoryObjectReceiveStream[types.JSONRPCMessage]
36
+
37
+ read_stream_writer, read_stream = anyio.create_memory_object_stream(0)
38
+ write_stream, write_stream_reader = anyio.create_memory_object_stream(0)
39
+
40
+ async with anyio.create_task_group() as tg:
41
+ try:
42
+ # Convert http(s):// to ws(s)://
43
+ ws_url = url.replace("http://", "ws://").replace("https://", "wss://")
44
+ logger.debug(f"Connecting to WebSocket endpoint: {remove_request_params(ws_url)}")
45
+
46
+ async with ws_connect(ws_url, extra_headers=headers, open_timeout=timeout) as websocket:
47
+ logger.debug("WebSocket connection established")
48
+
49
+ async def ws_reader(
50
+ websocket: WebSocketClientProtocol,
51
+ task_status: TaskStatus[None] = anyio.TASK_STATUS_IGNORED,
52
+ ):
53
+ try:
54
+ task_status.started()
55
+ async for message in websocket:
56
+ logger.debug(f"Received WebSocket message: {message}")
57
+ try:
58
+ parsed_message = types.JSONRPCMessage.model_validate_json(message)
59
+ logger.debug(f"Received server message: {parsed_message}")
60
+ await read_stream_writer.send(parsed_message)
61
+ except Exception as exc:
62
+ logger.error(f"Error parsing server message: {exc}")
63
+ await read_stream_writer.send(exc)
64
+ except Exception as exc:
65
+ logger.error(f"Error in ws_reader: {exc}")
66
+ await read_stream_writer.send(exc)
67
+ finally:
68
+ await read_stream_writer.aclose()
69
+
70
+ async def ws_writer(websocket: WebSocketClientProtocol):
71
+ try:
72
+ async with write_stream_reader:
73
+ async for message in write_stream_reader:
74
+ logger.debug(f"Sending client message: {message}")
75
+ await websocket.send(
76
+ message.model_dump_json(
77
+ by_alias=True,
78
+ exclude_none=True,
79
+ )
80
+ )
81
+ logger.debug("Client message sent successfully")
82
+ except Exception as exc:
83
+ logger.error(f"Error in ws_writer: {exc}")
84
+ finally:
85
+ await write_stream.aclose()
86
+
87
+ await tg.start(ws_reader, websocket)
88
+ tg.start_soon(ws_writer, websocket)
89
+
90
+ try:
91
+ yield read_stream, write_stream
92
+ finally:
93
+ tg.cancel_scope.cancel()
94
+ finally:
95
+ await read_stream_writer.aclose()
96
+ await write_stream.aclose()