asksql 2.1.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.
- asksql-2.1.0.dist-info/METADATA +517 -0
- asksql-2.1.0.dist-info/RECORD +291 -0
- asksql-2.1.0.dist-info/WHEEL +4 -0
- asksql-2.1.0.dist-info/entry_points.txt +4 -0
- asksql-2.1.0.dist-info/licenses/LICENSE +21 -0
- vanna/__init__.py +172 -0
- 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 +488 -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 +435 -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 +466 -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/legacy/ZhipuAI/ZhipuAI_Chat.py +233 -0
- vanna/legacy/ZhipuAI/ZhipuAI_embeddings.py +79 -0
- vanna/legacy/ZhipuAI/__init__.py +2 -0
- vanna/legacy/__init__.py +403 -0
- vanna/legacy/adapter.py +468 -0
- vanna/legacy/advanced/__init__.py +28 -0
- vanna/legacy/anthropic/__init__.py +1 -0
- vanna/legacy/anthropic/anthropic_chat.py +80 -0
- vanna/legacy/azuresearch/__init__.py +1 -0
- vanna/legacy/azuresearch/azuresearch_vector.py +274 -0
- vanna/legacy/base/__init__.py +1 -0
- vanna/legacy/base/base.py +2133 -0
- vanna/legacy/base/safe_exec.py +190 -0
- vanna/legacy/bedrock/__init__.py +1 -0
- vanna/legacy/bedrock/bedrock_converse.py +86 -0
- vanna/legacy/chromadb/__init__.py +1 -0
- vanna/legacy/chromadb/chromadb_vector.py +259 -0
- vanna/legacy/cohere/__init__.py +2 -0
- vanna/legacy/cohere/cohere_chat.py +99 -0
- vanna/legacy/cohere/cohere_embeddings.py +77 -0
- vanna/legacy/deepseek/__init__.py +1 -0
- vanna/legacy/deepseek/deepseek_chat.py +59 -0
- vanna/legacy/exceptions/__init__.py +46 -0
- vanna/legacy/faiss/__init__.py +1 -0
- vanna/legacy/faiss/faiss.py +230 -0
- vanna/legacy/flask/__init__.py +1355 -0
- vanna/legacy/flask/assets.py +58 -0
- vanna/legacy/flask/auth.py +56 -0
- vanna/legacy/google/__init__.py +2 -0
- vanna/legacy/google/bigquery_vector.py +293 -0
- vanna/legacy/google/gemini_chat.py +78 -0
- vanna/legacy/hf/__init__.py +1 -0
- vanna/legacy/hf/hf.py +80 -0
- vanna/legacy/local.py +8 -0
- vanna/legacy/marqo/__init__.py +1 -0
- vanna/legacy/marqo/marqo.py +168 -0
- vanna/legacy/milvus/__init__.py +1 -0
- vanna/legacy/milvus/milvus_vector.py +328 -0
- vanna/legacy/mistral/__init__.py +1 -0
- vanna/legacy/mistral/mistral.py +51 -0
- vanna/legacy/mock/__init__.py +3 -0
- vanna/legacy/mock/embedding.py +11 -0
- vanna/legacy/mock/llm.py +18 -0
- vanna/legacy/mock/vectordb.py +67 -0
- vanna/legacy/ollama/__init__.py +1 -0
- vanna/legacy/ollama/ollama.py +110 -0
- vanna/legacy/openai/__init__.py +2 -0
- vanna/legacy/openai/openai_chat.py +124 -0
- vanna/legacy/openai/openai_embeddings.py +46 -0
- vanna/legacy/opensearch/__init__.py +2 -0
- vanna/legacy/opensearch/opensearch_vector.py +369 -0
- vanna/legacy/opensearch/opensearch_vector_semantic.py +200 -0
- vanna/legacy/oracle/__init__.py +1 -0
- vanna/legacy/oracle/oracle_vector.py +584 -0
- vanna/legacy/pgvector/__init__.py +1 -0
- vanna/legacy/pgvector/pgvector.py +282 -0
- vanna/legacy/pinecone/__init__.py +3 -0
- vanna/legacy/pinecone/pinecone_vector.py +275 -0
- vanna/legacy/qdrant/__init__.py +3 -0
- vanna/legacy/qdrant/qdrant.py +329 -0
- vanna/legacy/qianfan/Qianfan_Chat.py +170 -0
- vanna/legacy/qianfan/Qianfan_embeddings.py +36 -0
- vanna/legacy/qianfan/__init__.py +2 -0
- vanna/legacy/qianwen/QianwenAI_chat.py +132 -0
- vanna/legacy/qianwen/QianwenAI_embeddings.py +46 -0
- vanna/legacy/qianwen/__init__.py +2 -0
- vanna/legacy/remote.py +79 -0
- vanna/legacy/types/__init__.py +292 -0
- vanna/legacy/utils.py +72 -0
- vanna/legacy/vannadb/__init__.py +1 -0
- vanna/legacy/vannadb/vannadb_vector.py +468 -0
- vanna/legacy/vllm/__init__.py +1 -0
- vanna/legacy/vllm/vllm.py +97 -0
- vanna/legacy/weaviate/__init__.py +1 -0
- vanna/legacy/weaviate/weaviate_vector.py +194 -0
- vanna/legacy/xinference/__init__.py +1 -0
- vanna/legacy/xinference/xinference.py +53 -0
- 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 +173 -0
- vanna/tools/visualize_data.py +195 -0
- vanna/utils/__init__.py +0 -0
- vanna/utils/sql_safety.py +84 -0
- vanna/web_components/__init__.py +44 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""Guarded execution of LLM-generated Plotly code (vanna-ai/vanna#1098).
|
|
2
|
+
|
|
3
|
+
`get_plotly_figure` runs Python written by the LLM, and the LLM prompt can be
|
|
4
|
+
influenced by users (e.g. the Flask `chart_instructions` query param). Without
|
|
5
|
+
a guard, a prompt injection becomes remote code execution.
|
|
6
|
+
|
|
7
|
+
This is defence in depth, NOT a true sandbox - CPython cannot be sandboxed
|
|
8
|
+
in-process. It closes the easy paths: arbitrary imports, `_`/dunder tricks,
|
|
9
|
+
file/network IO helpers, dangerous builtins and access to vanna's module globals.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import ast
|
|
13
|
+
import builtins
|
|
14
|
+
|
|
15
|
+
import pandas as pd
|
|
16
|
+
import plotly
|
|
17
|
+
import plotly.express as px
|
|
18
|
+
import plotly.graph_objects as go
|
|
19
|
+
|
|
20
|
+
ALLOWED_IMPORT_ROOTS = {"plotly", "pandas", "numpy", "math"}
|
|
21
|
+
|
|
22
|
+
# Attribute/import names that lead to the OS, the interpreter, or file/network IO.
|
|
23
|
+
DENIED_NAMES = {
|
|
24
|
+
# modules reachable as attributes of other modules
|
|
25
|
+
"os",
|
|
26
|
+
"sys",
|
|
27
|
+
"subprocess",
|
|
28
|
+
"builtins",
|
|
29
|
+
"importlib",
|
|
30
|
+
"io",
|
|
31
|
+
"ctypes",
|
|
32
|
+
"ctypeslib",
|
|
33
|
+
"socket",
|
|
34
|
+
"shutil",
|
|
35
|
+
"pickle",
|
|
36
|
+
"marshal",
|
|
37
|
+
"pty",
|
|
38
|
+
"requests",
|
|
39
|
+
# process / code execution
|
|
40
|
+
"system",
|
|
41
|
+
"popen",
|
|
42
|
+
"Popen",
|
|
43
|
+
"spawn",
|
|
44
|
+
"fork",
|
|
45
|
+
"eval",
|
|
46
|
+
"exec",
|
|
47
|
+
"compile",
|
|
48
|
+
"query",
|
|
49
|
+
"globals",
|
|
50
|
+
"locals",
|
|
51
|
+
"vars",
|
|
52
|
+
"getattr",
|
|
53
|
+
"setattr",
|
|
54
|
+
"delattr",
|
|
55
|
+
"open",
|
|
56
|
+
"input",
|
|
57
|
+
"breakpoint",
|
|
58
|
+
# pandas / numpy / plotly file IO
|
|
59
|
+
"to_csv",
|
|
60
|
+
"to_excel",
|
|
61
|
+
"to_json",
|
|
62
|
+
"to_html",
|
|
63
|
+
"to_parquet",
|
|
64
|
+
"to_pickle",
|
|
65
|
+
"to_sql",
|
|
66
|
+
"to_hdf",
|
|
67
|
+
"to_feather",
|
|
68
|
+
"to_stata",
|
|
69
|
+
"to_xml",
|
|
70
|
+
"to_orc",
|
|
71
|
+
"to_latex",
|
|
72
|
+
"to_markdown",
|
|
73
|
+
"to_string",
|
|
74
|
+
"to_clipboard",
|
|
75
|
+
"tofile",
|
|
76
|
+
"write_html",
|
|
77
|
+
"write_image",
|
|
78
|
+
"write_json",
|
|
79
|
+
"save",
|
|
80
|
+
"savez",
|
|
81
|
+
"savez_compressed",
|
|
82
|
+
"savetxt",
|
|
83
|
+
"load",
|
|
84
|
+
"loadtxt",
|
|
85
|
+
"genfromtxt",
|
|
86
|
+
"fromfile",
|
|
87
|
+
"memmap",
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
SAFE_BUILTIN_NAMES = [
|
|
91
|
+
"abs",
|
|
92
|
+
"all",
|
|
93
|
+
"any",
|
|
94
|
+
"bool",
|
|
95
|
+
"dict",
|
|
96
|
+
"enumerate",
|
|
97
|
+
"filter",
|
|
98
|
+
"float",
|
|
99
|
+
"int",
|
|
100
|
+
"isinstance",
|
|
101
|
+
"len",
|
|
102
|
+
"list",
|
|
103
|
+
"map",
|
|
104
|
+
"max",
|
|
105
|
+
"min",
|
|
106
|
+
"print",
|
|
107
|
+
"range",
|
|
108
|
+
"reversed",
|
|
109
|
+
"round",
|
|
110
|
+
"set",
|
|
111
|
+
"sorted",
|
|
112
|
+
"str",
|
|
113
|
+
"sum",
|
|
114
|
+
"tuple",
|
|
115
|
+
"zip",
|
|
116
|
+
"ValueError",
|
|
117
|
+
"TypeError",
|
|
118
|
+
"KeyError",
|
|
119
|
+
"IndexError",
|
|
120
|
+
"Exception",
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class UnsafePlotlyCodeError(ValueError):
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _is_denied(name: str) -> bool:
|
|
129
|
+
# A bare "_" is the usual throwaway loop variable; any other leading "_" is private/dunder.
|
|
130
|
+
if name != "_" and name.startswith("_"):
|
|
131
|
+
return True
|
|
132
|
+
return name in DENIED_NAMES or name.startswith("read_")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def validate_plotly_code(code: str) -> None:
|
|
136
|
+
"""Raise UnsafePlotlyCodeError if `code` uses anything outside the allowed subset."""
|
|
137
|
+
try:
|
|
138
|
+
tree = ast.parse(code)
|
|
139
|
+
except SyntaxError as e:
|
|
140
|
+
raise UnsafePlotlyCodeError(f"invalid Python: {e}") from e
|
|
141
|
+
|
|
142
|
+
for node in ast.walk(tree):
|
|
143
|
+
if isinstance(node, ast.Import):
|
|
144
|
+
for alias in node.names:
|
|
145
|
+
if alias.name.split(".")[0] not in ALLOWED_IMPORT_ROOTS:
|
|
146
|
+
raise UnsafePlotlyCodeError(
|
|
147
|
+
f"import of '{alias.name}' is not allowed"
|
|
148
|
+
)
|
|
149
|
+
elif isinstance(node, ast.ImportFrom):
|
|
150
|
+
if (
|
|
151
|
+
node.level
|
|
152
|
+
or (node.module or "").split(".")[0] not in ALLOWED_IMPORT_ROOTS
|
|
153
|
+
):
|
|
154
|
+
raise UnsafePlotlyCodeError(
|
|
155
|
+
f"import from '{node.module}' is not allowed"
|
|
156
|
+
)
|
|
157
|
+
for alias in node.names:
|
|
158
|
+
if alias.name == "*" or _is_denied(alias.name):
|
|
159
|
+
raise UnsafePlotlyCodeError(
|
|
160
|
+
f"import of '{alias.name}' is not allowed"
|
|
161
|
+
)
|
|
162
|
+
elif isinstance(node, ast.Attribute) and _is_denied(node.attr):
|
|
163
|
+
raise UnsafePlotlyCodeError(f"access to '.{node.attr}' is not allowed")
|
|
164
|
+
elif isinstance(node, ast.Name) and _is_denied(node.id):
|
|
165
|
+
raise UnsafePlotlyCodeError(f"use of '{node.id}' is not allowed")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _restricted_import(name, globals=None, locals=None, fromlist=(), level=0):
|
|
169
|
+
if level != 0 or name.split(".")[0] not in ALLOWED_IMPORT_ROOTS:
|
|
170
|
+
raise ImportError(f"import of '{name}' is not allowed in chart code")
|
|
171
|
+
return builtins.__import__(name, globals, locals, fromlist, level)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def exec_plotly_code(code: str, df: pd.DataFrame):
|
|
175
|
+
"""Validate and run LLM-generated Plotly code; return the `fig` it defines (or None)."""
|
|
176
|
+
validate_plotly_code(code)
|
|
177
|
+
|
|
178
|
+
safe_builtins = {name: getattr(builtins, name) for name in SAFE_BUILTIN_NAMES}
|
|
179
|
+
safe_builtins["__import__"] = _restricted_import
|
|
180
|
+
|
|
181
|
+
namespace = {
|
|
182
|
+
"__builtins__": safe_builtins,
|
|
183
|
+
"df": df,
|
|
184
|
+
"pd": pd,
|
|
185
|
+
"plotly": plotly,
|
|
186
|
+
"px": px,
|
|
187
|
+
"go": go,
|
|
188
|
+
}
|
|
189
|
+
exec(code, namespace)
|
|
190
|
+
return namespace.get("fig")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .bedrock_converse import Bedrock_Converse
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from ..base import VannaBase
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
import boto3
|
|
5
|
+
from botocore.exceptions import ClientError
|
|
6
|
+
except ImportError:
|
|
7
|
+
raise ImportError("Please install boto3 and botocore to use Amazon Bedrock models")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Bedrock_Converse(VannaBase):
|
|
11
|
+
def __init__(self, client=None, config=None):
|
|
12
|
+
VannaBase.__init__(self, config=config)
|
|
13
|
+
|
|
14
|
+
# default parameters
|
|
15
|
+
self.temperature = 0.0
|
|
16
|
+
self.max_tokens = 500
|
|
17
|
+
|
|
18
|
+
if client is None:
|
|
19
|
+
raise ValueError(
|
|
20
|
+
"A valid Bedrock runtime client must be provided to invoke Bedrock models"
|
|
21
|
+
)
|
|
22
|
+
else:
|
|
23
|
+
self.client = client
|
|
24
|
+
|
|
25
|
+
if config is None:
|
|
26
|
+
raise ValueError(
|
|
27
|
+
"Config is required with model_id and inference parameters"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if "modelId" not in config:
|
|
31
|
+
raise ValueError("config must contain a modelId to invoke")
|
|
32
|
+
else:
|
|
33
|
+
self.model = config["modelId"]
|
|
34
|
+
|
|
35
|
+
if "temperature" in config:
|
|
36
|
+
self.temperature = config["temperature"]
|
|
37
|
+
|
|
38
|
+
if "max_tokens" in config:
|
|
39
|
+
self.max_tokens = config["max_tokens"]
|
|
40
|
+
|
|
41
|
+
def system_message(self, message: str) -> dict:
|
|
42
|
+
return {"role": "system", "content": message}
|
|
43
|
+
|
|
44
|
+
def user_message(self, message: str) -> dict:
|
|
45
|
+
return {"role": "user", "content": message}
|
|
46
|
+
|
|
47
|
+
def assistant_message(self, message: str) -> dict:
|
|
48
|
+
return {"role": "assistant", "content": message}
|
|
49
|
+
|
|
50
|
+
def submit_prompt(self, prompt, **kwargs) -> str:
|
|
51
|
+
inference_config = {
|
|
52
|
+
"temperature": self.temperature,
|
|
53
|
+
"maxTokens": self.max_tokens,
|
|
54
|
+
}
|
|
55
|
+
additional_model_fields = {
|
|
56
|
+
"top_p": 1, # setting top_p value for nucleus sampling
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
system_message = None
|
|
60
|
+
no_system_prompt = []
|
|
61
|
+
for prompt_message in prompt:
|
|
62
|
+
role = prompt_message["role"]
|
|
63
|
+
if role == "system":
|
|
64
|
+
system_message = prompt_message["content"]
|
|
65
|
+
else:
|
|
66
|
+
no_system_prompt.append(
|
|
67
|
+
{"role": role, "content": [{"text": prompt_message["content"]}]}
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
converse_api_params = {
|
|
71
|
+
"modelId": self.model,
|
|
72
|
+
"messages": no_system_prompt,
|
|
73
|
+
"inferenceConfig": inference_config,
|
|
74
|
+
"additionalModelRequestFields": additional_model_fields,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if system_message:
|
|
78
|
+
converse_api_params["system"] = [{"text": system_message}]
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
response = self.client.converse(**converse_api_params)
|
|
82
|
+
text_content = response["output"]["message"]["content"][0]["text"]
|
|
83
|
+
return text_content
|
|
84
|
+
except ClientError as err:
|
|
85
|
+
message = err.response["Error"]["Message"]
|
|
86
|
+
raise Exception(f"A Bedrock client error occurred: {message}")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .chromadb_vector import ChromaDB_VectorStore
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
import chromadb
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from chromadb.config import Settings
|
|
7
|
+
from chromadb.utils import embedding_functions
|
|
8
|
+
|
|
9
|
+
from ..base import VannaBase
|
|
10
|
+
from ..utils import deterministic_uuid
|
|
11
|
+
|
|
12
|
+
default_ef = embedding_functions.DefaultEmbeddingFunction()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ChromaDB_VectorStore(VannaBase):
|
|
16
|
+
def __init__(self, config=None):
|
|
17
|
+
VannaBase.__init__(self, config=config)
|
|
18
|
+
if config is None:
|
|
19
|
+
config = {}
|
|
20
|
+
|
|
21
|
+
path = config.get("path", ".")
|
|
22
|
+
self.embedding_function = config.get("embedding_function", default_ef)
|
|
23
|
+
curr_client = config.get("client", "persistent")
|
|
24
|
+
collection_metadata = config.get("collection_metadata", None)
|
|
25
|
+
self.n_results_sql = config.get("n_results_sql", config.get("n_results", 10))
|
|
26
|
+
self.n_results_documentation = config.get(
|
|
27
|
+
"n_results_documentation", config.get("n_results", 10)
|
|
28
|
+
)
|
|
29
|
+
self.n_results_ddl = config.get("n_results_ddl", config.get("n_results", 10))
|
|
30
|
+
|
|
31
|
+
if curr_client == "persistent":
|
|
32
|
+
self.chroma_client = chromadb.PersistentClient(
|
|
33
|
+
path=path, settings=Settings(anonymized_telemetry=False)
|
|
34
|
+
)
|
|
35
|
+
elif curr_client == "in-memory":
|
|
36
|
+
self.chroma_client = chromadb.EphemeralClient(
|
|
37
|
+
settings=Settings(anonymized_telemetry=False)
|
|
38
|
+
)
|
|
39
|
+
elif isinstance(curr_client, chromadb.api.client.Client):
|
|
40
|
+
# allow providing client directly
|
|
41
|
+
self.chroma_client = curr_client
|
|
42
|
+
else:
|
|
43
|
+
raise ValueError(f"Unsupported client was set in config: {curr_client}")
|
|
44
|
+
|
|
45
|
+
self.documentation_collection = self.chroma_client.get_or_create_collection(
|
|
46
|
+
name="documentation",
|
|
47
|
+
embedding_function=self.embedding_function,
|
|
48
|
+
metadata=collection_metadata,
|
|
49
|
+
)
|
|
50
|
+
self.ddl_collection = self.chroma_client.get_or_create_collection(
|
|
51
|
+
name="ddl",
|
|
52
|
+
embedding_function=self.embedding_function,
|
|
53
|
+
metadata=collection_metadata,
|
|
54
|
+
)
|
|
55
|
+
self.sql_collection = self.chroma_client.get_or_create_collection(
|
|
56
|
+
name="sql",
|
|
57
|
+
embedding_function=self.embedding_function,
|
|
58
|
+
metadata=collection_metadata,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def generate_embedding(self, data: str, **kwargs) -> List[float]:
|
|
62
|
+
embedding = self.embedding_function([data])
|
|
63
|
+
if len(embedding) == 1:
|
|
64
|
+
return embedding[0]
|
|
65
|
+
return embedding
|
|
66
|
+
|
|
67
|
+
def add_question_sql(self, question: str, sql: str, **kwargs) -> str:
|
|
68
|
+
question_sql_json = json.dumps(
|
|
69
|
+
{
|
|
70
|
+
"question": question,
|
|
71
|
+
"sql": sql,
|
|
72
|
+
},
|
|
73
|
+
ensure_ascii=False,
|
|
74
|
+
)
|
|
75
|
+
id = deterministic_uuid(question_sql_json) + "-sql"
|
|
76
|
+
self.sql_collection.add(
|
|
77
|
+
documents=question_sql_json,
|
|
78
|
+
embeddings=self.generate_embedding(question_sql_json),
|
|
79
|
+
ids=id,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return id
|
|
83
|
+
|
|
84
|
+
def add_ddl(self, ddl: str, **kwargs) -> str:
|
|
85
|
+
id = deterministic_uuid(ddl) + "-ddl"
|
|
86
|
+
self.ddl_collection.add(
|
|
87
|
+
documents=ddl,
|
|
88
|
+
embeddings=self.generate_embedding(ddl),
|
|
89
|
+
ids=id,
|
|
90
|
+
)
|
|
91
|
+
return id
|
|
92
|
+
|
|
93
|
+
def add_documentation(self, documentation: str, **kwargs) -> str:
|
|
94
|
+
id = deterministic_uuid(documentation) + "-doc"
|
|
95
|
+
self.documentation_collection.add(
|
|
96
|
+
documents=documentation,
|
|
97
|
+
embeddings=self.generate_embedding(documentation),
|
|
98
|
+
ids=id,
|
|
99
|
+
)
|
|
100
|
+
return id
|
|
101
|
+
|
|
102
|
+
def get_training_data(self, **kwargs) -> pd.DataFrame:
|
|
103
|
+
sql_data = self.sql_collection.get()
|
|
104
|
+
|
|
105
|
+
df = pd.DataFrame()
|
|
106
|
+
|
|
107
|
+
if sql_data is not None:
|
|
108
|
+
# Extract the documents and ids
|
|
109
|
+
documents = [json.loads(doc) for doc in sql_data["documents"]]
|
|
110
|
+
ids = sql_data["ids"]
|
|
111
|
+
|
|
112
|
+
# Create a DataFrame
|
|
113
|
+
df_sql = pd.DataFrame(
|
|
114
|
+
{
|
|
115
|
+
"id": ids,
|
|
116
|
+
"question": [doc["question"] for doc in documents],
|
|
117
|
+
"content": [doc["sql"] for doc in documents],
|
|
118
|
+
}
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
df_sql["training_data_type"] = "sql"
|
|
122
|
+
|
|
123
|
+
df = pd.concat([df, df_sql])
|
|
124
|
+
|
|
125
|
+
ddl_data = self.ddl_collection.get()
|
|
126
|
+
|
|
127
|
+
if ddl_data is not None:
|
|
128
|
+
# Extract the documents and ids
|
|
129
|
+
documents = [doc for doc in ddl_data["documents"]]
|
|
130
|
+
ids = ddl_data["ids"]
|
|
131
|
+
|
|
132
|
+
# Create a DataFrame
|
|
133
|
+
df_ddl = pd.DataFrame(
|
|
134
|
+
{
|
|
135
|
+
"id": ids,
|
|
136
|
+
"question": [None for doc in documents],
|
|
137
|
+
"content": [doc for doc in documents],
|
|
138
|
+
}
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
df_ddl["training_data_type"] = "ddl"
|
|
142
|
+
|
|
143
|
+
df = pd.concat([df, df_ddl])
|
|
144
|
+
|
|
145
|
+
doc_data = self.documentation_collection.get()
|
|
146
|
+
|
|
147
|
+
if doc_data is not None:
|
|
148
|
+
# Extract the documents and ids
|
|
149
|
+
documents = [doc for doc in doc_data["documents"]]
|
|
150
|
+
ids = doc_data["ids"]
|
|
151
|
+
|
|
152
|
+
# Create a DataFrame
|
|
153
|
+
df_doc = pd.DataFrame(
|
|
154
|
+
{
|
|
155
|
+
"id": ids,
|
|
156
|
+
"question": [None for doc in documents],
|
|
157
|
+
"content": [doc for doc in documents],
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
df_doc["training_data_type"] = "documentation"
|
|
162
|
+
|
|
163
|
+
df = pd.concat([df, df_doc])
|
|
164
|
+
|
|
165
|
+
return df
|
|
166
|
+
|
|
167
|
+
def remove_training_data(self, id: str, **kwargs) -> bool:
|
|
168
|
+
if id.endswith("-sql"):
|
|
169
|
+
self.sql_collection.delete(ids=id)
|
|
170
|
+
return True
|
|
171
|
+
elif id.endswith("-ddl"):
|
|
172
|
+
self.ddl_collection.delete(ids=id)
|
|
173
|
+
return True
|
|
174
|
+
elif id.endswith("-doc"):
|
|
175
|
+
self.documentation_collection.delete(ids=id)
|
|
176
|
+
return True
|
|
177
|
+
else:
|
|
178
|
+
return False
|
|
179
|
+
|
|
180
|
+
def remove_collection(self, collection_name: str) -> bool:
|
|
181
|
+
"""
|
|
182
|
+
This function can reset the collection to empty state.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
collection_name (str): sql or ddl or documentation
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
bool: True if collection is deleted, False otherwise
|
|
189
|
+
"""
|
|
190
|
+
if collection_name == "sql":
|
|
191
|
+
self.chroma_client.delete_collection(name="sql")
|
|
192
|
+
self.sql_collection = self.chroma_client.get_or_create_collection(
|
|
193
|
+
name="sql", embedding_function=self.embedding_function
|
|
194
|
+
)
|
|
195
|
+
return True
|
|
196
|
+
elif collection_name == "ddl":
|
|
197
|
+
self.chroma_client.delete_collection(name="ddl")
|
|
198
|
+
self.ddl_collection = self.chroma_client.get_or_create_collection(
|
|
199
|
+
name="ddl", embedding_function=self.embedding_function
|
|
200
|
+
)
|
|
201
|
+
return True
|
|
202
|
+
elif collection_name == "documentation":
|
|
203
|
+
self.chroma_client.delete_collection(name="documentation")
|
|
204
|
+
self.documentation_collection = self.chroma_client.get_or_create_collection(
|
|
205
|
+
name="documentation", embedding_function=self.embedding_function
|
|
206
|
+
)
|
|
207
|
+
return True
|
|
208
|
+
else:
|
|
209
|
+
return False
|
|
210
|
+
|
|
211
|
+
@staticmethod
|
|
212
|
+
def _extract_documents(query_results) -> list:
|
|
213
|
+
"""
|
|
214
|
+
Static method to extract the documents from the results of a query.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
query_results (pd.DataFrame): The dataframe to use.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
List[str] or None: The extracted documents, or an empty list or
|
|
221
|
+
single document if an error occurred.
|
|
222
|
+
"""
|
|
223
|
+
if query_results is None:
|
|
224
|
+
return []
|
|
225
|
+
|
|
226
|
+
if "documents" in query_results:
|
|
227
|
+
documents = query_results["documents"]
|
|
228
|
+
|
|
229
|
+
if len(documents) == 1 and isinstance(documents[0], list):
|
|
230
|
+
try:
|
|
231
|
+
documents = [json.loads(doc) for doc in documents[0]]
|
|
232
|
+
except Exception as e:
|
|
233
|
+
return documents[0]
|
|
234
|
+
|
|
235
|
+
return documents
|
|
236
|
+
|
|
237
|
+
def get_similar_question_sql(self, question: str, **kwargs) -> list:
|
|
238
|
+
return ChromaDB_VectorStore._extract_documents(
|
|
239
|
+
self.sql_collection.query(
|
|
240
|
+
query_texts=[question],
|
|
241
|
+
n_results=self.n_results_sql,
|
|
242
|
+
)
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
def get_related_ddl(self, question: str, **kwargs) -> list:
|
|
246
|
+
return ChromaDB_VectorStore._extract_documents(
|
|
247
|
+
self.ddl_collection.query(
|
|
248
|
+
query_texts=[question],
|
|
249
|
+
n_results=self.n_results_ddl,
|
|
250
|
+
)
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
def get_related_documentation(self, question: str, **kwargs) -> list:
|
|
254
|
+
return ChromaDB_VectorStore._extract_documents(
|
|
255
|
+
self.documentation_collection.query(
|
|
256
|
+
query_texts=[question],
|
|
257
|
+
n_results=self.n_results_documentation,
|
|
258
|
+
)
|
|
259
|
+
)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from openai import OpenAI
|
|
4
|
+
|
|
5
|
+
from ..base import VannaBase
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Cohere_Chat(VannaBase):
|
|
9
|
+
def __init__(self, client=None, config=None):
|
|
10
|
+
VannaBase.__init__(self, config=config)
|
|
11
|
+
|
|
12
|
+
# default parameters - can be overridden using config
|
|
13
|
+
self.temperature = 0.2 # Lower temperature for more precise SQL generation
|
|
14
|
+
self.model = "command-a-03-2025" # Cohere's default model
|
|
15
|
+
|
|
16
|
+
if config is not None:
|
|
17
|
+
if "temperature" in config:
|
|
18
|
+
self.temperature = config["temperature"]
|
|
19
|
+
if "model" in config:
|
|
20
|
+
self.model = config["model"]
|
|
21
|
+
|
|
22
|
+
if client is not None:
|
|
23
|
+
self.client = client
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
# Check for API key in environment variable
|
|
27
|
+
api_key = os.getenv("COHERE_API_KEY")
|
|
28
|
+
|
|
29
|
+
# Check for API key in config
|
|
30
|
+
if config is not None and "api_key" in config:
|
|
31
|
+
api_key = config["api_key"]
|
|
32
|
+
|
|
33
|
+
# Validate API key
|
|
34
|
+
if not api_key:
|
|
35
|
+
raise ValueError(
|
|
36
|
+
"Cohere API key is required. Please provide it via config or set the COHERE_API_KEY environment variable."
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Initialize client with validated API key
|
|
40
|
+
self.client = OpenAI(
|
|
41
|
+
base_url="https://api.cohere.ai/compatibility/v1",
|
|
42
|
+
api_key=api_key,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def system_message(self, message: str) -> any:
|
|
46
|
+
return {
|
|
47
|
+
"role": "developer",
|
|
48
|
+
"content": message,
|
|
49
|
+
} # Cohere uses 'developer' for system role
|
|
50
|
+
|
|
51
|
+
def user_message(self, message: str) -> any:
|
|
52
|
+
return {"role": "user", "content": message}
|
|
53
|
+
|
|
54
|
+
def assistant_message(self, message: str) -> any:
|
|
55
|
+
return {"role": "assistant", "content": message}
|
|
56
|
+
|
|
57
|
+
def submit_prompt(self, prompt, **kwargs) -> str:
|
|
58
|
+
if prompt is None:
|
|
59
|
+
raise Exception("Prompt is None")
|
|
60
|
+
|
|
61
|
+
if len(prompt) == 0:
|
|
62
|
+
raise Exception("Prompt is empty")
|
|
63
|
+
|
|
64
|
+
# Count the number of tokens in the message log
|
|
65
|
+
# Use 4 as an approximation for the number of characters per token
|
|
66
|
+
num_tokens = 0
|
|
67
|
+
for message in prompt:
|
|
68
|
+
num_tokens += len(message["content"]) / 4
|
|
69
|
+
|
|
70
|
+
# Use model from kwargs, config, or default
|
|
71
|
+
model = kwargs.get("model", self.model)
|
|
72
|
+
if self.config is not None and "model" in self.config and model == self.model:
|
|
73
|
+
model = self.config["model"]
|
|
74
|
+
|
|
75
|
+
print(f"Using model {model} for {num_tokens} tokens (approx)")
|
|
76
|
+
try:
|
|
77
|
+
response = self.client.chat.completions.create(
|
|
78
|
+
model=model,
|
|
79
|
+
messages=prompt,
|
|
80
|
+
temperature=self.temperature,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Check if response has expected structure
|
|
84
|
+
if not response or not hasattr(response, "choices") or not response.choices:
|
|
85
|
+
raise ValueError("Received empty or malformed response from API")
|
|
86
|
+
|
|
87
|
+
if not response.choices[0] or not hasattr(response.choices[0], "message"):
|
|
88
|
+
raise ValueError("Response is missing expected 'message' field")
|
|
89
|
+
|
|
90
|
+
if not hasattr(response.choices[0].message, "content"):
|
|
91
|
+
raise ValueError("Response message is missing expected 'content' field")
|
|
92
|
+
|
|
93
|
+
return response.choices[0].message.content
|
|
94
|
+
|
|
95
|
+
except Exception as e:
|
|
96
|
+
# Log the error and raise a more informative exception
|
|
97
|
+
error_msg = f"Error processing Cohere chat response: {str(e)}"
|
|
98
|
+
print(error_msg)
|
|
99
|
+
raise Exception(error_msg)
|