datarobot-genai 0.2.12__py3-none-any.whl → 0.2.14__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.
@@ -21,7 +21,6 @@ from nat.data_models.api_server import ChatRequest
21
21
  from nat.data_models.api_server import ChatResponse
22
22
  from nat.data_models.intermediate_step import IntermediateStep
23
23
  from nat.data_models.intermediate_step import IntermediateStepType
24
- from nat.runtime.loader import load_workflow
25
24
  from nat.utils.type_utils import StrPath
26
25
  from openai.types.chat import CompletionCreateParams
27
26
  from ragas import MultiTurnSample
@@ -34,6 +33,8 @@ from datarobot_genai.core.agents.base import InvokeReturn
34
33
  from datarobot_genai.core.agents.base import UsageMetrics
35
34
  from datarobot_genai.core.agents.base import extract_user_prompt_content
36
35
  from datarobot_genai.core.agents.base import is_streaming
36
+ from datarobot_genai.core.mcp.common import MCPConfig
37
+ from datarobot_genai.nat.helpers import load_workflow
37
38
 
38
39
  logger = logging.getLogger(__name__)
39
40
 
@@ -166,17 +167,24 @@ class NatAgent(BaseAgent[None]):
166
167
  # Print commands may need flush=True to ensure they are displayed in real-time.
167
168
  print("Running agent with user prompt:", chat_request.messages[0].content, flush=True)
168
169
 
170
+ mcp_config = MCPConfig(
171
+ authorization_context=self.authorization_context,
172
+ forwarded_headers=self.forwarded_headers,
173
+ )
174
+ server_config = mcp_config.server_config
175
+ headers = server_config["headers"] if server_config else None
176
+
169
177
  if is_streaming(completion_create_params):
170
178
 
171
179
  async def stream_generator() -> AsyncGenerator[
172
180
  tuple[str, MultiTurnSample | None, UsageMetrics], None
173
181
  ]:
174
- usage_metrics: UsageMetrics = {
182
+ default_usage_metrics: UsageMetrics = {
175
183
  "completion_tokens": 0,
176
184
  "prompt_tokens": 0,
177
185
  "total_tokens": 0,
178
186
  }
179
- async with load_workflow(self.workflow_path) as workflow:
187
+ async with load_workflow(self.workflow_path, headers=headers) as workflow:
180
188
  async with workflow.run(chat_request) as runner:
181
189
  intermediate_future = pull_intermediate_structured()
182
190
  async for result in runner.result_stream():
@@ -188,7 +196,7 @@ class NatAgent(BaseAgent[None]):
188
196
  yield (
189
197
  result_text,
190
198
  None,
191
- usage_metrics,
199
+ default_usage_metrics,
192
200
  )
193
201
 
194
202
  steps = await intermediate_future
@@ -197,6 +205,11 @@ class NatAgent(BaseAgent[None]):
197
205
  for step in steps
198
206
  if step.event_type == IntermediateStepType.LLM_END
199
207
  ]
208
+ usage_metrics: UsageMetrics = {
209
+ "completion_tokens": 0,
210
+ "prompt_tokens": 0,
211
+ "total_tokens": 0,
212
+ }
200
213
  for step in llm_end_steps:
201
214
  if step.usage_info:
202
215
  token_usage = step.usage_info.token_usage
@@ -210,7 +223,7 @@ class NatAgent(BaseAgent[None]):
210
223
  return stream_generator()
211
224
 
212
225
  # Create and invoke the NAT (Nemo Agent Toolkit) Agentic Workflow with the inputs
213
- result, steps = await self.run_nat_workflow(self.workflow_path, chat_request)
226
+ result, steps = await self.run_nat_workflow(self.workflow_path, chat_request, headers)
214
227
 
215
228
  llm_end_steps = [step for step in steps if step.event_type == IntermediateStepType.LLM_END]
