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.
Files changed (302) hide show
  1. vanna/__init__.py +167 -395
  2. vanna/agents/__init__.py +7 -0
  3. vanna/capabilities/__init__.py +17 -0
  4. vanna/capabilities/agent_memory/__init__.py +21 -0
  5. vanna/capabilities/agent_memory/base.py +103 -0
  6. vanna/capabilities/agent_memory/models.py +53 -0
  7. vanna/capabilities/file_system/__init__.py +14 -0
  8. vanna/capabilities/file_system/base.py +71 -0
  9. vanna/capabilities/file_system/models.py +25 -0
  10. vanna/capabilities/sql_runner/__init__.py +13 -0
  11. vanna/capabilities/sql_runner/base.py +37 -0
  12. vanna/capabilities/sql_runner/models.py +13 -0
  13. vanna/components/__init__.py +92 -0
  14. vanna/components/base.py +11 -0
  15. vanna/components/rich/__init__.py +83 -0
  16. vanna/components/rich/containers/__init__.py +7 -0
  17. vanna/components/rich/containers/card.py +20 -0
  18. vanna/components/rich/data/__init__.py +9 -0
  19. vanna/components/rich/data/chart.py +17 -0
  20. vanna/components/rich/data/dataframe.py +93 -0
  21. vanna/components/rich/feedback/__init__.py +21 -0
  22. vanna/components/rich/feedback/badge.py +16 -0
  23. vanna/components/rich/feedback/icon_text.py +14 -0
  24. vanna/components/rich/feedback/log_viewer.py +41 -0
  25. vanna/components/rich/feedback/notification.py +19 -0
  26. vanna/components/rich/feedback/progress.py +37 -0
  27. vanna/components/rich/feedback/status_card.py +28 -0
  28. vanna/components/rich/feedback/status_indicator.py +14 -0
  29. vanna/components/rich/interactive/__init__.py +21 -0
  30. vanna/components/rich/interactive/button.py +95 -0
  31. vanna/components/rich/interactive/task_list.py +58 -0
  32. vanna/components/rich/interactive/ui_state.py +93 -0
  33. vanna/components/rich/specialized/__init__.py +7 -0
  34. vanna/components/rich/specialized/artifact.py +20 -0
  35. vanna/components/rich/text.py +16 -0
  36. vanna/components/simple/__init__.py +15 -0
  37. vanna/components/simple/image.py +15 -0
  38. vanna/components/simple/link.py +15 -0
  39. vanna/components/simple/text.py +11 -0
  40. vanna/core/__init__.py +193 -0
  41. vanna/core/_compat.py +19 -0
  42. vanna/core/agent/__init__.py +10 -0
  43. vanna/core/agent/agent.py +1407 -0
  44. vanna/core/agent/config.py +123 -0
  45. vanna/core/audit/__init__.py +28 -0
  46. vanna/core/audit/base.py +299 -0
  47. vanna/core/audit/models.py +131 -0
  48. vanna/core/component_manager.py +329 -0
  49. vanna/core/components.py +53 -0
  50. vanna/core/enhancer/__init__.py +11 -0
  51. vanna/core/enhancer/base.py +94 -0
  52. vanna/core/enhancer/default.py +118 -0
  53. vanna/core/enricher/__init__.py +10 -0
  54. vanna/core/enricher/base.py +59 -0
  55. vanna/core/errors.py +47 -0
  56. vanna/core/evaluation/__init__.py +81 -0
  57. vanna/core/evaluation/base.py +186 -0
  58. vanna/core/evaluation/dataset.py +254 -0
  59. vanna/core/evaluation/evaluators.py +376 -0
  60. vanna/core/evaluation/report.py +289 -0
  61. vanna/core/evaluation/runner.py +313 -0
  62. vanna/core/filter/__init__.py +10 -0
  63. vanna/core/filter/base.py +67 -0
  64. vanna/core/lifecycle/__init__.py +10 -0
  65. vanna/core/lifecycle/base.py +83 -0
  66. vanna/core/llm/__init__.py +16 -0
  67. vanna/core/llm/base.py +40 -0
  68. vanna/core/llm/models.py +61 -0
  69. vanna/core/middleware/__init__.py +10 -0
  70. vanna/core/middleware/base.py +69 -0
  71. vanna/core/observability/__init__.py +11 -0
  72. vanna/core/observability/base.py +88 -0
  73. vanna/core/observability/models.py +47 -0
  74. vanna/core/recovery/__init__.py +11 -0
  75. vanna/core/recovery/base.py +84 -0
  76. vanna/core/recovery/models.py +32 -0
  77. vanna/core/registry.py +278 -0
  78. vanna/core/rich_component.py +156 -0
  79. vanna/core/simple_component.py +27 -0
  80. vanna/core/storage/__init__.py +14 -0
  81. vanna/core/storage/base.py +46 -0
  82. vanna/core/storage/models.py +46 -0
  83. vanna/core/system_prompt/__init__.py +13 -0
  84. vanna/core/system_prompt/base.py +36 -0
  85. vanna/core/system_prompt/default.py +157 -0
  86. vanna/core/tool/__init__.py +18 -0
  87. vanna/core/tool/base.py +70 -0
  88. vanna/core/tool/models.py +84 -0
  89. vanna/core/user/__init__.py +17 -0
  90. vanna/core/user/base.py +29 -0
  91. vanna/core/user/models.py +25 -0
  92. vanna/core/user/request_context.py +70 -0
  93. vanna/core/user/resolver.py +42 -0
  94. vanna/core/validation.py +164 -0
  95. vanna/core/workflow/__init__.py +12 -0
  96. vanna/core/workflow/base.py +254 -0
  97. vanna/core/workflow/default.py +789 -0
  98. vanna/examples/__init__.py +1 -0
  99. vanna/examples/__main__.py +44 -0
  100. vanna/examples/anthropic_quickstart.py +80 -0
  101. vanna/examples/artifact_example.py +293 -0
  102. vanna/examples/claude_sqlite_example.py +236 -0
  103. vanna/examples/coding_agent_example.py +300 -0
  104. vanna/examples/custom_system_prompt_example.py +174 -0
  105. vanna/examples/default_workflow_handler_example.py +208 -0
  106. vanna/examples/email_auth_example.py +340 -0
  107. vanna/examples/evaluation_example.py +269 -0
  108. vanna/examples/extensibility_example.py +262 -0
  109. vanna/examples/minimal_example.py +67 -0
  110. vanna/examples/mock_auth_example.py +227 -0
  111. vanna/examples/mock_custom_tool.py +311 -0
  112. vanna/examples/mock_quickstart.py +79 -0
  113. vanna/examples/mock_quota_example.py +145 -0
  114. vanna/examples/mock_rich_components_demo.py +396 -0
  115. vanna/examples/mock_sqlite_example.py +223 -0
  116. vanna/examples/openai_quickstart.py +83 -0
  117. vanna/examples/primitive_components_demo.py +305 -0
  118. vanna/examples/quota_lifecycle_example.py +139 -0
  119. vanna/examples/visualization_example.py +251 -0
  120. vanna/integrations/__init__.py +17 -0
  121. vanna/integrations/anthropic/__init__.py +9 -0
  122. vanna/integrations/anthropic/llm.py +270 -0
  123. vanna/integrations/azureopenai/__init__.py +9 -0
  124. vanna/integrations/azureopenai/llm.py +329 -0
  125. vanna/integrations/azuresearch/__init__.py +7 -0
  126. vanna/integrations/azuresearch/agent_memory.py +413 -0
  127. vanna/integrations/bigquery/__init__.py +5 -0
  128. vanna/integrations/bigquery/sql_runner.py +81 -0
  129. vanna/integrations/chromadb/__init__.py +104 -0
  130. vanna/integrations/chromadb/agent_memory.py +416 -0
  131. vanna/integrations/clickhouse/__init__.py +5 -0
  132. vanna/integrations/clickhouse/sql_runner.py +82 -0
  133. vanna/integrations/duckdb/__init__.py +5 -0
  134. vanna/integrations/duckdb/sql_runner.py +65 -0
  135. vanna/integrations/faiss/__init__.py +7 -0
  136. vanna/integrations/faiss/agent_memory.py +431 -0
  137. vanna/integrations/google/__init__.py +9 -0
  138. vanna/integrations/google/gemini.py +370 -0
  139. vanna/integrations/hive/__init__.py +5 -0
  140. vanna/integrations/hive/sql_runner.py +87 -0
  141. vanna/integrations/local/__init__.py +17 -0
  142. vanna/integrations/local/agent_memory/__init__.py +7 -0
  143. vanna/integrations/local/agent_memory/in_memory.py +285 -0
  144. vanna/integrations/local/audit.py +59 -0
  145. vanna/integrations/local/file_system.py +242 -0
  146. vanna/integrations/local/file_system_conversation_store.py +255 -0
  147. vanna/integrations/local/storage.py +62 -0
  148. vanna/integrations/marqo/__init__.py +7 -0
  149. vanna/integrations/marqo/agent_memory.py +354 -0
  150. vanna/integrations/milvus/__init__.py +7 -0
  151. vanna/integrations/milvus/agent_memory.py +458 -0
  152. vanna/integrations/mock/__init__.py +9 -0
  153. vanna/integrations/mock/llm.py +65 -0
  154. vanna/integrations/mssql/__init__.py +5 -0
  155. vanna/integrations/mssql/sql_runner.py +66 -0
  156. vanna/integrations/mysql/__init__.py +5 -0
  157. vanna/integrations/mysql/sql_runner.py +92 -0
  158. vanna/integrations/ollama/__init__.py +7 -0
  159. vanna/integrations/ollama/llm.py +252 -0
  160. vanna/integrations/openai/__init__.py +10 -0
  161. vanna/integrations/openai/llm.py +267 -0
  162. vanna/integrations/openai/responses.py +163 -0
  163. vanna/integrations/opensearch/__init__.py +7 -0
  164. vanna/integrations/opensearch/agent_memory.py +411 -0
  165. vanna/integrations/oracle/__init__.py +5 -0
  166. vanna/integrations/oracle/sql_runner.py +75 -0
  167. vanna/integrations/pinecone/__init__.py +7 -0
  168. vanna/integrations/pinecone/agent_memory.py +329 -0
  169. vanna/integrations/plotly/__init__.py +5 -0
  170. vanna/integrations/plotly/chart_generator.py +313 -0
  171. vanna/integrations/postgres/__init__.py +9 -0
  172. vanna/integrations/postgres/sql_runner.py +112 -0
  173. vanna/integrations/premium/agent_memory/__init__.py +7 -0
  174. vanna/integrations/premium/agent_memory/premium.py +186 -0
  175. vanna/integrations/presto/__init__.py +5 -0
  176. vanna/integrations/presto/sql_runner.py +107 -0
  177. vanna/integrations/qdrant/__init__.py +7 -0
  178. vanna/integrations/qdrant/agent_memory.py +461 -0
  179. vanna/integrations/snowflake/__init__.py +5 -0
  180. vanna/integrations/snowflake/sql_runner.py +147 -0
  181. vanna/integrations/sqlite/__init__.py +9 -0
  182. vanna/integrations/sqlite/sql_runner.py +65 -0
  183. vanna/integrations/weaviate/__init__.py +7 -0
  184. vanna/integrations/weaviate/agent_memory.py +428 -0
  185. vanna/{ZhipuAI → legacy/ZhipuAI}/ZhipuAI_embeddings.py +11 -11
  186. vanna/legacy/__init__.py +403 -0
  187. vanna/legacy/adapter.py +463 -0
  188. vanna/{advanced → legacy/advanced}/__init__.py +3 -1
  189. vanna/{anthropic → legacy/anthropic}/anthropic_chat.py +9 -7
  190. vanna/{azuresearch → legacy/azuresearch}/azuresearch_vector.py +79 -41
  191. vanna/{base → legacy/base}/base.py +224 -217
  192. vanna/legacy/bedrock/__init__.py +1 -0
  193. vanna/{bedrock → legacy/bedrock}/bedrock_converse.py +13 -12
  194. vanna/{chromadb → legacy/chromadb}/chromadb_vector.py +3 -1
  195. vanna/legacy/cohere/__init__.py +2 -0
  196. vanna/{cohere → legacy/cohere}/cohere_chat.py +19 -14
  197. vanna/{cohere → legacy/cohere}/cohere_embeddings.py +25 -19
  198. vanna/{deepseek → legacy/deepseek}/deepseek_chat.py +5 -6
  199. vanna/legacy/faiss/__init__.py +1 -0
  200. vanna/{faiss → legacy/faiss}/faiss.py +113 -59
  201. vanna/{flask → legacy/flask}/__init__.py +84 -43
  202. vanna/{flask → legacy/flask}/assets.py +5 -5
  203. vanna/{flask → legacy/flask}/auth.py +5 -4
  204. vanna/{google → legacy/google}/bigquery_vector.py +75 -42
  205. vanna/{google → legacy/google}/gemini_chat.py +7 -3
  206. vanna/{hf → legacy/hf}/hf.py +0 -1
  207. vanna/{milvus → legacy/milvus}/milvus_vector.py +58 -35
  208. vanna/{mock → legacy/mock}/llm.py +0 -1
  209. vanna/legacy/mock/vectordb.py +67 -0
  210. vanna/legacy/ollama/ollama.py +110 -0
  211. vanna/{openai → legacy/openai}/openai_chat.py +2 -6
  212. vanna/legacy/opensearch/opensearch_vector.py +369 -0
  213. vanna/legacy/opensearch/opensearch_vector_semantic.py +200 -0
  214. vanna/legacy/oracle/oracle_vector.py +584 -0
  215. vanna/{pgvector → legacy/pgvector}/pgvector.py +42 -13
  216. vanna/{qdrant → legacy/qdrant}/qdrant.py +2 -6
  217. vanna/legacy/qianfan/Qianfan_Chat.py +170 -0
  218. vanna/legacy/qianfan/Qianfan_embeddings.py +36 -0
  219. vanna/legacy/qianwen/QianwenAI_chat.py +132 -0
  220. vanna/{remote.py → legacy/remote.py} +28 -26
  221. vanna/{utils.py → legacy/utils.py} +6 -11
  222. vanna/{vannadb → legacy/vannadb}/vannadb_vector.py +115 -46
  223. vanna/{vllm → legacy/vllm}/vllm.py +5 -6
  224. vanna/{weaviate → legacy/weaviate}/weaviate_vector.py +59 -40
  225. vanna/{xinference → legacy/xinference}/xinference.py +6 -6
  226. vanna/py.typed +0 -0
  227. vanna/servers/__init__.py +16 -0
  228. vanna/servers/__main__.py +8 -0
  229. vanna/servers/base/__init__.py +18 -0
  230. vanna/servers/base/chat_handler.py +65 -0
  231. vanna/servers/base/models.py +111 -0
  232. vanna/servers/base/rich_chat_handler.py +141 -0
  233. vanna/servers/base/templates.py +331 -0
  234. vanna/servers/cli/__init__.py +7 -0
  235. vanna/servers/cli/server_runner.py +204 -0
  236. vanna/servers/fastapi/__init__.py +7 -0
  237. vanna/servers/fastapi/app.py +163 -0
  238. vanna/servers/fastapi/routes.py +183 -0
  239. vanna/servers/flask/__init__.py +7 -0
  240. vanna/servers/flask/app.py +132 -0
  241. vanna/servers/flask/routes.py +137 -0
  242. vanna/tools/__init__.py +41 -0
  243. vanna/tools/agent_memory.py +322 -0
  244. vanna/tools/file_system.py +879 -0
  245. vanna/tools/python.py +222 -0
  246. vanna/tools/run_sql.py +165 -0
  247. vanna/tools/visualize_data.py +195 -0
  248. vanna/utils/__init__.py +0 -0
  249. vanna/web_components/__init__.py +44 -0
  250. vanna-2.0.0.dist-info/METADATA +485 -0
  251. vanna-2.0.0.dist-info/RECORD +289 -0
  252. vanna-2.0.0.dist-info/entry_points.txt +3 -0
  253. vanna/bedrock/__init__.py +0 -1
  254. vanna/cohere/__init__.py +0 -2
  255. vanna/faiss/__init__.py +0 -1
  256. vanna/mock/vectordb.py +0 -55
  257. vanna/ollama/ollama.py +0 -103
  258. vanna/opensearch/opensearch_vector.py +0 -392
  259. vanna/opensearch/opensearch_vector_semantic.py +0 -175
  260. vanna/oracle/oracle_vector.py +0 -585
  261. vanna/qianfan/Qianfan_Chat.py +0 -165
  262. vanna/qianfan/Qianfan_embeddings.py +0 -36
  263. vanna/qianwen/QianwenAI_chat.py +0 -133
  264. vanna-0.7.9.dist-info/METADATA +0 -408
  265. vanna-0.7.9.dist-info/RECORD +0 -79
  266. /vanna/{ZhipuAI → legacy/ZhipuAI}/ZhipuAI_Chat.py +0 -0
  267. /vanna/{ZhipuAI → legacy/ZhipuAI}/__init__.py +0 -0
  268. /vanna/{anthropic → legacy/anthropic}/__init__.py +0 -0
  269. /vanna/{azuresearch → legacy/azuresearch}/__init__.py +0 -0
  270. /vanna/{base → legacy/base}/__init__.py +0 -0
  271. /vanna/{chromadb → legacy/chromadb}/__init__.py +0 -0
  272. /vanna/{deepseek → legacy/deepseek}/__init__.py +0 -0
  273. /vanna/{exceptions → legacy/exceptions}/__init__.py +0 -0
  274. /vanna/{google → legacy/google}/__init__.py +0 -0
  275. /vanna/{hf → legacy/hf}/__init__.py +0 -0
  276. /vanna/{local.py → legacy/local.py} +0 -0
  277. /vanna/{marqo → legacy/marqo}/__init__.py +0 -0
  278. /vanna/{marqo → legacy/marqo}/marqo.py +0 -0
  279. /vanna/{milvus → legacy/milvus}/__init__.py +0 -0
  280. /vanna/{mistral → legacy/mistral}/__init__.py +0 -0
  281. /vanna/{mistral → legacy/mistral}/mistral.py +0 -0
  282. /vanna/{mock → legacy/mock}/__init__.py +0 -0
  283. /vanna/{mock → legacy/mock}/embedding.py +0 -0
  284. /vanna/{ollama → legacy/ollama}/__init__.py +0 -0
  285. /vanna/{openai → legacy/openai}/__init__.py +0 -0
  286. /vanna/{openai → legacy/openai}/openai_embeddings.py +0 -0
  287. /vanna/{opensearch → legacy/opensearch}/__init__.py +0 -0
  288. /vanna/{oracle → legacy/oracle}/__init__.py +0 -0
  289. /vanna/{pgvector → legacy/pgvector}/__init__.py +0 -0
  290. /vanna/{pinecone → legacy/pinecone}/__init__.py +0 -0
  291. /vanna/{pinecone → legacy/pinecone}/pinecone_vector.py +0 -0
  292. /vanna/{qdrant → legacy/qdrant}/__init__.py +0 -0
  293. /vanna/{qianfan → legacy/qianfan}/__init__.py +0 -0
  294. /vanna/{qianwen → legacy/qianwen}/QianwenAI_embeddings.py +0 -0
  295. /vanna/{qianwen → legacy/qianwen}/__init__.py +0 -0
  296. /vanna/{types → legacy/types}/__init__.py +0 -0
  297. /vanna/{vannadb → legacy/vannadb}/__init__.py +0 -0
  298. /vanna/{vllm → legacy/vllm}/__init__.py +0 -0
  299. /vanna/{weaviate → legacy/weaviate}/__init__.py +0 -0
  300. /vanna/{xinference → legacy/xinference}/__init__.py +0 -0
  301. {vanna-0.7.9.dist-info → vanna-2.0.0.dist-info}/WHEEL +0 -0
  302. {vanna-0.7.9.dist-info → vanna-2.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,88 @@
1
+ """
2
+ Base observability provider interface.
3
+
4
+ Observability providers allow you to collect telemetry data about
5
+ agent execution for monitoring and debugging.
6
+ """
7
+
8
+ from abc import ABC
9
+ from typing import Any, Dict, Optional
10
+
11
+ from .models import Span, Metric
12
+
13
+
14
+ class ObservabilityProvider(ABC):
15
+ """Provider for collecting telemetry and observability data.
16
+
17
+ Subclass this to create custom observability integrations that can:
18
+ - Emit metrics to monitoring systems
19
+ - Create distributed traces
20
+ - Log performance data
21
+ - Track costs and usage
22
+ - Monitor error rates
23
+
24
+ Example:
25
+ class PrometheusProvider(ObservabilityProvider):
26
+ def __init__(self, registry):
27
+ self.registry = registry
28
+ self.request_counter = Counter(
29
+ 'agent_requests_total',
30
+ 'Total agent requests',
31
+ registry=registry
32
+ )
33
+
34
+ async def record_metric(self, name: str, value: float, tags: Dict[str, str]) -> None:
35
+ if name == "agent.request":
36
+ self.request_counter.inc()
37
+
38
+ async def create_span(self, name: str, attributes: Optional[Dict[str, Any]] = None) -> Span:
39
+ span = Span(name=name, attributes=attributes or {})
40
+ return span
41
+
42
+ agent = AgentRunner(
43
+ llm_service=...,
44
+ observability_provider=PrometheusProvider(registry)
45
+ )
46
+ """
47
+
48
+ async def record_metric(
49
+ self,
50
+ name: str,
51
+ value: float,
52
+ unit: str = "",
53
+ tags: Optional[Dict[str, str]] = None,
54
+ ) -> None:
55
+ """Record a metric measurement.
56
+
57
+ Args:
58
+ name: Metric name (e.g., "agent.request.duration")
59
+ value: Metric value
60
+ unit: Unit of measurement (e.g., "ms", "tokens")
61
+ tags: Additional tags/labels for the metric
62
+ """
63
+ pass
64
+
65
+ async def create_span(
66
+ self, name: str, attributes: Optional[Dict[str, Any]] = None
67
+ ) -> Span:
68
+ """Create a new span for tracing.
69
+
70
+ Args:
71
+ name: Span name/operation
72
+ attributes: Initial span attributes
73
+
74
+ Returns:
75
+ Span object to track the operation
76
+
77
+ Note:
78
+ Call span.end() when the operation completes.
79
+ """
80
+ return Span(name=name, attributes=attributes or {})
81
+
82
+ async def end_span(self, span: Span) -> None:
83
+ """End a span and record it.
84
+
85
+ Args:
86
+ span: The span to end
87
+ """
88
+ span.end()
@@ -0,0 +1,47 @@
1
+ """
2
+ Observability models for spans and metrics.
3
+ """
4
+
5
+ import time
6
+ from typing import Any, Dict, Optional
7
+ from uuid import uuid4
8
+
9
+ from pydantic import BaseModel, Field
10
+
11
+
12
+ class Span(BaseModel):
13
+ """Represents a unit of work for distributed tracing."""
14
+
15
+ id: str = Field(default_factory=lambda: str(uuid4()), description="Span ID")
16
+ name: str = Field(description="Span name/operation")
17
+ start_time: float = Field(default_factory=time.time, description="Start timestamp")
18
+ end_time: Optional[float] = Field(default=None, description="End timestamp")
19
+ attributes: Dict[str, Any] = Field(
20
+ default_factory=dict, description="Span attributes"
21
+ )
22
+ parent_id: Optional[str] = Field(default=None, description="Parent span ID")
23
+
24
+ def end(self) -> None:
25
+ """Mark span as ended."""
26
+ if self.end_time is None:
27
+ self.end_time = time.time()
28
+
29
+ def duration_ms(self) -> Optional[float]:
30
+ """Get span duration in milliseconds."""
31
+ if self.end_time is None:
32
+ return None
33
+ return (self.end_time - self.start_time) * 1000
34
+
35
+ def set_attribute(self, key: str, value: Any) -> None:
36
+ """Set a span attribute."""
37
+ self.attributes[key] = value
38
+
39
+
40
+ class Metric(BaseModel):
41
+ """Represents a metric measurement."""
42
+
43
+ name: str = Field(description="Metric name")
44
+ value: float = Field(description="Metric value")
45
+ unit: str = Field(default="", description="Unit of measurement")
46
+ tags: Dict[str, str] = Field(default_factory=dict, description="Metric tags")
47
+ timestamp: float = Field(default_factory=time.time, description="Measurement time")
@@ -0,0 +1,11 @@
1
+ """
2
+ Error recovery system for handling failures gracefully.
3
+
4
+ This module provides interfaces for custom error handling, retry logic,
5
+ and fallback strategies.
6
+ """
7
+
8
+ from .base import ErrorRecoveryStrategy
9
+ from .models import RecoveryAction, RecoveryActionType
10
+
11
+ __all__ = ["ErrorRecoveryStrategy", "RecoveryAction", "RecoveryActionType"]
@@ -0,0 +1,84 @@
1
+ """
2
+ Base error recovery strategy interface.
3
+
4
+ Recovery strategies allow you to customize how the agent handles errors
5
+ during tool execution and LLM communication.
6
+ """
7
+
8
+ from abc import ABC
9
+ from typing import TYPE_CHECKING
10
+
11
+ from .models import RecoveryAction, RecoveryActionType
12
+
13
+ if TYPE_CHECKING:
14
+ from ..tool.models import ToolContext
15
+ from ..llm import LlmRequest
16
+
17
+
18
+ class ErrorRecoveryStrategy(ABC):
19
+ """Strategy for handling errors and implementing retry logic.
20
+
21
+ Subclass this to create custom error recovery strategies that can:
22
+ - Retry failed operations with backoff
23
+ - Fallback to alternative approaches
24
+ - Log errors to external systems
25
+ - Gracefully degrade functionality
26
+
27
+ Example:
28
+ class ExponentialBackoffStrategy(ErrorRecoveryStrategy):
29
+ async def handle_tool_error(
30
+ self, error: Exception, context: ToolContext, attempt: int
31
+ ) -> RecoveryAction:
32
+ if attempt < 3:
33
+ delay = (2 ** attempt) * 1000 # Exponential backoff
34
+ return RecoveryAction(
35
+ action=RecoveryActionType.RETRY,
36
+ retry_delay_ms=delay,
37
+ message=f"Retrying after {delay}ms"
38
+ )
39
+ return RecoveryAction(
40
+ action=RecoveryActionType.FAIL,
41
+ message="Max retries exceeded"
42
+ )
43
+
44
+ agent = AgentRunner(
45
+ llm_service=...,
46
+ error_recovery_strategy=ExponentialBackoffStrategy()
47
+ )
48
+ """
49
+
50
+ async def handle_tool_error(
51
+ self, error: Exception, context: "ToolContext", attempt: int = 1
52
+ ) -> RecoveryAction:
53
+ """Handle errors during tool execution.
54
+
55
+ Args:
56
+ error: The exception that occurred
57
+ context: Tool execution context
58
+ attempt: Current attempt number (1-indexed)
59
+
60
+ Returns:
61
+ RecoveryAction indicating how to proceed
62
+ """
63
+ # Default: fail immediately
64
+ return RecoveryAction(
65
+ action=RecoveryActionType.FAIL, message=f"Tool error: {str(error)}"
66
+ )
67
+
68
+ async def handle_llm_error(
69
+ self, error: Exception, request: "LlmRequest", attempt: int = 1
70
+ ) -> RecoveryAction:
71
+ """Handle errors during LLM communication.
72
+
73
+ Args:
74
+ error: The exception that occurred
75
+ request: The LLM request that failed
76
+ attempt: Current attempt number (1-indexed)
77
+
78
+ Returns:
79
+ RecoveryAction indicating how to proceed
80
+ """
81
+ # Default: fail immediately
82
+ return RecoveryAction(
83
+ action=RecoveryActionType.FAIL, message=f"LLM error: {str(error)}"
84
+ )
@@ -0,0 +1,32 @@
1
+ """
2
+ Recovery action models for error handling.
3
+ """
4
+
5
+ from enum import Enum
6
+ from typing import Any, Optional
7
+
8
+ from pydantic import BaseModel, Field
9
+
10
+
11
+ class RecoveryActionType(str, Enum):
12
+ """Types of recovery actions."""
13
+
14
+ RETRY = "retry"
15
+ FAIL = "fail"
16
+ FALLBACK = "fallback"
17
+ SKIP = "skip"
18
+
19
+
20
+ class RecoveryAction(BaseModel):
21
+ """Action to take when recovering from an error."""
22
+
23
+ action: RecoveryActionType = Field(description="Type of recovery action")
24
+ retry_delay_ms: Optional[int] = Field(
25
+ default=None, description="Delay before retry in milliseconds"
26
+ )
27
+ fallback_value: Optional[Any] = Field(
28
+ default=None, description="Fallback value to use"
29
+ )
30
+ message: Optional[str] = Field(
31
+ default=None, description="Message to include with action"
32
+ )
vanna/core/registry.py ADDED
@@ -0,0 +1,278 @@
1
+ """
2
+ Tool registry for the Vanna Agents framework.
3
+
4
+ This module provides the ToolRegistry class for managing and executing tools.
5
+ """
6
+
7
+ import time
8
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, TypeVar, Union
9
+
10
+ from .tool import Tool, ToolCall, ToolContext, ToolRejection, ToolResult, ToolSchema
11
+ from .user import User
12
+
13
+ if TYPE_CHECKING:
14
+ from .audit import AuditLogger
15
+ from .agent.config import AuditConfig
16
+
17
+ T = TypeVar("T")
18
+
19
+
20
+ class _LocalToolWrapper(Tool[T]):
21
+ """Wrapper for tools with configurable access groups."""
22
+
23
+ def __init__(self, wrapped_tool: Tool[T], access_groups: List[str]):
24
+ self._wrapped_tool = wrapped_tool
25
+ self._access_groups = access_groups
26
+
27
+ @property
28
+ def name(self) -> str:
29
+ return self._wrapped_tool.name
30
+
31
+ @property
32
+ def description(self) -> str:
33
+ return self._wrapped_tool.description
34
+
35
+ @property
36
+ def access_groups(self) -> List[str]:
37
+ return self._access_groups
38
+
39
+ def get_args_schema(self) -> Type[T]:
40
+ return self._wrapped_tool.get_args_schema()
41
+
42
+ async def execute(self, context: ToolContext, args: T) -> ToolResult:
43
+ return await self._wrapped_tool.execute(context, args)
44
+
45
+
46
+ class ToolRegistry:
47
+ """Registry for managing tools."""
48
+
49
+ def __init__(
50
+ self,
51
+ audit_logger: Optional["AuditLogger"] = None,
52
+ audit_config: Optional["AuditConfig"] = None,
53
+ ) -> None:
54
+ self._tools: Dict[str, Tool[Any]] = {}
55
+ self.audit_logger = audit_logger
56
+ if audit_config is not None:
57
+ self.audit_config = audit_config
58
+ else:
59
+ from .agent.config import AuditConfig
60
+
61
+ self.audit_config = AuditConfig()
62
+
63
+ def register_local_tool(self, tool: Tool[Any], access_groups: List[str]) -> None:
64
+ """Register a local tool with optional access group restrictions.
65
+
66
+ Args:
67
+ tool: The tool to register
68
+ access_groups: List of groups that can access this tool.
69
+ If None or empty, tool is accessible to all users.
70
+ """
71
+ if tool.name in self._tools:
72
+ raise ValueError(f"Tool '{tool.name}' already registered")
73
+
74
+ if access_groups:
75
+ # Wrap the tool with access groups
76
+ wrapped_tool = _LocalToolWrapper(tool, access_groups)
77
+ self._tools[tool.name] = wrapped_tool
78
+ else:
79
+ # No access restrictions, register as-is
80
+ self._tools[tool.name] = tool
81
+
82
+ async def get_tool(self, name: str) -> Optional[Tool[Any]]:
83
+ """Get a tool by name."""
84
+ return self._tools.get(name)
85
+
86
+ async def list_tools(self) -> List[str]:
87
+ """List all registered tool names."""
88
+ return list(self._tools.keys())
89
+
90
+ async def get_schemas(self, user: Optional[User] = None) -> List[ToolSchema]:
91
+ """Get schemas for all tools accessible to user."""
92
+ schemas = []
93
+ for tool in self._tools.values():
94
+ if user is None or await self._validate_tool_permissions(tool, user):
95
+ schemas.append(tool.get_schema())
96
+ return schemas
97
+
98
+ async def _validate_tool_permissions(self, tool: Tool[Any], user: User) -> bool:
99
+ """Validate if user has access to tool based on group membership.
100
+
101
+ Checks for intersection between user's group memberships and tool's access groups.
102
+ If tool has no access groups specified, it's accessible to all users.
103
+ """
104
+ tool_access_groups = tool.access_groups
105
+ if not tool_access_groups:
106
+ return True
107
+
108
+ user_groups = set(user.group_memberships)
109
+ tool_groups = set(tool_access_groups)
110
+ # Grant access if any group in user.group_memberships exists in tool.access_groups
111
+ return bool(user_groups & tool_groups)
112
+
113
+ async def transform_args(
114
+ self,
115
+ tool: Tool[T],
116
+ args: T,
117
+ user: User,
118
+ context: ToolContext,
119
+ ) -> Union[T, ToolRejection]:
120
+ """Transform and validate tool arguments based on user context.
121
+
122
+ This method allows per-user transformation of tool arguments, such as:
123
+ - Applying row-level security (RLS) to SQL queries
124
+ - Filtering available options based on user permissions
125
+ - Validating required arguments are present
126
+ - Redacting sensitive fields
127
+
128
+ The default implementation performs no transformation (NoOp).
129
+ Subclasses can override this method to implement custom transformation logic.
130
+
131
+ Args:
132
+ tool: The tool being executed
133
+ args: Already Pydantic-validated arguments
134
+ user: The user executing the tool
135
+ context: Full execution context
136
+
137
+ Returns:
138
+ Either:
139
+ - Transformed arguments (may be unchanged if no transformation needed)
140
+ - ToolRejection with explanation of why args were rejected
141
+ """
142
+ return args # Default: no transformation (NoOp)
143
+
144
+ async def execute(
145
+ self,
146
+ tool_call: ToolCall,
147
+ context: ToolContext,
148
+ ) -> ToolResult:
149
+ """Execute a tool call with validation."""
150
+ tool = await self.get_tool(tool_call.name)
151
+ if not tool:
152
+ msg = f"Tool '{tool_call.name}' not found"
153
+ return ToolResult(
154
+ success=False,
155
+ result_for_llm=msg,
156
+ ui_component=None,
157
+ error=msg,
158
+ )
159
+
160
+ # Validate group access
161
+ if not await self._validate_tool_permissions(tool, context.user):
162
+ msg = f"Insufficient group access for tool '{tool_call.name}'"
163
+
164
+ # Audit access denial
165
+ if (
166
+ self.audit_logger
167
+ and self.audit_config
168
+ and self.audit_config.log_tool_access_checks
169
+ ):
170
+ await self.audit_logger.log_tool_access_check(
171
+ user=context.user,
172
+ tool_name=tool_call.name,
173
+ access_granted=False,
174
+ required_groups=tool.access_groups,
175
+ context=context,
176
+ reason=msg,
177
+ )
178
+
179
+ return ToolResult(
180
+ success=False,
181
+ result_for_llm=msg,
182
+ ui_component=None,
183
+ error=msg,
184
+ )
185
+
186
+ # Validate and parse arguments
187
+ try:
188
+ args_model = tool.get_args_schema()
189
+ validated_args = args_model.model_validate(tool_call.arguments)
190
+ except Exception as e:
191
+ msg = f"Invalid arguments: {str(e)}"
192
+ return ToolResult(
193
+ success=False,
194
+ result_for_llm=msg,
195
+ ui_component=None,
196
+ error=msg,
197
+ )
198
+
199
+ # Transform/validate arguments based on user context
200
+ transform_result = await self.transform_args(
201
+ tool=tool,
202
+ args=validated_args,
203
+ user=context.user,
204
+ context=context,
205
+ )
206
+
207
+ if isinstance(transform_result, ToolRejection):
208
+ return ToolResult(
209
+ success=False,
210
+ result_for_llm=transform_result.reason,
211
+ ui_component=None,
212
+ error=transform_result.reason,
213
+ )
214
+
215
+ # Use transformed arguments for execution
216
+ final_args = transform_result
217
+
218
+ # Audit successful access check
219
+ if (
220
+ self.audit_logger
221
+ and self.audit_config
222
+ and self.audit_config.log_tool_access_checks
223
+ ):
224
+ await self.audit_logger.log_tool_access_check(
225
+ user=context.user,
226
+ tool_name=tool_call.name,
227
+ access_granted=True,
228
+ required_groups=tool.access_groups,
229
+ context=context,
230
+ )
231
+
232
+ # Audit tool invocation
233
+ if (
234
+ self.audit_logger
235
+ and self.audit_config
236
+ and self.audit_config.log_tool_invocations
237
+ ):
238
+ # Get UI features if available from context
239
+ ui_features = context.metadata.get("ui_features_available", [])
240
+ await self.audit_logger.log_tool_invocation(
241
+ user=context.user,
242
+ tool_call=tool_call,
243
+ ui_features=ui_features,
244
+ context=context,
245
+ sanitize_parameters=self.audit_config.sanitize_tool_parameters,
246
+ )
247
+
248
+ # Execute tool with context-first signature
249
+ try:
250
+ start_time = time.perf_counter()
251
+ result = await tool.execute(context, final_args)
252
+ execution_time_ms = (time.perf_counter() - start_time) * 1000
253
+
254
+ # Add execution time to metadata
255
+ result.metadata["execution_time_ms"] = execution_time_ms
256
+
257
+ # Audit tool result
258
+ if (
259
+ self.audit_logger
260
+ and self.audit_config
261
+ and self.audit_config.log_tool_results
262
+ ):
263
+ await self.audit_logger.log_tool_result(
264
+ user=context.user,
265
+ tool_call=tool_call,
266
+ result=result,
267
+ context=context,
268
+ )
269
+
270
+ return result
271
+ except Exception as e:
272
+ msg = f"Execution failed: {str(e)}"
273
+ return ToolResult(
274
+ success=False,
275
+ result_for_llm=msg,
276
+ ui_component=None,
277
+ error=msg,
278
+ )
@@ -0,0 +1,156 @@
1
+ """
2
+ Base classes for rich UI components.
3
+
4
+ This module provides the base RichComponent class and supporting enums
5
+ for the component system.
6
+ """
7
+
8
+ import uuid
9
+ from datetime import datetime
10
+ from enum import Enum
11
+ from typing import Any, Dict, List, TypeVar
12
+
13
+ from pydantic import BaseModel, Field
14
+
15
+ # Type variable for self-returning methods
16
+ T = TypeVar("T", bound="RichComponent")
17
+
18
+
19
+ class ComponentType(str, Enum):
20
+ """Types of rich UI components."""
21
+
22
+ # Basic components
23
+ TEXT = "text"
24
+ CARD = "card"
25
+ CONTAINER = "container"
26
+
27
+ # Primitive UI components (domain-agnostic)
28
+ STATUS_CARD = "status_card"
29
+ PROGRESS_DISPLAY = "progress_display"
30
+ LOG_VIEWER = "log_viewer"
31
+ BADGE = "badge"
32
+ ICON_TEXT = "icon_text"
33
+
34
+ # Interactive components
35
+ TASK_LIST = "task_list"
36
+ PROGRESS_BAR = "progress_bar"
37
+ BUTTON = "button"
38
+ BUTTON_GROUP = "button_group"
39
+
40
+ # Data components
41
+ TABLE = "table"
42
+ DATAFRAME = "dataframe"
43
+ CHART = "chart"
44
+ CODE_BLOCK = "code_block"
45
+
46
+ # Status components
47
+ STATUS_INDICATOR = "status_indicator"
48
+ NOTIFICATION = "notification"
49
+ ALERT = "alert"
50
+
51
+ # Artifact components
52
+ ARTIFACT = "artifact"
53
+
54
+ # UI state components
55
+ STATUS_BAR_UPDATE = "status_bar_update"
56
+ TASK_TRACKER_UPDATE = "task_tracker_update"
57
+ CHAT_INPUT_UPDATE = "chat_input_update"
58
+
59
+ # Legacy (deprecated - use primitives instead)
60
+ TOOL_EXECUTION = "tool_execution"
61
+
62
+
63
+ class ComponentLifecycle(str, Enum):
64
+ """Component lifecycle operations."""
65
+
66
+ CREATE = "create"
67
+ UPDATE = "update"
68
+ REPLACE = "replace"
69
+ REMOVE = "remove"
70
+
71
+
72
+ class RichComponent(BaseModel):
73
+ """Base class for all rich UI components."""
74
+
75
+ id: str = Field(default_factory=lambda: str(uuid.uuid4()))
76
+ type: ComponentType
77
+ lifecycle: ComponentLifecycle = ComponentLifecycle.CREATE
78
+ data: Dict[str, Any] = Field(default_factory=dict)
79
+ children: List[str] = Field(default_factory=list) # Child component IDs
80
+ timestamp: str = Field(default_factory=lambda: datetime.utcnow().isoformat())
81
+ visible: bool = True
82
+ interactive: bool = False
83
+
84
+ def update(self: T, **kwargs: Any) -> T:
85
+ """Create an updated copy of this component."""
86
+ updated_data = self.model_dump()
87
+ updated_data.update(kwargs)
88
+ updated_data["lifecycle"] = ComponentLifecycle.UPDATE
89
+ updated_data["timestamp"] = datetime.utcnow().isoformat()
90
+ return self.__class__(**updated_data)
91
+
92
+ def hide(self: T) -> T:
93
+ """Create a hidden copy of this component."""
94
+ return self.update(visible=False)
95
+
96
+ def show(self: T) -> T:
97
+ """Create a visible copy of this component."""
98
+ return self.update(visible=True)
99
+
100
+ def serialize_for_frontend(self) -> Dict[str, Any]:
101
+ """Normalize component payload for the frontend renderer.
102
+
103
+ The frontend expects component-specific fields to live under the
104
+ ``data`` key while the shared metadata (``id``, ``type``, layout hints,
105
+ etc.) remains at the top level. Pydantic's ``model_dump`` keeps
106
+ component attributes at the top level, so we remap them here before
107
+ streaming them across the wire.
108
+ """
109
+
110
+ # Base fields that should remain at the top level of the payload.
111
+ shared_fields = {
112
+ "id",
113
+ "type",
114
+ "lifecycle",
115
+ "children",
116
+ "timestamp",
117
+ "visible",
118
+ "interactive",
119
+ }
120
+
121
+ raw = self.model_dump()
122
+ payload: Dict[str, Any] = {}
123
+
124
+ # Preserve any existing data payload so implementations can opt-in to
125
+ # advanced usage without losing information.
126
+ raw_data = raw.get("data")
127
+ if raw_data is not None and isinstance(raw_data, dict):
128
+ component_data: Dict[str, Any] = raw_data.copy()
129
+ else:
130
+ # Handle case where data might be a sequence or other type, or None
131
+ component_data = {}
132
+
133
+ for key, value in raw.items():
134
+ if key in shared_fields:
135
+ payload[key] = value
136
+ elif key == "data":
137
+ # For most components, skip the base data field
138
+ continue
139
+ elif (
140
+ key == "rows"
141
+ and hasattr(self, "type")
142
+ and self.type.value == "dataframe"
143
+ ):
144
+ # For DataFrame components, the 'rows' field contains the actual row data
145
+ # which should be included in the component_data as 'data' for the frontend
146
+ component_data["data"] = value
147
+ else:
148
+ component_data[key] = value
149
+
150
+ payload["data"] = component_data
151
+
152
+ # Ensure enums are serialized as primitive values for the frontend.
153
+ payload["type"] = self.type.value
154
+ payload["lifecycle"] = self.lifecycle.value
155
+
156
+ return payload