Skip to content

Commit b70ec18

Browse files
feat: add load_model tool for resolving trained models (#45)
*Note: I realize #32 was just assigned to someone else, but I had already finished these changes locally before it was assigned because no contributor asked to take upon this issue at that time. I figured I would put the PR up just in case having it done is helpful to the project.* #### Reference Issues/PRs Closes #32 #### What does this implement/fix? Explain your changes. This implements a new `load_model` tool to allow LLMs to restore previously trained estimators from a local directory or URI without having to re-fit the data from scratch. **Implementation Details:** - Created `load_model_tool` in `src/sktime_mcp/tools/instantiate.py`. - Gracefully handles the optional `mlflow` dependency. If `mlflow` is missing, it returns a clear error message instructing the user/LLM to run `pip install sktime[mlflow]` rather than crashing the server. - Registers the loaded estimator instance dynamically back into the `HandleManager` marking it as fitted, allowing the LLM to immediately follow up with a `predict` call using the newly restored handle. - Added the tool routing logic to `call_tool` in `server.py`. #### Does your contribution introduce a new dependency? If yes, which one? No new hard dependencies. `mlflow` is utilized as an optional extension (`sktime[mlflow]`), strictly matching `sktime`'s existing dependency strategy. #### Demo Video [load_model.webm](https://github.com/user-attachments/assets/d36c8be5-8736-4ec5-be95-2bd534db799a) Co-authored-by: Shashank Shekhar Singh <123410790+Shashankss1205@users.noreply.github.com>
1 parent 57580b8 commit b70ec18

2 files changed

Lines changed: 71 additions & 0 deletions

File tree

src/sktime_mcp/server.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
instantiate_pipeline_tool,
2929
release_handle_tool,
3030
list_handles_tool,
31+
load_model_tool,
3132
)
3233
from sktime_mcp.tools.fit_predict import (
3334
fit_predict_tool,
@@ -473,6 +474,20 @@ async def list_tools() -> List[Tool]:
473474
"required": ["job_id"],
474475
},
475476
),
477+
Tool(
478+
name="load_model",
479+
description="Load a saved sktime model from a local path and register it for use",
480+
inputSchema={
481+
"type": "object",
482+
"properties": {
483+
"path": {
484+
"type": "string",
485+
"description": "Path to the saved model directory",
486+
},
487+
},
488+
"required": ["path"],
489+
},
490+
),
476491
Tool(
477492
name="cleanup_old_jobs",
478493
description="Remove jobs older than specified hours",
@@ -612,6 +627,8 @@ async def call_tool(name: str, arguments: Dict[str, Any]) -> List[TextContent]:
612627
result = delete_job_tool(arguments["job_id"])
613628
elif name == "cleanup_old_jobs":
614629
result = cleanup_old_jobs_tool(arguments.get("max_age_hours", 24))
630+
elif name == "load_model":
631+
result = load_model_tool(arguments["path"])
615632
elif name == "save_model":
616633
result = save_model_tool(
617634
arguments["estimator_handle"],

src/sktime_mcp/tools/instantiate.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,3 +249,57 @@ def list_handles_tool() -> Dict[str, Any]:
249249
"handles": handles,
250250
"count": len(handles),
251251
}
252+
253+
254+
def load_model_tool(path: str) -> Dict[str, Any]:
255+
"""
256+
Load a saved model from disk and register its handle.
257+
258+
Args:
259+
path: Path to the saved model directory.
260+
261+
Returns:
262+
Dictionary with success status and the new handle.
263+
"""
264+
import os
265+
266+
if not os.path.exists(path):
267+
return {
268+
"success": False,
269+
"error": f"Path does not exist: {path}",
270+
}
271+
272+
try:
273+
from sktime.utils.mlflow_sktime import load_model
274+
except ImportError:
275+
return {
276+
"success": False,
277+
"error": "The 'mlflow' package is required to load saved models. Please install it with: pip install sktime[mlflow]",
278+
}
279+
280+
try:
281+
instance = load_model(path)
282+
estimator_name = type(instance).__name__
283+
284+
handle_manager = get_handle_manager()
285+
handle_id = handle_manager.create_handle(
286+
estimator_name=estimator_name,
287+
instance=instance,
288+
params={},
289+
metadata={"source": "loaded", "path": path},
290+
)
291+
292+
handle_manager.mark_fitted(handle_id)
293+
294+
return {
295+
"success": True,
296+
"handle": handle_id,
297+
"estimator": estimator_name,
298+
"path": path,
299+
"message": f"Successfully loaded {estimator_name}",
300+
}
301+
except Exception as e:
302+
return {
303+
"success": False,
304+
"error": f"Failed to load model: {str(e)}",
305+
}

0 commit comments

Comments
 (0)