216
229
  usage_metrics: UsageMetrics = {
@@ -234,7 +247,7 @@ class NatAgent(BaseAgent[None]):
234
247
  return result_text, pipeline_interactions, usage_metrics
235
248
 
236
249
  async def run_nat_workflow(
237
- self, workflow_path: StrPath, chat_request: ChatRequest
250
+ self, workflow_path: StrPath, chat_request: ChatRequest, headers: dict[str, str] | None
238
251
  ) -> tuple[ChatResponse | str, list[IntermediateStep]]:
239
252
  """Run the NAT workflow with the provided config file and input string.
240
253
 
@@ -247,7 +260,7 @@ class NatAgent(BaseAgent[None]):
247
260
  ChatResponse | str: The result from the NAT workflow
248
261
  list[IntermediateStep]: The list of intermediate steps
249
262
  """
250
- async with load_workflow(workflow_path) as workflow:
263
+ async with load_workflow(workflow_path, headers=headers) as workflow:
251
264
  async with workflow.run(chat_request) as runner:
252
265
  intermediate_future = pull_intermediate_structured()
253
266
  runner_outputs = await runner.result()
@@ -14,6 +14,7 @@
14
14
 
15
15
  from collections.abc import AsyncGenerator
16
16
  from typing import Any
17
+ from typing import TypeVar
17
18
 
18
19
  from crewai import LLM
19
20
  from langchain_openai import ChatOpenAI
@@ -22,12 +23,32 @@ from llama_index.llms.litellm import LiteLLM
22
23
  from nat.builder.builder import Builder
23
24
  from nat.builder.framework_enum import LLMFrameworkEnum
24
25
  from nat.cli.register_workflow import register_llm_client
26
+ from nat.data_models.llm import LLMBaseConfig
27
+ from nat.data_models.retry_mixin import RetryMixin
28
+ from nat.plugins.langchain.llm import (
29
+ _patch_llm_based_on_config as langchain_patch_llm_based_on_config,
30
+ )
31
+ from nat.utils.exception_handlers.automatic_retries import patch_with_retry
25
32
 
26
33
  from ..nat.datarobot_llm_providers import DataRobotLLMComponentModelConfig
27
34
  from ..nat.datarobot_llm_providers import DataRobotLLMDeploymentModelConfig
28
35
  from ..nat.datarobot_llm_providers import DataRobotLLMGatewayModelConfig
29
36
  from ..nat.datarobot_llm_providers import DataRobotNIMModelConfig
30
37
 
38
+ ModelType = TypeVar("ModelType")
39
+
40
+
41
+ def _patch_llm_based_on_config(client: ModelType, llm_config: LLMBaseConfig) -> ModelType:
42
+ if isinstance(llm_config, RetryMixin):
43
+ client = patch_with_retry(
44
+ client,
45
+ retries=llm_config.num_retries,
46
+ retry_codes=llm_config.retry_on_status_codes,
47
+ retry_on_messages=llm_config.retry_on_errors,
48
+ )
49
+
50
+ return client
51
+
31
52
 
32
53
  class DataRobotChatOpenAI(ChatOpenAI):
33
54
  def _get_request_payload(
@@ -77,7 +98,8 @@ async def datarobot_llm_gateway_langchain(
77
98
  config["base_url"] = config["base_url"] + "/genai/llmgw"
78
99
  config["stream_options"] = {"include_usage": True}
79
100
  config["model"] = config["model"].removeprefix("datarobot/")
80
- yield DataRobotChatOpenAI(**config)
101
+ client = DataRobotChatOpenAI(**config)
102
+ yield langchain_patch_llm_based_on_config(client, config)
81
103
 
82
104
 
83
105
  @register_llm_client(
@@ -90,7 +112,8 @@ async def datarobot_llm_gateway_crewai(
90
112
  if not config["model"].startswith("datarobot/"):
91
113
  config["model"] = "datarobot/" + config["model"]
92
114
  config["base_url"] = config["base_url"].removesuffix("/api/v2")
93
- yield LLM(**config)
115
+ client = LLM(**config)
116
+ yield _patch_llm_based_on_config(client, config)
94
117
 
95
118
 
96
119
  @register_llm_client(
@@ -103,7 +126,8 @@ async def datarobot_llm_gateway_llamaindex(
103
126
  if not config["model"].startswith("datarobot/"):
104
127
  config["model"] = "datarobot/" + config["model"]
105
128
  config["api_base"] = config.pop("base_url").removesuffix("/api/v2")
106
- yield DataRobotLiteLLM(**config)
129
+ client = DataRobotLiteLLM(**config)
130
+ yield _patch_llm_based_on_config(client, config)
107
131
 
108
132
 
109
133
  @register_llm_client(
@@ -119,7 +143,8 @@ async def datarobot_llm_deployment_langchain(
119
143
  )
120
144
  config["stream_options"] = {"include_usage": True}
121
145
  config["model"] = config["model"].removeprefix("datarobot/")
122
- yield DataRobotChatOpenAI(**config)
146
+ client = DataRobotChatOpenAI(**config)
147
+ yield langchain_patch_llm_based_on_config(client, config)
123
148
 
124
149
 
125
150
  @register_llm_client(
@@ -136,7 +161,8 @@ async def datarobot_llm_deployment_crewai(
136
161
  if not config["model"].startswith("datarobot/"):
137
162
  config["model"] = "datarobot/" + config["model"]
138
163
  config["api_base"] = config.pop("base_url") + "/chat/completions"
139
- yield LLM(**config)
164
+ client = LLM(**config)
165
+ yield _patch_llm_based_on_config(client, config)
140
166
 
141
167
 
142
168
  @register_llm_client(
@@ -153,7 +179,8 @@ async def datarobot_llm_deployment_llamaindex(
153
179
  if not config["model"].startswith("datarobot/"):
154
180
  config["model"] = "datarobot/" + config["model"]
155
181
  config["api_base"] = config.pop("base_url") + "/chat/completions"
156
- yield DataRobotLiteLLM(**config)
182
+ client = DataRobotLiteLLM(**config)
183
+ yield _patch_llm_based_on_config(client, config)
157
184
 
158
185
 
159
186
  @register_llm_client(config_type=DataRobotNIMModelConfig, wrapper_type=LLMFrameworkEnum.LANGCHAIN)
@@ -167,7 +194,8 @@ async def datarobot_nim_langchain(
167
194
  )
168
195
  config["stream_options"] = {"include_usage": True}
169
196
  config["model"] = config["model"].removeprefix("datarobot/")
170
- yield DataRobotChatOpenAI(**config)
197
+ client = DataRobotChatOpenAI(**config)
198
+ yield langchain_patch_llm_based_on_config(client, config)
171
199
 
172
200
 
173
201
  @register_llm_client(config_type=DataRobotNIMModelConfig, wrapper_type=LLMFrameworkEnum.CREWAI)
@@ -182,7 +210,8 @@ async def datarobot_nim_crewai(
182
210
  if not config["model"].startswith("datarobot/"):
183
211
  config["model"] = "datarobot/" + config["model"]
184
212
  config["api_base"] = config.pop("base_url") + "/chat/completions"
185
- yield LLM(**config)
213
+ client = LLM(**config)
214
+ yield _patch_llm_based_on_config(client, config)
186
215
 
187
216
 
188
217
  @register_llm_client(config_type=DataRobotNIMModelConfig, wrapper_type=LLMFrameworkEnum.LLAMA_INDEX)
@@ -197,7 +226,8 @@ async def datarobot_nim_llamaindex(
197
226
  if not config["model"].startswith("datarobot/"):
198
227
  config["model"] = "datarobot/" + config["model"]
199
228
  config["api_base"] = config.pop("base_url") + "/chat/completions"
200
- yield DataRobotLiteLLM(**config)
229
+ client = DataRobotLiteLLM(**config)
230
+ yield _patch_llm_based_on_config(client, config)
201
231
 
202
232
 
203
233
  @register_llm_client(
@@ -212,7 +242,8 @@ async def datarobot_llm_component_langchain(
212
242
  config["stream_options"] = {"include_usage": True}
213
243
  config["model"] = config["model"].removeprefix("datarobot/")
214
244
  config.pop("use_datarobot_llm_gateway")
215
- yield DataRobotChatOpenAI(**config)
245
+ client = DataRobotChatOpenAI(**config)
246
+ yield langchain_patch_llm_based_on_config(client, config)
216
247
 
217
248
 
218
249
  @register_llm_client(
@@ -229,7 +260,8 @@ async def datarobot_llm_component_crewai(
229
260
  else:
230
261
  config["api_base"] = config.pop("base_url") + "/chat/completions"
231
262
  config.pop("use_datarobot_llm_gateway")
232
- yield LLM(**config)
263
+ client = LLM(**config)
264
+ yield _patch_llm_based_on_config(client, config)
233
265
 
234
266
 
235
267
  @register_llm_client(
@@ -246,4 +278,5 @@ async def datarobot_llm_component_llamaindex(
246
278
  else:
247
279
  config["api_base"] = config.pop("base_url") + "/chat/completions"
248
280
  config.pop("use_datarobot_llm_gateway")
249
- yield DataRobotLiteLLM(**config)
281
+ client = DataRobotLiteLLM(**config)
282
+ yield _patch_llm_based_on_config(client, config)
@@ -0,0 +1,87 @@
1
+ # Copyright 2025 DataRobot, Inc. and its affiliates.
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 collections.abc import AsyncGenerator
16
+ from contextlib import asynccontextmanager
17
+
18
+ from nat.builder.workflow import Workflow
19
+ from nat.builder.workflow_builder import WorkflowBuilder
20
+ from nat.data_models.config import Config
21
+ from nat.runtime.loader import PluginTypes
22
+ from nat.runtime.loader import discover_and_register_plugins
23
+ from nat.runtime.session import SessionManager
24
+ from nat.utils.data_models.schema_validator import validate_schema
25
+ from nat.utils.io.yaml_tools import yaml_load
26
+ from nat.utils.type_utils import StrPath
27
+
28
+
29
+ def load_config(config_file: StrPath, headers: dict[str, str] | None = None) -> Config:
30
+ """
31
+ Load a NAT configuration file with injected headers. It ensures that all plugins are
32
+ loaded and then validates the configuration file against the Config schema.
33
+
34
+ Parameters
35
+ ----------
36
+ config_file : StrPath
37
+ The path to the configuration file
38
+
39
+ Returns
40
+ -------
41
+ Config
42
+ The validated Config object
43
+ """
44
+ # Ensure all of the plugins are loaded
45
+ discover_and_register_plugins(PluginTypes.CONFIG_OBJECT)
46
+
47
+ config_yaml = yaml_load(config_file)
48
+
49
+ add_headers_to_datarobot_mcp_auth(config_yaml, headers)
50
+
51
+ # Validate configuration adheres to NAT schemas
52
+ validated_nat_config = validate_schema(config_yaml, Config)
53
+
54
+ return validated_nat_config
55
+
56
+
57
+ def add_headers_to_datarobot_mcp_auth(config_yaml: dict, headers: dict[str, str] | None) -> None:
58
+ if headers:
59
+ if authentication := config_yaml.get("authentication"):
60
+ for auth_name in authentication:
61
+ auth_config = authentication[auth_name]
62
+ if auth_config.get("_type") == "datarobot_mcp_auth":
63
+ auth_config["headers"] = headers
64
+
65
+
66
+ @asynccontextmanager
67
+ async def load_workflow(
68
+ config_file: StrPath, max_concurrency: int = -1, headers: dict[str, str] | None = None
69
+ ) -> AsyncGenerator[Workflow, None]:
70
+ """
71
+ Load the NAT configuration file and create a Runner object. This is the primary entry point for
72
+ running NAT workflows with injected headers.
73
+
74
+ Parameters
75
+ ----------
76
+ config_file : StrPath
77
+ The path to the configuration file
78
+ max_concurrency : int, optional
79
+ The maximum number of parallel workflow invocations to support. Specifying 0 or -1 will
80
+ allow an unlimited count, by default -1
81
+ """
82
+ # Load the config object
83
+ config = load_config(config_file, headers=headers)
84
+
85
+ # Must yield the workflow function otherwise it cleans up
86
+ async with WorkflowBuilder.from_config(config=config) as workflow:
87
+ yield SessionManager(await workflow.build(), max_concurrency=max_concurrency)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datarobot-genai
3
- Version: 0.2.12
3
+ Version: 0.2.14
4
4
  Summary: Generic helpers for GenAI
5
5
  Project-URL: Homepage, https://github.com/datarobot-oss/datarobot-genai
6
6
  Author: DataRobot, Inc.
@@ -100,14 +100,15 @@ datarobot_genai/llama_index/agent.py,sha256=V6ZsD9GcBDJS-RJo1tJtIHhyW69_78gM6_fO
100
100
  datarobot_genai/llama_index/base.py,sha256=ovcQQtC-djD_hcLrWdn93jg23AmD6NBEj7xtw4a6K6c,14481
101
101
  datarobot_genai/llama_index/mcp.py,sha256=leXqF1C4zhuYEKFwNEfZHY4dsUuGZk3W7KArY-zxVL8,2645
102
102
  datarobot_genai/nat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
103
- datarobot_genai/nat/agent.py,sha256=jDeIS9f-8vGbeLy5gQkSjeuHINx5Fh_4BvXYERsgIIk,10516
103
+ datarobot_genai/nat/agent.py,sha256=DuGrgqt1FzvAE-cRH_P3LTFUlwuClvbVurdwA-RsbuY,11177
104
104
  datarobot_genai/nat/datarobot_auth_provider.py,sha256=Z4NSsrHxK8hUeiqtK_lryHsUuZC74ziNo_FHbsZgtiM,4230
105
- datarobot_genai/nat/datarobot_llm_clients.py,sha256=STzAZ4OF8U-Y_cUTywxmKBGVotwsnbGP6vTojnu6q0g,9921
105
+ datarobot_genai/nat/datarobot_llm_clients.py,sha256=Yu208Ed_p_4P3HdpuM7fYnKcXtimORHpKlWVPyijpU8,11356
106
106
  datarobot_genai/nat/datarobot_llm_providers.py,sha256=aDoQcTeGI-odqydPXEX9OGGNFbzAtpqzTvHHEkmJuEQ,4963
107
107
  datarobot_genai/nat/datarobot_mcp_client.py,sha256=35FzilxNp4VqwBYI0NsOc91-xZm1C-AzWqrOdDy962A,9612
108
- datarobot_genai-0.2.12.dist-info/METADATA,sha256=5DnB86Cp4uSS6x5ZjJqblA40CusRaa9V5Jw0kiGSVig,6301
109
- datarobot_genai-0.2.12.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
110
- datarobot_genai-0.2.12.dist-info/entry_points.txt,sha256=jEW3WxDZ8XIK9-ISmTyt5DbmBb047rFlzQuhY09rGrM,284
111
- datarobot_genai-0.2.12.dist-info/licenses/AUTHORS,sha256=isJGUXdjq1U7XZ_B_9AH8Qf0u4eX0XyQifJZ_Sxm4sA,80
112
- datarobot_genai-0.2.12.dist-info/licenses/LICENSE,sha256=U2_VkLIktQoa60Nf6Tbt7E4RMlfhFSjWjcJJfVC-YCE,11341
113
- datarobot_genai-0.2.12.dist-info/RECORD,,
108
+ datarobot_genai/nat/helpers.py,sha256=Q7E3ADZdtFfS8E6OQPyw2wgA6laQ58N3bhLj5CBWwJs,3265
109
+ datarobot_genai-0.2.14.dist-info/METADATA,sha256=MWpePh1Ditr0AsXs5dR8XuQcur7DynbiFvjkBWY9NvM,6301
110
+ datarobot_genai-0.2.14.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
111
+ datarobot_genai-0.2.14.dist-info/entry_points.txt,sha256=jEW3WxDZ8XIK9-ISmTyt5DbmBb047rFlzQuhY09rGrM,284
112
+ datarobot_genai-0.2.14.dist-info/licenses/AUTHORS,sha256=isJGUXdjq1U7XZ_B_9AH8Qf0u4eX0XyQifJZ_Sxm4sA,80
113
+ datarobot_genai-0.2.14.dist-info/licenses/LICENSE,sha256=U2_VkLIktQoa60Nf6Tbt7E4RMlfhFSjWjcJJfVC-YCE,11341
114
+ datarobot_genai-0.2.14.dist-info/RECORD,,