dao-ai 0.1.10__tar.gz → 0.1.12__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.10 → dao_ai-0.1.12}/PKG-INFO +2 -2
  2. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/07_human_in_the_loop/human_in_the_loop.yaml +14 -0
  3. {dao_ai-0.1.10 → dao_ai-0.1.12}/pyproject.toml +2 -2
  4. {dao_ai-0.1.10 → dao_ai-0.1.12}/requirements.txt +1 -1
  5. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/apps/handlers.py +1 -1
  6. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/apps/resources.py +105 -12
  7. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/apps/server.py +2 -2
  8. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/providers/databricks.py +85 -21
  9. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_databricks.py +34 -22
  10. {dao_ai-0.1.10 → dao_ai-0.1.12}/.gitignore +0 -0
  11. {dao_ai-0.1.10 → dao_ai-0.1.12}/.python-version +0 -0
  12. {dao_ai-0.1.10 → dao_ai-0.1.12}/CHANGELOG.md +0 -0
  13. {dao_ai-0.1.10 → dao_ai-0.1.12}/CONTRIBUTING.md +0 -0
  14. {dao_ai-0.1.10 → dao_ai-0.1.12}/LICENSE +0 -0
  15. {dao_ai-0.1.10 → dao_ai-0.1.12}/Makefile +0 -0
  16. {dao_ai-0.1.10 → dao_ai-0.1.12}/README.md +0 -0
  17. {dao_ai-0.1.10 → dao_ai-0.1.12}/app.yaml +0 -0
  18. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/01_getting_started/README.md +0 -0
  19. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/01_getting_started/minimal.yaml +0 -0
  20. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/02_mcp/README.md +0 -0
  21. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/02_mcp/custom_mcp.yaml +0 -0
  22. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/02_mcp/external_mcp.yaml +0 -0
  23. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/02_mcp/filtered_mcp.yaml +0 -0
  24. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/02_mcp/managed_mcp.yaml +0 -0
  25. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/02_mcp/slack_integration.yaml +0 -0
  26. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/03_reranking/README.md +0 -0
  27. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/03_reranking/vector_search_with_reranking.yaml +0 -0
  28. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/04_genie/README.md +0 -0
  29. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/04_genie/genie_basic.yaml +0 -0
  30. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/04_genie/genie_lru_cache.yaml +0 -0
  31. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/04_genie/genie_semantic_cache.yaml +0 -0
  32. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/04_genie/genie_with_conversation_id.yaml +0 -0
  33. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/05_memory/README.md +0 -0
  34. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/05_memory/conversation_summarization.yaml +0 -0
  35. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/05_memory/in_memory_basic.yaml +0 -0
  36. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/05_memory/lakebase_persistence.yaml +0 -0
  37. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/05_memory/postgres_persistence.yaml +0 -0
  38. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/06_on_behalf_of_user/README.md +0 -0
  39. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/06_on_behalf_of_user/obo_basic.yaml +0 -0
  40. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/07_human_in_the_loop/README.md +0 -0
  41. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/08_guardrails/README.md +0 -0
  42. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/08_guardrails/guardrails_basic.yaml +0 -0
  43. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/09_structured_output/README.md +0 -0
  44. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/09_structured_output/structured_output.yaml +0 -0
  45. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/10_agent_integrations/README.md +0 -0
  46. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/10_agent_integrations/agent_bricks.yaml +0 -0
  47. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/10_agent_integrations/kasal.yaml +0 -0
  48. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/11_prompt_engineering/README.md +0 -0
  49. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/11_prompt_engineering/prompt_optimization.yaml +0 -0
  50. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/11_prompt_engineering/prompt_registry.yaml +0 -0
  51. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/12_middleware/README.md +0 -0
  52. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/12_middleware/combined_middleware.yaml +0 -0
  53. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/12_middleware/context_management.yaml +0 -0
  54. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/12_middleware/custom_field_validation.yaml +0 -0
  55. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/12_middleware/limit_middleware.yaml +0 -0
  56. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/12_middleware/logging_middleware.yaml +0 -0
  57. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/12_middleware/pii_middleware.yaml +0 -0
  58. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/12_middleware/retry_middleware.yaml +0 -0
  59. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/12_middleware/tool_selector_middleware.yaml +0 -0
  60. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/13_orchestration/README.md +0 -0
  61. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/13_orchestration/supervisor_pattern.yaml +0 -0
  62. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/13_orchestration/swarm_pattern.yaml +0 -0
  63. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/14_basic_tools/README.md +0 -0
  64. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/14_basic_tools/sql_tool_example.yaml +0 -0
  65. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/15_complete_applications/README.md +0 -0
  66. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/15_complete_applications/brick_store.yaml +0 -0
  67. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/15_complete_applications/deep_research.yaml +0 -0
  68. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/15_complete_applications/executive_assistant.yaml +0 -0
  69. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/15_complete_applications/genie_and_genie_mcp.yaml +0 -0
  70. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/15_complete_applications/genie_vector_search_hybrid.yaml +0 -0
  71. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/15_complete_applications/hardware_store.yaml +0 -0
  72. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/15_complete_applications/hardware_store_lakebase.yaml +0 -0
  73. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/15_complete_applications/hardware_store_swarm.yaml +0 -0
  74. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/15_complete_applications/quick_serve_restaurant.yaml +0 -0
  75. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/15_complete_applications/reservations_system.yaml +0 -0
  76. {dao_ai-0.1.10 → dao_ai-0.1.12}/config/examples/README.md +0 -0
  77. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/dais2025/appointments.sql +0 -0
  78. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/dais2025/appointments_data.sql +0 -0
  79. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/dais2025/brand_rep_demo_data.sql +0 -0
  80. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/dais2025/brand_rep_demo_queries.sql +0 -0
  81. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/dais2025/brand_rep_demo_tables.sql +0 -0
  82. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/dais2025/brand_rep_demo_validation.sql +0 -0
  83. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/dais2025/customers.sql +0 -0
  84. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/dais2025/customers_data.sql +0 -0
  85. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/dais2025/dim_stores.sql +0 -0
  86. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/dais2025/dim_stores_data.sql +0 -0
  87. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/dais2025/employee_performance.sql +0 -0
  88. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/dais2025/employee_performance_data.sql +0 -0
  89. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/dais2025/employee_tasks.sql +0 -0
  90. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/dais2025/employee_tasks_data.sql +0 -0
  91. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/dais2025/inventory.sql +0 -0
  92. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/dais2025/inventory_data.sql +0 -0
  93. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/dais2025/managers.sql +0 -0
  94. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/dais2025/managers_data.sql +0 -0
  95. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/dais2025/product_data.sql +0 -0
  96. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/dais2025/products.sql +0 -0
  97. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/dais2025/task_assignments.sql +0 -0
  98. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/hardware_store/inventory.snappy.parquet +0 -0
  99. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/hardware_store/inventory.sql +0 -0
  100. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/hardware_store/products.snappy.parquet +0 -0
  101. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/hardware_store/products.sql +0 -0
  102. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/quick_serve_restaurant/.gitkeep +0 -0
  103. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/quick_serve_restaurant/fulfil_item_orders.sql +0 -0
  104. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/quick_serve_restaurant/items_description.csv +0 -0
  105. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/quick_serve_restaurant/items_description.sql +0 -0
  106. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/quick_serve_restaurant/items_raw.csv +0 -0
  107. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/quick_serve_restaurant/items_raw.sql +0 -0
  108. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/quick_serve_restaurant/orders_raw.csv +0 -0
  109. {dao_ai-0.1.10 → dao_ai-0.1.12}/data/quick_serve_restaurant/orders_raw.sql +0 -0
  110. {dao_ai-0.1.10 → dao_ai-0.1.12}/databricks.yaml.template +0 -0
  111. {dao_ai-0.1.10 → dao_ai-0.1.12}/docs/architecture.md +0 -0
  112. {dao_ai-0.1.10 → dao_ai-0.1.12}/docs/cli-reference.md +0 -0
  113. {dao_ai-0.1.10 → dao_ai-0.1.12}/docs/configuration-reference.md +0 -0
  114. {dao_ai-0.1.10 → dao_ai-0.1.12}/docs/contributing.md +0 -0
  115. {dao_ai-0.1.10 → dao_ai-0.1.12}/docs/examples.md +0 -0
  116. {dao_ai-0.1.10 → dao_ai-0.1.12}/docs/faq.md +0 -0
  117. {dao_ai-0.1.10 → dao_ai-0.1.12}/docs/hardware_store/README.md +0 -0
  118. {dao_ai-0.1.10 → dao_ai-0.1.12}/docs/hardware_store/retail_supervisor.png +0 -0
  119. {dao_ai-0.1.10 → dao_ai-0.1.12}/docs/hardware_store/retail_swarm.png +0 -0
  120. {dao_ai-0.1.10 → dao_ai-0.1.12}/docs/images/genie.png +0 -0
  121. {dao_ai-0.1.10 → dao_ai-0.1.12}/docs/key-capabilities.md +0 -0
  122. {dao_ai-0.1.10 → dao_ai-0.1.12}/docs/python-api.md +0 -0
  123. {dao_ai-0.1.10 → dao_ai-0.1.12}/docs/quick_serve_restaurant/.gitkeep +0 -0
  124. {dao_ai-0.1.10 → dao_ai-0.1.12}/docs/quick_serve_restaurant/quick-serve-restaurant.png +0 -0
  125. {dao_ai-0.1.10 → dao_ai-0.1.12}/docs/why-dao.md +0 -0
  126. {dao_ai-0.1.10 → dao_ai-0.1.12}/environment.yaml +0 -0
  127. {dao_ai-0.1.10 → dao_ai-0.1.12}/examples/dais2025/examples.yaml +0 -0
  128. {dao_ai-0.1.10 → dao_ai-0.1.12}/examples/deep_research/examples.yaml +0 -0
  129. {dao_ai-0.1.10 → dao_ai-0.1.12}/examples/executive_assistant/examples.yaml +0 -0
  130. {dao_ai-0.1.10 → dao_ai-0.1.12}/examples/hardware_store/examples.yaml +0 -0
  131. {dao_ai-0.1.10 → dao_ai-0.1.12}/examples/quick_serve_restaurant/.gitkeep +0 -0
  132. {dao_ai-0.1.10 → dao_ai-0.1.12}/examples/quick_serve_restaurant/examples.yaml +0 -0
  133. {dao_ai-0.1.10 → dao_ai-0.1.12}/functions/dais2025/extract_store_numbers.sql +0 -0
  134. {dao_ai-0.1.10 → dao_ai-0.1.12}/functions/dais2025/find_inventory_by_sku.sql +0 -0
  135. {dao_ai-0.1.10 → dao_ai-0.1.12}/functions/dais2025/find_inventory_by_upc.sql +0 -0
  136. {dao_ai-0.1.10 → dao_ai-0.1.12}/functions/dais2025/find_product_by_sku.sql +0 -0
  137. {dao_ai-0.1.10 → dao_ai-0.1.12}/functions/dais2025/find_product_by_upc.sql +0 -0
  138. {dao_ai-0.1.10 → dao_ai-0.1.12}/functions/dais2025/find_store_by_number.sql +0 -0
  139. {dao_ai-0.1.10 → dao_ai-0.1.12}/functions/dais2025/find_store_inventory_by_sku.sql +0 -0
  140. {dao_ai-0.1.10 → dao_ai-0.1.12}/functions/dais2025/find_store_inventory_by_upc.sql +0 -0
  141. {dao_ai-0.1.10 → dao_ai-0.1.12}/functions/hardware_store/find_inventory_by_sku.sql +0 -0
  142. {dao_ai-0.1.10 → dao_ai-0.1.12}/functions/hardware_store/find_inventory_by_upc.sql +0 -0
  143. {dao_ai-0.1.10 → dao_ai-0.1.12}/functions/hardware_store/find_product_by_sku.sql +0 -0
  144. {dao_ai-0.1.10 → dao_ai-0.1.12}/functions/hardware_store/find_product_by_upc.sql +0 -0
  145. {dao_ai-0.1.10 → dao_ai-0.1.12}/functions/hardware_store/find_store_inventory_by_sku.sql +0 -0
  146. {dao_ai-0.1.10 → dao_ai-0.1.12}/functions/hardware_store/find_store_inventory_by_upc.sql +0 -0
  147. {dao_ai-0.1.10 → dao_ai-0.1.12}/functions/quick_serve_restaurant/.gitkeep +0 -0
  148. {dao_ai-0.1.10 → dao_ai-0.1.12}/functions/quick_serve_restaurant/insert_coffee_order.sql +0 -0
  149. {dao_ai-0.1.10 → dao_ai-0.1.12}/functions/quick_serve_restaurant/lookup_items_by_descriptions.sql +0 -0
  150. {dao_ai-0.1.10 → dao_ai-0.1.12}/functions/quick_serve_restaurant/match_historical_item_order_by_date.sql +0 -0
  151. {dao_ai-0.1.10 → dao_ai-0.1.12}/functions/quick_serve_restaurant/match_item_by_description_and_price.sql +0 -0
  152. {dao_ai-0.1.10 → dao_ai-0.1.12}/notebooks/01_ingest_and_transform.py +0 -0
  153. {dao_ai-0.1.10 → dao_ai-0.1.12}/notebooks/02_provision_vector_search.py +0 -0
  154. {dao_ai-0.1.10 → dao_ai-0.1.12}/notebooks/03_provision_lakebase.py +0 -0
  155. {dao_ai-0.1.10 → dao_ai-0.1.12}/notebooks/04_unity_catalog_tools.py +0 -0
  156. {dao_ai-0.1.10 → dao_ai-0.1.12}/notebooks/05_deploy_agent.py +0 -0
  157. {dao_ai-0.1.10 → dao_ai-0.1.12}/notebooks/06_generate_evaluation_data.py +0 -0
  158. {dao_ai-0.1.10 → dao_ai-0.1.12}/notebooks/07_run_evaluation.py +0 -0
  159. {dao_ai-0.1.10 → dao_ai-0.1.12}/notebooks/08_run_examples.py +0 -0
  160. {dao_ai-0.1.10 → dao_ai-0.1.12}/notebooks/09_evaluate_inferences.py +0 -0
  161. {dao_ai-0.1.10 → dao_ai-0.1.12}/notebooks/10_optimize_prompts.py +0 -0
  162. {dao_ai-0.1.10 → dao_ai-0.1.12}/notebooks/99_scratchpad.py +0 -0
  163. {dao_ai-0.1.10 → dao_ai-0.1.12}/schemas/bundle_config_schema.json +0 -0
  164. {dao_ai-0.1.10 → dao_ai-0.1.12}/schemas/model_config_schema.json +0 -0
  165. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dais2025/__init__.py +0 -0
  166. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dais2025/models.py +0 -0
  167. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dais2025/tools/__init__.py +0 -0
  168. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dais2025/tools/customer.py +0 -0
  169. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dais2025/tools/employee.py +0 -0
  170. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dais2025/tools/executive.py +0 -0
  171. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dais2025/tools/genie.py +0 -0
  172. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dais2025/tools/inventory.py +0 -0
  173. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dais2025/tools/models.py +0 -0
  174. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dais2025/tools/store.py +0 -0
  175. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/__init__.py +0 -0
  176. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/apps/__init__.py +0 -0
  177. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/apps/model_serving.py +0 -0
  178. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/catalog.py +0 -0
  179. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/cli.py +0 -0
  180. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/config.py +0 -0
  181. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/genie/__init__.py +0 -0
  182. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/genie/cache/__init__.py +0 -0
  183. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/genie/cache/base.py +0 -0
  184. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/genie/cache/core.py +0 -0
  185. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/genie/cache/lru.py +0 -0
  186. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/genie/cache/semantic.py +0 -0
  187. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/genie/core.py +0 -0
  188. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/graph.py +0 -0
  189. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/hooks/__init__.py +0 -0
  190. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/hooks/core.py +0 -0
  191. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/logging.py +0 -0
  192. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/memory/__init__.py +0 -0
  193. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/memory/base.py +0 -0
  194. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/memory/core.py +0 -0
  195. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/memory/databricks.py +0 -0
  196. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/memory/postgres.py +0 -0
  197. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/messages.py +0 -0
  198. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/middleware/__init__.py +0 -0
  199. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/middleware/assertions.py +0 -0
  200. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/middleware/base.py +0 -0
  201. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/middleware/context_editing.py +0 -0
  202. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/middleware/core.py +0 -0
  203. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/middleware/guardrails.py +0 -0
  204. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/middleware/human_in_the_loop.py +0 -0
  205. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/middleware/message_validation.py +0 -0
  206. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/middleware/model_call_limit.py +0 -0
  207. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/middleware/model_retry.py +0 -0
  208. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/middleware/pii.py +0 -0
  209. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/middleware/summarization.py +0 -0
  210. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/middleware/tool_call_limit.py +0 -0
  211. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/middleware/tool_retry.py +0 -0
  212. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/middleware/tool_selector.py +0 -0
  213. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/models.py +0 -0
  214. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/nodes.py +0 -0
  215. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/optimization.py +0 -0
  216. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/orchestration/__init__.py +0 -0
  217. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/orchestration/core.py +0 -0
  218. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/orchestration/supervisor.py +0 -0
  219. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/orchestration/swarm.py +0 -0
  220. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/prompts.py +0 -0
  221. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/providers/__init__.py +0 -0
  222. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/providers/base.py +0 -0
  223. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/state.py +0 -0
  224. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/tools/__init__.py +0 -0
  225. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/tools/agent.py +0 -0
  226. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/tools/core.py +0 -0
  227. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/tools/email.py +0 -0
  228. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/tools/genie.py +0 -0
  229. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/tools/mcp.py +0 -0
  230. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/tools/memory.py +0 -0
  231. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/tools/python.py +0 -0
  232. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/tools/search.py +0 -0
  233. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/tools/slack.py +0 -0
  234. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/tools/sql.py +0 -0
  235. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/tools/time.py +0 -0
  236. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/tools/unity_catalog.py +0 -0
  237. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/tools/vector_search.py +0 -0
  238. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/types.py +0 -0
  239. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/utils.py +0 -0
  240. {dao_ai-0.1.10 → dao_ai-0.1.12}/src/dao_ai/vector_search.py +0 -0
  241. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/config/test_model_config.yaml +0 -0
  242. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/conftest.py +0 -0
  243. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/middleware/test_context_editing.py +0 -0
  244. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/middleware/test_model_call_limit.py +0 -0
  245. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/middleware/test_model_retry.py +0 -0
  246. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/middleware/test_pii.py +0 -0
  247. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/middleware/test_tool_call_limit.py +0 -0
  248. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/middleware/test_tool_retry.py +0 -0
  249. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/middleware/test_tool_selector.py +0 -0
  250. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_agent_response_format.py +0 -0
  251. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_assertions_middleware.py +0 -0
  252. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_catalog.py +0 -0
  253. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_chat_history.py +0 -0
  254. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_config.py +0 -0
  255. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_function_parsing.py +0 -0
  256. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_genie.py +0 -0
  257. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_genie_conversation_ids_in_outputs.py +0 -0
  258. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_genie_databricks_integration.py +0 -0
  259. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_genie_room_model.py +0 -0
  260. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_guardrail_retry.py +0 -0
  261. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_hitl_config_model.py +0 -0
  262. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_hitl_responses_agent.py +0 -0
  263. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_hooks.py +0 -0
  264. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_human_in_the_loop.py +0 -0
  265. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_inference.py +0 -0
  266. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_inference_integration.py +0 -0
  267. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_input_output_structure.py +0 -0
  268. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_interrupt_type.py +0 -0
  269. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_llm_interrupt_handling.py +0 -0
  270. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_mcp.py +0 -0
  271. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_mcp_filtering.py +0 -0
  272. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_mcp_filtering_integration.py +0 -0
  273. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_mcp_function_model.py +0 -0
  274. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_message_validation_middleware.py +0 -0
  275. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_messages.py +0 -0
  276. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_models.py +0 -0
  277. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_optimization.py +0 -0
  278. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_postgres_integration.py +0 -0
  279. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_prompt_optimizations.py +0 -0
  280. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_prompts.py +0 -0
  281. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_reranking.py +0 -0
  282. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_reranking_integration.py +0 -0
  283. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_resources_model_genie_integration.py +0 -0
  284. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_response_format.py +0 -0
  285. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_responses_agent_structured_output_unit.py +0 -0
  286. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_semantic_cache_context.py +0 -0
  287. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_sql_tool.py +0 -0
  288. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_sql_tool_integration.py +0 -0
  289. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_state.py +0 -0
  290. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_summarization_inference.py +0 -0
  291. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_swarm_middleware.py +0 -0
  292. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_tools.py +0 -0
  293. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_types.py +0 -0
  294. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_unity_catalog.py +0 -0
  295. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_utils.py +0 -0
  296. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_utils_type_from_fqn.py +0 -0
  297. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_vector_search.py +0 -0
  298. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/test_warehouse_model.py +0 -0
  299. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/dao_ai/weather_server_mcp.py +0 -0
  300. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/hardware_store/.gitkeep +0 -0
  301. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/hardware_store/test_graph.py +0 -0
  302. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/images/doritos_upc.png +0 -0
  303. {dao_ai-0.1.10 → dao_ai-0.1.12}/tests/images/lays_upc.png +0 -0
  304. {dao_ai-0.1.10 → dao_ai-0.1.12}/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.10
3
+ Version: 0.1.12
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
@@ -28,7 +28,7 @@ Requires-Python: >=3.11
28
28
  Requires-Dist: databricks-agents>=1.9.0
29
29
  Requires-Dist: databricks-langchain[memory]>=0.12.1
30
30
  Requires-Dist: databricks-mcp>=0.5.0
31
- Requires-Dist: databricks-sdk[openai]>=0.76.0
31
+ Requires-Dist: databricks-sdk[openai]>=0.77.0
32
32
  Requires-Dist: ddgs>=9.10.0
33
33
  Requires-Dist: dspy>=2.6.27
34
34
  Requires-Dist: flashrank>=0.2.10
@@ -26,6 +26,18 @@
26
26
  # - Composite: { type: composite, options: [${VAR1}, ${VAR2}, default] }
27
27
  # - Primitives: Just use the value directly
28
28
 
29
+ variables:
30
+ client_id: &client_id
31
+ options:
32
+ - env: RETAIL_AI_DATABRICKS_CLIENT_ID # Service principal client ID
33
+ - scope: retail_consumer_goods
34
+ secret: RETAIL_AI_DATABRICKS_CLIENT_ID
35
+ client_secret: &client_secret
36
+ options:
37
+ - env: RETAIL_AI_DATABRICKS_CLIENT_SECRET # Service principal secret
38
+ - scope: retail_consumer_goods
39
+ secret: RETAIL_AI_DATABRICKS_CLIENT_SECRET
40
+
29
41
  schemas:
