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.
Files changed (125) 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 +364 -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 +350 -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 +70 -0
  37. datarobot_genai/drmcp/core/dynamic_prompts/register.py +205 -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 +515 -0
  56. datarobot_genai/drmcp/core/memory_management/__init__.py +13 -0
  57. datarobot_genai/drmcp/core/memory_management/manager.py +820 -0
  58. datarobot_genai/drmcp/core/memory_management/memory_tools.py +201 -0
  59. datarobot_genai/drmcp/core/routes.py +439 -0
  60. datarobot_genai/drmcp/core/routes_utils.py +30 -0
  61. datarobot_genai/drmcp/core/server_life_cycle.py +107 -0
  62. datarobot_genai/drmcp/core/telemetry.py +424 -0
  63. datarobot_genai/drmcp/core/tool_config.py +111 -0
  64. datarobot_genai/drmcp/core/tool_filter.py +117 -0
  65. datarobot_genai/drmcp/core/utils.py +138 -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/clients/__init__.py +0 -0
  69. datarobot_genai/drmcp/test_utils/clients/anthropic.py +68 -0
  70. datarobot_genai/drmcp/test_utils/clients/base.py +300 -0
  71. datarobot_genai/drmcp/test_utils/clients/dr_gateway.py +58 -0
  72. datarobot_genai/drmcp/test_utils/clients/openai.py +68 -0
  73. datarobot_genai/drmcp/test_utils/elicitation_test_tool.py +89 -0
  74. datarobot_genai/drmcp/test_utils/integration_mcp_server.py +109 -0
  75. datarobot_genai/drmcp/test_utils/mcp_utils_ete.py +133 -0
  76. datarobot_genai/drmcp/test_utils/mcp_utils_integration.py +107 -0
  77. datarobot_genai/drmcp/test_utils/test_interactive.py +205 -0
  78. datarobot_genai/drmcp/test_utils/tool_base_ete.py +220 -0
  79. datarobot_genai/drmcp/test_utils/utils.py +91 -0
  80. datarobot_genai/drmcp/tools/__init__.py +14 -0
  81. datarobot_genai/drmcp/tools/clients/__init__.py +14 -0
  82. datarobot_genai/drmcp/tools/clients/atlassian.py +188 -0
  83. datarobot_genai/drmcp/tools/clients/confluence.py +584 -0
  84. datarobot_genai/drmcp/tools/clients/gdrive.py +832 -0
  85. datarobot_genai/drmcp/tools/clients/jira.py +334 -0
  86. datarobot_genai/drmcp/tools/clients/microsoft_graph.py +479 -0
  87. datarobot_genai/drmcp/tools/clients/s3.py +28 -0
  88. datarobot_genai/drmcp/tools/confluence/__init__.py +14 -0
  89. datarobot_genai/drmcp/tools/confluence/tools.py +321 -0
  90. datarobot_genai/drmcp/tools/gdrive/__init__.py +0 -0
  91. datarobot_genai/drmcp/tools/gdrive/tools.py +347 -0
  92. datarobot_genai/drmcp/tools/jira/__init__.py +14 -0
  93. datarobot_genai/drmcp/tools/jira/tools.py +243 -0
  94. datarobot_genai/drmcp/tools/microsoft_graph/__init__.py +13 -0
  95. datarobot_genai/drmcp/tools/microsoft_graph/tools.py +198 -0
  96. datarobot_genai/drmcp/tools/predictive/__init__.py +27 -0
  97. datarobot_genai/drmcp/tools/predictive/data.py +133 -0
  98. datarobot_genai/drmcp/tools/predictive/deployment.py +91 -0
  99. datarobot_genai/drmcp/tools/predictive/deployment_info.py +392 -0
  100. datarobot_genai/drmcp/tools/predictive/model.py +148 -0
  101. datarobot_genai/drmcp/tools/predictive/predict.py +254 -0
  102. datarobot_genai/drmcp/tools/predictive/predict_realtime.py +307 -0
  103. datarobot_genai/drmcp/tools/predictive/project.py +90 -0
  104. datarobot_genai/drmcp/tools/predictive/training.py +661 -0
  105. datarobot_genai/langgraph/__init__.py +0 -0
  106. datarobot_genai/langgraph/agent.py +341 -0
  107. datarobot_genai/langgraph/mcp.py +73 -0
  108. datarobot_genai/llama_index/__init__.py +16 -0
  109. datarobot_genai/llama_index/agent.py +50 -0
  110. datarobot_genai/llama_index/base.py +299 -0
  111. datarobot_genai/llama_index/mcp.py +79 -0
  112. datarobot_genai/nat/__init__.py +0 -0
  113. datarobot_genai/nat/agent.py +275 -0
  114. datarobot_genai/nat/datarobot_auth_provider.py +110 -0
  115. datarobot_genai/nat/datarobot_llm_clients.py +318 -0
  116. datarobot_genai/nat/datarobot_llm_providers.py +130 -0
  117. datarobot_genai/nat/datarobot_mcp_client.py +266 -0
  118. datarobot_genai/nat/helpers.py +87 -0
  119. datarobot_genai/py.typed +0 -0
  120. datarobot_genai-0.2.31.dist-info/METADATA +145 -0
  121. datarobot_genai-0.2.31.dist-info/RECORD +125 -0
  122. datarobot_genai-0.2.31.dist-info/WHEEL +4 -0
  123. datarobot_genai-0.2.31.dist-info/entry_points.txt +5 -0
  124. datarobot_genai-0.2.31.dist-info/licenses/AUTHORS +2 -0
  125. datarobot_genai-0.2.31.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,334 @@
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
+ from http import HTTPStatus
17
+ from typing import Any
18
+
19
+ import httpx
20
+ from pydantic import BaseModel
21
+ from pydantic import Field
22
+
23
+ from .atlassian import ATLASSIAN_API_BASE
24
+ from .atlassian import get_atlassian_cloud_id
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+ RESPONSE_JIRA_ISSUE_FIELDS = {
29
+ "id",
30
+ "key",
31
+ "summary",
32
+ "status",
33
+ "reporter",
34
+ "assignee",
35
+ "created",
36
+ "updated",
37
+ }
38
+ RESPONSE_JIRA_ISSUE_FIELDS_STR = ",".join(RESPONSE_JIRA_ISSUE_FIELDS)
39
+
40
+
41
+ class _IssuePerson(BaseModel):
42
+ email_address: str = Field(alias="emailAddress")
43
+
44
+
45
+ class _IssueStatus(BaseModel):
46
+ name: str
47
+
48
+
49
+ class _IssueFields(BaseModel):
50
+ summary: str
51
+ status: _IssueStatus
52
+ reporter: _IssuePerson
53
+ assignee: _IssuePerson
54
+ created: str
55
+ updated: str
56
+
57
+
58
+ class Issue(BaseModel):
59
+ id: str
60
+ key: str
61
+ fields: _IssueFields
62
+
63
+ def as_flat_dict(self) -> dict[str, Any]:
64
+ return {
65
+ "id": self.id,
66
+ "key": self.key,
67
+ "summary": self.fields.summary,
68
+ "reporterEmailAddress": self.fields.reporter.email_address,
69
+ "assigneeEmailAddress": self.fields.assignee.email_address,
70
+ "created": self.fields.created,
71
+ "updated": self.fields.updated,
72
+ "status": self.fields.status.name,
73
+ }
74
+
75
+
76
+ class JiraClient:
77
+ """
78
+ Client for interacting with Jira API using OAuth access token.
79
+
80
+ At the moment of creating this client, official Jira SDK is not supporting async.
81
+ """
82
+
83
+ def __init__(self, access_token: str) -> None:
84
+ """
85
+ Initialize Jira client with access token.
86
+
87
+ Args:
88
+ access_token: OAuth access token for Atlassian API
89
+ """
90
+ self.access_token = access_token
91
+ self._client = httpx.AsyncClient(
92
+ headers={
93
+ "Authorization": f"Bearer {access_token}",
94
+ "Accept": "application/json",
95
+ "Content-Type": "application/json",
96
+ },
97
+ timeout=30.0,
98
+ )
99
+ self._cloud_id: str | None = None
100
+
101
+ async def _get_cloud_id(self) -> str:
102
+ """
103
+ Get the cloud ID for the authenticated Atlassian Jira instance.
104
+
105
+ According to Atlassian OAuth 2.0 documentation, API calls should use:
106
+ https://api.atlassian.com/ex/jira/{cloudId}/rest/api/3/...
107
+
108
+ Returns
109
+ -------
110
+ Cloud ID string
111
+
112
+ Raises
113
+ ------
114
+ ValueError: If cloud ID cannot be retrieved
115
+ """
116
+ if self._cloud_id:
117
+ return self._cloud_id
118
+
119
+ self._cloud_id = await get_atlassian_cloud_id(self._client, service_type="jira")
120
+ return self._cloud_id
121
+
122
+ async def _get_full_url(self, path: str) -> str:
123
+ """Return URL for Jira API."""
124
+ cloud_id = await self._get_cloud_id()
125
+ return f"{ATLASSIAN_API_BASE}/ex/jira/{cloud_id}/rest/api/3/{path}"
126
+
127
+ async def search_jira_issues(self, jql_query: str, max_results: int) -> list[Issue]:
128
+ """
129
+ Search Jira issues using JQL (Jira Query Language).
130
+
131
+ Args:
132
+ jql_query: JQL Query
133
+ max_results: Maximum number of issues to return
134
+
135
+ Returns
136
+ -------
137
+ List of Jira issues
138
+
139
+ Raises
140
+ ------
141
+ httpx.HTTPStatusError: If the API request fails
142
+ """
143
+ url = await self._get_full_url("search/jql")
144
+ response = await self._client.post(
145
+ url,
146
+ json={
147
+ "jql": jql_query,
148
+ "fields": list(RESPONSE_JIRA_ISSUE_FIELDS),
149
+ "maxResults": max_results,
150
+ },
151
+ )
152
+
153
+ response.raise_for_status()
154
+ raw_issues = response.json().get("issues", [])
155
+ issues = [Issue(**issue) for issue in raw_issues]
156
+ return issues
157
+
158
+ async def get_jira_issue(self, issue_key: str) -> Issue:
159
+ """
160
+ Get a Jira issue by its key.
161
+
162
+ Args:
163
+ issue_key: The key of the Jira issue, e.g., 'PROJ-123'
164
+
165
+ Returns
166
+ -------
167
+ Jira issue
168
+
169
+ Raises
170
+ ------
171
+ httpx.HTTPStatusError: If the API request fails
172
+ """
173
+ url = await self._get_full_url(f"issue/{issue_key}")
174
+ response = await self._client.get(url, params={"fields": RESPONSE_JIRA_ISSUE_FIELDS_STR})
175
+
176
+ if response.status_code == HTTPStatus.NOT_FOUND:
177
+ raise ValueError(f"{issue_key} not found")
178
+
179
+ response.raise_for_status()
180
+ issue = Issue(**response.json())
181
+ return issue
182
+
183
+ async def get_jira_issue_types(self, project_key: str) -> dict[str, str]:
184
+ """
185
+ Get Jira issue types possible for given project.
186
+
187
+ Args:
188
+ project_key: The key of the Jira project, e.g., 'PROJ'
189
+
190
+ Returns
191
+ -------
192
+ Dictionary where key is the issue type name and value is the issue type ID
193
+
194
+ Raises
195
+ ------
196
+ httpx.HTTPStatusError: If the API request fails
197
+ """
198
+ url = await self._get_full_url(f"issue/createmeta/{project_key}/issuetypes")
199
+ response = await self._client.get(url)
200
+
201
+ response.raise_for_status()
202
+ jsoned = response.json()
203
+ issue_types = {
204
+ issue_type["untranslatedName"]: issue_type["id"]
205
+ for issue_type in jsoned.get("issueTypes", [])
206
+ }
207
+ return issue_types
208
+
209
+ async def create_jira_issue(
210
+ self, project_key: str, summary: str, issue_type_id: str, description: str | None
211
+ ) -> str:
212
+ """
213
+ Create Jira issue.
214
+
215
+ Args:
216
+ project_key: The key of the Jira project, e.g., 'PROJ'
217
+ summary: Summary of Jira issue (title), e.g., 'Fix bug abc'
218
+ issue_type_id: ID type of Jira issue, e.g., "1"
219
+ description: Optional description of Jira issue
220
+
221
+ Returns
222
+ -------
223
+ Jira issue key
224
+
225
+ Raises
226
+ ------
227
+ httpx.HTTPStatusError: If the API request fails
228
+ """
229
+ url = await self._get_full_url("issue")
230
+ payload = {
231
+ "fields": {
232
+ "project": {"key": project_key},
233
+ "summary": summary,
234
+ "issuetype": {"id": issue_type_id},
235
+ }
236
+ }
237
+
238
+ if description:
239
+ payload["fields"]["description"] = {
240
+ "content": [
241
+ {"content": [{"text": description, "type": "text"}], "type": "paragraph"}
242
+ ],
243
+ "type": "doc",
244
+ "version": 1,
245
+ }
246
+
247
+ response = await self._client.post(url, json=payload)
248
+
249
+ response.raise_for_status()
250
+ jsoned = response.json()
251
+ return jsoned["key"]
252
+
253
+ async def update_jira_issue(self, issue_key: str, fields: dict[str, Any]) -> list[str]:
254
+ """
255
+ Update Jira issue.
256
+
257
+ Args:
258
+ issue_key: The key of the Jira issue, e.g., 'PROJ-123'
259
+ fields: A dictionary of field names and their new values
260
+ e.g., {'description': 'New content'}
261
+
262
+ Returns
263
+ -------
264
+ List of updated fields
265
+
266
+ Raises
267
+ ------
268
+ httpx.HTTPStatusError: If the API request fails
269
+ """
270
+ url = await self._get_full_url(f"issue/{issue_key}")
271
+ payload = {"fields": fields}
272
+
273
+ response = await self._client.put(url, json=payload)
274
+
275
+ response.raise_for_status()
276
+ return list(fields.keys())
277
+
278
+ async def get_available_jira_transitions(self, issue_key: str) -> dict[str, str]:
279
+ """
280
+ Get Available Jira Transitions.
281
+
282
+ Args:
283
+ issue_key: The key of the Jira issue, e.g., 'PROJ-123'
284
+
285
+ Returns
286
+ -------
287
+ Dictionary where key is the transition name and value is the transition ID
288
+
289
+ Raises
290
+ ------
291
+ httpx.HTTPStatusError: If the API request fails
292
+ """
293
+ url = await self._get_full_url(f"issue/{issue_key}/transitions")
294
+ response = await self._client.get(url)
295
+ response.raise_for_status()
296
+ jsoned = response.json()
297
+ transitions = {
298
+ transition["name"]: transition["id"] for transition in jsoned.get("transitions", [])
299
+ }
300
+ return transitions
301
+
302
+ async def transition_jira_issue(self, issue_key: str, transition_id: str) -> None:
303
+ """
304
+ Transition Jira issue.
305
+
306
+ Args:
307
+ issue_key: The key of the Jira issue, e.g., 'PROJ-123'
308
+ transition_id: Id of target transitionm e.g. '123'.
309
+ Can be obtained from `get_available_jira_transitions`.
310
+
311
+ Returns
312
+ -------
313
+ Nothing
314
+
315
+ Raises
316
+ ------
317
+ httpx.HTTPStatusError: If the API request fails
318
+ """
319
+ url = await self._get_full_url(f"issue/{issue_key}")
320
+ payload = {"transition": {"id": transition_id}}
321
+
322
+ response = await self._client.post(url, json=payload)
323
+
324
+ response.raise_for_status()
325
+
326
+ async def __aenter__(self) -> "JiraClient":
327
+ """Async context manager entry."""
328
+ return self
329
+
330
+ async def __aexit__(
331
+ self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: Any
332
+ ) -> None:
333
+ """Async context manager exit."""
334
+ await self._client.aclose()