dao-ai 0.0.28__py3-none-any.whl → 0.1.2__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.
- dao_ai/__init__.py +29 -0
- dao_ai/agent_as_code.py +2 -5
- dao_ai/cli.py +245 -40
- dao_ai/config.py +1491 -370
- dao_ai/genie/__init__.py +38 -0
- dao_ai/genie/cache/__init__.py +43 -0
- dao_ai/genie/cache/base.py +72 -0
- dao_ai/genie/cache/core.py +79 -0
- dao_ai/genie/cache/lru.py +347 -0
- dao_ai/genie/cache/semantic.py +970 -0
- dao_ai/genie/core.py +35 -0
- dao_ai/graph.py +27 -253
- dao_ai/hooks/__init__.py +9 -6
- dao_ai/hooks/core.py +27 -195
- dao_ai/logging.py +56 -0
- dao_ai/memory/__init__.py +10 -0
- dao_ai/memory/core.py +65 -30
- dao_ai/memory/databricks.py +402 -0
- dao_ai/memory/postgres.py +79 -38
- dao_ai/messages.py +6 -4
- dao_ai/middleware/__init__.py +125 -0
- dao_ai/middleware/assertions.py +806 -0
- dao_ai/middleware/base.py +50 -0
- dao_ai/middleware/core.py +67 -0
- dao_ai/middleware/guardrails.py +420 -0
- dao_ai/middleware/human_in_the_loop.py +232 -0
- dao_ai/middleware/message_validation.py +586 -0
- dao_ai/middleware/summarization.py +197 -0
- dao_ai/models.py +1306 -114
- dao_ai/nodes.py +245 -159
- dao_ai/optimization.py +674 -0
- dao_ai/orchestration/__init__.py +52 -0
- dao_ai/orchestration/core.py +294 -0
- dao_ai/orchestration/supervisor.py +278 -0
- dao_ai/orchestration/swarm.py +271 -0
- dao_ai/prompts.py +128 -31
- dao_ai/providers/databricks.py +573 -601
- dao_ai/state.py +157 -21
- dao_ai/tools/__init__.py +13 -5
- dao_ai/tools/agent.py +1 -3
- dao_ai/tools/core.py +64 -11
- dao_ai/tools/email.py +232 -0
- dao_ai/tools/genie.py +144 -294
- dao_ai/tools/mcp.py +223 -155
- dao_ai/tools/memory.py +50 -0
- dao_ai/tools/python.py +9 -14
- dao_ai/tools/search.py +14 -0
- dao_ai/tools/slack.py +22 -10
- dao_ai/tools/sql.py +202 -0
- dao_ai/tools/time.py +30 -7
- dao_ai/tools/unity_catalog.py +165 -88
- dao_ai/tools/vector_search.py +331 -221
- dao_ai/utils.py +166 -20
- dao_ai-0.1.2.dist-info/METADATA +455 -0
- dao_ai-0.1.2.dist-info/RECORD +64 -0
- dao_ai/chat_models.py +0 -204
- dao_ai/guardrails.py +0 -112
- dao_ai/tools/human_in_the_loop.py +0 -100
- dao_ai-0.0.28.dist-info/METADATA +0 -1168
- dao_ai-0.0.28.dist-info/RECORD +0 -41
- {dao_ai-0.0.28.dist-info → dao_ai-0.1.2.dist-info}/WHEEL +0 -0
- {dao_ai-0.0.28.dist-info → dao_ai-0.1.2.dist-info}/entry_points.txt +0 -0
- {dao_ai-0.0.28.dist-info → dao_ai-0.1.2.dist-info}/licenses/LICENSE +0 -0
dao_ai/tools/sql.py
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SQL execution tool for running SQL statements against Databricks SQL warehouses.
|
|
3
|
+
|
|
4
|
+
This module provides a factory function for creating tools that execute
|
|
5
|
+
pre-configured SQL statements against a Databricks SQL warehouse.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from databricks.sdk import WorkspaceClient
|
|
9
|
+
from databricks.sdk.service.sql import StatementResponse, StatementState
|
|
10
|
+
from langchain.tools import tool
|
|
11
|
+
from loguru import logger
|
|
12
|
+
|
|
13
|
+
from dao_ai.config import WarehouseModel, value_of
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def create_execute_statement_tool(
|
|
17
|
+
warehouse: WarehouseModel,
|
|
18
|
+
statement: str,
|
|
19
|
+
name: str = "execute_sql_tool",
|
|
20
|
+
description: str | None = None,
|
|
21
|
+
) -> tool:
|
|
22
|
+
"""
|
|
23
|
+
Create a tool for executing a pre-configured SQL statement against a Databricks SQL warehouse.
|
|
24
|
+
|
|
25
|
+
This factory function generates a tool that executes a specific SQL statement
|
|
26
|
+
(defined at configuration time) against a Databricks SQL warehouse. The SQL is
|
|
27
|
+
fixed and cannot be modified by the LLM at runtime, making this suitable for
|
|
28
|
+
providing agents with specific, pre-defined queries.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
warehouse: WarehouseModel containing warehouse configuration and credentials
|
|
32
|
+
sql: The SQL statement to execute (configured at tool creation time)
|
|
33
|
+
name: Optional custom name for the tool. Defaults to "execute_sql_tool"
|
|
34
|
+
description: Optional custom description for the tool. If None, uses default description
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
A LangChain tool that executes the pre-configured SQL statement and returns results
|
|
38
|
+
|
|
39
|
+
Example:
|
|
40
|
+
```python
|
|
41
|
+
from dao_ai.config import WarehouseModel
|
|
42
|
+
from dao_ai.tools.sql import create_execute_sql_tool
|
|
43
|
+
|
|
44
|
+
# Create warehouse model
|
|
45
|
+
warehouse = WarehouseModel(
|
|
46
|
+
name="analytics_warehouse",
|
|
47
|
+
warehouse_id="abc123def456",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Create SQL execution tool with pre-configured query
|
|
51
|
+
customer_count_tool = create_execute_sql_tool(
|
|
52
|
+
warehouse=warehouse,
|
|
53
|
+
sql="SELECT COUNT(*) as customer_count FROM catalog.schema.customers",
|
|
54
|
+
name="get_customer_count",
|
|
55
|
+
description="Get the total number of customers in the database"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Use the tool (no parameters needed - SQL is pre-configured)
|
|
59
|
+
result = customer_count_tool.invoke({})
|
|
60
|
+
```
|
|
61
|
+
"""
|
|
62
|
+
if description is None:
|
|
63
|
+
description = f"Execute a pre-configured SQL query against the {warehouse.name} warehouse and return the results."
|
|
64
|
+
|
|
65
|
+
warehouse_id: str = value_of(warehouse.warehouse_id)
|
|
66
|
+
workspace_client: WorkspaceClient = warehouse.workspace_client
|
|
67
|
+
|
|
68
|
+
logger.debug(
|
|
69
|
+
"Creating SQL execution tool",
|
|
70
|
+
tool_name=name,
|
|
71
|
+
warehouse_name=warehouse.name,
|
|
72
|
+
warehouse_id=warehouse_id,
|
|
73
|
+
sql_preview=statement[:100] + "..." if len(statement) > 100 else statement,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
@tool(name_or_callable=name, description=description)
|
|
77
|
+
def execute_statement_tool() -> str:
|
|
78
|
+
"""
|
|
79
|
+
Execute the pre-configured SQL statement against the Databricks SQL warehouse.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
A string containing the query results or execution status
|
|
83
|
+
"""
|
|
84
|
+
logger.info(
|
|
85
|
+
"Executing SQL statement",
|
|
86
|
+
tool_name=name,
|
|
87
|
+
warehouse_id=warehouse_id,
|
|
88
|
+
sql_preview=statement[:100] + "..." if len(statement) > 100 else statement,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
# Execute the SQL statement
|
|
93
|
+
statement_response: StatementResponse = (
|
|
94
|
+
workspace_client.statement_execution.execute_statement(
|
|
95
|
+
warehouse_id=warehouse_id,
|
|
96
|
+
statement=statement,
|
|
97
|
+
wait_timeout="30s",
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Poll for completion if still pending
|
|
102
|
+
while statement_response.status.state in [
|
|
103
|
+
StatementState.PENDING,
|
|
104
|
+
StatementState.RUNNING,
|
|
105
|
+
]:
|
|
106
|
+
logger.trace(
|
|
107
|
+
"SQL statement still executing, polling...",
|
|
108
|
+
statement_id=statement_response.statement_id,
|
|
109
|
+
state=statement_response.status.state,
|
|
110
|
+
)
|
|
111
|
+
statement_response = workspace_client.statement_execution.get_statement(
|
|
112
|
+
statement_response.statement_id
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Check execution status
|
|
116
|
+
if statement_response.status.state != StatementState.SUCCEEDED:
|
|
117
|
+
error_msg: str = (
|
|
118
|
+
f"SQL execution failed with state {statement_response.status.state}"
|
|
119
|
+
)
|
|
120
|
+
if statement_response.status.error:
|
|
121
|
+
error_msg += f": {statement_response.status.error.message}"
|
|
122
|
+
logger.error(
|
|
123
|
+
"SQL execution failed",
|
|
124
|
+
tool_name=name,
|
|
125
|
+
statement_id=statement_response.statement_id,
|
|
126
|
+
error=error_msg,
|
|
127
|
+
)
|
|
128
|
+
return f"Error: {error_msg}"
|
|
129
|
+
|
|
130
|
+
# Extract results
|
|
131
|
+
result = statement_response.result
|
|
132
|
+
if result is None:
|
|
133
|
+
logger.debug(
|
|
134
|
+
"SQL statement executed successfully with no results",
|
|
135
|
+
tool_name=name,
|
|
136
|
+
statement_id=statement_response.statement_id,
|
|
137
|
+
)
|
|
138
|
+
return "SQL statement executed successfully (no results returned)"
|
|
139
|
+
|
|
140
|
+
# Format results
|
|
141
|
+
if result.data_array:
|
|
142
|
+
rows = result.data_array
|
|
143
|
+
row_count = len(rows)
|
|
144
|
+
|
|
145
|
+
# Get column names if available
|
|
146
|
+
columns = []
|
|
147
|
+
if (
|
|
148
|
+
statement_response.manifest
|
|
149
|
+
and statement_response.manifest.schema
|
|
150
|
+
and statement_response.manifest.schema.columns
|
|
151
|
+
):
|
|
152
|
+
columns = [
|
|
153
|
+
col.name for col in statement_response.manifest.schema.columns
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
logger.info(
|
|
157
|
+
"SQL query returned results",
|
|
158
|
+
tool_name=name,
|
|
159
|
+
row_count=row_count,
|
|
160
|
+
column_count=len(columns),
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Format as a simple text table
|
|
164
|
+
result_lines = []
|
|
165
|
+
if columns:
|
|
166
|
+
result_lines.append(" | ".join(columns))
|
|
167
|
+
result_lines.append("-" * (len(" | ".join(columns))))
|
|
168
|
+
|
|
169
|
+
for row in rows:
|
|
170
|
+
result_lines.append(
|
|
171
|
+
" | ".join(
|
|
172
|
+
str(cell) if cell is not None else "NULL" for cell in row
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Add summary
|
|
177
|
+
result_lines.append("")
|
|
178
|
+
result_lines.append(
|
|
179
|
+
f"({row_count} row{'s' if row_count != 1 else ''} returned)"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
return "\n".join(result_lines)
|
|
183
|
+
else:
|
|
184
|
+
logger.debug(
|
|
185
|
+
"SQL statement executed successfully with empty result set",
|
|
186
|
+
tool_name=name,
|
|
187
|
+
statement_id=statement_response.statement_id,
|
|
188
|
+
)
|
|
189
|
+
return "SQL statement executed successfully (empty result set)"
|
|
190
|
+
|
|
191
|
+
except Exception as e:
|
|
192
|
+
error_msg = f"Failed to execute SQL: {str(e)}"
|
|
193
|
+
logger.error(
|
|
194
|
+
"SQL execution failed with exception",
|
|
195
|
+
tool_name=name,
|
|
196
|
+
warehouse_id=warehouse_id,
|
|
197
|
+
error=str(e),
|
|
198
|
+
exc_info=True,
|
|
199
|
+
)
|
|
200
|
+
return f"Error: {error_msg}"
|
|
201
|
+
|
|
202
|
+
return execute_statement_tool
|
dao_ai/tools/time.py
CHANGED
|
@@ -20,7 +20,7 @@ def current_time_tool() -> str:
|
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
22
|
time_now: str = datetime.now().isoformat()
|
|
23
|
-
logger.
|
|
23
|
+
logger.trace("Current time retrieved", time=time_now)
|
|
24
24
|
return time_now
|
|
25
25
|
|
|
26
26
|
|
|
@@ -38,7 +38,9 @@ def time_in_timezone_tool(timezone_name: str) -> str:
|
|
|
38
38
|
try:
|
|
39
39
|
tz = pytz.timezone(timezone_name)
|
|
40
40
|
time_in_tz = datetime.now(tz)
|
|
41
|
-
logger.
|
|
41
|
+
logger.trace(
|
|
42
|
+
"Time in timezone retrieved", timezone=timezone_name, time=str(time_in_tz)
|
|
43
|
+
)
|
|
42
44
|
return f"{time_in_tz.strftime('%Y-%m-%d %H:%M:%S %Z')} ({timezone_name})"
|
|
43
45
|
except Exception:
|
|
44
46
|
return f"Error: Invalid timezone '{timezone_name}'. Use format like 'US/Eastern' or 'Europe/London'"
|
|
@@ -77,7 +79,12 @@ def time_difference_tool(datetime1: str, datetime2: str) -> str:
|
|
|
77
79
|
parts.append(f"{seconds} second{'s' if seconds != 1 else ''}")
|
|
78
80
|
|
|
79
81
|
result = ", ".join(parts) if parts else "0 seconds"
|
|
80
|
-
logger.
|
|
82
|
+
logger.trace(
|
|
83
|
+
"Time difference calculated",
|
|
84
|
+
datetime1=datetime1,
|
|
85
|
+
datetime2=datetime2,
|
|
86
|
+
result=result,
|
|
87
|
+
)
|
|
81
88
|
return result
|
|
82
89
|
|
|
83
90
|
except Exception as e:
|
|
@@ -112,7 +119,14 @@ def add_time_tool(
|
|
|
112
119
|
new_dt = base_dt + timedelta(days=days, hours=hours, minutes=minutes)
|
|
113
120
|
|
|
114
121
|
result = new_dt.isoformat()
|
|
115
|
-
logger.
|
|
122
|
+
logger.trace(
|
|
123
|
+
"Time added to datetime",
|
|
124
|
+
base_datetime=base_datetime,
|
|
125
|
+
days=days,
|
|
126
|
+
hours=hours,
|
|
127
|
+
minutes=minutes,
|
|
128
|
+
result=result,
|
|
129
|
+
)
|
|
116
130
|
return result
|
|
117
131
|
|
|
118
132
|
except Exception as e:
|
|
@@ -164,7 +178,7 @@ def is_business_hours_tool(
|
|
|
164
178
|
elif not is_work_hours:
|
|
165
179
|
result += " (Outside 9 AM - 5 PM)"
|
|
166
180
|
|
|
167
|
-
logger.
|
|
181
|
+
logger.trace("Business hours check completed", result=result)
|
|
168
182
|
return result
|
|
169
183
|
|
|
170
184
|
except Exception as e:
|
|
@@ -199,7 +213,12 @@ def format_time_tool(datetime_str: str, format_type: str = "readable") -> str:
|
|
|
199
213
|
return f"Error: Unknown format type. Use: {', '.join(formats.keys())}"
|
|
200
214
|
|
|
201
215
|
result = dt.strftime(formats[format_type])
|
|
202
|
-
logger.
|
|
216
|
+
logger.trace(
|
|
217
|
+
"Datetime formatted",
|
|
218
|
+
datetime_str=datetime_str,
|
|
219
|
+
format_type=format_type,
|
|
220
|
+
result=result,
|
|
221
|
+
)
|
|
203
222
|
return result
|
|
204
223
|
|
|
205
224
|
except Exception as e:
|
|
@@ -266,7 +285,11 @@ def time_until_tool(target_datetime: str) -> str:
|
|
|
266
285
|
else "Less than 1 minute remaining"
|
|
267
286
|
)
|
|
268
287
|
|
|
269
|
-
logger.
|
|
288
|
+
logger.trace(
|
|
289
|
+
"Time until target calculated",
|
|
290
|
+
target_datetime=target_datetime,
|
|
291
|
+
result=result,
|
|
292
|
+
)
|
|
270
293
|
return result
|
|
271
294
|
|
|
272
295
|
except Exception as e:
|