vanna 0.7.9__py3-none-any.whl → 2.0.0rc1__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 +439 -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.0rc1.dist-info/METADATA +868 -0
- vanna-2.0.0rc1.dist-info/RECORD +289 -0
- vanna-2.0.0rc1.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.0rc1.dist-info}/WHEEL +0 -0
- {vanna-0.7.9.dist-info → vanna-2.0.0rc1.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
|
+
]
|
vanna/core/audit/base.py
ADDED
|
@@ -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)
|