datarobot-genai 0.2.41__tar.gz → 0.2.44__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 (128) hide show
  1. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/PKG-INFO +2 -1
  2. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/pyproject.toml +5 -1
  3. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/core/agents/base.py +5 -2
  4. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/core/chat/responses.py +6 -1
  5. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/crewai/agent.py +12 -4
  6. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/crewai/base.py +4 -1
  7. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/crewai/events.py +11 -4
  8. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/config.py +10 -0
  9. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/tool_config.py +8 -0
  10. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/clients/microsoft_graph.py +96 -0
  11. datarobot_genai-0.2.44/src/datarobot_genai/drmcp/tools/clients/tavily.py +199 -0
  12. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/gdrive/tools.py +2 -4
  13. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/microsoft_graph/tools.py +146 -1
  14. datarobot_genai-0.2.44/src/datarobot_genai/drmcp/tools/tavily/__init__.py +13 -0
  15. datarobot_genai-0.2.44/src/datarobot_genai/drmcp/tools/tavily/tools.py +148 -0
  16. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/langgraph/agent.py +10 -2
  17. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/llama_index/agent.py +12 -5
  18. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/llama_index/base.py +4 -1
  19. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/nat/agent.py +17 -6
  20. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/.gitignore +0 -0
  21. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/AUTHORS +0 -0
  22. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/LICENSE +0 -0
  23. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/README.md +0 -0
  24. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/__init__.py +0 -0
  25. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/core/__init__.py +0 -0
  26. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/core/agents/__init__.py +0 -0
  27. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/core/chat/__init__.py +0 -0
  28. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/core/chat/auth.py +0 -0
  29. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/core/chat/client.py +0 -0
  30. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/core/cli/__init__.py +0 -0
  31. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/core/cli/agent_environment.py +0 -0
  32. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/core/cli/agent_kernel.py +0 -0
  33. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/core/custom_model.py +0 -0
  34. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/core/mcp/__init__.py +0 -0
  35. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/core/mcp/common.py +0 -0
  36. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/core/telemetry_agent.py +0 -0
  37. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/core/utils/__init__.py +0 -0
  38. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/core/utils/auth.py +0 -0
  39. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/core/utils/urls.py +0 -0
  40. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/crewai/__init__.py +0 -0
  41. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/crewai/mcp.py +0 -0
  42. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/__init__.py +0 -0
  43. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/__init__.py +0 -0
  44. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/auth.py +0 -0
  45. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/clients.py +0 -0
  46. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/config_utils.py +0 -0
  47. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/constants.py +0 -0
  48. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/credentials.py +0 -0
  49. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dr_mcp_server.py +0 -0
  50. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dr_mcp_server_logo.py +0 -0
  51. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_prompts/__init__.py +0 -0
  52. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_prompts/controllers.py +0 -0
  53. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_prompts/dr_lib.py +0 -0
  54. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_prompts/register.py +0 -0
  55. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_prompts/utils.py +0 -0
  56. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/__init__.py +0 -0
  57. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/__init__.py +0 -0
  58. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/__init__.py +0 -0
  59. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/base.py +0 -0
  60. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/default.py +0 -0
  61. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/adapters/drum.py +0 -0
  62. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/config.py +0 -0
  63. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/controllers.py +0 -0
  64. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/metadata.py +0 -0
  65. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/register.py +0 -0
  66. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/schemas/drum_agentic_fallback_schema.json +0 -0
  67. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/deployment/schemas/drum_prediction_fallback_schema.json +0 -0
  68. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/register.py +0 -0
  69. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/dynamic_tools/schema.py +0 -0
  70. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/exceptions.py +0 -0
  71. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/logging.py +0 -0
  72. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/mcp_instance.py +0 -0
  73. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/memory_management/__init__.py +0 -0
  74. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/memory_management/manager.py +0 -0
  75. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/memory_management/memory_tools.py +0 -0
  76. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/routes.py +0 -0
  77. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/routes_utils.py +0 -0
  78. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/server_life_cycle.py +0 -0
  79. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/telemetry.py +0 -0
  80. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/tool_filter.py +0 -0
  81. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/core/utils.py +0 -0
  82. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/server.py +0 -0
  83. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/test_utils/__init__.py +0 -0
  84. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/test_utils/clients/__init__.py +0 -0
  85. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/test_utils/clients/anthropic.py +0 -0
  86. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/test_utils/clients/base.py +0 -0
  87. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/test_utils/clients/dr_gateway.py +0 -0
  88. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/test_utils/clients/openai.py +0 -0
  89. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/test_utils/elicitation_test_tool.py +0 -0
  90. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/test_utils/integration_mcp_server.py +0 -0
  91. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/test_utils/mcp_utils_ete.py +0 -0
  92. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/test_utils/mcp_utils_integration.py +0 -0
  93. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/test_utils/test_interactive.py +0 -0
  94. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/test_utils/tool_base_ete.py +0 -0
  95. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/test_utils/utils.py +0 -0
  96. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/__init__.py +0 -0
  97. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/clients/__init__.py +0 -0
  98. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/clients/atlassian.py +0 -0
  99. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/clients/confluence.py +0 -0
  100. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/clients/gdrive.py +0 -0
  101. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/clients/jira.py +0 -0
  102. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/clients/s3.py +0 -0
  103. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/confluence/__init__.py +0 -0
  104. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/confluence/tools.py +0 -0
  105. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/gdrive/__init__.py +0 -0
  106. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/jira/__init__.py +0 -0
  107. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/jira/tools.py +0 -0
  108. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/microsoft_graph/__init__.py +0 -0
  109. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/predictive/__init__.py +0 -0
  110. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/predictive/data.py +0 -0
  111. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/predictive/deployment.py +0 -0
  112. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/predictive/deployment_info.py +0 -0
  113. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/predictive/model.py +0 -0
  114. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/predictive/predict.py +0 -0
  115. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/predictive/predict_realtime.py +0 -0
  116. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/predictive/project.py +0 -0
  117. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/drmcp/tools/predictive/training.py +0 -0
  118. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/langgraph/__init__.py +0 -0
  119. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/langgraph/mcp.py +0 -0
  120. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/llama_index/__init__.py +0 -0
  121. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/llama_index/mcp.py +0 -0
  122. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/nat/__init__.py +0 -0
  123. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/nat/datarobot_auth_provider.py +0 -0
  124. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/nat/datarobot_llm_clients.py +0 -0
  125. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/nat/datarobot_llm_providers.py +0 -0
  126. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/nat/datarobot_mcp_client.py +0 -0
  127. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/src/datarobot_genai/nat/helpers.py +0 -0
  128. {datarobot_genai-0.2.41 → datarobot_genai-0.2.44}/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.2.41
