datarobot-genai 0.1.71__tar.gz → 0.2.5__tar.gz

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 (112) hide show
  1. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/PKG-INFO +8 -4
  2. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/pyproject.toml +9 -5
  3. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/core/agents/base.py +2 -1
  4. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/core/chat/responses.py +131 -4
  5. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/core/custom_model.py +0 -32
  6. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/core/utils/auth.py +16 -1
  7. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/config.py +52 -0
  8. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/dr_mcp_server.py +45 -8
  9. datarobot_genai-0.2.5/src/datarobot_genai/drmcp/core/dynamic_prompts/dr_lib.py +70 -0
  10. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/dynamic_prompts/register.py +4 -5
  11. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/mcp_instance.py +41 -2
  12. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/routes.py +4 -1
  13. datarobot_genai-0.2.5/src/datarobot_genai/drmcp/core/tool_config.py +95 -0
  14. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/test_utils/mcp_utils_ete.py +29 -0
  15. datarobot_genai-0.2.5/src/datarobot_genai/drmcp/tools/clients/__init__.py +14 -0
  16. datarobot_genai-0.2.5/src/datarobot_genai/drmcp/tools/clients/atlassian.py +188 -0
  17. datarobot_genai-0.2.5/src/datarobot_genai/drmcp/tools/clients/confluence.py +14 -0
  18. datarobot_genai-0.2.5/src/datarobot_genai/drmcp/tools/clients/jira.py +102 -0
  19. datarobot_genai-0.2.5/src/datarobot_genai/drmcp/tools/clients/s3.py +28 -0
  20. datarobot_genai-0.2.5/src/datarobot_genai/drmcp/tools/confluence/__init__.py +14 -0
  21. datarobot_genai-0.2.5/src/datarobot_genai/drmcp/tools/confluence/tools.py +13 -0
  22. datarobot_genai-0.2.5/src/datarobot_genai/drmcp/tools/jira/__init__.py +14 -0
  23. datarobot_genai-0.2.5/src/datarobot_genai/drmcp/tools/jira/tools.py +58 -0
  24. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/tools/predictive/predict.py +1 -1
  25. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/tools/predictive/predict_realtime.py +1 -1
  26. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/langgraph/agent.py +143 -42
  27. datarobot_genai-0.1.71/src/datarobot_genai/drmcp/core/dynamic_prompts/dr_lib.py +0 -128
  28. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/.gitignore +0 -0
  29. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/AUTHORS +0 -0
  30. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/LICENSE +0 -0
  31. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/README.md +0 -0
  32. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/__init__.py +0 -0
  33. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/core/__init__.py +0 -0
  34. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/core/agents/__init__.py +0 -0
  35. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/core/chat/__init__.py +0 -0
  36. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/core/chat/auth.py +0 -0
  37. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/core/chat/client.py +0 -0
  38. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/core/cli/__init__.py +0 -0
  39. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/core/cli/agent_environment.py +0 -0
  40. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/core/cli/agent_kernel.py +0 -0
  41. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/core/mcp/__init__.py +0 -0
  42. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/core/mcp/common.py +0 -0
  43. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/core/telemetry_agent.py +0 -0
  44. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/core/utils/__init__.py +0 -0
  45. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/core/utils/urls.py +0 -0
  46. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/crewai/__init__.py +0 -0
  47. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/crewai/agent.py +0 -0
  48. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/crewai/base.py +0 -0
  49. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/crewai/events.py +0 -0
  50. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/crewai/mcp.py +0 -0
  51. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/__init__.py +0 -0
  52. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/__init__.py +0 -0
  53. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/auth.py +0 -0
  54. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/clients.py +0 -0
  55. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/config_utils.py +0 -0
  56. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/constants.py +0 -0
  57. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/credentials.py +0 -0
  58. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/dr_mcp_server_logo.py +0 -0
  59. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/dynamic_prompts/__init__.py +0 -0
  60. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/dynamic_prompts/controllers.py +0 -0
  61. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/dynamic_prompts/utils.py +0 -0
  62. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/dynamic_tools/__init__.py +0 -0
  63. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/__init__.py +0 -0
  64. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/__init__.py +0 -0
  65. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/base.py +0 -0
  66. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/default.py +0 -0
  67. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/drum.py +0 -0
  68. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/config.py +0 -0
  69. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/controllers.py +0 -0
  70. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/metadata.py +0 -0
  71. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/register.py +0 -0
  72. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/schemas/drum_agentic_fallback_schema.json +0 -0
  73. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/schemas/drum_prediction_fallback_schema.json +0 -0
  74. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/dynamic_tools/register.py +0 -0
  75. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/dynamic_tools/schema.py +0 -0
  76. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/exceptions.py +0 -0
  77. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/logging.py +0 -0
  78. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/mcp_server_tools.py +0 -0
  79. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/memory_management/__init__.py +0 -0
  80. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/memory_management/manager.py +0 -0
  81. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/memory_management/memory_tools.py +0 -0
  82. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/routes_utils.py +0 -0
  83. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/server_life_cycle.py +0 -0
  84. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/telemetry.py +0 -0
  85. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/tool_filter.py +0 -0
  86. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/core/utils.py +0 -0
  87. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/server.py +0 -0
  88. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/test_utils/__init__.py +0 -0
  89. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/test_utils/integration_mcp_server.py +0 -0
  90. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/test_utils/mcp_utils_integration.py +0 -0
  91. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/test_utils/openai_llm_mcp_client.py +0 -0
  92. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/test_utils/tool_base_ete.py +0 -0
  93. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/test_utils/utils.py +0 -0
  94. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/tools/__init__.py +0 -0
  95. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/tools/predictive/__init__.py +0 -0
  96. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/tools/predictive/data.py +0 -0
  97. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/tools/predictive/deployment.py +0 -0
  98. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/tools/predictive/deployment_info.py +0 -0
  99. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/tools/predictive/model.py +0 -0
  100. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/tools/predictive/project.py +0 -0
  101. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/drmcp/tools/predictive/training.py +0 -0
  102. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/langgraph/__init__.py +0 -0
  103. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/langgraph/mcp.py +0 -0
  104. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/llama_index/__init__.py +0 -0
  105. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/llama_index/agent.py +0 -0
  106. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/llama_index/base.py +0 -0
  107. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/llama_index/mcp.py +0 -0
  108. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/nat/__init__.py +0 -0
  109. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/nat/agent.py +0 -0
  110. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/nat/datarobot_llm_clients.py +0 -0
  111. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/nat/datarobot_llm_providers.py +0 -0
  112. {datarobot_genai-0.1.71 → datarobot_genai-0.2.5}/src/datarobot_genai/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datarobot-genai
