datarobot-genai 0.1.71__py3-none-any.whl → 0.1.75__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.
@@ -14,13 +14,18 @@
14
14
 
15
15
  """OpenAI-compatible response helpers for chat interactions."""
16
16
 
17
+ import asyncio
18
+ import queue
17
19
  import time
18
20
  import traceback as tb
19
21
  import uuid
20
22
  from asyncio import AbstractEventLoop
21
23
  from collections.abc import AsyncGenerator
24
+ from collections.abc import AsyncIterator
22
25
  from collections.abc import Iterator
23
26
  from concurrent.futures import ThreadPoolExecutor
27
+ from typing import Any
28
+ from typing import TypeVar
24
29
 
25
30
  from openai.types import CompletionUsage
26
31
  from openai.types.chat import ChatCompletion
@@ -168,3 +173,97 @@ def to_custom_model_streaming_response(
168
173
  choices=[choice],
169
174
  usage=None,
170
175
  )
176
+
177
+
178
+ def streaming_iterator_to_custom_model_streaming_response(
179
+ streaming_response_iterator: Iterator[tuple[str, MultiTurnSample | None, dict[str, int]]],
180
+ model: str | object | None,
181
+ ) -> Iterator[CustomModelStreamingResponse]:
182
+ """Convert the OpenAI ChatCompletionChunk response to CustomModelStreamingResponse."""
183
+ completion_id = str(uuid.uuid4())
184
+ created = int(time.time())
185
+
186
+ last_pipeline_interactions = None
187
+ last_usage_metrics = None
188
+
189
+ while True:
190
+ try:
191
+ (
192
+ response_text,
193
+ pipeline_interactions,
194
+ usage_metrics,
195
+ ) = next(streaming_response_iterator)
196
+ last_pipeline_interactions = pipeline_interactions
197
+ last_usage_metrics = usage_metrics
198
+
199
+ if response_text:
200
+ choice = ChunkChoice(
201
+ index=0,
202
+ delta=ChoiceDelta(role="assistant", content=response_text),
203
+ finish_reason=None,
204
+ )
205
+ yield CustomModelStreamingResponse(
206
+ id=completion_id,
207
+ object="chat.completion.chunk",
208
+ created=created,
209
+ model=model,
210
+ choices=[choice],
211
+ usage=CompletionUsage(**usage_metrics) if usage_metrics else None,
212
+ )
213
+ except StopIteration:
214
+ break
215
+ # Yield final chunk indicating end of stream
216
+ choice = ChunkChoice(
217
+ index=0,
218
+ delta=ChoiceDelta(role="assistant"),
219
+ finish_reason="stop",
220
+ )
221
+ yield CustomModelStreamingResponse(
222
+ id=completion_id,
223
+ object="chat.completion.chunk",
224
+ created=created,
225
+ model=model,
226
+ choices=[choice],
227
+ usage=CompletionUsage(**last_usage_metrics) if last_usage_metrics else None,
228
+ pipeline_interactions=last_pipeline_interactions.model_dump_json()
229
+ if last_pipeline_interactions
230
+ else None,
231
+ )
232
+
233
+
234
+ T = TypeVar("T")
235
+
236
+
237
+ def async_gen_to_sync_thread(
238
+ async_iterator: AsyncIterator[T],
239
+ thread_pool_executor: ThreadPoolExecutor,
240
+ event_loop: asyncio.AbstractEventLoop,
241
+ ) -> Iterator[T]:
242
+ """Run an async iterator in a separate thread and provide a sync iterator."""
243
+ # A thread-safe queue for communication
244
+ sync_queue: queue.Queue[Any] = queue.Queue()
245
+ # A sentinel object to signal the end of the async generator
246
+ SENTINEL = object() # noqa: N806
247
+
248
+ async def run_async_to_queue() -> None:
249
+ """Run in the separate thread's event loop."""
250
+ try:
251
+ async for item in async_iterator:
252
+ sync_queue.put(item)
253
+ except Exception as e:
254
+ # Put the exception on the queue to be re-raised in the main thread
255
+ sync_queue.put(e)
256
+ finally:
257
+ # Signal the end of iteration
258
+ sync_queue.put(SENTINEL)
259
+
260
+ thread_pool_executor.submit(event_loop.run_until_complete, run_async_to_queue()).result()
261
+
262
+ # The main thread consumes items synchronously
263
+ while True:
264
+ item = sync_queue.get()
265
+ if item is SENTINEL:
266
+ break
267
+ if isinstance(item, Exception):
268
+ raise item
269
+ yield item
@@ -26,7 +26,6 @@ from concurrent.futures import ThreadPoolExecutor
26
26
  from typing import Any
