datarobot-genai 0.2.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 (101) hide show
  1. datarobot_genai/__init__.py +19 -0
  2. datarobot_genai/core/__init__.py +0 -0
  3. datarobot_genai/core/agents/__init__.py +43 -0
  4. datarobot_genai/core/agents/base.py +195 -0
  5. datarobot_genai/core/chat/__init__.py +19 -0
  6. datarobot_genai/core/chat/auth.py +146 -0
  7. datarobot_genai/core/chat/client.py +178 -0
  8. datarobot_genai/core/chat/responses.py +297 -0
  9. datarobot_genai/core/cli/__init__.py +18 -0
  10. datarobot_genai/core/cli/agent_environment.py +47 -0
  11. datarobot_genai/core/cli/agent_kernel.py +211 -0
  12. datarobot_genai/core/custom_model.py +141 -0
  13. datarobot_genai/core/mcp/__init__.py +0 -0
  14. datarobot_genai/core/mcp/common.py +218 -0
  15. datarobot_genai/core/telemetry_agent.py +126 -0
  16. datarobot_genai/core/utils/__init__.py +3 -0
  17. datarobot_genai/core/utils/auth.py +234 -0
  18. datarobot_genai/core/utils/urls.py +64 -0
  19. datarobot_genai/crewai/__init__.py +24 -0
  20. datarobot_genai/crewai/agent.py +42 -0
  21. datarobot_genai/crewai/base.py +159 -0
  22. datarobot_genai/crewai/events.py +117 -0
  23. datarobot_genai/crewai/mcp.py +59 -0
  24. datarobot_genai/drmcp/__init__.py +78 -0
  25. datarobot_genai/drmcp/core/__init__.py +13 -0
  26. datarobot_genai/drmcp/core/auth.py +165 -0
  27. datarobot_genai/drmcp/core/clients.py +180 -0
  28. datarobot_genai/drmcp/core/config.py +250 -0
  29. datarobot_genai/drmcp/core/config_utils.py +174 -0
  30. datarobot_genai/drmcp/core/constants.py +18 -0
  31. datarobot_genai/drmcp/core/credentials.py +190 -0
  32. datarobot_genai/drmcp/core/dr_mcp_server.py +316 -0
  33. datarobot_genai/drmcp/core/dr_mcp_server_logo.py +136 -0
  34. datarobot_genai/drmcp/core/dynamic_prompts/__init__.py +13 -0
  35. datarobot_genai/drmcp/core/dynamic_prompts/controllers.py +130 -0
  36. datarobot_genai/drmcp/core/dynamic_prompts/dr_lib.py +128 -0
  37. datarobot_genai/drmcp/core/dynamic_prompts/register.py +206 -0
  38. datarobot_genai/drmcp/core/dynamic_prompts/utils.py +33 -0
  39. datarobot_genai/drmcp/core/dynamic_tools/__init__.py +14 -0
  40. datarobot_genai/drmcp/core/dynamic_tools/deployment/__init__.py +0 -0
  41. datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/__init__.py +14 -0
  42. datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/base.py +72 -0
  43. datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/default.py +82 -0
  44. datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/drum.py +238 -0
  45. datarobot_genai/drmcp/core/dynamic_tools/deployment/config.py +228 -0
  46. datarobot_genai/drmcp/core/dynamic_tools/deployment/controllers.py +63 -0
  47. datarobot_genai/drmcp/core/dynamic_tools/deployment/metadata.py +162 -0
  48. datarobot_genai/drmcp/core/dynamic_tools/deployment/register.py +87 -0
  49. datarobot_genai/drmcp/core/dynamic_tools/deployment/schemas/drum_agentic_fallback_schema.json +36 -0
  50. datarobot_genai/drmcp/core/dynamic_tools/deployment/schemas/drum_prediction_fallback_schema.json +10 -0
  51. datarobot_genai/drmcp/core/dynamic_tools/register.py +254 -0
  52. datarobot_genai/drmcp/core/dynamic_tools/schema.py +532 -0
  53. datarobot_genai/drmcp/core/exceptions.py +25 -0
  54. datarobot_genai/drmcp/core/logging.py +98 -0
  55. datarobot_genai/drmcp/core/mcp_instance.py +542 -0
  56. datarobot_genai/drmcp/core/mcp_server_tools.py +129 -0
  57. datarobot_genai/drmcp/core/memory_management/__init__.py +13 -0
  58. datarobot_genai/drmcp/core/memory_management/manager.py +820 -0
  59. datarobot_genai/drmcp/core/memory_management/memory_tools.py +201 -0
  60. datarobot_genai/drmcp/core/routes.py +436 -0
  61. datarobot_genai/drmcp/core/routes_utils.py +30 -0
  62. datarobot_genai/drmcp/core/server_life_cycle.py +107 -0
  63. datarobot_genai/drmcp/core/telemetry.py +424 -0
  64. datarobot_genai/drmcp/core/tool_filter.py +108 -0
  65. datarobot_genai/drmcp/core/utils.py +131 -0
  66. datarobot_genai/drmcp/server.py +19 -0
  67. datarobot_genai/drmcp/test_utils/__init__.py +13 -0
  68. datarobot_genai/drmcp/test_utils/integration_mcp_server.py +102 -0
  69. datarobot_genai/drmcp/test_utils/mcp_utils_ete.py +96 -0
  70. datarobot_genai/drmcp/test_utils/mcp_utils_integration.py +94 -0
  71. datarobot_genai/drmcp/test_utils/openai_llm_mcp_client.py +234 -0
  72. datarobot_genai/drmcp/test_utils/tool_base_ete.py +151 -0
  73. datarobot_genai/drmcp/test_utils/utils.py +91 -0
  74. datarobot_genai/drmcp/tools/__init__.py +14 -0
  75. datarobot_genai/drmcp/tools/predictive/__init__.py +27 -0
  76. datarobot_genai/drmcp/tools/predictive/data.py +97 -0
  77. datarobot_genai/drmcp/tools/predictive/deployment.py +91 -0
  78. datarobot_genai/drmcp/tools/predictive/deployment_info.py +392 -0
  79. datarobot_genai/drmcp/tools/predictive/model.py +148 -0
  80. datarobot_genai/drmcp/tools/predictive/predict.py +254 -0
  81. datarobot_genai/drmcp/tools/predictive/predict_realtime.py +307 -0
  82. datarobot_genai/drmcp/tools/predictive/project.py +72 -0
  83. datarobot_genai/drmcp/tools/predictive/training.py +651 -0
  84. datarobot_genai/langgraph/__init__.py +0 -0
  85. datarobot_genai/langgraph/agent.py +341 -0
  86. datarobot_genai/langgraph/mcp.py +73 -0
  87. datarobot_genai/llama_index/__init__.py +16 -0
  88. datarobot_genai/llama_index/agent.py +50 -0
  89. datarobot_genai/llama_index/base.py +299 -0
  90. datarobot_genai/llama_index/mcp.py +79 -0
  91. datarobot_genai/nat/__init__.py +0 -0
  92. datarobot_genai/nat/agent.py +258 -0
  93. datarobot_genai/nat/datarobot_llm_clients.py +249 -0
  94. datarobot_genai/nat/datarobot_llm_providers.py +130 -0
  95. datarobot_genai/py.typed +0 -0
  96. datarobot_genai-0.2.0.dist-info/METADATA +139 -0
  97. datarobot_genai-0.2.0.dist-info/RECORD +101 -0
  98. datarobot_genai-0.2.0.dist-info/WHEEL +4 -0
  99. datarobot_genai-0.2.0.dist-info/entry_points.txt +3 -0
  100. datarobot_genai-0.2.0.dist-info/licenses/AUTHORS +2 -0
  101. datarobot_genai-0.2.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,151 @@
