asksql 2.1.0__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 (291) hide show
  1. asksql-2.1.0.dist-info/METADATA +517 -0
  2. asksql-2.1.0.dist-info/RECORD +291 -0
  3. asksql-2.1.0.dist-info/WHEEL +4 -0
  4. asksql-2.1.0.dist-info/entry_points.txt +4 -0
  5. asksql-2.1.0.dist-info/licenses/LICENSE +21 -0
  6. vanna/__init__.py +172 -0
  7. vanna/agents/__init__.py +7 -0
  8. vanna/capabilities/__init__.py +17 -0
  9. vanna/capabilities/agent_memory/__init__.py +21 -0
  10. vanna/capabilities/agent_memory/base.py +103 -0
  11. vanna/capabilities/agent_memory/models.py +53 -0
  12. vanna/capabilities/file_system/__init__.py +14 -0
  13. vanna/capabilities/file_system/base.py +71 -0
  14. vanna/capabilities/file_system/models.py +25 -0
  15. vanna/capabilities/sql_runner/__init__.py +13 -0
  16. vanna/capabilities/sql_runner/base.py +37 -0
  17. vanna/capabilities/sql_runner/models.py +13 -0
  18. vanna/components/__init__.py +92 -0
  19. vanna/components/base.py +11 -0
  20. vanna/components/rich/__init__.py +83 -0
  21. vanna/components/rich/containers/__init__.py +7 -0
  22. vanna/components/rich/containers/card.py +20 -0
  23. vanna/components/rich/data/__init__.py +9 -0
  24. vanna/components/rich/data/chart.py +17 -0
  25. vanna/components/rich/data/dataframe.py +93 -0
  26. vanna/components/rich/feedback/__init__.py +21 -0
  27. vanna/components/rich/feedback/badge.py +16 -0
  28. vanna/components/rich/feedback/icon_text.py +14 -0
  29. vanna/components/rich/feedback/log_viewer.py +41 -0
  30. vanna/components/rich/feedback/notification.py +19 -0
  31. vanna/components/rich/feedback/progress.py +37 -0
  32. vanna/components/rich/feedback/status_card.py +28 -0
  33. vanna/components/rich/feedback/status_indicator.py +14 -0
  34. vanna/components/rich/interactive/__init__.py +21 -0
  35. vanna/components/rich/interactive/button.py +95 -0
  36. vanna/components/rich/interactive/task_list.py +58 -0
  37. vanna/components/rich/interactive/ui_state.py +93 -0
  38. vanna/components/rich/specialized/__init__.py +7 -0
  39. vanna/components/rich/specialized/artifact.py +20 -0
  40. vanna/components/rich/text.py +16 -0
  41. vanna/components/simple/__init__.py +15 -0
  42. vanna/components/simple/image.py +15 -0
  43. vanna/components/simple/link.py +15 -0
  44. vanna/components/simple/text.py +11 -0
  45. vanna/core/__init__.py +193 -0
  46. vanna/core/_compat.py +19 -0
  47. vanna/core/agent/__init__.py +10 -0
  48. vanna/core/agent/agent.py +1407 -0
  49. vanna/core/agent/config.py +123 -0
  50. vanna/core/audit/__init__.py +28 -0
  51. vanna/core/audit/base.py +299 -0
  52. vanna/core/audit/models.py +131 -0
  53. vanna/core/component_manager.py +329 -0
  54. vanna/core/components.py +53 -0
  55. vanna/core/enhancer/__init__.py +11 -0
  56. vanna/core/enhancer/base.py +94 -0
  57. vanna/core/enhancer/default.py +118 -0
  58. vanna/core/enricher/__init__.py +10 -0
  59. vanna/core/enricher/base.py +59 -0
  60. vanna/core/errors.py +47 -0
  61. vanna/core/evaluation/__init__.py +81 -0
  62. vanna/core/evaluation/base.py +186 -0
  63. vanna/core/evaluation/dataset.py +254 -0
  64. vanna/core/evaluation/evaluators.py +376 -0
  65. vanna/core/evaluation/report.py +289 -0
  66. vanna/core/evaluation/runner.py +313 -0
  67. vanna/core/filter/__init__.py +10 -0
  68. vanna/core/filter/base.py +67 -0
  69. vanna/core/lifecycle/__init__.py +10 -0
  70. vanna/core/lifecycle/base.py +83 -0
  71. vanna/core/llm/__init__.py +16 -0
  72. vanna/core/llm/base.py +40 -0
  73. vanna/core/llm/models.py +61 -0
  74. vanna/core/middleware/__init__.py +10 -0
  75. vanna/core/middleware/base.py +69 -0
  76. vanna/core/observability/__init__.py +11 -0
  77. vanna/core/observability/base.py +88 -0
  78. vanna/core/observability/models.py +47 -0
  79. vanna/core/recovery/__init__.py +11 -0
  80. vanna/core/recovery/base.py +84 -0
  81. vanna/core/recovery/models.py +32 -0
  82. vanna/core/registry.py +278 -0
  83. vanna/core/rich_component.py +156 -0
  84. vanna/core/simple_component.py +27 -0
  85. vanna/core/storage/__init__.py +14 -0
  86. vanna/core/storage/base.py +46 -0
  87. vanna/core/storage/models.py +46 -0
  88. vanna/core/system_prompt/__init__.py +13 -0
  89. vanna/core/system_prompt/base.py +36 -0
  90. vanna/core/system_prompt/default.py +157 -0
  91. vanna/core/tool/__init__.py +18 -0
  92. vanna/core/tool/base.py +70 -0
  93. vanna/core/tool/models.py +84 -0
  94. vanna/core/user/__init__.py +17 -0
  95. vanna/core/user/base.py +29 -0
  96. vanna/core/user/models.py +25 -0
  97. vanna/core/user/request_context.py +70 -0
  98. vanna/core/user/resolver.py +42 -0
  99. vanna/core/validation.py +164 -0
  100. vanna/core/workflow/__init__.py +12 -0
  101. vanna/core/workflow/base.py +254 -0
  102. vanna/core/workflow/default.py +789 -0
  103. vanna/examples/__init__.py +1 -0
  104. vanna/examples/__main__.py +44 -0
  105. vanna/examples/anthropic_quickstart.py +80 -0
  106. vanna/examples/artifact_example.py +293 -0
  107. vanna/examples/claude_sqlite_example.py +236 -0
  108. vanna/examples/coding_agent_example.py +300 -0
  109. vanna/examples/custom_system_prompt_example.py +174 -0
  110. vanna/examples/default_workflow_handler_example.py +208 -0
  111. vanna/examples/email_auth_example.py +340 -0
  112. vanna/examples/evaluation_example.py +269 -0
  113. vanna/examples/extensibility_example.py +262 -0
  114. vanna/examples/minimal_example.py +67 -0
  115. vanna/examples/mock_auth_example.py +227 -0
  116. vanna/examples/mock_custom_tool.py +311 -0
  117. vanna/examples/mock_quickstart.py +79 -0
  118. vanna/examples/mock_quota_example.py +145 -0
  119. vanna/examples/mock_rich_components_demo.py +396 -0
  120. vanna/examples/mock_sqlite_example.py +223 -0
  121. vanna/examples/openai_quickstart.py +83 -0
  122. vanna/examples/primitive_components_demo.py +305 -0
  123. vanna/examples/quota_lifecycle_example.py +139 -0
  124. vanna/examples/visualization_example.py +251 -0
  125. vanna/integrations/__init__.py +17 -0
  126. vanna/integrations/anthropic/__init__.py +9 -0
  127. vanna/integrations/anthropic/llm.py +270 -0
  128. vanna/integrations/azureopenai/__init__.py +9 -0
  129. vanna/integrations/azureopenai/llm.py +329 -0
  130. vanna/integrations/azuresearch/__init__.py +7 -0
  131. vanna/integrations/azuresearch/agent_memory.py +413 -0
  132. vanna/integrations/bigquery/__init__.py +5 -0
  133. vanna/integrations/bigquery/sql_runner.py +81 -0
  134. vanna/integrations/chromadb/__init__.py +104 -0
  135. vanna/integrations/chromadb/agent_memory.py +488 -0
  136. vanna/integrations/clickhouse/__init__.py +5 -0
  137. vanna/integrations/clickhouse/sql_runner.py +82 -0
  138. vanna/integrations/duckdb/__init__.py +5 -0
  139. vanna/integrations/duckdb/sql_runner.py +65 -0
  140. vanna/integrations/faiss/__init__.py +7 -0
  141. vanna/integrations/faiss/agent_memory.py +435 -0
  142. vanna/integrations/google/__init__.py +9 -0
  143. vanna/integrations/google/gemini.py +370 -0
  144. vanna/integrations/hive/__init__.py +5 -0
  145. vanna/integrations/hive/sql_runner.py +87 -0
  146. vanna/integrations/local/__init__.py +17 -0
  147. vanna/integrations/local/agent_memory/__init__.py +7 -0
  148. vanna/integrations/local/agent_memory/in_memory.py +285 -0
  149. vanna/integrations/local/audit.py +59 -0
  150. vanna/integrations/local/file_system.py +242 -0
  151. vanna/integrations/local/file_system_conversation_store.py +255 -0
  152. vanna/integrations/local/storage.py +62 -0
  153. vanna/integrations/marqo/__init__.py +7 -0
  154. vanna/integrations/marqo/agent_memory.py +354 -0
  155. vanna/integrations/milvus/__init__.py +7 -0
  156. vanna/integrations/milvus/agent_memory.py +458 -0
  157. vanna/integrations/mock/__init__.py +9 -0
  158. vanna/integrations/mock/llm.py +65 -0
  159. vanna/integrations/mssql/__init__.py +5 -0
  160. vanna/integrations/mssql/sql_runner.py +66 -0
  161. vanna/integrations/mysql/__init__.py +5 -0
  162. vanna/integrations/mysql/sql_runner.py +92 -0
  163. vanna/integrations/ollama/__init__.py +7 -0
  164. vanna/integrations/ollama/llm.py +252 -0
  165. vanna/integrations/openai/__init__.py +10 -0
  166. vanna/integrations/openai/llm.py +267 -0
  167. vanna/integrations/openai/responses.py +163 -0
  168. vanna/integrations/opensearch/__init__.py +7 -0
  169. vanna/integrations/opensearch/agent_memory.py +411 -0
  170. vanna/integrations/oracle/__init__.py +5 -0
  171. vanna/integrations/oracle/sql_runner.py +75 -0
  172. vanna/integrations/pinecone/__init__.py +7 -0
  173. vanna/integrations/pinecone/agent_memory.py +329 -0
  174. vanna/integrations/plotly/__init__.py +5 -0
  175. vanna/integrations/plotly/chart_generator.py +313 -0
  176. vanna/integrations/postgres/__init__.py +9 -0
  177. vanna/integrations/postgres/sql_runner.py +112 -0
  178. vanna/integrations/premium/agent_memory/__init__.py +7 -0
  179. vanna/integrations/premium/agent_memory/premium.py +186 -0
  180. vanna/integrations/presto/__init__.py +5 -0
  181. vanna/integrations/presto/sql_runner.py +107 -0
  182. vanna/integrations/qdrant/__init__.py +7 -0
  183. vanna/integrations/qdrant/agent_memory.py +466 -0
  184. vanna/integrations/snowflake/__init__.py +5 -0
  185. vanna/integrations/snowflake/sql_runner.py +147 -0
  186. vanna/integrations/sqlite/__init__.py +9 -0
  187. vanna/integrations/sqlite/sql_runner.py +65 -0
  188. vanna/integrations/weaviate/__init__.py +7 -0
  189. vanna/integrations/weaviate/agent_memory.py +428 -0
  190. vanna/legacy/ZhipuAI/ZhipuAI_Chat.py +233 -0
  191. vanna/legacy/ZhipuAI/ZhipuAI_embeddings.py +79 -0
  192. vanna/legacy/ZhipuAI/__init__.py +2 -0
  193. vanna/legacy/__init__.py +403 -0
  194. vanna/legacy/adapter.py +468 -0
  195. vanna/legacy/advanced/__init__.py +28 -0
  196. vanna/legacy/anthropic/__init__.py +1 -0
  197. vanna/legacy/anthropic/anthropic_chat.py +80 -0
  198. vanna/legacy/azuresearch/__init__.py +1 -0
  199. vanna/legacy/azuresearch/azuresearch_vector.py +274 -0
  200. vanna/legacy/base/__init__.py +1 -0
  201. vanna/legacy/base/base.py +2133 -0
  202. vanna/legacy/base/safe_exec.py +190 -0
  203. vanna/legacy/bedrock/__init__.py +1 -0
  204. vanna/legacy/bedrock/bedrock_converse.py +86 -0
  205. vanna/legacy/chromadb/__init__.py +1 -0
  206. vanna/legacy/chromadb/chromadb_vector.py +259 -0
  207. vanna/legacy/cohere/__init__.py +2 -0
  208. vanna/legacy/cohere/cohere_chat.py +99 -0
  209. vanna/legacy/cohere/cohere_embeddings.py +77 -0
  210. vanna/legacy/deepseek/__init__.py +1 -0
  211. vanna/legacy/deepseek/deepseek_chat.py +59 -0
  212. vanna/legacy/exceptions/__init__.py +46 -0
  213. vanna/legacy/faiss/__init__.py +1 -0
  214. vanna/legacy/faiss/faiss.py +230 -0
  215. vanna/legacy/flask/__init__.py +1355 -0
  216. vanna/legacy/flask/assets.py +58 -0
  217. vanna/legacy/flask/auth.py +56 -0
  218. vanna/legacy/google/__init__.py +2 -0
  219. vanna/legacy/google/bigquery_vector.py +293 -0
  220. vanna/legacy/google/gemini_chat.py +78 -0
  221. vanna/legacy/hf/__init__.py +1 -0
  222. vanna/legacy/hf/hf.py +80 -0
  223. vanna/legacy/local.py +8 -0
  224. vanna/legacy/marqo/__init__.py +1 -0
  225. vanna/legacy/marqo/marqo.py +168 -0
  226. vanna/legacy/milvus/__init__.py +1 -0
  227. vanna/legacy/milvus/milvus_vector.py +328 -0
  228. vanna/legacy/mistral/__init__.py +1 -0
  229. vanna/legacy/mistral/mistral.py +51 -0
  230. vanna/legacy/mock/__init__.py +3 -0
  231. vanna/legacy/mock/embedding.py +11 -0
  232. vanna/legacy/mock/llm.py +18 -0
  233. vanna/legacy/mock/vectordb.py +67 -0
  234. vanna/legacy/ollama/__init__.py +1 -0
  235. vanna/legacy/ollama/ollama.py +110 -0
  236. vanna/legacy/openai/__init__.py +2 -0
  237. vanna/legacy/openai/openai_chat.py +124 -0
  238. vanna/legacy/openai/openai_embeddings.py +46 -0
  239. vanna/legacy/opensearch/__init__.py +2 -0
  240. vanna/legacy/opensearch/opensearch_vector.py +369 -0
  241. vanna/legacy/opensearch/opensearch_vector_semantic.py +200 -0
  242. vanna/legacy/oracle/__init__.py +1 -0
  243. vanna/legacy/oracle/oracle_vector.py +584 -0
  244. vanna/legacy/pgvector/__init__.py +1 -0
  245. vanna/legacy/pgvector/pgvector.py +282 -0
  246. vanna/legacy/pinecone/__init__.py +3 -0
  247. vanna/legacy/pinecone/pinecone_vector.py +275 -0
  248. vanna/legacy/qdrant/__init__.py +3 -0
  249. vanna/legacy/qdrant/qdrant.py +329 -0
  250. vanna/legacy/qianfan/Qianfan_Chat.py +170 -0
  251. vanna/legacy/qianfan/Qianfan_embeddings.py +36 -0
  252. vanna/legacy/qianfan/__init__.py +2 -0
  253. vanna/legacy/qianwen/QianwenAI_chat.py +132 -0
  254. vanna/legacy/qianwen/QianwenAI_embeddings.py +46 -0
  255. vanna/legacy/qianwen/__init__.py +2 -0
  256. vanna/legacy/remote.py +79 -0
  257. vanna/legacy/types/__init__.py +292 -0
  258. vanna/legacy/utils.py +72 -0
  259. vanna/legacy/vannadb/__init__.py +1 -0
  260. vanna/legacy/vannadb/vannadb_vector.py +468 -0
  261. vanna/legacy/vllm/__init__.py +1 -0
  262. vanna/legacy/vllm/vllm.py +97 -0
  263. vanna/legacy/weaviate/__init__.py +1 -0
  264. vanna/legacy/weaviate/weaviate_vector.py +194 -0
  265. vanna/legacy/xinference/__init__.py +1 -0
  266. vanna/legacy/xinference/xinference.py +53 -0
  267. vanna/py.typed +0 -0
  268. vanna/servers/__init__.py +16 -0
  269. vanna/servers/__main__.py +8 -0
  270. vanna/servers/base/__init__.py +18 -0
  271. vanna/servers/base/chat_handler.py +65 -0
  272. vanna/servers/base/models.py +111 -0
  273. vanna/servers/base/rich_chat_handler.py +141 -0
  274. vanna/servers/base/templates.py +331 -0
  275. vanna/servers/cli/__init__.py +7 -0
  276. vanna/servers/cli/server_runner.py +204 -0
  277. vanna/servers/fastapi/__init__.py +7 -0
  278. vanna/servers/fastapi/app.py +163 -0
  279. vanna/servers/fastapi/routes.py +183 -0
  280. vanna/servers/flask/__init__.py +7 -0
  281. vanna/servers/flask/app.py +132 -0
  282. vanna/servers/flask/routes.py +137 -0
  283. vanna/tools/__init__.py +41 -0
  284. vanna/tools/agent_memory.py +322 -0
  285. vanna/tools/file_system.py +879 -0
  286. vanna/tools/python.py +222 -0
  287. vanna/tools/run_sql.py +173 -0
  288. vanna/tools/visualize_data.py +195 -0
  289. vanna/utils/__init__.py +0 -0
  290. vanna/utils/sql_safety.py +84 -0
  291. vanna/web_components/__init__.py +44 -0