27
27
  from typing import Literal
28
28
 
29
- from datarobot_drum import RuntimeParameters
30
29
  from openai.types.chat import CompletionCreateParams
31
30
  from openai.types.chat.completion_create_params import CompletionCreateParamsNonStreaming
32
31
  from openai.types.chat.completion_create_params import CompletionCreateParamsStreaming
@@ -41,26 +40,6 @@ from datarobot_genai.core.telemetry_agent import instrument
41
40
  logger = logging.getLogger(__name__)
42
41
 
43
42
 
44
- def maybe_set_env_from_runtime_parameters(key: str) -> None:
45
- """Set an environment variable from a DRUM Runtime Parameter if it exists.
46
-
47
- This is safe to call outside of the DataRobot runtime. If the parameter is not available,
48
- the function does nothing.
49
- """
50
- runtime_parameter_placeholder_value = "SET_VIA_PULUMI_OR_MANUALLY"
51
- try:
52
- runtime_parameter_value = RuntimeParameters.get(key)
53
- if (
54
- runtime_parameter_value
55
- and len(runtime_parameter_value) > 0
56
- and runtime_parameter_value != runtime_parameter_placeholder_value
57
- ):
58
- os.environ[key] = runtime_parameter_value
59
- except ValueError:
60
- # Local dev: runtime parameters may be unavailable
61
- pass
62
-
63
-
64
43
  def load_model() -> tuple[ThreadPoolExecutor, asyncio.AbstractEventLoop]:
