datarobot-genai 0.2.31__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.
- datarobot_genai/__init__.py +19 -0
- datarobot_genai/core/__init__.py +0 -0
- datarobot_genai/core/agents/__init__.py +43 -0
- datarobot_genai/core/agents/base.py +195 -0
- datarobot_genai/core/chat/__init__.py +19 -0
- datarobot_genai/core/chat/auth.py +146 -0
- datarobot_genai/core/chat/client.py +178 -0
- datarobot_genai/core/chat/responses.py +297 -0
- datarobot_genai/core/cli/__init__.py +18 -0
- datarobot_genai/core/cli/agent_environment.py +47 -0
- datarobot_genai/core/cli/agent_kernel.py +211 -0
- datarobot_genai/core/custom_model.py +141 -0
- datarobot_genai/core/mcp/__init__.py +0 -0
- datarobot_genai/core/mcp/common.py +218 -0
- datarobot_genai/core/telemetry_agent.py +126 -0
- datarobot_genai/core/utils/__init__.py +3 -0
- datarobot_genai/core/utils/auth.py +234 -0
- datarobot_genai/core/utils/urls.py +64 -0
- datarobot_genai/crewai/__init__.py +24 -0
- datarobot_genai/crewai/agent.py +42 -0
- datarobot_genai/crewai/base.py +159 -0
- datarobot_genai/crewai/events.py +117 -0
- datarobot_genai/crewai/mcp.py +59 -0
- datarobot_genai/drmcp/__init__.py +78 -0
- datarobot_genai/drmcp/core/__init__.py +13 -0
- datarobot_genai/drmcp/core/auth.py +165 -0
- datarobot_genai/drmcp/core/clients.py +180 -0
- datarobot_genai/drmcp/core/config.py +364 -0
- datarobot_genai/drmcp/core/config_utils.py +174 -0
- datarobot_genai/drmcp/core/constants.py +18 -0
- datarobot_genai/drmcp/core/credentials.py +190 -0
- datarobot_genai/drmcp/core/dr_mcp_server.py +350 -0
- datarobot_genai/drmcp/core/dr_mcp_server_logo.py +136 -0
- datarobot_genai/drmcp/core/dynamic_prompts/__init__.py +13 -0
- datarobot_genai/drmcp/core/dynamic_prompts/controllers.py +130 -0
- datarobot_genai/drmcp/core/dynamic_prompts/dr_lib.py +70 -0
- datarobot_genai/drmcp/core/dynamic_prompts/register.py +205 -0
- datarobot_genai/drmcp/core/dynamic_prompts/utils.py +33 -0
- datarobot_genai/drmcp/core/dynamic_tools/__init__.py +14 -0
- datarobot_genai/drmcp/core/dynamic_tools/deployment/__init__.py +0 -0
- datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/__init__.py +14 -0
- datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/base.py +72 -0
- datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/default.py +82 -0
- datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/drum.py +238 -0
- datarobot_genai/drmcp/core/dynamic_tools/deployment/config.py +228 -0
- datarobot_genai/drmcp/core/dynamic_tools/deployment/controllers.py +63 -0
- datarobot_genai/drmcp/core/dynamic_tools/deployment/metadata.py +162 -0
- datarobot_genai/drmcp/core/dynamic_tools/deployment/register.py +87 -0
- datarobot_genai/drmcp/core/dynamic_tools/deployment/schemas/drum_agentic_fallback_schema.json +36 -0
- datarobot_genai/drmcp/core/dynamic_tools/deployment/schemas/drum_prediction_fallback_schema.json +10 -0
- datarobot_genai/drmcp/core/dynamic_tools/register.py +254 -0
- datarobot_genai/drmcp/core/dynamic_tools/schema.py +532 -0
- datarobot_genai/drmcp/core/exceptions.py +25 -0
- datarobot_genai/drmcp/core/logging.py +98 -0
- datarobot_genai/drmcp/core/mcp_instance.py +515 -0
- datarobot_genai/drmcp/core/memory_management/__init__.py +13 -0
- datarobot_genai/drmcp/core/memory_management/manager.py +820 -0
- datarobot_genai/drmcp/core/memory_management/memory_tools.py +201 -0
- datarobot_genai/drmcp/core/routes.py +439 -0
- datarobot_genai/drmcp/core/routes_utils.py +30 -0
- datarobot_genai/drmcp/core/server_life_cycle.py +107 -0
- datarobot_genai/drmcp/core/telemetry.py +424 -0
- datarobot_genai/drmcp/core/tool_config.py +111 -0
- datarobot_genai/drmcp/core/tool_filter.py +117 -0
- datarobot_genai/drmcp/core/utils.py +138 -0
- datarobot_genai/drmcp/server.py +19 -0
- datarobot_genai/drmcp/test_utils/__init__.py +13 -0
- datarobot_genai/drmcp/test_utils/clients/__init__.py +0 -0
- datarobot_genai/drmcp/test_utils/clients/anthropic.py +68 -0
- datarobot_genai/drmcp/test_utils/clients/base.py +300 -0
- datarobot_genai/drmcp/test_utils/clients/dr_gateway.py +58 -0
- datarobot_genai/drmcp/test_utils/clients/openai.py +68 -0
- datarobot_genai/drmcp/test_utils/elicitation_test_tool.py +89 -0
- datarobot_genai/drmcp/test_utils/integration_mcp_server.py +109 -0
- datarobot_genai/drmcp/test_utils/mcp_utils_ete.py +133 -0
- datarobot_genai/drmcp/test_utils/mcp_utils_integration.py +107 -0
- datarobot_genai/drmcp/test_utils/test_interactive.py +205 -0
- datarobot_genai/drmcp/test_utils/tool_base_ete.py +220 -0
- datarobot_genai/drmcp/test_utils/utils.py +91 -0
- datarobot_genai/drmcp/tools/__init__.py +14 -0
- datarobot_genai/drmcp/tools/clients/__init__.py +14 -0
- datarobot_genai/drmcp/tools/clients/atlassian.py +188 -0
- datarobot_genai/drmcp/tools/clients/confluence.py +584 -0
- datarobot_genai/drmcp/tools/clients/gdrive.py +832 -0
- datarobot_genai/drmcp/tools/clients/jira.py +334 -0
- datarobot_genai/drmcp/tools/clients/microsoft_graph.py +479 -0
- datarobot_genai/drmcp/tools/clients/s3.py +28 -0
- datarobot_genai/drmcp/tools/confluence/__init__.py +14 -0
- datarobot_genai/drmcp/tools/confluence/tools.py +321 -0
- datarobot_genai/drmcp/tools/gdrive/__init__.py +0 -0
- datarobot_genai/drmcp/tools/gdrive/tools.py +347 -0
- datarobot_genai/drmcp/tools/jira/__init__.py +14 -0
- datarobot_genai/drmcp/tools/jira/tools.py +243 -0
- datarobot_genai/drmcp/tools/microsoft_graph/__init__.py +13 -0
- datarobot_genai/drmcp/tools/microsoft_graph/tools.py +198 -0
- datarobot_genai/drmcp/tools/predictive/__init__.py +27 -0
- datarobot_genai/drmcp/tools/predictive/data.py +133 -0
- datarobot_genai/drmcp/tools/predictive/deployment.py +91 -0
- datarobot_genai/drmcp/tools/predictive/deployment_info.py +392 -0
- datarobot_genai/drmcp/tools/predictive/model.py +148 -0
- datarobot_genai/drmcp/tools/predictive/predict.py +254 -0
- datarobot_genai/drmcp/tools/predictive/predict_realtime.py +307 -0
- datarobot_genai/drmcp/tools/predictive/project.py +90 -0
- datarobot_genai/drmcp/tools/predictive/training.py +661 -0
- datarobot_genai/langgraph/__init__.py +0 -0
- datarobot_genai/langgraph/agent.py +341 -0
- datarobot_genai/langgraph/mcp.py +73 -0
- datarobot_genai/llama_index/__init__.py +16 -0
- datarobot_genai/llama_index/agent.py +50 -0
- datarobot_genai/llama_index/base.py +299 -0
- datarobot_genai/llama_index/mcp.py +79 -0
- datarobot_genai/nat/__init__.py +0 -0
- datarobot_genai/nat/agent.py +275 -0
- datarobot_genai/nat/datarobot_auth_provider.py +110 -0
- datarobot_genai/nat/datarobot_llm_clients.py +318 -0
- datarobot_genai/nat/datarobot_llm_providers.py +130 -0
- datarobot_genai/nat/datarobot_mcp_client.py +266 -0
- datarobot_genai/nat/helpers.py +87 -0
- datarobot_genai/py.typed +0 -0
- datarobot_genai-0.2.31.dist-info/METADATA +145 -0
- datarobot_genai-0.2.31.dist-info/RECORD +125 -0
- datarobot_genai-0.2.31.dist-info/WHEEL +4 -0
- datarobot_genai-0.2.31.dist-info/entry_points.txt +5 -0
- datarobot_genai-0.2.31.dist-info/licenses/AUTHORS +2 -0
- datarobot_genai-0.2.31.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,201 @@
|
|
|
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 datarobot_genai.drmcp.core.mcp_instance import dr_core_mcp_tool
|
|
19
|
+
|
|
20
|
+
from .manager import ToolContext
|
|
21
|
+
from .manager import get_memory_manager
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dr_core_mcp_tool()
|
|
25
|
+
async def store_resource(
|
|
26
|
+
data: Any,
|
|
27
|
+
memory_storage_id: str | None = None,
|
|
28
|
+
agent_identifier: str | None = None,
|
|
29
|
+
prompt: str | None = None,
|
|
30
|
+
tool_name: str | None = None,
|
|
31
|
+
tool_parameters: dict[str, Any] | None = None,
|
|
32
|
+
embedding_vector: list[float] | None = None,
|
|
33
|
+
) -> str:
|
|
34
|
+
"""
|
|
35
|
+
Store a resource in the memory storage.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
data: The data to store (string, json or binary)
|
|
39
|
+
memory_storage_id: Optional storage ID to associate the resource with
|
|
40
|
+
agent_identifier: Required if memory_storage_id is provided
|
|
41
|
+
prompt: Optional prompt used to generate this resource
|
|
42
|
+
tool_name: Optional name of the tool used to generate this resource
|
|
43
|
+
tool_parameters: Optional parameters used with the tool
|
|
44
|
+
embedding_vector: Optional embedding vector for the resource
|
|
45
|
+
|
|
46
|
+
Returns
|
|
47
|
+
-------
|
|
48
|
+
str: The ID of the stored resource
|
|
49
|
+
"""
|
|
50
|
+
tool_context = None
|
|
51
|
+
if tool_name and tool_parameters:
|
|
52
|
+
tool_context = ToolContext(name=tool_name, parameters=tool_parameters)
|
|
53
|
+
|
|
54
|
+
memory_manager = get_memory_manager()
|
|
55
|
+
if not memory_manager:
|
|
56
|
+
return "Memory manager not initialized"
|
|
57
|
+
|
|
58
|
+
resource_id = await memory_manager.store_resource(
|
|
59
|
+
data=data,
|
|
60
|
+
memory_storage_id=memory_storage_id,
|
|
61
|
+
agent_identifier=agent_identifier,
|
|
62
|
+
prompt=prompt,
|
|
63
|
+
tool_context=tool_context,
|
|
64
|
+
embedding_vector=embedding_vector,
|
|
65
|
+
)
|
|
66
|
+
return f"Resource stored with ID: {resource_id}"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dr_core_mcp_tool()
|
|
70
|
+
async def get_resource(
|
|
71
|
+
resource_id: str,
|
|
72
|
+
memory_storage_id: str | None = None,
|
|
73
|
+
agent_identifier: str | None = None,
|
|
74
|
+
include_data: bool = True,
|
|
75
|
+
) -> str:
|
|
76
|
+
"""
|
|
77
|
+
Get a resource and optionally its data from the memory storage.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
resource_id: The ID of the resource to retrieve
|
|
81
|
+
memory_storage_id: Optional storage ID the resource belongs to
|
|
82
|
+
agent_identifier: Required if memory_storage_id is provided
|
|
83
|
+
include_data: Whether to include the resource data in the response
|
|
84
|
+
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
str: JSON string containing the resource metadata and optionally its data
|
|
88
|
+
"""
|
|
89
|
+
memory_manager = get_memory_manager()
|
|
90
|
+
if not memory_manager:
|
|
91
|
+
return "Memory manager not initialized"
|
|
92
|
+
|
|
93
|
+
resource = await memory_manager.get_resource(
|
|
94
|
+
resource_id=resource_id,
|
|
95
|
+
memory_storage_id=memory_storage_id,
|
|
96
|
+
agent_identifier=agent_identifier,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if not resource:
|
|
100
|
+
return "Resource not found"
|
|
101
|
+
|
|
102
|
+
result = {
|
|
103
|
+
"id": resource.id,
|
|
104
|
+
"memory_storage_id": resource.memory_storage_id,
|
|
105
|
+
"prompt": resource.prompt,
|
|
106
|
+
"tool_context": resource.tool_context.model_dump() if resource.tool_context else None,
|
|
107
|
+
"embedding_vector": resource.embedding_vector,
|
|
108
|
+
"created_at": resource.created_at.isoformat(),
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if include_data:
|
|
112
|
+
data = await memory_manager.get_resource_data(
|
|
113
|
+
resource_id=resource_id,
|
|
114
|
+
memory_storage_id=memory_storage_id,
|
|
115
|
+
agent_identifier=agent_identifier,
|
|
116
|
+
)
|
|
117
|
+
if isinstance(data, bytes):
|
|
118
|
+
try:
|
|
119
|
+
# Try to decode as string if possible
|
|
120
|
+
result["data"] = data.decode("utf-8")
|
|
121
|
+
except UnicodeDecodeError:
|
|
122
|
+
# If binary data, return as is
|
|
123
|
+
result["data"] = data # type: ignore[assignment]
|
|
124
|
+
else:
|
|
125
|
+
result["data"] = data
|
|
126
|
+
|
|
127
|
+
return json.dumps(result, default=str)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@dr_core_mcp_tool()
|
|
131
|
+
async def list_resources(agent_identifier: str, memory_storage_id: str | None = None) -> str:
|
|
132
|
+
"""
|
|
133
|
+
List all resources from the memory storage.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
agent_identifier: Agent identifier to scope the search
|
|
137
|
+
memory_storage_id: Optional Storage ID to filter resources
|
|
138
|
+
|
|
139
|
+
Returns
|
|
140
|
+
-------
|
|
141
|
+
str: JSON string containing a list of resources
|
|
142
|
+
"""
|
|
143
|
+
memory_manager = get_memory_manager()
|
|
144
|
+
if not memory_manager:
|
|
145
|
+
return "Memory manager not initialized"
|
|
146
|
+
|
|
147
|
+
resources = await memory_manager.list_resources(
|
|
148
|
+
agent_identifier=agent_identifier, memory_storage_id=memory_storage_id
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
if not resources:
|
|
152
|
+
return "No resources found"
|
|
153
|
+
|
|
154
|
+
result = []
|
|
155
|
+
for resource in resources:
|
|
156
|
+
result.append(
|
|
157
|
+
{
|
|
158
|
+
"id": resource.id,
|
|
159
|
+
"memory_storage_id": resource.memory_storage_id,
|
|
160
|
+
"prompt": resource.prompt,
|
|
161
|
+
"tool_context": resource.tool_context.model_dump()
|
|
162
|
+
if resource.tool_context
|
|
163
|
+
else None,
|
|
164
|
+
"created_at": resource.created_at.isoformat(),
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
return json.dumps(result, default=str)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@dr_core_mcp_tool()
|
|
172
|
+
async def delete_resource(
|
|
173
|
+
resource_id: str,
|
|
174
|
+
memory_storage_id: str | None = None,
|
|
175
|
+
agent_identifier: str | None = None,
|
|
176
|
+
) -> str:
|
|
177
|
+
"""
|
|
178
|
+
Delete a resource from the memory storage.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
resource_id: The ID of the resource to delete
|
|
182
|
+
memory_storage_id: Optional storage ID the resource belongs to
|
|
183
|
+
agent_identifier: Required if memory_storage_id is provided
|
|
184
|
+
|
|
185
|
+
Returns
|
|
186
|
+
-------
|
|
187
|
+
str: Success or error message
|
|
188
|
+
"""
|
|
189
|
+
memory_manager = get_memory_manager()
|
|
190
|
+
if not memory_manager:
|
|
191
|
+
return "Memory manager not initialized"
|
|
192
|
+
|
|
193
|
+
success = await memory_manager.delete_resource(
|
|
194
|
+
resource_id=resource_id,
|
|
195
|
+
memory_storage_id=memory_storage_id,
|
|
196
|
+
agent_identifier=agent_identifier,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
if success:
|
|
200
|
+
return f"Resource {resource_id} deleted successfully"
|
|
201
|
+
return f"Failed to delete resource {resource_id}"
|
|
@@ -0,0 +1,439 @@
|
|
|
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
|
+
from http import HTTPStatus
|
|
15
|
+
from logging import getLogger
|
|
16
|
+
|
|
17
|
+
from botocore.exceptions import ClientError
|
|
18
|
+
from starlette.requests import Request
|
|
19
|
+
from starlette.responses import JSONResponse
|
|
20
|
+
|
|
21
|
+
from .dynamic_prompts.controllers import delete_registered_prompt_template
|
|
22
|
+
from .dynamic_prompts.controllers import refresh_registered_prompt_template
|
|
23
|
+
from .dynamic_prompts.controllers import register_prompt_from_prompt_template_id_and_version
|
|
24
|
+
from .dynamic_tools.deployment.controllers import delete_registered_tool_deployment
|
|
25
|
+
from .dynamic_tools.deployment.controllers import get_registered_tool_deployments
|
|
26
|
+
from .dynamic_tools.deployment.controllers import register_tool_for_deployment_id
|
|
27
|
+
from .mcp_instance import TaggedFastMCP
|
|
28
|
+
from .memory_management.manager import get_memory_manager
|
|
29
|
+
from .routes_utils import prefix_mount_path
|
|
30
|
+
|
|
31
|
+
logger = getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def register_routes(mcp: TaggedFastMCP) -> None:
|
|
35
|
+
"""Register all routes with the MCP server."""
|
|
36
|
+
|
|
37
|
+
@mcp.custom_route(prefix_mount_path("/"), methods=["GET"])
|
|
38
|
+
async def handle_health(_: Request) -> JSONResponse:
|
|
39
|
+
return JSONResponse(
|
|
40
|
+
status_code=HTTPStatus.OK,
|
|
41
|
+
content={
|
|
42
|
+
"status": "healthy",
|
|
43
|
+
"message": "DataRobot MCP Server is running",
|
|
44
|
+
},
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Custom endpoint to get all tags
|
|
48
|
+
@mcp.custom_route(prefix_mount_path("/tags"), methods=["GET"])
|
|
49
|
+
async def handle_tags(_: Request) -> JSONResponse:
|
|
50
|
+
try:
|
|
51
|
+
# TaggedFastMCP extends FastMCP with get_all_tags
|
|
52
|
+
tags = await mcp.get_all_tags() # type: ignore[attr-defined]
|
|
53
|
+
return JSONResponse(
|
|
54
|
+
status_code=HTTPStatus.OK,
|
|
55
|
+
content={
|
|
56
|
+
"tags": tags,
|
|
57
|
+
"count": len(tags),
|
|
58
|
+
"message": "All available tags retrieved successfully",
|
|
59
|
+
},
|
|
60
|
+
)
|
|
61
|
+
except Exception as e:
|
|
62
|
+
return JSONResponse(
|
|
63
|
+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
64
|
+
content={
|
|
65
|
+
"error": f"Failed to retrieve tags: {str(e)}",
|
|
66
|
+
},
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
memory_manager = get_memory_manager()
|
|
70
|
+
if memory_manager:
|
|
71
|
+
# Route to initialize a new storage for an agent
|
|
72
|
+
@mcp.custom_route(prefix_mount_path("/agent/{agent_id}/storage/{label}"), methods=["POST"])
|
|
73
|
+
async def initialize_agent_storage(request: Request) -> JSONResponse:
|
|
74
|
+
agent_id = request.path_params["agent_id"]
|
|
75
|
+
label = request.path_params["label"]
|
|
76
|
+
|
|
77
|
+
# Get storage name and config from request body
|
|
78
|
+
body = await request.json()
|
|
79
|
+
config = body.get("config")
|
|
80
|
+
|
|
81
|
+
# Initialize storage
|
|
82
|
+
storage_id = await memory_manager.initialize_storage(
|
|
83
|
+
agent_identifier=agent_id, label=label, storage_config=config
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
return JSONResponse(
|
|
87
|
+
status_code=HTTPStatus.OK,
|
|
88
|
+
content={
|
|
89
|
+
"agentId": agent_id,
|
|
90
|
+
"storageId": storage_id,
|
|
91
|
+
"label": label,
|
|
92
|
+
},
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Route to list all storages for an agent
|
|
96
|
+
@mcp.custom_route(prefix_mount_path("/agent/{agent_id}/storages"), methods=["GET"])
|
|
97
|
+
async def list_agent_storages(request: Request) -> JSONResponse:
|
|
98
|
+
agent_id = request.path_params["agent_id"]
|
|
99
|
+
storages = await memory_manager.list_storages(agent_identifier=agent_id)
|
|
100
|
+
|
|
101
|
+
if not storages:
|
|
102
|
+
return JSONResponse(
|
|
103
|
+
status_code=HTTPStatus.OK,
|
|
104
|
+
content={"agentId": agent_id, "storages": []},
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
storage_list = [
|
|
108
|
+
{
|
|
109
|
+
"storageId": storage.id,
|
|
110
|
+
"label": storage.label,
|
|
111
|
+
"createdAt": storage.created_at.isoformat(),
|
|
112
|
+
}
|
|
113
|
+
for storage in storages
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
return JSONResponse(
|
|
117
|
+
status_code=HTTPStatus.OK,
|
|
118
|
+
content={"agentId": agent_id, "storages": storage_list},
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Route to get a specific storage by ID
|
|
122
|
+
@mcp.custom_route(
|
|
123
|
+
prefix_mount_path("/agent/{agent_id}/storages/{storage_id}"),
|
|
124
|
+
methods=["GET"],
|
|
125
|
+
)
|
|
126
|
+
async def get_agent_storage(request: Request) -> JSONResponse:
|
|
127
|
+
agent_id = request.path_params["agent_id"]
|
|
128
|
+
storage_id = request.path_params["storage_id"]
|
|
129
|
+
|
|
130
|
+
storage = await memory_manager.get_storage(
|
|
131
|
+
agent_identifier=agent_id, memory_storage_id=storage_id
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
if storage:
|
|
135
|
+
return JSONResponse(
|
|
136
|
+
status_code=HTTPStatus.OK,
|
|
137
|
+
content={
|
|
138
|
+
"agentId": agent_id,
|
|
139
|
+
"storageId": storage.id,
|
|
140
|
+
"label": storage.label,
|
|
141
|
+
"createdAt": storage.created_at.isoformat(),
|
|
142
|
+
"storageConfig": storage.storage_config,
|
|
143
|
+
},
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
return JSONResponse(
|
|
147
|
+
status_code=HTTPStatus.NOT_FOUND,
|
|
148
|
+
content={"error": f"Storage {storage_id} not found for agent {agent_id}"},
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Route to delete a specific storage
|
|
152
|
+
@mcp.custom_route(
|
|
153
|
+
prefix_mount_path("/agent/{agent_id}/storages/{storage_id}"),
|
|
154
|
+
methods=["DELETE"],
|
|
155
|
+
)
|
|
156
|
+
async def delete_agent_storage(request: Request) -> JSONResponse:
|
|
157
|
+
agent_id = request.path_params["agent_id"]
|
|
158
|
+
storage_id = request.path_params["storage_id"]
|
|
159
|
+
|
|
160
|
+
success = await memory_manager.delete_storage(
|
|
161
|
+
memory_storage_id=storage_id, agent_identifier=agent_id
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
if success:
|
|
165
|
+
return JSONResponse(
|
|
166
|
+
status_code=HTTPStatus.OK,
|
|
167
|
+
content={"message": f"Storage {storage_id} deleted successfully"},
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
return JSONResponse(
|
|
171
|
+
status_code=HTTPStatus.NOT_FOUND,
|
|
172
|
+
content={"error": f"Storage {storage_id} not found for agent {agent_id}"},
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Route to delete all storages for an agent
|
|
176
|
+
@mcp.custom_route(prefix_mount_path("/agent/{agent_id}"), methods=["DELETE"])
|
|
177
|
+
async def delete_agent(request: Request) -> JSONResponse:
|
|
178
|
+
agent_id = request.path_params["agent_id"]
|
|
179
|
+
|
|
180
|
+
success = await memory_manager.delete_agent(agent_identifier=agent_id)
|
|
181
|
+
|
|
182
|
+
if success:
|
|
183
|
+
return JSONResponse(
|
|
184
|
+
status_code=HTTPStatus.OK,
|
|
185
|
+
content={"message": f"Agent {agent_id} and all storages deleted successfully"},
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
return JSONResponse(
|
|
189
|
+
status_code=HTTPStatus.NOT_FOUND,
|
|
190
|
+
content={"error": f"Agent {agent_id} not found"},
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Route to set active storage for an agent
|
|
194
|
+
@mcp.custom_route(
|
|
195
|
+
prefix_mount_path("/agent/{agent_id}/storages/{storage_id}/activate"),
|
|
196
|
+
methods=["POST"],
|
|
197
|
+
)
|
|
198
|
+
async def set_active_storage(request: Request) -> JSONResponse:
|
|
199
|
+
agent_id = request.path_params["agent_id"]
|
|
200
|
+
storage_id = request.path_params["storage_id"]
|
|
201
|
+
|
|
202
|
+
# First verify the storage exists
|
|
203
|
+
storage = await memory_manager.get_storage(
|
|
204
|
+
agent_identifier=agent_id, memory_storage_id=storage_id
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
if not storage:
|
|
208
|
+
return JSONResponse(
|
|
209
|
+
status_code=HTTPStatus.NOT_FOUND,
|
|
210
|
+
content={"error": f"Storage {storage_id} not found for agent {agent_id}"},
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# Set as active storage
|
|
214
|
+
await memory_manager.set_storage_id_for_agent(
|
|
215
|
+
agent_identifier=agent_id,
|
|
216
|
+
storage_id=storage_id,
|
|
217
|
+
label=storage.label,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
return JSONResponse(
|
|
221
|
+
status_code=HTTPStatus.OK,
|
|
222
|
+
content={
|
|
223
|
+
"agentId": agent_id,
|
|
224
|
+
"storageId": storage_id,
|
|
225
|
+
"label": storage.label,
|
|
226
|
+
"message": "Active storage set successfully",
|
|
227
|
+
},
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Route to get active storage for an agent
|
|
231
|
+
@mcp.custom_route(prefix_mount_path("/agent/{agent_id}/active-storage"), methods=["GET"])
|
|
232
|
+
async def get_active_storage(request: Request) -> JSONResponse:
|
|
233
|
+
agent_id = request.path_params["agent_id"]
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
storage_id = await memory_manager.get_active_storage_id_for_agent(
|
|
237
|
+
agent_identifier=agent_id
|
|
238
|
+
)
|
|
239
|
+
except ClientError as e:
|
|
240
|
+
if e.response["Error"]["Code"] == "404":
|
|
241
|
+
return JSONResponse(
|
|
242
|
+
status_code=HTTPStatus.NOT_FOUND,
|
|
243
|
+
content={"error": f"No active storage found for agent {agent_id}"},
|
|
244
|
+
)
|
|
245
|
+
return JSONResponse(
|
|
246
|
+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, content={"error": str(e)}
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
return JSONResponse(
|
|
250
|
+
status_code=HTTPStatus.OK,
|
|
251
|
+
content={
|
|
252
|
+
"agentId": agent_id,
|
|
253
|
+
"storageId": storage_id,
|
|
254
|
+
},
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
# Route to clear active storage for an agent
|
|
258
|
+
@mcp.custom_route(prefix_mount_path("/agent/{agent_id}/active-storage"), methods=["DELETE"])
|
|
259
|
+
async def clear_active_storage(request: Request) -> JSONResponse:
|
|
260
|
+
agent_id = request.path_params["agent_id"]
|
|
261
|
+
|
|
262
|
+
# Clear active storage
|
|
263
|
+
try:
|
|
264
|
+
await memory_manager.clear_storage_id_for_agent(agent_identifier=agent_id)
|
|
265
|
+
except ClientError as e:
|
|
266
|
+
if e.response["Error"]["Code"] == "404":
|
|
267
|
+
return JSONResponse(
|
|
268
|
+
status_code=HTTPStatus.NOT_FOUND,
|
|
269
|
+
content={"error": f"No active storage found for agent {agent_id}"},
|
|
270
|
+
)
|
|
271
|
+
return JSONResponse(status_code=500, content={"error": str(e)})
|
|
272
|
+
|
|
273
|
+
return JSONResponse(
|
|
274
|
+
status_code=HTTPStatus.OK,
|
|
275
|
+
content={"message": f"Active storage cleared for agent {agent_id}"},
|
|
276
|
+
)
|
|
277
|
+
else:
|
|
278
|
+
logger.info("Memory manager not initialized, skipping memory manager routes")
|
|
279
|
+
|
|
280
|
+
@mcp.custom_route(prefix_mount_path("/registeredDeployments/{deployment_id}"), methods=["PUT"])
|
|
281
|
+
async def add_deployment(request: Request) -> JSONResponse:
|
|
282
|
+
"""Add or update a deployment with a known deployment_id."""
|
|
283
|
+
deployment_id = request.path_params["deployment_id"]
|
|
284
|
+
try:
|
|
285
|
+
tool = await register_tool_for_deployment_id(deployment_id)
|
|
286
|
+
return JSONResponse(
|
|
287
|
+
status_code=HTTPStatus.CREATED,
|
|
288
|
+
content={
|
|
289
|
+
"name": tool.name,
|
|
290
|
+
"description": tool.description,
|
|
291
|
+
"tags": list(tool.tags),
|
|
292
|
+
"deploymentId": deployment_id,
|
|
293
|
+
},
|
|
294
|
+
)
|
|
295
|
+
except Exception as e:
|
|
296
|
+
return JSONResponse(
|
|
297
|
+
status_code=HTTPStatus.BAD_REQUEST,
|
|
298
|
+
content={"error": f"Failed to add deployment: {str(e)}"},
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
@mcp.custom_route(prefix_mount_path("/registeredDeployments"), methods=["GET"])
|
|
302
|
+
async def list_deployments(_: Request) -> JSONResponse:
|
|
303
|
+
"""List all deployments."""
|
|
304
|
+
try:
|
|
305
|
+
deployments = await get_registered_tool_deployments()
|
|
306
|
+
formatted_deployments = [
|
|
307
|
+
{"deploymentId": k, "toolName": v} for k, v in deployments.items()
|
|
308
|
+
]
|
|
309
|
+
return JSONResponse(
|
|
310
|
+
status_code=HTTPStatus.OK,
|
|
311
|
+
content={
|
|
312
|
+
"deployments": formatted_deployments,
|
|
313
|
+
"count": len(deployments),
|
|
314
|
+
},
|
|
315
|
+
)
|
|
316
|
+
except Exception as e:
|
|
317
|
+
return JSONResponse(
|
|
318
|
+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
319
|
+
content={"error": f"Failed to retrieve deployments: {str(e)}"},
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
@mcp.custom_route(
|
|
323
|
+
prefix_mount_path("/registeredDeployments/{deployment_id}"), methods=["DELETE"]
|
|
324
|
+
)
|
|
325
|
+
async def delete_deployment(request: Request) -> JSONResponse:
|
|
326
|
+
"""Delete (de-register) a deployment by deployment_id."""
|
|
327
|
+
deployment_id = request.path_params["deployment_id"]
|
|
328
|
+
try:
|
|
329
|
+
deleted = await delete_registered_tool_deployment(deployment_id)
|
|
330
|
+
if deleted is True:
|
|
331
|
+
return JSONResponse(
|
|
332
|
+
status_code=HTTPStatus.OK,
|
|
333
|
+
content={
|
|
334
|
+
"message": f"Tool with deployment {deployment_id} deleted successfully"
|
|
335
|
+
},
|
|
336
|
+
)
|
|
337
|
+
return JSONResponse(
|
|
338
|
+
status_code=HTTPStatus.NOT_FOUND,
|
|
339
|
+
content={"error": f"Tool with deployment {deployment_id} not found"},
|
|
340
|
+
)
|
|
341
|
+
except Exception as e:
|
|
342
|
+
return JSONResponse(
|
|
343
|
+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
344
|
+
content={"error": f"Failed to delete deployment: {str(e)}"},
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
@mcp.custom_route(prefix_mount_path("/registeredPrompts"), methods=["GET"])
|
|
348
|
+
async def list_prompt_templates(_: Request) -> JSONResponse:
|
|
349
|
+
"""List all prompt templates."""
|
|
350
|
+
try:
|
|
351
|
+
prompts = await mcp.get_prompt_mapping()
|
|
352
|
+
formatted_prompts = [
|
|
353
|
+
{
|
|
354
|
+
"promptTemplateId": pt_id,
|
|
355
|
+
"promptTemplateVersionId": ptv_id,
|
|
356
|
+
"promptName": p_name,
|
|
357
|
+
}
|
|
358
|
+
for pt_id, (ptv_id, p_name) in prompts.items()
|
|
359
|
+
]
|
|
360
|
+
return JSONResponse(
|
|
361
|
+
status_code=HTTPStatus.OK,
|
|
362
|
+
content={
|
|
363
|
+
"promptTemplates": formatted_prompts,
|
|
364
|
+
"count": len(formatted_prompts),
|
|
365
|
+
},
|
|
366
|
+
)
|
|
367
|
+
except Exception as e:
|
|
368
|
+
return JSONResponse(
|
|
369
|
+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
370
|
+
content={"error": f"Failed to retrieve promptTemplates: {str(e)}"},
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
@mcp.custom_route(
|
|
374
|
+
prefix_mount_path("/registeredPrompts/{prompt_template_id}"), methods=["DELETE"]
|
|
375
|
+
)
|
|
376
|
+
async def delete_prompt_template(request: Request) -> JSONResponse:
|
|
377
|
+
"""Delete (de-register) a prompt by prompt_template_id."""
|
|
378
|
+
prompt_template_id = request.path_params["prompt_template_id"]
|
|
379
|
+
try:
|
|
380
|
+
deleted = await delete_registered_prompt_template(prompt_template_id)
|
|
381
|
+
if deleted:
|
|
382
|
+
return JSONResponse(
|
|
383
|
+
status_code=HTTPStatus.OK,
|
|
384
|
+
content={
|
|
385
|
+
"message": f"Prompt with prompt template id {prompt_template_id} "
|
|
386
|
+
f"deleted successfully"
|
|
387
|
+
},
|
|
388
|
+
)
|
|
389
|
+
return JSONResponse(
|
|
390
|
+
status_code=HTTPStatus.NOT_FOUND,
|
|
391
|
+
content={"error": f"Prompt with prompt template id {prompt_template_id} not found"},
|
|
392
|
+
)
|
|
393
|
+
except Exception as e:
|
|
394
|
+
return JSONResponse(
|
|
395
|
+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
396
|
+
content={"error": f"Failed to delete prompt: {str(e)}"},
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
@mcp.custom_route(
|
|
400
|
+
prefix_mount_path("/registeredPrompts/{prompt_template_id}"),
|
|
401
|
+
methods=["PUT"],
|
|
402
|
+
)
|
|
403
|
+
async def add_prompt_template(request: Request) -> JSONResponse:
|
|
404
|
+
"""Add or update prompt template."""
|
|
405
|
+
prompt_template_id = request.path_params["prompt_template_id"]
|
|
406
|
+
prompt_template_version_id = request.query_params.get("promptTemplateVersionId")
|
|
407
|
+
try:
|
|
408
|
+
prompt = await register_prompt_from_prompt_template_id_and_version(
|
|
409
|
+
prompt_template_id, prompt_template_version_id
|
|
410
|
+
)
|
|
411
|
+
return JSONResponse(
|
|
412
|
+
status_code=HTTPStatus.CREATED,
|
|
413
|
+
content={
|
|
414
|
+
"name": prompt.name,
|
|
415
|
+
"description": prompt.description,
|
|
416
|
+
"promptTemplateId": prompt_template_id,
|
|
417
|
+
"promptTemplateVersionId": prompt.meta["prompt_template_version_id"],
|
|
418
|
+
},
|
|
419
|
+
)
|
|
420
|
+
except Exception as e:
|
|
421
|
+
return JSONResponse(
|
|
422
|
+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
423
|
+
content={"error": f"Failed to add prompt template: {str(e)}"},
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
@mcp.custom_route(prefix_mount_path("/registeredPrompts"), methods=["PUT"])
|
|
427
|
+
async def refresh_prompt_templates(_: Request) -> JSONResponse:
|
|
428
|
+
"""Refresh prompt templates."""
|
|
429
|
+
try:
|
|
430
|
+
await refresh_registered_prompt_template()
|
|
431
|
+
return JSONResponse(
|
|
432
|
+
status_code=HTTPStatus.OK,
|
|
433
|
+
content={"message": "Prompts refreshed successfully"},
|
|
434
|
+
)
|
|
435
|
+
except Exception as e:
|
|
436
|
+
return JSONResponse(
|
|
437
|
+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
438
|
+
content={"error": f"Failed to refresh prompt templates: {str(e)}"},
|
|
439
|
+
)
|
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
from .config import get_config
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def prefix_mount_path(endpoint: str) -> str:
|
|
19
|
+
config = get_config()
|
|
20
|
+
mount_path = config.mount_path
|
|
21
|
+
|
|
22
|
+
if mount_path == "/":
|
|
23
|
+
return endpoint
|
|
24
|
+
|
|
25
|
+
if mount_path.endswith("/"):
|
|
26
|
+
mount_path = mount_path[:-1]
|
|
27
|
+
|
|
28
|
+
if not endpoint.startswith("/"):
|
|
29
|
+
endpoint = "/" + endpoint
|
|
30
|
+
return mount_path + endpoint
|