3
- Version: 0.1.71
3
+ Version: 0.2.5
4
4
  Summary: Generic helpers for GenAI
5
5
  Project-URL: Homepage, https://github.com/datarobot-oss/datarobot-genai
6
6
  Author: DataRobot, Inc.
@@ -8,22 +8,25 @@ License: Apache-2.0
8
8
  License-File: AUTHORS
9
9
  License-File: LICENSE
10
10
  Requires-Python: <3.13,>=3.10
11
+ Requires-Dist: ag-ui-protocol<0.2.0,>=0.1.9
11
12
  Requires-Dist: datarobot-drum<2.0.0,>=1.17.5
12
13
  Requires-Dist: datarobot-predict<2.0.0,>=1.13.2
13
- Requires-Dist: datarobot<4.0.0,>=3.9.1
14
+ Requires-Dist: datarobot<4.0.0,>=3.10.0
14
15
  Requires-Dist: openai<2.0.0,>=1.76.2
15
16
  Requires-Dist: opentelemetry-instrumentation-aiohttp-client<1.0.0,>=0.43b0
16
17
  Requires-Dist: opentelemetry-instrumentation-httpx<1.0.0,>=0.43b0
17
18
  Requires-Dist: opentelemetry-instrumentation-openai<1.0.0,>=0.40.5
18
19
  Requires-Dist: opentelemetry-instrumentation-requests<1.0.0,>=0.43b0
20
+ Requires-Dist: opentelemetry-instrumentation-threading<1.0.0,>=0.43b0
19
21
  Requires-Dist: pandas<3.0.0,>=2.2.3