3
+ Version: 0.2.44
4
4
  Summary: Generic helpers for GenAI
5
5
  Project-URL: Homepage, https://github.com/datarobot-oss/datarobot-genai
6
6
  Author: DataRobot, Inc.
@@ -46,6 +46,7 @@ Requires-Dist: opentelemetry-sdk<2.0.0,>=1.22.0; extra == 'drmcp'
46
46
  Requires-Dist: pydantic-settings<3.0.0,>=2.1.0; extra == 'drmcp'
47
47
  Requires-Dist: pydantic<3.0.0,>=2.6.1; extra == 'drmcp'
48
48
  Requires-Dist: python-dotenv<2.0.0,>=1.1.0; extra == 'drmcp'
49
+ Requires-Dist: tavily-python<1.0.0,>=0.7.20; extra == 'drmcp'
49
50
  Provides-Extra: langgraph
50
51
  Requires-Dist: langchain-mcp-adapters<0.2.0,>=0.1.12; extra == 'langgraph'
51
52
  Requires-Dist: langgraph-prebuilt<0.7.0,>=0.2.3; extra == 'langgraph'
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "datarobot-genai"
7
- version = "0.2.41"
7
+ version = "0.2.44"
8
8
  description = "Generic helpers for GenAI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10, <3.13"
@@ -84,6 +84,7 @@ drmcp = [
84
84
  "python-dotenv>=1.1.0,<2.0.0",
85
85
  "boto3>=1.34.0,<2.0.0",
86
86
  "httpx>=0.28.1,<1.0.0",
87
+ "tavily-python>=0.7.20,<1.0.0",
87
88
  "pydantic>=2.6.1,<3.0.0",
88
89
  "pydantic-settings>=2.1.0,<3.0.0",
89
90
  "opentelemetry-api>=1.22.0,<2.0.0",
@@ -187,6 +188,9 @@ fixable = [
187
188
  "I001", # isort import order within functions
188
189
  "PLC0415",# pylint import-outside-toplevel for controlled local imports
189
190
  ]
191
+ "src/datarobot_genai/**/*.py" = [
192
+ "PLC0415",# pylint import-outside-toplevel: allow lazy imports for memory optimization
193
+ ]
190
194
 
191
195
  [tool.ruff.lint.isort]
192
196
  force-single-line = true
@@ -12,6 +12,8 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ from __future__ import annotations
16
+
15
17
  import abc
16
18
  import json
17
19
  import os
@@ -19,6 +21,7 @@ from collections.abc import AsyncGenerator
19
21
  from collections.abc import Mapping
20
22
  from typing import Any
21
23
  from typing import Generic
24
+ from typing import TypeAlias
22
25
  from typing import TypedDict
23
26
  from typing import TypeVar
24
27
  from typing import cast
@@ -98,7 +101,7 @@ class BaseAgent(Generic[TTool], abc.ABC):
98
101
  return get_api_base(self.api_base, deployment_id)
99
102
 
100
103
  @abc.abstractmethod
101
- async def invoke(self, completion_create_params: CompletionCreateParams) -> "InvokeReturn":
104
+ async def invoke(self, completion_create_params: CompletionCreateParams) -> InvokeReturn:
102
105
  raise NotImplementedError("Not implemented")
103
106
 
104
107
  @classmethod
@@ -167,7 +170,7 @@ class UsageMetrics(TypedDict):
167
170
 
168
171
 
169
172
  # Canonical return type for DRUM-compatible invoke implementations
170
- InvokeReturn = (
173
+ InvokeReturn: TypeAlias = (
171
174
  AsyncGenerator[tuple[str | Event, MultiTurnSample | None, UsageMetrics], None]
172
175
  | tuple[str, MultiTurnSample | None, UsageMetrics]
173
176
  )
@@ -14,6 +14,8 @@
14
14
 
15
15
  """OpenAI-compatible response helpers for chat interactions."""
16
16
 
17
+ from __future__ import annotations
18
+
17
19
  import asyncio
18
20
  import queue
19
21
  import time
@@ -24,6 +26,7 @@ from collections.abc import AsyncGenerator
24
26
  from collections.abc import AsyncIterator
25
27
  from collections.abc import Iterator
26
28
  from concurrent.futures import ThreadPoolExecutor
29
+ from typing import TYPE_CHECKING
27
30
  from typing import Any
28
31
  from typing import TypeVar
29
32
 
@@ -38,10 +41,12 @@ from openai.types.chat import ChatCompletionMessage
38
41
  from openai.types.chat.chat_completion import Choice
39
42
  from openai.types.chat.chat_completion_chunk import Choice as ChunkChoice
40
43
  from openai.types.chat.chat_completion_chunk import ChoiceDelta
41
- from ragas import MultiTurnSample
42
44
 
43
45
  from datarobot_genai.core.agents import default_usage_metrics
44
46
 
47
+ if TYPE_CHECKING:
48
+ from ragas import MultiTurnSample
49
+
45
50
 
46
51
  class CustomModelChatResponse(ChatCompletion):
47
52
  pipeline_interactions: str | None = None
@@ -11,15 +11,20 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
+ from __future__ import annotations
15
+
16
+ from typing import TYPE_CHECKING
14
17
 
15
18
  from crewai import LLM
16
- from ragas import MultiTurnSample
17
- from ragas.messages import AIMessage
18
- from ragas.messages import HumanMessage
19
- from ragas.messages import ToolMessage
20
19
 
21
20
  from datarobot_genai.core.utils.urls import get_api_base
22
21
 
22
+ if TYPE_CHECKING:
23
+ from ragas import MultiTurnSample
24
+ from ragas.messages import AIMessage
25
+ from ragas.messages import HumanMessage
26
+ from ragas.messages import ToolMessage
27
+
23
28
 
24
29
  def build_llm(
25
30
  *,
@@ -39,4 +44,7 @@ def create_pipeline_interactions_from_messages(
39
44
  ) -> MultiTurnSample | None:
40
45
  if not messages:
41
46
  return None
47
+ # Lazy import to reduce memory overhead when ragas is not used
48
+ from ragas import MultiTurnSample
49
+
42
50
  return MultiTurnSample(user_input=messages)
@@ -26,13 +26,13 @@ from __future__ import annotations
26
26
  import abc
27
27
  import asyncio
28
28
  from collections.abc import AsyncGenerator
29
+ from typing import TYPE_CHECKING
29
30
  from typing import Any
30
31
 
31
32
  from crewai import Crew
32
33
  from crewai.events.event_bus import CrewAIEventsBus
33
34
  from crewai.tools import BaseTool
34
35
  from openai.types.chat import CompletionCreateParams
35
- from ragas import MultiTurnSample
36
36
 
37
37
  from datarobot_genai.core.agents.base import BaseAgent
38
38
  from datarobot_genai.core.agents.base import InvokeReturn
@@ -44,6 +44,9 @@ from datarobot_genai.core.agents.base import is_streaming
44
44
  from .agent import create_pipeline_interactions_from_messages
45
45
  from .mcp import mcp_tools_context
46
46
 
47
+ if TYPE_CHECKING:
48
+ from ragas import MultiTurnSample
49
+
47
50
 
48
51
  class CrewAIAgent(BaseAgent[BaseTool], abc.ABC):
49
52
  """Abstract base agent for CrewAI workflows.
@@ -16,12 +16,13 @@ from __future__ import annotations
16
16
  import importlib
17
17
  import json
18
18
  import logging
19
+ from typing import TYPE_CHECKING
19
20
  from typing import Any
20
21
 
21
- from ragas.messages import AIMessage
22
- from ragas.messages import HumanMessage
23
- from ragas.messages import ToolCall
24
- from ragas.messages import ToolMessage
22
+ if TYPE_CHECKING:
23
+ from ragas.messages import AIMessage
24
+ from ragas.messages import HumanMessage
25
+ from ragas.messages import ToolMessage
25
26
 
26
27
  # Resolve crewai symbols at runtime to avoid mypy issues with untyped packages
27
28
  try:
@@ -65,6 +66,12 @@ class CrewAIEventListener:
65
66
  self.messages: list[HumanMessage | AIMessage | ToolMessage] = []
66
67
 
67
68
  def setup_listeners(self, crewai_event_bus: Any) -> None:
69
+ # Lazy import to reduce memory overhead when ragas is not used
70
+ from ragas.messages import AIMessage
71
+ from ragas.messages import HumanMessage
72
+ from ragas.messages import ToolCall
73
+ from ragas.messages import ToolMessage
74
+
68
75
  @crewai_event_bus.on(CrewKickoffStartedEvent)
69
76
  def on_crew_execution_started(_: Any, event: Any) -> None:
70
77
  self.messages.append(
@@ -79,6 +79,15 @@ class MCPToolConfig(BaseSettings):
79
79
  description="Enable/disable Sharepoint tools",
80
80
  )
81
81
 
82
+ enable_tavily_tools: bool = Field(
83
+ default=False,
84
+ validation_alias=AliasChoices(
85
+ RUNTIME_PARAM_ENV_VAR_NAME_PREFIX + "ENABLE_TAVILY_TOOLS",
86
+ "ENABLE_TAVILY_TOOLS",
87
+ ),
88
+ description="Enable/disable Tavily search tools",
89
+ )
90
+
82
91
  is_atlassian_oauth_provider_configured: bool = Field(
83
92
  default=False,
84
93
  validation_alias=AliasChoices(
@@ -131,6 +140,7 @@ class MCPToolConfig(BaseSettings):
131
140
  "enable_confluence_tools",
132
141
  "enable_gdrive_tools",
133
142
  "enable_microsoft_graph_tools",
143
+ "enable_tavily_tools",
134
144
  "is_atlassian_oauth_provider_configured",
135
145
  "is_google_oauth_provider_configured",
136
146
  "is_microsoft_oauth_provider_configured",
@@ -31,6 +31,7 @@ class ToolType(str, Enum):
31
31
  CONFLUENCE = "confluence"
32
32
  GDRIVE = "gdrive"
33
33
  MICROSOFT_GRAPH = "microsoft_graph"
34
+ TAVILY = "tavily"
34
35
 
35
36
 
36
37
  class ToolConfig(TypedDict):
@@ -80,6 +81,13 @@ TOOL_CONFIGS: dict[ToolType, ToolConfig] = {
80
81
  package_prefix="datarobot_genai.drmcp.tools.microsoft_graph",
81
82
  config_field_name="enable_microsoft_graph_tools",
82
83
  ),
84
+ ToolType.TAVILY: ToolConfig(
85
+ name="tavily",
86
+ oauth_check=None,
87
+ directory="tavily",
88
+ package_prefix="datarobot_genai.drmcp.tools.tavily",
89
+ config_field_name="enable_tavily_tools",
90
+ ),
83
91
  }
84
92
 
85
93
 
@@ -586,6 +586,102 @@ class MicrosoftGraphClient:
586
586
  f"Microsoft Graph API error {response.status_code}: {response.text}"
587
587
  )
588
588
 
589
+ async def update_item_metadata(
590
+ self,
591
+ item_id: str,
592
+ fields_to_update: dict[str, Any],
593
+ site_id: str | None = None,
594
+ list_id: str | None = None,
595
+ drive_id: str | None = None,
596
+ ) -> dict[str, Any]:
597
+ """Update metadata on a SharePoint list item or OneDrive/SharePoint drive item.
598
+
599
+ For SharePoint list items: Updates custom column values via the fields endpoint.
600
+ For drive items: Updates properties like name and description.
601
+
602
+ Args:
603
+ item_id: The ID of the item to update.
604
+ fields_to_update: Key-value pairs of metadata fields to modify.
605
+ site_id: For SharePoint list items - the site ID.
606
+ list_id: For SharePoint list items - the list ID.
607
+ drive_id: For OneDrive/drive items - the drive ID.
608
+
609
+ Returns
610
+ -------
611
+ The API response containing updated item data.
612
+
613
+ Raises
614
+ ------
615
+ MicrosoftGraphError: If validation fails or the API request fails.
616
+ """
617
+ if not item_id or not item_id.strip():
618
+ raise MicrosoftGraphError("item_id cannot be empty")
619
+ if not fields_to_update:
620
+ raise MicrosoftGraphError("fields_to_update cannot be empty")
621
+
622
+ # Determine the endpoint based on provided parameters
623
+ has_sharepoint_context = site_id is not None and list_id is not None
624
+ has_drive_context = drive_id is not None
625
+
626
+ if has_sharepoint_context and has_drive_context:
627
+ raise MicrosoftGraphError(
628
+ "Cannot specify both SharePoint (site_id + list_id) and OneDrive "
629
+ "(document_library_id) context. Choose one."
630
+ )
631
+
632
+ if not has_sharepoint_context and not has_drive_context:
633
+ raise MicrosoftGraphError(
634
+ "Must specify either SharePoint context (site_id + list_id) or "
635
+ "OneDrive context (document_library_id)."
636
+ )
637
+
638
+ try:
639
+ if has_sharepoint_context:
640
+ # PATCH /sites/{site-id}/lists/{list-id}/items/{item-id}/fields
641
+ url = f"{GRAPH_API_BASE}/sites/{site_id}/lists/{list_id}/items/{item_id}/fields"
642
+ response = await self._client.patch(url, json=fields_to_update)
643
+ else:
644
+ # Drive item: PATCH /drives/{drive-id}/items/{item-id}
645
+ url = f"{GRAPH_API_BASE}/drives/{drive_id}/items/{item_id}"
646
+ response = await self._client.patch(url, json=fields_to_update)
647
+
648
+ response.raise_for_status()
649
+ return response.json()
650
+ except httpx.HTTPStatusError as e:
651
+ raise self._handle_update_metadata_error(e, item_id) from e
652
+
653
+ def _handle_update_metadata_error(
654
+ self,
655
+ error: httpx.HTTPStatusError,
656
+ item_id: str,
657
+ ) -> MicrosoftGraphError:
658
+ """Handle HTTP errors for metadata updates and return appropriate MicrosoftGraphError."""
659
+ status_code = error.response.status_code
660
+ error_msg = f"Failed to update metadata: HTTP {status_code}"
661
+
662
+ if status_code == 400:
663
+ try:
664
+ error_data = error.response.json()
665
+ api_message = error_data.get("error", {}).get("message", "Invalid request")
666
+ error_msg = f"Bad request updating metadata: {api_message}"
667
+ except Exception:
668
+ error_msg = "Bad request: invalid field names or values."
669
+ elif status_code == 401:
670
+ error_msg = "Authentication failed. Access token may be expired or invalid."
671
+ elif status_code == 403:
672
+ error_msg = (
673
+ f"Permission denied: you don't have permission to update item '{item_id}'. "
674
+ "Requires Sites.ReadWrite.All or Files.ReadWrite.All permission."
675
+ )
676
+ elif status_code == 404:
677
+ error_msg = f"Item '{item_id}' not found."
678
+ elif status_code == 409:
679
+ error_msg = f"Conflict: item '{item_id}' may have been modified concurrently."
680
+ elif status_code == 429:
681
+ error_msg = "Rate limit exceeded. Please try again later."
682
+
683
+ return MicrosoftGraphError(error_msg)
684
+
589
685
  async def __aenter__(self) -> "MicrosoftGraphClient":
590
686
  """Async context manager entry."""
591
687
  return self
@@ -0,0 +1,199 @@
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
+ """Tavily API Client and utilities for API key authentication."""
16
+
17
+ import logging
18
+ from typing import Any
19
+ from typing import Literal
20
+
21
+ from fastmcp.exceptions import ToolError
22
+ from fastmcp.server.dependencies import get_http_headers
23
+ from pydantic import BaseModel
24
+ from pydantic import ConfigDict
25
+ from tavily import AsyncTavilyClient
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+ MAX_RESULTS: int = 20
30
+ MAX_CHUNKS_PER_SOURCE: int = 3
31
+
32
+ MAX_RESULTS_DEFAULT: int = 5
33
+ CHUNKS_PER_SOURCE_DEFAULT: int = 1
34
+
35
+
36
+ async def get_tavily_access_token() -> str:
37
+ """
38
+ Get Tavily API key from HTTP headers.
39
+
40
+ Returns
41
+ -------
42
+ API key string
43
+
44
+ Raises
45
+ ------
46
+ ToolError: If API key is not found in headers
47
+ """
48
+ headers = get_http_headers()
49
+
50
+ api_key = headers.get("x-tavily-api-key")
51
+ if api_key:
52
+ return api_key
53
+
54
+ logger.warning("Tavily API key not found in headers")
55
+ raise ToolError(
56
+ "Tavily API key not found in headers. Please provide it via 'x-tavily-api-key' header."
57
+ )
58
+
59
+
60
+ class TavilySearchResult(BaseModel):
61
+ """A single search result from Tavily API."""
62
+
63
+ title: str
64
+ url: str
65
+ content: str
66
+ score: float
67
+
68
+ model_config = ConfigDict(populate_by_name=True)
69
+
70
+ @classmethod
71
+ def from_tavily_sdk(cls, result: dict[str, Any]) -> "TavilySearchResult":
72
+ """Create a TavilySearchResult from Tavily SDK response data."""
73
+ return cls(
74
+ title=result.get("title", ""),
75
+ url=result.get("url", ""),
76
+ content=result.get("content", ""),
77
+ score=result.get("score", 0.0),
78
+ )
79
+
80
+ def as_flat_dict(self) -> dict[str, Any]:
81
+ """Return a flat dictionary representation of the search result."""
82
+ return self.model_dump(by_alias=True)
83
+
84
+
85
+ class TavilyImage(BaseModel):
86
+ """An image result from Tavily API."""
87
+
88
+ url: str
89
+ description: str | None = None
90
+
91
+ model_config = ConfigDict(populate_by_name=True)
92
+
93
+ @classmethod
94
+ def from_tavily_sdk(cls, image: dict[str, Any] | str) -> "TavilyImage":
95
+ """Create a TavilyImage from Tavily SDK response data."""
96
+ if isinstance(image, str):
97
+ return cls(url=image)
98
+ return cls(
99
+ url=image.get("url", ""),
100
+ description=image.get("description"),
101
+ )
102
+
103
+
104
+ class TavilyClient:
105
+ """Client for interacting with Tavily Search API.
106
+
107
+ This is a wrapper around the official tavily-python SDK.
108
+ """
109
+
110
+ def __init__(self, api_key: str) -> None:
111
+ self._client = AsyncTavilyClient(api_key=api_key)
112
+
113
+ async def search(
114
+ self,
115
+ query: str,
116
+ *,
117
+ topic: Literal["general", "news", "finance"] = "general",
118
+ search_depth: Literal["basic", "advanced"] = "basic",
119
+ max_results: int = MAX_RESULTS_DEFAULT,
120
+ time_range: Literal["day", "week", "month", "year"] | None = None,
121
+ include_images: bool = False,
122
+ include_image_descriptions: bool = False,
123
+ chunks_per_source: int = CHUNKS_PER_SOURCE_DEFAULT,
124
+ include_answer: bool = False,
125
+ ) -> dict[str, Any]:
126
+ """
127
+ Perform a web search using Tavily API.
128
+
129
+ Args:
130
+ query: The search query to execute.
131
+ topic: The category of search ("general", "news", or "finance").
132
+ search_depth: The depth of search ("basic" or "advanced").
133
+ max_results: Maximum number of results to return (1-20).
134
+ time_range: Time range filter ("day", "week", "month", "year").
135
+ include_images: Whether to include images in results.
136
+ include_image_descriptions: Whether to include image descriptions.
137
+ chunks_per_source: Maximum content snippets per URL (1-3).
138
+ include_answer: Whether to include an AI-generated answer.
139
+
140
+ Returns
141
+ -------
142
+ Dict with search results from Tavily API.
143
+
144
+ Raises
145
+ ------
146
+ ValueError: If validation fails.
147
+ TavilyInvalidAPIKeyError: If the API key is invalid.
148
+ TavilyUsageLimitExceededError: If usage limit is exceeded.
149
+ TavilyForbiddenError: If access is forbidden.
150
+ TavilyBadRequestError: If the request is malformed.
151
+ """
152
+ # Validate inputs
153
+ if not query:
154
+ raise ValueError("query cannot be empty.")
155
+ if isinstance(query, str) and not query.strip():
156
+ raise ValueError("query cannot be empty.")
157
+ if max_results <= 0:
158
+ raise ValueError("max_results must be greater than 0.")
159
+ if max_results > MAX_RESULTS:
160
+ raise ValueError(f"max_results must be smaller than or equal to {MAX_RESULTS}.")
161
+ if chunks_per_source <= 0:
162
+ raise ValueError("chunks_per_source must be greater than 0.")
163
+ if chunks_per_source > MAX_CHUNKS_PER_SOURCE:
164
+ raise ValueError(
165
+ f"chunks_per_source must be smaller than or equal to {MAX_CHUNKS_PER_SOURCE}."
166
+ )
167
+
168
+ # Clamp values to valid ranges
169
+ max_results = min(max_results, MAX_RESULTS)
170
+ chunks_per_source = min(chunks_per_source, MAX_CHUNKS_PER_SOURCE)
171
+
172
+ # Build search parameters
173
+ search_kwargs: dict[str, Any] = {
174
+ "query": query,
175
+ "topic": topic,
176
+ "search_depth": search_depth,
177
+ "max_results": max_results,
178
+ "include_images": include_images,
179
+ "include_image_descriptions": include_image_descriptions,
180
+ "chunks_per_source": chunks_per_source,
181
+ "include_answer": include_answer,
182
+ }
183
+
184
+ if time_range:
185
+ search_kwargs["time_range"] = time_range
186
+
187
+ return await self._client.search(**search_kwargs)
188
+
189
+ async def __aenter__(self) -> "TavilyClient":
190
+ """Async context manager entry."""
191
+ return self
192
+
193
+ async def __aexit__(
194
+ self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: Any
195
+ ) -> None:
196
+ """Async context manager exit."""
197
+ # AsyncTavilyClient doesn't have a close method, but we keep the context manager
198
+ # pattern for consistency with other clients
199
+ pass
@@ -33,9 +33,7 @@ from datarobot_genai.drmcp.tools.clients.gdrive import get_gdrive_access_token
33
33
  logger = logging.getLogger(__name__)
34
34
 
35
35
 
36
- @dr_mcp_tool(
37
- tags={"google", "gdrive", "list", "search", "files", "find", "contents"}, enabled=False
38
- )
36
+ @dr_mcp_tool(tags={"google", "gdrive", "list", "search", "files", "find", "contents"})
39
37
  async def gdrive_find_contents(
40
38
  *,
41
39
  page_size: Annotated[
@@ -317,7 +315,7 @@ async def gdrive_update_metadata(
317
315
  )
318
316
 
319
317
 
320
- @dr_mcp_tool(tags={"google", "gdrive", "manage", "access", "acl"})
318
+ @dr_mcp_tool(tags={"google", "gdrive", "manage", "access", "acl"}, enabled=False)
321
319
  async def gdrive_manage_access(
322
320
  *,
323
321
  file_id: Annotated[str, "The ID of the file or folder."],