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.
@@ -1,868 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: vanna
3
- Version: 2.0.0rc1
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 ; 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 ; 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+: Web-First, User-Aware Agent Framework
175
-
176
- > [!WARNING]
177
- > This version of Vanna is actively under development and may contain breaking changes until it is officially released to PyPI.
178
- >
179
- > To install while in development, use:
180
- > ```bash
181
- > pip install --force-reinstall --no-cache-dir 'vanna[flask,anthropic] @ git+https://github.com/vanna-ai/vanna.git@v2'
182
- > ```
183
-
184
- > [!IMPORTANT]
185
- > If you're upgrading from an older version of Vanna, use the [Migration Guide](MIGRATION_GUIDE.md).
186
-
187
- > **Turn natural language into data insights — with enterprise-grade security baked in**
188
-
189
- Vanna is purpose-built for **data analytics** with **user awareness** as a first-class concern. Drop in our web component, connect your existing auth, and start querying data securely.
190
-
191
- [![Python](https://img.shields.io/badge/python-3.8+-blue.svg)](https://python.org)
192
- [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
193
- [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
194
-
195
- ![Vanna2 Diagram](img/architecture.png)
196
-
197
- ---
198
-
199
- ## Why Vanna?
200
-
201
- ```mermaid
202
- graph LR
203
- A[👤 User asks:<br/>'Show me Q4 sales'] --> B[🔐 Identity flows through<br/>every layer]
204
- B --> C[🧰 Tools execute securely<br/>with permissions]
205
- C --> D[📊 Rich UI streams back<br/>tables, charts, code]
206
-
207
- style B fill:#FFD93D,stroke:#333,stroke-width:3px
208
- ```
209
-
210
- ### What Makes Vanna Different
211
-
212
- **Built for Production Data Analytics**
213
- - **Pre-built web component + backend** — No need to build your own chat UI
214
- - **User-aware at every layer** — Identity and permissions flow through the entire system
215
- - **Rich streaming responses** — Tables, charts, SQL code blocks, not just text
216
- - **Works with your existing auth** — Cookies, JWTs, session tokens
217
- - **Enterprise security built-in** — Row-level security, audit logs, rate limiting
218
-
219
-
220
-
221
- ---
222
-
223
- ## Quick Start
224
-
225
- ### Installation
226
-
227
- ```bash
228
- pip install vanna[anthropic] # or [openai]
229
- ```
230
-
231
- ### Basic Example
232
-
233
- ```python
234
- from vanna import Agent, AgentConfig
235
- from vanna.servers.fastapi import VannaFastAPIServer
236
- from vanna.core.registry import ToolRegistry
237
- from vanna.core.user import UserResolver, User, RequestContext
238
- from vanna.integrations.anthropic import AnthropicLlmService
239
- from vanna.tools import RunSqlTool
240
- from vanna.integrations.sqlite import SqliteRunner
241
-
242
- # 1. Define how to resolve users from requests
243
- class SimpleUserResolver(UserResolver):
244
- async def resolve_user(self, request_context: RequestContext) -> User:
245
- # In production, validate cookies/JWTs here
246
- user_id = request_context.get_cookie('user_id') or 'demo_user'
247
- return User(id=user_id, group_memberships=['read_sales'])
248
-
249
- # 2. Set up LLM and tools
250
- llm = AnthropicLlmService(model="claude-sonnet-4-5")
251
- tools = ToolRegistry()
252
- tools.register(RunSqlTool(sql_runner=SqliteRunner(database_path="./data.db")))
253
-
254
- # 3. Create agent
255
- agent = Agent(
256
- llm_service=llm,
257
- tool_registry=tools,
258
- user_resolver=SimpleUserResolver()
259
- )
260
-
261
- # 4. Create and run server
262
- server = VannaFastAPIServer(agent)
263
- app = server.create_app()
264
-
265
- # Run with: uvicorn main:app --host 0.0.0.0 --port 8000 --reload
266
- # Visit http://localhost:8000 to see the web UI
267
- ```
268
-
269
- ---
270
-
271
- ![Vanna2 Diagram](img/vanna2.svg)
272
-
273
- ---
274
-
275
- ## What Makes Vanna Unique
276
-
277
- ### 1. User-Aware by Design
278
-
279
- Every layer of the system knows **who the user is** and **what they can access**.
280
-
281
- ```mermaid
282
- sequenceDiagram
283
- participant U as 👤 User
284
- participant W as 🌐 Web Component
285
- participant S as 🐍 Flask/FastAPI
286
- participant R as 🪪 User Resolver
287
- participant A as 🤖 Agent
288
- participant T as 🧰 Tools
289
-
290
- U->>W: "Show Q4 sales"
291
- W->>S: POST /api/vanna/v2/chat_sse
292
- S->>R: Extract user identity
293
- R->>A: User(id=alice, group_memberships=[read_sales])
294
- A->>A: Generate personalized system prompt
295
- A->>T: Execute SQL tool (user-aware)
296
- T->>T: Apply row-level security
297
- T->>A: Return filtered results
298
- A->>W: Stream: Table → Chart → Summary
299
- W->>U: Display results
300
- ```
301
-
302
- **Not just authentication — authorization at every step:**
303
-
304
- - System prompt customized per user
305
- - Tools check permissions before execution
306
- - SQL queries filtered by row-level security
307
- - Audit logs per user
308
- - Rate limiting per user
309
-
310
- ### 2. Drop-in Web Component
311
-
312
- ```html
313
- <!-- Works with any existing app -->
314
- <vanna-chat
315
- api-endpoint="/api/vanna/v2/chat_sse"
316
- initial-message="What can I help you with?"
317
- theme="dark">
318
- </vanna-chat>
319
- ```
320
-
321
- **Features:**
322
- - Uses your existing cookies/JWTs (no new login system)
323
- - Renders streaming tables, charts, SQL code blocks
324
- - Responsive and customizable
325
- - Framework-agnostic (works with React, Vue, plain HTML)
326
-
327
- ### 3. Purpose-Built for Data Analytics
328
-
329
- **Out-of-the-box tools:**
330
- - SQL generation and execution (with user permissions)
331
- - Data visualization with Plotly
332
- - File system operations (for coding agents)
333
- - Python code execution (sandboxed)
334
-
335
- ```python
336
- from vanna.tools import RunSqlTool, VisualizeDataTool
337
- from vanna.integrations.sqlite import SqliteRunner
338
- from vanna.integrations.local import LocalFileSystem
339
-
340
- file_system = LocalFileSystem("./data")
341
-
342
- tools.register(RunSqlTool(
343
- sql_runner=SqliteRunner(database_path="./data.db"),
344
- file_system=file_system
345
- ))
346
- tools.register(VisualizeDataTool(file_system=file_system))
347
- ```
348
-
349
- ### 4. Enterprise-Ready
350
-
351
- | Feature | Description |
352
- |---------|-------------|
353
- | **Row-Level Security** | SQL tools respect database permissions |
354
- | **Audit Logs** | Every query and tool call logged per user |
355
- | **Rate Limiting** | Per-user token/request limits via lifecycle hooks |
356
- | **Observability** | Built-in tracing and debugging hooks |
357
- | **Conversation Management** | Persistent conversation storage |
358
- | **Content Filtering** | Extensible filtering system |
359
-
360
- ---
361
-
362
- ## Architecture
363
-
364
- ```mermaid
365
- graph TB
366
- subgraph Frontend["🌐 Frontend"]
367
- WC[Web Component<br/>&lt;vanna-chat&gt;]
368
- end
369
-
370
- subgraph Backend["🐍 Python Server (Flask/FastAPI)"]
371
- direction TB
372
- SSE[SSE/WebSocket Handler]
373
- end
374
-
375
- subgraph Agent["🤖 User-Aware Agent"]
376
- direction TB
377
- UR[🪪 User Resolver<br/>YOUR auth system]
378
- SP[⚙️ System Prompt<br/>Per-user customization]
379
- LLM[🧠 LLM<br/>Claude/GPT/Gemini]
380
- Tools[🧰 Tools<br/>SQL, Charts, Memory]
381
- Comp[📄 UI Components<br/>Tables, Charts, Code]
382
-
383
- UR --> SP
384
- SP --> LLM
385
- UR --> Tools
386
- Tools <--> LLM
387
- Tools --> Comp
388
- LLM --> Comp
389
- end
390
-
391
- WC -->|User question<br/>+ cookies/JWT| SSE
392
- SSE -->|Request context| UR
393
- Comp -->|Streaming components| SSE
394
- SSE -->|Progressive updates| WC
395
-
396
- style UR fill:#95E1D3
397
- style Tools fill:#FFD93D
398
- style Comp fill:#C7F1FF
399
- ```
400
-
401
- ### Core Concepts
402
-
403
- **1. User Resolver** — You define this!
404
-
405
- ```python
406
- from vanna.core.user import UserResolver, User, RequestContext
407
-
408
- class MyUserResolver(UserResolver):
409
- async def resolve_user(self, request_context: RequestContext) -> User:
410
- # Extract from your existing auth system
411
- token = request_context.get_header('Authorization')
412
- user_data = self.decode_jwt(token) # Your logic
413
-
414
- return User(
415
- id=user_data['id'],
416
- email=user_data['email'],
417
- group_memberships=user_data['groups'], # Key!
418
- metadata={'role': user_data['role']}
419
- )
420
- ```
421
-
422
- **2. User-Aware Tools** — Check permissions automatically
423
-
424
- ```python
425
- from vanna.core.tool import Tool, ToolContext, ToolResult
426
- from pydantic import BaseModel, Field
427
- from typing import Type
428
-
429
- class QueryArgs(BaseModel):
430
- query: str = Field(description="SQL query to execute")
431
-
432
- class CustomSQLTool(Tool[QueryArgs]):
433
- @property
434
- def name(self) -> str:
435
- return "query_database"
436
-
437
- @property
438
- def description(self) -> str:
439
- return "Execute a SQL query against the database"
440
-
441
- @property
442
- def access_groups(self) -> list[str]:
443
- return ["read_sales"] # Only users in this group can use this tool
444
-
445
- def get_args_schema(self) -> Type[QueryArgs]:
446
- return QueryArgs
447
-
448
- async def execute(self, context: ToolContext, args: QueryArgs) -> ToolResult:
449
- user = context.user # Automatically injected
450
-
451
- # Apply row-level security
452
- filtered_query = self.add_user_filters(args.query, user)
453
- results = await self.db.execute(filtered_query)
454
-
455
- return ToolResult(
456
- success=True,
457
- result_for_llm=str(results)
458
- )
459
- ```
460
-
461
- **3. Streaming UI Components**
462
-
463
- ```python
464
- async for component in agent.send_message(request_context=ctx, message="Show sales"):
465
- # Rich component: structured data (tables, charts, status cards)
466
- print(type(component.rich_component).__name__)
467
-
468
- # Simple component: plain text fallback
469
- print(component.simple_component.text)
470
- ```
471
-
472
- Output:
473
- ```
474
- StatusBarUpdateComponent # "Processing..."
475
- TaskTrackerUpdateComponent # "Load conversation context"
476
- RichTextComponent # "Let me query the sales data..."
477
- StatusCardComponent # "Executing run_sql"
478
- DataFrameComponent # Tabular results
479
- RichTextComponent # "Here are your top customers..."
480
- ```
481
-
482
- ---
483
-
484
- ## Add to Existing FastAPI App
485
-
486
- If you already have a FastAPI application, you can add Vanna as additional routes:
487
-
488
- ```python
489
- from fastapi import FastAPI
490
- from vanna import Agent, AgentConfig
491
- from vanna.servers.base import ChatHandler
492
- from vanna.servers.fastapi.routes import register_chat_routes
493
- from vanna.core.registry import ToolRegistry
494
- from vanna.core.user import UserResolver, User, RequestContext
495
- from vanna.integrations.anthropic import AnthropicLlmService
496
- from vanna.tools import RunSqlTool
497
- from vanna.integrations.sqlite import SqliteRunner
498
-
499
- # Your existing FastAPI app
500
- app = FastAPI()
501
-
502
- # Your existing routes
503
- @app.get('/api/users')
504
- async def get_users():
505
- return {'users': [...]}
506
-
507
- @app.get('/api/products')
508
- async def get_products():
509
- return {'products': [...]}
510
-
511
- # Add Vanna agent
512
- class CookieUserResolver(UserResolver):
513
- async def resolve_user(self, request_context: RequestContext) -> User:
514
- user_id = request_context.get_cookie('user_id') or 'anonymous'
515
- role = request_context.get_cookie('role') or 'guest'
516
-
517
- groups = []
518
- if role == 'admin':
519
- groups = ['read_sales', 'read_confidential', 'admin']
520
- elif role == 'analyst':
521
- groups = ['read_sales']
522
-
523
- return User(id=user_id, group_memberships=groups)
524
-
525
- # Set up agent
526
- llm = AnthropicLlmService(model="claude-sonnet-4-5")
527
- tools = ToolRegistry()
528
- tools.register(RunSqlTool(sql_runner=SqliteRunner(database_path="./data.db")))
529
-
530
- agent = Agent(
531
- llm_service=llm,
532
- tool_registry=tools,
533
- user_resolver=CookieUserResolver()
534
- )
535
-
536
- # Add Vanna routes to your existing app
537
- chat_handler = ChatHandler(agent)
538
- register_chat_routes(app, chat_handler)
539
-
540
- # Run with: uvicorn main:app --host 0.0.0.0 --port 8000 --reload
541
- ```
542
-
543
- This adds these endpoints to your existing FastAPI app:
544
- - `GET /` - Vanna web component UI (you may want to change this)
545
- - `POST /api/vanna/v2/chat_sse` - Server-Sent Events streaming
546
- - `POST /api/vanna/v2/chat_poll` - Polling endpoint
547
- - `GET /health` - Health check
548
-
549
- To customize the routes or serve the UI at a different path, see the [server configuration docs](https://docs.vanna.ai).
550
-
551
- ---
552
-
553
- ## Custom Tools
554
-
555
- Create custom tools by extending the `Tool` base class:
556
-
557
- ```python
558
- from vanna.core.tool import Tool, ToolContext, ToolResult
559
- from vanna.components import UiComponent, NotificationComponent, SimpleTextComponent, ComponentType
560
- from pydantic import BaseModel, Field
561
- from typing import Type
562
-
563
- class EmailArgs(BaseModel):
564
- recipient: str = Field(description="Email recipient")
565
- subject: str = Field(description="Email subject")
566
- body: str = Field(description="Email body")
567
-
568
- class EmailTool(Tool[EmailArgs]):
569
- @property
570
- def name(self) -> str:
571
- return "send_email"
572
-
573
- @property
574
- def description(self) -> str:
575
- return "Send an email to a user"
576
-
577
- @property
578
- def access_groups(self) -> list[str]:
579
- return ["send_email"] # Only users in this group can use this tool
580
-
581
- def get_args_schema(self) -> Type[EmailArgs]:
582
- return EmailArgs
583
-
584
- async def execute(self, context: ToolContext, args: EmailArgs) -> ToolResult:
585
- user = context.user
586
-
587
- # Check domain restrictions
588
- if not args.recipient.endswith('@company.com'):
589
- error_msg = "Can only send to company email addresses"
590
- return ToolResult(
591
- success=False,
592
- result_for_llm=error_msg,
593
- error=error_msg,
594
- ui_component=UiComponent(
595
- rich_component=NotificationComponent(
596
- type=ComponentType.NOTIFICATION,
597
- level="error",
598
- message=error_msg
599
- ),
600
- simple_component=SimpleTextComponent(text=error_msg)
601
- )
602
- )
603
-
604
- # Send email (your logic)
605
- await self.email_service.send(
606
- from_email=user.email,
607
- to=args.recipient,
608
- subject=args.subject,
609
- body=args.body
610
- )
611
-
612
- success_msg = f"Email sent to {args.recipient}"
613
- return ToolResult(
614
- success=True,
615
- result_for_llm=success_msg,
616
- ui_component=UiComponent(
617
- rich_component=NotificationComponent(
618
- type=ComponentType.NOTIFICATION,
619
- level="success",
620
- message=success_msg
621
- ),
622
- simple_component=SimpleTextComponent(text=success_msg)
623
- )
624
- )
625
-
626
- # Register tool
627
- tools.register(EmailTool())
628
- ```
629
-
630
- ---
631
-
632
- ## Configuration
633
-
634
- ### Agent Configuration
635
-
636
- ```python
637
- from vanna import AgentConfig
638
-
639
- config = AgentConfig(
640
- max_tool_iterations=10, # Max tool calls per message
641
- stream_responses=True, # Enable streaming
642
- temperature=0.7, # LLM temperature
643
- include_thinking_indicators=True, # Show "Thinking..." states
644
- auto_save_conversations=True, # Auto-persist conversations
645
- max_tokens=None # Maximum response tokens
646
- )
647
-
648
- agent = Agent(
649
- llm_service=llm,
650
- tool_registry=tools,
651
- user_resolver=user_resolver,
652
- config=config
653
- )
654
- ```
655
-
656
- ### Environment Variables
657
-
658
- ```bash
659
- # Anthropic
660
- export ANTHROPIC_API_KEY="sk-ant-..."
661
- export ANTHROPIC_MODEL="claude-sonnet-4-5"
662
-
663
- # OpenAI
664
- export OPENAI_API_KEY="sk-..."
665
- export OPENAI_MODEL="gpt-5"
666
-
667
- # Database
668
- export DATABASE_URL="postgresql://localhost/mydb"
669
- ```
670
-
671
- ---
672
-
673
- ## Advanced Features
674
-
675
- ### 1. Conversation Storage
676
-
677
- ```python
678
- from vanna.integrations.local import MemoryConversationStore
679
-
680
- # Use in-memory storage (default)
681
- store = MemoryConversationStore()
682
-
683
- agent = Agent(
684
- llm_service=llm,
685
- tool_registry=tools,
686
- user_resolver=user_resolver,
687
- conversation_store=store
688
- )
689
-
690
- # List user's conversations
691
- alice = User(id="alice")
692
- conversations = await store.list_conversations(user=alice)
693
-
694
- # Get conversation history
695
- conversation = await store.get_conversation(
696
- conversation_id="conv_123",
697
- user=alice
698
- )
699
- ```
700
-
701
- ### 2. Lifecycle Hooks
702
-
703
- ```python
704
- from vanna.core.lifecycle import LifecycleHook
705
-
706
- class QuotaCheckHook(LifecycleHook):
707
- async def before_message(self, user: User, message: str) -> str:
708
- # Check if user has quota remaining
709
- if not await self.check_quota(user.id):
710
- raise Exception("Quota exceeded")
711
- return message
712
-
713
- async def after_tool(self, result: ToolResult) -> ToolResult:
714
- # Log tool execution
715
- await self.log_tool_execution(result)
716
- return result
717
-
718
- agent = Agent(
719
- llm_service=llm,
720
- tool_registry=tools,
721
- user_resolver=user_resolver,
722
- lifecycle_hooks=[QuotaCheckHook()]
723
- )
724
- ```
725
-
726
- ### 3. LLM Middlewares
727
-
728
- ```python
729
- from vanna.core.middleware import LlmMiddleware
730
- from vanna.core.llm import LlmRequest, LlmResponse
731
-
732
- class CachingMiddleware(LlmMiddleware):
733
- async def before_llm_request(self, request: LlmRequest) -> LlmRequest:
734
- # Check cache before sending to LLM
735
- cached = await self.cache.get(request)
736
- if cached:
737
- return cached
738
- return request
739
-
740
- async def after_llm_response(
741
- self,
742
- request: LlmRequest,
743
- response: LlmResponse
744
- ) -> LlmResponse:
745
- # Cache the response
746
- await self.cache.set(request, response)
747
- return response
748
-
749
- agent = Agent(
750
- llm_service=llm,
751
- tool_registry=tools,
752
- user_resolver=user_resolver,
753
- llm_middlewares=[CachingMiddleware()]
754
- )
755
- ```
756
-
757
- ### 4. Observability
758
-
759
- ```python
760
- from vanna.core.observability import ObservabilityProvider
761
-
762
- class LoggingProvider(ObservabilityProvider):
763
- async def create_span(self, name: str, attributes: dict):
764
- print(f"Starting: {name}")
765
- return Span(name, attributes)
766
-
767
- async def record_metric(self, name: str, value: float, unit: str, tags: dict):
768
- print(f"Metric: {name} = {value}{unit}")
769
-
770
- agent = Agent(
771
- llm_service=llm,
772
- tool_registry=tools,
773
- user_resolver=user_resolver,
774
- observability_provider=LoggingProvider()
775
- )
776
- ```
777
-
778
- ### 5. Context Enrichers
779
-
780
- ```python
781
- from vanna.core.enricher import ToolContextEnricher
782
- from vanna.core.tool import ToolContext
783
-
784
- class UserMetadataEnricher(ToolContextEnricher):
785
- async def enrich_context(self, context: ToolContext) -> ToolContext:
786
- # Add additional user metadata from database
787
- user_metadata = await self.db.get_user_metadata(context.user.id)
788
- context.user.metadata.update(user_metadata)
789
- return context
790
-
791
- agent = Agent(
792
- llm_service=llm,
793
- tool_registry=tools,
794
- user_resolver=user_resolver,
795
- context_enrichers=[UserMetadataEnricher()]
796
- )
797
- ```
798
-
799
- ### 6. LLM Context Enhancers
800
-
801
- Enhance LLM system prompts and messages with additional context (e.g., from memory, RAG, documentation):
802
-
803
- ```python
804
- from vanna.core.enhancer import LlmContextEnhancer, DefaultLlmContextEnhancer
805
- from vanna.core.llm import LlmMessage
806
- from vanna.core.user import User
807
-
808
- class CustomLlmContextEnhancer(LlmContextEnhancer):
809
- async def enhance_system_prompt(
810
- self,
811
- system_prompt: str,
812
- user_message: str,
813
- user: User
814
- ) -> str:
815
- # Add relevant context to system prompt based on user message
816
- relevant_docs = await self.search_documentation(user_message)
817
- return system_prompt + f"\n\nRelevant documentation:\n{relevant_docs}"
818
-
819
- async def enhance_user_messages(
820
- self,
821
- messages: list[LlmMessage],
822
- user: User
823
- ) -> list[LlmMessage]:
824
- # Optionally modify user messages
825
- return messages
826
-
827
- # Use default implementation (uses AgentMemory for RAG)
828
- agent = Agent(
829
- llm_service=llm,
830
- tool_registry=tools,
831
- user_resolver=user_resolver,
832
- agent_memory=agent_memory,
833
- llm_context_enhancer=DefaultLlmContextEnhancer(agent_memory) # Default if not provided
834
- )
835
-
836
- # Or use custom implementation
837
- agent = Agent(
838
- llm_service=llm,
839
- tool_registry=tools,
840
- user_resolver=user_resolver,
841
- llm_context_enhancer=CustomLlmContextEnhancer()
842
- )
843
- ```
844
-
845
- **Key difference:**
846
- - **Context Enrichers** (ToolContextEnricher): Enrich tool execution context
847
- - **LLM Context Enhancers** (LlmContextEnhancer): Enhance LLM prompts and messages
848
-
849
- ---
850
-
851
- ## When to Use Vanna
852
-
853
- **Vanna is ideal for:**
854
- - Building data analytics applications with natural language interfaces
855
- - Applications requiring user-aware permissions throughout
856
- - Teams that want a pre-built web component + backend integration
857
- - Enterprise environments with strict security requirements
858
- - Use cases needing rich streaming responses (tables, charts, SQL)
859
- - Integrating with existing authentication systems
860
-
861
- ---
862
-
863
- ## Documentation
864
-
865
- - **Migration Guide**: [Migrating from Vanna 1.x to 2.0+](MIGRATION_GUIDE.md)
866
- - **GitHub Discussions**: [GitHub Discussions](https://github.com/vanna-ai/vanna/discussions)
867
- - **Email**: support@vanna.ai
868
-