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,123 @@
1
+ """
2
+ Agent configuration.
3
+
4
+ This module contains configuration models that control agent behavior.
5
+ """
6
+
7
+ from typing import TYPE_CHECKING, Dict, List, Optional
8
+
9
+ from pydantic import BaseModel, Field
10
+
11
+ from .._compat import StrEnum
12
+
13
+ if TYPE_CHECKING:
14
+ from ..user import User
15
+
16
+
17
+ class UiFeature(StrEnum):
18
+ UI_FEATURE_SHOW_TOOL_NAMES = "tool_names"
19
+ UI_FEATURE_SHOW_TOOL_ARGUMENTS = "tool_arguments"
20
+ UI_FEATURE_SHOW_TOOL_ERROR = "tool_error"
21
+ UI_FEATURE_SHOW_TOOL_INVOCATION_MESSAGE_IN_CHAT = "tool_invocation_message_in_chat"
22
+ UI_FEATURE_SHOW_MEMORY_DETAILED_RESULTS = "memory_detailed_results"
23
+
24
+
25
+ # Optional: you can also define defaults if you want a shared baseline
26
+ DEFAULT_UI_FEATURES: Dict[str, List[str]] = {
27
+ UiFeature.UI_FEATURE_SHOW_TOOL_NAMES: ["admin", "user"],
28
+ UiFeature.UI_FEATURE_SHOW_TOOL_ARGUMENTS: ["admin"],
29
+ UiFeature.UI_FEATURE_SHOW_TOOL_ERROR: ["admin"],
30
+ UiFeature.UI_FEATURE_SHOW_TOOL_INVOCATION_MESSAGE_IN_CHAT: ["admin"],
31
+ UiFeature.UI_FEATURE_SHOW_MEMORY_DETAILED_RESULTS: ["admin"],
32
+ }
33
+
34
+
35
+ class UiFeatures(BaseModel):
36
+ """UI features with group-based access control using the same pattern as tools.
37
+
38
+ Each field specifies which groups can access that UI feature.
39
+ Empty list means the feature is accessible to all users.
40
+ Uses the same intersection logic as tool access control.
41
+ """
42
+
43
+ # Custom features for extensibility
44
+ feature_group_access: Dict[str, List[str]] = Field(
45
+ default_factory=lambda: DEFAULT_UI_FEATURES.copy(),
46
+ description="Which groups can access UI features",
47
+ )
48
+
49
+ def can_user_access_feature(self, feature_name: str, user: "User") -> bool:
50
+ """Check if user can access a UI feature using same logic as tools.
51
+
52
+ Args:
53
+ feature_name: Name of the UI feature to check
54
+ user: User object with group_memberships
55
+
56
+ Returns:
57
+ True if user has access, False otherwise
58
+ """
59
+ # Then try custom features
60
+ if feature_name in self.feature_group_access:
61
+ allowed_groups = self.feature_group_access[feature_name]
62
+ else:
63
+ # Feature doesn't exist, deny access
64
+ return False
65
+
66
+ # Empty list means all users can access (same as tools)
67
+ if not allowed_groups:
68
+ return True
69
+
70
+ # Same intersection logic as tool access control
71
+ user_groups = set(user.group_memberships)
72
+ feature_groups = set(allowed_groups)
73
+ return bool(user_groups & feature_groups)
74
+
75
+ def register_feature(self, name: str, access_groups: List[str]) -> None:
76
+ """Register a custom UI feature with group access control.
77
+
78
+ Args:
79
+ name: Name of the custom feature
80
+ access_groups: List of groups that can access this feature
81
+ """
82
+ self.feature_group_access[name] = access_groups
83
+
84
+
85
+ class AuditConfig(BaseModel):
86
+ """Configuration for audit logging."""
87
+
88
+ enabled: bool = Field(default=True, description="Enable audit logging")
89
+ log_tool_access_checks: bool = Field(
90
+ default=True, description="Log tool access permission checks"
91
+ )
92
+ log_tool_invocations: bool = Field(
93
+ default=True, description="Log tool invocations with parameters"
94
+ )
95
+ log_tool_results: bool = Field(
96
+ default=True, description="Log tool execution results"
97
+ )
98
+ log_ui_feature_checks: bool = Field(
99
+ default=False, description="Log UI feature access checks (can be noisy)"
100
+ )
101
+ log_ai_responses: bool = Field(
102
+ default=True, description="Log AI-generated responses"
103
+ )
104
+ include_full_ai_responses: bool = Field(
105
+ default=False,
106
+ description="Include full AI response text in logs (privacy concern)",
107
+ )
108
+ sanitize_tool_parameters: bool = Field(
109
+ default=True, description="Sanitize sensitive parameters (passwords, tokens)"
110
+ )
111
+
112
+
113
+ class AgentConfig(BaseModel):
114
+ """Configuration for agent behavior."""
115
+
116
+ max_tool_iterations: int = Field(default=10, gt=0)
117
+ stream_responses: bool = Field(default=True)
118
+ auto_save_conversations: bool = Field(default=True)
119
+ include_thinking_indicators: bool = Field(default=True)
120
+ temperature: float = Field(default=0.7, ge=0.0, le=2.0)
121
+ max_tokens: Optional[int] = Field(default=None, gt=0)
122
+ ui_features: UiFeatures = Field(default_factory=UiFeatures)
123
+ audit_config: AuditConfig = Field(default_factory=AuditConfig)
@@ -0,0 +1,28 @@
1
+ """
2
+ Audit logging for the Vanna Agents framework.
3
+
4
+ This module provides interfaces and models for audit logging, enabling
5
+ tracking of user actions, tool invocations, and access control decisions.
6
+ """
7
+
8
+ from .base import AuditLogger
9
+ from .models import (
10
+ AiResponseEvent,
11
+ AuditEvent,
12
+ AuditEventType,
13
+ ToolAccessCheckEvent,
14
+ ToolInvocationEvent,
15
+ ToolResultEvent,
16
+ UiFeatureAccessCheckEvent,
17
+ )
18
+
19
+ __all__ = [
20
+ "AuditLogger",
21
+ "AuditEvent",
22
+ "AuditEventType",
23
+ "ToolAccessCheckEvent",
24
+ "ToolInvocationEvent",
25
+ "ToolResultEvent",
26
+ "UiFeatureAccessCheckEvent",
27
+ "AiResponseEvent",
28
+ ]
@@ -0,0 +1,299 @@
1
+ """
2
+ Base audit logger interface.
3
+
4
+ Audit loggers enable tracking user actions, tool invocations, and access control
5
+ decisions for security, compliance, and debugging.
6
+ """
7
+
8
+ import hashlib
9
+ from abc import ABC, abstractmethod
10
+ from datetime import datetime
11
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
12
+
13
+ from .models import (
14
+ AiResponseEvent,
15
+ AuditEvent,
16
+ ToolAccessCheckEvent,
17
+ ToolInvocationEvent,
18
+ ToolResultEvent,
19
+ UiFeatureAccessCheckEvent,
20
+ )
21
+
22
+ if TYPE_CHECKING:
23
+ from ..tool.models import ToolCall, ToolContext, ToolResult
24
+ from ..user.models import User
25
+
26
+
27
+ class AuditLogger(ABC):
28
+ """Abstract base class for audit logging implementations.
29
+
30
+ Implementations can:
31
+ - Write to files (JSON, CSV, etc.)
32
+ - Send to databases (Postgres, MongoDB, etc.)
33
+ - Stream to cloud services (CloudWatch, Datadog, etc.)
34
+ - Send to SIEM systems (Splunk, Elastic, etc.)
35
+
36
+ Example:
37
+ class PostgresAuditLogger(AuditLogger):
38
+ async def log_event(self, event: AuditEvent) -> None:
39
+ await self.db.execute(
40
+ "INSERT INTO audit_log (...) VALUES (...)",
41
+ event.model_dump()
42
+ )
43
+
44
+ agent = Agent(
45
+ llm_service=...,
46
+ audit_logger=PostgresAuditLogger(db_pool)
47
+ )
48
+ """
49
+
50
+ @abstractmethod
51
+ async def log_event(self, event: AuditEvent) -> None:
52
+ """Log a single audit event.
53
+
54
+ Args:
55
+ event: The audit event to log
56
+
57
+ Raises:
58
+ Exception: If logging fails critically
59
+ """
60
+ pass
61
+
62
+ async def log_tool_access_check(
63
+ self,
64
+ user: "User",
65
+ tool_name: str,
66
+ access_granted: bool,
67
+ required_groups: List[str],
68
+ context: "ToolContext",
69
+ reason: Optional[str] = None,
70
+ ) -> None:
71
+ """Convenience method for logging tool access checks.
72
+
73
+ Args:
74
+ user: User attempting to access the tool
75
+ tool_name: Name of the tool being accessed
76
+ access_granted: Whether access was granted
77
+ required_groups: Groups required to access the tool
78
+ context: Tool execution context
79
+ reason: Optional reason for denial
80
+ """
81
+ event = ToolAccessCheckEvent(
82
+ user_id=user.id,
83
+ username=user.username,
84
+ user_email=user.email,
85
+ user_groups=user.group_memberships,
86
+ conversation_id=context.conversation_id,
87
+ request_id=context.request_id,
88
+ tool_name=tool_name,
89
+ access_granted=access_granted,
90
+ required_groups=required_groups,
91
+ reason=reason,
92
+ )
93
+ await self.log_event(event)
94
+
95
+ async def log_tool_invocation(
96
+ self,
97
+ user: "User",
98
+ tool_call: "ToolCall",
99
+ ui_features: List[str],
100
+ context: "ToolContext",
101
+ sanitize_parameters: bool = True,
102
+ ) -> None:
103
+ """Convenience method for logging tool invocations.
104
+
105
+ Args:
106
+ user: User invoking the tool
107
+ tool_call: Tool call information
108
+ ui_features: List of UI features available to the user
109
+ context: Tool execution context
110
+ sanitize_parameters: Whether to sanitize sensitive parameters
111
+ """
112
+ parameters = tool_call.arguments.copy()
113
+ sanitized = False
114
+
115
+ if sanitize_parameters:
116
+ parameters, sanitized = self._sanitize_parameters(parameters)
117
+
118
+ event = ToolInvocationEvent(
119
+ user_id=user.id,
120
+ username=user.username,
121
+ user_email=user.email,
122
+ user_groups=user.group_memberships,
123
+ conversation_id=context.conversation_id,
124
+ request_id=context.request_id,
125
+ tool_call_id=tool_call.id,
126
+ tool_name=tool_call.name,
127
+ parameters=parameters,
128
+ parameters_sanitized=sanitized,
129
+ ui_features_available=ui_features,
130
+ )
131
+ await self.log_event(event)
132
+
133
+ async def log_tool_result(
134
+ self,
135
+ user: "User",
136
+ tool_call: "ToolCall",
137
+ result: "ToolResult",
138
+ context: "ToolContext",
139
+ ) -> None:
140
+ """Convenience method for logging tool results.
141
+
142
+ Args:
143
+ user: User who invoked the tool
144
+ tool_call: Tool call information
145
+ result: Tool execution result
146
+ context: Tool execution context
147
+ """
148
+ event = ToolResultEvent(
149
+ user_id=user.id,
150
+ username=user.username,
151
+ user_email=user.email,
152
+ user_groups=user.group_memberships,
153
+ conversation_id=context.conversation_id,
154
+ request_id=context.request_id,
155
+ tool_call_id=tool_call.id,
156
+ tool_name=tool_call.name,
157
+ success=result.success,
158
+ error=result.error,
159
+ execution_time_ms=result.metadata.get("execution_time_ms", 0.0),
160
+ result_size_bytes=(
161
+ len(result.result_for_llm.encode("utf-8"))
162
+ if result.result_for_llm
163
+ else 0
164
+ ),
165
+ ui_component_type=(
166
+ result.ui_component.__class__.__name__ if result.ui_component else None
167
+ ),
168
+ )
169
+ await self.log_event(event)
170
+
171
+ async def log_ui_feature_access(
172
+ self,
173
+ user: "User",
174
+ feature_name: str,
175
+ access_granted: bool,
176
+ required_groups: List[str],
177
+ conversation_id: str,
178
+ request_id: str,
179
+ ) -> None:
180
+ """Convenience method for logging UI feature access checks.
181
+
182
+ Args:
183
+ user: User attempting to access the feature
184
+ feature_name: Name of the UI feature
185
+ access_granted: Whether access was granted
186
+ required_groups: Groups required to access the feature
187
+ conversation_id: Conversation identifier
188
+ request_id: Request identifier
189
+ """
190
+ event = UiFeatureAccessCheckEvent(
191
+ user_id=user.id,
192
+ username=user.username,
193
+ user_email=user.email,
194
+ user_groups=user.group_memberships,
195
+ conversation_id=conversation_id,
196
+ request_id=request_id,
197
+ feature_name=feature_name,
198
+ access_granted=access_granted,
199
+ required_groups=required_groups,
200
+ )
201
+ await self.log_event(event)
202
+
203
+ async def log_ai_response(
204
+ self,
205
+ user: "User",
206
+ conversation_id: str,
207
+ request_id: str,
208
+ response_text: str,
209
+ tool_calls: List["ToolCall"],
210
+ model_info: Optional[Dict[str, Any]] = None,
211
+ include_full_text: bool = False,
212
+ ) -> None:
213
+ """Convenience method for logging AI responses.
214
+
215
+ Args:
216
+ user: User receiving the response
217
+ conversation_id: Conversation identifier
218
+ request_id: Request identifier
219
+ response_text: The AI-generated response text
220
+ tool_calls: List of tool calls in the response
221
+ model_info: Optional model configuration info
222
+ include_full_text: Whether to include full response text
223
+ """
224
+ response_hash = hashlib.sha256(response_text.encode("utf-8")).hexdigest()
225
+
226
+ event = AiResponseEvent(
227
+ user_id=user.id,
228
+ username=user.username,
229
+ user_email=user.email,
230
+ user_groups=user.group_memberships,
231
+ conversation_id=conversation_id,
232
+ request_id=request_id,
233
+ response_length_chars=len(response_text),
234
+ response_text=response_text if include_full_text else None,
235
+ response_hash=response_hash,
236
+ model_name=model_info.get("model") if model_info else None,
237
+ temperature=model_info.get("temperature") if model_info else None,
238
+ tool_calls_count=len(tool_calls),
239
+ tool_names=[tc.name for tc in tool_calls],
240
+ )
241
+ await self.log_event(event)
242
+
243
+ async def query_events(
244
+ self,
245
+ filters: Optional[Dict[str, Any]] = None,
246
+ start_time: Optional[datetime] = None,
247
+ end_time: Optional[datetime] = None,
248
+ limit: int = 100,
249
+ ) -> List[AuditEvent]:
250
+ """Query audit events (optional, for implementations that support it).
251
+
252
+ Args:
253
+ filters: Filter criteria (user_id, event_type, etc.)
254
+ start_time: Filter events after this time
255
+ end_time: Filter events before this time
256
+ limit: Maximum number of events to return
257
+
258
+ Returns:
259
+ List of matching audit events
260
+
261
+ Raises:
262
+ NotImplementedError: If query not supported by implementation
263
+ """
264
+ raise NotImplementedError("Query not supported by this implementation")
265
+
266
+ def _sanitize_parameters(
267
+ self, parameters: Dict[str, Any]
268
+ ) -> tuple[Dict[str, Any], bool]:
269
+ """Sanitize sensitive data from parameters.
270
+
271
+ Args:
272
+ parameters: Raw parameters dict
273
+
274
+ Returns:
275
+ Tuple of (sanitized_parameters, was_sanitized)
276
+ """
277
+ sanitized = parameters.copy()
278
+ was_sanitized = False
279
+
280
+ # Common sensitive field patterns
281
+ sensitive_patterns = [
282
+ "password",
283
+ "secret",
284
+ "token",
285
+ "api_key",
286
+ "apikey",
287
+ "credential",
288
+ "auth",
289
+ "private_key",
290
+ "access_key",
291
+ ]
292
+
293
+ for key in list(sanitized.keys()):
294
+ key_lower = key.lower()
295
+ if any(pattern in key_lower for pattern in sensitive_patterns):
296
+ sanitized[key] = "[REDACTED]"
297
+ was_sanitized = True
298
+
299
+ return sanitized, was_sanitized
@@ -0,0 +1,131 @@
1
+ """
2
+ Audit event models.
3
+
4
+ This module contains data models for audit logging events.
5
+ """
6
+
7
+ import uuid
8
+ from datetime import datetime
9
+ from typing import Any, Dict, List, Optional
10
+
11
+ from pydantic import BaseModel, Field
12
+
13
+ from .._compat import StrEnum
14
+
15
+
16
+ class AuditEventType(StrEnum):
17
+ """Types of audit events."""
18
+
19
+ # Access control events
20
+ TOOL_ACCESS_CHECK = "tool_access_check"
21
+ UI_FEATURE_ACCESS_CHECK = "ui_feature_access_check"
22
+
23
+ # Tool execution events
24
+ TOOL_INVOCATION = "tool_invocation"
25
+ TOOL_RESULT = "tool_result"
26
+
27
+ # Conversation events
28
+ MESSAGE_RECEIVED = "message_received"
29
+ AI_RESPONSE_GENERATED = "ai_response_generated"
30
+ CONVERSATION_CREATED = "conversation_created"
31
+
32
+ # Security events
33
+ ACCESS_DENIED = "access_denied"
34
+ AUTHENTICATION_ATTEMPT = "authentication_attempt"
35
+
36
+
37
+ class AuditEvent(BaseModel):
38
+ """Base audit event with common fields."""
39
+
40
+ event_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
41
+ event_type: AuditEventType
42
+ timestamp: datetime = Field(default_factory=datetime.utcnow)
43
+
44
+ # User context
45
+ user_id: str
46
+ username: Optional[str] = None
47
+ user_email: Optional[str] = None
48
+ user_groups: List[str] = Field(default_factory=list)
49
+
50
+ # Request context
51
+ conversation_id: str
52
+ request_id: str
53
+ remote_addr: Optional[str] = None
54
+
55
+ # Event-specific data
56
+ details: Dict[str, Any] = Field(default_factory=dict)
57
+
58
+ # Privacy/redaction markers
59
+ contains_pii: bool = False
60
+ redacted_fields: List[str] = Field(default_factory=list)
61
+
62
+
63
+ class ToolAccessCheckEvent(AuditEvent):
64
+ """Audit event for tool access permission checks."""
65
+
66
+ event_type: AuditEventType = AuditEventType.TOOL_ACCESS_CHECK
67
+ tool_name: str
68
+ access_granted: bool
69
+ required_groups: List[str] = Field(default_factory=list)
70
+ reason: Optional[str] = None
71
+
72
+
73
+ class ToolInvocationEvent(AuditEvent):
74
+ """Audit event for actual tool executions."""
75
+
76
+ event_type: AuditEventType = AuditEventType.TOOL_INVOCATION
77
+ tool_call_id: str
78
+ tool_name: str
79
+
80
+ # Parameters with sanitization support
81
+ parameters: Dict[str, Any] = Field(default_factory=dict)
82
+ parameters_sanitized: bool = False
83
+
84
+ # UI context at invocation time
85
+ ui_features_available: List[str] = Field(default_factory=list)
86
+
87
+
88
+ class ToolResultEvent(AuditEvent):
89
+ """Audit event for tool execution results."""
90
+
91
+ event_type: AuditEventType = AuditEventType.TOOL_RESULT
92
+ tool_call_id: str
93
+ tool_name: str
94
+ success: bool
95
+ error: Optional[str] = None
96
+ execution_time_ms: float = 0.0
97
+
98
+ # Result metadata (without full content for size)
99
+ result_size_bytes: Optional[int] = None
100
+ ui_component_type: Optional[str] = None
101
+
102
+
103
+ class UiFeatureAccessCheckEvent(AuditEvent):
104
+ """Audit event for UI feature access checks."""
105
+
106
+ event_type: AuditEventType = AuditEventType.UI_FEATURE_ACCESS_CHECK
107
+ feature_name: str
108
+ access_granted: bool
109
+ required_groups: List[str] = Field(default_factory=list)
110
+
111
+
112
+ class AiResponseEvent(AuditEvent):
113
+ """Audit event for AI-generated responses."""
114
+
115
+ event_type: AuditEventType = AuditEventType.AI_RESPONSE_GENERATED
116
+
117
+ # Response metadata
118
+ response_length_chars: int
119
+ response_length_tokens: Optional[int] = None
120
+
121
+ # Full text (optional, configurable)
122
+ response_text: Optional[str] = None
123
+ response_hash: str # SHA256 for integrity verification
124
+
125
+ # Model info
126
+ model_name: Optional[str] = None
127
+ temperature: Optional[float] = None
128
+
129
+ # Tool calls in response
130
+ tool_calls_count: int = 0
131
+ tool_names: List[str] = Field(default_factory=list)