vanna 2.0.0rc1__py3-none-any.whl → 2.0.2__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.
@@ -16,6 +16,15 @@ try:
16
16
  from chromadb.config import Settings
17
17
  from chromadb.utils import embedding_functions
18
18
 
19
+ try:
20
+ from chromadb.errors import NotFoundError
21
+ except ImportError:
22
+ # Fallback for older ChromaDB versions that don't have chromadb.errors
23
+ class NotFoundError(Exception):
24
+ """Fallback NotFoundError for older ChromaDB versions."""
25
+
26
+ pass
27
+
19
28
  CHROMADB_AVAILABLE = True
20
29
  except ImportError:
21
30
  CHROMADB_AVAILABLE = False
@@ -31,7 +40,64 @@ from vanna.core.tool import ToolContext
31
40
 
32
41
 
33
42
  class ChromaAgentMemory(AgentMemory):
34
- """ChromaDB-based implementation of AgentMemory."""
43
+ """ChromaDB-based implementation of AgentMemory.
44
+
45
+ This implementation uses ChromaDB's PersistentClient to store agent memories
46
+ on disk, ensuring they persist across application restarts.
47
+
48
+ Key Features:
49
+ - Persistent storage: All memories are automatically saved to disk
50
+ - Efficient retrieval: Existing collections are loaded without re-initializing
51
+ embedding functions, avoiding unnecessary model downloads
52
+ - Flexible embedding: Supports custom embedding functions or uses ChromaDB's
53
+ default embedding function
54
+
55
+ Args:
56
+ persist_directory: Directory where ChromaDB will store its data.
57
+ Defaults to "./chroma_memory". Use an absolute path
58
+ for production deployments to ensure consistent location
59
+ across restarts.
60
+ collection_name: Name of the ChromaDB collection to use. Multiple agents
61
+ can share the same persist_directory with different
62
+ collection names.
63
+ embedding_function: Optional custom embedding function. If not provided,
64
+ ChromaDB's DefaultEmbeddingFunction is used (requires
65
+ internet connection on first use to download the model).
66
+ Once a collection is created, subsequent application
67
+ restarts will retrieve the existing collection without
68
+ re-downloading the model.
69
+
70
+ Example:
71
+ >>> from vanna.integrations.chromadb import ChromaAgentMemory
72
+ >>> # Basic usage with defaults
73
+ >>> memory = ChromaAgentMemory(
74
+ ... persist_directory="/app/data/chroma",
75
+ ... collection_name="my_agent_memory"
76
+ ... )
77
+ >>>
78
+ >>> # With custom embedding function (e.g., for offline use)
79
+ >>> from chromadb.utils import embedding_functions
80
+ >>> ef = embedding_functions.SentenceTransformerEmbeddingFunction()
81
+ >>> memory = ChromaAgentMemory(
82
+ ... persist_directory="/app/data/chroma",
83
+ ... embedding_function=ef
84
+ ... )
85
+
86
+ Note:
87
+ The default embedding function downloads an ONNX model (~80MB) on first use.
88
+ For air-gapped or offline environments, pre-download the model or provide
89
+ a custom embedding function.
90
+
91
+ Limitation:
92
+ This class does not validate that an existing Chroma collection was created
93
+ with the same embedding function as the one configured for the current
94
+ ``ChromaAgentMemory`` instance. If you reuse a collection (same
95
+ ``persist_directory`` and ``collection_name``) with a different embedding
96
+ function than was originally used, queries may fail or produce incorrect
97
+ similarity results. It is your responsibility to ensure that a given
98
+ collection is always accessed with a consistent embedding function, or to
99
+ implement your own validation around collection creation and reuse.
100
+ """
35
101
 
36
102
  def __init__(
37
103
  self,
@@ -76,12 +142,14 @@ class ChromaAgentMemory(AgentMemory):
76
142
  """Get or create ChromaDB collection."""
77
143
  if self._collection is None:
78
144
  client = self._get_client()
79
- embedding_func = self._get_embedding_function()
80
145
  try:
81
- self._collection = client.get_collection(
82
- name=self.collection_name, embedding_function=embedding_func
83
- )
84
- except Exception:
146
+ # Try to get existing collection first
147
+ # Don't pass embedding_function here to avoid re-instantiating/downloading it
148
+ # For existing collections, ChromaDB uses the stored embedding function configuration
149
+ self._collection = client.get_collection(name=self.collection_name)
150
+ except NotFoundError:
151
+ # Collection doesn't exist, create it with embedding function
152
+ embedding_func = self._get_embedding_function()
85
153
  self._collection = client.create_collection(
86
154
  name=self.collection_name,
87
155
  embedding_function=embedding_func,
@@ -215,6 +283,10 @@ class ChromaAgentMemory(AgentMemory):
215
283
  for i, (doc_id, metadata) in enumerate(
216
284
  zip(results["ids"], results["metadatas"])
217
285
  ):
286
+ # Skip text memories - they have is_text_memory flag
287
+ if metadata.get("is_text_memory"):
288
+ continue
289
+
218
290
  args = json.loads(metadata.get("args_json", "{}"))
219
291
  metadata_dict = json.loads(metadata.get("metadata_json", "{}"))
220
292
 
@@ -218,6 +218,10 @@ class FAISSAgentMemory(AgentMemory):
218
218
 
219
219
  memories = []
220
220
  for entry in sorted_entries[:limit]:
221
+ # Skip text memories - they have is_text_memory flag
222
+ if entry.get("is_text_memory"):
223
+ continue
224
+
221
225
  memory = ToolMemory(
222
226
  memory_id=entry["memory_id"],
223
227
  question=entry["question"],
@@ -148,13 +148,24 @@ class QdrantAgentMemory(AgentMemory):
148
148
  if conditions:
149
149
  query_filter = Filter(must=conditions)
150
150
 
151
- results = client.search(
152
- collection_name=self.collection_name,
153
- query_vector=embedding,
154
- limit=limit,
155
- query_filter=query_filter,
156
- score_threshold=similarity_threshold,
157
- )
151
+ # Use query_points for newer qdrant-client (1.8.0+) or search for older versions
152
+ if hasattr(client, "query_points"):
153
+ results = client.query_points(
154
+ collection_name=self.collection_name,
155
+ query=embedding,
156
+ limit=limit,
157
+ query_filter=query_filter,
158
+ score_threshold=similarity_threshold,
159
+ ).points
160
+ else:
161
+ # Fallback to search method for older qdrant-client versions
162
+ results = client.search(
163
+ collection_name=self.collection_name,
164
+ query_vector=embedding,
165
+ limit=limit,
166
+ query_filter=query_filter,
167
+ score_threshold=similarity_threshold,
168
+ )
158
169
 
159
170
  search_results = []
160
171
  for i, hit in enumerate(results):
@@ -204,6 +215,11 @@ class QdrantAgentMemory(AgentMemory):
204
215
  memories = []
205
216
  for point in sorted_points[:limit]:
206
217
  payload = point.payload
218
+
219
+ # Skip text memories - they have is_text_memory flag
220
+ if payload.get("is_text_memory"):
221
+ continue
222
+
207
223
  memory = ToolMemory(
208
224
  memory_id=str(point.id),
209
225
  question=payload["question"],
@@ -293,13 +309,24 @@ class QdrantAgentMemory(AgentMemory):
293
309
  ]
294
310
  )
295
311
 
296
- results = client.search(
297
- collection_name=self.collection_name,
298
- query_vector=embedding,
299
- limit=limit,
300
- query_filter=query_filter,
301
- score_threshold=similarity_threshold,
302
- )
312
+ # Use query_points for newer qdrant-client (1.8.0+) or search for older versions
313
+ if hasattr(client, "query_points"):
314
+ results = client.query_points(
315
+ collection_name=self.collection_name,
316
+ query=embedding,
317
+ limit=limit,
318
+ query_filter=query_filter,
319
+ score_threshold=similarity_threshold,
320
+ ).points
321
+ else:
322
+ # Fallback to search method for older qdrant-client versions
323
+ results = client.search(
324
+ collection_name=self.collection_name,
325
+ query_vector=embedding,
326
+ limit=limit,
327
+ query_filter=query_filter,
328
+ score_threshold=similarity_threshold,
329
+ )
303
330
 
304
331
  search_results = []
305
332
  for i, hit in enumerate(results):
@@ -0,0 +1,485 @@
1
+ Metadata-Version: 2.4
2
+ Name: vanna
3
+ Version: 2.0.2
4
+ Summary: Generate SQL queries from natural language
5
+ Author-email: Zain Hoda <zain@vanna.ai>
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ License-File: LICENSE
12
+ Requires-Dist: pydantic>=2.0.0
13
+ Requires-Dist: click>=8.0.0
14
+ Requires-Dist: pandas
15
+ Requires-Dist: httpx>=0.28.0
16
+ Requires-Dist: PyYAML
17
+ Requires-Dist: plotly
18
+ Requires-Dist: tabulate
19
+ Requires-Dist: sqlparse
20
+ Requires-Dist: sqlalchemy
21
+ Requires-Dist: requests
22
+ Requires-Dist: psycopg2-binary ; extra == "all"
23
+ Requires-Dist: db-dtypes ; extra == "all"
24
+ Requires-Dist: PyMySQL ; extra == "all"
25
+ Requires-Dist: google-cloud-bigquery ; extra == "all"
26
+ Requires-Dist: snowflake-connector-python ; extra == "all"
27
+ Requires-Dist: duckdb ; extra == "all"
28
+ Requires-Dist: openai ; extra == "all"
29
+ Requires-Dist: qianfan ; extra == "all"
30
+ Requires-Dist: mistralai>=1.0.0 ; extra == "all"
31
+ Requires-Dist: chromadb>=1.1.0 ; extra == "all"
32
+ Requires-Dist: anthropic ; extra == "all"
33
+ Requires-Dist: zhipuai ; extra == "all"
34
+ Requires-Dist: marqo ; extra == "all"
35
+ Requires-Dist: google-generativeai ; extra == "all"
36
+ Requires-Dist: google-cloud-aiplatform ; extra == "all"
37
+ Requires-Dist: qdrant-client>=1.0.0 ; extra == "all"
38
+ Requires-Dist: fastembed ; extra == "all"
39
+ Requires-Dist: ollama ; extra == "all"
40
+ Requires-Dist: httpx ; extra == "all"
41
+ Requires-Dist: opensearch-py ; extra == "all"
42
+ Requires-Dist: opensearch-dsl ; extra == "all"
43
+ Requires-Dist: transformers ; extra == "all"
44
+ Requires-Dist: pinecone ; extra == "all"
45
+ Requires-Dist: pymilvus[model] ; extra == "all"
46
+ Requires-Dist: weaviate-client ; extra == "all"
47
+ Requires-Dist: azure-search-documents ; extra == "all"
48
+ Requires-Dist: azure-identity ; extra == "all"
49
+ Requires-Dist: azure-common ; extra == "all"
50
+ Requires-Dist: faiss-cpu ; extra == "all"
51
+ Requires-Dist: boto ; extra == "all"
52
+ Requires-Dist: boto3 ; extra == "all"
53
+ Requires-Dist: botocore ; extra == "all"
54
+ Requires-Dist: langchain_core ; extra == "all"
55
+ Requires-Dist: langchain_postgres ; extra == "all"
56
+ Requires-Dist: langchain-community ; extra == "all"
57
+ Requires-Dist: langchain-huggingface ; extra == "all"
58
+ Requires-Dist: xinference-client ; extra == "all"
59
+ Requires-Dist: anthropic ; extra == "anthropic"
60
+ Requires-Dist: openai ; extra == "azureopenai"
61
+ Requires-Dist: azure-identity ; extra == "azureopenai"
62
+ Requires-Dist: azure-search-documents ; extra == "azuresearch"
63
+ Requires-Dist: azure-identity ; extra == "azuresearch"
64
+ Requires-Dist: azure-common ; extra == "azuresearch"
65
+ Requires-Dist: fastembed ; extra == "azuresearch"
66
+ Requires-Dist: boto3 ; extra == "bedrock"
67
+ Requires-Dist: botocore ; extra == "bedrock"
68
+ Requires-Dist: google-cloud-bigquery ; extra == "bigquery"
69
+ Requires-Dist: chromadb>=1.1.0 ; extra == "chromadb"
70
+ Requires-Dist: clickhouse_connect ; extra == "clickhouse"
71
+ Requires-Dist: pytest>=7.0.0 ; extra == "dev"
72
+ Requires-Dist: pytest-asyncio>=0.21.0 ; extra == "dev"
73
+ Requires-Dist: pytest-mock>=3.10.0 ; extra == "dev"
74
+ Requires-Dist: pytest-cov>=4.0.0 ; extra == "dev"
75
+ Requires-Dist: tox>=4.0.0 ; extra == "dev"
76
+ Requires-Dist: mypy ; extra == "dev"
77
+ Requires-Dist: ruff ; extra == "dev"
78
+ Requires-Dist: pandas-stubs ; extra == "dev"
79
+ Requires-Dist: plotly-stubs ; extra == "dev"
80
+ Requires-Dist: types-PyYAML ; extra == "dev"
81
+ Requires-Dist: types-requests ; extra == "dev"
82
+ Requires-Dist: types-tabulate ; extra == "dev"
83
+ Requires-Dist: duckdb ; extra == "duckdb"
84
+ Requires-Dist: faiss-cpu ; extra == "faiss-cpu"
85
+ Requires-Dist: faiss-gpu ; extra == "faiss-gpu"
86
+ Requires-Dist: fastapi>=0.68.0 ; extra == "fastapi"
87
+ Requires-Dist: uvicorn>=0.15.0 ; extra == "fastapi"
88
+ Requires-Dist: flask>=2.0.0 ; extra == "flask"
89
+ Requires-Dist: flask-cors>=4.0.0 ; extra == "flask"
90
+ Requires-Dist: google-genai ; extra == "gemini"
91
+ Requires-Dist: google-generativeai ; extra == "google"
92
+ Requires-Dist: google-cloud-aiplatform ; extra == "google"
93
+ Requires-Dist: transformers ; extra == "hf"
94
+ Requires-Dist: pyhive ; extra == "hive"
95
+ Requires-Dist: thrift ; extra == "hive"
96
+ Requires-Dist: marqo ; extra == "marqo"
97
+ Requires-Dist: pymilvus[model] ; extra == "milvus"
98
+ Requires-Dist: mistralai>=1.0.0 ; extra == "mistralai"
99
+ Requires-Dist: pyodbc ; extra == "mssql"
100
+ Requires-Dist: PyMySQL ; extra == "mysql"
101
+ Requires-Dist: ollama ; extra == "ollama"
102
+ Requires-Dist: httpx ; extra == "ollama"
103
+ Requires-Dist: openai ; extra == "openai"
104
+ Requires-Dist: opensearch-py ; extra == "opensearch"
105
+ Requires-Dist: opensearch-dsl ; extra == "opensearch"
106
+ Requires-Dist: langchain-community ; extra == "opensearch"
107
+ Requires-Dist: langchain-huggingface ; extra == "opensearch"
108
+ Requires-Dist: oracledb ; extra == "oracle"
109
+ Requires-Dist: chromadb<1.0.0 ; extra == "oracle"
110
+ Requires-Dist: langchain-postgres>=0.0.12 ; extra == "pgvector"
111
+ Requires-Dist: pinecone ; extra == "pinecone"
112
+ Requires-Dist: fastembed ; extra == "pinecone"
113
+ Requires-Dist: psycopg2-binary ; extra == "postgres"
114
+ Requires-Dist: db-dtypes ; extra == "postgres"
115
+ Requires-Dist: pyhive ; extra == "presto"
116
+ Requires-Dist: thrift ; extra == "presto"
117
+ Requires-Dist: qdrant-client>=1.0.0 ; extra == "qdrant"
118
+ Requires-Dist: fastembed ; extra == "qdrant"
119
+ Requires-Dist: qianfan ; extra == "qianfan"
120
+ Requires-Dist: vanna[flask, fastapi] ; extra == "servers"
121
+ Requires-Dist: snowflake-connector-python ; extra == "snowflake"
122
+ Requires-Dist: pytest>=7.0.0 ; extra == "test"
123
+ Requires-Dist: pytest-asyncio>=0.21.0 ; extra == "test"
124
+ Requires-Dist: pytest-mock>=3.10.0 ; extra == "test"
125
+ Requires-Dist: pytest-cov>=4.0.0 ; extra == "test"
126
+ Requires-Dist: tox>=4.0.0 ; extra == "test"
127
+ Requires-Dist: vllm ; extra == "vllm"
128
+ Requires-Dist: weaviate-client ; extra == "weaviate"
129
+ Requires-Dist: xinference-client ; extra == "xinference-client"
130
+ Requires-Dist: zhipuai ; extra == "zhipuai"
131
+ Project-URL: Bug Tracker, https://github.com/vanna-ai/vanna/issues
132
+ Project-URL: Homepage, https://github.com/vanna-ai/vanna
133
+ Provides-Extra: all
134
+ Provides-Extra: anthropic
135
+ Provides-Extra: azureopenai
136
+ Provides-Extra: azuresearch
137
+ Provides-Extra: bedrock
138
+ Provides-Extra: bigquery
139
+ Provides-Extra: chromadb
140
+ Provides-Extra: clickhouse
141
+ Provides-Extra: dev
142
+ Provides-Extra: duckdb
143
+ Provides-Extra: faiss-cpu
144
+ Provides-Extra: faiss-gpu
145
+ Provides-Extra: fastapi
146
+ Provides-Extra: flask
147
+ Provides-Extra: gemini
148
+ Provides-Extra: google
149
+ Provides-Extra: hf
150
+ Provides-Extra: hive
151
+ Provides-Extra: marqo
152
+ Provides-Extra: milvus
153
+ Provides-Extra: mistralai
154
+ Provides-Extra: mssql
155
+ Provides-Extra: mysql
156
+ Provides-Extra: ollama
157
+ Provides-Extra: openai
158
+ Provides-Extra: opensearch
159
+ Provides-Extra: oracle
160
+ Provides-Extra: pgvector
161
+ Provides-Extra: pinecone
162
+ Provides-Extra: postgres
163
+ Provides-Extra: presto
164
+ Provides-Extra: qdrant
165
+ Provides-Extra: qianfan
166
+ Provides-Extra: servers
167
+ Provides-Extra: snowflake
168
+ Provides-Extra: test
169
+ Provides-Extra: vllm
170
+ Provides-Extra: weaviate
171
+ Provides-Extra: xinference-client
172
+ Provides-Extra: zhipuai
173
+
174
+ # Vanna 2.0: Turn Questions into Data Insights
175
+
176
+ **Natural language → SQL → Answers.** Now with enterprise security and user-aware permissions.
177
+
178
+ [![Python](https://img.shields.io/badge/python-3.8+-blue.svg)](https://python.org)
179
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
180
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
181
+
182
+ https://github.com/user-attachments/assets/476cd421-d0b0-46af-8b29-0f40c73d6d83
183
+
184
+
185
+ ![Vanna2 Demo](img/architecture.png)
186
+
187
+ ---
188
+
189
+ ## What's New in 2.0
190
+
191
+ 🔐 **User-Aware at Every Layer** — Queries automatically filtered per user permissions
192
+
193
+ 🎨 **Modern Web Interface** — Beautiful pre-built `<vanna-chat>` component
194
+
195
+ ⚡ **Streaming Responses** — Real-time tables, charts, and progress updates
196
+
197
+ 🔒 **Enterprise Security** — Row-level security, audit logs, rate limiting
198
+
199
+ 🔄 **Production-Ready** — FastAPI integration, observability, lifecycle hooks
200
+
201
+ > **Upgrading from 0.x?** See the [Migration Guide](MIGRATION_GUIDE.md) | [What changed?](#migration-notes)
202
+
203
+ ---
204
+
205
+ ## Get Started
206
+
207
+ ### Try it with Sample Data
208
+
209
+ [Quickstart](https://vanna.ai/docs/quick-start)
210
+
211
+ ### Configure
212
+
213
+ [Configure](https://vanna.ai/docs/configure)
214
+
215
+ ### Web Component
216
+
217
+ ```html
218
+ <!-- Drop into any existing webpage -->
219
+ <script src="https://img.vanna.ai/vanna-components.js"></script>
220
+ <vanna-chat
221
+ sse-endpoint="https://your-api.com/chat"
222
+ theme="dark">
223
+ </vanna-chat>
224
+ ```
225
+
226
+ Uses your existing cookies/JWTs. Works with React, Vue, or plain HTML.
227
+
228
+ ---
229
+
230
+ ## What You Get
231
+
232
+ Ask a question in natural language and get back:
233
+
234
+ **1. Streaming Progress Updates**
235
+
236
+ **2. SQL Code Block (By default only shown to "admin" users)**
237
+
238
+ **3. Interactive Data Table**
239
+
240
+ **4. Charts** (Plotly visualizations)
241
+
242
+ **5. Natural Language Summary**
243
+
244
+ All streamed in real-time to your web component.
245
+
246
+ ---
247
+
248
+ ## Why Vanna 2.0?
249
+
250
+ ### ✅ Get Started Instantly
251
+ * Production chat interface
252
+ * Custom agent with your database
253
+ * Embed in any webpage
254
+
255
+ ### ✅ Enterprise-Ready Security
256
+ **User-aware at every layer** — Identity flows through system prompts, tool execution, and SQL filtering
257
+ **Row-level security** — Queries automatically filtered per user permissions
258
+ **Audit logs** — Every query tracked per user for compliance
259
+ **Rate limiting** — Per-user quotas via lifecycle hooks
260
+
261
+ ### ✅ Beautiful Web UI Included
262
+ **Pre-built `<vanna-chat>` component** — No need to build your own chat interface
263
+ **Streaming tables & charts** — Rich components, not just text
264
+ **Responsive & customizable** — Works on mobile, desktop, light/dark themes
265
+ **Framework-agnostic** — React, Vue, plain HTML
266
+
267
+ ### ✅ Works With Your Stack
268
+ **Any LLM:** OpenAI, Anthropic, Ollama, Azure, Google Gemini, AWS Bedrock, Mistral, Others
269
+ **Any Database:** PostgreSQL, MySQL, Snowflake, BigQuery, Redshift, SQLite, Oracle, SQL Server, DuckDB, ClickHouse, Others
270
+ **Your Auth System:** Bring your own — cookies, JWTs, OAuth tokens
271
+ **Your Framework:** FastAPI, Flask
272
+
273
+ ### ✅ Extensible But Opinionated
274
+ **Custom tools** — Extend the `Tool` base class
275
+ **Lifecycle hooks** — Quota checking, logging, content filtering
276
+ **LLM middlewares** — Caching, prompt engineering
277
+ **Observability** — Built-in tracing and metrics
278
+
279
+ ---
280
+
281
+ ## Architecture
282
+
283
+ ![Vanna2 Diagram](img/vanna2.svg)
284
+
285
+ ---
286
+
287
+ ## How It Works
288
+
289
+ ```mermaid
290
+ sequenceDiagram
291
+ participant U as 👤 User
292
+ participant W as 🌐 <vanna-chat>
293
+ participant S as 🐍 Your Server
294
+ participant A as 🤖 Agent
295
+ participant T as 🧰 Tools
296
+
297
+ U->>W: "Show Q4 sales"
298
+ W->>S: POST /api/vanna/v2/chat_sse (with auth)
299
+ S->>A: User(id=alice, groups=[read_sales])
300
+ A->>T: Execute SQL tool (user-aware)
301
+ T->>T: Apply row-level security
302
+ T->>A: Filtered results
303
+ A->>W: Stream: Table → Chart → Summary
304
+ W->>U: Display beautiful UI
305
+ ```
306
+
307
+ **Key Concepts:**
308
+
309
+ 1. **User Resolver** — You define how to extract user identity from requests (cookies, JWTs, etc.)
310
+ 2. **User-Aware Tools** — Tools automatically check permissions based on user's group memberships
311
+ 3. **Streaming Components** — Backend streams structured UI components (tables, charts) to frontend
312
+ 4. **Built-in Web UI** — Pre-built `<vanna-chat>` component renders everything beautifully
313
+
314
+ ---
315
+
316
+ ## Production Setup with Your Auth
317
+
318
+ Here's a complete example integrating Vanna with your existing FastAPI app and authentication:
319
+
320
+ ```python
321
+ from fastapi import FastAPI
322
+ from vanna import Agent
323
+ from vanna.servers.fastapi.routes import register_chat_routes
324
+ from vanna.servers.base import ChatHandler
325
+ from vanna.core.user import UserResolver, User, RequestContext
326
+ from vanna.integrations.anthropic import AnthropicLlmService
327
+ from vanna.tools import RunSqlTool
328
+ from vanna.integrations.sqlite import SqliteRunner
329
+ from vanna.core.registry import ToolRegistry
330
+
331
+ # Your existing FastAPI app
332
+ app = FastAPI()
333
+
334
+ # 1. Define your user resolver (using YOUR auth system)
335
+ class MyUserResolver(UserResolver):
336
+ async def resolve_user(self, request_context: RequestContext) -> User:
337
+ # Extract from cookies, JWTs, or session
338
+ token = request_context.get_header('Authorization')
339
+ user_data = self.decode_jwt(token) # Your existing logic
340
+
341
+ return User(
342
+ id=user_data['id'],
343
+ email=user_data['email'],
344
+ group_memberships=user_data['groups'] # Used for permissions
345
+ )
346
+
347
+ # 2. Set up agent with tools
348
+ llm = AnthropicLlmService(model="claude-sonnet-4-5")
349
+ tools = ToolRegistry()
350
+ tools.register(RunSqlTool(sql_runner=SqliteRunner("./data.db")))
351
+
352
+ agent = Agent(
353
+ llm_service=llm,
354
+ tool_registry=tools,
355
+ user_resolver=MyUserResolver()
356
+ )
357
+
358
+ # 3. Add Vanna routes to your app
359
+ chat_handler = ChatHandler(agent)
360
+ register_chat_routes(app, chat_handler)
361
+
362
+ # Now you have:
363
+ # - POST /api/vanna/v2/chat_sse (streaming endpoint)
364
+ # - GET / (optional web UI)
365
+ ```
366
+
367
+ **Then in your frontend:**
368
+ ```html
369
+ <vanna-chat sse-endpoint="/api/vanna/v2/chat_sse"></vanna-chat>
370
+ ```
371
+
372
+ See [Full Documentation](https://vanna.ai/docs) for custom tools, lifecycle hooks, and advanced configuration
373
+
374
+ ---
375
+
376
+ ## Custom Tools
377
+
378
+ Extend Vanna with custom tools for your specific use case:
379
+
380
+ ```python
381
+ from vanna.core.tool import Tool, ToolContext, ToolResult
382
+ from pydantic import BaseModel, Field
383
+ from typing import Type
384
+
385
+ class EmailArgs(BaseModel):
386
+ recipient: str = Field(description="Email recipient")
387
+ subject: str = Field(description="Email subject")
388
+
389
+ class EmailTool(Tool[EmailArgs]):
390
+ @property
391
+ def name(self) -> str:
392
+ return "send_email"
393
+
394
+ @property
395
+ def access_groups(self) -> list[str]:
396
+ return ["send_email"] # Permission check
397
+
398
+ def get_args_schema(self) -> Type[EmailArgs]:
399
+ return EmailArgs
400
+
401
+ async def execute(self, context: ToolContext, args: EmailArgs) -> ToolResult:
402
+ user = context.user # Automatically injected
403
+
404
+ # Your business logic
405
+ await self.email_service.send(
406
+ from_email=user.email,
407
+ to=args.recipient,
408
+ subject=args.subject
409
+ )
410
+
411
+ return ToolResult(success=True, result_for_llm=f"Email sent to {args.recipient}")
412
+
413
+ # Register your tool
414
+ tools.register(EmailTool())
415
+ ```
416
+
417
+ ---
418
+
419
+ ## Advanced Features
420
+
421
+ Vanna 2.0 includes powerful enterprise features for production use:
422
+
423
+ **Lifecycle Hooks** — Add quota checking, custom logging, content filtering at key points in the request lifecycle
424
+
425
+ **LLM Middlewares** — Implement caching, prompt engineering, or cost tracking around LLM calls
426
+
427
+ **Conversation Storage** — Persist and retrieve conversation history per user
428
+
429
+ **Observability** — Built-in tracing and metrics integration
430
+
431
+ **Context Enrichers** — Add RAG, memory, or documentation to enhance agent responses
432
+
433
+ **Agent Configuration** — Control streaming, temperature, max iterations, and more
434
+
435
+ ---
436
+
437
+ ## Use Cases
438
+
439
+ **Vanna is ideal for:**
440
+ - 📊 Data analytics applications with natural language interfaces
441
+ - 🔐 Multi-tenant SaaS needing user-aware permissions
442
+ - 🎨 Teams wanting a pre-built web component + backend
443
+ - 🏢 Enterprise environments with security/audit requirements
444
+ - 📈 Applications needing rich streaming responses (tables, charts, SQL)
445
+ - 🔄 Integrating with existing authentication systems
446
+
447
+ ---
448
+
449
+ ## Community & Support
450
+
451
+ - 📖 **[Full Documentation](https://vanna.ai/docs)** — Complete guides and API reference
452
+ - 💡 **[GitHub Discussions](https://github.com/vanna-ai/vanna/discussions)** — Feature requests and Q&A
453
+ - 🐛 **[GitHub Issues](https://github.com/vanna-ai/vanna/issues)** — Bug reports
454
+ - 📧 **Enterprise Support** — support@vanna.ai
455
+
456
+ ---
457
+
458
+ ## Migration Notes
459
+
460
+ **Upgrading from Vanna 0.x?**
461
+
462
+ Vanna 2.0 is a complete rewrite focused on user-aware agents and production deployments. Key changes:
463
+
464
+ - **New API**: Agent-based instead of `VannaBase` class methods
465
+ - **User-aware**: Every component now knows the user identity
466
+ - **Streaming**: Rich UI components instead of text/dataframes
467
+ - **Web-first**: Built-in `<vanna-chat>` component and server
468
+
469
+ **Migration path:**
470
+
471
+ 1. **Quick wrap** — Use `LegacyVannaAdapter` to wrap your existing Vanna 0.x instance and get the new web UI immediately
472
+ 2. **Gradual migration** — Incrementally move to the new Agent API and tools
473
+
474
+ See the complete [Migration Guide](MIGRATION_GUIDE.md) for step-by-step instructions.
475
+
476
+ ---
477
+
478
+ ## License
479
+
480
+ MIT License — See [LICENSE](LICENSE) for details.
481
+
482
+ ---
483
+
484
+ **Built with ❤️ by the Vanna team** | [Website](https://vanna.ai) | [Docs](https://vanna.ai/docs) | [Discussions](https://github.com/vanna-ai/vanna/discussions)
485
+