deepset-mcp 0.0.2__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 +0 -0
- deepset_mcp/agents/__init__.py +0 -0
- deepset_mcp/agents/debugging/__init__.py +0 -0
- deepset_mcp/agents/debugging/debugging_agent.py +37 -0
- deepset_mcp/agents/debugging/system_prompt.md +214 -0
- deepset_mcp/agents/generalist/__init__.py +0 -0
- deepset_mcp/agents/generalist/generalist_agent.py +38 -0
- deepset_mcp/agents/generalist/system_prompt.md +241 -0
- deepset_mcp/api/README.md +536 -0
- deepset_mcp/api/__init__.py +0 -0
- deepset_mcp/api/client.py +277 -0
- deepset_mcp/api/custom_components/__init__.py +0 -0
- deepset_mcp/api/custom_components/models.py +25 -0
- deepset_mcp/api/custom_components/protocols.py +17 -0
- deepset_mcp/api/custom_components/resource.py +56 -0
- deepset_mcp/api/exceptions.py +70 -0
- deepset_mcp/api/haystack_service/__init__.py +0 -0
- deepset_mcp/api/haystack_service/protocols.py +13 -0
- deepset_mcp/api/haystack_service/resource.py +55 -0
- deepset_mcp/api/indexes/__init__.py +0 -0
- deepset_mcp/api/indexes/models.py +63 -0
- deepset_mcp/api/indexes/protocols.py +53 -0
- deepset_mcp/api/indexes/resource.py +138 -0
- deepset_mcp/api/integrations/__init__.py +1 -0
- deepset_mcp/api/integrations/models.py +49 -0
- deepset_mcp/api/integrations/protocols.py +27 -0
- deepset_mcp/api/integrations/resource.py +57 -0
- deepset_mcp/api/pipeline/__init__.py +17 -0
- deepset_mcp/api/pipeline/log_level.py +9 -0
- deepset_mcp/api/pipeline/models.py +235 -0
- deepset_mcp/api/pipeline/protocols.py +83 -0
- deepset_mcp/api/pipeline/resource.py +378 -0
- deepset_mcp/api/pipeline_template/__init__.py +0 -0
- deepset_mcp/api/pipeline_template/models.py +56 -0
- deepset_mcp/api/pipeline_template/protocols.py +17 -0
- deepset_mcp/api/pipeline_template/resource.py +88 -0
- deepset_mcp/api/protocols.py +122 -0
- deepset_mcp/api/secrets/__init__.py +0 -0
- deepset_mcp/api/secrets/models.py +16 -0
- deepset_mcp/api/secrets/protocols.py +29 -0
- deepset_mcp/api/secrets/resource.py +112 -0
- deepset_mcp/api/shared_models.py +17 -0
- deepset_mcp/api/transport.py +336 -0
- deepset_mcp/api/user/__init__.py +0 -0
- deepset_mcp/api/user/protocols.py +11 -0
- deepset_mcp/api/user/resource.py +38 -0
- deepset_mcp/api/workspace/__init__.py +7 -0
- deepset_mcp/api/workspace/models.py +23 -0
- deepset_mcp/api/workspace/protocols.py +41 -0
- deepset_mcp/api/workspace/resource.py +94 -0
- deepset_mcp/benchmark/README.md +425 -0
- deepset_mcp/benchmark/__init__.py +1 -0
- deepset_mcp/benchmark/agent_configs/debugging_agent.yml +10 -0
- deepset_mcp/benchmark/agent_configs/generalist_agent.yml +6 -0
- deepset_mcp/benchmark/dp_validation_error_analysis/__init__.py +0 -0
- deepset_mcp/benchmark/dp_validation_error_analysis/eda.ipynb +757 -0
- deepset_mcp/benchmark/dp_validation_error_analysis/prepare_interaction_data.ipynb +167 -0
- deepset_mcp/benchmark/dp_validation_error_analysis/preprocessing_utils.py +213 -0
- deepset_mcp/benchmark/runner/__init__.py +0 -0
- deepset_mcp/benchmark/runner/agent_benchmark_runner.py +561 -0
- deepset_mcp/benchmark/runner/agent_loader.py +110 -0
- deepset_mcp/benchmark/runner/cli.py +39 -0
- deepset_mcp/benchmark/runner/cli_agent.py +373 -0
- deepset_mcp/benchmark/runner/cli_index.py +71 -0
- deepset_mcp/benchmark/runner/cli_pipeline.py +73 -0
- deepset_mcp/benchmark/runner/cli_tests.py +226 -0
- deepset_mcp/benchmark/runner/cli_utils.py +61 -0
- deepset_mcp/benchmark/runner/config.py +73 -0
- deepset_mcp/benchmark/runner/config_loader.py +64 -0
- deepset_mcp/benchmark/runner/interactive.py +140 -0
- deepset_mcp/benchmark/runner/models.py +203 -0
- deepset_mcp/benchmark/runner/repl.py +67 -0
- deepset_mcp/benchmark/runner/setup_actions.py +238 -0
- deepset_mcp/benchmark/runner/streaming.py +360 -0
- deepset_mcp/benchmark/runner/teardown_actions.py +196 -0
- deepset_mcp/benchmark/runner/tracing.py +21 -0
- deepset_mcp/benchmark/tasks/chat_rag_answers_wrong_format.yml +16 -0
- deepset_mcp/benchmark/tasks/documents_output_wrong.yml +13 -0
- deepset_mcp/benchmark/tasks/jinja_str_instead_of_complex_type.yml +11 -0
- deepset_mcp/benchmark/tasks/jinja_syntax_error.yml +11 -0
- deepset_mcp/benchmark/tasks/missing_output_mapping.yml +14 -0
- deepset_mcp/benchmark/tasks/no_query_input.yml +13 -0
- deepset_mcp/benchmark/tasks/pipelines/chat_agent_jinja_str.yml +141 -0
- deepset_mcp/benchmark/tasks/pipelines/chat_agent_jinja_syntax.yml +141 -0
- deepset_mcp/benchmark/tasks/pipelines/chat_rag_answers_wrong_format.yml +181 -0
- deepset_mcp/benchmark/tasks/pipelines/chat_rag_missing_output_mapping.yml +189 -0
- deepset_mcp/benchmark/tasks/pipelines/rag_documents_wrong_format.yml +193 -0
- deepset_mcp/benchmark/tasks/pipelines/rag_no_query_input.yml +191 -0
- deepset_mcp/benchmark/tasks/pipelines/standard_index.yml +167 -0
- deepset_mcp/initialize_embedding_model.py +12 -0
- deepset_mcp/main.py +133 -0
- deepset_mcp/prompts/deepset_copilot_prompt.md +271 -0
- deepset_mcp/prompts/deepset_debugging_agent.md +214 -0
- deepset_mcp/store.py +5 -0
- deepset_mcp/tool_factory.py +473 -0
- deepset_mcp/tools/__init__.py +0 -0
- deepset_mcp/tools/custom_components.py +52 -0
- deepset_mcp/tools/doc_search.py +83 -0
- deepset_mcp/tools/haystack_service.py +358 -0
- deepset_mcp/tools/haystack_service_models.py +97 -0
- deepset_mcp/tools/indexes.py +129 -0
- deepset_mcp/tools/model_protocol.py +16 -0
- deepset_mcp/tools/pipeline.py +335 -0
- deepset_mcp/tools/pipeline_template.py +116 -0
- deepset_mcp/tools/secrets.py +45 -0
- deepset_mcp/tools/tokonomics/__init__.py +73 -0
- deepset_mcp/tools/tokonomics/decorators.py +396 -0
- deepset_mcp/tools/tokonomics/explorer.py +347 -0
- deepset_mcp/tools/tokonomics/object_store.py +177 -0
- deepset_mcp/tools/workspace.py +61 -0
- deepset_mcp-0.0.2.dist-info/METADATA +288 -0
- deepset_mcp-0.0.2.dist-info/RECORD +114 -0
- deepset_mcp-0.0.2.dist-info/WHEEL +4 -0
- deepset_mcp-0.0.2.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Decorator factories for explorable and referenceable tools.
|
|
3
|
+
|
|
4
|
+
This module provides the @explorable and @referenceable decorators that enable
|
|
5
|
+
tools to store their outputs and accept reference inputs.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import inspect
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
from functools import wraps
|
|
13
|
+
from types import UnionType
|
|
14
|
+
from typing import Any, TypeVar, Union, get_args, get_origin
|
|
15
|
+
|
|
16
|
+
from glom import GlomError, glom
|
|
17
|
+
|
|
18
|
+
from .explorer import RichExplorer
|
|
19
|
+
from .object_store import Explorable, ObjectRef, ObjectStore
|
|
20
|
+
|
|
21
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _is_reference(value: Any) -> bool:
|
|
25
|
+
"""Check if a value is a reference string."""
|
|
26
|
+
return isinstance(value, str) and ObjectRef.parse(value) is not None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _type_allows_str(annotation: Any) -> bool:
|
|
30
|
+
"""Check if a type annotation already allows string values."""
|
|
31
|
+
if annotation is str:
|
|
32
|
+
return True
|
|
33
|
+
origin = get_origin(annotation)
|
|
34
|
+
if origin in {Union, UnionType}:
|
|
35
|
+
return str in get_args(annotation)
|
|
36
|
+
return False
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _add_str_to_type(annotation: Any) -> Any:
|
|
40
|
+
"""Add str to a type annotation to allow reference strings."""
|
|
41
|
+
if annotation is inspect.Parameter.empty:
|
|
42
|
+
return str
|
|
43
|
+
if _type_allows_str(annotation):
|
|
44
|
+
return annotation
|
|
45
|
+
return annotation | str
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _enhance_docstring_for_references(original: str, param_info: dict[str, dict[str, Any]], func_name: str) -> str:
|
|
49
|
+
"""Add reference documentation to function docstring.
|
|
50
|
+
|
|
51
|
+
:param original: Original docstring.
|
|
52
|
+
:param param_info: Parameter modification info.
|
|
53
|
+
:param func_name: Function name for examples.
|
|
54
|
+
:return: Enhanced docstring.
|
|
55
|
+
"""
|
|
56
|
+
if not original:
|
|
57
|
+
original = f"{func_name} function with reference support."
|
|
58
|
+
|
|
59
|
+
# Build the reference section
|
|
60
|
+
ref_section = [
|
|
61
|
+
"",
|
|
62
|
+
"**Reference Support**",
|
|
63
|
+
"",
|
|
64
|
+
"All parameters accept object references in the form ``@obj_id`` or ``@obj_id.path.to.value``.",
|
|
65
|
+
"",
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
if param_info:
|
|
69
|
+
ref_section.append("Parameter types after decoration:")
|
|
70
|
+
ref_section.append("")
|
|
71
|
+
for name, info in param_info.items():
|
|
72
|
+
if info["accepts_str"]:
|
|
73
|
+
ref_section.append(f"- ``{name}``: {info['original']} (already accepts strings)")
|
|
74
|
+
else:
|
|
75
|
+
ref_section.append(f"- ``{name}``: {info['original']} → {info['modified']} (now accepts references)")
|
|
76
|
+
ref_section.append("")
|
|
77
|
+
|
|
78
|
+
ref_section.extend(
|
|
79
|
+
[
|
|
80
|
+
"Examples::",
|
|
81
|
+
"",
|
|
82
|
+
" # Direct call with values",
|
|
83
|
+
f" {func_name}(data={{'key': 'value'}}, threshold=10)",
|
|
84
|
+
"",
|
|
85
|
+
" # Call with references",
|
|
86
|
+
f" {func_name}(data='@obj_001', threshold='@obj_002.config.threshold')",
|
|
87
|
+
"",
|
|
88
|
+
" # Mixed call",
|
|
89
|
+
f" {func_name}(data='@obj_001.items', threshold=10)",
|
|
90
|
+
]
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
return original.rstrip() + "\n" + "\n".join(ref_section)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _enhance_docstring_for_explorable(original: str, func_name: str) -> str:
|
|
97
|
+
"""Add explorable documentation to function docstring.
|
|
98
|
+
|
|
99
|
+
:param original: Original docstring.
|
|
100
|
+
:param func_name: Function name.
|
|
101
|
+
:return: Enhanced docstring.
|
|
102
|
+
"""
|
|
103
|
+
if not original:
|
|
104
|
+
original = f"{func_name} function with stored output."
|
|
105
|
+
|
|
106
|
+
section = [
|
|
107
|
+
"",
|
|
108
|
+
"**Output Storage**",
|
|
109
|
+
"",
|
|
110
|
+
"The output of this function is automatically stored and can be referenced in other functions.",
|
|
111
|
+
"The function returns a formatted preview of the result along with an object ID (e.g., ``@obj_123``).",
|
|
112
|
+
"",
|
|
113
|
+
"Use the returned object ID to pass this result to other functions that accept references.",
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
return original.rstrip() + "\n" + "\n".join(section)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def explorable(
|
|
120
|
+
*,
|
|
121
|
+
object_store: ObjectStore,
|
|
122
|
+
explorer: RichExplorer,
|
|
123
|
+
) -> Callable[[F], F]:
|
|
124
|
+
"""Decorator factory that stores function results for later reference.
|
|
125
|
+
|
|
126
|
+
:param object_store: The object store instance to use for storage.
|
|
127
|
+
:param explorer: The RichExplorer instance to use for previews.
|
|
128
|
+
:return: Decorator function.
|
|
129
|
+
|
|
130
|
+
Examples
|
|
131
|
+
--------
|
|
132
|
+
>>> store = ObjectStore()
|
|
133
|
+
>>> explorer = RichExplorer(store)
|
|
134
|
+
>>>
|
|
135
|
+
>>> @explorable(object_store=store, explorer=explorer)
|
|
136
|
+
... def process_data(data: dict) -> dict:
|
|
137
|
+
... return {"processed": data}
|
|
138
|
+
...
|
|
139
|
+
>>> result = process_data({"input": "value"})
|
|
140
|
+
>>> # result contains a preview and object ID like "@obj_001"
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
def decorator(func: F) -> F:
|
|
144
|
+
if inspect.iscoroutinefunction(func):
|
|
145
|
+
|
|
146
|
+
@wraps(func)
|
|
147
|
+
async def async_wrapper(*args: Any, **kwargs: Any) -> Explorable[Any]:
|
|
148
|
+
result = await func(*args, **kwargs)
|
|
149
|
+
obj_id = object_store.put(result)
|
|
150
|
+
preview = explorer.explore(obj_id)
|
|
151
|
+
return Explorable(obj_id, result, preview)
|
|
152
|
+
|
|
153
|
+
# Enhance docstring
|
|
154
|
+
async_wrapper.__doc__ = _enhance_docstring_for_explorable(func.__doc__ or "", func.__name__)
|
|
155
|
+
return async_wrapper # type: ignore[return-value]
|
|
156
|
+
else:
|
|
157
|
+
|
|
158
|
+
@wraps(func)
|
|
159
|
+
def sync_wrapper(*args: Any, **kwargs: Any) -> Explorable[Any]:
|
|
160
|
+
result = func(*args, **kwargs)
|
|
161
|
+
obj_id = object_store.put(result)
|
|
162
|
+
preview = explorer.explore(obj_id)
|
|
163
|
+
return Explorable(obj_id, result, preview)
|
|
164
|
+
|
|
165
|
+
# Enhance docstring
|
|
166
|
+
sync_wrapper.__doc__ = _enhance_docstring_for_explorable(func.__doc__ or "", func.__name__)
|
|
167
|
+
return sync_wrapper # type: ignore[return-value]
|
|
168
|
+
|
|
169
|
+
return decorator
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def referenceable(
|
|
173
|
+
*,
|
|
174
|
+
object_store: ObjectStore,
|
|
175
|
+
explorer: RichExplorer,
|
|
176
|
+
) -> Callable[[F], F]:
|
|
177
|
+
"""Decorator factory that enables parameters to accept object references.
|
|
178
|
+
|
|
179
|
+
Parameters can accept reference strings like '@obj_001' or '@obj_001.path.to.value'
|
|
180
|
+
which are automatically resolved before calling the function.
|
|
181
|
+
|
|
182
|
+
:param object_store: The object store instance to use for lookups.
|
|
183
|
+
:param explorer: The RichExplorer instance to use for path validation.
|
|
184
|
+
:return: Decorator function.
|
|
185
|
+
|
|
186
|
+
Examples
|
|
187
|
+
--------
|
|
188
|
+
>>> store = ObjectStore()
|
|
189
|
+
>>> explorer = RichExplorer(store)
|
|
190
|
+
>>>
|
|
191
|
+
>>> @referenceable(object_store=store, explorer=explorer)
|
|
192
|
+
... def process_data(data: dict, threshold: int) -> str:
|
|
193
|
+
... return f"Processed {len(data)} items with threshold {threshold}"
|
|
194
|
+
...
|
|
195
|
+
>>> # Call with actual values
|
|
196
|
+
>>> process_data({"a": 1, "b": 2}, 10)
|
|
197
|
+
>>>
|
|
198
|
+
>>> # Call with references
|
|
199
|
+
>>> process_data("@obj_001", "@obj_002.config.threshold")
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
def resolve_reference(ref_str: str) -> Any:
|
|
203
|
+
"""Resolve a reference string to its actual value."""
|
|
204
|
+
ref = ObjectRef.parse(ref_str)
|
|
205
|
+
if ref is None:
|
|
206
|
+
raise ValueError(f"Invalid reference format: {ref_str}")
|
|
207
|
+
|
|
208
|
+
obj = object_store.get(ref.obj_id)
|
|
209
|
+
if obj is None:
|
|
210
|
+
raise ValueError(f"Object @{ref.obj_id} not found or expired")
|
|
211
|
+
|
|
212
|
+
if ref.path:
|
|
213
|
+
try:
|
|
214
|
+
explorer._validate_path(ref.path)
|
|
215
|
+
return glom(obj, explorer._parse_path(ref.path))
|
|
216
|
+
except GlomError as exc:
|
|
217
|
+
raise ValueError(f"Navigation error at {ref.path}: {exc}") from exc
|
|
218
|
+
except ValueError as exc:
|
|
219
|
+
raise ValueError(f"Invalid path {ref.path}: {exc}") from exc
|
|
220
|
+
|
|
221
|
+
return obj
|
|
222
|
+
|
|
223
|
+
def decorator(func: F) -> F:
|
|
224
|
+
sig = inspect.signature(func)
|
|
225
|
+
|
|
226
|
+
# Track which parameters need type modifications
|
|
227
|
+
param_info: dict[str, dict[str, Any]] = {}
|
|
228
|
+
new_params = []
|
|
229
|
+
|
|
230
|
+
for name, param in sig.parameters.items():
|
|
231
|
+
ann = param.annotation
|
|
232
|
+
if ann is inspect.Parameter.empty:
|
|
233
|
+
ann = Any
|
|
234
|
+
|
|
235
|
+
if _type_allows_str(ann):
|
|
236
|
+
# Already accepts strings
|
|
237
|
+
param_info[name] = {"original": ann, "modified": ann, "accepts_str": True}
|
|
238
|
+
new_params.append(param)
|
|
239
|
+
else:
|
|
240
|
+
# Add str to type union
|
|
241
|
+
new_ann = _add_str_to_type(ann)
|
|
242
|
+
param_info[name] = {"original": ann, "modified": new_ann, "accepts_str": False}
|
|
243
|
+
new_params.append(param.replace(annotation=new_ann))
|
|
244
|
+
|
|
245
|
+
new_sig = sig.replace(parameters=new_params)
|
|
246
|
+
|
|
247
|
+
if inspect.iscoroutinefunction(func):
|
|
248
|
+
|
|
249
|
+
@wraps(func)
|
|
250
|
+
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
251
|
+
# Bind arguments to get parameter names
|
|
252
|
+
bound = sig.bind(*args, **kwargs)
|
|
253
|
+
bound.apply_defaults()
|
|
254
|
+
|
|
255
|
+
# Resolve references
|
|
256
|
+
resolved_args = []
|
|
257
|
+
resolved_kwargs = {}
|
|
258
|
+
|
|
259
|
+
for name, value in bound.arguments.items():
|
|
260
|
+
info = param_info.get(name, {})
|
|
261
|
+
|
|
262
|
+
# Check for invalid string values
|
|
263
|
+
if not info.get("accepts_str", True) and isinstance(value, str):
|
|
264
|
+
if not _is_reference(value):
|
|
265
|
+
raise TypeError(
|
|
266
|
+
f"Parameter '{name}' expects {info['original']}, "
|
|
267
|
+
f"got string '{value}'. Use '@obj_id' for references."
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
# Resolve references
|
|
271
|
+
if _is_reference(value):
|
|
272
|
+
value = resolve_reference(value)
|
|
273
|
+
|
|
274
|
+
# Reconstruct args/kwargs
|
|
275
|
+
param = sig.parameters[name]
|
|
276
|
+
if param.kind in (param.POSITIONAL_ONLY, param.POSITIONAL_OR_KEYWORD):
|
|
277
|
+
if name in list(sig.parameters)[: len(bound.args)]:
|
|
278
|
+
resolved_args.append(value)
|
|
279
|
+
else:
|
|
280
|
+
resolved_kwargs[name] = value
|
|
281
|
+
else:
|
|
282
|
+
resolved_kwargs[name] = value
|
|
283
|
+
|
|
284
|
+
return await func(*resolved_args, **resolved_kwargs)
|
|
285
|
+
|
|
286
|
+
# Update signature and docstring
|
|
287
|
+
async_wrapper.__signature__ = new_sig # type: ignore[attr-defined]
|
|
288
|
+
async_wrapper.__doc__ = _enhance_docstring_for_references(func.__doc__ or "", param_info, func.__name__)
|
|
289
|
+
return async_wrapper # type: ignore[return-value]
|
|
290
|
+
else:
|
|
291
|
+
|
|
292
|
+
@wraps(func)
|
|
293
|
+
def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
294
|
+
# Bind arguments to get parameter names
|
|
295
|
+
bound = sig.bind(*args, **kwargs)
|
|
296
|
+
bound.apply_defaults()
|
|
297
|
+
|
|
298
|
+
# Resolve references
|
|
299
|
+
resolved_args = []
|
|
300
|
+
resolved_kwargs = {}
|
|
301
|
+
|
|
302
|
+
for name, value in bound.arguments.items():
|
|
303
|
+
info = param_info.get(name, {})
|
|
304
|
+
|
|
305
|
+
# Check for invalid string values
|
|
306
|
+
if not info.get("accepts_str", True) and isinstance(value, str):
|
|
307
|
+
if not _is_reference(value):
|
|
308
|
+
raise TypeError(
|
|
309
|
+
f"Parameter '{name}' expects {info['original']}, "
|
|
310
|
+
f"got string '{value}'. Use '@obj_id' for references."
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
# Resolve references
|
|
314
|
+
if _is_reference(value):
|
|
315
|
+
value = resolve_reference(value)
|
|
316
|
+
|
|
317
|
+
# Reconstruct args/kwargs
|
|
318
|
+
param = sig.parameters[name]
|
|
319
|
+
if param.kind in (param.POSITIONAL_ONLY, param.POSITIONAL_OR_KEYWORD):
|
|
320
|
+
if name in list(sig.parameters)[: len(bound.args)]:
|
|
321
|
+
resolved_args.append(value)
|
|
322
|
+
else:
|
|
323
|
+
resolved_kwargs[name] = value
|
|
324
|
+
else:
|
|
325
|
+
resolved_kwargs[name] = value
|
|
326
|
+
|
|
327
|
+
return func(*resolved_args, **resolved_kwargs)
|
|
328
|
+
|
|
329
|
+
# Update signature and docstring
|
|
330
|
+
sync_wrapper.__signature__ = new_sig # type: ignore[attr-defined]
|
|
331
|
+
sync_wrapper.__doc__ = _enhance_docstring_for_references(func.__doc__ or "", param_info, func.__name__)
|
|
332
|
+
return sync_wrapper # type: ignore[return-value]
|
|
333
|
+
|
|
334
|
+
return decorator
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def explorable_and_referenceable(
|
|
338
|
+
*,
|
|
339
|
+
object_store: ObjectStore,
|
|
340
|
+
explorer: RichExplorer,
|
|
341
|
+
) -> Callable[[F], F]:
|
|
342
|
+
"""Decorator factory that combines @explorable and @referenceable functionality.
|
|
343
|
+
|
|
344
|
+
The decorated function can accept reference parameters AND stores its result
|
|
345
|
+
in the object store for later reference.
|
|
346
|
+
|
|
347
|
+
:param object_store: The object store instance to use.
|
|
348
|
+
:param explorer: The RichExplorer instance to use.
|
|
349
|
+
:return: Decorator function.
|
|
350
|
+
|
|
351
|
+
Examples
|
|
352
|
+
--------
|
|
353
|
+
>>> store = ObjectStore()
|
|
354
|
+
>>> explorer = RichExplorer(store)
|
|
355
|
+
>>>
|
|
356
|
+
>>> @explorable_and_referenceable(object_store=store, explorer=explorer)
|
|
357
|
+
... def merge_data(data1: dict, data2: dict) -> dict:
|
|
358
|
+
... return {**data1, **data2}
|
|
359
|
+
...
|
|
360
|
+
>>> # Accepts references and returns preview with object ID
|
|
361
|
+
>>> result = merge_data("@obj_001", {"new": "data"})
|
|
362
|
+
>>> # result contains a preview and can be referenced as "@obj_002"
|
|
363
|
+
"""
|
|
364
|
+
|
|
365
|
+
def decorator(func: F) -> F:
|
|
366
|
+
# First apply referenceable to handle input references
|
|
367
|
+
ref_func = referenceable(object_store=object_store, explorer=explorer)(func)
|
|
368
|
+
# Then apply explorable to handle output storage
|
|
369
|
+
exp_func = explorable(object_store=object_store, explorer=explorer)(ref_func)
|
|
370
|
+
|
|
371
|
+
# Combine docstrings (remove duplicate function name line)
|
|
372
|
+
if ref_func.__doc__ and exp_func.__doc__:
|
|
373
|
+
# Take the reference part from ref_func and explorable part from exp_func
|
|
374
|
+
ref_lines = ref_func.__doc__.split("\n")
|
|
375
|
+
exp_lines = exp_func.__doc__.split("\n")
|
|
376
|
+
|
|
377
|
+
# Find where the reference section starts
|
|
378
|
+
ref_start = next((i for i, line in enumerate(ref_lines) if "**Reference Support**" in line), len(ref_lines))
|
|
379
|
+
# Find where the explorable section starts
|
|
380
|
+
exp_start = next((i for i, line in enumerate(exp_lines) if "**Output Storage**" in line), 0)
|
|
381
|
+
|
|
382
|
+
# Combine: original + reference section + explorable section
|
|
383
|
+
# Take everything from ref_func including reference section but excluding examples
|
|
384
|
+
# Find end of reference section (before examples)
|
|
385
|
+
ref_end = len(ref_lines)
|
|
386
|
+
for i in range(ref_start, len(ref_lines)):
|
|
387
|
+
if "Examples::" in ref_lines[i]:
|
|
388
|
+
ref_end = i
|
|
389
|
+
break
|
|
390
|
+
|
|
391
|
+
combined = ref_lines[:ref_end] + exp_lines[exp_start:]
|
|
392
|
+
exp_func.__doc__ = "\n".join(combined)
|
|
393
|
+
|
|
394
|
+
return exp_func
|
|
395
|
+
|
|
396
|
+
return decorator
|