fiftyone-mcp-server 0.1.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.
- fiftyone_mcp/__init__.py +11 -0
- fiftyone_mcp/config/settings.json +15 -0
- fiftyone_mcp/server.py +127 -0
- fiftyone_mcp/tools/__init__.py +7 -0
- fiftyone_mcp/tools/datasets.py +210 -0
- fiftyone_mcp/tools/operators.py +575 -0
- fiftyone_mcp/tools/plugins.py +289 -0
- fiftyone_mcp/tools/session.py +352 -0
- fiftyone_mcp/tools/utils.py +112 -0
- fiftyone_mcp_server-0.1.0.dist-info/METADATA +174 -0
- fiftyone_mcp_server-0.1.0.dist-info/RECORD +13 -0
- fiftyone_mcp_server-0.1.0.dist-info/WHEEL +4 -0
- fiftyone_mcp_server-0.1.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Operator execution tools for FiftyOne MCP server.
|
|
3
|
+
|
|
4
|
+
| Copyright 2017-2025, Voxel51, Inc.
|
|
5
|
+
| `voxel51.com <https://voxel51.com/>`_
|
|
6
|
+
|
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
import re
|
|
13
|
+
import traceback
|
|
14
|
+
|
|
15
|
+
import fiftyone as fo
|
|
16
|
+
from eta.core.utils import PackageError
|
|
17
|
+
from fiftyone.operators import registry as op_registry
|
|
18
|
+
from fiftyone.operators.executor import (
|
|
19
|
+
ExecutionContext,
|
|
20
|
+
Executor,
|
|
21
|
+
execute_or_delegate_operator,
|
|
22
|
+
)
|
|
23
|
+
from mcp.types import Tool, TextContent
|
|
24
|
+
|
|
25
|
+
from .utils import format_response, safe_serialize
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
_context_manager = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _build_dependency_error_response(error, operator_uri):
|
|
34
|
+
"""Builds a structured error response for missing dependencies.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
error: the exception that was raised
|
|
38
|
+
operator_uri: the URI of the operator that failed
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
a dict with error details and installation instructions
|
|
42
|
+
"""
|
|
43
|
+
error_str = str(error)
|
|
44
|
+
|
|
45
|
+
match = re.search(
|
|
46
|
+
r"requires that ['\"]([^'\"]+)['\"] is installed", error_str
|
|
47
|
+
)
|
|
48
|
+
package = match.group(1) if match else "unknown"
|
|
49
|
+
|
|
50
|
+
return format_response(
|
|
51
|
+
None,
|
|
52
|
+
success=False,
|
|
53
|
+
error_type="missing_dependency",
|
|
54
|
+
error=f"Operator '{operator_uri}' requires '{package}' which is not installed",
|
|
55
|
+
missing_package=package,
|
|
56
|
+
install_command=f"pip install {package}",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_context_manager():
|
|
61
|
+
"""Gets the global context manager instance.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
a :class:`ContextManager` instance
|
|
65
|
+
"""
|
|
66
|
+
global _context_manager
|
|
67
|
+
if _context_manager is None:
|
|
68
|
+
_context_manager = ContextManager()
|
|
69
|
+
|
|
70
|
+
return _context_manager
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def set_context(
|
|
74
|
+
dataset_name,
|
|
75
|
+
view_stages=None,
|
|
76
|
+
selected_samples=None,
|
|
77
|
+
selected_labels=None,
|
|
78
|
+
current_sample=None,
|
|
79
|
+
):
|
|
80
|
+
"""Sets the execution context for operators.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
dataset_name: the name of the dataset to work with
|
|
84
|
+
view_stages (None): an optional list of DatasetView stages to
|
|
85
|
+
filter/transform the dataset
|
|
86
|
+
selected_samples (None): an optional list of selected sample IDs
|
|
87
|
+
selected_labels (None): an optional list of selected labels
|
|
88
|
+
current_sample (None): an optional ID of the current sample being
|
|
89
|
+
viewed
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
a dict with context state summary
|
|
93
|
+
"""
|
|
94
|
+
cm = get_context_manager()
|
|
95
|
+
return cm.set_context(
|
|
96
|
+
dataset_name,
|
|
97
|
+
view_stages=view_stages,
|
|
98
|
+
selected_samples=selected_samples,
|
|
99
|
+
selected_labels=selected_labels,
|
|
100
|
+
current_sample=current_sample,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def get_context():
|
|
105
|
+
"""Gets the current execution context state.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
a dict with current context state
|
|
109
|
+
"""
|
|
110
|
+
cm = get_context_manager()
|
|
111
|
+
return cm.get_context()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def clear_context():
|
|
115
|
+
"""Clears the execution context.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
a dict with success message
|
|
119
|
+
"""
|
|
120
|
+
cm = get_context_manager()
|
|
121
|
+
return cm.clear_context()
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def list_operators(builtin_only=None, operator_type=None):
|
|
125
|
+
"""Lists all available FiftyOne operators.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
builtin_only (None): if True, only builtin operators. If False, only
|
|
129
|
+
custom operators. If None, all operators
|
|
130
|
+
operator_type (None): filter by type: ``"operator"`` or ``"panel"``
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
a dict containing list of operators
|
|
134
|
+
"""
|
|
135
|
+
try:
|
|
136
|
+
builtin = "all"
|
|
137
|
+
if builtin_only is True:
|
|
138
|
+
builtin = True
|
|
139
|
+
elif builtin_only is False:
|
|
140
|
+
builtin = False
|
|
141
|
+
|
|
142
|
+
operators = op_registry.list_operators(
|
|
143
|
+
enabled=True, builtin=builtin, type=operator_type
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
operator_list = []
|
|
147
|
+
for op in operators:
|
|
148
|
+
operator_list.append(
|
|
149
|
+
{
|
|
150
|
+
"uri": op.uri,
|
|
151
|
+
"name": op.name,
|
|
152
|
+
"label": op.config.label,
|
|
153
|
+
"description": op.config.description,
|
|
154
|
+
"plugin_name": op.plugin_name,
|
|
155
|
+
"builtin": op.builtin,
|
|
156
|
+
"dynamic": op.config.dynamic,
|
|
157
|
+
}
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
return format_response(
|
|
161
|
+
{"count": len(operator_list), "operators": operator_list}
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
except Exception as e:
|
|
165
|
+
logger.error(f"Failed to list operators: {e}")
|
|
166
|
+
return format_response(None, success=False, error=str(e))
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def get_operator_schema(operator_uri):
|
|
170
|
+
"""Gets the input schema for a specific operator.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
operator_uri: the URI of the operator (e.g.,
|
|
174
|
+
``"@voxel51/operators/tag_samples"``)
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
a dict containing the operator's input schema
|
|
178
|
+
"""
|
|
179
|
+
try:
|
|
180
|
+
operator = op_registry.get_operator(operator_uri)
|
|
181
|
+
if operator is None:
|
|
182
|
+
return format_response(
|
|
183
|
+
None,
|
|
184
|
+
success=False,
|
|
185
|
+
error=f"Operator '{operator_uri}' not found",
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
cm = get_context_manager()
|
|
189
|
+
ctx = cm.get_execution_context()
|
|
190
|
+
if ctx is None:
|
|
191
|
+
return format_response(
|
|
192
|
+
None,
|
|
193
|
+
success=False,
|
|
194
|
+
error="Context not set. Use set_context first to get dynamic schema.",
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
input_property = operator.resolve_input(ctx)
|
|
198
|
+
schema = input_property.to_json() if input_property else {}
|
|
199
|
+
|
|
200
|
+
return format_response(
|
|
201
|
+
{
|
|
202
|
+
"operator_uri": operator_uri,
|
|
203
|
+
"operator_label": operator.config.label,
|
|
204
|
+
"input_schema": schema,
|
|
205
|
+
}
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
except Exception as e:
|
|
209
|
+
logger.error(
|
|
210
|
+
f"Failed to get operator schema for '{operator_uri}': {e}"
|
|
211
|
+
)
|
|
212
|
+
return format_response(None, success=False, error=str(e))
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
async def execute_operator_async(operator_uri, params=None):
|
|
216
|
+
"""Executes a FiftyOne operator asynchronously.
|
|
217
|
+
|
|
218
|
+
Uses FiftyOne's execute_or_delegate_operator which properly handles
|
|
219
|
+
generators, delegated execution, and other operator execution modes.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
operator_uri: the URI of the operator to execute
|
|
223
|
+
params (None): an optional dict of parameters for the operator
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
a dict containing execution result
|
|
227
|
+
"""
|
|
228
|
+
try:
|
|
229
|
+
operator = op_registry.get_operator(operator_uri)
|
|
230
|
+
if operator is None:
|
|
231
|
+
return format_response(
|
|
232
|
+
None,
|
|
233
|
+
success=False,
|
|
234
|
+
error=f"Operator '{operator_uri}' not found",
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
cm = get_context_manager()
|
|
238
|
+
if cm.request_params:
|
|
239
|
+
request_params = dict(cm.request_params)
|
|
240
|
+
else:
|
|
241
|
+
request_params = {}
|
|
242
|
+
|
|
243
|
+
request_params["params"] = params or {}
|
|
244
|
+
|
|
245
|
+
execution_result = await execute_or_delegate_operator(
|
|
246
|
+
operator_uri,
|
|
247
|
+
request_params,
|
|
248
|
+
exhaust=True,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
execution_result.raise_exceptions()
|
|
252
|
+
|
|
253
|
+
return format_response(
|
|
254
|
+
{
|
|
255
|
+
"operator_uri": operator_uri,
|
|
256
|
+
"success": True,
|
|
257
|
+
"result": (
|
|
258
|
+
safe_serialize(execution_result.result)
|
|
259
|
+
if execution_result
|
|
260
|
+
else None
|
|
261
|
+
),
|
|
262
|
+
}
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
except (ImportError, ModuleNotFoundError, PackageError) as e:
|
|
266
|
+
return _build_dependency_error_response(e, operator_uri)
|
|
267
|
+
|
|
268
|
+
except Exception as e:
|
|
269
|
+
logger.error(f"Failed to execute operator '{operator_uri}': {e}")
|
|
270
|
+
return format_response(
|
|
271
|
+
None,
|
|
272
|
+
success=False,
|
|
273
|
+
error=str(e),
|
|
274
|
+
traceback=traceback.format_exc(),
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def execute_operator(operator_uri, params=None):
|
|
279
|
+
"""Executes a FiftyOne operator.
|
|
280
|
+
|
|
281
|
+
Synchronous wrapper around execute_operator_async.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
operator_uri: the URI of the operator to execute
|
|
285
|
+
params (None): an optional dict of parameters for the operator
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
a dict containing execution result
|
|
289
|
+
"""
|
|
290
|
+
return asyncio.run(execute_operator_async(operator_uri, params))
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def get_operator_tools():
|
|
294
|
+
"""Gets the list of operator-related MCP tools.
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
a list of :class:`mcp.types.Tool` instances
|
|
298
|
+
"""
|
|
299
|
+
return [
|
|
300
|
+
Tool(
|
|
301
|
+
name="set_context",
|
|
302
|
+
description="Set the execution context for FiftyOne operators. REQUIRED before executing operators or getting schemas. This defines what dataset, view, and selection subsequent operators will work with. The context persists across multiple operator executions until changed.",
|
|
303
|
+
inputSchema={
|
|
304
|
+
"type": "object",
|
|
305
|
+
"properties": {
|
|
306
|
+
"dataset_name": {
|
|
307
|
+
"type": "string",
|
|
308
|
+
"description": "Name of the dataset to work with",
|
|
309
|
+
},
|
|
310
|
+
"view_stages": {
|
|
311
|
+
"type": "array",
|
|
312
|
+
"description": "Optional DatasetView stages to filter/transform the dataset",
|
|
313
|
+
"items": {"type": "object"},
|
|
314
|
+
},
|
|
315
|
+
"selected_samples": {
|
|
316
|
+
"type": "array",
|
|
317
|
+
"description": "Optional list of selected sample IDs",
|
|
318
|
+
"items": {"type": "string"},
|
|
319
|
+
},
|
|
320
|
+
"selected_labels": {
|
|
321
|
+
"type": "array",
|
|
322
|
+
"description": "Optional list of selected labels",
|
|
323
|
+
"items": {"type": "object"},
|
|
324
|
+
},
|
|
325
|
+
"current_sample": {
|
|
326
|
+
"type": "string",
|
|
327
|
+
"description": "Optional ID of the current sample being viewed",
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
"required": ["dataset_name"],
|
|
331
|
+
},
|
|
332
|
+
),
|
|
333
|
+
Tool(
|
|
334
|
+
name="get_context",
|
|
335
|
+
description="Get the current execution context state including dataset, view, and selection information.",
|
|
336
|
+
inputSchema={"type": "object", "properties": {}},
|
|
337
|
+
),
|
|
338
|
+
Tool(
|
|
339
|
+
name="list_operators",
|
|
340
|
+
description="Discover all available FiftyOne operators (80+ built-in operators from plugins). Returns operators from installed plugins including: @voxel51/operators (50+ core operators like tag_samples, clone_samples), @voxel51/brain (similarity, duplicates, visualization), @voxel51/utils (create_dataset, delete_dataset, clone_dataset), @voxel51/io (import/export), @voxel51/evaluation, @voxel51/annotation, @voxel51/zoo. Use this FIRST to discover what operators are available before executing them.",
|
|
341
|
+
inputSchema={
|
|
342
|
+
"type": "object",
|
|
343
|
+
"properties": {
|
|
344
|
+
"builtin_only": {
|
|
345
|
+
"type": "boolean",
|
|
346
|
+
"description": "If true, only return built-in operators. If false, only custom operators. If not provided, return all.",
|
|
347
|
+
},
|
|
348
|
+
"operator_type": {
|
|
349
|
+
"type": "string",
|
|
350
|
+
"enum": ["operator", "panel"],
|
|
351
|
+
"description": "Filter by operator type. Omit to return all types.",
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
),
|
|
356
|
+
Tool(
|
|
357
|
+
name="get_operator_schema",
|
|
358
|
+
description="Get the dynamic input schema for a specific operator. Schemas are context-aware and change based on the current dataset/view/selection. Use this AFTER list_operators to understand what parameters an operator accepts. Requires context to be set via set_context first.",
|
|
359
|
+
inputSchema={
|
|
360
|
+
"type": "object",
|
|
361
|
+
"properties": {
|
|
362
|
+
"operator_uri": {
|
|
363
|
+
"type": "string",
|
|
364
|
+
"description": "The URI of the operator from list_operators (e.g., '@voxel51/brain/compute_similarity', '@voxel51/utils/create_dataset')",
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
"required": ["operator_uri"],
|
|
368
|
+
},
|
|
369
|
+
),
|
|
370
|
+
Tool(
|
|
371
|
+
name="execute_operator",
|
|
372
|
+
description="Execute any FiftyOne operator with the current execution context. This provides access to 80+ operators from the plugin ecosystem. WORKFLOW: (1) Call list_operators to discover available operators, (2) Call get_operator_schema to see required parameters, (3) Call execute_operator with the operator URI and params. Common examples: '@voxel51/brain/compute_similarity' for image similarity, '@voxel51/utils/create_dataset' for dataset creation, '@voxel51/operators/tag_samples' for tagging. Requires context set via set_context first.",
|
|
373
|
+
inputSchema={
|
|
374
|
+
"type": "object",
|
|
375
|
+
"properties": {
|
|
376
|
+
"operator_uri": {
|
|
377
|
+
"type": "string",
|
|
378
|
+
"description": "The URI of the operator to execute (from list_operators)",
|
|
379
|
+
},
|
|
380
|
+
"params": {
|
|
381
|
+
"type": "object",
|
|
382
|
+
"description": "Parameters for the operator. Use get_operator_schema to see what parameters are required.",
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
"required": ["operator_uri"],
|
|
386
|
+
},
|
|
387
|
+
),
|
|
388
|
+
]
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
async def handle_tool_call(name, arguments):
|
|
392
|
+
"""Handles operator tool calls.
|
|
393
|
+
|
|
394
|
+
Args:
|
|
395
|
+
name: the name of the tool
|
|
396
|
+
arguments: a dict of arguments for the tool
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
a list of :class:`mcp.types.TextContent` instances
|
|
400
|
+
"""
|
|
401
|
+
if name == "set_context":
|
|
402
|
+
result = set_context(
|
|
403
|
+
dataset_name=arguments["dataset_name"],
|
|
404
|
+
view_stages=arguments.get("view_stages"),
|
|
405
|
+
selected_samples=arguments.get("selected_samples"),
|
|
406
|
+
selected_labels=arguments.get("selected_labels"),
|
|
407
|
+
current_sample=arguments.get("current_sample"),
|
|
408
|
+
)
|
|
409
|
+
elif name == "get_context":
|
|
410
|
+
result = get_context()
|
|
411
|
+
elif name == "list_operators":
|
|
412
|
+
result = list_operators(
|
|
413
|
+
builtin_only=arguments.get("builtin_only"),
|
|
414
|
+
operator_type=arguments.get("operator_type"),
|
|
415
|
+
)
|
|
416
|
+
elif name == "get_operator_schema":
|
|
417
|
+
result = get_operator_schema(arguments["operator_uri"])
|
|
418
|
+
elif name == "execute_operator":
|
|
419
|
+
result = await execute_operator_async(
|
|
420
|
+
operator_uri=arguments["operator_uri"],
|
|
421
|
+
params=arguments.get("params", {}),
|
|
422
|
+
)
|
|
423
|
+
else:
|
|
424
|
+
result = format_response(
|
|
425
|
+
None, success=False, error=f"Unknown tool: {name}"
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
class ContextManager(object):
|
|
432
|
+
"""Manages the execution context state for FiftyOne operators.
|
|
433
|
+
|
|
434
|
+
This class maintains the current dataset, view, and selection state that
|
|
435
|
+
operators use for execution.
|
|
436
|
+
"""
|
|
437
|
+
|
|
438
|
+
def __init__(self):
|
|
439
|
+
self.request_params = {}
|
|
440
|
+
|
|
441
|
+
def set_context(
|
|
442
|
+
self,
|
|
443
|
+
dataset_name,
|
|
444
|
+
view_stages=None,
|
|
445
|
+
selected_samples=None,
|
|
446
|
+
selected_labels=None,
|
|
447
|
+
current_sample=None,
|
|
448
|
+
):
|
|
449
|
+
"""Sets the execution context.
|
|
450
|
+
|
|
451
|
+
Args:
|
|
452
|
+
dataset_name: the name of the dataset to work with
|
|
453
|
+
view_stages (None): an optional list of DatasetView stages
|
|
454
|
+
selected_samples (None): an optional list of selected sample IDs
|
|
455
|
+
selected_labels (None): an optional list of selected labels
|
|
456
|
+
current_sample (None): an optional ID of the current sample
|
|
457
|
+
|
|
458
|
+
Returns:
|
|
459
|
+
a dict with context state summary
|
|
460
|
+
"""
|
|
461
|
+
try:
|
|
462
|
+
if not fo.dataset_exists(dataset_name):
|
|
463
|
+
return format_response(
|
|
464
|
+
None,
|
|
465
|
+
success=False,
|
|
466
|
+
error=f"Dataset '{dataset_name}' does not exist",
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
self.request_params = {
|
|
470
|
+
"dataset_name": dataset_name,
|
|
471
|
+
"view": view_stages or [],
|
|
472
|
+
"selected": selected_samples or [],
|
|
473
|
+
"selected_labels": selected_labels or [],
|
|
474
|
+
"params": {},
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if current_sample:
|
|
478
|
+
self.request_params["current_sample"] = current_sample
|
|
479
|
+
|
|
480
|
+
dataset = fo.load_dataset(dataset_name)
|
|
481
|
+
|
|
482
|
+
return format_response(
|
|
483
|
+
{
|
|
484
|
+
"dataset_name": dataset_name,
|
|
485
|
+
"dataset_info": {
|
|
486
|
+
"num_samples": len(dataset),
|
|
487
|
+
"media_type": dataset.media_type,
|
|
488
|
+
},
|
|
489
|
+
"view_stages_count": (
|
|
490
|
+
len(view_stages) if view_stages else 0
|
|
491
|
+
),
|
|
492
|
+
"selected_samples_count": (
|
|
493
|
+
len(selected_samples) if selected_samples else 0
|
|
494
|
+
),
|
|
495
|
+
"selected_labels_count": (
|
|
496
|
+
len(selected_labels) if selected_labels else 0
|
|
497
|
+
),
|
|
498
|
+
"has_current_sample": current_sample is not None,
|
|
499
|
+
}
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
except Exception as e:
|
|
503
|
+
logger.error(f"Failed to set context: {e}")
|
|
504
|
+
return format_response(None, success=False, error=str(e))
|
|
505
|
+
|
|
506
|
+
def get_context(self):
|
|
507
|
+
"""Gets the current execution context state.
|
|
508
|
+
|
|
509
|
+
Returns:
|
|
510
|
+
a dict with current context state
|
|
511
|
+
"""
|
|
512
|
+
try:
|
|
513
|
+
if not self.request_params:
|
|
514
|
+
return format_response(
|
|
515
|
+
{
|
|
516
|
+
"context_set": False,
|
|
517
|
+
"message": "No context set. Use set_context first.",
|
|
518
|
+
}
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
dataset_name = self.request_params.get("dataset_name")
|
|
522
|
+
dataset = fo.load_dataset(dataset_name) if dataset_name else None
|
|
523
|
+
|
|
524
|
+
return format_response(
|
|
525
|
+
{
|
|
526
|
+
"context_set": True,
|
|
527
|
+
"dataset_name": dataset_name,
|
|
528
|
+
"dataset_info": (
|
|
529
|
+
{
|
|
530
|
+
"num_samples": len(dataset),
|
|
531
|
+
"media_type": dataset.media_type,
|
|
532
|
+
}
|
|
533
|
+
if dataset
|
|
534
|
+
else None
|
|
535
|
+
),
|
|
536
|
+
"view_stages_count": len(
|
|
537
|
+
self.request_params.get("view", [])
|
|
538
|
+
),
|
|
539
|
+
"selected_samples_count": len(
|
|
540
|
+
self.request_params.get("selected", [])
|
|
541
|
+
),
|
|
542
|
+
"selected_labels_count": len(
|
|
543
|
+
self.request_params.get("selected_labels", [])
|
|
544
|
+
),
|
|
545
|
+
"has_current_sample": "current_sample"
|
|
546
|
+
in self.request_params,
|
|
547
|
+
}
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
except Exception as e:
|
|
551
|
+
logger.error(f"Failed to get context: {e}")
|
|
552
|
+
return format_response(None, success=False, error=str(e))
|
|
553
|
+
|
|
554
|
+
def get_execution_context(self):
|
|
555
|
+
"""Builds an ExecutionContext from current state.
|
|
556
|
+
|
|
557
|
+
Returns:
|
|
558
|
+
an :class:`ExecutionContext` instance, or None if context not set
|
|
559
|
+
"""
|
|
560
|
+
if not self.request_params:
|
|
561
|
+
return None
|
|
562
|
+
|
|
563
|
+
return ExecutionContext(
|
|
564
|
+
request_params=self.request_params,
|
|
565
|
+
executor=Executor(),
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
def clear_context(self):
|
|
569
|
+
"""Clears the execution context.
|
|
570
|
+
|
|
571
|
+
Returns:
|
|
572
|
+
a dict with success message
|
|
573
|
+
"""
|
|
574
|
+
self.request_params = {}
|
|
575
|
+
return format_response({"message": "Context cleared"})
|