30
42
  retail_schema: &retail_schema
31
43
  catalog_name: nfleming
@@ -42,6 +54,8 @@ resources:
42
54
  lakebase_db: &lakebase_db
43
55
  name: retail-consumer-goods
44
56
  instance_name: retail-consumer-goods
57
+ client_id: *client_id
58
+ client_secret: *client_secret
45
59
 
46
60
  # =============================================================================
47
61
  # TOOLS WITH HUMAN-IN-THE-LOOP CONFIGURATIONS
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "dao-ai"
7
- version = "0.1.10"
7
+ version = "0.1.12"
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" }
@@ -46,7 +46,7 @@ dependencies = [
46
46
  "databricks-agents>=1.9.0",
47
47
  "databricks-langchain[memory]>=0.12.1",
48
48
  "databricks-mcp>=0.5.0",
49
- "databricks-sdk[openai]>=0.76.0",
49
+ "databricks-sdk[openai]>=0.77.0",
50
50
  "ddgs>=9.10.0",
51
51
  "dspy>=2.6.27",
52
52
  "flashrank>=0.2.10",
@@ -33,7 +33,7 @@ databricks-agents==1.9.0
33
33
  databricks-ai-bridge==0.11.0
34
34
  databricks-langchain==0.12.1
35
35
  databricks-mcp==0.5.1
36
- databricks-sdk==0.76.0
36
+ databricks-sdk==0.77.0
37
37
  dataclasses-json==0.6.7
38
38
  datasets==4.4.2
39
39
  ddgs==9.10.0
@@ -34,7 +34,7 @@ mlflow.set_tracking_uri("databricks")
34
34
  mlflow.langchain.autolog()
35
35
 
36
36
  # Get config path from environment or use default
37
- config_path: str = os.environ.get("DAO_AI_CONFIG_PATH", "model_config.yaml")
37
+ config_path: str = os.environ.get("DAO_AI_CONFIG_PATH", "dao_ai.yaml")
38
38
 
39
39
  # Load configuration using AppConfig.from_file (consistent with CLI, notebook, builder)
40
40
  config: AppConfig = AppConfig.from_file(config_path)
@@ -38,6 +38,8 @@ from databricks.sdk.service.apps import (
38
38
  AppResource,
39
39
  AppResourceDatabase,
40
40
  AppResourceDatabaseDatabasePermission,
41
+ AppResourceExperiment,
42
+ AppResourceExperimentExperimentPermission,
41
43
  AppResourceGenieSpace,
42
44
  AppResourceGenieSpaceGenieSpacePermission,
43
45
  AppResourceSecret,
@@ -308,6 +310,25 @@ def _extract_secrets_from_config(config: AppConfig) -> list[dict[str, Any]]:
308
310
  A list of secret resource dictionaries with unique scope/key pairs
309
311
  """
310
312
  secrets: dict[tuple[str, str], dict[str, Any]] = {}
313
+ used_names: set[str] = set()
314
+
315
+ def get_unique_resource_name(base_name: str) -> str:
316
+ """Generate a unique resource name, adding suffix if needed."""
317
+ sanitized = _sanitize_resource_name(base_name)
318
+ if sanitized not in used_names:
319
+ used_names.add(sanitized)
320
+ return sanitized
321
+ # Name collision - add numeric suffix
322
+ counter = 1
323
+ while True:
324
+ # Leave room for suffix (e.g., "_1", "_2", etc.)
325
+ suffix = f"_{counter}"
326
+ max_base_len = 30 - len(suffix)
327
+ candidate = sanitized[:max_base_len] + suffix
328
+ if candidate not in used_names:
329
+ used_names.add(candidate)
330
+ return candidate
331
+ counter += 1
311
332
 
312
333
  def extract_from_value(value: Any, path: str = "") -> None:
313
334
  """Recursively extract secrets from any value."""
@@ -315,9 +336,10 @@ def _extract_secrets_from_config(config: AppConfig) -> list[dict[str, Any]]:
315
336
  secret_key = (value.scope, value.secret)
316
337
  if secret_key not in secrets:
317
338
  # Create a unique name for the secret resource
318
- resource_name = f"{value.scope}_{value.secret}".replace(
319
- "-", "_"
320
- ).replace("/", "_")
339
+ base_name = f"{value.scope}_{value.secret}".replace("-", "_").replace(
340
+ "/", "_"
341
+ )
342
+ resource_name = get_unique_resource_name(base_name)
321
343
  secrets[secret_key] = {
322
344
  "name": resource_name,
323
345
  "type": "secret",
@@ -325,7 +347,9 @@ def _extract_secrets_from_config(config: AppConfig) -> list[dict[str, Any]]:
325
347
  "key": value.secret,
326
348
  "permissions": [{"level": "READ"}],
327
349
  }
328
- logger.debug(f"Found secret: {value.scope}/{value.secret} at {path}")
350
+ logger.debug(
351
+ f"Found secret: {value.scope}/{value.secret} at {path} -> resource: {resource_name}"
352
+ )
329
353
  elif isinstance(value, dict):
330
354
  for k, v in value.items():
331
355
  extract_from_value(v, f"{path}.{k}" if path else k)
@@ -518,7 +542,10 @@ def _sanitize_resource_name(name: str) -> str:
518
542
  return sanitized
519
543
 
520
544
 
521
- def generate_sdk_resources(config: AppConfig) -> list[AppResource]:
545
+ def generate_sdk_resources(
546
+ config: AppConfig,
547
+ experiment_id: str | None = None,
548
+ ) -> list[AppResource]:
522
549
  """
523
550
  Generate Databricks SDK AppResource objects from an AppConfig.
524
551
 
@@ -528,6 +555,9 @@ def generate_sdk_resources(config: AppConfig) -> list[AppResource]:
528
555
 
529
556
  Args:
530
557
  config: The AppConfig containing resource definitions
558
+ experiment_id: Optional MLflow experiment ID to add as a resource.
559
+ When provided, the experiment is added with CAN_EDIT permission,
560
+ allowing the app to log traces and runs.
531
561
 
532
562
  Returns:
533
563
  A list of AppResource objects for the Databricks SDK
@@ -536,13 +566,17 @@ def generate_sdk_resources(config: AppConfig) -> list[AppResource]:
536
566
  >>> from databricks.sdk import WorkspaceClient
537
567
  >>> from databricks.sdk.service.apps import App
538
568
  >>> config = AppConfig.from_file("model_config.yaml")
539
- >>> resources = generate_sdk_resources(config)
569
+ >>> resources = generate_sdk_resources(config, experiment_id="12345")
540
570
  >>> w = WorkspaceClient()
541
571
  >>> app = App(name="my-app", resources=resources)
542
572
  >>> w.apps.create_and_wait(app=app)
543
573
  """
544
574
  resources: list[AppResource] = []
545
575
 
576
+ # Add experiment resource if provided
577
+ if experiment_id:
578
+ resources.append(_extract_sdk_experiment_resource(experiment_id))
579
+
546
580
  if config.resources is None:
547
581
  logger.debug("No resources defined in config")
548
582
  return resources
@@ -685,6 +719,36 @@ def _extract_sdk_volume_resources(
685
719
  return resources
686
720
 
687
721
 
722
+ def _extract_sdk_experiment_resource(
723
+ experiment_id: str,
724
+ resource_name: str = "experiment",
725
+ ) -> AppResource:
726
+ """Create SDK AppResource for MLflow experiment.
727
+
728
+ This allows the Databricks App to log traces and runs to the specified
729
+ MLflow experiment. The experiment ID is exposed via the MLFLOW_EXPERIMENT_ID
730
+ environment variable using valueFrom: experiment in app.yaml.
731
+
732
+ Args:
733
+ experiment_id: The MLflow experiment ID
734
+ resource_name: The resource key name (default: "experiment")
735
+
736
+ Returns:
737
+ An AppResource for the MLflow experiment
738
+ """
739
+ resource = AppResource(
740
+ name=resource_name,
741
+ experiment=AppResourceExperiment(
742
+ experiment_id=experiment_id,
743
+ permission=AppResourceExperimentExperimentPermission.CAN_EDIT,
744
+ ),
745
+ )
746
+ logger.debug(
747
+ f"Extracted SDK experiment resource: {resource_name} -> {experiment_id}"
748
+ )
749
+ return resource
750
+
751
+
688
752
  def _extract_sdk_secrets_from_config(config: AppConfig) -> list[AppResource]:
689
753
  """
690
754
  Extract SDK AppResource objects for all secrets referenced in the config.
@@ -700,6 +764,25 @@ def _extract_sdk_secrets_from_config(config: AppConfig) -> list[AppResource]:
700
764
  A list of AppResource objects for secrets
701
765
  """
702
766
  secrets: dict[tuple[str, str], AppResource] = {}
767
+ used_names: set[str] = set()
768
+
769
+ def get_unique_resource_name(base_name: str) -> str:
770
+ """Generate a unique resource name, adding suffix if needed."""
771
+ sanitized = _sanitize_resource_name(base_name)
772
+ if sanitized not in used_names:
773
+ used_names.add(sanitized)
774
+ return sanitized
775
+ # Name collision - add numeric suffix
776
+ counter = 1
777
+ while True:
778
+ # Leave room for suffix (e.g., "_1", "_2", etc.)
779
+ suffix = f"_{counter}"
780
+ max_base_len = 30 - len(suffix)
781
+ candidate = sanitized[:max_base_len] + suffix
782
+ if candidate not in used_names:
783
+ used_names.add(candidate)
784
+ return candidate
785
+ counter += 1
703
786
 
704
787
  def extract_from_value(value: Any) -> None:
705
788
  """Recursively extract secrets from any value."""
@@ -707,10 +790,10 @@ def _extract_sdk_secrets_from_config(config: AppConfig) -> list[AppResource]:
707
790
  secret_key = (value.scope, value.secret)
708
791
  if secret_key not in secrets:
709
792
  # Create a unique name for the secret resource
710
- resource_name = f"{value.scope}_{value.secret}".replace(
711
- "-", "_"
712
- ).replace("/", "_")
713
- resource_name = _sanitize_resource_name(resource_name)
793
+ base_name = f"{value.scope}_{value.secret}".replace("-", "_").replace(
794
+ "/", "_"
795
+ )
796
+ resource_name = get_unique_resource_name(base_name)
714
797
 
715
798
  resource = AppResource(
716
799
  name=resource_name,
@@ -722,7 +805,7 @@ def _extract_sdk_secrets_from_config(config: AppConfig) -> list[AppResource]:
722
805
  )
723
806
  secrets[secret_key] = resource
724
807
  logger.debug(
725
- f"Found secret for SDK resource: {value.scope}/{value.secret}"
808
+ f"Found secret for SDK resource: {value.scope}/{value.secret} -> resource: {resource_name}"
726
809
  )
727
810
  elif isinstance(value, dict):
728
811
  for v in value.values():
@@ -926,12 +1009,22 @@ def generate_app_yaml(
926
1009
  env_vars: list[dict[str, str]] = [
927
1010
  {"name": "MLFLOW_TRACKING_URI", "value": "databricks"},
928
1011
  {"name": "MLFLOW_REGISTRY_URI", "value": "databricks-uc"},
929
- {"name": "DAO_AI_CONFIG_PATH", "value": "model_config.yaml"},
1012
+ {"name": "MLFLOW_EXPERIMENT_ID", "valueFrom": "experiment"},
1013
+ {"name": "DAO_AI_CONFIG_PATH", "value": "dao_ai.yaml"},
930
1014
  ]
931
1015
 
932
1016
  # Extract environment variables from config.app.environment_vars
933
1017
  config_env_vars = _extract_env_vars_from_config(config)
934
1018
 
1019
+ # Environment variables that are automatically provided by Databricks Apps
1020
+ # and should not be included in app.yaml
1021
+ platform_provided_env_vars = {"DATABRICKS_HOST"}
1022
+
1023
+ # Filter out platform-provided env vars from config
1024
+ config_env_vars = [
1025
+ e for e in config_env_vars if e["name"] not in platform_provided_env_vars
1026
+ ]
1027
+
935
1028
  # Merge config env vars, avoiding duplicates (config takes precedence)
936
1029
  base_env_names = {e["name"] for e in env_vars}
937
1030
  for config_env in config_env_vars:
@@ -7,13 +7,13 @@ uses the AgentServer for the Databricks Apps runtime.
7
7
 
8
8
  Configuration Loading:
9
9
  The config path is specified via the DAO_AI_CONFIG_PATH environment variable,
10
- or defaults to model_config.yaml in the current directory.
10
+ or defaults to dao_ai.yaml in the current directory.
11
11
 
12
12
  Usage:
13
13
  # With environment variable
14
14
  DAO_AI_CONFIG_PATH=/path/to/config.yaml python -m dao_ai.apps.server
15
15
 
16
- # With default model_config.yaml in current directory
16
+ # With default dao_ai.yaml in current directory
17
17
  python -m dao_ai.apps.server
18
18
  """
19
19
 
@@ -152,25 +152,77 @@ class DatabricksProvider(ServiceProvider):
152
152
  client_secret: str | None = None,
153
153
  workspace_host: str | None = None,
154
154
  ) -> None:
155
- if w is None:
156
- w = _workspace_client(
157
- pat=pat,
158
- client_id=client_id,
159
- client_secret=client_secret,
160
- workspace_host=workspace_host,
155
+ # Store credentials for lazy initialization
156
+ self._pat = pat
157
+ self._client_id = client_id
158
+ self._client_secret = client_secret
159
+ self._workspace_host = workspace_host
160
+
161
+ # Lazy initialization for WorkspaceClient
162
+ self._w: WorkspaceClient | None = w
163
+ self._w_initialized = w is not None
164
+
165
+ # Lazy initialization for VectorSearchClient - only create when needed
166
+ # This avoids authentication errors in Databricks Apps where VSC
167
+ # requires explicit credentials but the platform uses ambient auth
168
+ self._vsc: VectorSearchClient | None = vsc
169
+ self._vsc_initialized = vsc is not None
170
+
171
+ # Lazy initialization for DatabricksFunctionClient
172
+ self._dfs: DatabricksFunctionClient | None = dfs
173
+ self._dfs_initialized = dfs is not None
174
+
175
+ @property
176
+ def w(self) -> WorkspaceClient:
177
+ """Lazy initialization of WorkspaceClient."""
178
+ if not self._w_initialized:
179
+ self._w = _workspace_client(
180
+ pat=self._pat,
181
+ client_id=self._client_id,
182
+ client_secret=self._client_secret,
183
+ workspace_host=self._workspace_host,
161
184
  )
162
- if vsc is None:
163
- vsc = _vector_search_client(
164
- pat=pat,
165
- client_id=client_id,
166
- client_secret=client_secret,
167
- workspace_host=workspace_host,
185
+ self._w_initialized = True
186
+ return self._w # type: ignore[return-value]
187
+
188
+ @w.setter
189
+ def w(self, value: WorkspaceClient) -> None:
190
+ """Set WorkspaceClient and mark as initialized."""
191
+ self._w = value
192
+ self._w_initialized = True
193
+
194
+ @property
195
+ def vsc(self) -> VectorSearchClient:
196
+ """Lazy initialization of VectorSearchClient."""
197
+ if not self._vsc_initialized:
198
+ self._vsc = _vector_search_client(
199
+ pat=self._pat,
200
+ client_id=self._client_id,
201
+ client_secret=self._client_secret,
202
+ workspace_host=self._workspace_host,
168
203
  )
169
- if dfs is None:
170
- dfs = _function_client(w=w)
171
- self.w = w
172
- self.vsc = vsc
173
- self.dfs = dfs
204
+ self._vsc_initialized = True
205
+ return self._vsc # type: ignore[return-value]
206
+
207
+ @vsc.setter
208
+ def vsc(self, value: VectorSearchClient) -> None:
209
+ """Set VectorSearchClient and mark as initialized."""
210
+ self._vsc = value
211
+ self._vsc_initialized = True
212
+
213
+ @property
214
+ def dfs(self) -> DatabricksFunctionClient:
215
+ """Lazy initialization of DatabricksFunctionClient."""
216
+ if not self._dfs_initialized:
217
+ self._dfs = _function_client(w=self.w)
218
+ self._dfs_initialized = True
219
+ return self._dfs # type: ignore[return-value]
220
+
221
+ @dfs.setter
222
+ def dfs(self, value: DatabricksFunctionClient) -> None:
223
+ """Set DatabricksFunctionClient and mark as initialized."""
224
+ self._dfs = value
225
+ self._dfs_initialized = True
174
226
 
175
227
  def experiment_name(self, config: AppConfig) -> str:
176
228
  current_user: User = self.w.current_user.me()
@@ -558,11 +610,21 @@ class DatabricksProvider(ServiceProvider):
558
610
 
559
611
  logger.info("Using workspace source path", source_path=source_path)
560
612
 
613
+ # Get or create experiment for this app (for tracing and tracking)
614
+ from mlflow.entities import Experiment
615
+
616
+ experiment: Experiment = self.get_or_create_experiment(config)
617
+ logger.info(
618
+ "Using MLflow experiment for app",
619
+ experiment_name=experiment.name,
620
+ experiment_id=experiment.experiment_id,
621
+ )
622
+
561
623
  # Upload the configuration file to the workspace
562
624
  source_config_path: str | None = config.source_config_path
563
625
  if source_config_path:
564
626
  # Read the config file and upload to workspace
565
- config_file_name: str = "model_config.yaml"
627
+ config_file_name: str = "dao_ai.yaml"
566
628
  workspace_config_path: str = f"{source_path}/{config_file_name}"
567
629
 
568
630
  logger.info(
@@ -593,7 +655,7 @@ class DatabricksProvider(ServiceProvider):
593
655
  logger.warning(
594
656
  "No source config path available. "
595
657
  "Ensure DAO_AI_CONFIG_PATH is set in the app environment or "
596
- "model_config.yaml exists in the app source directory."
658
+ "dao_ai.yaml exists in the app source directory."
597
659
  )
598
660
 
599
661
  # Generate and upload app.yaml with dynamically discovered resources
@@ -618,13 +680,15 @@ class DatabricksProvider(ServiceProvider):
618
680
  )
619
681
  logger.info("app.yaml with resources uploaded", path=app_yaml_path)
620
682
 
621
- # Generate SDK resources from the config
683
+ # Generate SDK resources from the config (including experiment)
622
684
  from dao_ai.apps.resources import (
623
685
  generate_sdk_resources,
624
686
  generate_user_api_scopes,
625
687
  )
626
688
 
627
- sdk_resources = generate_sdk_resources(config)
689
+ sdk_resources = generate_sdk_resources(
690
+ config, experiment_id=experiment.experiment_id
691
+ )
628
692
  if sdk_resources:
629
693
  logger.info(
630
694
  "Discovered app resources from config",
@@ -684,22 +684,28 @@ def test_deploy_apps_agent_creates_new_app():
684
684
  # Mock current user
685
685
  provider.w.current_user.me.return_value = mock_user
686
686
 
687
- # Simulate app doesn't exist
688
- provider.w.apps.get.side_effect = NotFound("App not found")
689
- provider.w.apps.create_and_wait.return_value = mock_created_app
690
- provider.w.apps.deploy_and_wait.return_value = mock_deployment
687
+ # Mock MLflow experiment
688
+ mock_experiment = MagicMock()
689
+ mock_experiment.experiment_id = "12345"
690
+ with patch.object(
691
+ provider, "get_or_create_experiment", return_value=mock_experiment
692
+ ):
693
+ # Simulate app doesn't exist
694
+ provider.w.apps.get.side_effect = NotFound("App not found")
695
+ provider.w.apps.create_and_wait.return_value = mock_created_app
696
+ provider.w.apps.deploy_and_wait.return_value = mock_deployment
691
697
 
692
- provider.deploy_apps_agent(mock_config)
698
+ provider.deploy_apps_agent(mock_config)
693
699
 
694
- # Verify create_and_wait was called with an App object
695
- provider.w.apps.create_and_wait.assert_called_once()
696
- call_args = provider.w.apps.create_and_wait.call_args
697
- app_arg = call_args.kwargs.get("app")
698
- assert app_arg is not None
699
- assert app_arg.name == "test-app" # Normalized: underscores become dashes
700
- assert app_arg.description == "Test app description"
701
- # Verify deploy_and_wait was called
702
- provider.w.apps.deploy_and_wait.assert_called_once()
700
+ # Verify create_and_wait was called with an App object
701
+ provider.w.apps.create_and_wait.assert_called_once()
702
+ call_args = provider.w.apps.create_and_wait.call_args
703
+ app_arg = call_args.kwargs.get("app")
704
+ assert app_arg is not None
705
+ assert app_arg.name == "test-app" # Normalized: underscores become dashes
706
+ assert app_arg.description == "Test app description"
707
+ # Verify deploy_and_wait was called
708
+ provider.w.apps.deploy_and_wait.assert_called_once()
703
709
 
704
710
 
705
711
  @pytest.mark.unit
@@ -746,16 +752,22 @@ def test_deploy_apps_agent_updates_existing_app():
746
752
  # Mock current user
747
753
  provider.w.current_user.me.return_value = mock_user
748
754
 
749
- # Simulate app already exists
750
- provider.w.apps.get.return_value = mock_existing_app
751
- provider.w.apps.deploy_and_wait.return_value = mock_deployment
755
+ # Mock MLflow experiment
756
+ mock_experiment = MagicMock()
757
+ mock_experiment.experiment_id = "12345"
758
+ with patch.object(
759
+ provider, "get_or_create_experiment", return_value=mock_experiment
760
+ ):
761
+ # Simulate app already exists
762
+ provider.w.apps.get.return_value = mock_existing_app
763
+ provider.w.apps.deploy_and_wait.return_value = mock_deployment
752
764
 
753
- provider.deploy_apps_agent(mock_config)
765
+ provider.deploy_apps_agent(mock_config)
754
766
 
755
- # Verify create_and_wait was NOT called (app already exists)
756
- provider.w.apps.create_and_wait.assert_not_called()
757
- # Verify deploy_and_wait was called
758
- provider.w.apps.deploy_and_wait.assert_called_once()
767
+ # Verify create_and_wait was NOT called (app already exists)
768
+ provider.w.apps.create_and_wait.assert_not_called()
769
+ # Verify deploy_and_wait was called
770
+ provider.w.apps.deploy_and_wait.assert_called_once()
759
771
 
760
772
 
761
773
  @pytest.mark.unit
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes