vanna 0.7.9__py3-none-any.whl ā 2.0.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.
- vanna/__init__.py +167 -395
- vanna/agents/__init__.py +7 -0
- vanna/capabilities/__init__.py +17 -0
- vanna/capabilities/agent_memory/__init__.py +21 -0
- vanna/capabilities/agent_memory/base.py +103 -0
- vanna/capabilities/agent_memory/models.py +53 -0
- vanna/capabilities/file_system/__init__.py +14 -0
- vanna/capabilities/file_system/base.py +71 -0
- vanna/capabilities/file_system/models.py +25 -0
- vanna/capabilities/sql_runner/__init__.py +13 -0
- vanna/capabilities/sql_runner/base.py +37 -0
- vanna/capabilities/sql_runner/models.py +13 -0
- vanna/components/__init__.py +92 -0
- vanna/components/base.py +11 -0
- vanna/components/rich/__init__.py +83 -0
- vanna/components/rich/containers/__init__.py +7 -0
- vanna/components/rich/containers/card.py +20 -0
- vanna/components/rich/data/__init__.py +9 -0
- vanna/components/rich/data/chart.py +17 -0
- vanna/components/rich/data/dataframe.py +93 -0
- vanna/components/rich/feedback/__init__.py +21 -0
- vanna/components/rich/feedback/badge.py +16 -0
- vanna/components/rich/feedback/icon_text.py +14 -0
- vanna/components/rich/feedback/log_viewer.py +41 -0
- vanna/components/rich/feedback/notification.py +19 -0
- vanna/components/rich/feedback/progress.py +37 -0
- vanna/components/rich/feedback/status_card.py +28 -0
- vanna/components/rich/feedback/status_indicator.py +14 -0
- vanna/components/rich/interactive/__init__.py +21 -0
- vanna/components/rich/interactive/button.py +95 -0
- vanna/components/rich/interactive/task_list.py +58 -0
- vanna/components/rich/interactive/ui_state.py +93 -0
- vanna/components/rich/specialized/__init__.py +7 -0
- vanna/components/rich/specialized/artifact.py +20 -0
- vanna/components/rich/text.py +16 -0
- vanna/components/simple/__init__.py +15 -0
- vanna/components/simple/image.py +15 -0
- vanna/components/simple/link.py +15 -0
- vanna/components/simple/text.py +11 -0
- vanna/core/__init__.py +193 -0
- vanna/core/_compat.py +19 -0
- vanna/core/agent/__init__.py +10 -0
- vanna/core/agent/agent.py +1407 -0
- vanna/core/agent/config.py +123 -0
- vanna/core/audit/__init__.py +28 -0
- vanna/core/audit/base.py +299 -0
- vanna/core/audit/models.py +131 -0
- vanna/core/component_manager.py +329 -0
- vanna/core/components.py +53 -0
- vanna/core/enhancer/__init__.py +11 -0
- vanna/core/enhancer/base.py +94 -0
- vanna/core/enhancer/default.py +118 -0
- vanna/core/enricher/__init__.py +10 -0
- vanna/core/enricher/base.py +59 -0
- vanna/core/errors.py +47 -0
- vanna/core/evaluation/__init__.py +81 -0
- vanna/core/evaluation/base.py +186 -0
- vanna/core/evaluation/dataset.py +254 -0
- vanna/core/evaluation/evaluators.py +376 -0
- vanna/core/evaluation/report.py +289 -0
- vanna/core/evaluation/runner.py +313 -0
- vanna/core/filter/__init__.py +10 -0
- vanna/core/filter/base.py +67 -0
- vanna/core/lifecycle/__init__.py +10 -0
- vanna/core/lifecycle/base.py +83 -0
- vanna/core/llm/__init__.py +16 -0
- vanna/core/llm/base.py +40 -0
- vanna/core/llm/models.py +61 -0
- vanna/core/middleware/__init__.py +10 -0
- vanna/core/middleware/base.py +69 -0
- vanna/core/observability/__init__.py +11 -0
- vanna/core/observability/base.py +88 -0
- vanna/core/observability/models.py +47 -0
- vanna/core/recovery/__init__.py +11 -0
- vanna/core/recovery/base.py +84 -0
- vanna/core/recovery/models.py +32 -0
- vanna/core/registry.py +278 -0
- vanna/core/rich_component.py +156 -0
- vanna/core/simple_component.py +27 -0
- vanna/core/storage/__init__.py +14 -0
- vanna/core/storage/base.py +46 -0
- vanna/core/storage/models.py +46 -0
- vanna/core/system_prompt/__init__.py +13 -0
- vanna/core/system_prompt/base.py +36 -0
- vanna/core/system_prompt/default.py +157 -0
- vanna/core/tool/__init__.py +18 -0
- vanna/core/tool/base.py +70 -0
- vanna/core/tool/models.py +84 -0
- vanna/core/user/__init__.py +17 -0
- vanna/core/user/base.py +29 -0
- vanna/core/user/models.py +25 -0
- vanna/core/user/request_context.py +70 -0
- vanna/core/user/resolver.py +42 -0
- vanna/core/validation.py +164 -0
- vanna/core/workflow/__init__.py +12 -0
- vanna/core/workflow/base.py +254 -0
- vanna/core/workflow/default.py +789 -0
- vanna/examples/__init__.py +1 -0
- vanna/examples/__main__.py +44 -0
- vanna/examples/anthropic_quickstart.py +80 -0
- vanna/examples/artifact_example.py +293 -0
- vanna/examples/claude_sqlite_example.py +236 -0
- vanna/examples/coding_agent_example.py +300 -0
- vanna/examples/custom_system_prompt_example.py +174 -0
- vanna/examples/default_workflow_handler_example.py +208 -0
- vanna/examples/email_auth_example.py +340 -0
- vanna/examples/evaluation_example.py +269 -0
- vanna/examples/extensibility_example.py +262 -0
- vanna/examples/minimal_example.py +67 -0
- vanna/examples/mock_auth_example.py +227 -0
- vanna/examples/mock_custom_tool.py +311 -0
- vanna/examples/mock_quickstart.py +79 -0
- vanna/examples/mock_quota_example.py +145 -0
- vanna/examples/mock_rich_components_demo.py +396 -0
- vanna/examples/mock_sqlite_example.py +223 -0
- vanna/examples/openai_quickstart.py +83 -0
- vanna/examples/primitive_components_demo.py +305 -0
- vanna/examples/quota_lifecycle_example.py +139 -0
- vanna/examples/visualization_example.py +251 -0
- vanna/integrations/__init__.py +17 -0
- vanna/integrations/anthropic/__init__.py +9 -0
- vanna/integrations/anthropic/llm.py +270 -0
- vanna/integrations/azureopenai/__init__.py +9 -0
- vanna/integrations/azureopenai/llm.py +329 -0
- vanna/integrations/azuresearch/__init__.py +7 -0
- vanna/integrations/azuresearch/agent_memory.py +413 -0
- vanna/integrations/bigquery/__init__.py +5 -0
- vanna/integrations/bigquery/sql_runner.py +81 -0
- vanna/integrations/chromadb/__init__.py +104 -0
- vanna/integrations/chromadb/agent_memory.py +416 -0
- vanna/integrations/clickhouse/__init__.py +5 -0
- vanna/integrations/clickhouse/sql_runner.py +82 -0
- vanna/integrations/duckdb/__init__.py +5 -0
- vanna/integrations/duckdb/sql_runner.py +65 -0
- vanna/integrations/faiss/__init__.py +7 -0
- vanna/integrations/faiss/agent_memory.py +431 -0
- vanna/integrations/google/__init__.py +9 -0
- vanna/integrations/google/gemini.py +370 -0
- vanna/integrations/hive/__init__.py +5 -0
- vanna/integrations/hive/sql_runner.py +87 -0
- vanna/integrations/local/__init__.py +17 -0
- vanna/integrations/local/agent_memory/__init__.py +7 -0
- vanna/integrations/local/agent_memory/in_memory.py +285 -0
- vanna/integrations/local/audit.py +59 -0
- vanna/integrations/local/file_system.py +242 -0
- vanna/integrations/local/file_system_conversation_store.py +255 -0
- vanna/integrations/local/storage.py +62 -0
- vanna/integrations/marqo/__init__.py +7 -0
- vanna/integrations/marqo/agent_memory.py +354 -0
- vanna/integrations/milvus/__init__.py +7 -0
- vanna/integrations/milvus/agent_memory.py +458 -0
- vanna/integrations/mock/__init__.py +9 -0
- vanna/integrations/mock/llm.py +65 -0
- vanna/integrations/mssql/__init__.py +5 -0
- vanna/integrations/mssql/sql_runner.py +66 -0
- vanna/integrations/mysql/__init__.py +5 -0
- vanna/integrations/mysql/sql_runner.py +92 -0
- vanna/integrations/ollama/__init__.py +7 -0
- vanna/integrations/ollama/llm.py +252 -0
- vanna/integrations/openai/__init__.py +10 -0
- vanna/integrations/openai/llm.py +267 -0
- vanna/integrations/openai/responses.py +163 -0
- vanna/integrations/opensearch/__init__.py +7 -0
- vanna/integrations/opensearch/agent_memory.py +411 -0
- vanna/integrations/oracle/__init__.py +5 -0
- vanna/integrations/oracle/sql_runner.py +75 -0
- vanna/integrations/pinecone/__init__.py +7 -0
- vanna/integrations/pinecone/agent_memory.py +329 -0
- vanna/integrations/plotly/__init__.py +5 -0
- vanna/integrations/plotly/chart_generator.py +313 -0
- vanna/integrations/postgres/__init__.py +9 -0
- vanna/integrations/postgres/sql_runner.py +112 -0
- vanna/integrations/premium/agent_memory/__init__.py +7 -0
- vanna/integrations/premium/agent_memory/premium.py +186 -0
- vanna/integrations/presto/__init__.py +5 -0
- vanna/integrations/presto/sql_runner.py +107 -0
- vanna/integrations/qdrant/__init__.py +7 -0
- vanna/integrations/qdrant/agent_memory.py +461 -0
- vanna/integrations/snowflake/__init__.py +5 -0
- vanna/integrations/snowflake/sql_runner.py +147 -0
- vanna/integrations/sqlite/__init__.py +9 -0
- vanna/integrations/sqlite/sql_runner.py +65 -0
- vanna/integrations/weaviate/__init__.py +7 -0
- vanna/integrations/weaviate/agent_memory.py +428 -0
- vanna/{ZhipuAI ā legacy/ZhipuAI}/ZhipuAI_embeddings.py +11 -11
- vanna/legacy/__init__.py +403 -0
- vanna/legacy/adapter.py +463 -0
- vanna/{advanced ā legacy/advanced}/__init__.py +3 -1
- vanna/{anthropic ā legacy/anthropic}/anthropic_chat.py +9 -7
- vanna/{azuresearch ā legacy/azuresearch}/azuresearch_vector.py +79 -41
- vanna/{base ā legacy/base}/base.py +224 -217
- vanna/legacy/bedrock/__init__.py +1 -0
- vanna/{bedrock ā legacy/bedrock}/bedrock_converse.py +13 -12
- vanna/{chromadb ā legacy/chromadb}/chromadb_vector.py +3 -1
- vanna/legacy/cohere/__init__.py +2 -0
- vanna/{cohere ā legacy/cohere}/cohere_chat.py +19 -14
- vanna/{cohere ā legacy/cohere}/cohere_embeddings.py +25 -19
- vanna/{deepseek ā legacy/deepseek}/deepseek_chat.py +5 -6
- vanna/legacy/faiss/__init__.py +1 -0
- vanna/{faiss ā legacy/faiss}/faiss.py +113 -59
- vanna/{flask ā legacy/flask}/__init__.py +84 -43
- vanna/{flask ā legacy/flask}/assets.py +5 -5
- vanna/{flask ā legacy/flask}/auth.py +5 -4
- vanna/{google ā legacy/google}/bigquery_vector.py +75 -42
- vanna/{google ā legacy/google}/gemini_chat.py +7 -3
- vanna/{hf ā legacy/hf}/hf.py +0 -1
- vanna/{milvus ā legacy/milvus}/milvus_vector.py +58 -35
- vanna/{mock ā legacy/mock}/llm.py +0 -1
- vanna/legacy/mock/vectordb.py +67 -0
- vanna/legacy/ollama/ollama.py +110 -0
- vanna/{openai ā legacy/openai}/openai_chat.py +2 -6
- vanna/legacy/opensearch/opensearch_vector.py +369 -0
- vanna/legacy/opensearch/opensearch_vector_semantic.py +200 -0
- vanna/legacy/oracle/oracle_vector.py +584 -0
- vanna/{pgvector ā legacy/pgvector}/pgvector.py +42 -13
- vanna/{qdrant ā legacy/qdrant}/qdrant.py +2 -6
- vanna/legacy/qianfan/Qianfan_Chat.py +170 -0
- vanna/legacy/qianfan/Qianfan_embeddings.py +36 -0
- vanna/legacy/qianwen/QianwenAI_chat.py +132 -0
- vanna/{remote.py ā legacy/remote.py} +28 -26
- vanna/{utils.py ā legacy/utils.py} +6 -11
- vanna/{vannadb ā legacy/vannadb}/vannadb_vector.py +115 -46
- vanna/{vllm ā legacy/vllm}/vllm.py +5 -6
- vanna/{weaviate ā legacy/weaviate}/weaviate_vector.py +59 -40
- vanna/{xinference ā legacy/xinference}/xinference.py +6 -6
- vanna/py.typed +0 -0
- vanna/servers/__init__.py +16 -0
- vanna/servers/__main__.py +8 -0
- vanna/servers/base/__init__.py +18 -0
- vanna/servers/base/chat_handler.py +65 -0
- vanna/servers/base/models.py +111 -0
- vanna/servers/base/rich_chat_handler.py +141 -0
- vanna/servers/base/templates.py +331 -0
- vanna/servers/cli/__init__.py +7 -0
- vanna/servers/cli/server_runner.py +204 -0
- vanna/servers/fastapi/__init__.py +7 -0
- vanna/servers/fastapi/app.py +163 -0
- vanna/servers/fastapi/routes.py +183 -0
- vanna/servers/flask/__init__.py +7 -0
- vanna/servers/flask/app.py +132 -0
- vanna/servers/flask/routes.py +137 -0
- vanna/tools/__init__.py +41 -0
- vanna/tools/agent_memory.py +322 -0
- vanna/tools/file_system.py +879 -0
- vanna/tools/python.py +222 -0
- vanna/tools/run_sql.py +165 -0
- vanna/tools/visualize_data.py +195 -0
- vanna/utils/__init__.py +0 -0
- vanna/web_components/__init__.py +44 -0
- vanna-2.0.0.dist-info/METADATA +485 -0
- vanna-2.0.0.dist-info/RECORD +289 -0
- vanna-2.0.0.dist-info/entry_points.txt +3 -0
- vanna/bedrock/__init__.py +0 -1
- vanna/cohere/__init__.py +0 -2
- vanna/faiss/__init__.py +0 -1
- vanna/mock/vectordb.py +0 -55
- vanna/ollama/ollama.py +0 -103
- vanna/opensearch/opensearch_vector.py +0 -392
- vanna/opensearch/opensearch_vector_semantic.py +0 -175
- vanna/oracle/oracle_vector.py +0 -585
- vanna/qianfan/Qianfan_Chat.py +0 -165
- vanna/qianfan/Qianfan_embeddings.py +0 -36
- vanna/qianwen/QianwenAI_chat.py +0 -133
- vanna-0.7.9.dist-info/METADATA +0 -408
- vanna-0.7.9.dist-info/RECORD +0 -79
- /vanna/{ZhipuAI ā legacy/ZhipuAI}/ZhipuAI_Chat.py +0 -0
- /vanna/{ZhipuAI ā legacy/ZhipuAI}/__init__.py +0 -0
- /vanna/{anthropic ā legacy/anthropic}/__init__.py +0 -0
- /vanna/{azuresearch ā legacy/azuresearch}/__init__.py +0 -0
- /vanna/{base ā legacy/base}/__init__.py +0 -0
- /vanna/{chromadb ā legacy/chromadb}/__init__.py +0 -0
- /vanna/{deepseek ā legacy/deepseek}/__init__.py +0 -0
- /vanna/{exceptions ā legacy/exceptions}/__init__.py +0 -0
- /vanna/{google ā legacy/google}/__init__.py +0 -0
- /vanna/{hf ā legacy/hf}/__init__.py +0 -0
- /vanna/{local.py ā legacy/local.py} +0 -0
- /vanna/{marqo ā legacy/marqo}/__init__.py +0 -0
- /vanna/{marqo ā legacy/marqo}/marqo.py +0 -0
- /vanna/{milvus ā legacy/milvus}/__init__.py +0 -0
- /vanna/{mistral ā legacy/mistral}/__init__.py +0 -0
- /vanna/{mistral ā legacy/mistral}/mistral.py +0 -0
- /vanna/{mock ā legacy/mock}/__init__.py +0 -0
- /vanna/{mock ā legacy/mock}/embedding.py +0 -0
- /vanna/{ollama ā legacy/ollama}/__init__.py +0 -0
- /vanna/{openai ā legacy/openai}/__init__.py +0 -0
- /vanna/{openai ā legacy/openai}/openai_embeddings.py +0 -0
- /vanna/{opensearch ā legacy/opensearch}/__init__.py +0 -0
- /vanna/{oracle ā legacy/oracle}/__init__.py +0 -0
- /vanna/{pgvector ā legacy/pgvector}/__init__.py +0 -0
- /vanna/{pinecone ā legacy/pinecone}/__init__.py +0 -0
- /vanna/{pinecone ā legacy/pinecone}/pinecone_vector.py +0 -0
- /vanna/{qdrant ā legacy/qdrant}/__init__.py +0 -0
- /vanna/{qianfan ā legacy/qianfan}/__init__.py +0 -0
- /vanna/{qianwen ā legacy/qianwen}/QianwenAI_embeddings.py +0 -0
- /vanna/{qianwen ā legacy/qianwen}/__init__.py +0 -0
- /vanna/{types ā legacy/types}/__init__.py +0 -0
- /vanna/{vannadb ā legacy/vannadb}/__init__.py +0 -0
- /vanna/{vllm ā legacy/vllm}/__init__.py +0 -0
- /vanna/{weaviate ā legacy/weaviate}/__init__.py +0 -0
- /vanna/{xinference ā legacy/xinference}/__init__.py +0 -0
- {vanna-0.7.9.dist-info ā vanna-2.0.0.dist-info}/WHEEL +0 -0
- {vanna-0.7.9.dist-info ā vanna-2.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Comprehensive example demonstrating all extensibility interfaces.
|
|
3
|
+
|
|
4
|
+
This example shows how to use:
|
|
5
|
+
- LlmMiddleware for caching
|
|
6
|
+
- ErrorRecoveryStrategy for retry logic
|
|
7
|
+
- ToolContextEnricher for adding user preferences
|
|
8
|
+
- ConversationFilter for context window management
|
|
9
|
+
- ObservabilityProvider for monitoring
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
import time
|
|
14
|
+
from typing import Any, Dict, List, Optional
|
|
15
|
+
|
|
16
|
+
from vanna.core import (
|
|
17
|
+
Agent,
|
|
18
|
+
LlmMiddleware,
|
|
19
|
+
ErrorRecoveryStrategy,
|
|
20
|
+
ToolContextEnricher,
|
|
21
|
+
ConversationFilter,
|
|
22
|
+
ObservabilityProvider,
|
|
23
|
+
User,
|
|
24
|
+
ToolContext,
|
|
25
|
+
Conversation,
|
|
26
|
+
Message,
|
|
27
|
+
LlmRequest,
|
|
28
|
+
LlmResponse,
|
|
29
|
+
Span,
|
|
30
|
+
Metric,
|
|
31
|
+
)
|
|
32
|
+
from vanna.core.recovery import RecoveryAction, RecoveryActionType
|
|
33
|
+
from vanna.core.registry import ToolRegistry
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# 1. LlmMiddleware Example: Simple Caching
|
|
37
|
+
class CachingMiddleware(LlmMiddleware):
|
|
38
|
+
"""Cache LLM responses to reduce costs and latency."""
|
|
39
|
+
|
|
40
|
+
def __init__(self) -> None:
|
|
41
|
+
self.cache: Dict[str, LlmResponse] = {}
|
|
42
|
+
self.hits = 0
|
|
43
|
+
self.misses = 0
|
|
44
|
+
|
|
45
|
+
def _compute_cache_key(self, request: LlmRequest) -> str:
|
|
46
|
+
"""Create cache key from request."""
|
|
47
|
+
messages_str = str([(m.role, m.content) for m in request.messages])
|
|
48
|
+
return f"{messages_str}:{request.temperature}"
|
|
49
|
+
|
|
50
|
+
async def before_llm_request(self, request: LlmRequest) -> LlmRequest:
|
|
51
|
+
"""Check cache before sending request."""
|
|
52
|
+
cache_key = self._compute_cache_key(request)
|
|
53
|
+
if cache_key in self.cache:
|
|
54
|
+
self.hits += 1
|
|
55
|
+
print(f"[CACHE HIT] Cache stats: {self.hits} hits, {self.misses} misses")
|
|
56
|
+
return request
|
|
57
|
+
|
|
58
|
+
async def after_llm_response(
|
|
59
|
+
self, request: LlmRequest, response: LlmResponse
|
|
60
|
+
) -> LlmResponse:
|
|
61
|
+
"""Cache the response."""
|
|
62
|
+
cache_key = self._compute_cache_key(request)
|
|
63
|
+
if cache_key not in self.cache:
|
|
64
|
+
self.cache[cache_key] = response
|
|
65
|
+
self.misses += 1
|
|
66
|
+
print(f"[CACHE MISS] Caching response")
|
|
67
|
+
return response
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# 2. ErrorRecoveryStrategy Example: Exponential Backoff
|
|
71
|
+
class ExponentialBackoffStrategy(ErrorRecoveryStrategy):
|
|
72
|
+
"""Retry failed operations with exponential backoff."""
|
|
73
|
+
|
|
74
|
+
def __init__(self, max_retries: int = 3) -> None:
|
|
75
|
+
self.max_retries = max_retries
|
|
76
|
+
|
|
77
|
+
async def handle_tool_error(
|
|
78
|
+
self, error: Exception, context: ToolContext, attempt: int = 1
|
|
79
|
+
) -> RecoveryAction:
|
|
80
|
+
"""Retry tool errors with exponential backoff."""
|
|
81
|
+
if attempt < self.max_retries:
|
|
82
|
+
delay_ms = (2 ** (attempt - 1)) * 1000
|
|
83
|
+
print(
|
|
84
|
+
f"[RETRY] Tool failed, retrying in {delay_ms}ms (attempt {attempt}/{self.max_retries})"
|
|
85
|
+
)
|
|
86
|
+
return RecoveryAction(
|
|
87
|
+
action=RecoveryActionType.RETRY,
|
|
88
|
+
retry_delay_ms=delay_ms,
|
|
89
|
+
message=f"Retrying after {delay_ms}ms",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
print(f"[FAIL] Max retries exceeded for tool error: {error}")
|
|
93
|
+
return RecoveryAction(
|
|
94
|
+
action=RecoveryActionType.FAIL,
|
|
95
|
+
message=f"Tool error after {self.max_retries} attempts: {str(error)}",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
async def handle_llm_error(
|
|
99
|
+
self, error: Exception, request: LlmRequest, attempt: int = 1
|
|
100
|
+
) -> RecoveryAction:
|
|
101
|
+
"""Retry LLM errors with backoff."""
|
|
102
|
+
if attempt < self.max_retries:
|
|
103
|
+
delay_ms = (2 ** (attempt - 1)) * 1000
|
|
104
|
+
print(
|
|
105
|
+
f"[RETRY] LLM failed, retrying in {delay_ms}ms (attempt {attempt}/{self.max_retries})"
|
|
106
|
+
)
|
|
107
|
+
return RecoveryAction(
|
|
108
|
+
action=RecoveryActionType.RETRY,
|
|
109
|
+
retry_delay_ms=delay_ms,
|
|
110
|
+
message=f"Retrying LLM after {delay_ms}ms",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
print(f"[FAIL] Max retries exceeded for LLM error: {error}")
|
|
114
|
+
return RecoveryAction(
|
|
115
|
+
action=RecoveryActionType.FAIL,
|
|
116
|
+
message=f"LLM error after {self.max_retries} attempts: {str(error)}",
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# 3. ToolContextEnricher Example: Add User Preferences
|
|
121
|
+
class UserPreferencesEnricher(ToolContextEnricher):
|
|
122
|
+
"""Enrich context with user preferences."""
|
|
123
|
+
|
|
124
|
+
def __init__(self) -> None:
|
|
125
|
+
# Mock user preferences database
|
|
126
|
+
self.preferences: Dict[str, Dict[str, Any]] = {
|
|
127
|
+
"user123": {
|
|
128
|
+
"timezone": "America/New_York",
|
|
129
|
+
"language": "en",
|
|
130
|
+
"theme": "dark",
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async def enrich_context(self, context: ToolContext) -> ToolContext:
|
|
135
|
+
"""Add user preferences to context."""
|
|
136
|
+
prefs = self.preferences.get(context.user.id, {})
|
|
137
|
+
context.metadata["user_preferences"] = prefs
|
|
138
|
+
context.metadata["timezone"] = prefs.get("timezone", "UTC")
|
|
139
|
+
print(f"[ENRICH] Added preferences for user {context.user.id}: {prefs}")
|
|
140
|
+
return context
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# 4. ConversationFilter Example: Context Window Management
|
|
144
|
+
class ContextWindowFilter(ConversationFilter):
|
|
145
|
+
"""Limit conversation to fit within context window."""
|
|
146
|
+
|
|
147
|
+
def __init__(self, max_messages: int = 20) -> None:
|
|
148
|
+
self.max_messages = max_messages
|
|
149
|
+
|
|
150
|
+
async def filter_messages(self, messages: List[Message]) -> List[Message]:
|
|
151
|
+
"""Keep only recent messages within limit."""
|
|
152
|
+
if len(messages) <= self.max_messages:
|
|
153
|
+
return messages
|
|
154
|
+
|
|
155
|
+
# Keep system messages and recent messages
|
|
156
|
+
system_messages = [m for m in messages if m.role == "system"]
|
|
157
|
+
other_messages = [m for m in messages if m.role != "system"]
|
|
158
|
+
|
|
159
|
+
# Take the most recent messages
|
|
160
|
+
recent_messages = other_messages[-self.max_messages :]
|
|
161
|
+
filtered = system_messages + recent_messages
|
|
162
|
+
|
|
163
|
+
print(f"[FILTER] Reduced {len(messages)} messages to {len(filtered)}")
|
|
164
|
+
return filtered
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# 5. ObservabilityProvider Example: Simple Logging
|
|
168
|
+
class LoggingObservabilityProvider(ObservabilityProvider):
|
|
169
|
+
"""Log metrics and spans for monitoring."""
|
|
170
|
+
|
|
171
|
+
def __init__(self) -> None:
|
|
172
|
+
self.metrics: List[Metric] = []
|
|
173
|
+
self.spans: List[Span] = []
|
|
174
|
+
|
|
175
|
+
async def record_metric(
|
|
176
|
+
self,
|
|
177
|
+
name: str,
|
|
178
|
+
value: float,
|
|
179
|
+
unit: str = "",
|
|
180
|
+
tags: Optional[Dict[str, str]] = None,
|
|
181
|
+
) -> None:
|
|
182
|
+
"""Record and log a metric."""
|
|
183
|
+
metric = Metric(name=name, value=value, unit=unit, tags=tags or {})
|
|
184
|
+
self.metrics.append(metric)
|
|
185
|
+
tags_str = ", ".join(f"{k}={v}" for k, v in (tags or {}).items())
|
|
186
|
+
print(f"[METRIC] {name}: {value}{unit} {tags_str}")
|
|
187
|
+
|
|
188
|
+
async def create_span(
|
|
189
|
+
self, name: str, attributes: Optional[Dict[str, Any]] = None
|
|
190
|
+
) -> Span:
|
|
191
|
+
"""Create a span for tracing."""
|
|
192
|
+
span = Span(name=name, attributes=attributes or {})
|
|
193
|
+
print(f"[SPAN START] {name}")
|
|
194
|
+
return span
|
|
195
|
+
|
|
196
|
+
async def end_span(self, span: Span) -> None:
|
|
197
|
+
"""End and record a span."""
|
|
198
|
+
span.end()
|
|
199
|
+
self.spans.append(span)
|
|
200
|
+
duration = span.duration_ms() or 0
|
|
201
|
+
print(f"[SPAN END] {span.name}: {duration:.2f}ms")
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
async def run_example() -> None:
|
|
205
|
+
"""
|
|
206
|
+
Example showing all extensibility interfaces working together.
|
|
207
|
+
"""
|
|
208
|
+
from vanna.integrations.anthropic import AnthropicLlmService
|
|
209
|
+
|
|
210
|
+
# Create all extensibility components
|
|
211
|
+
caching_middleware = CachingMiddleware()
|
|
212
|
+
retry_strategy = ExponentialBackoffStrategy(max_retries=3)
|
|
213
|
+
preferences_enricher = UserPreferencesEnricher()
|
|
214
|
+
context_filter = ContextWindowFilter(max_messages=20)
|
|
215
|
+
observability = LoggingObservabilityProvider()
|
|
216
|
+
|
|
217
|
+
# Mock conversation store
|
|
218
|
+
class MockStore:
|
|
219
|
+
async def get_conversation(self, cid: str, uid: str) -> Optional[Conversation]:
|
|
220
|
+
return None
|
|
221
|
+
|
|
222
|
+
async def create_conversation(
|
|
223
|
+
self, cid: str, uid: str, title: str
|
|
224
|
+
) -> Conversation:
|
|
225
|
+
return Conversation(
|
|
226
|
+
id=cid, user_id=uid, messages=[Message(role="user", content=title)]
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
async def update_conversation(self, conv: Conversation) -> None:
|
|
230
|
+
pass
|
|
231
|
+
|
|
232
|
+
async def delete_conversation(self, cid: str, uid: str) -> bool:
|
|
233
|
+
return False
|
|
234
|
+
|
|
235
|
+
async def list_conversations(
|
|
236
|
+
self, uid: str, limit: int = 50, offset: int = 0
|
|
237
|
+
) -> List[Conversation]:
|
|
238
|
+
return []
|
|
239
|
+
|
|
240
|
+
# Create agent with all extensibility components
|
|
241
|
+
agent = Agent(
|
|
242
|
+
llm_service=AnthropicLlmService(api_key="test-key"),
|
|
243
|
+
tool_registry=ToolRegistry(),
|
|
244
|
+
conversation_store=MockStore(), # type: ignore
|
|
245
|
+
llm_middlewares=[caching_middleware],
|
|
246
|
+
error_recovery_strategy=retry_strategy,
|
|
247
|
+
context_enrichers=[preferences_enricher],
|
|
248
|
+
conversation_filters=[context_filter],
|
|
249
|
+
observability_provider=observability,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
print("ā Agent created with all extensibility components:")
|
|
253
|
+
print(f" - LLM Middleware: {len(agent.llm_middlewares)} middlewares")
|
|
254
|
+
print(f" - Error Recovery: {type(agent.error_recovery_strategy).__name__}")
|
|
255
|
+
print(f" - Context Enrichers: {len(agent.context_enrichers)} enrichers")
|
|
256
|
+
print(f" - Conversation Filters: {len(agent.conversation_filters)} filters")
|
|
257
|
+
print(f" - Observability: {type(agent.observability_provider).__name__}")
|
|
258
|
+
print("\nš All extensibility interfaces integrated successfully!")
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
if __name__ == "__main__":
|
|
262
|
+
asyncio.run(run_example())
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Minimal Claude + SQLite example ready for FastAPI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from vanna import AgentConfig, Agent
|
|
9
|
+
from vanna.core.registry import ToolRegistry
|
|
10
|
+
from vanna.integrations.anthropic import AnthropicLlmService
|
|
11
|
+
from vanna.integrations.sqlite import SqliteRunner
|
|
12
|
+
from vanna.integrations.local import LocalFileSystem
|
|
13
|
+
from vanna.tools import (
|
|
14
|
+
RunSqlTool,
|
|
15
|
+
# Visualization
|
|
16
|
+
VisualizeDataTool,
|
|
17
|
+
# Python execution
|
|
18
|
+
RunPythonFileTool,
|
|
19
|
+
PipInstallTool,
|
|
20
|
+
# File system (for coding agents)
|
|
21
|
+
SearchFilesTool,
|
|
22
|
+
ListFilesTool,
|
|
23
|
+
ReadFileTool,
|
|
24
|
+
WriteFileTool,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
_DB = Path(__file__).resolve().parents[2] / "Chinook.sqlite"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def create_demo_agent() -> Agent:
|
|
31
|
+
# Load environment variables from .env file
|
|
32
|
+
from dotenv import load_dotenv
|
|
33
|
+
|
|
34
|
+
load_dotenv()
|
|
35
|
+
|
|
36
|
+
llm = AnthropicLlmService(model=os.getenv("ANTHROPIC_MODEL", "claude-sonnet-4-5"))
|
|
37
|
+
|
|
38
|
+
# Shared file system for all tools
|
|
39
|
+
file_system = LocalFileSystem("./claude_data")
|
|
40
|
+
|
|
41
|
+
tools = ToolRegistry()
|
|
42
|
+
|
|
43
|
+
# 1. Basic SQL agent - query databases
|
|
44
|
+
tools.register(
|
|
45
|
+
RunSqlTool(
|
|
46
|
+
sql_runner=SqliteRunner(database_path=str(_DB)),
|
|
47
|
+
file_system=file_system,
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# 2. Add visualization - create charts from data
|
|
52
|
+
tools.register(VisualizeDataTool(file_system=file_system))
|
|
53
|
+
|
|
54
|
+
# 3. Add Python execution - build dashboards with artifacts
|
|
55
|
+
# tools.register(RunPythonFileTool(file_system=file_system))
|
|
56
|
+
# tools.register(PipInstallTool(file_system=file_system))
|
|
57
|
+
|
|
58
|
+
# 4. Full coding agent - read, write, search files
|
|
59
|
+
# tools.register(SearchFilesTool(file_system=file_system))
|
|
60
|
+
# tools.register(ListFilesTool(file_system=file_system))
|
|
61
|
+
# tools.register(ReadFileTool(file_system=file_system))
|
|
62
|
+
# tools.register(WriteFileTool(file_system=file_system))
|
|
63
|
+
|
|
64
|
+
return Agent(
|
|
65
|
+
llm_service=llm,
|
|
66
|
+
tool_registry=tools,
|
|
67
|
+
)
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Mock authentication example to verify user resolution is working.
|
|
3
|
+
|
|
4
|
+
This example demonstrates the new UserResolver architecture where:
|
|
5
|
+
1. UserResolver is a required parameter of Agent
|
|
6
|
+
2. Agent.send_message() accepts RequestContext (not User directly)
|
|
7
|
+
3. The Agent resolves the user internally using the UserResolver
|
|
8
|
+
|
|
9
|
+
The agent uses an LLM middleware to inject user info into the response,
|
|
10
|
+
so we can verify the authentication is working correctly.
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
python -m vanna.examples.mock_auth_example
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import asyncio
|
|
19
|
+
|
|
20
|
+
from vanna import AgentConfig, Agent
|
|
21
|
+
from vanna.core.registry import ToolRegistry
|
|
22
|
+
from vanna.core.llm import LlmRequest, LlmResponse
|
|
23
|
+
from vanna.core.middleware import LlmMiddleware
|
|
24
|
+
from vanna.integrations.mock import MockLlmService
|
|
25
|
+
from vanna.core.user import CookieEmailUserResolver, RequestContext
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class UserEchoMiddleware(LlmMiddleware):
|
|
29
|
+
"""Middleware that injects user email into LLM responses."""
|
|
30
|
+
|
|
31
|
+
async def after_llm_response(
|
|
32
|
+
self, request: LlmRequest, response: LlmResponse
|
|
33
|
+
) -> LlmResponse:
|
|
34
|
+
"""Inject user email into response."""
|
|
35
|
+
# Extract user email from request user_id (which is set to user.id in the agent)
|
|
36
|
+
user_id = request.user_id
|
|
37
|
+
|
|
38
|
+
# Create a new response with user info
|
|
39
|
+
new_content = f"Hello! You are authenticated as: {user_id}"
|
|
40
|
+
|
|
41
|
+
return LlmResponse(
|
|
42
|
+
content=new_content,
|
|
43
|
+
finish_reason=response.finish_reason,
|
|
44
|
+
usage=response.usage,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def create_demo_agent() -> Agent:
|
|
49
|
+
"""Create a demo agent for server usage.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Configured Agent instance with cookie-based authentication
|
|
53
|
+
"""
|
|
54
|
+
# Create a mock LLM
|
|
55
|
+
llm_service = MockLlmService(response_content="Mock response")
|
|
56
|
+
|
|
57
|
+
# Empty tool registry
|
|
58
|
+
tool_registry = ToolRegistry()
|
|
59
|
+
|
|
60
|
+
# Cookie-based user resolver
|
|
61
|
+
user_resolver = CookieEmailUserResolver(cookie_name="vanna_email")
|
|
62
|
+
|
|
63
|
+
# User echo middleware
|
|
64
|
+
middleware = UserEchoMiddleware()
|
|
65
|
+
|
|
66
|
+
# Create agent with user resolver and middleware
|
|
67
|
+
agent = Agent(
|
|
68
|
+
llm_service=llm_service,
|
|
69
|
+
tool_registry=tool_registry,
|
|
70
|
+
user_resolver=user_resolver,
|
|
71
|
+
llm_middlewares=[middleware],
|
|
72
|
+
config=AgentConfig(
|
|
73
|
+
stream_responses=True,
|
|
74
|
+
include_thinking_indicators=False,
|
|
75
|
+
),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return agent
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
async def demo_authentication():
|
|
82
|
+
"""Demonstrate authentication with different request contexts."""
|
|
83
|
+
agent = create_demo_agent()
|
|
84
|
+
|
|
85
|
+
print("=== Mock Authentication Demo ===")
|
|
86
|
+
print("This example verifies that user resolution is working correctly.\n")
|
|
87
|
+
|
|
88
|
+
# Test 1: Request with email cookie
|
|
89
|
+
print("š¹ Test 1: Authenticated user (alice@example.com)")
|
|
90
|
+
request_context = RequestContext(
|
|
91
|
+
cookies={"vanna_email": "alice@example.com"},
|
|
92
|
+
headers={},
|
|
93
|
+
remote_addr="127.0.0.1",
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
print(
|
|
97
|
+
"Request context:",
|
|
98
|
+
{
|
|
99
|
+
"cookies": request_context.cookies,
|
|
100
|
+
"headers": request_context.headers,
|
|
101
|
+
"remote_addr": request_context.remote_addr,
|
|
102
|
+
},
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Send message - Agent will resolve user internally
|
|
106
|
+
agent_response = ""
|
|
107
|
+
async for component in agent.send_message(
|
|
108
|
+
request_context=request_context,
|
|
109
|
+
message="Who am I?",
|
|
110
|
+
conversation_id="test_conv_1",
|
|
111
|
+
):
|
|
112
|
+
# Extract and display user info from the resolved user
|
|
113
|
+
if hasattr(component, "rich_component"):
|
|
114
|
+
rich = component.rich_component
|
|
115
|
+
# Check if it's a text component
|
|
116
|
+
if rich.type.value == "text":
|
|
117
|
+
# Access content directly from the component (before serialization)
|
|
118
|
+
if hasattr(rich, "content"):
|
|
119
|
+
agent_response = rich.content
|
|
120
|
+
|
|
121
|
+
print(f"Agent response: {agent_response}")
|
|
122
|
+
|
|
123
|
+
# Verify user was resolved by checking the conversation store
|
|
124
|
+
user_resolver = agent.user_resolver
|
|
125
|
+
resolved_user = await user_resolver.resolve_user(request_context)
|
|
126
|
+
print(
|
|
127
|
+
f"ā
Resolved user: {resolved_user.email} (username: {resolved_user.username}, id: {resolved_user.id})"
|
|
128
|
+
)
|
|
129
|
+
print(f" Permissions: {resolved_user.permissions}")
|
|
130
|
+
print(f" Metadata: {resolved_user.metadata}")
|
|
131
|
+
|
|
132
|
+
print("\n" + "=" * 60 + "\n")
|
|
133
|
+
|
|
134
|
+
# Test 2: Request without email cookie (anonymous)
|
|
135
|
+
print("š¹ Test 2: Anonymous user (no cookie)")
|
|
136
|
+
anonymous_context = RequestContext(cookies={}, headers={}, remote_addr="127.0.0.1")
|
|
137
|
+
|
|
138
|
+
print(
|
|
139
|
+
"Request context:",
|
|
140
|
+
{
|
|
141
|
+
"cookies": anonymous_context.cookies,
|
|
142
|
+
"headers": anonymous_context.headers,
|
|
143
|
+
"remote_addr": anonymous_context.remote_addr,
|
|
144
|
+
},
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
agent_response = ""
|
|
148
|
+
async for component in agent.send_message(
|
|
149
|
+
request_context=anonymous_context,
|
|
150
|
+
message="Who am I?",
|
|
151
|
+
conversation_id="test_conv_2",
|
|
152
|
+
):
|
|
153
|
+
if hasattr(component, "rich_component"):
|
|
154
|
+
rich = component.rich_component
|
|
155
|
+
if rich.type.value == "text" and hasattr(rich, "content"):
|
|
156
|
+
agent_response = rich.content
|
|
157
|
+
|
|
158
|
+
print(f"Agent response: {agent_response}")
|
|
159
|
+
|
|
160
|
+
resolved_user = await user_resolver.resolve_user(anonymous_context)
|
|
161
|
+
print(
|
|
162
|
+
f"ā
Resolved user: {resolved_user.email or 'None'} (username: {resolved_user.username}, id: {resolved_user.id})"
|
|
163
|
+
)
|
|
164
|
+
print(f" Permissions: {resolved_user.permissions}")
|
|
165
|
+
print(f" Metadata: {resolved_user.metadata}")
|
|
166
|
+
|
|
167
|
+
print("\n" + "=" * 60 + "\n")
|
|
168
|
+
|
|
169
|
+
# Test 3: Different user
|
|
170
|
+
print("š¹ Test 3: Different authenticated user (bob@company.com)")
|
|
171
|
+
bob_context = RequestContext(
|
|
172
|
+
cookies={"vanna_email": "bob@company.com"},
|
|
173
|
+
headers={"User-Agent": "Mozilla/5.0"},
|
|
174
|
+
remote_addr="192.168.1.100",
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
print(
|
|
178
|
+
"Request context:",
|
|
179
|
+
{
|
|
180
|
+
"cookies": bob_context.cookies,
|
|
181
|
+
"headers": bob_context.headers,
|
|
182
|
+
"remote_addr": bob_context.remote_addr,
|
|
183
|
+
},
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
agent_response = ""
|
|
187
|
+
async for component in agent.send_message(
|
|
188
|
+
request_context=bob_context, message="Who am I?", conversation_id="test_conv_3"
|
|
189
|
+
):
|
|
190
|
+
if hasattr(component, "rich_component"):
|
|
191
|
+
rich = component.rich_component
|
|
192
|
+
if rich.type.value == "text" and hasattr(rich, "content"):
|
|
193
|
+
agent_response = rich.content
|
|
194
|
+
|
|
195
|
+
print(f"Agent response: {agent_response}")
|
|
196
|
+
|
|
197
|
+
resolved_user = await user_resolver.resolve_user(bob_context)
|
|
198
|
+
print(
|
|
199
|
+
f"ā
Resolved user: {resolved_user.email} (username: {resolved_user.username}, id: {resolved_user.id})"
|
|
200
|
+
)
|
|
201
|
+
print(f" Permissions: {resolved_user.permissions}")
|
|
202
|
+
print(f" Metadata: {resolved_user.metadata}")
|
|
203
|
+
|
|
204
|
+
print("\n" + "=" * 60)
|
|
205
|
+
print("\nā
Authentication demo complete!")
|
|
206
|
+
print("\nKey Features Verified:")
|
|
207
|
+
print("⢠UserResolver is part of Agent")
|
|
208
|
+
print("⢠Agent.send_message() accepts RequestContext")
|
|
209
|
+
print("⢠User resolution happens internally in Agent")
|
|
210
|
+
print("⢠CookieEmailUserResolver extracts email from vanna_email cookie")
|
|
211
|
+
print("⢠Anonymous users are created when no cookie is present")
|
|
212
|
+
print("⢠Different users can be resolved from different request contexts")
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
async def main():
|
|
216
|
+
"""Run the authentication example."""
|
|
217
|
+
await demo_authentication()
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def run_interactive():
|
|
221
|
+
"""Entry point for interactive usage."""
|
|
222
|
+
print("Starting mock authentication example...")
|
|
223
|
+
asyncio.run(main())
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
if __name__ == "__main__":
|
|
227
|
+
run_interactive()
|