65
44
  """Initialize a dedicated event loop within a worker thread.
66
45
 
@@ -83,7 +62,6 @@ def chat_entrypoint(
83
62
  load_model_result: tuple[ThreadPoolExecutor, asyncio.AbstractEventLoop],
84
63
  *,
85
64
  work_dir: str | None = None,
86
- runtime_parameter_keys: list[str] | None = None,
87
65
  framework: Literal["crewai", "langgraph", "llamaindex", "nat"] | None = None,
88
66
  **kwargs: Any,
89
67
  ) -> CustomModelChatResponse | Iterator[CustomModelStreamingResponse]:
@@ -103,10 +81,6 @@ def chat_entrypoint(
103
81
  work_dir : Optional[str]
104
82
  Working directory to ``chdir`` into before invoking the agent. This is useful
105
83
  when relative paths are used in agent templates.
106
- runtime_parameter_keys : Optional[List[str]]
107
- Runtime parameter keys (DataRobot custom model) to propagate into env. When
108
- ``None``, defaults to
109
- ``['EXTERNAL_MCP_URL', 'MCP_DEPLOYMENT_ID']``.
110
84
  framework : Optional[Literal["crewai", "langgraph", "llamaindex", "nat"]]
111
85
  When provided, idempotently instruments HTTP clients, OpenAI SDK, and the
112
86
  given framework. If omitted, general instrumentation is still applied.
@@ -129,12 +103,6 @@ def chat_entrypoint(
129
103
  except Exception as e:
130
104
  logger.warning(f"Failed to change working directory to {work_dir}: {e}")
131
105
 
132
- # Load MCP runtime parameters and session secret if configured
133
- if runtime_parameter_keys is None:
134
- runtime_parameter_keys = ["EXTERNAL_MCP_URL", "MCP_DEPLOYMENT_ID"]
135
- for key in runtime_parameter_keys:
136
- maybe_set_env_from_runtime_parameters(key)
137
-
138
106
  # Retrieve authorization context using all supported methods for downstream agents/tools
139
107
  completion_create_params["authorization_context"] = resolve_authorization_context(
140
108
  completion_create_params, **kwargs
@@ -74,7 +74,22 @@ class AuthContextHeaderHandler:
74
74
  if algorithm is None:
75
75
  raise ValueError("Algorithm None is not allowed. Use a secure algorithm like HS256.")
76
76
 
77
- self.secret_key = secret_key or AuthContextConfig().session_secret_key
77
+ # Get secret key from parameter, config, or environment variable
78
+ # Handle the case where AuthContextConfig() initialization fails due to
79
+ # a bug in the datarobot package when SESSION_SECRET_KEY is not set
80
+ if secret_key:
81
+ self.secret_key = secret_key
82
+ else:
83
+ try:
84
+ config = AuthContextConfig()
85
+ self.secret_key = config.session_secret_key or ""
86
+ except (TypeError, AttributeError, Exception):
87
+ # Fallback to reading environment variable directly if config initialization fails
88
+ # This can happen when SESSION_SECRET_KEY is not set and the datarobot package's
89
+ # getenv function encounters a bug with None values
90
+ # it tries to check if "apiToken" in payload: when payload is None
91
+ self.secret_key = ""
92
+
78
93
  self.algorithm = algorithm
79
94
  self.validate_signature = validate_signature
80
95
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datarobot-genai
3
- Version: 0.1.71
3
+ Version: 0.1.75
4
4
  Summary: Generic helpers for GenAI
5
5
  Project-URL: Homepage, https://github.com/datarobot-oss/datarobot-genai
6
6
  Author: DataRobot, Inc.
@@ -23,7 +23,7 @@ Requires-Dist: ragas<0.4.0,>=0.3.8
23
23
  Requires-Dist: requests<3.0.0,>=2.32.4
24
24
  Provides-Extra: crewai
25
25
  Requires-Dist: crewai-tools[mcp]<0.77.0,>=0.69.0; extra == 'crewai'
26
- Requires-Dist: crewai<1.0.0,>=0.193.2; extra == 'crewai'
26
+ Requires-Dist: crewai>=1.1.0; extra == 'crewai'
27
27
  Requires-Dist: opentelemetry-instrumentation-crewai<1.0.0,>=0.40.5; extra == 'crewai'
28
28
  Requires-Dist: pybase64<2.0.0,>=1.4.2; extra == 'crewai'
29
29
  Provides-Extra: drmcp
@@ -56,8 +56,8 @@ Requires-Dist: llama-index<0.14.0,>=0.13.6; extra == 'llamaindex'
56
56
  Requires-Dist: opentelemetry-instrumentation-llamaindex<1.0.0,>=0.40.5; extra == 'llamaindex'
57
57
  Requires-Dist: pypdf<7.0.0,>=6.0.0; extra == 'llamaindex'
58
58
  Provides-Extra: nat
59
+ Requires-Dist: crewai>=1.1.0; (python_version >= '3.11') and extra == 'nat'
59
60
  Requires-Dist: llama-index-llms-litellm<0.7.0,>=0.4.1; extra == 'nat'
60
- Requires-Dist: nvidia-nat-crewai==1.3.0; (python_version >= '3.11') and extra == 'nat'
61
61
  Requires-Dist: nvidia-nat-langchain==1.3.0; (python_version >= '3.11') and extra == 'nat'
62
62
  Requires-Dist: nvidia-nat-opentelemetry==1.3.0; (python_version >= '3.11') and extra == 'nat'
63
63
  Requires-Dist: nvidia-nat==1.3.0; (python_version >= '3.11') and extra == 'nat'
@@ -1,21 +1,21 @@
1
1
  datarobot_genai/__init__.py,sha256=QwWCADIZigzGvew8tGT9rAjgQjchSMjaZtFPbi3Bt2s,527
2
2
  datarobot_genai/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  datarobot_genai/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- datarobot_genai/core/custom_model.py,sha256=8SMW3BAToI44331pF6fATQlS-FulDXop1OmmrVxuB1U,7155
4
+ datarobot_genai/core/custom_model.py,sha256=9rSJhKZ1Fo8BpVVLnRMUj3QV2AYUr5ADHEs0aW6s2tY,5813
5
5
  datarobot_genai/core/telemetry_agent.py,sha256=CxvoyResG3jXQ7ucU26NXCzWjWQyua-5qSYvVxpZJQg,5343
6
6
  datarobot_genai/core/agents/__init__.py,sha256=mTG_QVV5aoOWOgVA3KEq7KQLJllyxtG2ZQoq9wiUNYo,1542
7
7
  datarobot_genai/core/agents/base.py,sha256=kyMk9m1qGFT_XretYIEbP0F-vstPIWh_OZlECqWXvdw,6685
8
8
  datarobot_genai/core/chat/__init__.py,sha256=kAxp4Dc-6HIM_cdBl-3IxwzJQr13UYYQ2Zc-hMwz2F8,638
9
9
  datarobot_genai/core/chat/auth.py,sha256=6qITKTHFtESsBc2NsA6cvJf78pPUrcA5XV3Vxlhb5us,5457
10
10
  datarobot_genai/core/chat/client.py,sha256=fk8MebXa8_R33VK0_DrXCS0Fgw3wFvPEvsuubC27c3s,6639
11
- datarobot_genai/core/chat/responses.py,sha256=DEMkSLyDKeCWl_RhWxIfncWWCDrKIZr6HwRkVWC6n3c,5911
11
+ datarobot_genai/core/chat/responses.py,sha256=CCkeRpoG7RDI0-JBFhfSUVNaUOspTeH93q3YTUHpZ4A,9242
12
12
  datarobot_genai/core/cli/__init__.py,sha256=B93Yb6VavoZpatrh8ltCL6YglIfR5FHgytXbO9UuxBw,733
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
16
  datarobot_genai/core/mcp/common.py,sha256=Y8SjuquUODKEfI7T9X-QuTMKdIlpCWFI1b3xs6tmHFA,7812
17
17
  datarobot_genai/core/utils/__init__.py,sha256=VxtRUz6iwb04eFQQy0zqTNXLAkYpPXcJxVoKV0nOdXk,59
18
- datarobot_genai/core/utils/auth.py,sha256=Xo1PxVr6oMgtMHkmHdS02klDKK1cyDpjGvIMF4Tx0Lo,7874
18
+ datarobot_genai/core/utils/auth.py,sha256=LpSoHdPD2siskYwG8q4f9cike4VQdgFWJpuJrpiszXU,8674
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
@@ -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=STzAZ4OF8U-Y_cUTywxmKBGVotwsnbGP6vTojnu6q0g,9921
95
95
  datarobot_genai/nat/datarobot_llm_providers.py,sha256=aDoQcTeGI-odqydPXEX9OGGNFbzAtpqzTvHHEkmJuEQ,4963
96
- datarobot_genai-0.1.71.dist-info/METADATA,sha256=Mt1gNWIU1Jp3Vu6NkXO78vKjjXBnNToA0Vrjj0hLH-I,5918
97
- datarobot_genai-0.1.71.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
98
- datarobot_genai-0.1.71.dist-info/entry_points.txt,sha256=CZhmZcSyt_RBltgLN_b9xasJD6J5SaDc_z7K0wuOY9Y,150
99
- datarobot_genai-0.1.71.dist-info/licenses/AUTHORS,sha256=isJGUXdjq1U7XZ_B_9AH8Qf0u4eX0XyQifJZ_Sxm4sA,80
100
- datarobot_genai-0.1.71.dist-info/licenses/LICENSE,sha256=U2_VkLIktQoa60Nf6Tbt7E4RMlfhFSjWjcJJfVC-YCE,11341
101
- datarobot_genai-0.1.71.dist-info/RECORD,,
96
+ datarobot_genai-0.1.75.dist-info/METADATA,sha256=7tWV-nzYpfF4oJgeUO8oINJ7sf5pkC2fvmIfFQuc3x8,5898
97
+ datarobot_genai-0.1.75.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
98
+ datarobot_genai-0.1.75.dist-info/entry_points.txt,sha256=CZhmZcSyt_RBltgLN_b9xasJD6J5SaDc_z7K0wuOY9Y,150
99
+ datarobot_genai-0.1.75.dist-info/licenses/AUTHORS,sha256=isJGUXdjq1U7XZ_B_9AH8Qf0u4eX0XyQifJZ_Sxm4sA,80
100
+ datarobot_genai-0.1.75.dist-info/licenses/LICENSE,sha256=U2_VkLIktQoa60Nf6Tbt7E4RMlfhFSjWjcJJfVC-YCE,11341
101
+ datarobot_genai-0.1.75.dist-info/RECORD,,