vanna 0.7.9__py3-none-any.whl → 2.0.0rc1__py3-none-any.whl

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 (302) hide show
  1. vanna/__init__.py +167 -395
  2. vanna/agents/__init__.py +7 -0
  3. vanna/capabilities/__init__.py +17 -0
  4. vanna/capabilities/agent_memory/__init__.py +21 -0
  5. vanna/capabilities/agent_memory/base.py +103 -0
  6. vanna/capabilities/agent_memory/models.py +53 -0
  7. vanna/capabilities/file_system/__init__.py +14 -0
  8. vanna/capabilities/file_system/base.py +71 -0
  9. vanna/capabilities/file_system/models.py +25 -0
  10. vanna/capabilities/sql_runner/__init__.py +13 -0
  11. vanna/capabilities/sql_runner/base.py +37 -0
  12. vanna/capabilities/sql_runner/models.py +13 -0
  13. vanna/components/__init__.py +92 -0
  14. vanna/components/base.py +11 -0
  15. vanna/components/rich/__init__.py +83 -0
  16. vanna/components/rich/containers/__init__.py +7 -0
  17. vanna/components/rich/containers/card.py +20 -0
  18. vanna/components/rich/data/__init__.py +9 -0
  19. vanna/components/rich/data/chart.py +17 -0
  20. vanna/components/rich/data/dataframe.py +93 -0
  21. vanna/components/rich/feedback/__init__.py +21 -0
  22. vanna/components/rich/feedback/badge.py +16 -0
  23. vanna/components/rich/feedback/icon_text.py +14 -0
  24. vanna/components/rich/feedback/log_viewer.py +41 -0
  25. vanna/components/rich/feedback/notification.py +19 -0
  26. vanna/components/rich/feedback/progress.py +37 -0
  27. vanna/components/rich/feedback/status_card.py +28 -0
  28. vanna/components/rich/feedback/status_indicator.py +14 -0
  29. vanna/components/rich/interactive/__init__.py +21 -0
  30. vanna/components/rich/interactive/button.py +95 -0
  31. vanna/components/rich/interactive/task_list.py +58 -0
  32. vanna/components/rich/interactive/ui_state.py +93 -0
  33. vanna/components/rich/specialized/__init__.py +7 -0
  34. vanna/components/rich/specialized/artifact.py +20 -0
  35. vanna/components/rich/text.py +16 -0
  36. vanna/components/simple/__init__.py +15 -0
  37. vanna/components/simple/image.py +15 -0
  38. vanna/components/simple/link.py +15 -0
  39. vanna/components/simple/text.py +11 -0
  40. vanna/core/__init__.py +193 -0
  41. vanna/core/_compat.py +19 -0
  42. vanna/core/agent/__init__.py +10 -0
  43. vanna/core/agent/agent.py +1407 -0
  44. vanna/core/agent/config.py +123 -0
  45. vanna/core/audit/__init__.py +28 -0
  46. vanna/core/audit/base.py +299 -0
  47. vanna/core/audit/models.py +131 -0
  48. vanna/core/component_manager.py +329 -0
  49. vanna/core/components.py +53 -0
  50. vanna/core/enhancer/__init__.py +11 -0
  51. vanna/core/enhancer/base.py +94 -0
  52. vanna/core/enhancer/default.py +118 -0
  53. vanna/core/enricher/__init__.py +10 -0
  54. vanna/core/enricher/base.py +59 -0
  55. vanna/core/errors.py +47 -0
  56. vanna/core/evaluation/__init__.py +81 -0
  57. vanna/core/evaluation/base.py +186 -0
  58. vanna/core/evaluation/dataset.py +254 -0
  59. vanna/core/evaluation/evaluators.py +376 -0
  60. vanna/core/evaluation/report.py +289 -0
  61. vanna/core/evaluation/runner.py +313 -0
  62. vanna/core/filter/__init__.py +10 -0
  63. vanna/core/filter/base.py +67 -0
  64. vanna/core/lifecycle/__init__.py +10 -0
  65. vanna/core/lifecycle/base.py +83 -0
  66. vanna/core/llm/__init__.py +16 -0
  67. vanna/core/llm/base.py +40 -0
  68. vanna/core/llm/models.py +61 -0
  69. vanna/core/middleware/__init__.py +10 -0
  70. vanna/core/middleware/base.py +69 -0
  71. vanna/core/observability/__init__.py +11 -0
  72. vanna/core/observability/base.py +88 -0
  73. vanna/core/observability/models.py +47 -0
  74. vanna/core/recovery/__init__.py +11 -0
  75. vanna/core/recovery/base.py +84 -0
  76. vanna/core/recovery/models.py +32 -0
  77. vanna/core/registry.py +278 -0
  78. vanna/core/rich_component.py +156 -0
  79. vanna/core/simple_component.py +27 -0
  80. vanna/core/storage/__init__.py +14 -0
  81. vanna/core/storage/base.py +46 -0
  82. vanna/core/storage/models.py +46 -0
  83. vanna/core/system_prompt/__init__.py +13 -0
  84. vanna/core/system_prompt/base.py +36 -0
  85. vanna/core/system_prompt/default.py +157 -0
  86. vanna/core/tool/__init__.py +18 -0
  87. vanna/core/tool/base.py +70 -0
  88. vanna/core/tool/models.py +84 -0
  89. vanna/core/user/__init__.py +17 -0
  90. vanna/core/user/base.py +29 -0
  91. vanna/core/user/models.py +25 -0
  92. vanna/core/user/request_context.py +70 -0
  93. vanna/core/user/resolver.py +42 -0
  94. vanna/core/validation.py +164 -0
  95. vanna/core/workflow/__init__.py +12 -0
  96. vanna/core/workflow/base.py +254 -0
  97. vanna/core/workflow/default.py +789 -0
  98. vanna/examples/__init__.py +1 -0
  99. vanna/examples/__main__.py +44 -0
  100. vanna/examples/anthropic_quickstart.py +80 -0
  101. vanna/examples/artifact_example.py +293 -0
  102. vanna/examples/claude_sqlite_example.py +236 -0
  103. vanna/examples/coding_agent_example.py +300 -0
  104. vanna/examples/custom_system_prompt_example.py +174 -0
  105. vanna/examples/default_workflow_handler_example.py +208 -0
  106. vanna/examples/email_auth_example.py +340 -0
  107. vanna/examples/evaluation_example.py +269 -0
  108. vanna/examples/extensibility_example.py +262 -0
  109. vanna/examples/minimal_example.py +67 -0
  110. vanna/examples/mock_auth_example.py +227 -0
  111. vanna/examples/mock_custom_tool.py +311 -0
  112. vanna/examples/mock_quickstart.py +79 -0
  113. vanna/examples/mock_quota_example.py +145 -0
  114. vanna/examples/mock_rich_components_demo.py +396 -0
  115. vanna/examples/mock_sqlite_example.py +223 -0
  116. vanna/examples/openai_quickstart.py +83 -0
  117. vanna/examples/primitive_components_demo.py +305 -0
  118. vanna/examples/quota_lifecycle_example.py +139 -0
  119. vanna/examples/visualization_example.py +251 -0
  120. vanna/integrations/__init__.py +17 -0
  121. vanna/integrations/anthropic/__init__.py +9 -0
  122. vanna/integrations/anthropic/llm.py +270 -0
  123. vanna/integrations/azureopenai/__init__.py +9 -0
  124. vanna/integrations/azureopenai/llm.py +329 -0
  125. vanna/integrations/azuresearch/__init__.py +7 -0
  126. vanna/integrations/azuresearch/agent_memory.py +413 -0
  127. vanna/integrations/bigquery/__init__.py +5 -0
  128. vanna/integrations/bigquery/sql_runner.py +81 -0
  129. vanna/integrations/chromadb/__init__.py +104 -0
  130. vanna/integrations/chromadb/agent_memory.py +416 -0
  131. vanna/integrations/clickhouse/__init__.py +5 -0
  132. vanna/integrations/clickhouse/sql_runner.py +82 -0
  133. vanna/integrations/duckdb/__init__.py +5 -0
  134. vanna/integrations/duckdb/sql_runner.py +65 -0
  135. vanna/integrations/faiss/__init__.py +7 -0
  136. vanna/integrations/faiss/agent_memory.py +431 -0
  137. vanna/integrations/google/__init__.py +9 -0
  138. vanna/integrations/google/gemini.py +370 -0
  139. vanna/integrations/hive/__init__.py +5 -0
  140. vanna/integrations/hive/sql_runner.py +87 -0
  141. vanna/integrations/local/__init__.py +17 -0
  142. vanna/integrations/local/agent_memory/__init__.py +7 -0
  143. vanna/integrations/local/agent_memory/in_memory.py +285 -0
  144. vanna/integrations/local/audit.py +59 -0
  145. vanna/integrations/local/file_system.py +242 -0
  146. vanna/integrations/local/file_system_conversation_store.py +255 -0
  147. vanna/integrations/local/storage.py +62 -0
  148. vanna/integrations/marqo/__init__.py +7 -0
  149. vanna/integrations/marqo/agent_memory.py +354 -0
  150. vanna/integrations/milvus/__init__.py +7 -0
  151. vanna/integrations/milvus/agent_memory.py +458 -0
  152. vanna/integrations/mock/__init__.py +9 -0
  153. vanna/integrations/mock/llm.py +65 -0
  154. vanna/integrations/mssql/__init__.py +5 -0
  155. vanna/integrations/mssql/sql_runner.py +66 -0
  156. vanna/integrations/mysql/__init__.py +5 -0
  157. vanna/integrations/mysql/sql_runner.py +92 -0
  158. vanna/integrations/ollama/__init__.py +7 -0
  159. vanna/integrations/ollama/llm.py +252 -0
  160. vanna/integrations/openai/__init__.py +10 -0
  161. vanna/integrations/openai/llm.py +267 -0
  162. vanna/integrations/openai/responses.py +163 -0
  163. vanna/integrations/opensearch/__init__.py +7 -0
  164. vanna/integrations/opensearch/agent_memory.py +411 -0
  165. vanna/integrations/oracle/__init__.py +5 -0
  166. vanna/integrations/oracle/sql_runner.py +75 -0
  167. vanna/integrations/pinecone/__init__.py +7 -0
  168. vanna/integrations/pinecone/agent_memory.py +329 -0
  169. vanna/integrations/plotly/__init__.py +5 -0
  170. vanna/integrations/plotly/chart_generator.py +313 -0
  171. vanna/integrations/postgres/__init__.py +9 -0
  172. vanna/integrations/postgres/sql_runner.py +112 -0
  173. vanna/integrations/premium/agent_memory/__init__.py +7 -0
  174. vanna/integrations/premium/agent_memory/premium.py +186 -0
  175. vanna/integrations/presto/__init__.py +5 -0
  176. vanna/integrations/presto/sql_runner.py +107 -0
  177. vanna/integrations/qdrant/__init__.py +7 -0
  178. vanna/integrations/qdrant/agent_memory.py +439 -0
  179. vanna/integrations/snowflake/__init__.py +5 -0
  180. vanna/integrations/snowflake/sql_runner.py +147 -0
  181. vanna/integrations/sqlite/__init__.py +9 -0
  182. vanna/integrations/sqlite/sql_runner.py +65 -0
  183. vanna/integrations/weaviate/__init__.py +7 -0
  184. vanna/integrations/weaviate/agent_memory.py +428 -0
  185. vanna/{ZhipuAI → legacy/ZhipuAI}/ZhipuAI_embeddings.py +11 -11
  186. vanna/legacy/__init__.py +403 -0
  187. vanna/legacy/adapter.py +463 -0
  188. vanna/{advanced → legacy/advanced}/__init__.py +3 -1
  189. vanna/{anthropic → legacy/anthropic}/anthropic_chat.py +9 -7
  190. vanna/{azuresearch → legacy/azuresearch}/azuresearch_vector.py +79 -41
  191. vanna/{base → legacy/base}/base.py +224 -217
  192. vanna/legacy/bedrock/__init__.py +1 -0
  193. vanna/{bedrock → legacy/bedrock}/bedrock_converse.py +13 -12
  194. vanna/{chromadb → legacy/chromadb}/chromadb_vector.py +3 -1
  195. vanna/legacy/cohere/__init__.py +2 -0
  196. vanna/{cohere → legacy/cohere}/cohere_chat.py +19 -14
  197. vanna/{cohere → legacy/cohere}/cohere_embeddings.py +25 -19
  198. vanna/{deepseek → legacy/deepseek}/deepseek_chat.py +5 -6
  199. vanna/legacy/faiss/__init__.py +1 -0
  200. vanna/{faiss → legacy/faiss}/faiss.py +113 -59
  201. vanna/{flask → legacy/flask}/__init__.py +84 -43
  202. vanna/{flask → legacy/flask}/assets.py +5 -5
  203. vanna/{flask → legacy/flask}/auth.py +5 -4
  204. vanna/{google → legacy/google}/bigquery_vector.py +75 -42
  205. vanna/{google → legacy/google}/gemini_chat.py +7 -3
  206. vanna/{hf → legacy/hf}/hf.py +0 -1
  207. vanna/{milvus → legacy/milvus}/milvus_vector.py +58 -35
  208. vanna/{mock → legacy/mock}/llm.py +0 -1
  209. vanna/legacy/mock/vectordb.py +67 -0
  210. vanna/legacy/ollama/ollama.py +110 -0
  211. vanna/{openai → legacy/openai}/openai_chat.py +2 -6
  212. vanna/legacy/opensearch/opensearch_vector.py +369 -0
  213. vanna/legacy/opensearch/opensearch_vector_semantic.py +200 -0
  214. vanna/legacy/oracle/oracle_vector.py +584 -0
  215. vanna/{pgvector → legacy/pgvector}/pgvector.py +42 -13
  216. vanna/{qdrant → legacy/qdrant}/qdrant.py +2 -6
  217. vanna/legacy/qianfan/Qianfan_Chat.py +170 -0
  218. vanna/legacy/qianfan/Qianfan_embeddings.py +36 -0
  219. vanna/legacy/qianwen/QianwenAI_chat.py +132 -0
  220. vanna/{remote.py → legacy/remote.py} +28 -26
  221. vanna/{utils.py → legacy/utils.py} +6 -11
  222. vanna/{vannadb → legacy/vannadb}/vannadb_vector.py +115 -46
  223. vanna/{vllm → legacy/vllm}/vllm.py +5 -6
  224. vanna/{weaviate → legacy/weaviate}/weaviate_vector.py +59 -40
  225. vanna/{xinference → legacy/xinference}/xinference.py +6 -6
  226. vanna/py.typed +0 -0
  227. vanna/servers/__init__.py +16 -0
  228. vanna/servers/__main__.py +8 -0
  229. vanna/servers/base/__init__.py +18 -0
  230. vanna/servers/base/chat_handler.py +65 -0
  231. vanna/servers/base/models.py +111 -0
  232. vanna/servers/base/rich_chat_handler.py +141 -0
  233. vanna/servers/base/templates.py +331 -0
  234. vanna/servers/cli/__init__.py +7 -0
  235. vanna/servers/cli/server_runner.py +204 -0
  236. vanna/servers/fastapi/__init__.py +7 -0
  237. vanna/servers/fastapi/app.py +163 -0
  238. vanna/servers/fastapi/routes.py +183 -0
  239. vanna/servers/flask/__init__.py +7 -0
  240. vanna/servers/flask/app.py +132 -0
  241. vanna/servers/flask/routes.py +137 -0
  242. vanna/tools/__init__.py +41 -0
  243. vanna/tools/agent_memory.py +322 -0
  244. vanna/tools/file_system.py +879 -0
  245. vanna/tools/python.py +222 -0
  246. vanna/tools/run_sql.py +165 -0
  247. vanna/tools/visualize_data.py +195 -0
  248. vanna/utils/__init__.py +0 -0
  249. vanna/web_components/__init__.py +44 -0
  250. vanna-2.0.0rc1.dist-info/METADATA +868 -0
  251. vanna-2.0.0rc1.dist-info/RECORD +289 -0
  252. vanna-2.0.0rc1.dist-info/entry_points.txt +3 -0
  253. vanna/bedrock/__init__.py +0 -1
  254. vanna/cohere/__init__.py +0 -2
  255. vanna/faiss/__init__.py +0 -1
  256. vanna/mock/vectordb.py +0 -55
  257. vanna/ollama/ollama.py +0 -103
  258. vanna/opensearch/opensearch_vector.py +0 -392
  259. vanna/opensearch/opensearch_vector_semantic.py +0 -175
  260. vanna/oracle/oracle_vector.py +0 -585
  261. vanna/qianfan/Qianfan_Chat.py +0 -165
  262. vanna/qianfan/Qianfan_embeddings.py +0 -36
  263. vanna/qianwen/QianwenAI_chat.py +0 -133
  264. vanna-0.7.9.dist-info/METADATA +0 -408
  265. vanna-0.7.9.dist-info/RECORD +0 -79
  266. /vanna/{ZhipuAI → legacy/ZhipuAI}/ZhipuAI_Chat.py +0 -0
  267. /vanna/{ZhipuAI → legacy/ZhipuAI}/__init__.py +0 -0
  268. /vanna/{anthropic → legacy/anthropic}/__init__.py +0 -0
  269. /vanna/{azuresearch → legacy/azuresearch}/__init__.py +0 -0
  270. /vanna/{base → legacy/base}/__init__.py +0 -0
  271. /vanna/{chromadb → legacy/chromadb}/__init__.py +0 -0
  272. /vanna/{deepseek → legacy/deepseek}/__init__.py +0 -0
  273. /vanna/{exceptions → legacy/exceptions}/__init__.py +0 -0
  274. /vanna/{google → legacy/google}/__init__.py +0 -0
  275. /vanna/{hf → legacy/hf}/__init__.py +0 -0
  276. /vanna/{local.py → legacy/local.py} +0 -0
  277. /vanna/{marqo → legacy/marqo}/__init__.py +0 -0
  278. /vanna/{marqo → legacy/marqo}/marqo.py +0 -0
  279. /vanna/{milvus → legacy/milvus}/__init__.py +0 -0
  280. /vanna/{mistral → legacy/mistral}/__init__.py +0 -0
  281. /vanna/{mistral → legacy/mistral}/mistral.py +0 -0
  282. /vanna/{mock → legacy/mock}/__init__.py +0 -0
  283. /vanna/{mock → legacy/mock}/embedding.py +0 -0
  284. /vanna/{ollama → legacy/ollama}/__init__.py +0 -0
  285. /vanna/{openai → legacy/openai}/__init__.py +0 -0
  286. /vanna/{openai → legacy/openai}/openai_embeddings.py +0 -0
  287. /vanna/{opensearch → legacy/opensearch}/__init__.py +0 -0
  288. /vanna/{oracle → legacy/oracle}/__init__.py +0 -0
  289. /vanna/{pgvector → legacy/pgvector}/__init__.py +0 -0
  290. /vanna/{pinecone → legacy/pinecone}/__init__.py +0 -0
  291. /vanna/{pinecone → legacy/pinecone}/pinecone_vector.py +0 -0
  292. /vanna/{qdrant → legacy/qdrant}/__init__.py +0 -0
  293. /vanna/{qianfan → legacy/qianfan}/__init__.py +0 -0
  294. /vanna/{qianwen → legacy/qianwen}/QianwenAI_embeddings.py +0 -0
  295. /vanna/{qianwen → legacy/qianwen}/__init__.py +0 -0
  296. /vanna/{types → legacy/types}/__init__.py +0 -0
  297. /vanna/{vannadb → legacy/vannadb}/__init__.py +0 -0
  298. /vanna/{vllm → legacy/vllm}/__init__.py +0 -0
  299. /vanna/{weaviate → legacy/weaviate}/__init__.py +0 -0
  300. /vanna/{xinference → legacy/xinference}/__init__.py +0 -0
  301. {vanna-0.7.9.dist-info → vanna-2.0.0rc1.dist-info}/WHEEL +0 -0
  302. {vanna-0.7.9.dist-info → vanna-2.0.0rc1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,255 @@
1
+ """
2
+ File system conversation store implementation.
3
+
4
+ This module provides a file-based implementation of the ConversationStore
5
+ interface that persists conversations to disk as a directory structure.
6
+ """
7
+
8
+ import json
9
+ import os
10
+ from pathlib import Path
11
+ from typing import Dict, List, Optional
12
+ from datetime import datetime
13
+ import time
14
+
15
+ from vanna.core.storage import ConversationStore, Conversation, Message
16
+ from vanna.core.user import User
17
+
18
+
19
+ class FileSystemConversationStore(ConversationStore):
20
+ """File system-based conversation store.
21
+
22
+ Stores conversations as directories with individual message files:
23
+ conversations/{conversation_id}/
24
+ metadata.json - conversation metadata (id, user info, timestamps)
25
+ messages/
26
+ {timestamp}_{index}.json - individual message files
27
+ """
28
+
29
+ def __init__(self, base_dir: str = "conversations") -> None:
30
+ """Initialize the file system conversation store.
31
+
32
+ Args:
33
+ base_dir: Base directory for storing conversations
34
+ """
35
+ self.base_dir = Path(base_dir)
36
+ self.base_dir.mkdir(parents=True, exist_ok=True)
37
+
38
+ def _get_conversation_dir(self, conversation_id: str) -> Path:
39
+ """Get the directory path for a conversation."""
40
+ return self.base_dir / conversation_id
41
+
42
+ def _get_metadata_path(self, conversation_id: str) -> Path:
43
+ """Get the metadata file path for a conversation."""
44
+ return self._get_conversation_dir(conversation_id) / "metadata.json"
45
+
46
+ def _get_messages_dir(self, conversation_id: str) -> Path:
47
+ """Get the messages directory for a conversation."""
48
+ return self._get_conversation_dir(conversation_id) / "messages"
49
+
50
+ def _save_metadata(self, conversation: Conversation) -> None:
51
+ """Save conversation metadata to disk."""
52
+ conv_dir = self._get_conversation_dir(conversation.id)
53
+ conv_dir.mkdir(parents=True, exist_ok=True)
54
+
55
+ metadata = {
56
+ "id": conversation.id,
57
+ "user": conversation.user.model_dump(mode="json"),
58
+ "created_at": conversation.created_at.isoformat(),
59
+ "updated_at": conversation.updated_at.isoformat(),
60
+ }
61
+
62
+ metadata_path = self._get_metadata_path(conversation.id)
63
+ with open(metadata_path, "w") as f:
64
+ json.dump(metadata, f, indent=2)
65
+
66
+ def _load_messages(self, conversation_id: str) -> List[Message]:
67
+ """Load all messages for a conversation."""
68
+ messages_dir = self._get_messages_dir(conversation_id)
69
+
70
+ if not messages_dir.exists():
71
+ return []
72
+
73
+ messages = []
74
+ # Sort message files by name (timestamp_index ensures correct order)
75
+ message_files = sorted(messages_dir.glob("*.json"))
76
+
77
+ for file_path in message_files:
78
+ try:
79
+ with open(file_path, "r") as f:
80
+ data = json.load(f)
81
+ message = Message.model_validate(data)
82
+ messages.append(message)
83
+ except (json.JSONDecodeError, ValueError) as e:
84
+ print(f"Failed to load message from {file_path}: {e}")
85
+ continue
86
+
87
+ return messages
88
+
89
+ def _append_message(
90
+ self, conversation_id: str, message: Message, index: int
91
+ ) -> None:
92
+ """Append a message to the conversation."""
93
+ messages_dir = self._get_messages_dir(conversation_id)
94
+ messages_dir.mkdir(parents=True, exist_ok=True)
95
+
96
+ # Use timestamp + index to ensure unique, ordered filenames
97
+ timestamp = int(time.time() * 1000000) # microseconds
98
+ filename = f"{timestamp}_{index:06d}.json"
99
+ file_path = messages_dir / filename
100
+
101
+ with open(file_path, "w") as f:
102
+ json.dump(message.model_dump(mode="json"), f, indent=2)
103
+
104
+ async def create_conversation(
105
+ self, conversation_id: str, user: User, initial_message: str
106
+ ) -> Conversation:
107
+ """Create a new conversation with the specified ID."""
108
+ conversation = Conversation(
109
+ id=conversation_id,
110
+ user=user,
111
+ messages=[Message(role="user", content=initial_message)],
112
+ )
113
+
114
+ # Save metadata
115
+ self._save_metadata(conversation)
116
+
117
+ # Save initial message
118
+ self._append_message(conversation_id, conversation.messages[0], 0)
119
+
120
+ return conversation
121
+
122
+ async def get_conversation(
123
+ self, conversation_id: str, user: User
124
+ ) -> Optional[Conversation]:
125
+ """Get conversation by ID, scoped to user."""
126
+ metadata_path = self._get_metadata_path(conversation_id)
127
+
128
+ if not metadata_path.exists():
129
+ return None
130
+
131
+ try:
132
+ # Load metadata
133
+ with open(metadata_path, "r") as f:
134
+ metadata = json.load(f)
135
+
136
+ # Verify ownership
137
+ if metadata["user"]["id"] != user.id:
138
+ return None
139
+
140
+ # Load all messages
141
+ messages = self._load_messages(conversation_id)
142
+
143
+ # Reconstruct conversation
144
+ conversation = Conversation(
145
+ id=metadata["id"],
146
+ user=User.model_validate(metadata["user"]),
147
+ messages=messages,
148
+ created_at=datetime.fromisoformat(metadata["created_at"]),
149
+ updated_at=datetime.fromisoformat(metadata["updated_at"]),
150
+ )
151
+
152
+ return conversation
153
+ except (json.JSONDecodeError, ValueError, KeyError) as e:
154
+ print(f"Failed to load conversation {conversation_id}: {e}")
155
+ return None
156
+
157
+ async def update_conversation(self, conversation: Conversation) -> None:
158
+ """Update conversation with new messages."""
159
+ # Update the updated_at timestamp
160
+ conversation.updated_at = datetime.now()
161
+
162
+ # Save updated metadata
163
+ self._save_metadata(conversation)
164
+
165
+ # Get existing messages count to determine new message indices
166
+ existing_messages = self._load_messages(conversation.id)
167
+ existing_count = len(existing_messages)
168
+
169
+ # Only append new messages (ones not already saved)
170
+ for i, message in enumerate(
171
+ conversation.messages[existing_count:], start=existing_count
172
+ ):
173
+ self._append_message(conversation.id, message, i)
174
+
175
+ async def delete_conversation(self, conversation_id: str, user: User) -> bool:
176
+ """Delete conversation."""
177
+ conv_dir = self._get_conversation_dir(conversation_id)
178
+
179
+ if not conv_dir.exists():
180
+ return False
181
+
182
+ # Verify ownership before deleting
183
+ conversation = await self.get_conversation(conversation_id, user)
184
+ if not conversation:
185
+ return False
186
+
187
+ try:
188
+ # Delete all message files
189
+ messages_dir = self._get_messages_dir(conversation_id)
190
+ if messages_dir.exists():
191
+ for file_path in messages_dir.glob("*.json"):
192
+ file_path.unlink()
193
+ messages_dir.rmdir()
194
+
195
+ # Delete metadata
196
+ metadata_path = self._get_metadata_path(conversation_id)
197
+ if metadata_path.exists():
198
+ metadata_path.unlink()
199
+
200
+ # Delete conversation directory
201
+ conv_dir.rmdir()
202
+
203
+ return True
204
+ except OSError as e:
205
+ print(f"Failed to delete conversation {conversation_id}: {e}")
206
+ return False
207
+
208
+ async def list_conversations(
209
+ self, user: User, limit: int = 50, offset: int = 0
210
+ ) -> List[Conversation]:
211
+ """List conversations for user."""
212
+ if not self.base_dir.exists():
213
+ return []
214
+
215
+ conversations = []
216
+
217
+ # Iterate through all conversation directories
218
+ for conv_dir in self.base_dir.iterdir():
219
+ if not conv_dir.is_dir():
220
+ continue
221
+
222
+ metadata_path = conv_dir / "metadata.json"
223
+ if not metadata_path.exists():
224
+ continue
225
+
226
+ try:
227
+ # Load metadata
228
+ with open(metadata_path, "r") as f:
229
+ metadata = json.load(f)
230
+
231
+ # Skip conversations not owned by this user
232
+ if metadata["user"]["id"] != user.id:
233
+ continue
234
+
235
+ # Load messages
236
+ messages = self._load_messages(conv_dir.name)
237
+
238
+ # Reconstruct conversation
239
+ conversation = Conversation(
240
+ id=metadata["id"],
241
+ user=User.model_validate(metadata["user"]),
242
+ messages=messages,
243
+ created_at=datetime.fromisoformat(metadata["created_at"]),
244
+ updated_at=datetime.fromisoformat(metadata["updated_at"]),
245
+ )
246
+ conversations.append(conversation)
247
+ except (json.JSONDecodeError, ValueError, KeyError) as e:
248
+ print(f"Failed to load conversation from {conv_dir}: {e}")
249
+ continue
250
+
251
+ # Sort by updated_at desc
252
+ conversations.sort(key=lambda x: x.updated_at, reverse=True)
253
+
254
+ # Apply pagination
255
+ return conversations[offset : offset + limit]
@@ -0,0 +1,62 @@
1
+ """
2
+ In-memory conversation store implementation.
3
+
4
+ This module provides a simple in-memory implementation of the ConversationStore
5
+ interface, useful for testing and development.
6
+ """
7
+
8
+ from typing import Dict, List, Optional
9
+
10
+ from vanna.core.storage import ConversationStore, Conversation, Message
11
+ from vanna.core.user import User
12
+
13
+
14
+ class MemoryConversationStore(ConversationStore):
15
+ """In-memory conversation store."""
16
+
17
+ def __init__(self) -> None:
18
+ self._conversations: Dict[str, Conversation] = {}
19
+
20
+ async def create_conversation(
21
+ self, conversation_id: str, user: User, initial_message: str
22
+ ) -> Conversation:
23
+ """Create a new conversation with the specified ID."""
24
+ conversation = Conversation(
25
+ id=conversation_id,
26
+ user=user,
27
+ messages=[Message(role="user", content=initial_message)],
28
+ )
29
+ self._conversations[conversation_id] = conversation
30
+ return conversation
31
+
32
+ async def get_conversation(
33
+ self, conversation_id: str, user: User
34
+ ) -> Optional[Conversation]:
35
+ """Get conversation by ID, scoped to user."""
36
+ conversation = self._conversations.get(conversation_id)
37
+ if conversation and conversation.user.id == user.id:
38
+ return conversation
39
+ return None
40
+
41
+ async def update_conversation(self, conversation: Conversation) -> None:
42
+ """Update conversation with new messages."""
43
+ self._conversations[conversation.id] = conversation
44
+
45
+ async def delete_conversation(self, conversation_id: str, user: User) -> bool:
46
+ """Delete conversation."""
47
+ conversation = await self.get_conversation(conversation_id, user)
48
+ if conversation:
49
+ del self._conversations[conversation_id]
50
+ return True
51
+ return False
52
+
53
+ async def list_conversations(
54
+ self, user: User, limit: int = 50, offset: int = 0
55
+ ) -> List[Conversation]:
56
+ """List conversations for user."""
57
+ user_conversations = [
58
+ conv for conv in self._conversations.values() if conv.user.id == user.id
59
+ ]
60
+ # Sort by updated_at desc
61
+ user_conversations.sort(key=lambda x: x.updated_at, reverse=True)
62
+ return user_conversations[offset : offset + limit]
@@ -0,0 +1,7 @@
1
+ """
2
+ Marqo integration for Vanna Agents.
3
+ """
4
+
5
+ from .agent_memory import MarqoAgentMemory
6
+
7
+ __all__ = ["MarqoAgentMemory"]
@@ -0,0 +1,354 @@
1
+ """
2
+ Marqo vector database implementation of AgentMemory.
3
+
4
+ This implementation uses Marqo for vector storage of tool usage patterns.
5
+ """
6
+
7
+ import json
8
+ import uuid
9
+ from datetime import datetime
10
+ from typing import Any, Dict, List, Optional
11
+ import asyncio
12
+ from concurrent.futures import ThreadPoolExecutor
13
+
14
+ try:
15
+ import marqo
16
+
17
+ MARQO_AVAILABLE = True
18
+ except ImportError:
19
+ MARQO_AVAILABLE = False
20
+
21
+ from vanna.capabilities.agent_memory import (
22
+ AgentMemory,
23
+ TextMemory,
24
+ TextMemorySearchResult,
25
+ ToolMemory,
26
+ ToolMemorySearchResult,
27
+ )
28
+ from vanna.core.tool import ToolContext
29
+
30
+
31
+ class MarqoAgentMemory(AgentMemory):
32
+ """Marqo-based implementation of AgentMemory."""
33
+
34
+ def __init__(
35
+ self,
36
+ url: str = "http://localhost:8882",
37
+ index_name: str = "tool-memories",
38
+ api_key: Optional[str] = None,
39
+ ):
40
+ if not MARQO_AVAILABLE:
41
+ raise ImportError(
42
+ "Marqo is required for MarqoAgentMemory. Install with: pip install marqo"
43
+ )
44
+
45
+ self.url = url
46
+ self.index_name = index_name
47
+ self.api_key = api_key
48
+ self._client = None
49
+ self._executor = ThreadPoolExecutor(max_workers=2)
50
+
51
+ def _get_client(self):
52
+ """Get or create Marqo client."""
53
+ if self._client is None:
54
+ self._client = marqo.Client(url=self.url, api_key=self.api_key)
55
+
56
+ # Create index if it doesn't exist
57
+ try:
58
+ self._client.get_index(self.index_name)
59
+ except Exception:
60
+ self._client.create_index(self.index_name)
61
+
62
+ return self._client
63
+
64
+ async def save_tool_usage(
65
+ self,
66
+ question: str,
67
+ tool_name: str,
68
+ args: Dict[str, Any],
69
+ context: ToolContext,
70
+ success: bool = True,
71
+ metadata: Optional[Dict[str, Any]] = None,
72
+ ) -> None:
73
+ """Save a tool usage pattern."""
74
+
75
+ def _save():
76
+ client = self._get_client()
77
+
78
+ memory_id = str(uuid.uuid4())
79
+ timestamp = datetime.now().isoformat()
80
+
81
+ document = {
82
+ "_id": memory_id,
83
+ "question": question,
84
+ "tool_name": tool_name,
85
+ "args": json.dumps(args),
86
+ "timestamp": timestamp,
87
+ "success": success,
88
+ "metadata": json.dumps(metadata or {}),
89
+ }
90
+
91
+ client.index(self.index_name).add_documents(
92
+ [document], tensor_fields=["question"]
93
+ )
94
+
95
+ await asyncio.get_event_loop().run_in_executor(self._executor, _save)
96
+
97
+ async def search_similar_usage(
98
+ self,
99
+ question: str,
100
+ context: ToolContext,
101
+ *,
102
+ limit: int = 10,
103
+ similarity_threshold: float = 0.7,
104
+ tool_name_filter: Optional[str] = None,
105
+ ) -> List[ToolMemorySearchResult]:
106
+ """Search for similar tool usage patterns."""
107
+
108
+ def _search():
109
+ client = self._get_client()
110
+
111
+ # Build filter
112
+ filter_string = "success:true"
113
+ if tool_name_filter:
114
+ filter_string += f" AND tool_name:{tool_name_filter}"
115
+
116
+ results = client.index(self.index_name).search(
117
+ q=question, limit=limit, filter_string=filter_string
118
+ )
119
+
120
+ search_results = []
121
+ for i, hit in enumerate(results["hits"]):
122
+ # Marqo returns score
123
+ similarity_score = hit.get("_score", 0)
124
+
125
+ if similarity_score >= similarity_threshold:
126
+ args = json.loads(hit.get("args", "{}"))
127
+ metadata_dict = json.loads(hit.get("metadata", "{}"))
128
+
129
+ memory = ToolMemory(
130
+ memory_id=hit["_id"],
131
+ question=hit["question"],
132
+ tool_name=hit["tool_name"],
133
+ args=args,
134
+ timestamp=hit.get("timestamp"),
135
+ success=hit.get("success", True),
136
+ metadata=metadata_dict,
137
+ )
138
+
139
+ search_results.append(
140
+ ToolMemorySearchResult(
141
+ memory=memory, similarity_score=similarity_score, rank=i + 1
142
+ )
143
+ )
144
+
145
+ return search_results
146
+
147
+ return await asyncio.get_event_loop().run_in_executor(self._executor, _search)
148
+
149
+ async def get_recent_memories(
150
+ self, context: ToolContext, limit: int = 10
151
+ ) -> List[ToolMemory]:
152
+ """Get recently added memories."""
153
+
154
+ def _get_recent():
155
+ client = self._get_client()
156
+
157
+ # Search with wildcard and sort by timestamp
158
+ results = client.index(self.index_name).search(
159
+ q="*", limit=limit, sort="timestamp:desc"
160
+ )
161
+
162
+ memories = []
163
+ for hit in results.get("hits", []):
164
+ args = json.loads(hit.get("args", "{}"))
165
+ metadata_dict = json.loads(hit.get("metadata", "{}"))
166
+
167
+ memory = ToolMemory(
168
+ memory_id=hit["_id"],
169
+ question=hit["question"],
170
+ tool_name=hit["tool_name"],
171
+ args=args,
172
+ timestamp=hit.get("timestamp"),
173
+ success=hit.get("success", True),
174
+ metadata=metadata_dict,
175
+ )
176
+ memories.append(memory)
177
+
178
+ return memories
179
+
180
+ return await asyncio.get_event_loop().run_in_executor(
181
+ self._executor, _get_recent
182
+ )
183
+
184
+ async def delete_by_id(self, context: ToolContext, memory_id: str) -> bool:
185
+ """Delete a memory by its ID."""
186
+
187
+ def _delete():
188
+ client = self._get_client()
189
+
190
+ try:
191
+ client.index(self.index_name).delete_documents(ids=[memory_id])
192
+ return True
193
+ except Exception:
194
+ return False
195
+
196
+ return await asyncio.get_event_loop().run_in_executor(self._executor, _delete)
197
+
198
+ async def save_text_memory(self, content: str, context: ToolContext) -> TextMemory:
199
+ """Save a text memory."""
200
+
201
+ def _save():
202
+ client = self._get_client()
203
+
204
+ memory_id = str(uuid.uuid4())
205
+ timestamp = datetime.now().isoformat()
206
+
207
+ document = {
208
+ "_id": memory_id,
209
+ "content": content,
210
+ "timestamp": timestamp,
211
+ "is_text_memory": True,
212
+ }
213
+
214
+ client.index(self.index_name).add_documents(
215
+ [document], tensor_fields=["content"]
216
+ )
217
+
218
+ return TextMemory(memory_id=memory_id, content=content, timestamp=timestamp)
219
+
220
+ return await asyncio.get_event_loop().run_in_executor(self._executor, _save)
221
+
222
+ async def search_text_memories(
223
+ self,
224
+ query: str,
225
+ context: ToolContext,
226
+ *,
227
+ limit: int = 10,
228
+ similarity_threshold: float = 0.7,
229
+ ) -> List[TextMemorySearchResult]:
230
+ """Search for similar text memories."""
231
+
232
+ def _search():
233
+ client = self._get_client()
234
+
235
+ filter_string = "is_text_memory:true"
236
+
237
+ results = client.index(self.index_name).search(
238
+ q=query, limit=limit, filter_string=filter_string
239
+ )
240
+
241
+ search_results = []
242
+ for i, hit in enumerate(results["hits"]):
243
+ similarity_score = hit.get("_score", 0)
244
+
245
+ if similarity_score >= similarity_threshold:
246
+ memory = TextMemory(
247
+ memory_id=hit["_id"],
248
+ content=hit.get("content", ""),
249
+ timestamp=hit.get("timestamp"),
250
+ )
251
+
252
+ search_results.append(
253
+ TextMemorySearchResult(
254
+ memory=memory, similarity_score=similarity_score, rank=i + 1
255
+ )
256
+ )
257
+
258
+ return search_results
259
+
260
+ return await asyncio.get_event_loop().run_in_executor(self._executor, _search)
261
+
262
+ async def get_recent_text_memories(
263
+ self, context: ToolContext, limit: int = 10
264
+ ) -> List[TextMemory]:
265
+ """Get recently added text memories."""
266
+
267
+ def _get_recent():
268
+ client = self._get_client()
269
+
270
+ results = client.index(self.index_name).search(
271
+ q="*",
272
+ limit=limit,
273
+ filter_string="is_text_memory:true",
274
+ sort="timestamp:desc",
275
+ )
276
+
277
+ memories = []
278
+ for hit in results.get("hits", []):
279
+ memory = TextMemory(
280
+ memory_id=hit["_id"],
281
+ content=hit.get("content", ""),
282
+ timestamp=hit.get("timestamp"),
283
+ )
284
+ memories.append(memory)
285
+
286
+ return memories
287
+
288
+ return await asyncio.get_event_loop().run_in_executor(
289
+ self._executor, _get_recent
290
+ )
291
+
292
+ async def delete_text_memory(self, context: ToolContext, memory_id: str) -> bool:
293
+ """Delete a text memory by its ID."""
294
+
295
+ def _delete():
296
+ client = self._get_client()
297
+
298
+ try:
299
+ client.index(self.index_name).delete_documents(ids=[memory_id])
300
+ return True
301
+ except Exception:
302
+ return False
303
+
304
+ return await asyncio.get_event_loop().run_in_executor(self._executor, _delete)
305
+
306
+ async def clear_memories(
307
+ self,
308
+ context: ToolContext,
309
+ tool_name: Optional[str] = None,
310
+ before_date: Optional[str] = None,
311
+ ) -> int:
312
+ """Clear stored memories."""
313
+
314
+ def _clear():
315
+ client = self._get_client()
316
+
317
+ # Build filter for search
318
+ filter_parts = []
319
+ if tool_name:
320
+ filter_parts.append(f"tool_name:{tool_name}")
321
+ if before_date:
322
+ filter_parts.append(f"timestamp:[* TO {before_date}]")
323
+
324
+ if filter_parts or (tool_name is None and before_date is None):
325
+ filter_string = " AND ".join(filter_parts) if filter_parts else None
326
+
327
+ if filter_string:
328
+ # Search for documents to delete
329
+ results = client.index(self.index_name).search(
330
+ q="*",
331
+ limit=1000, # Max results
332
+ filter_string=filter_string,
333
+ )
334
+
335
+ ids_to_delete = [hit["_id"] for hit in results.get("hits", [])]
336
+
337
+ if ids_to_delete:
338
+ client.index(self.index_name).delete_documents(
339
+ ids=ids_to_delete
340
+ )
341
+
342
+ return len(ids_to_delete)
343
+ else:
344
+ # Delete entire index and recreate
345
+ try:
346
+ client.delete_index(self.index_name)
347
+ client.create_index(self.index_name)
348
+ except Exception:
349
+ pass
350
+ return 0
351
+
352
+ return 0
353
+
354
+ return await asyncio.get_event_loop().run_in_executor(self._executor, _clear)
@@ -0,0 +1,7 @@
1
+ """
2
+ Milvus integration for Vanna Agents.
3
+ """
4
+
5
+ from .agent_memory import MilvusAgentMemory
6
+
7
+ __all__ = ["MilvusAgentMemory"]