dao-ai 0.0.25__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.
Files changed (63) hide show
  1. dao_ai/__init__.py +29 -0
  2. dao_ai/agent_as_code.py +5 -5
  3. dao_ai/cli.py +245 -40
  4. dao_ai/config.py +1863 -338
  5. dao_ai/genie/__init__.py +38 -0
  6. dao_ai/genie/cache/__init__.py +43 -0
  7. dao_ai/genie/cache/base.py +72 -0
  8. dao_ai/genie/cache/core.py +79 -0
  9. dao_ai/genie/cache/lru.py +347 -0
  10. dao_ai/genie/cache/semantic.py +970 -0
  11. dao_ai/genie/core.py +35 -0
  12. dao_ai/graph.py +27 -228
  13. dao_ai/hooks/__init__.py +9 -6
  14. dao_ai/hooks/core.py +27 -195
  15. dao_ai/logging.py +56 -0
  16. dao_ai/memory/__init__.py +10 -0
  17. dao_ai/memory/core.py +65 -30
  18. dao_ai/memory/databricks.py +402 -0
  19. dao_ai/memory/postgres.py +79 -38
  20. dao_ai/messages.py +6 -4
  21. dao_ai/middleware/__init__.py +125 -0
  22. dao_ai/middleware/assertions.py +806 -0
  23. dao_ai/middleware/base.py +50 -0
  24. dao_ai/middleware/core.py +67 -0
  25. dao_ai/middleware/guardrails.py +420 -0
  26. dao_ai/middleware/human_in_the_loop.py +232 -0
  27. dao_ai/middleware/message_validation.py +586 -0
  28. dao_ai/middleware/summarization.py +197 -0
  29. dao_ai/models.py +1306 -114
  30. dao_ai/nodes.py +261 -166
  31. dao_ai/optimization.py +674 -0
  32. dao_ai/orchestration/__init__.py +52 -0
  33. dao_ai/orchestration/core.py +294 -0
  34. dao_ai/orchestration/supervisor.py +278 -0
  35. dao_ai/orchestration/swarm.py +271 -0
  36. dao_ai/prompts.py +128 -31
  37. dao_ai/providers/databricks.py +645 -172
  38. dao_ai/state.py +157 -21
  39. dao_ai/tools/__init__.py +13 -5
  40. dao_ai/tools/agent.py +1 -3
  41. dao_ai/tools/core.py +64 -11
  42. dao_ai/tools/email.py +232 -0
  43. dao_ai/tools/genie.py +144 -295
  44. dao_ai/tools/mcp.py +220 -133
  45. dao_ai/tools/memory.py +50 -0
  46. dao_ai/tools/python.py +9 -14
  47. dao_ai/tools/search.py +14 -0
  48. dao_ai/tools/slack.py +22 -10
  49. dao_ai/tools/sql.py +202 -0
  50. dao_ai/tools/time.py +30 -7
  51. dao_ai/tools/unity_catalog.py +165 -88
  52. dao_ai/tools/vector_search.py +360 -40
  53. dao_ai/utils.py +218 -16
  54. dao_ai-0.1.2.dist-info/METADATA +455 -0
  55. dao_ai-0.1.2.dist-info/RECORD +64 -0
  56. {dao_ai-0.0.25.dist-info → dao_ai-0.1.2.dist-info}/WHEEL +1 -1
  57. dao_ai/chat_models.py +0 -204
  58. dao_ai/guardrails.py +0 -112
  59. dao_ai/tools/human_in_the_loop.py +0 -100
  60. dao_ai-0.0.25.dist-info/METADATA +0 -1165
  61. dao_ai-0.0.25.dist-info/RECORD +0 -41
  62. {dao_ai-0.0.25.dist-info → dao_ai-0.1.2.dist-info}/entry_points.txt +0 -0
  63. {dao_ai-0.0.25.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.debug(f"Current time: {time_now}")
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.debug(f"Time in {timezone_name}: {time_in_tz}")
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.debug(f"Time difference between {datetime1} and {datetime2}: {result}")
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.debug(f"Added {days}d {hours}h {minutes}m to {base_datetime}: {result}")
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.debug(f"Business hours check: {result}")
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.debug(f"Formatted {datetime_str} as {format_type}: {result}")
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.debug(f"Time until {target_datetime}: {result}")
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: