asksql 2.1.0__tar.gz → 2.2.0__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 (290) hide show
  1. {asksql-2.1.0 → asksql-2.2.0}/PKG-INFO +11 -2
  2. {asksql-2.1.0 → asksql-2.2.0}/README.md +10 -1
  3. {asksql-2.1.0 → asksql-2.2.0}/pyproject.toml +1 -1
  4. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/__init__.py +1 -1
  5. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/agent/agent.py +28 -1
  6. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/agent/config.py +17 -1
  7. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/workflow/default.py +51 -0
  8. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/postgres/sql_runner.py +4 -5
  9. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/sqlite/sql_runner.py +4 -5
  10. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/adapter.py +5 -4
  11. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/servers/fastapi/app.py +13 -1
  12. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/tools/agent_memory.py +72 -2
  13. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/tools/run_sql.py +26 -3
  14. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/utils/sql_safety.py +19 -0
  15. {asksql-2.1.0 → asksql-2.2.0}/LICENSE +0 -0
  16. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/agents/__init__.py +0 -0
  17. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/capabilities/__init__.py +0 -0
  18. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/capabilities/agent_memory/__init__.py +0 -0
  19. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/capabilities/agent_memory/base.py +0 -0
  20. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/capabilities/agent_memory/models.py +0 -0
  21. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/capabilities/file_system/__init__.py +0 -0
  22. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/capabilities/file_system/base.py +0 -0
  23. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/capabilities/file_system/models.py +0 -0
  24. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/capabilities/sql_runner/__init__.py +0 -0
  25. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/capabilities/sql_runner/base.py +0 -0
  26. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/capabilities/sql_runner/models.py +0 -0
  27. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/components/__init__.py +0 -0
  28. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/components/base.py +0 -0
  29. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/components/rich/__init__.py +0 -0
  30. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/components/rich/containers/__init__.py +0 -0
  31. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/components/rich/containers/card.py +0 -0
  32. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/components/rich/data/__init__.py +0 -0
  33. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/components/rich/data/chart.py +0 -0
  34. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/components/rich/data/dataframe.py +0 -0
  35. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/components/rich/feedback/__init__.py +0 -0
  36. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/components/rich/feedback/badge.py +0 -0
  37. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/components/rich/feedback/icon_text.py +0 -0
  38. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/components/rich/feedback/log_viewer.py +0 -0
  39. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/components/rich/feedback/notification.py +0 -0
  40. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/components/rich/feedback/progress.py +0 -0
  41. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/components/rich/feedback/status_card.py +0 -0
  42. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/components/rich/feedback/status_indicator.py +0 -0
  43. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/components/rich/interactive/__init__.py +0 -0
  44. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/components/rich/interactive/button.py +0 -0
  45. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/components/rich/interactive/task_list.py +0 -0
  46. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/components/rich/interactive/ui_state.py +0 -0
  47. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/components/rich/specialized/__init__.py +0 -0
  48. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/components/rich/specialized/artifact.py +0 -0
  49. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/components/rich/text.py +0 -0
  50. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/components/simple/__init__.py +0 -0
  51. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/components/simple/image.py +0 -0
  52. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/components/simple/link.py +0 -0
  53. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/components/simple/text.py +0 -0
  54. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/__init__.py +0 -0
  55. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/_compat.py +0 -0
  56. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/agent/__init__.py +0 -0
  57. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/audit/__init__.py +0 -0
  58. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/audit/base.py +0 -0
  59. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/audit/models.py +0 -0
  60. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/component_manager.py +0 -0
  61. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/components.py +0 -0
  62. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/enhancer/__init__.py +0 -0
  63. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/enhancer/base.py +0 -0
  64. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/enhancer/default.py +0 -0
  65. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/enricher/__init__.py +0 -0
  66. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/enricher/base.py +0 -0
  67. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/errors.py +0 -0
  68. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/evaluation/__init__.py +0 -0
  69. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/evaluation/base.py +0 -0
  70. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/evaluation/dataset.py +0 -0
  71. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/evaluation/evaluators.py +0 -0
  72. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/evaluation/report.py +0 -0
  73. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/evaluation/runner.py +0 -0
  74. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/filter/__init__.py +0 -0
  75. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/filter/base.py +0 -0
  76. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/lifecycle/__init__.py +0 -0
  77. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/lifecycle/base.py +0 -0
  78. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/llm/__init__.py +0 -0
  79. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/llm/base.py +0 -0
  80. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/llm/models.py +0 -0
  81. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/middleware/__init__.py +0 -0
  82. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/middleware/base.py +0 -0
  83. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/observability/__init__.py +0 -0
  84. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/observability/base.py +0 -0
  85. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/observability/models.py +0 -0
  86. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/recovery/__init__.py +0 -0
  87. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/recovery/base.py +0 -0
  88. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/recovery/models.py +0 -0
  89. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/registry.py +0 -0
  90. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/rich_component.py +0 -0
  91. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/simple_component.py +0 -0
  92. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/storage/__init__.py +0 -0
  93. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/storage/base.py +0 -0
  94. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/storage/models.py +0 -0
  95. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/system_prompt/__init__.py +0 -0
  96. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/system_prompt/base.py +0 -0
  97. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/system_prompt/default.py +0 -0
  98. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/tool/__init__.py +0 -0
  99. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/tool/base.py +0 -0
  100. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/tool/models.py +0 -0
  101. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/user/__init__.py +0 -0
  102. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/user/base.py +0 -0
  103. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/user/models.py +0 -0
  104. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/user/request_context.py +0 -0
  105. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/user/resolver.py +0 -0
  106. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/validation.py +0 -0
  107. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/workflow/__init__.py +0 -0
  108. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/core/workflow/base.py +0 -0
  109. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/examples/__init__.py +0 -0
  110. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/examples/__main__.py +0 -0
  111. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/examples/anthropic_quickstart.py +0 -0
  112. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/examples/artifact_example.py +0 -0
  113. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/examples/claude_sqlite_example.py +0 -0
  114. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/examples/coding_agent_example.py +0 -0
  115. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/examples/custom_system_prompt_example.py +0 -0
  116. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/examples/default_workflow_handler_example.py +0 -0
  117. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/examples/email_auth_example.py +0 -0
  118. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/examples/evaluation_example.py +0 -0
  119. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/examples/extensibility_example.py +0 -0
  120. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/examples/minimal_example.py +0 -0
  121. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/examples/mock_auth_example.py +0 -0
  122. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/examples/mock_custom_tool.py +0 -0
  123. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/examples/mock_quickstart.py +0 -0
  124. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/examples/mock_quota_example.py +0 -0
  125. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/examples/mock_rich_components_demo.py +0 -0
  126. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/examples/mock_sqlite_example.py +0 -0
  127. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/examples/openai_quickstart.py +0 -0
  128. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/examples/primitive_components_demo.py +0 -0
  129. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/examples/quota_lifecycle_example.py +0 -0
  130. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/examples/visualization_example.py +0 -0
  131. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/__init__.py +0 -0
  132. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/anthropic/__init__.py +0 -0
  133. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/anthropic/llm.py +0 -0
  134. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/azureopenai/__init__.py +0 -0
  135. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/azureopenai/llm.py +0 -0
  136. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/azuresearch/__init__.py +0 -0
  137. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/azuresearch/agent_memory.py +0 -0
  138. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/bigquery/__init__.py +0 -0
  139. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/bigquery/sql_runner.py +0 -0
  140. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/chromadb/__init__.py +0 -0
  141. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/chromadb/agent_memory.py +0 -0
  142. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/clickhouse/__init__.py +0 -0
  143. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/clickhouse/sql_runner.py +0 -0
  144. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/duckdb/__init__.py +0 -0
  145. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/duckdb/sql_runner.py +0 -0
  146. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/faiss/__init__.py +0 -0
  147. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/faiss/agent_memory.py +0 -0
  148. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/google/__init__.py +0 -0
  149. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/google/gemini.py +0 -0
  150. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/hive/__init__.py +0 -0
  151. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/hive/sql_runner.py +0 -0
  152. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/local/__init__.py +0 -0
  153. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/local/agent_memory/__init__.py +0 -0
  154. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/local/agent_memory/in_memory.py +0 -0
  155. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/local/audit.py +0 -0
  156. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/local/file_system.py +0 -0
  157. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/local/file_system_conversation_store.py +0 -0
  158. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/local/storage.py +0 -0
  159. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/marqo/__init__.py +0 -0
  160. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/marqo/agent_memory.py +0 -0
  161. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/milvus/__init__.py +0 -0
  162. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/milvus/agent_memory.py +0 -0
  163. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/mock/__init__.py +0 -0
  164. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/mock/llm.py +0 -0
  165. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/mssql/__init__.py +0 -0
  166. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/mssql/sql_runner.py +0 -0
  167. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/mysql/__init__.py +0 -0
  168. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/mysql/sql_runner.py +0 -0
  169. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/ollama/__init__.py +0 -0
  170. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/ollama/llm.py +0 -0
  171. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/openai/__init__.py +0 -0
  172. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/openai/llm.py +0 -0
  173. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/openai/responses.py +0 -0
  174. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/opensearch/__init__.py +0 -0
  175. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/opensearch/agent_memory.py +0 -0
  176. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/oracle/__init__.py +0 -0
  177. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/oracle/sql_runner.py +0 -0
  178. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/pinecone/__init__.py +0 -0
  179. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/pinecone/agent_memory.py +0 -0
  180. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/plotly/__init__.py +0 -0
  181. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/plotly/chart_generator.py +0 -0
  182. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/postgres/__init__.py +0 -0
  183. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/premium/agent_memory/__init__.py +0 -0
  184. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/premium/agent_memory/premium.py +0 -0
  185. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/presto/__init__.py +0 -0
  186. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/presto/sql_runner.py +0 -0
  187. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/qdrant/__init__.py +0 -0
  188. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/qdrant/agent_memory.py +0 -0
  189. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/snowflake/__init__.py +0 -0
  190. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/snowflake/sql_runner.py +0 -0
  191. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/sqlite/__init__.py +0 -0
  192. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/weaviate/__init__.py +0 -0
  193. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/integrations/weaviate/agent_memory.py +0 -0
  194. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/ZhipuAI/ZhipuAI_Chat.py +0 -0
  195. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/ZhipuAI/ZhipuAI_embeddings.py +0 -0
  196. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/ZhipuAI/__init__.py +0 -0
  197. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/__init__.py +0 -0
  198. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/advanced/__init__.py +0 -0
  199. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/anthropic/__init__.py +0 -0
  200. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/anthropic/anthropic_chat.py +0 -0
  201. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/azuresearch/__init__.py +0 -0
  202. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/azuresearch/azuresearch_vector.py +0 -0
  203. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/base/__init__.py +0 -0
  204. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/base/base.py +0 -0
  205. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/base/safe_exec.py +0 -0
  206. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/bedrock/__init__.py +0 -0
  207. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/bedrock/bedrock_converse.py +0 -0
  208. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/chromadb/__init__.py +0 -0
  209. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/chromadb/chromadb_vector.py +0 -0
  210. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/cohere/__init__.py +0 -0
  211. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/cohere/cohere_chat.py +0 -0
  212. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/cohere/cohere_embeddings.py +0 -0
  213. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/deepseek/__init__.py +0 -0
  214. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/deepseek/deepseek_chat.py +0 -0
  215. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/exceptions/__init__.py +0 -0
  216. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/faiss/__init__.py +0 -0
  217. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/faiss/faiss.py +0 -0
  218. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/flask/__init__.py +0 -0
  219. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/flask/assets.py +0 -0
  220. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/flask/auth.py +0 -0
  221. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/google/__init__.py +0 -0
  222. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/google/bigquery_vector.py +0 -0
  223. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/google/gemini_chat.py +0 -0
  224. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/hf/__init__.py +0 -0
  225. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/hf/hf.py +0 -0
  226. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/local.py +0 -0
  227. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/marqo/__init__.py +0 -0
  228. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/marqo/marqo.py +0 -0
  229. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/milvus/__init__.py +0 -0
  230. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/milvus/milvus_vector.py +0 -0
  231. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/mistral/__init__.py +0 -0
  232. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/mistral/mistral.py +0 -0
  233. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/mock/__init__.py +0 -0
  234. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/mock/embedding.py +0 -0
  235. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/mock/llm.py +0 -0
  236. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/mock/vectordb.py +0 -0
  237. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/ollama/__init__.py +0 -0
  238. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/ollama/ollama.py +0 -0
  239. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/openai/__init__.py +0 -0
  240. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/openai/openai_chat.py +0 -0
  241. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/openai/openai_embeddings.py +0 -0
  242. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/opensearch/__init__.py +0 -0
  243. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/opensearch/opensearch_vector.py +0 -0
  244. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/opensearch/opensearch_vector_semantic.py +0 -0
  245. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/oracle/__init__.py +0 -0
  246. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/oracle/oracle_vector.py +0 -0
  247. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/pgvector/__init__.py +0 -0
  248. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/pgvector/pgvector.py +0 -0
  249. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/pinecone/__init__.py +0 -0
  250. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/pinecone/pinecone_vector.py +0 -0
  251. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/qdrant/__init__.py +0 -0
  252. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/qdrant/qdrant.py +0 -0
  253. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/qianfan/Qianfan_Chat.py +0 -0
  254. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/qianfan/Qianfan_embeddings.py +0 -0
  255. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/qianfan/__init__.py +0 -0
  256. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/qianwen/QianwenAI_chat.py +0 -0
  257. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/qianwen/QianwenAI_embeddings.py +0 -0
  258. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/qianwen/__init__.py +0 -0
  259. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/remote.py +0 -0
  260. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/types/__init__.py +0 -0
  261. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/utils.py +0 -0
  262. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/vannadb/__init__.py +0 -0
  263. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/vannadb/vannadb_vector.py +0 -0
  264. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/vllm/__init__.py +0 -0
  265. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/vllm/vllm.py +0 -0
  266. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/weaviate/__init__.py +0 -0
  267. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/weaviate/weaviate_vector.py +0 -0
  268. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/xinference/__init__.py +0 -0
  269. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/legacy/xinference/xinference.py +0 -0
  270. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/py.typed +0 -0
  271. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/servers/__init__.py +0 -0
  272. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/servers/__main__.py +0 -0
  273. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/servers/base/__init__.py +0 -0
  274. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/servers/base/chat_handler.py +0 -0
  275. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/servers/base/models.py +0 -0
  276. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/servers/base/rich_chat_handler.py +0 -0
  277. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/servers/base/templates.py +0 -0
  278. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/servers/cli/__init__.py +0 -0
  279. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/servers/cli/server_runner.py +0 -0
  280. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/servers/fastapi/__init__.py +0 -0
  281. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/servers/fastapi/routes.py +0 -0
  282. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/servers/flask/__init__.py +0 -0
  283. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/servers/flask/app.py +0 -0
  284. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/servers/flask/routes.py +0 -0
  285. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/tools/__init__.py +0 -0
  286. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/tools/file_system.py +0 -0
  287. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/tools/python.py +0 -0
  288. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/tools/visualize_data.py +0 -0
  289. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/utils/__init__.py +0 -0
  290. {asksql-2.1.0 → asksql-2.2.0}/src/vanna/web_components/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: asksql
