datarobot-genai 0.1.68__py3-none-any.whl → 0.1.70__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/core/mcp/common.py +82 -48
- datarobot_genai/crewai/base.py +33 -53
- {datarobot_genai-0.1.68.dist-info → datarobot_genai-0.1.70.dist-info}/METADATA +1 -1
- {datarobot_genai-0.1.68.dist-info → datarobot_genai-0.1.70.dist-info}/RECORD +8 -8
- {datarobot_genai-0.1.68.dist-info → datarobot_genai-0.1.70.dist-info}/WHEEL +0 -0
- {datarobot_genai-0.1.68.dist-info → datarobot_genai-0.1.70.dist-info}/entry_points.txt +0 -0
- {datarobot_genai-0.1.68.dist-info → datarobot_genai-0.1.70.dist-info}/licenses/AUTHORS +0 -0
- {datarobot_genai-0.1.68.dist-info → datarobot_genai-0.1.70.dist-info}/licenses/LICENSE +0 -0
|
@@ -13,16 +13,20 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
import json
|
|
16
|
+
import logging
|
|
16
17
|
import re
|
|
18
|
+
from http import HTTPStatus
|
|
17
19
|
from typing import Any
|
|
18
20
|
from typing import Literal
|
|
19
|
-
from urllib.parse import urlparse
|
|
20
21
|
|
|
22
|
+
import requests
|
|
21
23
|
from datarobot.core.config import DataRobotAppFrameworkBaseSettings
|
|
22
24
|
from pydantic import field_validator
|
|
23
25
|
|
|
24
26
|
from datarobot_genai.core.utils.auth import AuthContextHeaderHandler
|
|
25
27
|
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
26
30
|
|
|
27
31
|
class MCPConfig(DataRobotAppFrameworkBaseSettings):
|
|
28
32
|
"""Configuration for MCP server connection.
|
|
@@ -39,6 +43,7 @@ class MCPConfig(DataRobotAppFrameworkBaseSettings):
|
|
|
39
43
|
datarobot_api_token: str | None = None
|
|
40
44
|
authorization_context: dict[str, Any] | None = None
|
|
41
45
|
forwarded_headers: dict[str, str] | None = None
|
|
46
|
+
mcp_server_port: int | None = None
|
|
42
47
|
|
|
43
48
|
_auth_context_handler: AuthContextHeaderHandler | None = None
|
|
44
49
|
_server_config: dict[str, Any] | None = None
|
|
@@ -49,17 +54,14 @@ class MCPConfig(DataRobotAppFrameworkBaseSettings):
|
|
|
49
54
|
if value is None:
|
|
50
55
|
return None
|
|
51
56
|
|
|
52
|
-
if not isinstance(value, str):
|
|
53
|
-
msg = "external_mcp_headers must be a JSON string"
|
|
54
|
-
raise TypeError(msg)
|
|
55
|
-
|
|
56
57
|
candidate = value.strip()
|
|
57
58
|
|
|
58
59
|
try:
|
|
59
60
|
json.loads(candidate)
|
|
60
|
-
except json.JSONDecodeError
|
|
61
|
+
except json.JSONDecodeError:
|
|
61
62
|
msg = "external_mcp_headers must be valid JSON"
|
|
62
|
-
|
|
63
|
+
logger.warning(msg)
|
|
64
|
+
return None
|
|
63
65
|
|
|
64
66
|
return candidate
|
|
65
67
|
|
|
@@ -69,15 +71,12 @@ class MCPConfig(DataRobotAppFrameworkBaseSettings):
|
|
|
69
71
|
if value is None:
|
|
70
72
|
return None
|
|
71
73
|
|
|
72
|
-
if not isinstance(value, str):
|
|
73
|
-
msg = "mcp_deployment_id must be a string"
|
|
74
|
-
raise TypeError(msg)
|
|
75
|
-
|
|
76
74
|
candidate = value.strip()
|
|
77
75
|
|
|
78
76
|
if not re.fullmatch(r"[0-9a-fA-F]{24}", candidate):
|
|
79
77
|
msg = "mcp_deployment_id must be a valid 24-character hex ID"
|
|
80
|
-
|
|
78
|
+
logger.warning(msg)
|
|
79
|
+
return None
|
|
81
80
|
|
|
82
81
|
return candidate
|
|
83
82
|
|
|
@@ -112,6 +111,45 @@ class MCPConfig(DataRobotAppFrameworkBaseSettings):
|
|
|
112
111
|
# Authorization context not available (e.g., in tests)
|
|
113
112
|
return {}
|
|
114
113
|
|
|
114
|
+
def _build_authenticated_headers(self) -> dict[str, str]:
|
|
115
|
+
"""Build headers for authenticated requests.
|
|
116
|
+
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
Dictionary containing forwarded headers (if available) and authentication headers.
|
|
120
|
+
"""
|
|
121
|
+
headers: dict[str, str] = {}
|
|
122
|
+
if self.forwarded_headers:
|
|
123
|
+
headers.update(self.forwarded_headers)
|
|
124
|
+
headers.update(self._authorization_bearer_header())
|
|
125
|
+
headers.update(self._authorization_context_header())
|
|
126
|
+
return headers
|
|
127
|
+
|
|
128
|
+
def _check_localhost_server(self, url: str, timeout: float = 2.0) -> bool:
|
|
129
|
+
"""Check if MCP server is running on localhost.
|
|
130
|
+
|
|
131
|
+
Parameters
|
|
132
|
+
----------
|
|
133
|
+
url : str
|
|
134
|
+
The URL to check.
|
|
135
|
+
timeout : float, optional
|
|
136
|
+
Request timeout in seconds (default: 2.0).
|
|
137
|
+
|
|
138
|
+
Returns
|
|
139
|
+
-------
|
|
140
|
+
bool
|
|
141
|
+
True if server is running and responding with OK status, False otherwise.
|
|
142
|
+
"""
|
|
143
|
+
try:
|
|
144
|
+
response = requests.get(url, timeout=timeout)
|
|
145
|
+
return (
|
|
146
|
+
response.status_code == HTTPStatus.OK
|
|
147
|
+
and response.json().get("message") == "DataRobot MCP Server is running"
|
|
148
|
+
)
|
|
149
|
+
except requests.RequestException as e:
|
|
150
|
+
logger.debug(f"Failed to connect to MCP server at {url}: {e}")
|
|
151
|
+
return False
|
|
152
|
+
|
|
115
153
|
def _build_server_config(self) -> dict[str, Any] | None:
|
|
116
154
|
"""
|
|
117
155
|
Get MCP server configuration.
|
|
@@ -121,34 +159,6 @@ class MCPConfig(DataRobotAppFrameworkBaseSettings):
|
|
|
121
159
|
Server configuration dict with url, transport, and optional headers,
|
|
122
160
|
or None if not configured.
|
|
123
161
|
"""
|
|
124
|
-
if self.external_mcp_url:
|
|
125
|
-
# External MCP URL - no authentication needed
|
|
126
|
-
headers: dict[str, str] = {}
|
|
127
|
-
|
|
128
|
-
# Forward headers for localhost connections
|
|
129
|
-
if self.forwarded_headers:
|
|
130
|
-
try:
|
|
131
|
-
parsed_url = urlparse(self.external_mcp_url)
|
|
132
|
-
hostname = parsed_url.hostname or ""
|
|
133
|
-
# Check if hostname is localhost or 127.0.0.1
|
|
134
|
-
if hostname in ("localhost", "127.0.0.1", "::1"):
|
|
135
|
-
headers.update(self.forwarded_headers)
|
|
136
|
-
except Exception:
|
|
137
|
-
# If URL parsing fails, fall back to simple string check
|
|
138
|
-
if "localhost" in self.external_mcp_url or "127.0.0.1" in self.external_mcp_url:
|
|
139
|
-
headers.update(self.forwarded_headers)
|
|
140
|
-
|
|
141
|
-
# Merge external headers if provided
|
|
142
|
-
if self.external_mcp_headers:
|
|
143
|
-
external_headers = json.loads(self.external_mcp_headers)
|
|
144
|
-
headers.update(external_headers)
|
|
145
|
-
|
|
146
|
-
return {
|
|
147
|
-
"url": self.external_mcp_url.rstrip("/"),
|
|
148
|
-
"transport": self.external_mcp_transport,
|
|
149
|
-
"headers": headers,
|
|
150
|
-
}
|
|
151
|
-
|
|
152
162
|
if self.mcp_deployment_id:
|
|
153
163
|
# DataRobot deployment ID - requires authentication
|
|
154
164
|
if self.datarobot_endpoint is None:
|
|
@@ -165,15 +175,9 @@ class MCPConfig(DataRobotAppFrameworkBaseSettings):
|
|
|
165
175
|
base_url = f"{base_url}/api/v2"
|
|
166
176
|
|
|
167
177
|
url = f"{base_url}/deployments/{self.mcp_deployment_id}/directAccess/mcp"
|
|
178
|
+
headers = self._build_authenticated_headers()
|
|
168
179
|
|
|
169
|
-
|
|
170
|
-
headers = {}
|
|
171
|
-
if self.forwarded_headers:
|
|
172
|
-
headers.update(self.forwarded_headers)
|
|
173
|
-
|
|
174
|
-
# Add authentication headers
|
|
175
|
-
headers.update(self._authorization_bearer_header())
|
|
176
|
-
headers.update(self._authorization_context_header())
|
|
180
|
+
logger.info(f"Using DataRobot hosted MCP deployment: {url}")
|
|
177
181
|
|
|
178
182
|
return {
|
|
179
183
|
"url": url,
|
|
@@ -181,4 +185,34 @@ class MCPConfig(DataRobotAppFrameworkBaseSettings):
|
|
|
181
185
|
"headers": headers,
|
|
182
186
|
}
|
|
183
187
|
|
|
188
|
+
if self.external_mcp_url:
|
|
189
|
+
# External MCP URL - no authentication needed
|
|
190
|
+
headers = {}
|
|
191
|
+
|
|
192
|
+
# Merge external headers if provided
|
|
193
|
+
if self.external_mcp_headers:
|
|
194
|
+
external_headers = json.loads(self.external_mcp_headers)
|
|
195
|
+
headers.update(external_headers)
|
|
196
|
+
|
|
197
|
+
logger.info(f"Using external MCP URL: {self.external_mcp_url}")
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
"url": self.external_mcp_url.rstrip("/"),
|
|
201
|
+
"transport": self.external_mcp_transport,
|
|
202
|
+
"headers": headers,
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
# No MCP configuration found, setup localhost if running locally
|
|
206
|
+
if self.mcp_server_port:
|
|
207
|
+
url = f"http://localhost:{self.mcp_server_port}"
|
|
208
|
+
if self._check_localhost_server(url):
|
|
209
|
+
headers = self._build_authenticated_headers()
|
|
210
|
+
logger.info(f"Using localhost MCP server: {url}")
|
|
211
|
+
return {
|
|
212
|
+
"url": f"{url}/mcp",
|
|
213
|
+
"transport": "streamable-http",
|
|
214
|
+
"headers": headers,
|
|
215
|
+
}
|
|
216
|
+
logger.warning(f"MCP server is not running or not responding at {url}")
|
|
217
|
+
|
|
184
218
|
return None
|
datarobot_genai/crewai/base.py
CHANGED
|
@@ -80,6 +80,37 @@ class CrewAIAgent(BaseAgent[BaseTool], abc.ABC):
|
|
|
80
80
|
"""
|
|
81
81
|
raise NotImplementedError
|
|
82
82
|
|
|
83
|
+
def _extract_pipeline_interactions(self) -> MultiTurnSample | None:
|
|
84
|
+
"""Extract pipeline interactions from event listener if available."""
|
|
85
|
+
if not hasattr(self, "event_listener"):
|
|
86
|
+
return None
|
|
87
|
+
try:
|
|
88
|
+
listener = getattr(self, "event_listener", None)
|
|
89
|
+
messages = getattr(listener, "messages", None) if listener is not None else None
|
|
90
|
+
return create_pipeline_interactions_from_messages(messages)
|
|
91
|
+
except Exception:
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
def _extract_usage_metrics(self, crew_output: Any) -> UsageMetrics:
|
|
95
|
+
"""Extract usage metrics from crew output."""
|
|
96
|
+
token_usage = getattr(crew_output, "token_usage", None)
|
|
97
|
+
if token_usage is not None:
|
|
98
|
+
return {
|
|
99
|
+
"completion_tokens": int(getattr(token_usage, "completion_tokens", 0)),
|
|
100
|
+
"prompt_tokens": int(getattr(token_usage, "prompt_tokens", 0)),
|
|
101
|
+
"total_tokens": int(getattr(token_usage, "total_tokens", 0)),
|
|
102
|
+
}
|
|
103
|
+
return default_usage_metrics()
|
|
104
|
+
|
|
105
|
+
def _process_crew_output(
|
|
106
|
+
self, crew_output: Any
|
|
107
|
+
) -> tuple[str, MultiTurnSample | None, UsageMetrics]:
|
|
108
|
+
"""Process crew output into response tuple."""
|
|
109
|
+
response_text = str(crew_output.raw)
|
|
110
|
+
pipeline_interactions = self._extract_pipeline_interactions()
|
|
111
|
+
usage_metrics = self._extract_usage_metrics(crew_output)
|
|
112
|
+
return response_text, pipeline_interactions, usage_metrics
|
|
113
|
+
|
|
83
114
|
async def invoke(self, completion_create_params: CompletionCreateParams) -> InvokeReturn:
|
|
84
115
|
"""Run the CrewAI workflow with the provided completion parameters."""
|
|
85
116
|
user_prompt_content = extract_user_prompt_content(completion_create_params)
|
|
@@ -116,64 +147,13 @@ class CrewAIAgent(BaseAgent[BaseTool], abc.ABC):
|
|
|
116
147
|
async def _gen() -> AsyncGenerator[
|
|
117
148
|
tuple[str, MultiTurnSample | None, UsageMetrics]
|
|
118
149
|
]:
|
|
119
|
-
# Run kickoff in a worker thread.
|
|
120
150
|
crew_output = await asyncio.to_thread(
|
|
121
151
|
crew.kickoff,
|
|
122
152
|
inputs=self.make_kickoff_inputs(user_prompt_content),
|
|
123
153
|
)
|
|
124
|
-
|
|
125
|
-
pipeline_interactions = None
|
|
126
|
-
if hasattr(self, "event_listener"):
|
|
127
|
-
try:
|
|
128
|
-
listener = getattr(self, "event_listener", None)
|
|
129
|
-
messages = (
|
|
130
|
-
getattr(listener, "messages", None)
|
|
131
|
-
if listener is not None
|
|
132
|
-
else None
|
|
133
|
-
)
|
|
134
|
-
pipeline_interactions = create_pipeline_interactions_from_messages(
|
|
135
|
-
messages
|
|
136
|
-
)
|
|
137
|
-
except Exception:
|
|
138
|
-
pipeline_interactions = None
|
|
139
|
-
|
|
140
|
-
token_usage = getattr(crew_output, "token_usage", None)
|
|
141
|
-
if token_usage is not None:
|
|
142
|
-
usage_metrics: UsageMetrics = {
|
|
143
|
-
"completion_tokens": int(getattr(token_usage, "completion_tokens", 0)),
|
|
144
|
-
"prompt_tokens": int(getattr(token_usage, "prompt_tokens", 0)),
|
|
145
|
-
"total_tokens": int(getattr(token_usage, "total_tokens", 0)),
|
|
146
|
-
}
|
|
147
|
-
else:
|
|
148
|
-
usage_metrics = default_usage_metrics()
|
|
149
|
-
|
|
150
|
-
# Finalize stream with empty chunk carrying interactions and usage
|
|
151
|
-
yield "", pipeline_interactions, usage_metrics
|
|
154
|
+
yield self._process_crew_output(crew_output)
|
|
152
155
|
|
|
153
156
|
return _gen()
|
|
154
157
|
|
|
155
|
-
# Non-streaming: run to completion and return final result
|
|
156
158
|
crew_output = crew.kickoff(inputs=self.make_kickoff_inputs(user_prompt_content))
|
|
157
|
-
|
|
158
|
-
response_text = str(crew_output.raw)
|
|
159
|
-
|
|
160
|
-
pipeline_interactions = None
|
|
161
|
-
if hasattr(self, "event_listener"):
|
|
162
|
-
try:
|
|
163
|
-
listener = getattr(self, "event_listener", None)
|
|
164
|
-
messages = getattr(listener, "messages", None) if listener is not None else None
|
|
165
|
-
pipeline_interactions = create_pipeline_interactions_from_messages(messages)
|
|
166
|
-
except Exception:
|
|
167
|
-
pipeline_interactions = None
|
|
168
|
-
|
|
169
|
-
token_usage = getattr(crew_output, "token_usage", None)
|
|
170
|
-
if token_usage is not None:
|
|
171
|
-
usage_metrics: UsageMetrics = {
|
|
172
|
-
"completion_tokens": int(getattr(token_usage, "completion_tokens", 0)),
|
|
173
|
-
"prompt_tokens": int(getattr(token_usage, "prompt_tokens", 0)),
|
|
174
|
-
"total_tokens": int(getattr(token_usage, "total_tokens", 0)),
|
|
175
|
-
}
|
|
176
|
-
else:
|
|
177
|
-
usage_metrics = default_usage_metrics()
|
|
178
|
-
|
|
179
|
-
return response_text, pipeline_interactions, usage_metrics
|
|
159
|
+
return self._process_crew_output(crew_output)
|
|
@@ -13,13 +13,13 @@ datarobot_genai/core/cli/__init__.py,sha256=B93Yb6VavoZpatrh8ltCL6YglIfR5FHgytXb
|
|
|
13
13
|
datarobot_genai/core/cli/agent_environment.py,sha256=BJzQoiDvZF5gW4mFE71U0yeg-l72C--kxiE-fv6W194,1662
|
|
14
14
|
datarobot_genai/core/cli/agent_kernel.py,sha256=3XX58DQ6XPpWB_tn5m3iGb3XTfhZf5X3W9tc6ADieU4,7790
|
|
15
15
|
datarobot_genai/core/mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
datarobot_genai/core/mcp/common.py,sha256=
|
|
16
|
+
datarobot_genai/core/mcp/common.py,sha256=Y8SjuquUODKEfI7T9X-QuTMKdIlpCWFI1b3xs6tmHFA,7812
|
|
17
17
|
datarobot_genai/core/utils/__init__.py,sha256=VxtRUz6iwb04eFQQy0zqTNXLAkYpPXcJxVoKV0nOdXk,59
|
|
18
18
|
datarobot_genai/core/utils/auth.py,sha256=Xo1PxVr6oMgtMHkmHdS02klDKK1cyDpjGvIMF4Tx0Lo,7874
|
|
19
19
|
datarobot_genai/core/utils/urls.py,sha256=tk0t13duDEPcmwz2OnS4vwEdatruiuX8lnxMMhSaJik,2289
|
|
20
20
|
datarobot_genai/crewai/__init__.py,sha256=MtFnHA3EtmgiK_GjwUGPgQQ6G1MCEzz1SDBwQi9lE8M,706
|
|
21
21
|
datarobot_genai/crewai/agent.py,sha256=vp8_2LExpeLls7Fpzo0R6ud5I6Ryfu3n3oVTN4Yyi6A,1417
|
|
22
|
-
datarobot_genai/crewai/base.py,sha256=
|
|
22
|
+
datarobot_genai/crewai/base.py,sha256=JLljEN7sj8zaH8OamYoevFBZzza5BjZ4f0CGHRp2jUU,6447
|
|
23
23
|
datarobot_genai/crewai/events.py,sha256=K67bO1zwPrxmppz2wh8dFGNbVebyWGXAMD7oodFE2sQ,5462
|
|
24
24
|
datarobot_genai/crewai/mcp.py,sha256=AJTrs-8KdiRSjRECfBT1lJOsszWMoFoN9NIa1p5_wsM,2115
|
|
25
25
|
datarobot_genai/drmcp/__init__.py,sha256=JE83bfpGU7v77VzrDdlb0l8seM5OwUsUbaQErJ2eisc,2983
|
|
@@ -93,9 +93,9 @@ datarobot_genai/nat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
|
|
|
93
93
|
datarobot_genai/nat/agent.py,sha256=siBLDWAff2-JwZ8Q3iNpM_e4_IoSwG9IvY0hyEjNenw,10292
|
|
94
94
|
datarobot_genai/nat/datarobot_llm_clients.py,sha256=IZq_kooUL8QyDTkpEreszLQk9vCzg6-FbTjIkXR9wc0,7203
|
|
95
95
|
datarobot_genai/nat/datarobot_llm_providers.py,sha256=lOVaL_0Fl6-7GFYl3HmfqttqKpKt-2w8o92P3T7B6cU,3683
|
|
96
|
-
datarobot_genai-0.1.
|
|
97
|
-
datarobot_genai-0.1.
|
|
98
|
-
datarobot_genai-0.1.
|
|
99
|
-
datarobot_genai-0.1.
|
|
100
|
-
datarobot_genai-0.1.
|
|
101
|
-
datarobot_genai-0.1.
|
|
96
|
+
datarobot_genai-0.1.70.dist-info/METADATA,sha256=Dwm4E8vbAhiaRmO-TcfHOgti00CJP7GyTsKTn5efiew,5918
|
|
97
|
+
datarobot_genai-0.1.70.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
98
|
+
datarobot_genai-0.1.70.dist-info/entry_points.txt,sha256=CZhmZcSyt_RBltgLN_b9xasJD6J5SaDc_z7K0wuOY9Y,150
|
|
99
|
+
datarobot_genai-0.1.70.dist-info/licenses/AUTHORS,sha256=isJGUXdjq1U7XZ_B_9AH8Qf0u4eX0XyQifJZ_Sxm4sA,80
|
|
100
|
+
datarobot_genai-0.1.70.dist-info/licenses/LICENSE,sha256=U2_VkLIktQoa60Nf6Tbt7E4RMlfhFSjWjcJJfVC-YCE,11341
|
|
101
|
+
datarobot_genai-0.1.70.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|