deepset-mcp 0.0.4rc1__py3-none-any.whl → 0.0.5__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.
- deepset_mcp/__init__.py +5 -5
- deepset_mcp/api/transport.py +5 -2
- deepset_mcp/config.py +9 -1
- deepset_mcp/main.py +186 -169
- deepset_mcp/py.typed +0 -0
- deepset_mcp/server.py +154 -0
- deepset_mcp/store.py +51 -2
- deepset_mcp/tool_factory.py +301 -428
- deepset_mcp/tool_models.py +42 -0
- deepset_mcp/tool_registry.py +208 -0
- deepset_mcp/tools/object_store.py +49 -0
- deepset_mcp/tools/tokonomics/__init__.py +2 -61
- deepset_mcp/tools/tokonomics/decorators.py +60 -87
- deepset_mcp/tools/tokonomics/explorer.py +32 -17
- deepset_mcp/tools/tokonomics/object_store.py +116 -139
- {deepset_mcp-0.0.4rc1.dist-info → deepset_mcp-0.0.5.dist-info}/METADATA +59 -13
- {deepset_mcp-0.0.4rc1.dist-info → deepset_mcp-0.0.5.dist-info}/RECORD +20 -15
- deepset_mcp-0.0.5.dist-info/entry_points.txt +2 -0
- deepset_mcp-0.0.4rc1.dist-info/entry_points.txt +0 -2
- {deepset_mcp-0.0.4rc1.dist-info → deepset_mcp-0.0.5.dist-info}/WHEEL +0 -0
- {deepset_mcp-0.0.4rc1.dist-info → deepset_mcp-0.0.5.dist-info}/licenses/LICENSE +0 -0
deepset_mcp/tool_factory.py
CHANGED
|
@@ -6,459 +6,327 @@
|
|
|
6
6
|
|
|
7
7
|
import functools
|
|
8
8
|
import inspect
|
|
9
|
-
import
|
|
10
|
-
import os
|
|
9
|
+
import re
|
|
11
10
|
from collections.abc import Awaitable, Callable
|
|
12
|
-
from dataclasses import dataclass
|
|
13
|
-
from enum import StrEnum
|
|
14
11
|
from typing import Any
|
|
15
12
|
|
|
16
|
-
from mcp.server.fastmcp import FastMCP
|
|
13
|
+
from mcp.server.fastmcp import Context, FastMCP
|
|
17
14
|
|
|
18
15
|
from deepset_mcp.api.client import AsyncDeepsetClient
|
|
19
|
-
from deepset_mcp.config import DEFAULT_CLIENT_HEADER
|
|
20
|
-
from deepset_mcp.
|
|
21
|
-
from deepset_mcp.
|
|
22
|
-
from deepset_mcp.tools.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
)
|
|
29
|
-
from deepset_mcp.tools.haystack_service import (
|
|
30
|
-
get_component_definition as get_component_definition_tool,
|
|
31
|
-
get_custom_components as get_custom_components_tool,
|
|
32
|
-
list_component_families as list_component_families_tool,
|
|
33
|
-
search_component_definition as search_component_definition_tool,
|
|
34
|
-
)
|
|
35
|
-
from deepset_mcp.tools.indexes import (
|
|
36
|
-
create_index as create_index_tool,
|
|
37
|
-
deploy_index as deploy_index_tool,
|
|
38
|
-
get_index as get_index_tool,
|
|
39
|
-
list_indexes as list_indexes_tool,
|
|
40
|
-
update_index as update_index_tool,
|
|
16
|
+
from deepset_mcp.config import DEFAULT_CLIENT_HEADER, DOCS_SEARCH_TOOL_NAME
|
|
17
|
+
from deepset_mcp.tool_models import DeepsetDocsConfig, MemoryType, ToolConfig, WorkspaceMode
|
|
18
|
+
from deepset_mcp.tool_registry import TOOL_REGISTRY
|
|
19
|
+
from deepset_mcp.tools.tokonomics import (
|
|
20
|
+
ObjectStore,
|
|
21
|
+
RichExplorer,
|
|
22
|
+
explorable,
|
|
23
|
+
explorable_and_referenceable,
|
|
24
|
+
referenceable,
|
|
41
25
|
)
|
|
42
26
|
|
|
43
|
-
# Import all tool functions
|
|
44
|
-
from deepset_mcp.tools.pipeline import (
|
|
45
|
-
create_pipeline as create_pipeline_tool,
|
|
46
|
-
deploy_pipeline as deploy_pipeline_tool,
|
|
47
|
-
get_pipeline as get_pipeline_tool,
|
|
48
|
-
get_pipeline_logs as get_pipeline_logs_tool,
|
|
49
|
-
list_pipelines as list_pipelines_tool,
|
|
50
|
-
search_pipeline as search_pipeline_tool,
|
|
51
|
-
update_pipeline as update_pipeline_tool,
|
|
52
|
-
validate_pipeline as validate_pipeline_tool,
|
|
53
|
-
)
|
|
54
|
-
from deepset_mcp.tools.pipeline_template import (
|
|
55
|
-
get_template as get_pipeline_template_tool,
|
|
56
|
-
list_templates as list_pipeline_templates_tool,
|
|
57
|
-
search_templates as search_pipeline_templates_tool,
|
|
58
|
-
)
|
|
59
|
-
from deepset_mcp.tools.secrets import (
|
|
60
|
-
get_secret as get_secret_tool,
|
|
61
|
-
list_secrets as list_secrets_tool,
|
|
62
|
-
)
|
|
63
|
-
from deepset_mcp.tools.tokonomics import RichExplorer, explorable, explorable_and_referenceable, referenceable
|
|
64
|
-
from deepset_mcp.tools.workspace import (
|
|
65
|
-
create_workspace as create_workspace_tool,
|
|
66
|
-
get_workspace as get_workspace_tool,
|
|
67
|
-
list_workspaces as list_workspaces_tool,
|
|
68
|
-
)
|
|
69
27
|
|
|
28
|
+
def apply_custom_args(base_func: Callable[..., Any], config: ToolConfig) -> Callable[..., Any]:
|
|
29
|
+
"""
|
|
30
|
+
Applies custom keyword arguments defined in the ToolConfig to a function.
|
|
70
31
|
|
|
71
|
-
|
|
72
|
-
"""Checks if documentation search is available."""
|
|
73
|
-
return bool(
|
|
74
|
-
os.environ.get("DEEPSET_DOCS_WORKSPACE", False)
|
|
75
|
-
and os.environ.get("DEEPSET_DOCS_PIPELINE_NAME", False)
|
|
76
|
-
and os.environ.get("DEEPSET_DOCS_API_KEY", False)
|
|
77
|
-
)
|
|
32
|
+
Removes the partially applied keyword arguments from the function's signature and docstring.
|
|
78
33
|
|
|
34
|
+
:param base_func: The function to apply custom keyword arguments to.
|
|
35
|
+
:param config: The ToolConfig for the function.
|
|
36
|
+
:returns: Function with custom arguments applied and updated signature/docstring.
|
|
37
|
+
"""
|
|
38
|
+
if not config.custom_args:
|
|
39
|
+
return base_func
|
|
79
40
|
|
|
80
|
-
|
|
41
|
+
@functools.wraps(base_func)
|
|
42
|
+
async def func_with_custom_args(*args: Any, **kwargs: Any) -> Any:
|
|
43
|
+
# Create a partial function with the custom arguments bound.
|
|
44
|
+
partial_func = functools.partial(base_func, **(config.custom_args or {}))
|
|
45
|
+
# Await the result of the partial function call.
|
|
46
|
+
return await partial_func(**kwargs)
|
|
81
47
|
|
|
48
|
+
# Remove custom args from signature
|
|
49
|
+
original_sig = inspect.signature(base_func)
|
|
50
|
+
new_params = [p for name, p in original_sig.parameters.items() if name not in config.custom_args]
|
|
51
|
+
func_with_custom_args.__signature__ = original_sig.replace(parameters=new_params) # type: ignore
|
|
82
52
|
|
|
83
|
-
|
|
84
|
-
|
|
53
|
+
# Remove custom args from docstring.
|
|
54
|
+
func_with_custom_args.__doc__ = remove_params_from_docstring(base_func.__doc__, set(config.custom_args.keys()))
|
|
85
55
|
|
|
86
|
-
|
|
87
|
-
You can also fetch any nested path by using the path-parameter
|
|
88
|
-
(e.g. `{"object_id": "@obj_001", "path": "user_info.given_name"}`
|
|
89
|
-
-> returns the content at obj.user_info.given_name).
|
|
56
|
+
return func_with_custom_args
|
|
90
57
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
"""
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
object_id: str,
|
|
99
|
-
start: int = 0,
|
|
100
|
-
end: int | None = None,
|
|
101
|
-
path: str = "",
|
|
102
|
-
) -> str:
|
|
103
|
-
"""Extract a slice from a string or list object that is stored in the object store.
|
|
104
|
-
|
|
105
|
-
:param object_id: Identifier of the object.
|
|
106
|
-
:param start: Start index for slicing.
|
|
107
|
-
:param end: End index for slicing (optional - leave empty to get slice from start to end of sequence).
|
|
108
|
-
:param path: Navigation path to object to slice (optional).
|
|
109
|
-
:return: String representation of the slice.
|
|
58
|
+
|
|
59
|
+
def remove_params_from_docstring(docstring: str | None, params_to_remove: set[str]) -> str:
|
|
60
|
+
"""Removes specified parameters from a function's docstring.
|
|
61
|
+
|
|
62
|
+
:param docstring: The docstring to remove the parameters from.
|
|
63
|
+
:param params_to_remove: The set of parameters to remove.
|
|
64
|
+
:returns: The changed docstring.
|
|
110
65
|
"""
|
|
111
|
-
|
|
66
|
+
if docstring is None:
|
|
67
|
+
return ""
|
|
68
|
+
|
|
69
|
+
for param_name in params_to_remove:
|
|
70
|
+
docstring = re.sub(
|
|
71
|
+
rf"^\s*:param\s+{re.escape(param_name)}.*?(?=^\s*:|^\s*$|\Z)",
|
|
72
|
+
"",
|
|
73
|
+
docstring,
|
|
74
|
+
flags=re.MULTILINE | re.DOTALL,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
return "\n".join([line.rstrip() for line in docstring.strip().split("\n")])
|
|
112
78
|
|
|
113
79
|
|
|
114
|
-
|
|
115
|
-
|
|
80
|
+
def apply_workspace(
|
|
81
|
+
base_func: Callable[..., Any], config: ToolConfig, workspace_mode: WorkspaceMode, workspace: str | None = None
|
|
82
|
+
) -> Callable[..., Any]:
|
|
83
|
+
"""
|
|
84
|
+
Applies a deepset workspace to the function depending on the workspace mode and the ToolConfig.
|
|
116
85
|
|
|
117
|
-
|
|
118
|
-
information about features, API usage, best practices, and troubleshooting guides.
|
|
119
|
-
Use this when you need to look up specific deepset functionality or help users
|
|
120
|
-
understand how to use deepset features.
|
|
86
|
+
Removes the workspace argument from the function's signature and docstring if applied.
|
|
121
87
|
|
|
122
|
-
:param
|
|
123
|
-
:
|
|
88
|
+
:param base_func: The function to apply workspace to.
|
|
89
|
+
:param config: The ToolConfig for the function.
|
|
90
|
+
:param workspace_mode: The WorkspaceMode for the function.
|
|
91
|
+
:param workspace: The workspace to use for static mode.
|
|
92
|
+
:returns: Function with workspace handling applied and updated signature/docstring.
|
|
93
|
+
:raises ValueError: If workspace is required but not available.
|
|
124
94
|
"""
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
) as client:
|
|
128
|
-
response = await search_docs_tool(
|
|
129
|
-
client=client,
|
|
130
|
-
workspace=os.environ["DEEPSET_DOCS_WORKSPACE"],
|
|
131
|
-
pipeline_name=os.environ["DEEPSET_DOCS_PIPELINE_NAME"],
|
|
132
|
-
query=query,
|
|
133
|
-
)
|
|
134
|
-
return response
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
class WorkspaceMode(StrEnum):
|
|
138
|
-
"""Configuration for how workspace is provided to tools."""
|
|
139
|
-
|
|
140
|
-
STATIC = "static" # workspace from env, no parameter in tool signature
|
|
141
|
-
DYNAMIC = "dynamic" # workspace as required parameter in tool signature
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
class MemoryType(StrEnum):
|
|
145
|
-
"""Configuration for how memory is provided to tools."""
|
|
146
|
-
|
|
147
|
-
EXPLORABLE = "explorable"
|
|
148
|
-
REFERENCEABLE = "referenceable"
|
|
149
|
-
BOTH = "both"
|
|
150
|
-
NO_MEMORY = "no_memory"
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
@dataclass
|
|
154
|
-
class ToolConfig:
|
|
155
|
-
"""Configuration for tool registration."""
|
|
156
|
-
|
|
157
|
-
needs_client: bool = False
|
|
158
|
-
needs_workspace: bool = False
|
|
159
|
-
memory_type: MemoryType = MemoryType.NO_MEMORY
|
|
160
|
-
custom_args: dict[str, Any] | None = None # For special cases like search_component_definition
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
def get_workspace_from_env() -> str:
|
|
164
|
-
"""Gets the workspace configured from environment variable."""
|
|
165
|
-
workspace = os.environ.get("DEEPSET_WORKSPACE")
|
|
166
|
-
if not workspace:
|
|
167
|
-
raise ValueError("DEEPSET_WORKSPACE environment variable not set")
|
|
168
|
-
return workspace
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
TOOL_REGISTRY: dict[str, tuple[Callable[..., Any], ToolConfig]] = {
|
|
172
|
-
# Workspace tools
|
|
173
|
-
"list_pipelines": (
|
|
174
|
-
list_pipelines_tool,
|
|
175
|
-
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.EXPLORABLE),
|
|
176
|
-
),
|
|
177
|
-
"create_pipeline": (
|
|
178
|
-
create_pipeline_tool,
|
|
179
|
-
ToolConfig(
|
|
180
|
-
needs_client=True,
|
|
181
|
-
needs_workspace=True,
|
|
182
|
-
memory_type=MemoryType.BOTH,
|
|
183
|
-
custom_args={"skip_validation_errors": True},
|
|
184
|
-
),
|
|
185
|
-
),
|
|
186
|
-
"update_pipeline": (
|
|
187
|
-
update_pipeline_tool,
|
|
188
|
-
ToolConfig(
|
|
189
|
-
needs_client=True,
|
|
190
|
-
needs_workspace=True,
|
|
191
|
-
memory_type=MemoryType.BOTH,
|
|
192
|
-
custom_args={"skip_validation_errors": True},
|
|
193
|
-
),
|
|
194
|
-
),
|
|
195
|
-
"get_pipeline": (
|
|
196
|
-
get_pipeline_tool,
|
|
197
|
-
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.EXPLORABLE),
|
|
198
|
-
),
|
|
199
|
-
"deploy_pipeline": (
|
|
200
|
-
deploy_pipeline_tool,
|
|
201
|
-
ToolConfig(
|
|
202
|
-
needs_client=True,
|
|
203
|
-
needs_workspace=True,
|
|
204
|
-
memory_type=MemoryType.EXPLORABLE,
|
|
205
|
-
custom_args={"wait_for_deployment": True, "timeout_seconds": 600, "poll_interval": 5},
|
|
206
|
-
),
|
|
207
|
-
),
|
|
208
|
-
"validate_pipeline": (
|
|
209
|
-
validate_pipeline_tool,
|
|
210
|
-
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.BOTH),
|
|
211
|
-
),
|
|
212
|
-
"get_pipeline_logs": (
|
|
213
|
-
get_pipeline_logs_tool,
|
|
214
|
-
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.EXPLORABLE),
|
|
215
|
-
),
|
|
216
|
-
"search_pipeline": (
|
|
217
|
-
search_pipeline_tool,
|
|
218
|
-
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.EXPLORABLE),
|
|
219
|
-
),
|
|
220
|
-
"list_indexes": (
|
|
221
|
-
list_indexes_tool,
|
|
222
|
-
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.EXPLORABLE),
|
|
223
|
-
),
|
|
224
|
-
"get_index": (
|
|
225
|
-
get_index_tool,
|
|
226
|
-
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.EXPLORABLE),
|
|
227
|
-
),
|
|
228
|
-
"create_index": (
|
|
229
|
-
create_index_tool,
|
|
230
|
-
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.BOTH),
|
|
231
|
-
),
|
|
232
|
-
"update_index": (
|
|
233
|
-
update_index_tool,
|
|
234
|
-
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.BOTH),
|
|
235
|
-
),
|
|
236
|
-
"deploy_index": (
|
|
237
|
-
deploy_index_tool,
|
|
238
|
-
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.EXPLORABLE),
|
|
239
|
-
),
|
|
240
|
-
"list_templates": (
|
|
241
|
-
list_pipeline_templates_tool,
|
|
242
|
-
ToolConfig(
|
|
243
|
-
needs_client=True,
|
|
244
|
-
needs_workspace=True,
|
|
245
|
-
memory_type=MemoryType.EXPLORABLE,
|
|
246
|
-
custom_args={"field": "created_at", "order": "DESC", "limit": 100},
|
|
247
|
-
),
|
|
248
|
-
),
|
|
249
|
-
"get_template": (
|
|
250
|
-
get_pipeline_template_tool,
|
|
251
|
-
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.EXPLORABLE),
|
|
252
|
-
),
|
|
253
|
-
"search_templates": (
|
|
254
|
-
search_pipeline_templates_tool,
|
|
255
|
-
ToolConfig(
|
|
256
|
-
needs_client=True,
|
|
257
|
-
needs_workspace=True,
|
|
258
|
-
memory_type=MemoryType.EXPLORABLE,
|
|
259
|
-
custom_args={"model": get_initialized_model()},
|
|
260
|
-
),
|
|
261
|
-
),
|
|
262
|
-
"list_custom_component_installations": (
|
|
263
|
-
list_custom_component_installations_tool,
|
|
264
|
-
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.EXPLORABLE),
|
|
265
|
-
),
|
|
266
|
-
"get_latest_custom_component_installation_logs": (
|
|
267
|
-
get_latest_custom_component_installation_logs_tool,
|
|
268
|
-
ToolConfig(needs_client=True, needs_workspace=True, memory_type=MemoryType.EXPLORABLE),
|
|
269
|
-
),
|
|
270
|
-
# Non-workspace tools
|
|
271
|
-
"list_component_families": (
|
|
272
|
-
list_component_families_tool,
|
|
273
|
-
ToolConfig(needs_client=True, memory_type=MemoryType.EXPLORABLE),
|
|
274
|
-
),
|
|
275
|
-
"get_component_definition": (
|
|
276
|
-
get_component_definition_tool,
|
|
277
|
-
ToolConfig(needs_client=True, memory_type=MemoryType.EXPLORABLE),
|
|
278
|
-
),
|
|
279
|
-
"search_component_definitions": (
|
|
280
|
-
search_component_definition_tool,
|
|
281
|
-
ToolConfig(
|
|
282
|
-
needs_client=True, memory_type=MemoryType.EXPLORABLE, custom_args={"model": get_initialized_model()}
|
|
283
|
-
),
|
|
284
|
-
),
|
|
285
|
-
"get_custom_components": (
|
|
286
|
-
get_custom_components_tool,
|
|
287
|
-
ToolConfig(needs_client=True, memory_type=MemoryType.EXPLORABLE),
|
|
288
|
-
),
|
|
289
|
-
"list_secrets": (list_secrets_tool, ToolConfig(needs_client=True, memory_type=MemoryType.EXPLORABLE)),
|
|
290
|
-
"get_secret": (get_secret_tool, ToolConfig(needs_client=True, memory_type=MemoryType.EXPLORABLE)),
|
|
291
|
-
"list_workspaces": (list_workspaces_tool, ToolConfig(needs_client=True, memory_type=MemoryType.EXPLORABLE)),
|
|
292
|
-
"get_workspace": (get_workspace_tool, ToolConfig(needs_client=True, memory_type=MemoryType.EXPLORABLE)),
|
|
293
|
-
"create_workspace": (create_workspace_tool, ToolConfig(needs_client=True, memory_type=MemoryType.EXPLORABLE)),
|
|
294
|
-
"get_from_object_store": (get_from_object_store, ToolConfig(memory_type=MemoryType.NO_MEMORY)),
|
|
295
|
-
"get_slice_from_object_store": (get_slice_from_object_store, ToolConfig(memory_type=MemoryType.NO_MEMORY)),
|
|
296
|
-
"search_docs": (search_docs, ToolConfig(memory_type=MemoryType.NO_MEMORY)),
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
def create_enhanced_tool(
|
|
301
|
-
base_func: Callable[..., Any], config: ToolConfig, workspace_mode: WorkspaceMode, workspace: str | None = None
|
|
302
|
-
) -> Callable[..., Awaitable[Any]]:
|
|
303
|
-
"""Universal tool creator that handles client injection, workspace, and decorators.
|
|
95
|
+
if not config.needs_workspace:
|
|
96
|
+
return base_func
|
|
304
97
|
|
|
305
|
-
|
|
306
|
-
It can inject a `client`, manage a `workspace` parameter (either explicitly required
|
|
307
|
-
or implicitly provided from the environment), and apply memory-related decorators.
|
|
98
|
+
if workspace_mode == WorkspaceMode.STATIC:
|
|
308
99
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
100
|
+
@functools.wraps(base_func)
|
|
101
|
+
async def workspace_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
102
|
+
return await base_func(*args, workspace=workspace, **kwargs)
|
|
312
103
|
|
|
313
|
-
|
|
314
|
-
|
|
104
|
+
# Remove workspace from signature
|
|
105
|
+
original_sig = inspect.signature(base_func)
|
|
106
|
+
new_params = [p for name, p in original_sig.parameters.items() if name != "workspace"]
|
|
107
|
+
workspace_wrapper.__signature__ = original_sig.replace(parameters=new_params) # type: ignore
|
|
315
108
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
109
|
+
# Remove workspace from docstring
|
|
110
|
+
workspace_wrapper.__doc__ = remove_params_from_docstring(base_func.__doc__, {"workspace"})
|
|
111
|
+
|
|
112
|
+
return workspace_wrapper
|
|
113
|
+
else:
|
|
114
|
+
# For dynamic mode, workspace is passed as parameter
|
|
115
|
+
return base_func
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def apply_memory(
|
|
119
|
+
base_func: Callable[..., Any], config: ToolConfig, store: ObjectStore | None = None
|
|
120
|
+
) -> Callable[..., Any]:
|
|
121
|
+
"""
|
|
122
|
+
Applies memory decorators to a function if requested in the ToolConfig.
|
|
321
123
|
|
|
322
|
-
|
|
323
|
-
|
|
124
|
+
:param base_func: The function to apply memory decorator to.
|
|
125
|
+
:param config: The ToolConfig for the function.
|
|
126
|
+
:param store: The ObjectStore instance to use
|
|
127
|
+
:returns: Function with memory decorators applied.
|
|
128
|
+
:raises ValueError: If an invalid memory type is specified.
|
|
324
129
|
"""
|
|
325
|
-
|
|
130
|
+
if config.memory_type == MemoryType.NO_MEMORY:
|
|
131
|
+
return base_func
|
|
326
132
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
func_to_decorate: Any
|
|
330
|
-
if config.custom_args:
|
|
133
|
+
if store is None:
|
|
134
|
+
raise ValueError("ObjectStore instance is required for memory decorators")
|
|
331
135
|
|
|
332
|
-
|
|
333
|
-
async def func_with_custom_args(*args: Any, **kwargs: Any) -> Any:
|
|
334
|
-
# Create a partial function with the custom arguments bound.
|
|
335
|
-
partial_func = functools.partial(original_func, **(config.custom_args or {}))
|
|
336
|
-
# Await the result of the partial function call.
|
|
337
|
-
return await partial_func(**kwargs)
|
|
136
|
+
explorer = RichExplorer(store)
|
|
338
137
|
|
|
339
|
-
|
|
138
|
+
if config.memory_type == MemoryType.EXPLORABLE:
|
|
139
|
+
return explorable(object_store=store, explorer=explorer)(base_func)
|
|
140
|
+
elif config.memory_type == MemoryType.REFERENCEABLE:
|
|
141
|
+
return referenceable(object_store=store, explorer=explorer)(base_func)
|
|
142
|
+
elif config.memory_type == MemoryType.BOTH:
|
|
143
|
+
return explorable_and_referenceable(object_store=store, explorer=explorer)(base_func)
|
|
340
144
|
else:
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
# Apply memory-related decorators to the (potentially wrapped) function
|
|
344
|
-
decorated_func = func_to_decorate
|
|
345
|
-
if config.memory_type != MemoryType.NO_MEMORY:
|
|
346
|
-
store = STORE
|
|
347
|
-
explorer = RichExplorer(store)
|
|
348
|
-
|
|
349
|
-
if config.memory_type == MemoryType.EXPLORABLE:
|
|
350
|
-
decorated_func = explorable(object_store=store, explorer=explorer)(decorated_func)
|
|
351
|
-
elif config.memory_type == MemoryType.REFERENCEABLE:
|
|
352
|
-
decorated_func = referenceable(object_store=store, explorer=explorer)(decorated_func)
|
|
353
|
-
elif config.memory_type == MemoryType.BOTH:
|
|
354
|
-
decorated_func = explorable_and_referenceable(object_store=store, explorer=explorer)(decorated_func)
|
|
355
|
-
|
|
356
|
-
# Determine the parameters to remove from the original function's signature
|
|
357
|
-
params_to_remove: set[str] = set()
|
|
358
|
-
if config.custom_args:
|
|
359
|
-
params_to_remove.update(config.custom_args.keys())
|
|
360
|
-
if config.needs_client:
|
|
361
|
-
params_to_remove.add("client")
|
|
362
|
-
if config.needs_workspace and workspace_mode == WorkspaceMode.STATIC:
|
|
363
|
-
params_to_remove.add("workspace")
|
|
364
|
-
|
|
365
|
-
# Create the new signature from the original function
|
|
366
|
-
original_sig = inspect.signature(original_func)
|
|
367
|
-
final_params = [p for name, p in original_sig.parameters.items() if name not in params_to_remove]
|
|
368
|
-
|
|
369
|
-
# Convert all positional-or-keyword parameters to be keyword-only
|
|
370
|
-
keyword_only_params = [
|
|
371
|
-
p.replace(kind=inspect.Parameter.KEYWORD_ONLY) if p.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD else p
|
|
372
|
-
for p in final_params
|
|
373
|
-
]
|
|
374
|
-
new_sig = original_sig.replace(parameters=keyword_only_params)
|
|
375
|
-
|
|
376
|
-
# Create the final wrapper function that handles client/workspace injection
|
|
377
|
-
if config.needs_client:
|
|
378
|
-
if config.needs_workspace:
|
|
379
|
-
if workspace_mode == WorkspaceMode.STATIC:
|
|
380
|
-
|
|
381
|
-
async def workspace_environment_wrapper(**kwargs: Any) -> Any:
|
|
382
|
-
ws = workspace or get_workspace_from_env()
|
|
383
|
-
async with AsyncDeepsetClient(transport_config=DEFAULT_CLIENT_HEADER) as client:
|
|
384
|
-
return await decorated_func(client=client, workspace=ws, **kwargs)
|
|
385
|
-
|
|
386
|
-
wrapper = workspace_environment_wrapper
|
|
387
|
-
else: # DYNAMIC mode
|
|
388
|
-
|
|
389
|
-
async def workspace_explicit_wrapper(**kwargs: Any) -> Any:
|
|
390
|
-
async with AsyncDeepsetClient(transport_config=DEFAULT_CLIENT_HEADER) as client:
|
|
391
|
-
# The first argument is the workspace, which must be passed by keyword.
|
|
392
|
-
return await decorated_func(client=client, **kwargs)
|
|
393
|
-
|
|
394
|
-
wrapper = workspace_explicit_wrapper
|
|
395
|
-
else: # Client-only tools
|
|
396
|
-
|
|
397
|
-
async def client_only_wrapper(**kwargs: Any) -> Any:
|
|
398
|
-
async with AsyncDeepsetClient(transport_config=DEFAULT_CLIENT_HEADER) as client:
|
|
399
|
-
return await decorated_func(client=client, **kwargs)
|
|
400
|
-
|
|
401
|
-
wrapper = client_only_wrapper
|
|
402
|
-
else: # No injection needed
|
|
403
|
-
if inspect.iscoroutinefunction(decorated_func):
|
|
404
|
-
|
|
405
|
-
async def no_injection_wrapper(**kwargs: Any) -> Any:
|
|
406
|
-
return await decorated_func(**kwargs)
|
|
407
|
-
|
|
408
|
-
wrapper = no_injection_wrapper
|
|
409
|
-
else:
|
|
145
|
+
raise ValueError(f"Invalid memory type: {config.memory_type}")
|
|
410
146
|
|
|
411
|
-
@functools.wraps(decorated_func)
|
|
412
|
-
async def async_wrapper(**kwargs: Any) -> Any:
|
|
413
|
-
return decorated_func(**kwargs)
|
|
414
|
-
|
|
415
|
-
wrapper = async_wrapper
|
|
416
|
-
|
|
417
|
-
# Set metadata on the final wrapper
|
|
418
|
-
wrapper.__signature__ = new_sig # type: ignore
|
|
419
|
-
wrapper.__name__ = original_func.__name__
|
|
420
|
-
|
|
421
|
-
# Process the docstring to remove injected and partially applied parameters
|
|
422
|
-
if original_func.__doc__:
|
|
423
|
-
import re
|
|
424
|
-
|
|
425
|
-
doc = original_func.__doc__
|
|
426
|
-
params_to_remove_from_doc = set()
|
|
427
|
-
if config.needs_client:
|
|
428
|
-
params_to_remove_from_doc.add("client")
|
|
429
|
-
if config.needs_workspace and workspace_mode == WorkspaceMode.STATIC:
|
|
430
|
-
params_to_remove_from_doc.add("workspace")
|
|
431
|
-
if config.custom_args:
|
|
432
|
-
params_to_remove_from_doc.update(config.custom_args.keys())
|
|
433
|
-
|
|
434
|
-
for param_name in params_to_remove_from_doc:
|
|
435
|
-
doc = re.sub(
|
|
436
|
-
rf"^\s*:param\s+{re.escape(param_name)}.*?(?=^\s*:|^\s*$|\Z)",
|
|
437
|
-
"",
|
|
438
|
-
doc,
|
|
439
|
-
flags=re.MULTILINE | re.DOTALL,
|
|
440
|
-
)
|
|
441
147
|
|
|
442
|
-
|
|
148
|
+
def apply_client(
|
|
149
|
+
base_func: Callable[..., Any],
|
|
150
|
+
config: ToolConfig,
|
|
151
|
+
use_request_context: bool = True,
|
|
152
|
+
base_url: str | None = None,
|
|
153
|
+
api_key: str | None = None,
|
|
154
|
+
) -> Callable[..., Any]:
|
|
155
|
+
"""
|
|
156
|
+
Applies the deepset API client to a function.
|
|
157
|
+
|
|
158
|
+
Optionally collects the API key from the request context, when use_request_context is True.
|
|
159
|
+
Modifies the function's signature and docstring to remove the client argument.
|
|
160
|
+
Adds a 'ctx' argument to the signature if the request context is used.
|
|
161
|
+
|
|
162
|
+
:param base_func: The function to apply the client to.
|
|
163
|
+
:param config: The ToolConfig for the function.
|
|
164
|
+
:param use_request_context: Whether to collect the API key from the request context.
|
|
165
|
+
:param base_url: Base URL for the deepset API.
|
|
166
|
+
:param api_key: The API key to use.
|
|
167
|
+
:returns: Function with client injection applied and updated signature/docstring.
|
|
168
|
+
:raises ValueError: If API key cannot be extracted from request context.
|
|
169
|
+
"""
|
|
170
|
+
if not config.needs_client:
|
|
171
|
+
return base_func
|
|
172
|
+
|
|
173
|
+
if use_request_context:
|
|
174
|
+
|
|
175
|
+
@functools.wraps(base_func)
|
|
176
|
+
async def client_wrapper_with_context(*args: Any, **kwargs: Any) -> Any:
|
|
177
|
+
ctx = kwargs.pop("ctx", None)
|
|
178
|
+
if not ctx:
|
|
179
|
+
raise ValueError("Context is required for client authentication")
|
|
180
|
+
|
|
181
|
+
api_key = ctx.request_context.request.headers.get("Authorization")
|
|
182
|
+
if not api_key:
|
|
183
|
+
raise ValueError("No Authorization header found in request context")
|
|
184
|
+
|
|
185
|
+
api_key = api_key.replace("Bearer ", "")
|
|
186
|
+
|
|
187
|
+
if not api_key:
|
|
188
|
+
raise ValueError("API key cannot be empty")
|
|
189
|
+
|
|
190
|
+
client_kwargs: dict[str, Any] = {"transport_config": DEFAULT_CLIENT_HEADER, "api_key": api_key}
|
|
191
|
+
if base_url:
|
|
192
|
+
client_kwargs["base_url"] = base_url
|
|
193
|
+
async with AsyncDeepsetClient(**client_kwargs) as client:
|
|
194
|
+
return await base_func(*args, client=client, **kwargs)
|
|
195
|
+
|
|
196
|
+
# Remove client from signature and add ctx
|
|
197
|
+
original_sig = inspect.signature(base_func)
|
|
198
|
+
new_params = [p for name, p in original_sig.parameters.items() if name != "client"]
|
|
199
|
+
ctx_param = inspect.Parameter(name="ctx", kind=inspect.Parameter.KEYWORD_ONLY, annotation=Context)
|
|
200
|
+
new_params.append(ctx_param)
|
|
201
|
+
client_wrapper_with_context.__signature__ = original_sig.replace(parameters=new_params) # type: ignore
|
|
202
|
+
client_wrapper_with_context.__doc__ = remove_params_from_docstring(base_func.__doc__, {"client"})
|
|
203
|
+
|
|
204
|
+
return client_wrapper_with_context
|
|
443
205
|
else:
|
|
444
|
-
wrapper.__doc__ = original_func.__doc__
|
|
445
206
|
|
|
446
|
-
|
|
207
|
+
@functools.wraps(base_func)
|
|
208
|
+
async def client_wrapper_without_context(*args: Any, **kwargs: Any) -> Any:
|
|
209
|
+
client_kwargs: dict[str, Any] = {"transport_config": DEFAULT_CLIENT_HEADER, "api_key": api_key}
|
|
210
|
+
if base_url:
|
|
211
|
+
client_kwargs["base_url"] = base_url
|
|
212
|
+
async with AsyncDeepsetClient(**client_kwargs) as client:
|
|
213
|
+
return await base_func(*args, client=client, **kwargs)
|
|
214
|
+
|
|
215
|
+
# Remove client from signature
|
|
216
|
+
original_sig = inspect.signature(base_func)
|
|
217
|
+
new_params = [p for name, p in original_sig.parameters.items() if name != "client"]
|
|
218
|
+
client_wrapper_without_context.__signature__ = original_sig.replace(parameters=new_params) # type: ignore
|
|
219
|
+
|
|
220
|
+
# Remove client from docstring
|
|
221
|
+
client_wrapper_without_context.__doc__ = remove_params_from_docstring(base_func.__doc__, {"client"})
|
|
222
|
+
|
|
223
|
+
return client_wrapper_without_context
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def build_tool(
|
|
227
|
+
base_func: Callable[..., Any],
|
|
228
|
+
config: ToolConfig,
|
|
229
|
+
workspace_mode: WorkspaceMode,
|
|
230
|
+
api_key: str | None = None,
|
|
231
|
+
workspace: str | None = None,
|
|
232
|
+
use_request_context: bool = True,
|
|
233
|
+
base_url: str | None = None,
|
|
234
|
+
object_store: ObjectStore | None = None,
|
|
235
|
+
) -> Callable[..., Awaitable[Any]]:
|
|
236
|
+
"""
|
|
237
|
+
Universal tool creator that handles client injection, workspace, and decorators.
|
|
238
|
+
|
|
239
|
+
This function takes a base tool function and enhances it based on the tool's configuration.
|
|
240
|
+
|
|
241
|
+
:param base_func: The base tool function.
|
|
242
|
+
:param config: Tool configuration specifying dependencies and custom arguments.
|
|
243
|
+
:param workspace_mode: How the workspace should be handled.
|
|
244
|
+
:param api_key: The deepset API key to use.
|
|
245
|
+
:param workspace: The workspace to use when using a static workspace.
|
|
246
|
+
:param use_request_context: Whether to collect the API key from the request context.
|
|
247
|
+
:param base_url: Base URL for the deepset API.
|
|
248
|
+
:param object_store: The ObjectStore instance to use for memory decorators.
|
|
249
|
+
:returns: An enhanced, awaitable tool function with an updated signature and docstring.
|
|
250
|
+
"""
|
|
251
|
+
enhanced_func = base_func
|
|
252
|
+
|
|
253
|
+
# Apply custom arguments first
|
|
254
|
+
enhanced_func = apply_custom_args(enhanced_func, config)
|
|
255
|
+
|
|
256
|
+
# Apply memory decorators with the provided store
|
|
257
|
+
enhanced_func = apply_memory(enhanced_func, config, object_store)
|
|
258
|
+
|
|
259
|
+
# Apply workspace handling
|
|
260
|
+
enhanced_func = apply_workspace(enhanced_func, config, workspace_mode, workspace)
|
|
261
|
+
|
|
262
|
+
# Apply client injection (adds ctx parameter if needed)
|
|
263
|
+
enhanced_func = apply_client(
|
|
264
|
+
enhanced_func, config, use_request_context=use_request_context, base_url=base_url, api_key=api_key
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# Create final async wrapper if needed
|
|
268
|
+
if not inspect.iscoroutinefunction(enhanced_func):
|
|
269
|
+
|
|
270
|
+
@functools.wraps(enhanced_func)
|
|
271
|
+
async def async_wrapper(**kwargs: Any) -> Any:
|
|
272
|
+
return enhanced_func(**kwargs)
|
|
273
|
+
|
|
274
|
+
# Copy over the signature from the enhanced function
|
|
275
|
+
async_wrapper.__signature__ = inspect.signature(enhanced_func) # type: ignore
|
|
276
|
+
return async_wrapper
|
|
277
|
+
|
|
278
|
+
enhanced_func.__name__ = base_func.__name__
|
|
279
|
+
|
|
280
|
+
return enhanced_func
|
|
447
281
|
|
|
448
282
|
|
|
449
283
|
def register_tools(
|
|
450
|
-
|
|
284
|
+
mcp_server_instance: FastMCP,
|
|
285
|
+
workspace_mode: WorkspaceMode,
|
|
286
|
+
api_key: str | None = None,
|
|
287
|
+
workspace: str | None = None,
|
|
288
|
+
tool_names: set[str] | None = None,
|
|
289
|
+
get_api_key_from_authorization_header: bool = True,
|
|
290
|
+
docs_config: DeepsetDocsConfig | None = None,
|
|
291
|
+
base_url: str | None = None,
|
|
292
|
+
object_store: ObjectStore | None = None,
|
|
451
293
|
) -> None:
|
|
452
294
|
"""Register tools with unified configuration.
|
|
453
295
|
|
|
454
296
|
Args:
|
|
455
|
-
|
|
297
|
+
mcp_server_instance: FastMCP server instance
|
|
456
298
|
workspace_mode: How workspace should be handled
|
|
457
|
-
|
|
299
|
+
api_key: An api key for the deepset AI platform; only needs to be provided when not read from request context.
|
|
300
|
+
workspace: Workspace to use; only needs to be provided if using a static workspace.
|
|
458
301
|
tool_names: Set of tool names to register (if None, registers all tools)
|
|
302
|
+
get_api_key_from_authorization_header: Whether to use request context to retrieve an API key for tool execution.
|
|
303
|
+
docs_config: Configuration for the deepset documentation search tool.
|
|
304
|
+
base_url: Base URL for the deepset API.
|
|
305
|
+
object_store: The ObjectStore instance to use for memory decorators.
|
|
459
306
|
"""
|
|
460
|
-
|
|
461
|
-
|
|
307
|
+
if api_key is None and not get_api_key_from_authorization_header:
|
|
308
|
+
raise ValueError(
|
|
309
|
+
"'api_key' cannot be 'None' when 'use_request_context' is False. "
|
|
310
|
+
"Either pass 'api_key' or 'use_request_context'."
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
if workspace_mode == WorkspaceMode.STATIC and workspace is None:
|
|
314
|
+
raise ValueError(
|
|
315
|
+
"'workspace_mode' set to 'static' but no workspace provided. "
|
|
316
|
+
"You need to set a deepset workspace name as 'workspace'."
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
if docs_config is None and tool_names is None:
|
|
320
|
+
raise ValueError(
|
|
321
|
+
f"'docs_config' cannot be None when requesting to register all tools. "
|
|
322
|
+
f"Either pass 'docs_config' or disable the '{DOCS_SEARCH_TOOL_NAME}' tool."
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
if docs_config is None and tool_names is not None and DOCS_SEARCH_TOOL_NAME in tool_names:
|
|
326
|
+
raise ValueError(
|
|
327
|
+
f"Requested to register '{DOCS_SEARCH_TOOL_NAME}' tool but 'docs_config' is 'None'. "
|
|
328
|
+
f"Provide a valid 'docs_config' to register this tool."
|
|
329
|
+
)
|
|
462
330
|
|
|
463
331
|
# Validate tool names if provided
|
|
464
332
|
if tool_names is not None:
|
|
@@ -469,30 +337,35 @@ def register_tools(
|
|
|
469
337
|
sorted_all = sorted(all_tools)
|
|
470
338
|
raise ValueError(f"Unknown tools: {', '.join(sorted_invalid)}\nAvailable tools: {', '.join(sorted_all)}")
|
|
471
339
|
|
|
472
|
-
# Warn if search_docs was requested but config is missing
|
|
473
|
-
if "search_docs" in tool_names and not docs_available:
|
|
474
|
-
logging.warning(
|
|
475
|
-
"Documentation search tool requested but not available. To enable, set the DEEPSET_DOCS_SHARE_URL "
|
|
476
|
-
"environment variable."
|
|
477
|
-
)
|
|
478
|
-
|
|
479
340
|
tools_to_register = tool_names.copy()
|
|
480
341
|
else:
|
|
481
342
|
tools_to_register = set(TOOL_REGISTRY.keys())
|
|
482
343
|
|
|
483
|
-
# Warn if search_docs would be skipped in "all tools" mode
|
|
484
|
-
if not docs_available:
|
|
485
|
-
logging.warning(
|
|
486
|
-
"Documentation search tool not enabled. To enable, set the DEEPSET_DOCS_SHARE_URL environment variable."
|
|
487
|
-
)
|
|
488
|
-
|
|
489
|
-
# Remove search_docs if config is not available
|
|
490
|
-
if not docs_available:
|
|
491
|
-
tools_to_register.discard("search_docs")
|
|
492
|
-
|
|
493
344
|
for tool_name in tools_to_register:
|
|
494
345
|
base_func, config = TOOL_REGISTRY[tool_name]
|
|
495
|
-
# Create enhanced tool
|
|
496
|
-
enhanced_tool = create_enhanced_tool(base_func, config, workspace_mode, workspace)
|
|
497
346
|
|
|
498
|
-
|
|
347
|
+
if tool_name == DOCS_SEARCH_TOOL_NAME:
|
|
348
|
+
# search_docs is a special tool.
|
|
349
|
+
# base_func is a factory function.
|
|
350
|
+
# We configure with the docs_config to get the actual tool function.
|
|
351
|
+
enhanced_tool = base_func(config=docs_config)
|
|
352
|
+
elif tool_name in ("get_from_object_store", "get_slice_from_object_store"):
|
|
353
|
+
# ObjectStore tools are factory functions that need an explorer created from the store
|
|
354
|
+
if object_store is None:
|
|
355
|
+
raise ValueError(f"ObjectStore instance is required for {tool_name}")
|
|
356
|
+
|
|
357
|
+
explorer = RichExplorer(store=object_store)
|
|
358
|
+
enhanced_tool = base_func(explorer=explorer)
|
|
359
|
+
else:
|
|
360
|
+
enhanced_tool = build_tool(
|
|
361
|
+
base_func=base_func,
|
|
362
|
+
config=config,
|
|
363
|
+
workspace_mode=workspace_mode,
|
|
364
|
+
workspace=workspace,
|
|
365
|
+
use_request_context=get_api_key_from_authorization_header,
|
|
366
|
+
base_url=base_url,
|
|
367
|
+
object_store=object_store,
|
|
368
|
+
api_key=api_key,
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
mcp_server_instance.add_tool(enhanced_tool, name=tool_name)
|