@@ -0,0 +1,428 @@
1
+ """
2
+ Weaviate vector database implementation of AgentMemory.
3
+
4
+ This implementation uses Weaviate for semantic search and 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 weaviate
16
+ from weaviate.classes.config import (
17
+ Configure,
18
+ Property,
19
+ DataType as WeaviateDataType,
20
+ )
21
+
22
+ WEAVIATE_AVAILABLE = True
23
+ except ImportError:
24
+ WEAVIATE_AVAILABLE = False
25
+
26
+ from vanna.capabilities.agent_memory import (
27
+ AgentMemory,
28
+ TextMemory,
29
+ TextMemorySearchResult,
30
+ ToolMemory,
31
+ ToolMemorySearchResult,
32
+ )
33
+ from vanna.core.tool import ToolContext
34
+
35
+
36
+ class WeaviateAgentMemory(AgentMemory):
37
+ """Weaviate-based implementation of AgentMemory."""
38
+
39
+ def __init__(
40
+ self,
41
+ collection_name: str = "ToolMemory",
42
+ url: str = "http://localhost:8080",
43
+ api_key: Optional[str] = None,
44
+ dimension: int = 384,
45
+ ):
46
+ if not WEAVIATE_AVAILABLE:
47
+ raise ImportError(
48
+ "Weaviate is required for WeaviateAgentMemory. Install with: pip install weaviate-client"
49
+ )
50
+
51
+ self.collection_name = collection_name
52
+ self.url = url
53
+ self.api_key = api_key
54
+ self.dimension = dimension
55
+ self._client = None
56
+ self._executor = ThreadPoolExecutor(max_workers=2)
57
+
58
+ def _get_client(self):
59
+ """Get or create Weaviate client."""
60
+ if self._client is None:
61
+ if self.api_key:
62
+ self._client = weaviate.connect_to_weaviate_cloud(
63
+ cluster_url=self.url,
64
+ auth_credentials=weaviate.auth.AuthApiKey(self.api_key),
65
+ )
66
+ else:
67
+ self._client = weaviate.connect_to_local(
68
+ host=self.url.replace("http://", "").replace("https://", "")
69
+ )
70
+
71
+ # Create collection if it doesn't exist
72
+ if not self._client.collections.exists(self.collection_name):
73
+ self._client.collections.create(
74
+ name=self.collection_name,
75
+ vectorizer_config=Configure.Vectorizer.none(),
76
+ properties=[
77
+ Property(name="question", data_type=WeaviateDataType.TEXT),
78
+ Property(name="tool_name", data_type=WeaviateDataType.TEXT),
79
+ Property(name="args_json", data_type=WeaviateDataType.TEXT),
80
+ Property(name="timestamp", data_type=WeaviateDataType.TEXT),
81
+ Property(name="success", data_type=WeaviateDataType.BOOL),
82
+ Property(name="metadata_json", data_type=WeaviateDataType.TEXT),
83
+ ],
84
+ )
85
+
86
+ return self._client
87
+
88
+ def _create_embedding(self, text: str) -> List[float]:
89
+ """Create a simple embedding from text (placeholder)."""
90
+ import hashlib
91
+
92
+ hash_val = int(hashlib.md5(text.encode()).hexdigest(), 16)
93
+ return [(hash_val >> i) % 100 / 100.0 for i in range(self.dimension)]
94
+
95
+ async def save_tool_usage(
96
+ self,
97
+ question: str,
98
+ tool_name: str,
99
+ args: Dict[str, Any],
100
+ context: ToolContext,
101
+ success: bool = True,
102
+ metadata: Optional[Dict[str, Any]] = None,
103
+ ) -> None:
104
+ """Save a tool usage pattern."""
105
+
106
+ def _save():
107
+ client = self._get_client()
108
+ collection = client.collections.get(self.collection_name)
109
+
110
+ memory_id = str(uuid.uuid4())
111
+ timestamp = datetime.now().isoformat()
112
+ embedding = self._create_embedding(question)
113
+
114
+ properties = {
115
+ "question": question,
116
+ "tool_name": tool_name,
117
+ "args_json": json.dumps(args),
118
+ "timestamp": timestamp,
119
+ "success": success,
120
+ "metadata_json": json.dumps(metadata or {}),
121
+ }
122
+
123
+ collection.data.insert(
124
+ properties=properties, vector=embedding, uuid=memory_id
125
+ )
126
+
127
+ await asyncio.get_event_loop().run_in_executor(self._executor, _save)
128
+
129
+ async def search_similar_usage(
130
+ self,
131
+ question: str,
132
+ context: ToolContext,
133
+ *,
134
+ limit: int = 10,
135
+ similarity_threshold: float = 0.7,
136
+ tool_name_filter: Optional[str] = None,
137
+ ) -> List[ToolMemorySearchResult]:
138
+ """Search for similar tool usage patterns."""
139
+
140
+ def _search():
141
+ client = self._get_client()
142
+ collection = client.collections.get(self.collection_name)
143
+
144
+ embedding = self._create_embedding(question)
145
+
146
+ # Build filter
147
+ filters = weaviate.classes.query.Filter.by_property("success").equal(True)
148
+ if tool_name_filter:
149
+ filters = filters & weaviate.classes.query.Filter.by_property(
150
+ "tool_name"
151
+ ).equal(tool_name_filter)
152
+
153
+ response = collection.query.near_vector(
154
+ near_vector=embedding,
155
+ limit=limit,
156
+ filters=filters,
157
+ return_metadata=weaviate.classes.query.MetadataQuery(distance=True),
158
+ )
159
+
160
+ search_results = []
161
+ for i, obj in enumerate(response.objects):
162
+ # Weaviate returns distance, convert to similarity
163
+ distance = obj.metadata.distance if obj.metadata else 1.0
164
+ similarity_score = 1 - distance
165
+
166
+ if similarity_score >= similarity_threshold:
167
+ properties = obj.properties
168
+ args = json.loads(properties.get("args_json", "{}"))
169
+ metadata_dict = json.loads(properties.get("metadata_json", "{}"))
170
+
171
+ memory = ToolMemory(
172
+ memory_id=str(obj.uuid),
173
+ question=properties.get("question"),
174
+ tool_name=properties.get("tool_name"),
175
+ args=args,
176
+ timestamp=properties.get("timestamp"),
177
+ success=properties.get("success", True),
178
+ metadata=metadata_dict,
179
+ )
180
+
181
+ search_results.append(
182
+ ToolMemorySearchResult(
183
+ memory=memory, similarity_score=similarity_score, rank=i + 1
184
+ )
185
+ )
186
+
187
+ return search_results
188
+
189
+ return await asyncio.get_event_loop().run_in_executor(self._executor, _search)
190
+
191
+ async def get_recent_memories(
192
+ self, context: ToolContext, limit: int = 10
193
+ ) -> List[ToolMemory]:
194
+ """Get recently added memories."""
195
+
196
+ def _get_recent():
197
+ client = self._get_client()
198
+ collection = client.collections.get(self.collection_name)
199
+
200
+ # Query and sort by timestamp
201
+ response = collection.query.fetch_objects(limit=1000)
202
+
203
+ # Convert to list and sort
204
+ objects_list = list(response.objects)
205
+ sorted_objects = sorted(
206
+ objects_list,
207
+ key=lambda o: o.properties.get("timestamp", ""),
208
+ reverse=True,
209
+ )
210
+
211
+ memories = []
212
+ for obj in sorted_objects[:limit]:
213
+ properties = obj.properties
214
+ args = json.loads(properties.get("args_json", "{}"))
215
+ metadata_dict = json.loads(properties.get("metadata_json", "{}"))
216
+
217
+ memory = ToolMemory(
218
+ memory_id=str(obj.uuid),
219
+ question=properties.get("question"),
220
+ tool_name=properties.get("tool_name"),
221
+ args=args,
222
+ timestamp=properties.get("timestamp"),
223
+ success=properties.get("success", True),
224
+ metadata=metadata_dict,
225
+ )
226
+ memories.append(memory)
227
+
228
+ return memories
229
+
230
+ return await asyncio.get_event_loop().run_in_executor(
231
+ self._executor, _get_recent
232
+ )
233
+
234
+ async def delete_by_id(self, context: ToolContext, memory_id: str) -> bool:
235
+ """Delete a memory by its ID."""
236
+
237
+ def _delete():
238
+ client = self._get_client()
239
+ collection = client.collections.get(self.collection_name)
240
+
241
+ try:
242
+ collection.data.delete_by_id(uuid=memory_id)
243
+ return True
244
+ except Exception:
245
+ return False
246
+
247
+ return await asyncio.get_event_loop().run_in_executor(self._executor, _delete)
248
+
249
+ async def save_text_memory(self, content: str, context: ToolContext) -> TextMemory:
250
+ """Save a text memory."""
251
+
252
+ def _save():
253
+ client = self._get_client()
254
+ collection = client.collections.get(self.collection_name)
255
+
256
+ memory_id = str(uuid.uuid4())
257
+ timestamp = datetime.now().isoformat()
258
+ embedding = self._create_embedding(content)
259
+
260
+ properties = {
261
+ "question": content, # Using question field for content
262
+ "tool_name": "", # Empty for text memories
263
+ "args_json": "",
264
+ "timestamp": timestamp,
265
+ "success": True,
266
+ "metadata_json": json.dumps({"is_text_memory": True}),
267
+ }
268
+
269
+ collection.data.insert(
270
+ properties=properties, vector=embedding, uuid=memory_id
271
+ )
272
+
273
+ return TextMemory(memory_id=memory_id, content=content, timestamp=timestamp)
274
+
275
+ return await asyncio.get_event_loop().run_in_executor(self._executor, _save)
276
+
277
+ async def search_text_memories(
278
+ self,
279
+ query: str,
280
+ context: ToolContext,
281
+ *,
282
+ limit: int = 10,
283
+ similarity_threshold: float = 0.7,
284
+ ) -> List[TextMemorySearchResult]:
285
+ """Search for similar text memories."""
286
+
287
+ def _search():
288
+ client = self._get_client()
289
+ collection = client.collections.get(self.collection_name)
290
+
291
+ embedding = self._create_embedding(query)
292
+
293
+ # Build filter for text memories (empty tool_name)
294
+ filters = weaviate.classes.query.Filter.by_property("tool_name").equal("")
295
+
296
+ response = collection.query.near_vector(
297
+ near_vector=embedding,
298
+ limit=limit,
299
+ filters=filters,
300
+ return_metadata=weaviate.classes.query.MetadataQuery(distance=True),
301
+ )
302
+
303
+ search_results = []
304
+ for i, obj in enumerate(response.objects):
305
+ distance = obj.metadata.distance if obj.metadata else 1.0
306
+ similarity_score = 1 - distance
307
+
308
+ if similarity_score >= similarity_threshold:
309
+ properties = obj.properties
310
+ content = properties.get("question", "")
311
+
312
+ memory = TextMemory(
313
+ memory_id=str(obj.uuid),
314
+ content=content,
315
+ timestamp=properties.get("timestamp"),
316
+ )
317
+
318
+ search_results.append(
319
+ TextMemorySearchResult(
320
+ memory=memory, similarity_score=similarity_score, rank=i + 1
321
+ )
322
+ )
323
+
324
+ return search_results
325
+
326
+ return await asyncio.get_event_loop().run_in_executor(self._executor, _search)
327
+
328
+ async def get_recent_text_memories(
329
+ self, context: ToolContext, limit: int = 10
330
+ ) -> List[TextMemory]:
331
+ """Get recently added text memories."""
332
+
333
+ def _get_recent():
334
+ client = self._get_client()
335
+ collection = client.collections.get(self.collection_name)
336
+
337
+ # Query text memories (empty tool_name) and sort by timestamp
338
+ response = collection.query.fetch_objects(
339
+ filters=weaviate.classes.query.Filter.by_property("tool_name").equal(
340
+ ""
341
+ ),
342
+ limit=1000,
343
+ )
344
+
345
+ # Convert to list and sort
346
+ objects_list = list(response.objects)
347
+ sorted_objects = sorted(
348
+ objects_list,
349
+ key=lambda o: o.properties.get("timestamp", ""),
350
+ reverse=True,
351
+ )
352
+
353
+ memories = []
354
+ for obj in sorted_objects[:limit]:
355
+ properties = obj.properties
356
+ content = properties.get("question", "")
357
+
358
+ memory = TextMemory(
359
+ memory_id=str(obj.uuid),
360
+ content=content,
361
+ timestamp=properties.get("timestamp"),
362
+ )
363
+ memories.append(memory)
364
+
365
+ return memories
366
+
367
+ return await asyncio.get_event_loop().run_in_executor(
368
+ self._executor, _get_recent
369
+ )
370
+
371
+ async def delete_text_memory(self, context: ToolContext, memory_id: str) -> bool:
372
+ """Delete a text memory by its ID."""
373
+
374
+ def _delete():
375
+ client = self._get_client()
376
+ collection = client.collections.get(self.collection_name)
377
+
378
+ try:
379
+ collection.data.delete_by_id(uuid=memory_id)
380
+ return True
381
+ except Exception:
382
+ return False
383
+
384
+ return await asyncio.get_event_loop().run_in_executor(self._executor, _delete)
385
+
386
+ async def clear_memories(
387
+ self,
388
+ context: ToolContext,
389
+ tool_name: Optional[str] = None,
390
+ before_date: Optional[str] = None,
391
+ ) -> int:
392
+ """Clear stored memories."""
393
+
394
+ def _clear():
395
+ client = self._get_client()
396
+ collection = client.collections.get(self.collection_name)
397
+
398
+ # Build filter
399
+ if tool_name and before_date:
400
+ filters = weaviate.classes.query.Filter.by_property("tool_name").equal(
401
+ tool_name
402
+ ) & weaviate.classes.query.Filter.by_property("timestamp").less_than(
403
+ before_date
404
+ )
405
+ elif tool_name:
406
+ filters = weaviate.classes.query.Filter.by_property("tool_name").equal(
407
+ tool_name
408
+ )
409
+ elif before_date:
410
+ filters = weaviate.classes.query.Filter.by_property(
411
+ "timestamp"
412
+ ).less_than(before_date)
413
+ else:
414
+ filters = None
415
+
416
+ if filters:
417
+ collection.data.delete_many(where=filters)
418
+ else:
419
+ # Delete all
420
+ collection.data.delete_many(
421
+ where=weaviate.classes.query.Filter.by_property(
422
+ "success"
423
+ ).contains_any([True, False])
424
+ )
425
+
426
+ return 0
427
+
428
+ return await asyncio.get_event_loop().run_in_executor(self._executor, _clear)
@@ -0,0 +1,233 @@
1
+ import re
2
+ from typing import List
3
+
4
+ import pandas as pd
5
+ from zhipuai import ZhipuAI
6
+
7
+ from ..base import VannaBase
8
+
9
+
10
+ class ZhipuAI_Chat(VannaBase):
11
+ def __init__(self, config=None):
12
+ VannaBase.__init__(self, config=config)
13
+ if config is None:
14
+ return
15
+ if "api_key" not in config:
16
+ raise Exception("Missing api_key in config")
17
+ self.api_key = config["api_key"]
18
+ self.model = config["model"] if "model" in config else "glm-4"
19
+ self.api_url = "https://open.bigmodel.cn/api/paas/v4/chat/completions"
20
+
21
+ # Static methods similar to those in ZhipuAI_Chat for message formatting and utility
22
+ @staticmethod
23
+ def system_message(message: str) -> dict:
24
+ return {"role": "system", "content": message}
25
+
26
+ @staticmethod
27
+ def user_message(message: str) -> dict:
28
+ return {"role": "user", "content": message}
29
+
30
+ @staticmethod
31
+ def assistant_message(message: str) -> dict:
32
+ return {"role": "assistant", "content": message}
33
+
34
+ @staticmethod
35
+ def str_to_approx_token_count(string: str) -> int:
36
+ return len(string) / 4
37
+
38
+ @staticmethod
39
+ def add_ddl_to_prompt(
40
+ initial_prompt: str, ddl_list: List[str], max_tokens: int = 14000
41
+ ) -> str:
42
+ if len(ddl_list) > 0:
43
+ initial_prompt += "\nYou may use the following DDL statements as a reference for what tables might be available. Use responses to past questions also to guide you:\n\n"
44
+
45
+ for ddl in ddl_list:
46
+ if (
47
+ ZhipuAI_Chat.str_to_approx_token_count(initial_prompt)
48
+ + ZhipuAI_Chat.str_to_approx_token_count(ddl)
49
+ < max_tokens
50
+ ):
51
+ initial_prompt += f"{ddl}\n\n"
52
+
53
+ return initial_prompt
54
+
55
+ @staticmethod
56
+ def add_documentation_to_prompt(
57
+ initial_prompt: str, documentation_List: List[str], max_tokens: int = 14000
58
+ ) -> str:
59
+ if len(documentation_List) > 0:
60
+ initial_prompt += "\nYou may use the following documentation as a reference for what tables might be available. Use responses to past questions also to guide you:\n\n"
61
+
62
+ for documentation in documentation_List:
63
+ if (
64
+ ZhipuAI_Chat.str_to_approx_token_count(initial_prompt)
65
+ + ZhipuAI_Chat.str_to_approx_token_count(documentation)
66
+ < max_tokens
67
+ ):
68
+ initial_prompt += f"{documentation}\n\n"
69
+
70
+ return initial_prompt
71
+
72
+ @staticmethod
73
+ def add_sql_to_prompt(
74
+ initial_prompt: str, sql_List: List[str], max_tokens: int = 14000
75
+ ) -> str:
76
+ if len(sql_List) > 0:
77
+ initial_prompt += "\nYou may use the following SQL statements as a reference for what tables might be available. Use responses to past questions also to guide you:\n\n"
78
+
79
+ for question in sql_List:
80
+ if (
81
+ ZhipuAI_Chat.str_to_approx_token_count(initial_prompt)
82
+ + ZhipuAI_Chat.str_to_approx_token_count(question["sql"])
83
+ < max_tokens
84
+ ):
85
+ initial_prompt += f"{question['question']}\n{question['sql']}\n\n"
86
+
87
+ return initial_prompt
88
+
89
+ def get_sql_prompt(
90
+ self,
91
+ question: str,
92
+ question_sql_list: List,
93
+ ddl_list: List,
94
+ doc_list: List,
95
+ **kwargs,
96
+ ):
97
+ initial_prompt = "The user provides a question and you provide SQL. You will only respond with SQL code and not with any explanations.\n\nRespond with only SQL code. Do not answer with any explanations -- just the code.\n"
98
+
99
+ initial_prompt = ZhipuAI_Chat.add_ddl_to_prompt(
100
+ initial_prompt, ddl_list, max_tokens=14000
101
+ )
102
+
103
+ initial_prompt = ZhipuAI_Chat.add_documentation_to_prompt(
104
+ initial_prompt, doc_list, max_tokens=14000
105
+ )
106
+
107
+ message_log = [ZhipuAI_Chat.system_message(initial_prompt)]
108
+
109
+ for example in question_sql_list:
110
+ if example is None:
111
+ print("example is None")
112
+ else:
113
+ if example is not None and "question" in example and "sql" in example:
114
+ message_log.append(ZhipuAI_Chat.user_message(example["question"]))
115
+ message_log.append(ZhipuAI_Chat.assistant_message(example["sql"]))
116
+
117
+ message_log.append({"role": "user", "content": question})
118
+
119
+ return message_log
120
+
121
+ def get_followup_questions_prompt(
122
+ self,
123
+ question: str,
124
+ df: pd.DataFrame,
125
+ question_sql_list: List,
126
+ ddl_list: List,
127
+ doc_list: List,
128
+ **kwargs,
129
+ ):
130
+ initial_prompt = f"The user initially asked the question: '{question}': \n\n"
131
+
132
+ initial_prompt = ZhipuAI_Chat.add_ddl_to_prompt(
133
+ initial_prompt, ddl_list, max_tokens=14000
134
+ )
135
+
136
+ initial_prompt = ZhipuAI_Chat.add_documentation_to_prompt(
137
+ initial_prompt, doc_list, max_tokens=14000
138
+ )
139
+
140
+ initial_prompt = ZhipuAI_Chat.add_sql_to_prompt(
141
+ initial_prompt, question_sql_list, max_tokens=14000
142
+ )
143
+
144
+ message_log = [ZhipuAI_Chat.system_message(initial_prompt)]
145
+ message_log.append(
146
+ ZhipuAI_Chat.user_message(
147
+ "Generate a List of followup questions that the user might ask about this data. Respond with a List of questions, one per line. Do not answer with any explanations -- just the questions."
148
+ )
149
+ )
150
+
151
+ return message_log
152
+
153
+ def generate_question(self, sql: str, **kwargs) -> str:
154
+ response = self.submit_prompt(
155
+ [
156
+ self.system_message(
157
+ "The user will give you SQL and you will try to guess what the business question this query is answering. Return just the question without any additional explanation. Do not reference the table name in the question."
158
+ ),
159
+ self.user_message(sql),
160
+ ],
161
+ **kwargs,
162
+ )
163
+
164
+ return response
165
+
166
+ def _extract_python_code(self, markdown_string: str) -> str:
167
+ # Regex pattern to match Python code blocks
168
+ pattern = r"```[\w\s]*python\n([\s\S]*?)```|```([\s\S]*?)```"
169
+
170
+ # Find all matches in the markdown string
171
+ matches = re.findall(pattern, markdown_string, re.IGNORECASE)
172
+
173
+ # Extract the Python code from the matches
174
+ python_code = []
175
+ for match in matches:
176
+ python = match[0] if match[0] else match[1]
177
+ python_code.append(python.strip())
178
+
179
+ if len(python_code) == 0:
180
+ return markdown_string
181
+
182
+ return python_code[0]
183
+
184
+ def _sanitize_plotly_code(self, raw_plotly_code: str) -> str:
185
+ # Remove the fig.show() statement from the plotly code
186
+ plotly_code = raw_plotly_code.replace("fig.show()", "")
187
+
188
+ return plotly_code
189
+
190
+ def generate_plotly_code(
191
+ self, question: str = None, sql: str = None, df_metadata: str = None, **kwargs
192
+ ) -> str:
193
+ if question is not None:
194
+ system_msg = f"The following is a pandas DataFrame that contains the results of the query that answers the question the user asked: '{question}'"
195
+ else:
196
+ system_msg = "The following is a pandas DataFrame "
197
+
198
+ if sql is not None:
199
+ system_msg += f"\n\nThe DataFrame was produced using this query: {sql}\n\n"
200
+
201
+ system_msg += f"The following is information about the resulting pandas DataFrame 'df': \n{df_metadata}"
202
+
203
+ message_log = [
204
+ self.system_message(system_msg),
205
+ self.user_message(
206
+ "Can you generate the Python plotly code to chart the results of the dataframe? Assume the data is in a pandas dataframe called 'df'. If there is only one value in the dataframe, use an Indicator. Respond with only Python code. Do not answer with any explanations -- just the code."
207
+ ),
208
+ ]
209
+
210
+ plotly_code = self.submit_prompt(message_log, kwargs=kwargs)
211
+
212
+ return self._sanitize_plotly_code(self._extract_python_code(plotly_code))
213
+
214
+ def submit_prompt(
215
+ self, prompt, max_tokens=500, temperature=0.7, top_p=0.7, stop=None, **kwargs
216
+ ):
217
+ if prompt is None:
218
+ raise Exception("Prompt is None")
219
+
220
+ if len(prompt) == 0:
221
+ raise Exception("Prompt is empty")
222
+
223
+ client = ZhipuAI(api_key=self.api_key)
224
+ response = client.chat.completions.create(
225
+ model="glm-4",
226
+ max_tokens=max_tokens,
227
+ temperature=temperature,
228
+ top_p=top_p,
229
+ stop=stop,
230
+ messages=prompt,
231
+ )
232
+
233
+ return response.choices[0].message.content