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,329 @@
1
+ """
2
+ Pinecone vector database implementation of AgentMemory.
3
+
4
+ This implementation uses Pinecone for cloud-based 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
+ from pinecone import Pinecone, ServerlessSpec
16
+
17
+ PINECONE_AVAILABLE = True
18
+ except ImportError:
19
+ PINECONE_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 PineconeAgentMemory(AgentMemory):
32
+ """Pinecone-based implementation of AgentMemory."""
33
+
34
+ def __init__(
35
+ self,
36
+ api_key: str,
37
+ index_name: str = "tool-memories",
38
+ environment: str = "us-east-1",
39
+ dimension: int = 384,
40
+ metric: str = "cosine",
41
+ ):
42
+ if not PINECONE_AVAILABLE:
43
+ raise ImportError(
44
+ "Pinecone is required for PineconeAgentMemory. Install with: pip install pinecone-client"
45
+ )
46
+
47
+ self.api_key = api_key
48
+ self.index_name = index_name
49
+ self.environment = environment
50
+ self.dimension = dimension
51
+ self.metric = metric
52
+ self._client = None
53
+ self._index = None
54
+ self._executor = ThreadPoolExecutor(max_workers=2)
55
+
56
+ def _get_client(self):
57
+ """Get or create Pinecone client."""
58
+ if self._client is None:
59
+ self._client = Pinecone(api_key=self.api_key)
60
+ return self._client
61
+
62
+ def _get_index(self):
63
+ """Get or create Pinecone index."""
64
+ if self._index is None:
65
+ client = self._get_client()
66
+
67
+ # Create index if it doesn't exist
68
+ if self.index_name not in client.list_indexes().names():
69
+ client.create_index(
70
+ name=self.index_name,
71
+ dimension=self.dimension,
72
+ metric=self.metric,
73
+ spec=ServerlessSpec(cloud="aws", region=self.environment),
74
+ )
75
+
76
+ self._index = client.Index(self.index_name)
77
+ return self._index
78
+
79
+ def _create_embedding(self, text: str) -> List[float]:
80
+ """Create a simple embedding from text (placeholder - should use actual embedding model)."""
81
+ # TODO: Replace with actual embedding model
82
+ import hashlib
83
+
84
+ hash_val = int(hashlib.md5(text.encode()).hexdigest(), 16)
85
+ return [(hash_val >> i) % 100 / 100.0 for i in range(self.dimension)]
86
+
87
+ async def save_tool_usage(
88
+ self,
89
+ question: str,
90
+ tool_name: str,
91
+ args: Dict[str, Any],
92
+ context: ToolContext,
93
+ success: bool = True,
94
+ metadata: Optional[Dict[str, Any]] = None,
95
+ ) -> None:
96
+ """Save a tool usage pattern."""
97
+
98
+ def _save():
99
+ index = self._get_index()
100
+
101
+ memory_id = str(uuid.uuid4())
102
+ timestamp = datetime.now().isoformat()
103
+ embedding = self._create_embedding(question)
104
+
105
+ # Pinecone metadata must be simple types
106
+ memory_metadata = {
107
+ "question": question,
108
+ "tool_name": tool_name,
109
+ "args_json": json.dumps(args),
110
+ "timestamp": timestamp,
111
+ "success": success,
112
+ "metadata_json": json.dumps(metadata or {}),
113
+ }
114
+
115
+ index.upsert(vectors=[(memory_id, embedding, memory_metadata)])
116
+
117
+ await asyncio.get_event_loop().run_in_executor(self._executor, _save)
118
+
119
+ async def search_similar_usage(
120
+ self,
121
+ question: str,
122
+ context: ToolContext,
123
+ *,
124
+ limit: int = 10,
125
+ similarity_threshold: float = 0.7,
126
+ tool_name_filter: Optional[str] = None,
127
+ ) -> List[ToolMemorySearchResult]:
128
+ """Search for similar tool usage patterns."""
129
+
130
+ def _search():
131
+ index = self._get_index()
132
+
133
+ embedding = self._create_embedding(question)
134
+
135
+ # Build filter
136
+ filter_dict = {"success": True}
137
+ if tool_name_filter:
138
+ filter_dict["tool_name"] = tool_name_filter
139
+
140
+ results = index.query(
141
+ vector=embedding, top_k=limit, filter=filter_dict, include_metadata=True
142
+ )
143
+
144
+ search_results = []
145
+ for i, match in enumerate(results.matches):
146
+ # Pinecone returns similarity score directly
147
+ similarity_score = match.score
148
+
149
+ if similarity_score >= similarity_threshold:
150
+ metadata = match.metadata
151
+ args = json.loads(metadata.get("args_json", "{}"))
152
+ metadata_dict = json.loads(metadata.get("metadata_json", "{}"))
153
+
154
+ memory = ToolMemory(
155
+ memory_id=match.id,
156
+ question=metadata["question"],
157
+ tool_name=metadata["tool_name"],
158
+ args=args,
159
+ timestamp=metadata.get("timestamp"),
160
+ success=metadata.get("success", True),
161
+ metadata=metadata_dict,
162
+ )
163
+
164
+ search_results.append(
165
+ ToolMemorySearchResult(
166
+ memory=memory, similarity_score=similarity_score, rank=i + 1
167
+ )
168
+ )
169
+
170
+ return search_results
171
+
172
+ return await asyncio.get_event_loop().run_in_executor(self._executor, _search)
173
+
174
+ async def get_recent_memories(
175
+ self, context: ToolContext, limit: int = 10
176
+ ) -> List[ToolMemory]:
177
+ """Get recently added memories."""
178
+
179
+ def _get_recent():
180
+ index = self._get_index()
181
+
182
+ # Pinecone doesn't have a native "get all" - we need to query with a dummy vector
183
+ # or use the list operation with metadata filtering
184
+ # This is a limitation - we'll return empty for now
185
+ # In production, you'd maintain a separate timestamp index or use Pinecone's metadata filtering
186
+ return []
187
+
188
+ return await asyncio.get_event_loop().run_in_executor(
189
+ self._executor, _get_recent
190
+ )
191
+
192
+ async def delete_by_id(self, context: ToolContext, memory_id: str) -> bool:
193
+ """Delete a memory by its ID."""
194
+
195
+ def _delete():
196
+ index = self._get_index()
197
+
198
+ try:
199
+ index.delete(ids=[memory_id])
200
+ return True
201
+ except Exception:
202
+ return False
203
+
204
+ return await asyncio.get_event_loop().run_in_executor(self._executor, _delete)
205
+
206
+ async def save_text_memory(self, content: str, context: ToolContext) -> TextMemory:
207
+ """Save a text memory."""
208
+
209
+ def _save():
210
+ index = self._get_index()
211
+
212
+ memory_id = str(uuid.uuid4())
213
+ timestamp = datetime.now().isoformat()
214
+ embedding = self._create_embedding(content)
215
+
216
+ memory_metadata = {
217
+ "content": content,
218
+ "timestamp": timestamp,
219
+ "is_text_memory": True,
220
+ }
221
+
222
+ index.upsert(vectors=[(memory_id, embedding, memory_metadata)])
223
+
224
+ return TextMemory(memory_id=memory_id, content=content, timestamp=timestamp)
225
+
226
+ return await asyncio.get_event_loop().run_in_executor(self._executor, _save)
227
+
228
+ async def search_text_memories(
229
+ self,
230
+ query: str,
231
+ context: ToolContext,
232
+ *,
233
+ limit: int = 10,
234
+ similarity_threshold: float = 0.7,
235
+ ) -> List[TextMemorySearchResult]:
236
+ """Search for similar text memories."""
237
+
238
+ def _search():
239
+ index = self._get_index()
240
+
241
+ embedding = self._create_embedding(query)
242
+
243
+ filter_dict = {"is_text_memory": True}
244
+
245
+ results = index.query(
246
+ vector=embedding, top_k=limit, filter=filter_dict, include_metadata=True
247
+ )
248
+
249
+ search_results = []
250
+ for i, match in enumerate(results.matches):
251
+ similarity_score = match.score
252
+
253
+ if similarity_score >= similarity_threshold:
254
+ metadata = match.metadata
255
+
256
+ memory = TextMemory(
257
+ memory_id=match.id,
258
+ content=metadata.get("content", ""),
259
+ timestamp=metadata.get("timestamp"),
260
+ )
261
+
262
+ search_results.append(
263
+ TextMemorySearchResult(
264
+ memory=memory, similarity_score=similarity_score, rank=i + 1
265
+ )
266
+ )
267
+
268
+ return search_results
269
+
270
+ return await asyncio.get_event_loop().run_in_executor(self._executor, _search)
271
+
272
+ async def get_recent_text_memories(
273
+ self, context: ToolContext, limit: int = 10
274
+ ) -> List[TextMemory]:
275
+ """Get recently added text memories."""
276
+
277
+ def _get_recent():
278
+ # Pinecone doesn't have a native "get all sorted by timestamp" operation
279
+ # This is a limitation - returning empty list
280
+ # In production, you'd need to maintain a separate index or use metadata filtering
281
+ return []
282
+
283
+ return await asyncio.get_event_loop().run_in_executor(
284
+ self._executor, _get_recent
285
+ )
286
+
287
+ async def delete_text_memory(self, context: ToolContext, memory_id: str) -> bool:
288
+ """Delete a text memory by its ID."""
289
+
290
+ def _delete():
291
+ index = self._get_index()
292
+
293
+ try:
294
+ index.delete(ids=[memory_id])
295
+ return True
296
+ except Exception:
297
+ return False
298
+
299
+ return await asyncio.get_event_loop().run_in_executor(self._executor, _delete)
300
+
301
+ async def clear_memories(
302
+ self,
303
+ context: ToolContext,
304
+ tool_name: Optional[str] = None,
305
+ before_date: Optional[str] = None,
306
+ ) -> int:
307
+ """Clear stored memories."""
308
+
309
+ def _clear():
310
+ index = self._get_index()
311
+
312
+ # Build filter
313
+ filter_dict = {}
314
+ if tool_name:
315
+ filter_dict["tool_name"] = tool_name
316
+ if before_date:
317
+ filter_dict["timestamp"] = {"$lt": before_date}
318
+
319
+ if filter_dict:
320
+ # Delete with filter
321
+ index.delete(filter=filter_dict)
322
+ else:
323
+ # Delete all
324
+ index.delete(delete_all=True)
325
+
326
+ # Pinecone doesn't return count of deleted items
327
+ return 0
328
+
329
+ return await asyncio.get_event_loop().run_in_executor(self._executor, _clear)
@@ -0,0 +1,5 @@
1
+ """Plotly integration for chart generation."""
2
+
3
+ from .chart_generator import PlotlyChartGenerator
4
+
5
+ __all__ = ["PlotlyChartGenerator"]
@@ -0,0 +1,313 @@
1
+ """Plotly-based chart generator with automatic chart type selection."""
2
+
3
+ from typing import Dict, Any, List, cast
4
+ import json
5
+ import pandas as pd
6
+ import plotly.graph_objects as go
7
+ import plotly.express as px
8
+ import plotly.io as pio
9
+
10
+
11
+ class PlotlyChartGenerator:
12
+ """Generate Plotly charts using heuristics based on DataFrame characteristics."""
13
+
14
+ # Vanna brand colors from landing page
15
+ THEME_COLORS = {
16
+ "navy": "#023d60",
17
+ "cream": "#e7e1cf",
18
+ "teal": "#15a8a8",
19
+ "orange": "#fe5d26",
20
+ "magenta": "#bf1363",
21
+ }
22
+
23
+ # Color palette for charts (excluding cream as it's too light for data)
24
+ COLOR_PALETTE = ["#15a8a8", "#fe5d26", "#bf1363", "#023d60"]
25
+
26
+ def generate_chart(self, df: pd.DataFrame, title: str = "Chart") -> Dict[str, Any]:
27
+ """Generate a Plotly chart based on DataFrame shape and types.
28
+
29
+ Heuristics:
30
+ - 4+ columns: table
31
+ - 1 numeric column: histogram
32
+ - 2 columns (1 categorical, 1 numeric): bar chart
33
+ - 2 numeric columns: scatter plot
34
+ - 3+ numeric columns: correlation heatmap or multi-line chart
35
+ - Time series data: line chart
36
+ - Multiple categorical: grouped bar chart
37
+
38
+ Args:
39
+ df: DataFrame to visualize
40
+ title: Title for the chart
41
+
42
+ Returns:
43
+ Plotly figure as dictionary
44
+
45
+ Raises:
46
+ ValueError: If DataFrame is empty or cannot be visualized
47
+ """
48
+ if df.empty:
49
+ raise ValueError("Cannot visualize empty DataFrame")
50
+
51
+ # Heuristic: If 4 or more columns, render as a table
52
+ if len(df.columns) >= 4:
53
+ fig = self._create_table(df, title)
54
+ result: Dict[str, Any] = json.loads(pio.to_json(fig))
55
+ return result
56
+
57
+ # Identify column types
58
+ numeric_cols = df.select_dtypes(include=["number"]).columns.tolist()
59
+ categorical_cols = df.select_dtypes(
60
+ include=["object", "category"]
61
+ ).columns.tolist()
62
+ datetime_cols = df.select_dtypes(include=["datetime64"]).columns.tolist()
63
+
64
+ # Check for time series
65
+ is_timeseries = len(datetime_cols) > 0
66
+
67
+ # Apply heuristics
68
+ if is_timeseries and len(numeric_cols) > 0:
69
+ # Time series line chart
70
+ fig = self._create_time_series_chart(
71
+ df, datetime_cols[0], numeric_cols, title
72
+ )
73
+ elif len(numeric_cols) == 1 and len(categorical_cols) == 0:
74
+ # Single numeric column: histogram
75
+ fig = self._create_histogram(df, numeric_cols[0], title)
76
+ elif len(numeric_cols) == 1 and len(categorical_cols) == 1:
77
+ # One categorical, one numeric: bar chart
78
+ fig = self._create_bar_chart(
79
+ df, categorical_cols[0], numeric_cols[0], title
80
+ )
81
+ elif len(numeric_cols) == 2:
82
+ # Two numeric columns: scatter plot
83
+ fig = self._create_scatter_plot(df, numeric_cols[0], numeric_cols[1], title)
84
+ elif len(numeric_cols) >= 3:
85
+ # Multiple numeric columns: correlation heatmap
86
+ fig = self._create_correlation_heatmap(df, numeric_cols, title)
87
+ elif len(categorical_cols) >= 2:
88
+ # Multiple categorical: grouped bar chart
89
+ fig = self._create_grouped_bar_chart(df, categorical_cols, title)
90
+ else:
91
+ # Fallback: show first two columns as scatter/bar
92
+ if len(df.columns) >= 2:
93
+ fig = self._create_generic_chart(
94
+ df, df.columns[0], df.columns[1], title
95
+ )
96
+ else:
97
+ raise ValueError(
98
+ "Cannot determine appropriate visualization for this DataFrame"
99
+ )
100
+
101
+ # Convert to JSON-serializable dict using plotly's JSON encoder
102
+ result = json.loads(pio.to_json(fig))
103
+ return result
104
+
105
+ def _apply_standard_layout(self, fig: go.Figure) -> go.Figure:
106
+ """Apply consistent Vanna brand styling to all charts.
107
+
108
+ Uses Vanna brand colors from the landing page for a cohesive look.
109
+
110
+ Args:
111
+ fig: Plotly figure to update
112
+
113
+ Returns:
114
+ Updated figure with Vanna brand styling
115
+ """
116
+ fig.update_layout(
117
+ # paper_bgcolor='white',
118
+ # plot_bgcolor='white',
119
+ font={"color": self.THEME_COLORS["navy"]}, # Navy for text
120
+ autosize=True, # Allow chart to resize responsively
121
+ colorway=self.COLOR_PALETTE, # Use Vanna brand colors for data
122
+ # Don't set width/height - let frontend handle sizing
123
+ )
124
+ return fig
125
+
126
+ def _create_histogram(self, df: pd.DataFrame, column: str, title: str) -> go.Figure:
127
+ """Create a histogram for a single numeric column."""
128
+ fig = px.histogram(
129
+ df,
130
+ x=column,
131
+ title=title,
132
+ color_discrete_sequence=[self.THEME_COLORS["teal"]],
133
+ )
134
+ fig.update_layout(xaxis_title=column, yaxis_title="Count", showlegend=False)
135
+ self._apply_standard_layout(fig)
136
+ return fig
137
+
138
+ def _create_bar_chart(
139
+ self, df: pd.DataFrame, x_col: str, y_col: str, title: str
140
+ ) -> go.Figure:
141
+ """Create a bar chart for categorical vs numeric data."""
142
+ # Aggregate if needed
143
+ agg_df = df.groupby(x_col)[y_col].sum().reset_index()
144
+ fig = px.bar(
145
+ agg_df,
146
+ x=x_col,
147
+ y=y_col,
148
+ title=title,
149
+ color_discrete_sequence=[self.THEME_COLORS["orange"]],
150
+ )
151
+ fig.update_layout(xaxis_title=x_col, yaxis_title=y_col)
152
+ self._apply_standard_layout(fig)
153
+ return fig
154
+
155
+ def _create_scatter_plot(
156
+ self, df: pd.DataFrame, x_col: str, y_col: str, title: str
157
+ ) -> go.Figure:
158
+ """Create a scatter plot for two numeric columns."""
159
+ fig = px.scatter(
160
+ df,
161
+ x=x_col,
162
+ y=y_col,
163
+ title=title,
164
+ color_discrete_sequence=[self.THEME_COLORS["magenta"]],
165
+ )
166
+ fig.update_layout(xaxis_title=x_col, yaxis_title=y_col)
167
+ self._apply_standard_layout(fig)
168
+ return fig
169
+
170
+ def _create_correlation_heatmap(
171
+ self, df: pd.DataFrame, columns: List[str], title: str
172
+ ) -> go.Figure:
173
+ """Create a correlation heatmap for multiple numeric columns."""
174
+ corr_matrix = df[columns].corr()
175
+ # Custom Vanna color scale: navy (negative) -> cream (neutral) -> teal (positive)
176
+ vanna_colorscale = [
177
+ [0.0, self.THEME_COLORS["navy"]],
178
+ [0.5, self.THEME_COLORS["cream"]],
179
+ [1.0, self.THEME_COLORS["teal"]],
180
+ ]
181
+ fig = cast(
182
+ go.Figure,
183
+ px.imshow(
184
+ corr_matrix,
185
+ title=title,
186
+ labels=dict(color="Correlation"),
187
+ x=columns,
188
+ y=columns,
189
+ color_continuous_scale=vanna_colorscale,
190
+ zmin=-1,
191
+ zmax=1,
192
+ ),
193
+ )
194
+ self._apply_standard_layout(fig)
195
+ return fig
196
+
197
+ def _create_time_series_chart(
198
+ self, df: pd.DataFrame, time_col: str, value_cols: List[str], title: str
199
+ ) -> go.Figure:
200
+ """Create a time series line chart."""
201
+ fig = go.Figure()
202
+
203
+ for i, col in enumerate(value_cols[:5]): # Limit to 5 lines for readability
204
+ color = self.COLOR_PALETTE[i % len(self.COLOR_PALETTE)]
205
+ fig.add_trace(
206
+ go.Scatter(
207
+ x=df[time_col],
208
+ y=df[col],
209
+ mode="lines",
210
+ name=col,
211
+ line=dict(color=color),
212
+ )
213
+ )
214
+
215
+ fig.update_layout(
216
+ title=title,
217
+ xaxis_title=time_col,
218
+ yaxis_title="Value",
219
+ hovermode="x unified",
220
+ )
221
+ self._apply_standard_layout(fig)
222
+ return fig
223
+
224
+ def _create_grouped_bar_chart(
225
+ self, df: pd.DataFrame, categorical_cols: List[str], title: str
226
+ ) -> go.Figure:
227
+ """Create a grouped bar chart for multiple categorical columns."""
228
+ # Use first two categorical columns
229
+ if len(categorical_cols) >= 2:
230
+ # Count occurrences
231
+ grouped = df.groupby(categorical_cols[:2]).size().reset_index(name="count")
232
+ fig = px.bar(
233
+ grouped,
234
+ x=categorical_cols[0],
235
+ y="count",
236
+ color=categorical_cols[1],
237
+ title=title,
238
+ barmode="group",
239
+ color_discrete_sequence=self.COLOR_PALETTE,
240
+ )
241
+ self._apply_standard_layout(fig)
242
+ return fig
243
+ else:
244
+ # Single categorical: value counts
245
+ counts = df[categorical_cols[0]].value_counts().reset_index()
246
+ counts.columns = [categorical_cols[0], "count"]
247
+ fig = px.bar(
248
+ counts,
249
+ x=categorical_cols[0],
250
+ y="count",
251
+ title=title,
252
+ color_discrete_sequence=[self.THEME_COLORS["teal"]],
253
+ )
254
+ self._apply_standard_layout(fig)
255
+ return fig
256
+
257
+ def _create_generic_chart(
258
+ self, df: pd.DataFrame, col1: str, col2: str, title: str
259
+ ) -> go.Figure:
260
+ """Create a generic chart for any two columns."""
261
+ # Try to determine the best representation
262
+ if pd.api.types.is_numeric_dtype(df[col1]) and pd.api.types.is_numeric_dtype(
263
+ df[col2]
264
+ ):
265
+ return self._create_scatter_plot(df, col1, col2, title)
266
+ else:
267
+ # Treat first as categorical, second as value
268
+ fig = px.bar(
269
+ df,
270
+ x=col1,
271
+ y=col2,
272
+ title=title,
273
+ color_discrete_sequence=[self.THEME_COLORS["orange"]],
274
+ )
275
+ self._apply_standard_layout(fig)
276
+ return fig
277
+
278
+ def _create_table(self, df: pd.DataFrame, title: str) -> go.Figure:
279
+ """Create a Plotly table for DataFrames with 4 or more columns."""
280
+ # Prepare header
281
+ header_values = list(df.columns)
282
+
283
+ # Prepare cell values (transpose to get columns)
284
+ cell_values = [df[col].tolist() for col in df.columns]
285
+
286
+ # Create the table
287
+ fig = go.Figure(
288
+ data=[
289
+ go.Table(
290
+ header=dict(
291
+ values=header_values,
292
+ fill_color=self.THEME_COLORS["navy"],
293
+ font=dict(color="white", size=12),
294
+ align="left",
295
+ ),
296
+ cells=dict(
297
+ values=cell_values,
298
+ fill_color=[
299
+ [
300
+ self.THEME_COLORS["cream"] if i % 2 == 0 else "white"
301
+ for i in range(len(df))
302
+ ]
303
+ ],
304
+ font=dict(color=self.THEME_COLORS["navy"], size=11),
305
+ align="left",
306
+ ),
307
+ )
308
+ ]
309
+ )
310
+
311
+ fig.update_layout(title=title, font={"color": self.THEME_COLORS["navy"]})
312
+
313
+ return fig
@@ -0,0 +1,9 @@
1
+ """
2
+ PostgreSQL integration.
3
+
4
+ This module provides PostgreSQL runner implementation.
5
+ """
6
+
7
+ from .sql_runner import PostgresRunner
8
+
9
+ __all__ = ["PostgresRunner"]