dao-ai 0.1.1__py3-none-any.whl → 0.1.3__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 (47) hide show
  1. dao_ai/agent_as_code.py +2 -5
  2. dao_ai/cli.py +65 -15
  3. dao_ai/config.py +672 -218
  4. dao_ai/genie/cache/core.py +6 -2
  5. dao_ai/genie/cache/lru.py +29 -11
  6. dao_ai/genie/cache/semantic.py +95 -44
  7. dao_ai/hooks/core.py +5 -5
  8. dao_ai/logging.py +56 -0
  9. dao_ai/memory/core.py +61 -44
  10. dao_ai/memory/databricks.py +54 -41
  11. dao_ai/memory/postgres.py +77 -36
  12. dao_ai/middleware/assertions.py +45 -17
  13. dao_ai/middleware/core.py +13 -7
  14. dao_ai/middleware/guardrails.py +30 -25
  15. dao_ai/middleware/human_in_the_loop.py +9 -5
  16. dao_ai/middleware/message_validation.py +61 -29
  17. dao_ai/middleware/summarization.py +16 -11
  18. dao_ai/models.py +172 -69
  19. dao_ai/nodes.py +148 -19
  20. dao_ai/optimization.py +26 -16
  21. dao_ai/orchestration/core.py +15 -8
  22. dao_ai/orchestration/supervisor.py +22 -8
  23. dao_ai/orchestration/swarm.py +57 -12
  24. dao_ai/prompts.py +17 -17
  25. dao_ai/providers/databricks.py +365 -155
  26. dao_ai/state.py +24 -6
  27. dao_ai/tools/__init__.py +2 -0
  28. dao_ai/tools/agent.py +1 -3
  29. dao_ai/tools/core.py +7 -7
  30. dao_ai/tools/email.py +29 -77
  31. dao_ai/tools/genie.py +18 -13
  32. dao_ai/tools/mcp.py +223 -156
  33. dao_ai/tools/python.py +5 -2
  34. dao_ai/tools/search.py +1 -1
  35. dao_ai/tools/slack.py +21 -9
  36. dao_ai/tools/sql.py +202 -0
  37. dao_ai/tools/time.py +30 -7
  38. dao_ai/tools/unity_catalog.py +129 -86
  39. dao_ai/tools/vector_search.py +318 -244
  40. dao_ai/utils.py +15 -10
  41. dao_ai-0.1.3.dist-info/METADATA +455 -0
  42. dao_ai-0.1.3.dist-info/RECORD +64 -0
  43. dao_ai-0.1.1.dist-info/METADATA +0 -1878
  44. dao_ai-0.1.1.dist-info/RECORD +0 -62
  45. {dao_ai-0.1.1.dist-info → dao_ai-0.1.3.dist-info}/WHEEL +0 -0
  46. {dao_ai-0.1.1.dist-info → dao_ai-0.1.3.dist-info}/entry_points.txt +0 -0
  47. {dao_ai-0.1.1.dist-info → dao_ai-0.1.3.dist-info}/licenses/LICENSE +0 -0
@@ -1,17 +1,17 @@
1
- from typing import Any, Dict, Optional, Sequence, Union
1
+ from typing import Any, Dict, Optional, Sequence, Set
2
2
 
3
3
  from databricks.sdk import WorkspaceClient
4
- from databricks.sdk.service.catalog import PermissionsChange, Privilege
4
+ from databricks.sdk.service.catalog import FunctionInfo, PermissionsChange, Privilege
5
5
  from databricks_langchain import DatabricksFunctionClient, UCFunctionToolkit
6
6
  from langchain_core.runnables.base import RunnableLike
7
7
  from langchain_core.tools import StructuredTool
8
8
  from loguru import logger
9
+ from pydantic import BaseModel
9
10
  from unitycatalog.ai.core.base import FunctionExecutionResult
10
11
 
11
12
  from dao_ai.config import (
12
13
  AnyVariable,
13
14
  CompositeVariableModel,
14
- ToolModel,
15
15
  UnityCatalogFunctionModel,
16
16
  value_of,
17
17
  )