22
+ Requires-Dist: pyarrow==20.0.0
20
23
  Requires-Dist: pyjwt<3.0.0,>=2.10.1
21
24
  Requires-Dist: pypdf<7.0.0,>=6.1.3
22
25
  Requires-Dist: ragas<0.4.0,>=0.3.8
23
26
  Requires-Dist: requests<3.0.0,>=2.32.4
24
27
  Provides-Extra: crewai
25
28
  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'
29
+ Requires-Dist: crewai>=1.1.0; extra == 'crewai'
27
30
  Requires-Dist: opentelemetry-instrumentation-crewai<1.0.0,>=0.40.5; extra == 'crewai'
28
31
  Requires-Dist: pybase64<2.0.0,>=1.4.2; extra == 'crewai'
29
32
  Provides-Extra: drmcp
@@ -56,8 +59,9 @@ Requires-Dist: llama-index<0.14.0,>=0.13.6; extra == 'llamaindex'
56
59
  Requires-Dist: opentelemetry-instrumentation-llamaindex<1.0.0,>=0.40.5; extra == 'llamaindex'
57
60
  Requires-Dist: pypdf<7.0.0,>=6.0.0; extra == 'llamaindex'
58
61
  Provides-Extra: nat
62
+ Requires-Dist: anyio==4.11.0; extra == 'nat'
63
+ Requires-Dist: crewai>=1.1.0; (python_version >= '3.11') and extra == 'nat'
59
64
  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
65
  Requires-Dist: nvidia-nat-langchain==1.3.0; (python_version >= '3.11') and extra == 'nat'
62
66
  Requires-Dist: nvidia-nat-opentelemetry==1.3.0; (python_version >= '3.11') and extra == 'nat'
63
67
  Requires-Dist: nvidia-nat==1.3.0; (python_version >= '3.11') and extra == 'nat'
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "datarobot-genai"
7
- version = "0.1.71"
7
+ version = "0.2.5"
8
8
  description = "Generic helpers for GenAI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10, <3.13"
@@ -12,7 +12,7 @@ license = { text = "Apache-2.0" }
12
12
  authors = [{ name = "DataRobot, Inc." }]