3
- Version: 2.1.0
3
+ Version: 2.2.0
4
4
  Summary: Community-maintained, security-focused fork of Vanna: generate SQL from natural language. Drop-in: keeps `import vanna`.
5
5
  Author-email: Zain Hoda <zain@vanna.ai>
6
6
  Maintainer: Ayush Srivastava
@@ -176,6 +176,11 @@ Provides-Extra: zhipuai
176
176
 
177
177
  # asksql
178
178
 
179
+ [![PyPI](https://img.shields.io/pypi/v/asksql.svg)](https://pypi.org/project/asksql/)
180
+ [![Tests](https://github.com/buhtig47/vanna/actions/workflows/tests.yml/badge.svg)](https://github.com/buhtig47/vanna/actions/workflows/tests.yml)
181
+ [![Python](https://img.shields.io/badge/python-3.10%E2%80%933.13-blue.svg)](https://github.com/buhtig47/vanna/actions/workflows/tests.yml)
182
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/buhtig47/vanna/blob/main/LICENSE)
183
+
179
184
  **The community-maintained, security-focused continuation of [Vanna](https://github.com/vanna-ai/vanna).**
180
185
 
181
186
  > 🚧 **Work in progress.** Vanna was archived on 29 March 2026 with ~227 open issues. asksql picks it up: security fixes first, then bug fixes and dependency updates.
@@ -187,7 +192,11 @@ pip uninstall vanna
187
192
  pip install asksql
188
193
  ```
189
194
 
190
- asksql keeps the `vanna` import name (like Pillow did for PIL), so `import vanna`, `from vanna import Agent` and the legacy 1.x `VannaBase` API work as before. Don't install both packages in the same environment.
195
+ asksql keeps the `vanna` import name (like Pillow did for PIL), so `import vanna`, `from vanna import Agent` and the legacy `VannaBase` API work as before. Uninstall Vanna **before** installing asksql, because both ship the same `vanna/` folder.
196
+
197
+ 📘 **[Migration guide](https://github.com/buhtig47/vanna/blob/main/MIGRATING_FROM_VANNA.md)**: requirements files, coming from Vanna 0.x, behaviour changes, troubleshooting.
198
+
199
+ 🔌 **[Server API](https://github.com/buhtig47/vanna/blob/main/SERVER_API.md)**: HTTP endpoints (`/health`, `chat_poll`, `chat_sse`), request/response format, CORS.
191
200
 
192
201
  ### What's fixed so far
193
202
 
@@ -1,5 +1,10 @@
1
1
  # asksql
2
2
 
3
+ [![PyPI](https://img.shields.io/pypi/v/asksql.svg)](https://pypi.org/project/asksql/)
4
+ [![Tests](https://github.com/buhtig47/vanna/actions/workflows/tests.yml/badge.svg)](https://github.com/buhtig47/vanna/actions/workflows/tests.yml)
5
+ [![Python](https://img.shields.io/badge/python-3.10%E2%80%933.13-blue.svg)](https://github.com/buhtig47/vanna/actions/workflows/tests.yml)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/buhtig47/vanna/blob/main/LICENSE)
7
+
3
8
  **The community-maintained, security-focused continuation of [Vanna](https://github.com/vanna-ai/vanna).**
4
9
 
5
10
  > 🚧 **Work in progress.** Vanna was archived on 29 March 2026 with ~227 open issues. asksql picks it up: security fixes first, then bug fixes and dependency updates.
@@ -11,7 +16,11 @@ pip uninstall vanna
11
16
  pip install asksql
12
17
  ```
13
18
 
14
- asksql keeps the `vanna` import name (like Pillow did for PIL), so `import vanna`, `from vanna import Agent` and the legacy 1.x `VannaBase` API work as before. Don't install both packages in the same environment.
19
+ asksql keeps the `vanna` import name (like Pillow did for PIL), so `import vanna`, `from vanna import Agent` and the legacy `VannaBase` API work as before. Uninstall Vanna **before** installing asksql, because both ship the same `vanna/` folder.
20
+
21
+ 📘 **[Migration guide](https://github.com/buhtig47/vanna/blob/main/MIGRATING_FROM_VANNA.md)**: requirements files, coming from Vanna 0.x, behaviour changes, troubleshooting.
22
+
23
+ 🔌 **[Server API](https://github.com/buhtig47/vanna/blob/main/SERVER_API.md)**: HTTP endpoints (`/health`, `chat_poll`, `chat_sse`), request/response format, CORS.
15
24
 
16
25
  ### What's fixed so far
17
26
 
@@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi"
4
4
 
5
5
  [project]
6
6
  name = "asksql"
7
- version = "2.1.0"
7
+ version = "2.2.0"
8
8
  authors = [
9
9
  { name="Zain Hoda", email="zain@vanna.ai" },
10
10
  ]
@@ -6,7 +6,7 @@ with tool execution, conversation management, and user scoping.
6
6
  """
7
7
 
8
8
  # Version information
9
- __version__ = "2.1.0" # keep in sync with pyproject.toml
9
+ __version__ = "2.2.0" # keep in sync with pyproject.toml
10
10
 
11
11
  # Import core framework components
12
12
  from .core import (
@@ -980,8 +980,35 @@ class Agent:
980
980
 
981
981
  if has_tool_error_access:
982
982
  yield result.ui_component
983
+ elif result.metadata.get("contains_schema_details"):
984
+ # Table/column listings from schema lookups are only shown to
985
+ # users with the "schema_details" UI feature (vanna-ai/vanna#1105)
986
+ has_schema_access = (
987
+ self.config.ui_features.can_user_access_feature(
988
+ UiFeature.UI_FEATURE_SHOW_SCHEMA_DETAILS, user
989
+ )
990
+ )
991
+
992
+ if (
993
+ self.audit_logger
994
+ and self.config.audit_config.enabled
995
+ and self.config.audit_config.log_ui_feature_checks
996
+ ):
997
+ await self.audit_logger.log_ui_feature_access(
998
+ user=user,
999
+ feature_name=UiFeature.UI_FEATURE_SHOW_SCHEMA_DETAILS,
1000
+ access_granted=has_schema_access,
1001
+ required_groups=self.config.ui_features.feature_group_access.get(
1002
+ UiFeature.UI_FEATURE_SHOW_SCHEMA_DETAILS, []
1003
+ ),
1004
+ conversation_id=conversation.id,
1005
+ request_id=request_id,
1006
+ )
1007
+
1008
+ if has_schema_access:
1009
+ yield result.ui_component
983
1010
  else:
984
- # Success results are always shown if they exist
1011
+ # Other success results are always shown if they exist
985
1012
  yield result.ui_component
986
1013
 
987
1014
  # Collect tool result data
@@ -6,7 +6,7 @@ This module contains configuration models that control agent behavior.
6
6
 
7
7
  from typing import TYPE_CHECKING, Dict, List, Optional
8
8
 
9
- from pydantic import BaseModel, Field
9
+ from pydantic import BaseModel, Field, field_validator
10
10
 
11
11
  from .._compat import StrEnum
12
12
 
@@ -20,6 +20,8 @@ class UiFeature(StrEnum):
20
20
  UI_FEATURE_SHOW_TOOL_ERROR = "tool_error"
21
21
  UI_FEATURE_SHOW_TOOL_INVOCATION_MESSAGE_IN_CHAT = "tool_invocation_message_in_chat"
22
22
  UI_FEATURE_SHOW_MEMORY_DETAILED_RESULTS = "memory_detailed_results"
23
+ # Results of queries that inspect the database structure (tables, columns)
24
+ UI_FEATURE_SHOW_SCHEMA_DETAILS = "schema_details"
23
25
 
24
26
 
25
27
  # Optional: you can also define defaults if you want a shared baseline
@@ -29,6 +31,7 @@ DEFAULT_UI_FEATURES: Dict[str, List[str]] = {
29
31
  UiFeature.UI_FEATURE_SHOW_TOOL_ERROR: ["admin"],
30
32
  UiFeature.UI_FEATURE_SHOW_TOOL_INVOCATION_MESSAGE_IN_CHAT: ["admin"],
31
33
  UiFeature.UI_FEATURE_SHOW_MEMORY_DETAILED_RESULTS: ["admin"],
34
+ UiFeature.UI_FEATURE_SHOW_SCHEMA_DETAILS: ["admin"],
32
35
  }
33
36
 
34
37
 
@@ -46,6 +49,19 @@ class UiFeatures(BaseModel):
46
49
  description="Which groups can access UI features",
47
50
  )
48
51
 
52
+ @field_validator("feature_group_access")
53
+ @classmethod
54
+ def _add_new_default_features(
55
+ cls, value: Dict[str, List[str]]
56
+ ) -> Dict[str, List[str]]:
57
+ # Configs written before "schema_details" existed would otherwise hide it from
58
+ # everyone (unknown features are denied), so give it its default groups.
59
+ value.setdefault(
60
+ UiFeature.UI_FEATURE_SHOW_SCHEMA_DETAILS,
61
+ list(DEFAULT_UI_FEATURES[UiFeature.UI_FEATURE_SHOW_SCHEMA_DETAILS]),
62
+ )
63
+ return value
64
+
49
65
  def can_user_access_feature(self, feature_name: str, user: "User") -> bool:
50
66
  """Check if user can access a UI feature using same logic as tools.
51
67
 
@@ -158,6 +158,10 @@ class DefaultWorkflowHandler(WorkflowHandler):
158
158
  memory_id = message.strip()[8:].strip() # Extract ID after "/delete "
159
159
  return await self._delete_memory(agent, user, conversation, memory_id)
160
160
 
161
+ if message.strip().lower().startswith("/save_memory "):
162
+ pending_id = message.strip()[len("/save_memory ") :].strip()
163
+ return await self._approve_memory(agent, user, conversation, pending_id)
164
+
161
165
  # Don't handle other messages, pass to LLM
162
166
  return WorkflowResult(should_skip_llm=False)
163
167
 
@@ -680,6 +684,53 @@ class DefaultWorkflowHandler(WorkflowHandler):
680
684
  ],
681
685
  )
682
686
 
687
+ async def _approve_memory(
688
+ self,
689
+ agent: "Agent",
690
+ user: "User",
691
+ conversation: "Conversation",
692
+ pending_id: str,
693
+ ) -> WorkflowResult:
694
+ """Save a memory the user approved with the "Save to memory" button (vanna-ai/vanna#1103)."""
695
+ from vanna.core.tool import ToolContext
696
+ from vanna.tools.agent_memory import SaveQuestionToolArgsTool
697
+
698
+ def reply(content: str) -> WorkflowResult:
699
+ return WorkflowResult(
700
+ should_skip_llm=True,
701
+ components=[
702
+ UiComponent(
703
+ rich_component=RichTextComponent(
704
+ content=content, markdown=True
705
+ ),
706
+ simple_component=None,
707
+ )
708
+ ],
709
+ )
710
+
711
+ tool = await agent.tool_registry.get_tool("save_question_tool_args")
712
+ # register_local_tool(..., access_groups=[...]) wraps the tool
713
+ tool = getattr(tool, "_wrapped_tool", tool)
714
+ if not isinstance(tool, SaveQuestionToolArgsTool) or not getattr(
715
+ agent, "agent_memory", None
716
+ ):
717
+ return reply("# ⚠️ Nothing to save\n\nMemory saving is not configured.")
718
+
719
+ context = ToolContext(
720
+ user=user,
721
+ conversation_id=conversation.id,
722
+ request_id=str(uuid.uuid4()),
723
+ agent_memory=agent.agent_memory,
724
+ )
725
+ if await tool.approve(pending_id, context):
726
+ return reply(
727
+ "# ✅ Saved to memory\n\nSimilar questions will reuse this query."
728
+ )
729
+ return reply(
730
+ "# ❌ Nothing to save\n\nThis save request has expired, was already saved, "
731
+ "or belongs to another user."
732
+ )
733
+
683
734
  async def _delete_memory(
684
735
  self, agent: "Agent", user: "User", conversation: "Conversation", memory_id: str
685
736
  ) -> WorkflowResult:
@@ -87,11 +87,10 @@ class PostgresRunner(SqlRunner):
87
87
  # Execute the query
88
88
  cursor.execute(args.sql)
89
89
 
90
- # Determine if this is a SELECT query or modification query
91
- query_type = args.sql.strip().upper().split()[0]
92
-
93
- if query_type == "SELECT":
94
- # Fetch results for SELECT queries
90
+ # DB-API: description is set only for statements that return rows. Checking the
91
+ # first word missed WITH ... SELECT and queries starting with a comment.
92
+ if cursor.description is not None:
93
+ # Fetch results for queries that return rows
95
94
  rows = cursor.fetchall()
96
95
  if not rows:
97
96
  # Return empty DataFrame
@@ -40,11 +40,10 @@ class SqliteRunner(SqlRunner):
40
40
  # Execute the query
41
41
  cursor.execute(args.sql)
42
42
 
43
- # Determine if this is a SELECT query or modification query
44
- query_type = args.sql.strip().upper().split()[0]
45
-
46
- if query_type == "SELECT":
47
- # Fetch results for SELECT queries
43
+ # DB-API: description is set only for statements that return rows. Checking the
44
+ # first word missed WITH ... SELECT and queries starting with a comment.
45
+ if cursor.description is not None:
46
+ # Fetch results for queries that return rows
48
47
  rows = cursor.fetchall()
49
48
  if not rows:
50
49
  # Return empty DataFrame
@@ -125,17 +125,18 @@ class LegacyVannaAdapter(ToolRegistry, AgentMemory):
125
125
  sql_runner = LegacySqlRunner(self.vn)
126
126
 
127
127
  # Register the RunSqlTool with user and admin access
128
+ config = getattr(self.vn, "config", None) or {}
128
129
  run_sql_tool = RunSqlTool(
129
130
  sql_runner,
130
- allow_write_sql=bool(
131
- (getattr(self.vn, "config", None) or {}).get("allow_write_sql", False)
132
- ),
131
+ allow_write_sql=bool(config.get("allow_write_sql", False)),
133
132
  )
134
133
  self.register_local_tool(run_sql_tool, access_groups=["user", "admin"])
135
134
 
136
135
  # Register memory tools using the internal _agent_memory instance
137
136
  # SaveQuestionToolArgsTool - for saving question-tool-args patterns (admin only)
138
- save_memory_tool = SaveQuestionToolArgsTool()
137
+ save_memory_tool = SaveQuestionToolArgsTool(
138
+ require_approval=bool(config.get("require_memory_approval", False))
139
+ )
139
140
  self.register_local_tool(save_memory_tool, access_groups=["admin"])
140
141
 
141
142
  # SearchSavedCorrectToolUsesTool - for searching similar patterns (user and admin)
@@ -2,6 +2,7 @@
2
2
  FastAPI server factory for Vanna Agents.
3
3
  """
4
4
 
5
+ import warnings
5
6
  from typing import Any, Dict, Optional
6
7
 
7
8
  from fastapi import FastAPI
@@ -49,7 +50,18 @@ class VannaFastAPIServer:
49
50
 
50
51
  # Set sensible defaults
51
52
  cors_params.setdefault("allow_origins", ["*"])
52
- cors_params.setdefault("allow_credentials", True)
53
+ # Wildcard origins + credentials lets ANY website send requests with the
54
+ # user's cookies and read the answers. Only allow credentials by default
55
+ # when the origins are an explicit allow-list.
56
+ wildcard = "*" in cors_params["allow_origins"]
57
+ cors_params.setdefault("allow_credentials", not wildcard)
58
+ if wildcard and cors_params["allow_credentials"]:
59
+ warnings.warn(
60
+ "CORS allow_origins=['*'] with allow_credentials=True lets any website "
61
+ "call this server with your users' cookies. List your frontend origins "
62
+ "in config['cors']['allow_origins'] instead.",
63
+ stacklevel=2,
64
+ )
53
65
  cors_params.setdefault("allow_methods", ["*"])
54
66
  cors_params.setdefault("allow_headers", ["*"])
55
67
 
@@ -7,7 +7,9 @@ The tools access AgentMemory via ToolContext, which is populated by the Agent.
7
7
  """
8
8
 
9
9
  import logging
10
- from typing import Any, Dict, List, Optional, Type
10
+ import uuid
11
+ from collections import OrderedDict
12
+ from typing import Any, Dict, List, Optional, Tuple, Type
11
13
  from pydantic import BaseModel, Field
12
14
 
13
15
  logger = logging.getLogger(__name__)
@@ -19,8 +21,14 @@ from vanna.components import (
19
21
  UiComponent,
20
22
  StatusBarUpdateComponent,
21
23
  CardComponent,
24
+ ButtonComponent,
25
+ SimpleTextComponent,
22
26
  )
23
27
 
28
+ # Chat command the "Save to memory" button sends (handled by DefaultWorkflowHandler)
29
+ SAVE_MEMORY_COMMAND = "/save_memory"
30
+ MAX_PENDING_APPROVALS = 1000
31
+
24
32
 
25
33
  class SaveQuestionToolArgsParams(BaseModel):
26
34
  """Parameters for saving question-tool-argument combinations."""
@@ -58,7 +66,66 @@ class SaveTextMemoryParams(BaseModel):
58
66
 
59
67
 
60
68
  class SaveQuestionToolArgsTool(Tool[SaveQuestionToolArgsParams]):
61
- """Tool for saving successful question-tool-argument combinations."""
69
+ """Tool for saving successful question-tool-argument combinations.
70
+
71
+ Args:
72
+ require_approval: If True, the LLM can only *propose* a memory. The user
73
+ sees a "Save to memory" button and the pattern is saved only when
74
+ they click it. Prevents SQL that ran but returned wrong data from
75
+ being saved and reused (vanna-ai/vanna#1103).
76
+ """
77
+
78
+ def __init__(self, require_approval: bool = False):
79
+ self.require_approval = require_approval
80
+ # pending_id -> (user_id, args); bounded so it can't grow forever
81
+ self._pending: "OrderedDict[str, Tuple[str, SaveQuestionToolArgsParams]]" = (
82
+ OrderedDict()
83
+ )
84
+
85
+ async def approve(self, pending_id: str, context: ToolContext) -> bool:
86
+ """Save a pending memory. Only the user it was proposed to can approve it, once."""
87
+ pending = self._pending.get(pending_id)
88
+ if pending is None or pending[0] != context.user.id:
89
+ return False
90
+ del self._pending[pending_id]
91
+ _, args = pending
92
+ await context.agent_memory.save_tool_usage(
93
+ question=args.question,
94
+ tool_name=args.tool_name,
95
+ args=args.args,
96
+ context=context,
97
+ success=True,
98
+ )
99
+ return True
100
+
101
+ def _propose(
102
+ self, context: ToolContext, args: SaveQuestionToolArgsParams
103
+ ) -> ToolResult:
104
+ pending_id = uuid.uuid4().hex[:12]
105
+ self._pending[pending_id] = (context.user.id, args)
106
+ while len(self._pending) > MAX_PENDING_APPROVALS:
107
+ self._pending.popitem(last=False)
108
+
109
+ command = f"{SAVE_MEMORY_COMMAND} {pending_id}"
110
+ return ToolResult(
111
+ success=True,
112
+ result_for_llm=(
113
+ "Not saved yet: the user must approve saving this pattern to memory. "
114
+ "A 'Save to memory' button has been shown to them. Do not call this tool again for it."
115
+ ),
116
+ ui_component=UiComponent(
117
+ rich_component=ButtonComponent(
118
+ label="Save to memory",
119
+ action=command,
120
+ variant="secondary",
121
+ size="small",
122
+ icon="👍",
123
+ ),
124
+ simple_component=SimpleTextComponent(
125
+ text=f"If this answer is correct, send `{command}` to save it to memory."
126
+ ),
127
+ ),
128
+ )
62
129
 
63
130
  @property
64
131
  def name(self) -> str:
@@ -77,6 +144,9 @@ class SaveQuestionToolArgsTool(Tool[SaveQuestionToolArgsParams]):
77
144
  self, context: ToolContext, args: SaveQuestionToolArgsParams
78
145
  ) -> ToolResult:
79
146
  """Save the tool usage pattern to agent memory."""
147
+ if self.require_approval:
148
+ return self._propose(context, args)
149
+
80
150
  try:
81
151
  await context.agent_memory.save_tool_usage(
82
152
  question=args.question,
@@ -1,6 +1,7 @@
1
1
  """Generic SQL query execution tool with dependency injection."""
2
2
 
3
3
  from typing import Any, Dict, List, Optional, Type, cast
4
+ import re
4
5
  import uuid
5
6
  from vanna.core.tool import Tool, ToolContext, ToolResult
6
7
  from vanna.components import (
@@ -13,7 +14,22 @@ from vanna.components import (
13
14
  from vanna.capabilities.sql_runner import SqlRunner, RunSqlToolArgs
14
15
  from vanna.capabilities.file_system import FileSystem
15
16
  from vanna.integrations.local import LocalFileSystem
16
- from vanna.utils.sql_safety import ensure_read_only_sql
17
+ from vanna.utils.sql_safety import ensure_read_only_sql, sql_returns_rows
18
+
19
+ # Catalog tables/commands that describe the database structure rather than business data
20
+ _SCHEMA_QUERY = re.compile(
21
+ r"\b(information_schema|sqlite_master|sqlite_schema|pg_catalog|pg_tables|pg_class"
22
+ r"|pg_attribute|sys\.(tables|columns|objects|schemas)|all_tables|user_tables"
23
+ r"|all_tab_columns|user_tab_columns|all_views|system\.(tables|columns)"
24
+ r"|duckdb_tables|duckdb_columns|table_info|pragma_\w+)\b"
25
+ r"|^\s*(show|describe|desc|pragma)\b",
26
+ re.IGNORECASE,
27
+ )
28
+
29
+
30
+ def is_schema_query(sql: str) -> bool:
31
+ """True if the query inspects database structure (tables, columns) - vanna-ai/vanna#1105."""
32
+ return bool(_SCHEMA_QUERY.search(sql or ""))
17
33
 
18
34
 
19
35
  class RunSqlTool(Tool[RunSqlToolArgs]):
@@ -70,7 +86,7 @@ class RunSqlTool(Tool[RunSqlToolArgs]):
70
86
  # Determine query type
71
87
  query_type = args.sql.strip().upper().split()[0]
72
88
 
73
- if query_type == "SELECT":
89
+ if sql_returns_rows(args.sql):
74
90
  # Handle SELECT queries with results
75
91
  if df.empty:
76
92
  result = "Query executed successfully. No rows returned."
@@ -135,7 +151,11 @@ class RunSqlTool(Tool[RunSqlToolArgs]):
135
151
  else:
136
152
  # For non-SELECT queries (INSERT, UPDATE, DELETE, etc.)
137
153
  # The SqlRunner should return a DataFrame with affected row count
138
- rows_affected = len(df) if not df.empty else 0
154
+ if "rows_affected" in df.columns and not df.empty:
155
+ # Built-in runners report the count as a one-row DataFrame
156
+ rows_affected = int(df["rows_affected"].iloc[0])
157
+ else:
158
+ rows_affected = len(df)
139
159
  result = (
140
160
  f"Query executed successfully. {rows_affected} row(s) affected."
141
161
  )
@@ -148,6 +168,9 @@ class RunSqlTool(Tool[RunSqlToolArgs]):
148
168
  simple_component=SimpleTextComponent(text=result),
149
169
  )
150
170
 
171
+ # The agent hides these results from users without the "schema_details" UI feature
172
+ metadata["contains_schema_details"] = is_schema_query(args.sql)
173
+
151
174
  return ToolResult(
152
175
  success=True,
153
176
  result_for_llm=result,
@@ -82,3 +82,22 @@ def ensure_read_only_sql(sql: str) -> None:
82
82
  word = token.value.strip('`"[]').upper()
83
83
  if word in DENIED_WORDS or word.startswith(DENIED_PREFIXES):
84
84
  raise UnsafeSqlError(f"'{token.value}' is not allowed. {WRITE_SQL_HINT}")
85
+
86
+
87
+ # Leading keywords of statements that return a result set but that sqlparse types as UNKNOWN
88
+ _ROW_RETURNING_KEYWORDS = {"SHOW", "DESCRIBE", "DESC", "EXPLAIN", "PRAGMA", "VALUES"}
89
+
90
+
91
+ def sql_returns_rows(sql: str) -> bool:
92
+ """True if `sql` returns a result set (SELECT, WITH ... SELECT, SHOW, ...).
93
+
94
+ Unlike checking the first word, this handles CTEs and leading comments.
95
+ """
96
+ statements = [s for s in sqlparse.parse(sql or "") if s.value.strip(" \t\r\n;")]
97
+ if not statements:
98
+ return False
99
+ statement = statements[-1]
100
+ if statement.get_type() == "SELECT":
101
+ return True
102
+ first = statement.token_first(skip_cm=True, skip_ws=True)
103
+ return first is not None and first.normalized.upper() in _ROW_RETURNING_KEYWORDS
File without changes
File without changes
File without changes