dao-ai 0.1.13__tar.gz → 0.1.14__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 (304) hide show
  1. {dao_ai-0.1.13 → dao_ai-0.1.14}/PKG-INFO +1 -1
  2. {dao_ai-0.1.13 → dao_ai-0.1.14}/pyproject.toml +1 -1
  3. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/apps/handlers.py +22 -1
  4. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/config.py +55 -33
  5. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/tools/genie.py +72 -20
  6. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/tools/mcp.py +36 -13
  7. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/tools/slack.py +13 -2
  8. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/tools/sql.py +7 -3
  9. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/tools/unity_catalog.py +32 -10
  10. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/tools/vector_search.py +61 -35
  11. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_databricks.py +46 -25
  12. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_reranking.py +8 -5
  13. {dao_ai-0.1.13 → dao_ai-0.1.14}/.gitignore +0 -0
  14. {dao_ai-0.1.13 → dao_ai-0.1.14}/.python-version +0 -0
  15. {dao_ai-0.1.13 → dao_ai-0.1.14}/CHANGELOG.md +0 -0
  16. {dao_ai-0.1.13 → dao_ai-0.1.14}/CONTRIBUTING.md +0 -0
  17. {dao_ai-0.1.13 → dao_ai-0.1.14}/LICENSE +0 -0
  18. {dao_ai-0.1.13 → dao_ai-0.1.14}/Makefile +0 -0
  19. {dao_ai-0.1.13 → dao_ai-0.1.14}/README.md +0 -0
  20. {dao_ai-0.1.13 → dao_ai-0.1.14}/app.yaml +0 -0
  21. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/01_getting_started/README.md +0 -0
  22. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/01_getting_started/minimal.yaml +0 -0
  23. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/02_mcp/README.md +0 -0
  24. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/02_mcp/custom_mcp.yaml +0 -0
  25. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/02_mcp/external_mcp.yaml +0 -0
  26. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/02_mcp/filtered_mcp.yaml +0 -0
  27. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/02_mcp/managed_mcp.yaml +0 -0
  28. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/02_mcp/slack_integration.yaml +0 -0
  29. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/03_reranking/README.md +0 -0
  30. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/03_reranking/vector_search_with_reranking.yaml +0 -0
  31. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/04_genie/README.md +0 -0
  32. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/04_genie/genie_basic.yaml +0 -0
  33. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/04_genie/genie_lru_cache.yaml +0 -0
  34. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/04_genie/genie_semantic_cache.yaml +0 -0
  35. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/04_genie/genie_with_conversation_id.yaml +0 -0
  36. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/05_memory/README.md +0 -0
  37. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/05_memory/conversation_summarization.yaml +0 -0
  38. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/05_memory/in_memory_basic.yaml +0 -0
  39. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/05_memory/lakebase_persistence.yaml +0 -0
  40. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/05_memory/postgres_persistence.yaml +0 -0
  41. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/06_on_behalf_of_user/README.md +0 -0
  42. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/06_on_behalf_of_user/obo_basic.yaml +0 -0
  43. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/07_human_in_the_loop/README.md +0 -0
  44. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/07_human_in_the_loop/human_in_the_loop.yaml +0 -0
  45. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/08_guardrails/README.md +0 -0
  46. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/08_guardrails/guardrails_basic.yaml +0 -0
  47. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/09_structured_output/README.md +0 -0
  48. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/09_structured_output/structured_output.yaml +0 -0
  49. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/10_agent_integrations/README.md +0 -0
  50. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/10_agent_integrations/agent_bricks.yaml +0 -0
  51. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/10_agent_integrations/kasal.yaml +0 -0
  52. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/11_prompt_engineering/README.md +0 -0
  53. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/11_prompt_engineering/prompt_optimization.yaml +0 -0
  54. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/11_prompt_engineering/prompt_registry.yaml +0 -0
  55. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/12_middleware/README.md +0 -0
  56. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/12_middleware/combined_middleware.yaml +0 -0
  57. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/12_middleware/context_management.yaml +0 -0
  58. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/12_middleware/custom_field_validation.yaml +0 -0
  59. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/12_middleware/limit_middleware.yaml +0 -0
  60. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/12_middleware/logging_middleware.yaml +0 -0
  61. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/12_middleware/pii_middleware.yaml +0 -0
  62. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/12_middleware/retry_middleware.yaml +0 -0
  63. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/12_middleware/tool_selector_middleware.yaml +0 -0
  64. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/13_orchestration/README.md +0 -0
  65. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/13_orchestration/supervisor_pattern.yaml +0 -0
  66. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/13_orchestration/swarm_pattern.yaml +0 -0
  67. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/14_basic_tools/README.md +0 -0
  68. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/14_basic_tools/sql_tool_example.yaml +0 -0
  69. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/15_complete_applications/README.md +0 -0
  70. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/15_complete_applications/brick_store.yaml +0 -0
  71. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/15_complete_applications/deep_research.yaml +0 -0
  72. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/15_complete_applications/executive_assistant.yaml +0 -0
  73. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/15_complete_applications/genie_and_genie_mcp.yaml +0 -0
  74. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/15_complete_applications/genie_vector_search_hybrid.yaml +0 -0
  75. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/15_complete_applications/hardware_store.yaml +0 -0
  76. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/15_complete_applications/hardware_store_lakebase.yaml +0 -0
  77. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/15_complete_applications/hardware_store_swarm.yaml +0 -0
  78. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/15_complete_applications/quick_serve_restaurant.yaml +0 -0
  79. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/15_complete_applications/reservations_system.yaml +0 -0
  80. {dao_ai-0.1.13 → dao_ai-0.1.14}/config/examples/README.md +0 -0
  81. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/dais2025/appointments.sql +0 -0
  82. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/dais2025/appointments_data.sql +0 -0
  83. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/dais2025/brand_rep_demo_data.sql +0 -0
  84. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/dais2025/brand_rep_demo_queries.sql +0 -0
  85. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/dais2025/brand_rep_demo_tables.sql +0 -0
  86. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/dais2025/brand_rep_demo_validation.sql +0 -0
  87. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/dais2025/customers.sql +0 -0
  88. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/dais2025/customers_data.sql +0 -0
  89. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/dais2025/dim_stores.sql +0 -0
  90. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/dais2025/dim_stores_data.sql +0 -0
  91. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/dais2025/employee_performance.sql +0 -0
  92. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/dais2025/employee_performance_data.sql +0 -0
  93. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/dais2025/employee_tasks.sql +0 -0
  94. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/dais2025/employee_tasks_data.sql +0 -0
  95. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/dais2025/inventory.sql +0 -0
  96. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/dais2025/inventory_data.sql +0 -0
  97. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/dais2025/managers.sql +0 -0
  98. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/dais2025/managers_data.sql +0 -0
  99. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/dais2025/product_data.sql +0 -0
  100. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/dais2025/products.sql +0 -0
  101. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/dais2025/task_assignments.sql +0 -0
  102. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/hardware_store/inventory.snappy.parquet +0 -0
  103. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/hardware_store/inventory.sql +0 -0
  104. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/hardware_store/products.snappy.parquet +0 -0
  105. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/hardware_store/products.sql +0 -0
  106. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/quick_serve_restaurant/.gitkeep +0 -0
  107. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/quick_serve_restaurant/fulfil_item_orders.sql +0 -0
  108. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/quick_serve_restaurant/items_description.csv +0 -0
  109. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/quick_serve_restaurant/items_description.sql +0 -0
  110. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/quick_serve_restaurant/items_raw.csv +0 -0
  111. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/quick_serve_restaurant/items_raw.sql +0 -0
  112. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/quick_serve_restaurant/orders_raw.csv +0 -0
  113. {dao_ai-0.1.13 → dao_ai-0.1.14}/data/quick_serve_restaurant/orders_raw.sql +0 -0
  114. {dao_ai-0.1.13 → dao_ai-0.1.14}/databricks.yaml.template +0 -0
  115. {dao_ai-0.1.13 → dao_ai-0.1.14}/docs/architecture.md +0 -0
  116. {dao_ai-0.1.13 → dao_ai-0.1.14}/docs/cli-reference.md +0 -0
  117. {dao_ai-0.1.13 → dao_ai-0.1.14}/docs/configuration-reference.md +0 -0
  118. {dao_ai-0.1.13 → dao_ai-0.1.14}/docs/contributing.md +0 -0
  119. {dao_ai-0.1.13 → dao_ai-0.1.14}/docs/examples.md +0 -0
  120. {dao_ai-0.1.13 → dao_ai-0.1.14}/docs/faq.md +0 -0
  121. {dao_ai-0.1.13 → dao_ai-0.1.14}/docs/hardware_store/README.md +0 -0
  122. {dao_ai-0.1.13 → dao_ai-0.1.14}/docs/hardware_store/retail_supervisor.png +0 -0
  123. {dao_ai-0.1.13 → dao_ai-0.1.14}/docs/hardware_store/retail_swarm.png +0 -0
  124. {dao_ai-0.1.13 → dao_ai-0.1.14}/docs/images/genie.png +0 -0
  125. {dao_ai-0.1.13 → dao_ai-0.1.14}/docs/key-capabilities.md +0 -0
  126. {dao_ai-0.1.13 → dao_ai-0.1.14}/docs/python-api.md +0 -0
  127. {dao_ai-0.1.13 → dao_ai-0.1.14}/docs/quick_serve_restaurant/.gitkeep +0 -0
  128. {dao_ai-0.1.13 → dao_ai-0.1.14}/docs/quick_serve_restaurant/quick-serve-restaurant.png +0 -0
  129. {dao_ai-0.1.13 → dao_ai-0.1.14}/docs/why-dao.md +0 -0
  130. {dao_ai-0.1.13 → dao_ai-0.1.14}/environment.yaml +0 -0
  131. {dao_ai-0.1.13 → dao_ai-0.1.14}/examples/dais2025/examples.yaml +0 -0
  132. {dao_ai-0.1.13 → dao_ai-0.1.14}/examples/deep_research/examples.yaml +0 -0
  133. {dao_ai-0.1.13 → dao_ai-0.1.14}/examples/executive_assistant/examples.yaml +0 -0
  134. {dao_ai-0.1.13 → dao_ai-0.1.14}/examples/hardware_store/examples.yaml +0 -0
  135. {dao_ai-0.1.13 → dao_ai-0.1.14}/examples/quick_serve_restaurant/.gitkeep +0 -0
  136. {dao_ai-0.1.13 → dao_ai-0.1.14}/examples/quick_serve_restaurant/examples.yaml +0 -0
  137. {dao_ai-0.1.13 → dao_ai-0.1.14}/functions/dais2025/extract_store_numbers.sql +0 -0
  138. {dao_ai-0.1.13 → dao_ai-0.1.14}/functions/dais2025/find_inventory_by_sku.sql +0 -0
  139. {dao_ai-0.1.13 → dao_ai-0.1.14}/functions/dais2025/find_inventory_by_upc.sql +0 -0
  140. {dao_ai-0.1.13 → dao_ai-0.1.14}/functions/dais2025/find_product_by_sku.sql +0 -0
  141. {dao_ai-0.1.13 → dao_ai-0.1.14}/functions/dais2025/find_product_by_upc.sql +0 -0
  142. {dao_ai-0.1.13 → dao_ai-0.1.14}/functions/dais2025/find_store_by_number.sql +0 -0
  143. {dao_ai-0.1.13 → dao_ai-0.1.14}/functions/dais2025/find_store_inventory_by_sku.sql +0 -0
  144. {dao_ai-0.1.13 → dao_ai-0.1.14}/functions/dais2025/find_store_inventory_by_upc.sql +0 -0
  145. {dao_ai-0.1.13 → dao_ai-0.1.14}/functions/hardware_store/find_inventory_by_sku.sql +0 -0
  146. {dao_ai-0.1.13 → dao_ai-0.1.14}/functions/hardware_store/find_inventory_by_upc.sql +0 -0
  147. {dao_ai-0.1.13 → dao_ai-0.1.14}/functions/hardware_store/find_product_by_sku.sql +0 -0
  148. {dao_ai-0.1.13 → dao_ai-0.1.14}/functions/hardware_store/find_product_by_upc.sql +0 -0
  149. {dao_ai-0.1.13 → dao_ai-0.1.14}/functions/hardware_store/find_store_inventory_by_sku.sql +0 -0
  150. {dao_ai-0.1.13 → dao_ai-0.1.14}/functions/hardware_store/find_store_inventory_by_upc.sql +0 -0
  151. {dao_ai-0.1.13 → dao_ai-0.1.14}/functions/quick_serve_restaurant/.gitkeep +0 -0
  152. {dao_ai-0.1.13 → dao_ai-0.1.14}/functions/quick_serve_restaurant/insert_coffee_order.sql +0 -0
  153. {dao_ai-0.1.13 → dao_ai-0.1.14}/functions/quick_serve_restaurant/lookup_items_by_descriptions.sql +0 -0
  154. {dao_ai-0.1.13 → dao_ai-0.1.14}/functions/quick_serve_restaurant/match_historical_item_order_by_date.sql +0 -0
  155. {dao_ai-0.1.13 → dao_ai-0.1.14}/functions/quick_serve_restaurant/match_item_by_description_and_price.sql +0 -0
  156. {dao_ai-0.1.13 → dao_ai-0.1.14}/notebooks/01_ingest_and_transform.py +0 -0
  157. {dao_ai-0.1.13 → dao_ai-0.1.14}/notebooks/02_provision_vector_search.py +0 -0
  158. {dao_ai-0.1.13 → dao_ai-0.1.14}/notebooks/03_provision_lakebase.py +0 -0
  159. {dao_ai-0.1.13 → dao_ai-0.1.14}/notebooks/04_unity_catalog_tools.py +0 -0
  160. {dao_ai-0.1.13 → dao_ai-0.1.14}/notebooks/05_deploy_agent.py +0 -0
  161. {dao_ai-0.1.13 → dao_ai-0.1.14}/notebooks/06_generate_evaluation_data.py +0 -0
  162. {dao_ai-0.1.13 → dao_ai-0.1.14}/notebooks/07_run_evaluation.py +0 -0
  163. {dao_ai-0.1.13 → dao_ai-0.1.14}/notebooks/08_run_examples.py +0 -0
  164. {dao_ai-0.1.13 → dao_ai-0.1.14}/notebooks/09_evaluate_inferences.py +0 -0
  165. {dao_ai-0.1.13 → dao_ai-0.1.14}/notebooks/10_optimize_prompts.py +0 -0
  166. {dao_ai-0.1.13 → dao_ai-0.1.14}/notebooks/99_scratchpad.py +0 -0
  167. {dao_ai-0.1.13 → dao_ai-0.1.14}/requirements.txt +0 -0
  168. {dao_ai-0.1.13 → dao_ai-0.1.14}/schemas/bundle_config_schema.json +0 -0
  169. {dao_ai-0.1.13 → dao_ai-0.1.14}/schemas/model_config_schema.json +0 -0
  170. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dais2025/__init__.py +0 -0
  171. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dais2025/models.py +0 -0
  172. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dais2025/tools/__init__.py +0 -0
  173. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dais2025/tools/customer.py +0 -0
  174. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dais2025/tools/employee.py +0 -0
  175. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dais2025/tools/executive.py +0 -0
  176. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dais2025/tools/genie.py +0 -0
  177. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dais2025/tools/inventory.py +0 -0
  178. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dais2025/tools/models.py +0 -0
  179. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dais2025/tools/store.py +0 -0
  180. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/__init__.py +0 -0
  181. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/apps/__init__.py +0 -0
  182. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/apps/model_serving.py +0 -0
  183. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/apps/resources.py +0 -0
  184. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/apps/server.py +0 -0
  185. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/catalog.py +0 -0
  186. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/cli.py +0 -0
  187. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/genie/__init__.py +0 -0
  188. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/genie/cache/__init__.py +0 -0
  189. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/genie/cache/base.py +0 -0
  190. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/genie/cache/core.py +0 -0
  191. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/genie/cache/lru.py +0 -0
  192. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/genie/cache/semantic.py +0 -0
  193. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/genie/core.py +0 -0
  194. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/graph.py +0 -0
  195. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/hooks/__init__.py +0 -0
  196. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/hooks/core.py +0 -0
  197. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/logging.py +0 -0
  198. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/memory/__init__.py +0 -0
  199. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/memory/base.py +0 -0
  200. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/memory/core.py +0 -0
  201. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/memory/databricks.py +0 -0
  202. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/memory/postgres.py +0 -0
  203. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/messages.py +0 -0
  204. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/middleware/__init__.py +0 -0
  205. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/middleware/assertions.py +0 -0
  206. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/middleware/base.py +0 -0
  207. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/middleware/context_editing.py +0 -0
  208. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/middleware/core.py +0 -0
  209. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/middleware/guardrails.py +0 -0
  210. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/middleware/human_in_the_loop.py +0 -0
  211. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/middleware/message_validation.py +0 -0
  212. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/middleware/model_call_limit.py +0 -0
  213. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/middleware/model_retry.py +0 -0
  214. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/middleware/pii.py +0 -0
  215. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/middleware/summarization.py +0 -0
  216. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/middleware/tool_call_limit.py +0 -0
  217. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/middleware/tool_retry.py +0 -0
  218. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/middleware/tool_selector.py +0 -0
  219. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/models.py +0 -0
  220. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/nodes.py +0 -0
  221. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/optimization.py +0 -0
  222. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/orchestration/__init__.py +0 -0
  223. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/orchestration/core.py +0 -0
  224. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/orchestration/supervisor.py +0 -0
  225. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/orchestration/swarm.py +0 -0
  226. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/prompts.py +0 -0
  227. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/providers/__init__.py +0 -0
  228. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/providers/base.py +0 -0
  229. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/providers/databricks.py +0 -0
  230. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/state.py +0 -0
  231. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/tools/__init__.py +0 -0
  232. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/tools/agent.py +0 -0
  233. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/tools/core.py +0 -0
  234. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/tools/email.py +0 -0
  235. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/tools/memory.py +0 -0
  236. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/tools/python.py +0 -0
  237. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/tools/search.py +0 -0
  238. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/tools/time.py +0 -0
  239. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/types.py +0 -0
  240. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/utils.py +0 -0
  241. {dao_ai-0.1.13 → dao_ai-0.1.14}/src/dao_ai/vector_search.py +0 -0
  242. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/config/test_model_config.yaml +0 -0
  243. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/conftest.py +0 -0
  244. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/middleware/test_context_editing.py +0 -0
  245. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/middleware/test_model_call_limit.py +0 -0
  246. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/middleware/test_model_retry.py +0 -0
  247. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/middleware/test_pii.py +0 -0
  248. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/middleware/test_tool_call_limit.py +0 -0
  249. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/middleware/test_tool_retry.py +0 -0
  250. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/middleware/test_tool_selector.py +0 -0
  251. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_agent_response_format.py +0 -0
  252. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_assertions_middleware.py +0 -0
  253. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_catalog.py +0 -0
  254. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_chat_history.py +0 -0
  255. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_config.py +0 -0
  256. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_function_parsing.py +0 -0
  257. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_genie.py +0 -0
  258. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_genie_conversation_ids_in_outputs.py +0 -0
  259. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_genie_databricks_integration.py +0 -0
  260. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_genie_room_model.py +0 -0
  261. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_guardrail_retry.py +0 -0
  262. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_hitl_config_model.py +0 -0
  263. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_hitl_responses_agent.py +0 -0
  264. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_hooks.py +0 -0
  265. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_human_in_the_loop.py +0 -0
  266. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_inference.py +0 -0
  267. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_inference_integration.py +0 -0
  268. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_input_output_structure.py +0 -0
  269. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_interrupt_type.py +0 -0
  270. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_llm_interrupt_handling.py +0 -0
  271. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_mcp.py +0 -0
  272. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_mcp_filtering.py +0 -0
  273. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_mcp_filtering_integration.py +0 -0
  274. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_mcp_function_model.py +0 -0
  275. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_message_validation_middleware.py +0 -0
  276. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_messages.py +0 -0
  277. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_models.py +0 -0
  278. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_optimization.py +0 -0
  279. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_postgres_integration.py +0 -0
  280. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_prompt_optimizations.py +0 -0
  281. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_prompts.py +0 -0
  282. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_reranking_integration.py +0 -0
  283. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_resources_model_genie_integration.py +0 -0
  284. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_response_format.py +0 -0
  285. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_responses_agent_structured_output_unit.py +0 -0
  286. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_semantic_cache_context.py +0 -0
  287. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_sql_tool.py +0 -0
  288. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_sql_tool_integration.py +0 -0
  289. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_state.py +0 -0
  290. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_summarization_inference.py +0 -0
  291. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_swarm_middleware.py +0 -0
  292. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_tools.py +0 -0
  293. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_types.py +0 -0
  294. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_unity_catalog.py +0 -0
  295. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_utils.py +0 -0
  296. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_utils_type_from_fqn.py +0 -0
  297. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_vector_search.py +0 -0
  298. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/test_warehouse_model.py +0 -0
  299. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/dao_ai/weather_server_mcp.py +0 -0
  300. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/hardware_store/.gitkeep +0 -0
  301. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/hardware_store/test_graph.py +0 -0
  302. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/images/doritos_upc.png +0 -0
  303. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/images/lays_upc.png +0 -0
  304. {dao_ai-0.1.13 → dao_ai-0.1.14}/tests/quick_serve_restaurant/.gitkeep +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dao-ai
3
- Version: 0.1.13
3
+ Version: 0.1.14
4
4
  Summary: DAO AI: A modular, multi-agent orchestration framework for complex AI workflows. Supports agent handoff, tool integration, and dynamic configuration via YAML.
5
5
  Project-URL: Homepage, https://github.com/natefleming/dao-ai
6
6
  Project-URL: Documentation, https://natefleming.github.io/dao-ai
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "dao-ai"
7
- version = "0.1.13"
7
+ version = "0.1.14"
8
8
  description = "DAO AI: A modular, multi-agent orchestration framework for complex AI workflows. Supports agent handoff, tool integration, and dynamic configuration via YAML."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -14,7 +14,7 @@ from typing import AsyncGenerator
14
14
 
15
15
  import mlflow
16
16
  from dotenv import load_dotenv
17
- from mlflow.genai.agent_server import invoke, stream
17
+ from mlflow.genai.agent_server import get_request_headers, invoke, stream
18
18
  from mlflow.types.responses import (
19
19
  ResponsesAgentRequest,
20
20
  ResponsesAgentResponse,
@@ -25,6 +25,23 @@ from dao_ai.config import AppConfig
25
25
  from dao_ai.logging import configure_logging
26
26
  from dao_ai.models import LanggraphResponsesAgent
27
27
 
28
+
29
+ def _inject_headers_into_request(request: ResponsesAgentRequest) -> None:
30
+ """Inject request headers into custom_inputs for Context propagation.
31
+
32
+ Captures headers from the MLflow AgentServer context (where they're available)
33
+ and injects them into request.custom_inputs.configurable.headers so they
34
+ flow through to Context and can be used for OBO authentication.
35
+ """
36
+ headers: dict[str, str] = get_request_headers()
37
+ if headers:
38
+ if request.custom_inputs is None:
39
+ request.custom_inputs = {}
40
+ if "configurable" not in request.custom_inputs:
41
+ request.custom_inputs["configurable"] = {}
42
+ request.custom_inputs["configurable"]["headers"] = headers
43
+
44
+
28
45
  # Load environment variables from .env.local if it exists
29
46
  load_dotenv(dotenv_path=".env.local", override=True)
30
47
 
@@ -61,6 +78,8 @@ async def non_streaming(request: ResponsesAgentRequest) -> ResponsesAgentRespons
61
78
  Returns:
62
79
  ResponsesAgentResponse with the complete output
63
80
  """
81
+ # Capture headers while in the AgentServer async context (before they're lost)
82
+ _inject_headers_into_request(request)
64
83
  return await _responses_agent.apredict(request)
65
84
 
66
85
 
@@ -80,5 +99,7 @@ async def streaming(
80
99
  Yields:
81
100
  ResponsesAgentStreamEvent objects as they are generated
82
101
  """
102
+ # Capture headers while in the AgentServer async context (before they're lost)
103
+ _inject_headers_into_request(request)
83
104
  async for event in _responses_agent.apredict_stream(request):
84
105
  yield event
@@ -7,6 +7,7 @@ from enum import Enum
7
7
  from os import PathLike
8
8
  from pathlib import Path
9
9
  from typing import (
10
+ TYPE_CHECKING,
10
11
  Any,
11
12
  Callable,
12
13
  Iterator,
@@ -18,6 +19,9 @@ from typing import (
18
19
  Union,
19
20
  )
20
21
 
22
+ if TYPE_CHECKING:
23
+ from dao_ai.state import Context
24
+
21
25
  from databricks.sdk import WorkspaceClient
22
26
  from databricks.sdk.credentials_provider import (
23
27
  CredentialsStrategy,
@@ -284,8 +288,8 @@ class IsDatabricksResource(ABC, BaseModel):
284
288
 
285
289
  Authentication priority:
286
290
  1. On-Behalf-Of User (on_behalf_of_user=True):
287
- - Forwarded headers (Databricks Apps)
288
- - ModelServingUserCredentials (Model Serving)
291
+ - Uses ModelServingUserCredentials (Model Serving)
292
+ - For Databricks Apps with headers, use workspace_client_from(context)
289
293
  2. Service Principal (client_id + client_secret + workspace_host)
290
294
  3. PAT (pat + workspace_host)
291
295
  4. Ambient/default authentication
@@ -294,37 +298,6 @@ class IsDatabricksResource(ABC, BaseModel):
294
298
 
295
299
  # Check for OBO first (highest priority)
296
300
  if self.on_behalf_of_user:
297
- # In Databricks Apps, use forwarded headers for per-user auth
298
- from mlflow.genai.agent_server import get_request_headers
299
-
300
- headers = get_request_headers()
301
- logger.debug(f"Headers received: {list(headers.keys())}")
302
- # Try both lowercase and title-case header names (HTTP headers are case-insensitive)
303
- forwarded_token = headers.get("x-forwarded-access-token") or headers.get(
304
- "X-Forwarded-Access-Token"
305
- )
306
-
307
- if forwarded_token:
308
- forwarded_user = headers.get("x-forwarded-user") or headers.get(
309
- "X-Forwarded-User", "unknown"
310
- )
311
- logger.debug(
312
- f"Creating WorkspaceClient for {self.__class__.__name__} "
313
- f"with OBO using forwarded token from Databricks Apps",
314
- forwarded_user=forwarded_user,
315
- )
316
- # Use workspace_host if configured, otherwise SDK will auto-detect
317
- workspace_host_value: str | None = (
318
- normalize_host(value_of(self.workspace_host))
319
- if self.workspace_host
320
- else None
321
- )
322
- return WorkspaceClient(
323
- host=workspace_host_value,
324
- token=forwarded_token,
325
- auth_type="pat",
326
- )
327
-
328
301
  credentials_strategy: CredentialsStrategy = ModelServingUserCredentials()
329
302
  logger.debug(
330
303
  f"Creating WorkspaceClient for {self.__class__.__name__} "
@@ -383,6 +356,55 @@ class IsDatabricksResource(ABC, BaseModel):
383
356
  )
384
357
  return WorkspaceClient()
385
358
 
359
+ def workspace_client_from(self, context: "Context | None") -> WorkspaceClient:
360
+ """
361
+ Get a WorkspaceClient using headers from the provided Context.
362
+
363
+ Use this method from tools that have access to ToolRuntime[Context].
364
+ This allows OBO authentication to work in Databricks Apps where headers
365
+ are captured at request entry and passed through the Context.
366
+
367
+ Args:
368
+ context: Runtime context containing headers for OBO auth.
369
+ If None or no headers, falls back to workspace_client property.
370
+
371
+ Returns:
372
+ WorkspaceClient configured with appropriate authentication.
373
+ """
374
+ from dao_ai.utils import normalize_host
375
+
376
+ # Check if we have headers in context for OBO
377
+ if context and context.headers and self.on_behalf_of_user:
378
+ headers = context.headers
379
+ # Try both lowercase and title-case header names (HTTP headers are case-insensitive)
380
+ forwarded_token = headers.get("x-forwarded-access-token") or headers.get(
381
+ "X-Forwarded-Access-Token"
382
+ )
383
+
384
+ if forwarded_token:
385
+ forwarded_user = headers.get("x-forwarded-user") or headers.get(
386
+ "X-Forwarded-User", "unknown"
387
+ )
388
+ logger.debug(
389
+ f"Creating WorkspaceClient for {self.__class__.__name__} "
390
+ f"with OBO using forwarded token from Context",
391
+ forwarded_user=forwarded_user,
392
+ )
393
+ # Use workspace_host if configured, otherwise SDK will auto-detect
394
+ workspace_host_value: str | None = (
395
+ normalize_host(value_of(self.workspace_host))
396
+ if self.workspace_host
397
+ else None
398
+ )
399
+ return WorkspaceClient(
400
+ host=workspace_host_value,
401
+ token=forwarded_token,
402
+ auth_type="pat",
403
+ )
404
+
405
+ # Fall back to existing workspace_client property
406
+ return self.workspace_client
407
+
386
408
 
387
409
  class DeploymentTarget(str, Enum):
388
410
  """Target platform for agent deployment."""
@@ -139,29 +139,53 @@ Returns:
139
139
  GenieResponse: A response object containing the conversation ID and result from Genie."""
140
140
  tool_description = tool_description + function_docs
141
141
 
142
- genie: Genie = Genie(
143
- space_id=space_id,
144
- client=genie_room.workspace_client,
145
- truncate_results=truncate_results,
146
- )
142
+ # Cache for genie service - created lazily on first call
143
+ # This allows us to use workspace_client_from with runtime context for OBO
144
+ _cached_genie_service: GenieServiceBase | None = None
145
+
146
+ def _get_genie_service(context: Context | None) -> GenieServiceBase:
147
+ """Get or create the Genie service, using context for OBO auth if available."""
148
+ nonlocal _cached_genie_service
149
+
150
+ # Use cached service if available (for non-OBO or after first call)
151
+ # For OBO, we need fresh workspace client each time to use the user's token
152
+ if _cached_genie_service is not None and not genie_room.on_behalf_of_user:
153
+ return _cached_genie_service
154
+
155
+ # Get workspace client using context for OBO support
156
+ from databricks.sdk import WorkspaceClient
147
157
 
148
- genie_service: GenieServiceBase = GenieService(genie)
149
-
150
- # Wrap with semantic cache first (checked second due to decorator pattern)
151
- if semantic_cache_parameters is not None:
152
- genie_service = SemanticCacheService(
153
- impl=genie_service,
154
- parameters=semantic_cache_parameters,
155
- workspace_client=genie_room.workspace_client, # Pass workspace client for conversation history
156
- ).initialize() # Eagerly initialize to fail fast and create table
157
-
158
- # Wrap with LRU cache last (checked first - fast O(1) exact match)
159
- if lru_cache_parameters is not None:
160
- genie_service = LRUCacheService(
161
- impl=genie_service,
162
- parameters=lru_cache_parameters,
158
+ workspace_client: WorkspaceClient = genie_room.workspace_client_from(context)
159
+
160
+ genie: Genie = Genie(
161
+ space_id=space_id,
162
+ client=workspace_client,
163
+ truncate_results=truncate_results,
163
164
  )
164
165
 
166
+ genie_service: GenieServiceBase = GenieService(genie)
167
+
168
+ # Wrap with semantic cache first (checked second due to decorator pattern)
169
+ if semantic_cache_parameters is not None:
170
+ genie_service = SemanticCacheService(
171
+ impl=genie_service,
172
+ parameters=semantic_cache_parameters,
173
+ workspace_client=workspace_client,
174
+ ).initialize()
175
+
176
+ # Wrap with LRU cache last (checked first - fast O(1) exact match)
177
+ if lru_cache_parameters is not None:
178
+ genie_service = LRUCacheService(
179
+ impl=genie_service,
180
+ parameters=lru_cache_parameters,
181
+ )
182
+
183
+ # Cache for non-OBO scenarios
184
+ if not genie_room.on_behalf_of_user:
185
+ _cached_genie_service = genie_service
186
+
187
+ return genie_service
188
+
165
189
  @tool(
166
190
  name_or_callable=tool_name,
167
191
  description=tool_description,
@@ -177,6 +201,10 @@ GenieResponse: A response object containing the conversation ID and result from
177
201
  # Access state through runtime
178
202
  state: AgentState = runtime.state
179
203
  tool_call_id: str = runtime.tool_call_id
204
+ context: Context | None = runtime.context
205
+
206
+ # Get genie service with OBO support via context
207
+ genie_service: GenieServiceBase = _get_genie_service(context)
180
208
 
181
209
  # Ensure space_id is a string for state keys
182
210
  space_id_str: str = str(space_id)
@@ -194,6 +222,14 @@ GenieResponse: A response object containing the conversation ID and result from
194
222
  conversation_id=existing_conversation_id,
195
223
  )
196
224
 
225
+ # Log the prompt being sent to Genie
226
+ logger.trace(
227
+ "Sending prompt to Genie",
228
+ space_id=space_id_str,
229
+ conversation_id=existing_conversation_id,
230
+ prompt=question[:500] + "..." if len(question) > 500 else question,
231
+ )
232
+
197
233
  # Call ask_question which always returns CacheResult with cache metadata
198
234
  cache_result: CacheResult = genie_service.ask_question(
199
235
  question, conversation_id=existing_conversation_id
@@ -211,6 +247,22 @@ GenieResponse: A response object containing the conversation ID and result from
211
247
  cache_key=cache_key,
212
248
  )
213
249
 
250
+ # Log truncated response for debugging
251
+ result_preview: str = str(genie_response.result)
252
+ if len(result_preview) > 500:
253
+ result_preview = result_preview[:500] + "..."
254
+ logger.trace(
255
+ "Genie response content",
256
+ question=question[:100] + "..." if len(question) > 100 else question,
257
+ query=genie_response.query,
258
+ description=(
259
+ genie_response.description[:200] + "..."
260
+ if genie_response.description and len(genie_response.description) > 200
261
+ else genie_response.description
262
+ ),
263
+ result_preview=result_preview,
264
+ )
265
+
214
266
  # Update session state with cache information
215
267
  if persist_conversation:
216
268
  session.genie.update_space(
@@ -30,6 +30,7 @@ from dao_ai.config import (
30
30
  McpFunctionModel,
31
31
  TransportType,
32
32
  )
33
+ from dao_ai.state import Context
33
34
 
34
35
 
35
36
  @dataclass
@@ -173,6 +174,7 @@ def _get_auth_resource(function: McpFunctionModel) -> IsDatabricksResource:
173
174
 
174
175
  def _build_connection_config(
175
176
  function: McpFunctionModel,
177
+ context: Context | None = None,
176
178
  ) -> dict[str, Any]:
177
179
  """
178
180
  Build the connection configuration dictionary for MultiServerMCPClient.
@@ -193,6 +195,7 @@ def _build_connection_config(
193
195
 
194
196
  Args:
195
197
  function: The MCP function model configuration.
198
+ context: Optional runtime context with headers for OBO auth.
196
199
 
197
200
  Returns:
198
201
  A dictionary containing the transport-specific connection settings.
@@ -205,14 +208,17 @@ def _build_connection_config(
205
208
  }
206
209
 
207
210
  # For HTTP transport, use DatabricksOAuthClientProvider with unified auth
211
+ from databricks.sdk import WorkspaceClient
208
212
  from databricks_mcp import DatabricksOAuthClientProvider
209
213
 
210
214
  # Get the resource to use for authentication
211
- auth_resource = _get_auth_resource(function)
215
+ auth_resource: IsDatabricksResource = _get_auth_resource(function)
212
216
 
213
- # Get workspace client from the auth resource
214
- workspace_client = auth_resource.workspace_client
215
- auth_provider = DatabricksOAuthClientProvider(workspace_client)
217
+ # Get workspace client from the auth resource with OBO support via context
218
+ workspace_client: WorkspaceClient = auth_resource.workspace_client_from(context)
219
+ auth_provider: DatabricksOAuthClientProvider = DatabricksOAuthClientProvider(
220
+ workspace_client
221
+ )
216
222
 
217
223
  # Log which resource is providing auth
218
224
  resource_name = (
@@ -509,19 +515,28 @@ async def acreate_mcp_tools(
509
515
  def _create_tool_wrapper(mcp_tool: Tool) -> RunnableLike:
510
516
  """
511
517
  Create a LangChain tool wrapper for an MCP tool.
518
+
519
+ Supports OBO authentication via context headers.
512
520
  """
521
+ from langchain.tools import ToolRuntime
513
522
 
514
523
  @create_tool(
515
524
  mcp_tool.name,
516
525
  description=mcp_tool.description or f"MCP tool: {mcp_tool.name}",
517
526
  args_schema=mcp_tool.inputSchema,
518
527
  )
519
- async def tool_wrapper(**kwargs: Any) -> str:
528
+ async def tool_wrapper(
529
+ runtime: ToolRuntime[Context] = None,
530
+ **kwargs: Any,
531
+ ) -> str:
520
532
  """Execute MCP tool with fresh session."""
521
533
  logger.trace("Invoking MCP tool", tool_name=mcp_tool.name, args=kwargs)
522
534
 
523
- invocation_client = MultiServerMCPClient(
524
- {"mcp_function": _build_connection_config(function)}
535
+ # Get context for OBO support
536
+ context: Context | None = runtime.context if runtime else None
537
+
538
+ invocation_client: MultiServerMCPClient = MultiServerMCPClient(
539
+ {"mcp_function": _build_connection_config(function, context)}
525
540
  )
526
541
 
527
542
  try:
@@ -530,7 +545,7 @@ async def acreate_mcp_tools(
530
545
  mcp_tool.name, kwargs
531
546
  )
532
547
 
533
- text_result = _extract_text_content(result)
548
+ text_result: str = _extract_text_content(result)
534
549
 
535
550
  logger.trace(
536
551
  "MCP tool completed",
@@ -625,20 +640,28 @@ def create_mcp_tools(
625
640
  This wrapper handles:
626
641
  - Fresh session creation per invocation (stateless)
627
642
  - Content extraction to plain text (avoiding extra fields)
643
+ - OBO authentication via context headers
628
644
  """
645
+ from langchain.tools import ToolRuntime
629
646
 
630
647
  @create_tool(
631
648
  mcp_tool.name,
632
649
  description=mcp_tool.description or f"MCP tool: {mcp_tool.name}",
633
650
  args_schema=mcp_tool.inputSchema,
634
651
  )
635
- async def tool_wrapper(**kwargs: Any) -> str:
652
+ async def tool_wrapper(
653
+ runtime: ToolRuntime[Context] = None,
654
+ **kwargs: Any,
655
+ ) -> str:
636
656
  """Execute MCP tool with fresh session."""
637
657
  logger.trace("Invoking MCP tool", tool_name=mcp_tool.name, args=kwargs)
638
658
 
639
- # Create a fresh client/session for each invocation
640
- invocation_client = MultiServerMCPClient(
641
- {"mcp_function": _build_connection_config(function)}
659
+ # Get context for OBO support
660
+ context: Context | None = runtime.context if runtime else None
661
+
662
+ # Create a fresh client/session for each invocation with OBO support
663
+ invocation_client: MultiServerMCPClient = MultiServerMCPClient(
664
+ {"mcp_function": _build_connection_config(function, context)}
642
665
  )
643
666
 
644
667
  try:
@@ -648,7 +671,7 @@ def create_mcp_tools(
648
671
  )
649
672
 
650
673
  # Extract text content, avoiding extra fields
651
- text_result = _extract_text_content(result)
674
+ text_result: str = _extract_text_content(result)
652
675
 
653
676
  logger.trace(
654
677
  "MCP tool completed",
@@ -1,11 +1,13 @@
1
1
  from typing import Any, Callable, Optional
2
2
 
3
3
  from databricks.sdk.service.serving import ExternalFunctionRequestHttpMethod
4
+ from langchain.tools import ToolRuntime
4
5
  from langchain_core.tools import tool
5
6
  from loguru import logger
6
7
  from requests import Response
7
8
 
8
9
  from dao_ai.config import ConnectionModel
10
+ from dao_ai.state import Context
9
11
 
10
12
 
11
13
  def _find_channel_id_by_name(
@@ -129,8 +131,17 @@ def create_send_slack_message_tool(
129
131
  name_or_callable=name,
130
132
  description=description,
131
133
  )
132
- def send_slack_message(text: str) -> str:
133
- response: Response = connection.workspace_client.serving_endpoints.http_request(
134
+ def send_slack_message(
135
+ text: str,
136
+ runtime: ToolRuntime[Context] = None,
137
+ ) -> str:
138
+ from databricks.sdk import WorkspaceClient
139
+
140
+ # Get workspace client with OBO support via context
141
+ context: Context | None = runtime.context if runtime else None
142
+ workspace_client: WorkspaceClient = connection.workspace_client_from(context)
143
+
144
+ response: Response = workspace_client.serving_endpoints.http_request(
134
145
  conn=connection.name,
135
146
  method=ExternalFunctionRequestHttpMethod.POST,
136
147
  path="/api/chat.postMessage",
@@ -7,10 +7,11 @@ pre-configured SQL statements against a Databricks SQL warehouse.
7
7
 
8
8
  from databricks.sdk import WorkspaceClient
9
9
  from databricks.sdk.service.sql import StatementResponse, StatementState
10
- from langchain.tools import tool
10
+ from langchain.tools import ToolRuntime, tool
11
11
  from loguru import logger
12
12
 
13
13
  from dao_ai.config import WarehouseModel, value_of
14
+ from dao_ai.state import Context
14
15
 
15
16
 
16
17
  def create_execute_statement_tool(
@@ -63,7 +64,6 @@ def create_execute_statement_tool(
63
64
  description = f"Execute a pre-configured SQL query against the {warehouse.name} warehouse and return the results."
64
65
 
65
66
  warehouse_id: str = value_of(warehouse.warehouse_id)
66
- workspace_client: WorkspaceClient = warehouse.workspace_client
67
67
 
68
68
  logger.debug(
69
69
  "Creating SQL execution tool",
@@ -74,7 +74,7 @@ def create_execute_statement_tool(
74
74
  )
75
75
 
76
76
  @tool(name_or_callable=name, description=description)
77
- def execute_statement_tool() -> str:
77
+ def execute_statement_tool(runtime: ToolRuntime[Context] = None) -> str:
78
78
  """
79
79
  Execute the pre-configured SQL statement against the Databricks SQL warehouse.
80
80
 
@@ -88,6 +88,10 @@ def create_execute_statement_tool(
88
88
  sql_preview=statement[:100] + "..." if len(statement) > 100 else statement,
89
89
  )
90
90
 
91
+ # Get workspace client with OBO support via context
92
+ context: Context | None = runtime.context if runtime else None
93
+ workspace_client: WorkspaceClient = warehouse.workspace_client_from(context)
94
+
91
95
  try:
92
96
  # Execute the SQL statement
93
97
  statement_response: StatementResponse = (
@@ -1,10 +1,11 @@
1
- from typing import Any, Dict, Optional, Sequence, Set
1
+ from typing import Annotated, Any, Dict, Optional, Sequence, Set
2
2
 
3
3
  from databricks.sdk import WorkspaceClient
4
4
  from databricks.sdk.service.catalog import FunctionInfo, PermissionsChange, Privilege
5
5
  from databricks_langchain import DatabricksFunctionClient, UCFunctionToolkit
6
+ from langchain.tools import ToolRuntime
6
7
  from langchain_core.runnables.base import RunnableLike
7
- from langchain_core.tools import StructuredTool
8
+ from langchain_core.tools import InjectedToolArg, StructuredTool
8
9
  from loguru import logger
9
10
  from pydantic import BaseModel
10
11
  from unitycatalog.ai.core.base import FunctionExecutionResult
@@ -15,6 +16,7 @@ from dao_ai.config import (
15
16
  UnityCatalogFunctionModel,
16
17
  value_of,
17
18
  )
19
+ from dao_ai.state import Context
18
20
  from dao_ai.utils import normalize_host
19
21
 
20
22
 
@@ -35,13 +37,11 @@ def create_uc_tools(
35
37
  A sequence of BaseTool objects that wrap the specified UC functions
36
38
  """
37
39
  original_function_model: UnityCatalogFunctionModel | None = None
38
- workspace_client: WorkspaceClient | None = None
39
40
  function_name: str
40
41
 
41
42
  if isinstance(function, UnityCatalogFunctionModel):
42
43
  original_function_model = function
43
44
  function_name = function.resource.full_name
44
- workspace_client = function.resource.workspace_client
45
45
  else:
46
46
  function_name = function
47
47
 
@@ -56,6 +56,12 @@ def create_uc_tools(
56
56
  # Use with_partial_args directly with UnityCatalogFunctionModel
57
57
  tools = [with_partial_args(original_function_model)]
58
58
  else:
59
+ # For standard UC toolkit, we need workspace_client at creation time
60
+ # Use the resource's workspace_client (will use ambient auth if no OBO)
61
+ workspace_client: WorkspaceClient | None = None
62
+ if original_function_model:
63
+ workspace_client = original_function_model.resource.workspace_client
64
+
59
65
  # Fallback to standard UC toolkit approach
60
66
  client: DatabricksFunctionClient = DatabricksFunctionClient(
61
67
  client=workspace_client
@@ -356,7 +362,6 @@ def with_partial_args(
356
362
  # Get function info from the resource
357
363
  function_name: str = uc_function.resource.full_name
358
364
  tool_name: str = uc_function.resource.name or function_name.replace(".", "_")
359
- workspace_client: WorkspaceClient = uc_function.resource.workspace_client
360
365
 
361
366
  logger.debug(
362
367
  "Creating UC tool with partial args",
@@ -365,7 +370,7 @@ def with_partial_args(
365
370
  partial_args=list(resolved_args.keys()),
366
371
  )
367
372
 
368
- # Grant permissions if we have credentials
373
+ # Grant permissions if we have credentials (using ambient auth for setup)
369
374
  if "client_id" in resolved_args:
370
375
  client_id: str = resolved_args["client_id"]
371
376
  host: Optional[str] = resolved_args.get("host")
@@ -376,14 +381,18 @@ def with_partial_args(
376
381
  "Failed to grant permissions", function_name=function_name, error=str(e)
377
382
  )
378
383
 
379
- # Create the client for function execution using the resource's workspace client
380
- client: DatabricksFunctionClient = DatabricksFunctionClient(client=workspace_client)
384
+ # Get workspace client for schema introspection (uses ambient auth at definition time)
385
+ # Actual execution will use OBO via context
386
+ setup_workspace_client: WorkspaceClient = uc_function.resource.workspace_client
387
+ setup_client: DatabricksFunctionClient = DatabricksFunctionClient(
388
+ client=setup_workspace_client
389
+ )
381
390
 
382
391
  # Try to get the function schema for better tool definition
383
392
  schema_model: type[BaseModel]
384
393
  tool_description: str
385
394
  try:
386
- function_info: FunctionInfo = client.get_function(function_name)
395
+ function_info: FunctionInfo = setup_client.get_function(function_name)
387
396
  schema_info = generate_function_input_params_schema(function_info)
388
397
  tool_description = (
389
398
  function_info.comment or f"Unity Catalog function: {function_name}"
@@ -419,8 +428,21 @@ def with_partial_args(
419
428
  tool_description = f"Unity Catalog function: {function_name}"
420
429
 
421
430
  # Create a wrapper function that calls _execute_uc_function with partial args
422
- def uc_function_wrapper(**kwargs) -> str:
431
+ # Uses InjectedToolArg to ensure runtime is injected but hidden from the LLM
432
+ def uc_function_wrapper(
433
+ runtime: Annotated[ToolRuntime[Context], InjectedToolArg] = None,
434
+ **kwargs: Any,
435
+ ) -> str:
423
436
  """Wrapper function that executes Unity Catalog function with partial args."""
437
+ # Get workspace client with OBO support via context
438
+ context: Context | None = runtime.context if runtime else None
439
+ workspace_client: WorkspaceClient = uc_function.resource.workspace_client_from(
440
+ context
441
+ )
442
+ client: DatabricksFunctionClient = DatabricksFunctionClient(
443
+ client=workspace_client
444
+ )
445
+
424
446
  return _execute_uc_function(
425
447
  client=client,
426
448
  function_name=function_name,