@@ -34,36 +34,39 @@ def create_uc_tools(
34
34
  Returns:
35
35
  A sequence of BaseTool objects that wrap the specified UC functions
36
36
  """
37
+ original_function_model: UnityCatalogFunctionModel | None = None
38
+ workspace_client: WorkspaceClient | None = None
39
+ function_name: str
37
40
 
38
- logger.debug(f"create_uc_tools: {function}")
39
-
40
- original_function_model = None
41
41
  if isinstance(function, UnityCatalogFunctionModel):
42
42
  original_function_model = function
43
- function_name = function.full_name
43
+ function_name = function.resource.full_name
44
+ workspace_client = function.resource.workspace_client
44
45
  else:
45
46
  function_name = function
46
47
 
48
+ logger.trace("Creating UC tools", function_name=function_name)
49
+
47
50
  # Determine which tools to create
51
+ tools: list[RunnableLike]
48
52
  if original_function_model and original_function_model.partial_args:
49
- logger.debug("Found partial_args, creating custom tool with partial arguments")
50
- # Create a ToolModel wrapper for the with_partial_args function
51
- tool_model = ToolModel(
52
- name=original_function_model.name, function=original_function_model
53
+ logger.debug(
54
+ "Creating custom tool with partial arguments", function_name=function_name
53
55
  )
54
-
55
- # Use with_partial_args to create the authenticated tool
56
- tools = [with_partial_args(tool_model, original_function_model.partial_args)]
56
+ # Use with_partial_args directly with UnityCatalogFunctionModel
57
+ tools = [with_partial_args(original_function_model)]
57
58
  else:
58
59
  # Fallback to standard UC toolkit approach
59
- client: DatabricksFunctionClient = DatabricksFunctionClient()
60
+ client: DatabricksFunctionClient = DatabricksFunctionClient(
61
+ client=workspace_client
62
+ )
60
63
 
61
64
  toolkit: UCFunctionToolkit = UCFunctionToolkit(
62
65
  function_names=[function_name], client=client
63
66
  )
64
67
 
65
68
  tools = toolkit.tools or []
66
- logger.debug(f"Retrieved tools: {tools}")
69
+ logger.trace("Retrieved tools", tools_count=len(tools))
67
70
 
68
71
  # HITL is now handled at middleware level via HumanInTheLoopMiddleware
69
72
  return list(tools)
@@ -72,7 +75,7 @@ def create_uc_tools(
72
75
  def _execute_uc_function(
73
76
  client: DatabricksFunctionClient,
74
77
  function_name: str,
75
- partial_args: Dict[str, str] = None,
78
+ partial_args: Optional[Dict[str, str]] = None,
76
79
  **kwargs: Any,
77
80
  ) -> str:
78
81
  """Execute Unity Catalog function with partial args and provided parameters."""
@@ -84,7 +87,9 @@ def _execute_uc_function(
84
87
  all_params.update(kwargs)
85
88
 
86
89
  logger.debug(
87
- f"Calling UC function {function_name} with parameters: {list(all_params.keys())}"
90
+ "Calling UC function",
91
+ function_name=function_name,
92
+ parameters=list(all_params.keys()),
88
93
  )
89
94
 
90
95
  result: FunctionExecutionResult = client.execute_function(
@@ -93,11 +98,19 @@ def _execute_uc_function(
93
98
 
94
99
  # Handle errors and extract result
95
100
  if result.error:
96
- logger.error(f"Unity Catalog function error: {result.error}")
101
+ logger.error(
102
+ "Unity Catalog function error",
103
+ function_name=function_name,
104
+ error=result.error,
105
+ )
97
106
  raise RuntimeError(f"Function execution failed: {result.error}")
98
107
 
99
108
  result_value: str = result.value if result.value is not None else str(result)
100
- logger.debug(f"UC function result: {result_value}")
109
+ logger.trace(
110
+ "UC function result",
111
+ function_name=function_name,
112
+ result_length=len(str(result_value)),
113
+ )
101
114
  return result_value
102
115
 
103
116
 
@@ -116,21 +129,29 @@ def _grant_function_permissions(
116
129
  """
117
130
  try:
118
131
  # Initialize workspace client
119
- workspace_client = WorkspaceClient(host=host) if host else WorkspaceClient()
132
+ workspace_client: WorkspaceClient = (
133
+ WorkspaceClient(host=host) if host else WorkspaceClient()
134
+ )
120
135
 
121
136
  # Parse the function name to get catalog and schema
122
- parts = function_name.split(".")
137
+ parts: list[str] = function_name.split(".")
123
138
  if len(parts) != 3:
124
139
  logger.warning(
125
- f"Invalid function name format: {function_name}. Expected catalog.schema.function"
140
+ "Invalid function name format, expected catalog.schema.function",
141
+ function_name=function_name,
126
142
  )
127
143
  return
128
144
 
145
+ catalog_name: str
146
+ schema_name: str
147
+ func_name: str
129
148
  catalog_name, schema_name, func_name = parts
130
- schema_full_name = f"{catalog_name}.{schema_name}"
149
+ schema_full_name: str = f"{catalog_name}.{schema_name}"
131
150
 
132
151
  logger.debug(
133
- f"Granting comprehensive permissions on function {function_name} to principal {client_id}"
152
+ "Granting comprehensive permissions",
153
+ function_name=function_name,
154
+ principal=client_id,
134
155
  )
135
156
 
136
157
  # 1. Grant EXECUTE permission on the function
@@ -142,9 +163,13 @@ def _grant_function_permissions(
142
163
  PermissionsChange(principal=client_id, add=[Privilege.EXECUTE])
143
164
  ],
144
165
  )
145
- logger.debug(f"Granted EXECUTE on function {function_name}")
166
+ logger.trace("Granted EXECUTE permission", function_name=function_name)
146
167
  except Exception as e:
147
- logger.warning(f"Failed to grant EXECUTE on function {function_name}: {e}")
168
+ logger.warning(
169
+ "Failed to grant EXECUTE permission",
170
+ function_name=function_name,
171
+ error=str(e),
172
+ )
148
173
 
149
174
  # 2. Grant USE_SCHEMA permission on the schema
150
175
  try:
@@ -158,10 +183,12 @@ def _grant_function_permissions(
158
183
  )
159
184
  ],
160
185
  )
161
- logger.debug(f"Granted USE_SCHEMA on schema {schema_full_name}")
186
+ logger.trace("Granted USE_SCHEMA permission", schema=schema_full_name)
162
187
  except Exception as e:
163
188
  logger.warning(
164
- f"Failed to grant USE_SCHEMA on schema {schema_full_name}: {e}"
189
+ "Failed to grant USE_SCHEMA permission",
190
+ schema=schema_full_name,
191
+ error=str(e),
165
192
  )
166
193
 
167
194
  # 3. Grant USE_CATALOG and BROWSE permissions on the catalog
@@ -176,25 +203,34 @@ def _grant_function_permissions(
176
203
  )
177
204
  ],
178
205
  )
179
- logger.debug(f"Granted USE_CATALOG and BROWSE on catalog {catalog_name}")
206
+ logger.trace(
207
+ "Granted USE_CATALOG and BROWSE permissions", catalog=catalog_name
208
+ )
180
209
  except Exception as e:
181
210
  logger.warning(
182
- f"Failed to grant catalog permissions on {catalog_name}: {e}"
211
+ "Failed to grant catalog permissions",
212
+ catalog=catalog_name,
213
+ error=str(e),
183
214
  )
184
215
 
185
216
  logger.debug(
186
- f"Successfully granted comprehensive permissions on {function_name} to {client_id}"
217
+ "Successfully granted comprehensive permissions",
218
+ function_name=function_name,
219
+ principal=client_id,
187
220
  )
188
221
 
189
222
  except Exception as e:
190
223
  logger.warning(
191
- f"Failed to grant permissions on function {function_name} to {client_id}: {e}"
224
+ "Failed to grant permissions",
225
+ function_name=function_name,
226
+ principal=client_id,
227
+ error=str(e),
192
228
  )
193
229
  # Don't fail the tool creation if permission granting fails
194
230
  pass
195
231
 
196
232
 
197
- def _create_filtered_schema(original_schema: type, exclude_fields: set[str]) -> type:
233
+ def _create_filtered_schema(original_schema: type, exclude_fields: Set[str]) -> type:
198
234
  """
199
235
  Create a new Pydantic model that excludes specified fields from the original schema.
200
236
 
@@ -205,23 +241,27 @@ def _create_filtered_schema(original_schema: type, exclude_fields: set[str]) ->
205
241
  Returns:
206
242
  A new Pydantic model class with the specified fields removed
207
243
  """
208
- from pydantic import BaseModel, Field, create_model
209
- from pydantic.fields import PydanticUndefined
244
+ from pydantic import Field, create_model
245
+ from pydantic.fields import FieldInfo, PydanticUndefined
210
246
 
211
247
  try:
212
248
  # Get the original model's fields (Pydantic v2)
213
- original_fields = original_schema.model_fields
214
- filtered_field_definitions = {}
249
+ original_fields: dict[str, FieldInfo] = original_schema.model_fields
250
+ filtered_field_definitions: dict[str, tuple[type, FieldInfo]] = {}
215
251
 
216
- for name, field in original_fields.items():
217
- if name not in exclude_fields:
252
+ field_name: str
253
+ field: FieldInfo
254
+ for field_name, field in original_fields.items():
255
+ if field_name not in exclude_fields:
218
256
  # Reconstruct the field definition for create_model
219
- field_type = field.annotation
220
- field_default = (
257
+ field_type: type = field.annotation
258
+ field_default: Any = (
221
259
  field.default if field.default is not PydanticUndefined else ...
222
260
  )
223
- field_info = Field(default=field_default, description=field.description)
224
- filtered_field_definitions[name] = (field_type, field_info)
261
+ field_info: FieldInfo = Field(
262
+ default=field_default, description=field.description
263
+ )
264
+ filtered_field_definitions[field_name] = (field_type, field_info)
225
265
 
226
266
  # If no fields remain after filtering, return a generic empty schema
227
267
  if not filtered_field_definitions:
@@ -234,18 +274,18 @@ def _create_filtered_schema(original_schema: type, exclude_fields: set[str]) ->
234
274
  return EmptySchema
235
275
 
236
276
  # Create the new model dynamically
237
- model_name = f"Filtered{original_schema.__name__}"
238
- docstring = getattr(
277
+ model_name: str = f"Filtered{original_schema.__name__}"
278
+ docstring: str = getattr(
239
279
  original_schema, "__doc__", "Filtered Unity Catalog function parameters."
240
280
  )
241
281
 
242
- filtered_model = create_model(
282
+ filtered_model: type[BaseModel] = create_model(
243
283
  model_name, __doc__=docstring, **filtered_field_definitions
244
284
  )
245
285
  return filtered_model
246
286
 
247
287
  except Exception as e:
248
- logger.warning(f"Failed to create filtered schema: {e}")
288
+ logger.warning("Failed to create filtered schema", error=str(e))
249
289
 
250
290
  # Fallback to generic schema
251
291
  class GenericFilteredSchema(BaseModel):
@@ -257,8 +297,7 @@ def _create_filtered_schema(original_schema: type, exclude_fields: set[str]) ->
257
297
 
258
298
 
259
299
  def with_partial_args(
260
- tool: Union[ToolModel, Dict[str, Any]],
261
- partial_args: dict[str, AnyVariable] = {},
300
+ uc_function: UnityCatalogFunctionModel,
262
301
  ) -> StructuredTool:
263
302
  """
264
303
  Create a Unity Catalog tool with partial arguments pre-filled.
@@ -267,12 +306,8 @@ def with_partial_args(
267
306
  already resolved, so the caller only needs to provide the remaining parameters.
268
307
 
269
308
  Args:
270
- tool: ToolModel containing the Unity Catalog function configuration
271
- partial_args: Dictionary of arguments to pre-fill in the tool.
272
- Supports:
273
- - client_id, client_secret: OAuth credentials directly
274
- - service_principal: ServicePrincipalModel with client_id and client_secret
275
- - host or workspace_host: Databricks workspace host
309
+ uc_function: UnityCatalogFunctionModel containing the function configuration
310
+ and partial_args to pre-fill.
276
311
 
277
312
  Returns:
278
313
  StructuredTool: A LangChain tool with partial arguments pre-filled
@@ -281,10 +316,12 @@ def with_partial_args(
281
316
 
282
317
  from dao_ai.config import ServicePrincipalModel
283
318
 
284
- logger.debug(f"with_partial_args: {tool}")
319
+ partial_args: dict[str, AnyVariable] = uc_function.partial_args or {}
285
320
 
286
321
  # Convert dict-based variables to CompositeVariableModel and resolve their values
287
322
  resolved_args: dict[str, Any] = {}
323
+ k: str
324
+ v: AnyVariable
288
325
  for k, v in partial_args.items():
289
326
  if isinstance(v, dict):
290
327
  resolved_args[k] = value_of(CompositeVariableModel(**v))
@@ -293,7 +330,7 @@ def with_partial_args(
293
330
 
294
331
  # Handle service_principal - expand into client_id and client_secret
295
332
  if "service_principal" in resolved_args:
296
- sp = resolved_args.pop("service_principal")
333
+ sp: Any = resolved_args.pop("service_principal")
297
334
  if isinstance(sp, dict):
298
335
  sp = ServicePrincipalModel(**sp)
299
336
  if isinstance(sp, ServicePrincipalModel):
@@ -316,17 +353,17 @@ def with_partial_args(
316
353
  if host:
317
354
  resolved_args["host"] = host
318
355
 
319
- logger.debug(f"Resolved partial args: {resolved_args.keys()}")
356
+ # Get function info from the resource
357
+ function_name: str = uc_function.resource.full_name
358
+ tool_name: str = uc_function.resource.name or function_name.replace(".", "_")
359
+ workspace_client: WorkspaceClient = uc_function.resource.workspace_client
320
360
 
321
- if isinstance(tool, dict):
322
- tool = ToolModel(**tool)
323
-
324
- unity_catalog_function = tool.function
325
- if isinstance(unity_catalog_function, dict):
326
- unity_catalog_function = UnityCatalogFunctionModel(**unity_catalog_function)
327
-
328
- function_name: str = unity_catalog_function.full_name
329
- logger.debug(f"Creating UC tool with partial args for: {function_name}")
361
+ logger.debug(
362
+ "Creating UC tool with partial args",
363
+ function_name=function_name,
364
+ tool_name=tool_name,
365
+ partial_args=list(resolved_args.keys()),
366
+ )
330
367
 
331
368
  # Grant permissions if we have credentials
332
369
  if "client_id" in resolved_args:
@@ -335,36 +372,44 @@ def with_partial_args(
335
372
  try:
336
373
  _grant_function_permissions(function_name, client_id, host)
337
374
  except Exception as e:
338
- logger.warning(f"Failed to grant permissions: {e}")
375
+ logger.warning(
376
+ "Failed to grant permissions", function_name=function_name, error=str(e)
377
+ )
339
378
 
340
- # Create the client for function execution
341
- client: DatabricksFunctionClient = DatabricksFunctionClient()
379
+ # Create the client for function execution using the resource's workspace client
380
+ client: DatabricksFunctionClient = DatabricksFunctionClient(client=workspace_client)
342
381
 
343
382
  # Try to get the function schema for better tool definition
383
+ schema_model: type[BaseModel]
384
+ tool_description: str
344
385
  try:
345
- function_info = client.get_function(function_name)
386
+ function_info: FunctionInfo = client.get_function(function_name)
346
387
  schema_info = generate_function_input_params_schema(function_info)
347
388
  tool_description = (
348
389
  function_info.comment or f"Unity Catalog function: {function_name}"
349
390
  )
350
391
 
351
- logger.debug(
352
- f"Generated schema for function {function_name}: {schema_info.pydantic_model}"
392
+ logger.trace(
393
+ "Generated function schema",
394
+ function_name=function_name,
395
+ schema=schema_info.pydantic_model.__name__,
353
396
  )
354
- logger.debug(f"Tool description: {tool_description}")
355
397
 
356
398
  # Create a modified schema that excludes partial args
357
- original_schema = schema_info.pydantic_model
399
+ original_schema: type = schema_info.pydantic_model
358
400
  schema_model = _create_filtered_schema(original_schema, resolved_args.keys())
359
- logger.debug(
360
- f"Filtered schema excludes partial args: {list(resolved_args.keys())}"
401
+ logger.trace(
402
+ "Filtered schema to exclude partial args",
403
+ function_name=function_name,
404
+ excluded_args=list(resolved_args.keys()),
361
405
  )
362
406
 
363
407
  except Exception as e:
364
- logger.warning(f"Could not introspect function {function_name}: {e}")
365
- # Fallback to a generic schema
366
- from pydantic import BaseModel
408
+ logger.warning(
409
+ "Could not introspect function", function_name=function_name, error=str(e)
410
+ )
367
411
 
412
+ # Fallback to a generic schema
368
413
  class GenericUCParams(BaseModel):
369
414
  """Generic parameters for Unity Catalog function."""
370
415
 
@@ -384,14 +429,12 @@ def with_partial_args(
384
429
  )
385
430
 
386
431
  # Set the function name for the decorator
387
- uc_function_wrapper.__name__ = tool.name or function_name.replace(".", "_")
432
+ uc_function_wrapper.__name__ = tool_name
388
433
 
389
434
  # Create the tool using LangChain's StructuredTool
390
- from langchain_core.tools import StructuredTool
391
-
392
- partial_tool = StructuredTool.from_function(
435
+ partial_tool: StructuredTool = StructuredTool.from_function(
393
436
  func=uc_function_wrapper,
394
- name=tool.name or function_name.replace(".", "_"),
437
+ name=tool_name,
395
438
  description=tool_description,
396
439
  args_schema=schema_model,
397
440
  )