1
+ # Copyright 2025 DataRobot, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import json
16
+ from typing import Any
17
+
18
+ from pydantic import BaseModel
19
+
20
+ from .openai_llm_mcp_client import LLMResponse
21
+
22
+
23
+ class ToolCallTestExpectations(BaseModel):
24
+ """Class to store tool call information."""
25
+
26
+ name: str
27
+ parameters: dict[str, Any]
28
+ result: str | dict[str, Any]
29
+
30
+
31
+ class ETETestExpectations(BaseModel):
32
+ """Class to store test expectations for ETE tests."""
33
+
34
+ potential_no_tool_calls: bool = False
35
+ tool_calls_expected: list[ToolCallTestExpectations]
36
+ llm_response_content_contains_expectations: list[str]
37
+
38
+
39
+ SHOULD_NOT_BE_EMPTY = "SHOULD_NOT_BE_EMPTY"
40
+
41
+
42
+ def _check_dict_has_keys(
43
+ expected: dict[str, Any],
44
+ actual: dict[str, Any] | list[dict[str, Any]],
45
+ path: str = "",
46
+ ) -> bool:
47
+ """
48
+ Recursively check if all keys in expected dict exist in actual dict or in each item of
49
+ actual list.
50
+ Returns True if all expected keys exist, False otherwise.
51
+ """
52
+ # If actual is a list, check each item against the expected structure
53
+ if isinstance(actual, list):
54
+ if not actual: # Empty list
55
+ return False
56
+ # Check first item against expected structure
57
+ return _check_dict_has_keys(expected, actual[0], path)
58
+
59
+ # Regular dict check
60
+ for key, value in expected.items():
61
+ current_path = f"{path}.{key}" if path else key
62
+ if key not in actual:
63
+ return False
64
+ if isinstance(value, dict):
65
+ if not isinstance(actual[key], dict):
66
+ return False
67
+ if not _check_dict_has_keys(value, actual[key], current_path):
68
+ return False
69
+ return True
70
+
71
+
72
+ class ToolBaseE2E:
73
+ """Base class for end-to-end tests."""
74
+
75
+ async def _run_test_with_expectations(
76
+ self,
77
+ prompt: str,
78
+ test_expectations: ETETestExpectations,
79
+ openai_llm_client: Any,
80
+ mcp_session: Any,
81
+ test_name: str,
82
+ ) -> None:
83
+ """
84
+ Run a test with given expectations and validate the results.
85
+
86
+ Args:
87
+ prompt: The prompt to send to the LLM
88
+ test_expectations: ETETestExpectations object containing test expectations with keys:
89
+ - tool_calls_expected: List of expected tool calls with their parameters and results
90
+ - llm_response_content_contains_expectations: Expected content in the LLM response
91
+ openai_llm_client: The OpenAI LLM client
92
+ mcp_session: The test session
93
+ test_name: The name of the test (e.g. test_get_best_model_success)
94
+ """
95
+ # Get the test file name from the class name
96
+ file_name = self.__class__.__name__.lower().replace("e2e", "").replace("test", "")
97
+ output_file_name = f"{file_name}_{test_name}"
98
+
99
+ # Act
100
+ response: LLMResponse = await openai_llm_client.process_prompt_with_mcp_support(
101
+ prompt, mcp_session, output_file_name
102
+ )
103
+
104
+ # sometimes llm are too smart and doesn't call tools especially for the case when file
105
+ # doesn't exist
106
+ if test_expectations.potential_no_tool_calls and len(response.tool_calls) == 0:
107
+ pass
108
+ else:
109
+ # Verify LLM decided to use tools
110
+ assert len(response.tool_calls) == len(test_expectations.tool_calls_expected), (
111
+ "LLM should have decided to call tools"
112
+ )
113
+
114
+ for i, tool_call in enumerate(response.tool_calls):
115
+ assert tool_call.tool_name == test_expectations.tool_calls_expected[i].name, (
116
+ f"Should have called {test_expectations.tool_calls_expected[i].name} tool, but "
117
+ f"got: {tool_call.tool_name}"
118
+ )
119
+ assert tool_call.parameters == test_expectations.tool_calls_expected[i].parameters, (
120
+ f"Should have called {tool_call.tool_name} tool with the correct parameters, but "
121
+ f"got: {tool_call.parameters}"
122
+ )
123
+ if test_expectations.tool_calls_expected[i].result != SHOULD_NOT_BE_EMPTY:
124
+ expected_result = test_expectations.tool_calls_expected[i].result
125
+ if isinstance(expected_result, str):
126
+ assert expected_result in response.tool_results[i], (
127
+ f"Should have called {tool_call.tool_name} tool with the correct result, "
128
+ f"but got: {response.tool_results[i]}"
129
+ )
130
+ else:
131
+ actual_result = json.loads(response.tool_results[i])
132
+ assert _check_dict_has_keys(expected_result, actual_result), (
133
+ f"Should have called {tool_call.tool_name} tool with the correct result "
134
+ f"structure, but got: {response.tool_results[i]}"
135
+ )
136
+ else:
137
+ assert len(response.tool_results[i]) > 0, (
138
+ f"Should have called {tool_call.tool_name} tool with non-empty result, but "
139
+ f"got: {response.tool_results[i]}"
140
+ )
141
+
142
+ # Verify LLM provided comprehensive response
143
+ assert len(response.content) > 100, "LLM should provide detailed response"
144
+ assert any(
145
+ expected_response.lower() in response.content
146
+ for expected_response in test_expectations.llm_response_content_contains_expectations
147
+ ), (
148
+ f"Response should mention "
149
+ f"{test_expectations.llm_response_content_contains_expectations}, "
150
+ f"but got: {response.content}"
151
+ )
@@ -0,0 +1,91 @@
1
+ # Copyright 2025 DataRobot, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import datetime
16
+ import json
17
+ import os
18
+ from typing import TYPE_CHECKING
19
+ from typing import Any
20
+
21
+ if TYPE_CHECKING:
22
+ from .openai_llm_mcp_client import LLMResponse
23
+
24
+ from dotenv import load_dotenv
25
+
26
+
27
+ def load_env() -> None:
28
+ load_dotenv(dotenv_path=".env", verbose=True, override=True)
29
+
30
+
31
+ def format_tool_call(tool_call: dict[str, Any]) -> str:
32
+ """Format a single tool call in a readable way."""
33
+ return (
34
+ f"Tool: {tool_call['tool_name']}\n"
35
+ f"Parameters: {json.dumps(tool_call['parameters'], indent=2)}\n"
36
+ f"Reasoning: {tool_call['reasoning']}"
37
+ )
38
+
39
+
40
+ def format_response(response: "LLMResponse") -> str:
41
+ """Format the LLM response in a readable way."""
42
+ formatted_parts = []
43
+
44
+ # Format the main content
45
+ formatted_parts.append("=== LLM Response ===\n")
46
+ formatted_parts.append(response.content)
47
+
48
+ # Format tool calls if any
49
+ if response.tool_calls:
50
+ formatted_parts.append("\n=== Tools Used ===")
51
+ for i, tool_call in enumerate(response.tool_calls, 1):
52
+ formatted_parts.append(f"\nTool Call #{i}:")
53
+ formatted_parts.append(
54
+ format_tool_call(
55
+ {
56
+ "tool_name": tool_call.tool_name,
57
+ "parameters": tool_call.parameters,
58
+ "reasoning": tool_call.reasoning,
59
+ }
60
+ )
61
+ )
62
+
63
+ # Format tool results if any
64
+ if response.tool_results:
65
+ formatted_parts.append("\n=== Tool Results ===")
66
+ for i, result in enumerate(response.tool_results, 1):
67
+ formatted_parts.append(f"\nResult #{i}:")
68
+ formatted_parts.append(result)
69
+
70
+ return "\n".join(formatted_parts)
71
+
72
+
73
+ def save_response_to_file(response: "LLMResponse", name: str | None = None) -> None:
74
+ """Save the response to a file in a readable format.
75
+
76
+ Args:
77
+ response: The LLM response to save
78
+ name: Optional name to use in the filename. If not provided,
79
+ will use a timestamp only.
80
+ """
81
+ # Create responses directory with timestamp
82
+ timestamp = datetime.datetime.now().strftime("%Y%m%d")
83
+ dir_path = "test_results/drmcp/.ete_responses/" + timestamp
84
+ os.makedirs(dir_path, exist_ok=True)
85
+
86
+ # Save both raw JSON and formatted text
87
+ base_name = f"{name}" if name else "response"
88
+
89
+ # Save formatted text
90
+ with open(f"{dir_path}/{base_name}.txt", "w") as f:
91
+ f.write(format_response(response))
@@ -0,0 +1,14 @@
1
+ # Copyright 2025 DataRobot, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
@@ -0,0 +1,27 @@
1
+ # Copyright 2025 DataRobot, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """
16
+ DataRobot MCP tools for interacting with DataRobot platform.
17
+
18
+ This package contains pre-built tools for DataRobot operations:
19
+ - data: Upload and manage datasets
20
+ - project: Create and manage projects
21
+ - model: List and interact with models
22
+ - training: Train models with autopilot
23
+ - deployment: Deploy and manage models
24
+ - predict: Make batch predictions
25
+ - predict_realtime: Make real-time predictions
26
+ - deployment_info: Get deployment information
27
+ """
@@ -0,0 +1,97 @@
1
+ # Copyright 2025 DataRobot, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import logging
16
+ import os
17
+
18
+ from datarobot_genai.drmcp.core.clients import get_sdk_client
19
+ from datarobot_genai.drmcp.core.mcp_instance import dr_mcp_tool
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ @dr_mcp_tool(tags={"data", "management", "upload"})
25
+ async def upload_dataset_to_ai_catalog(file_path: str) -> str:
26
+ """
27
+ Upload a dataset to the DataRobot AI Catalog.
28
+
29
+ Args:
30
+ file_path: Path to the file to upload.
31
+
32
+ Returns
33
+ -------
34
+ A string summary of the upload result.
35
+ """
36
+ client = get_sdk_client()
37
+ if not os.path.exists(file_path):
38
+ logger.error(f"File not found: {file_path}")
39
+ return f"File not found: {file_path}"
40
+ catalog_item = client.Dataset.create_from_file(file_path)
41
+ logger.info(f"Successfully uploaded dataset: {catalog_item.id}")
42
+ return f"AI Catalog ID: {catalog_item.id}"
43
+
44
+
45
+ @dr_mcp_tool(tags={"data", "management", "list"})
46
+ async def list_ai_catalog_items() -> str:
47
+ """
48
+ List all AI Catalog items (datasets) for the authenticated user.
49
+
50
+ Returns
51
+ -------
52
+ A string summary of the AI Catalog items with their IDs and names.
53
+ """
54
+ client = get_sdk_client()
55
+ datasets = client.Dataset.list()
56
+ if not datasets:
57
+ logger.info("No AI Catalog items found")
58
+ return "No AI Catalog items found."
59
+ result = "\n".join(f"{ds.id}: {ds.name}" for ds in datasets)
60
+ logger.info(f"Found {len(datasets)} AI Catalog items")
61
+ return result
62
+
63
+
64
+ # from fastmcp import Context
65
+
66
+ # from datarobot_genai.drmcp.core.memory_management import MemoryManager, get_memory_manager
67
+
68
+
69
+ # @dr_mcp_tool()
70
+ # async def list_ai_catalog_items(
71
+ # ctx: Context, agent_id: str = None, storage_id: str = None
72
+ # ) -> str:
73
+ # """
74
+ # List all AI Catalog items (datasets) for the authenticated user.
75
+
76
+ # Returns:
77
+ # a resource id that can be used to retrieve the list of AI Catalog items using the
78
+ # get_resource tool
79
+ # """
80
+ # client = get_sdk_client()
81
+ # datasets = client.Dataset.list()
82
+ # if not datasets:
83
+ # logger.info("No AI Catalog items found")
84
+ # return "No AI Catalog items found."
85
+ # result = "\n".join(f"{ds.id}: {ds.name}" for ds in datasets)
86
+
87
+ # if MemoryManager.is_initialized():
88
+ # resource_id = await get_memory_manager().store_resource(
89
+ # data=result,
90
+ # memory_storage_id=storage_id,
91
+ # agent_identifier=agent_id,
92
+ # )
93
+ # else:
94
+ # raise ValueError("MemoryManager is not initialized")
95
+
96
+ # logger.info(f"Found {len(datasets)} AI Catalog items")
97
+ # return resource_id
@@ -0,0 +1,91 @@
1
+ # Copyright 2025 DataRobot, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import json
16
+ import logging
17
+
18
+ from datarobot_genai.drmcp.core.clients import get_sdk_client
19
+ from datarobot_genai.drmcp.core.mcp_instance import dr_mcp_tool
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ @dr_mcp_tool(tags={"deployment", "management", "list"})
25
+ async def list_deployments() -> str:
26
+ """
27
+ List all DataRobot deployments for the authenticated user.
28
+
29
+ Returns
30
+ -------
31
+ A string summary of the user's DataRobot deployments.
32
+ """
33
+ client = get_sdk_client()
34
+ deployments = client.Deployment.list()
35
+ if not deployments:
36
+ logger.info("No deployments found")
37
+ return "No deployments found."
38
+ result = "\n".join(f"{d.id}: {d.label}" for d in deployments)
39
+ logger.info(f"Found {len(deployments)} deployments")
40
+ return result
41
+
42
+
43
+ @dr_mcp_tool(tags={"deployment", "model", "info"})
44
+ async def get_model_info_from_deployment(deployment_id: str) -> str:
45
+ """
46
+ Get model info associated with a given deployment ID.
47
+
48
+ Args:
49
+ deployment_id: The ID of the DataRobot deployment.
50
+
51
+ Returns
52
+ -------
53
+ The model info associated with the deployment as a JSON string.
54
+ """
55
+ client = get_sdk_client()
56
+ deployment = client.Deployment.get(deployment_id)
57
+ logger.info(f"Retrieved model info for deployment {deployment_id}")
58
+ return json.dumps(deployment.model, indent=2)
59
+
60
+
61
+ @dr_mcp_tool(tags={"deployment", "model", "create"})
62
+ async def deploy_model(model_id: str, label: str, description: str = "") -> str:
63
+ """
64
+ Deploy a model by creating a new DataRobot deployment.
65
+
66
+ Args:
67
+ model_id: The ID of the DataRobot model to deploy.
68
+ label: The label/name for the deployment.
69
+ description: Optional description for the deployment.
70
+
71
+ Returns
72
+ -------
73
+ JSON string with deployment ID and label, or error message.
74
+ """
75
+ client = get_sdk_client()
76
+ try:
77
+ prediction_servers = client.PredictionServer.list()
78
+ if not prediction_servers:
79
+ logger.error("No prediction servers available")
80
+ return json.dumps({"error": "No prediction servers available"})
81
+ deployment = client.Deployment.create_from_learning_model(
82
+ model_id=model_id,
83
+ label=label,
84
+ description=description,
85
+ default_prediction_server_id=prediction_servers[0].id,
86
+ )
87
+ logger.info(f"Created deployment {deployment.id} with label {label}")
88
+ return json.dumps({"deployment_id": deployment.id, "label": label})
89
+ except Exception as e:
90
+ logger.error(f"Error deploying model {model_id}: {type(e).__name__}: {e}")
91
+ return json.dumps({"error": f"Error deploying model {model_id}: {type(e).__name__}: {e}"})