13
13
  dependencies = [
14
14
  "requests>=2.32.4,<3.0.0",
15
- "datarobot>=3.9.1,<4.0.0",
15
+ "datarobot>=3.10.0,<4.0.0",
16
16
  "datarobot-predict>=1.13.2,<2.0.0",
17
17
  "openai>=1.76.2,<2.0.0",
18
18
  "pandas>=2.2.3,<3.0.0",
@@ -24,6 +24,9 @@ dependencies = [
24
24
  "opentelemetry-instrumentation-aiohttp-client>=0.43b0,<1.0.0",
25
25
  "opentelemetry-instrumentation-httpx>=0.43b0,<1.0.0",
26
26
  "opentelemetry-instrumentation-openai>=0.40.5,<1.0.0",
27
+ "opentelemetry-instrumentation-threading>=0.43b0,<1.0.0",
28
+ "ag-ui-protocol>=0.1.9,<0.2.0",
29
+ "pyarrow==20.0.0",
27
30
  ]
28
31
 
29
32
  [project.urls]
@@ -35,7 +38,7 @@ datarobot_llm_clients = "datarobot_genai.nat.datarobot_llm_clients"
35
38
 
36
39
  [project.optional-dependencies]
37
40
  crewai = [
38
- "crewai>=0.193.2,<1.0.0",
41
+ "crewai>=1.1.0",
39
42
  "crewai-tools[mcp]>=0.69.0,<0.77.0",
40
43
  "opentelemetry-instrumentation-crewai>=0.40.5,<1.0.0",
41
44
  "pybase64>=1.4.2,<2.0.0",
@@ -59,12 +62,13 @@ llamaindex = [
59
62
  nat = [
60
63
  "nvidia-nat==1.3.0; python_version >= '3.11'",
61
64
  "nvidia-nat-opentelemetry==1.3.0; python_version >= '3.11'",
62
- "nvidia-nat-crewai==1.3.0; python_version >= '3.11'",
63
65
  "nvidia-nat-langchain==1.3.0; python_version >= '3.11'",
66
+ "crewai>=1.1.0; python_version >= '3.11'",
64
67
  "llama-index-llms-litellm>=0.4.1,<0.7.0", # Need this to support datarobot-llm plugin
65
68
  "opentelemetry-instrumentation-crewai>=0.40.5,<1.0.0",
66
69
  "opentelemetry-instrumentation-llamaindex>=0.40.5,<1.0.0",
67
70
  "opentelemetry-instrumentation-langchain>=0.40.5,<1.0.0",
71
+ "anyio==4.11.0",
68
72
  ]
69
73
  pydanticai = [
70
74
  "logfire>=4.6.0,<5.0.0",
@@ -104,7 +108,7 @@ include = [
104
108
  [tool.uv]
105
109
  package = true
106
110
  override-dependencies = [
107
- "ragas>=0.3.8",
111
+ "ragas>=0.3.8,<0.4.0",
108
112
  ]
109
113
  exclude-dependencies = [
110
114
  "uv",
@@ -23,6 +23,7 @@ from typing import TypedDict
23
23
  from typing import TypeVar
24
24
  from typing import cast
25
25
 
26
+ from ag_ui.core import Event
26
27
  from openai.types.chat import CompletionCreateParams
27
28
  from ragas import MultiTurnSample
28
29
 
@@ -167,7 +168,7 @@ class UsageMetrics(TypedDict):
167
168
 
168
169
  # Canonical return type for DRUM-compatible invoke implementations
169
170
  InvokeReturn = (
170
- AsyncGenerator[tuple[str, MultiTurnSample | None, UsageMetrics], None]
171
+ AsyncGenerator[tuple[str | Event, MultiTurnSample | None, UsageMetrics], None]
171
172
  | tuple[str, MultiTurnSample | None, UsageMetrics]
172
173
  )
173
174
 
@@ -14,14 +14,23 @@
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
 
30
+ from ag_ui.core import BaseEvent
31
+ from ag_ui.core import Event
32
+ from ag_ui.core import TextMessageChunkEvent
33
+ from ag_ui.core import TextMessageContentEvent
25
34
  from openai.types import CompletionUsage
26
35
  from openai.types.chat import ChatCompletion
27
36
  from openai.types.chat import ChatCompletionChunk
@@ -40,6 +49,7 @@ class CustomModelChatResponse(ChatCompletion):
40
49
 
41
50
  class CustomModelStreamingResponse(ChatCompletionChunk):
42
51
  pipeline_interactions: str | None = None
52
+ event: Event | None = None
43
53
 
44
54
 
45
55
  def to_custom_model_chat_response(
@@ -83,7 +93,7 @@ def to_custom_model_streaming_response(
83
93
  thread_pool_executor: ThreadPoolExecutor,
84
94
  event_loop: AbstractEventLoop,
85
95
  streaming_response_generator: AsyncGenerator[
86
- tuple[str, MultiTurnSample | None, dict[str, int]], None
96
+ tuple[str | Event, MultiTurnSample | None, dict[str, int]], None
87
97
  ],
88
98
  model: str | object | None,
89
99
  ) -> Iterator[CustomModelStreamingResponse]:
@@ -105,7 +115,7 @@ def to_custom_model_streaming_response(
105
115
  while True:
106
116
  try:
107
117
  (
108
- response_text,
118
+ response_text_or_event,
109
119
  pipeline_interactions,
110
120
  usage_metrics,
111
121
  ) = thread_pool_executor.submit(
@@ -114,10 +124,10 @@ def to_custom_model_streaming_response(
114
124
  last_pipeline_interactions = pipeline_interactions
115
125
  last_usage_metrics = usage_metrics
116
126
 
117
- if response_text:
127
+ if isinstance(response_text_or_event, str) and response_text_or_event:
118
128
  choice = ChunkChoice(
119
129
  index=0,
120
- delta=ChoiceDelta(role="assistant", content=response_text),
130
+ delta=ChoiceDelta(role="assistant", content=response_text_or_event),
121
131
  finish_reason=None,
122
132
  )
123
133
  yield CustomModelStreamingResponse(
@@ -130,6 +140,29 @@ def to_custom_model_streaming_response(
130
140
  if usage_metrics
131
141
  else None,
132
142
  )
143
+ elif isinstance(response_text_or_event, BaseEvent):
144
+ content = ""
145
+ if isinstance(
146
+ response_text_or_event, (TextMessageContentEvent, TextMessageChunkEvent)
147
+ ):
148
+ content = response_text_or_event.delta or content
149
+ choice = ChunkChoice(
150
+ index=0,
151
+ delta=ChoiceDelta(role="assistant", content=content),
152
+ finish_reason=None,
153
+ )
154
+
155
+ yield CustomModelStreamingResponse(
156
+ id=completion_id,
157
+ object="chat.completion.chunk",
158
+ created=created,
159
+ model=model,
160
+ choices=[choice],
161
+ usage=CompletionUsage.model_validate(required_usage_metrics | usage_metrics)
162
+ if usage_metrics
163
+ else None,
164
+ event=response_text_or_event,
165
+ )
133
166
  except StopAsyncIteration:
134
167
  break
135
168
  event_loop.run_until_complete(streaming_response_generator.aclose())
@@ -168,3 +201,97 @@ def to_custom_model_streaming_response(
168
201
  choices=[choice],
169
202
  usage=None,
170
203
  )
204
+
205
+
206
+ def streaming_iterator_to_custom_model_streaming_response(
207
+ streaming_response_iterator: Iterator[tuple[str, MultiTurnSample | None, dict[str, int]]],
208
+ model: str | object | None,
209
+ ) -> Iterator[CustomModelStreamingResponse]:
210
+ """Convert the OpenAI ChatCompletionChunk response to CustomModelStreamingResponse."""
211
+ completion_id = str(uuid.uuid4())
212
+ created = int(time.time())
213
+
214
+ last_pipeline_interactions = None
215
+ last_usage_metrics = None
216
+
217
+ while True:
218
+ try:
219
+ (
220
+ response_text,
221
+ pipeline_interactions,
222
+ usage_metrics,
223
+ ) = next(streaming_response_iterator)
224
+ last_pipeline_interactions = pipeline_interactions
225
+ last_usage_metrics = usage_metrics
226
+
227
+ if response_text:
228
+ choice = ChunkChoice(
229
+ index=0,
230
+ delta=ChoiceDelta(role="assistant", content=response_text),
231
+ finish_reason=None,
232
+ )
233
+ yield CustomModelStreamingResponse(
234
+ id=completion_id,
235
+ object="chat.completion.chunk",
236
+ created=created,
237
+ model=model,
238
+ choices=[choice],
239
+ usage=CompletionUsage(**usage_metrics) if usage_metrics else None,
240
+ )
241
+ except StopIteration:
242
+ break
243
+ # Yield final chunk indicating end of stream
244
+ choice = ChunkChoice(
245
+ index=0,
246
+ delta=ChoiceDelta(role="assistant"),
247
+ finish_reason="stop",
248
+ )
249
+ yield CustomModelStreamingResponse(
250
+ id=completion_id,
251
+ object="chat.completion.chunk",
252
+ created=created,
253
+ model=model,
254
+ choices=[choice],
255
+ usage=CompletionUsage(**last_usage_metrics) if last_usage_metrics else None,
256
+ pipeline_interactions=last_pipeline_interactions.model_dump_json()
257
+ if last_pipeline_interactions
258
+ else None,
259
+ )
260
+
261
+
262
+ T = TypeVar("T")
263
+
264
+
265
+ def async_gen_to_sync_thread(
266
+ async_iterator: AsyncIterator[T],
267
+ thread_pool_executor: ThreadPoolExecutor,
268
+ event_loop: asyncio.AbstractEventLoop,
269
+ ) -> Iterator[T]:
270
+ """Run an async iterator in a separate thread and provide a sync iterator."""
271
+ # A thread-safe queue for communication
272
+ sync_queue: queue.Queue[Any] = queue.Queue()
273
+ # A sentinel object to signal the end of the async generator
274
+ SENTINEL = object() # noqa: N806
275
+
276
+ async def run_async_to_queue() -> None:
277
+ """Run in the separate thread's event loop."""
278
+ try:
279
+ async for item in async_iterator:
280
+ sync_queue.put(item)
281
+ except Exception as e:
282
+ # Put the exception on the queue to be re-raised in the main thread
283
+ sync_queue.put(e)
284
+ finally:
285
+ # Signal the end of iteration
286
+ sync_queue.put(SENTINEL)
287
+
288
+ thread_pool_executor.submit(event_loop.run_until_complete, run_async_to_queue()).result()
289
+
290
+ # The main thread consumes items synchronously
291
+ while True:
292
+ item = sync_queue.get()
293
+ if item is SENTINEL:
294
+ break
295
+ if isinstance(item, Exception):
296
+ raise item
297
+ 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
 
@@ -197,6 +197,54 @@ class MCPServerConfig(BaseSettings):
197
197
  description="Enable/disable predictive tools",
198
198
  )
199
199
 
200
+ # Jira tools
201
+ enable_jira_tools: bool = Field(
202
+ default=False,
203
+ validation_alias=AliasChoices(
204
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "ENABLE_JIRA_TOOLS",
205
+ "ENABLE_JIRA_TOOLS",
206
+ ),
207
+ description="Enable/disable Jira tools",
208
+ )
209
+ is_jira_oauth_provider_configured: bool = Field(
210
+ default=False,
211
+ validation_alias=AliasChoices(
212
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "IS_JIRA_OAUTH_PROVIDER_CONFIGURED",
213
+ "IS_JIRA_OAUTH_PROVIDER_CONFIGURED",
214
+ ),
215
+ description="Whether Jira OAuth provider is configured for Jira integration",
216
+ )
217
+
218
+ @property
219
+ def is_jira_oauth_configured(self) -> bool:
220
+ return self.is_jira_oauth_provider_configured or bool(
221
+ os.getenv("JIRA_CLIENT_ID") and os.getenv("JIRA_CLIENT_SECRET")
222
+ )
223
+
224
+ # Confluence tools
225
+ enable_confluence_tools: bool = Field(
226
+ default=False,
227
+ validation_alias=AliasChoices(
228
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "ENABLE_CONFLUENCE_TOOLS",
229
+ "ENABLE_CONFLUENCE_TOOLS",
230
+ ),
231
+ description="Enable/disable Confluence tools",
232
+ )
233
+ is_confluence_oauth_provider_configured: bool = Field(
234
+ default=False,
235
+ validation_alias=AliasChoices(
236
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "IS_CONFLUENCE_OAUTH_PROVIDER_CONFIGURED",
237
+ "IS_CONFLUENCE_OAUTH_PROVIDER_CONFIGURED",
238
+ ),
239
+ description="Whether Confluence OAuth provider is configured for Confluence integration",
240
+ )
241
+
242
+ @property
243
+ def is_confluence_oauth_configured(self) -> bool:
244
+ return self.is_confluence_oauth_provider_configured or bool(
245
+ os.getenv("CONFLUENCE_CLIENT_ID") and os.getenv("CONFLUENCE_CLIENT_SECRET")
246
+ )
247
+
200
248
  @field_validator(
201
249
  "otel_attributes",
202
250
  mode="before",
@@ -220,6 +268,10 @@ class MCPServerConfig(BaseSettings):
220
268
  "tool_registration_duplicate_behavior",
221
269
  "mcp_server_register_dynamic_prompts_on_startup",
222
270
  "enable_predictive_tools",
271
+ "enable_jira_tools",
272
+ "is_jira_oauth_provider_configured",
273
+ "enable_confluence_tools",
274
+ "is_confluence_oauth_provider_configured",
223
275
  mode="before",
224
276
  )
225
277
  @classmethod
@@ -40,6 +40,8 @@ from .routes_utils import prefix_mount_path
40
40
  from .server_life_cycle import BaseServerLifecycle
41
41
  from .telemetry import OtelASGIMiddleware
42
42
  from .telemetry import initialize_telemetry
43
+ from .tool_config import TOOL_CONFIGS
44
+ from .tool_config import is_tool_enabled
43
45
 
44
46
 
45
47
  def _import_modules_from_dir(
@@ -115,6 +117,9 @@ class DataRobotMCPServer:
115
117
  self._mcp = mcp
116
118
  self._mcp_transport = transport
117
119
 
120
+ # Configure MCP server capabilities
121
+ self._configure_mcp_capabilities()
122
+
118
123
  # Initialize telemetry
119
124
  initialize_telemetry(mcp)
120
125
 
@@ -139,11 +144,12 @@ class DataRobotMCPServer:
139
144
 
140
145
  # Load static tools modules
141
146
  base_dir = os.path.dirname(os.path.dirname(__file__))
142
- if self._config.enable_predictive_tools:
143
- _import_modules_from_dir(
144
- os.path.join(base_dir, "tools", "predictive"),
145
- "datarobot_genai.drmcp.tools.predictive",
146
- )
147
+ for tool_type, tool_config in TOOL_CONFIGS.items():
148
+ if is_tool_enabled(tool_type, self._config):
149
+ _import_modules_from_dir(
150
+ os.path.join(base_dir, "tools", tool_config["directory"]),
151
+ tool_config["package_prefix"],
152
+ )
147
153
 
148
154
  # Load memory management tools if available
149
155
  if self._memory_manager:
@@ -163,6 +169,37 @@ class DataRobotMCPServer:
163
169
  if transport == "streamable-http":
164
170
  register_routes(self._mcp)
165
171
 
172
+ def _configure_mcp_capabilities(self) -> None:
173
+ """Configure MCP capabilities that FastMCP doesn't expose directly.
174
+
175
+ See: https://github.com/modelcontextprotocol/python-sdk/issues/1126
176
+ """
177
+ server = self._mcp._mcp_server
178
+
179
+ # Declare prompts_changed capability (capabilities.prompts.listChanged: true)
180
+ server.notification_options.prompts_changed = True
181
+
182
+ # Declare experimental capabilities ( experimental.dynamic_prompts: true)
183
+ server.experimental_capabilities = {"dynamic_prompts": {"enabled": True}}
184
+
185
+ # Patch to include experimental_capabilities (FastMCP doesn't expose this)
186
+ original = server.create_initialization_options
187
+
188
+ def patched(
189
+ notification_options: Any = None,
190
+ experimental_capabilities: dict[str, dict[str, Any]] | None = None,
191
+ **kwargs: Any,
192
+ ) -> Any:
193
+ if experimental_capabilities is None:
194
+ experimental_capabilities = getattr(server, "experimental_capabilities", None)
195
+ return original(
196
+ notification_options=notification_options,
197
+ experimental_capabilities=experimental_capabilities,
198
+ **kwargs,
199
+ )
200
+
201
+ server.create_initialization_options = patched
202
+
166
203
  def run(self, show_banner: bool = False) -> None:
167
204
  """Run the DataRobot MCP server synchronously."""
168
205
  try:
@@ -179,6 +216,9 @@ class DataRobotMCPServer:
179
216
  self._logger.info("Registering dynamic prompts from prompt management...")
180
217
  asyncio.run(register_prompts_from_datarobot_prompt_management())
181
218
 
219
+ # Execute pre-server start actions
220
+ asyncio.run(self._lifecycle.pre_server_start(self._mcp))
221
+
182
222
  # List registered tools, prompts, and resources before starting server
183
223
  tools = asyncio.run(self._mcp._list_tools_mcp())
184
224
  prompts = asyncio.run(self._mcp._list_prompts_mcp())
@@ -198,9 +238,6 @@ class DataRobotMCPServer:
198
238
  for resource in resources:
199
239
  self._logger.info(f" > {resource.name}")
200
240
 
201
- # Execute pre-server start actions
202
- asyncio.run(self._lifecycle.pre_server_start(self._mcp))
203
-
204
241
  # Create event loop for async operations
205
242
  loop = asyncio.new_event_loop()
206
243
  asyncio.set_event_loop(loop)
@@ -0,0 +1,70 @@
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 collections import defaultdict
15
+
16
+ import datarobot as dr
17
+
18
+ from datarobot_genai.drmcp.core.clients import get_api_client
19
+
20
+
21
+ def get_datarobot_prompt_templates() -> list[dr.genai.PromptTemplate]:
22
+ try:
23
+ return dr.genai.PromptTemplate.list()
24
+ except Exception:
25
+ return []
26
+
27
+
28
+ def get_datarobot_prompt_template_versions(
29
+ prompt_template_ids: list[str],
30
+ ) -> dict[str, list[dr.genai.PromptTemplateVersion]]:
31
+ # Still missing in SDK
32
+ prompt_template_versions_data = dr.utils.pagination.unpaginate(
33
+ initial_url="genai/promptTemplates/versions/",
34
+ initial_params={
35
+ "promptTemplateIds": prompt_template_ids,
36
+ },
37
+ client=get_api_client(),
38
+ )
39
+ prompt_template_versions = defaultdict(list)
40
+ for prompt_template_version in prompt_template_versions_data:
41
+ prompt_template_versions[prompt_template_version["promptTemplateId"]].append(
42
+ dr.genai.PromptTemplateVersion(
43
+ id=prompt_template_version["id"],
44
+ prompt_template_id=prompt_template_version["promptTemplateId"],
45
+ prompt_text=prompt_template_version["promptText"],
46
+ commit_comment=prompt_template_version["commitComment"],
47
+ version=prompt_template_version["version"],
48
+ variables=prompt_template_version["variables"],
49
+ creation_date=prompt_template_version["creationDate"],
50
+ creation_user_id=prompt_template_version["creationUserId"],
51
+ user_name=prompt_template_version["userName"],
52
+ )
53
+ )
54
+ return prompt_template_versions
55
+
56
+
57
+ def get_datarobot_prompt_template(prompt_template_id: str) -> dr.genai.PromptTemplate | None:
58
+ try:
59
+ return dr.genai.PromptTemplate.get(prompt_template_id)
60
+ except Exception:
61
+ return None
62
+
63
+
64
+ def get_datarobot_prompt_template_version(
65
+ prompt_template_id: str, prompt_template_version_id: str
66
+ ) -> dr.genai.PromptTemplateVersion | None:
67
+ try:
68
+ return dr.genai.PromptTemplateVersion.get(prompt_template_id, prompt_template_version_id)
69
+ except Exception:
70
+ return None
@@ -18,15 +18,13 @@ from collections.abc import Callable
18
18
  from inspect import Parameter
19
19
  from inspect import Signature
20
20
 
21
+ import datarobot as dr
21
22
  from fastmcp.prompts.prompt import Prompt
22
23
  from pydantic import Field
23
24
 
24
25
  from datarobot_genai.drmcp.core.exceptions import DynamicPromptRegistrationError
25
26
  from datarobot_genai.drmcp.core.mcp_instance import register_prompt
26
27
 
27
- from .dr_lib import DrPrompt
28
- from .dr_lib import DrPromptVersion
29
- from .dr_lib import DrVariable
30
28
  from .dr_lib import get_datarobot_prompt_template_versions
31
29
  from .dr_lib import get_datarobot_prompt_templates
32
30
 
@@ -57,7 +55,8 @@ async def register_prompts_from_datarobot_prompt_management() -> None:
57
55
 
58
56
 
59
57
  async def register_prompt_from_datarobot_prompt_management(
60
- prompt_template: DrPrompt, prompt_template_version: DrPromptVersion | None = None
58
+ prompt_template: dr.genai.PromptTemplate,
59
+ prompt_template_version: dr.genai.PromptTemplateVersion | None = None,
61
60
  ) -> Prompt:
62
61
  """Register a single prompt.
63
62
 
@@ -173,7 +172,7 @@ def to_valid_mcp_prompt_name(s: str) -> str:
173
172
 
174
173
 
175
174
  def make_prompt_function(
176
- name: str, description: str, prompt_text: str, variables: list[DrVariable]
175
+ name: str, description: str, prompt_text: str, variables: list[dr.genai.Variable]
177
176
  ) -> Callable:
178
177
  params = []
179
178
  for v in variables: