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.
Files changed (63) hide show
  1. dao_ai/__init__.py +29 -0
  2. dao_ai/agent_as_code.py +2 -5
  3. dao_ai/cli.py +245 -40
  4. dao_ai/config.py +1491 -370
  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 -253
  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 +245 -159
  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 +573 -601
  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 -294
  44. dao_ai/tools/mcp.py +223 -155
  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 +331 -221
  53. dao_ai/utils.py +166 -20
  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/chat_models.py +0 -204
  57. dao_ai/guardrails.py +0 -112
  58. dao_ai/tools/human_in_the_loop.py +0 -100
  59. dao_ai-0.0.28.dist-info/METADATA +0 -1168
  60. dao_ai-0.0.28.dist-info/RECORD +0 -41
  61. {dao_ai-0.0.28.dist-info → dao_ai-0.1.2.dist-info}/WHEEL +0 -0
  62. {dao_ai-0.0.28.dist-info → dao_ai-0.1.2.dist-info}/entry_points.txt +0 -0
  63. {dao_ai-0.0.28.dist-info → dao_ai-0.1.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,232 @@
1
+ """
2
+ Human-in-the-loop middleware for DAO AI agents.
3
+
4
+ This module provides utilities for creating HITL middleware from DAO AI configuration.
5
+ It re-exports LangChain's built-in HumanInTheLoopMiddleware.
6
+
7
+ LangChain's HumanInTheLoopMiddleware automatically:
8
+ - Pauses agent execution for human approval of tool calls
9
+ - Allows humans to approve, edit, or reject tool calls
10
+ - Uses LangGraph's interrupt mechanism for persistence across pauses
11
+
12
+ Example:
13
+ from dao_ai.middleware import create_human_in_the_loop_middleware
14
+
15
+ middleware = create_human_in_the_loop_middleware(
16
+ interrupt_on={"send_email": True, "delete_record": True},
17
+ )
18
+ """
19
+
20
+ from typing import Any, Sequence
21
+
22
+ from langchain.agents.middleware import HumanInTheLoopMiddleware
23
+ from langchain.agents.middleware.human_in_the_loop import (
24
+ Action,
25
+ ActionRequest,
26
+ ApproveDecision,
27
+ Decision,
28
+ DecisionType,
29
+ EditDecision,
30
+ HITLRequest,
31
+ HITLResponse,
32
+ InterruptOnConfig,
33
+ RejectDecision,
34
+ ReviewConfig,
35
+ )
36
+ from loguru import logger
37
+
38
+ from dao_ai.config import HumanInTheLoopModel, ToolModel
39
+
40
+ __all__ = [
41
+ # LangChain middleware
42
+ "HumanInTheLoopMiddleware",
43
+ # LangChain HITL types
44
+ "Action",
45
+ "ActionRequest",
46
+ "ApproveDecision",
47
+ "Decision",
48
+ "DecisionType",
49
+ "EditDecision",
50
+ "HITLRequest",
51
+ "HITLResponse",
52
+ "InterruptOnConfig",
53
+ "RejectDecision",
54
+ "ReviewConfig",
55
+ # DAO AI helper functions and models
56
+ "create_human_in_the_loop_middleware",
57
+ "create_hitl_middleware_from_tool_models",
58
+ ]
59
+
60
+
61
+ def _hitl_config_to_allowed_decisions(
62
+ hitl_config: HumanInTheLoopModel,
63
+ ) -> list[DecisionType]:
64
+ """
65
+ Extract allowed decisions from HumanInTheLoopModel.
66
+
67
+ LangChain's HumanInTheLoopMiddleware supports 3 decision types:
68
+ - "approve": Execute tool with original arguments
69
+ - "edit": Modify arguments before execution
70
+ - "reject": Skip execution with optional feedback message
71
+
72
+ Args:
73
+ hitl_config: HumanInTheLoopModel with allowed_decisions
74
+
75
+ Returns:
76
+ List of allowed decision types (e.g., ["approve", "edit", "reject"])
77
+ """
78
+ return hitl_config.allowed_decisions # type: ignore
79
+
80
+
81
+ def _config_to_interrupt_on_entry(
82
+ config: HumanInTheLoopModel | bool,
83
+ ) -> dict[str, Any] | bool:
84
+ """
85
+ Convert a HITL config value to interrupt_on entry format.
86
+
87
+ Args:
88
+ config: HumanInTheLoopModel, True, or False
89
+
90
+ Returns:
91
+ dict with allowed_decisions and optional description, True, or False
92
+ """
93
+ if config is False:
94
+ return False
95
+ if config is True:
96
+ return {"allowed_decisions": ["approve", "edit", "reject"]}
97
+ if isinstance(config, HumanInTheLoopModel):
98
+ interrupt_entry: dict[str, Any] = {
99
+ "allowed_decisions": _hitl_config_to_allowed_decisions(config)
100
+ }
101
+ # If review_prompt is provided, use it as the description
102
+ if config.review_prompt is not None:
103
+ interrupt_entry["description"] = config.review_prompt
104
+ return interrupt_entry
105
+
106
+ logger.warning(
107
+ "Unknown HITL config type, defaulting to True",
108
+ config_type=type(config).__name__,
109
+ )
110
+ return True
111
+
112
+
113
+ def create_human_in_the_loop_middleware(
114
+ interrupt_on: dict[str, HumanInTheLoopModel | bool | dict[str, Any]],
115
+ description_prefix: str = "Tool execution pending approval",
116
+ ) -> HumanInTheLoopMiddleware:
117
+ """
118
+ Create a HumanInTheLoopMiddleware instance.
119
+
120
+ Factory function for creating LangChain's built-in HumanInTheLoopMiddleware.
121
+ Accepts HumanInTheLoopModel, bool, or raw dict configurations per tool.
122
+
123
+ Note: This middleware requires a checkpointer to be configured on the agent.
124
+
125
+ Args:
126
+ interrupt_on: Dictionary mapping tool names to HITL configuration.
127
+ Each tool can be configured with:
128
+ - HumanInTheLoopModel: Full configuration with custom settings
129
+ - True: Enable HITL with default settings (approve, edit, reject)
130
+ - False: Disable HITL for this tool
131
+ - dict: Raw interrupt_on config (e.g., {"allowed_decisions": [...]})
132
+ description_prefix: Message prefix shown when pausing for review
133
+
134
+ Returns:
135
+ HumanInTheLoopMiddleware configured with the specified parameters
136
+
137
+ Example:
138
+ from dao_ai.config import HumanInTheLoopModel
139
+
140
+ middleware = create_human_in_the_loop_middleware(
141
+ interrupt_on={
142
+ "send_email": HumanInTheLoopModel(review_prompt="Review email"),
143
+ "delete_record": True,
144
+ "search": False,
145
+ },
146
+ )
147
+ """
148
+ # Convert HumanInTheLoopModel entries to dict format
149
+ normalized_interrupt_on: dict[str, Any] = {}
150
+ for tool_name, config in interrupt_on.items():
151
+ if isinstance(config, (HumanInTheLoopModel, bool)):
152
+ normalized_interrupt_on[tool_name] = _config_to_interrupt_on_entry(config)
153
+ else:
154
+ # Already in dict format
155
+ normalized_interrupt_on[tool_name] = config
156
+
157
+ logger.debug(
158
+ "Creating HITL middleware",
159
+ tools_count=len(normalized_interrupt_on),
160
+ tools=list(normalized_interrupt_on.keys()),
161
+ )
162
+
163
+ return HumanInTheLoopMiddleware(
164
+ interrupt_on=normalized_interrupt_on,
165
+ description_prefix=description_prefix,
166
+ )
167
+
168
+
169
+ def create_hitl_middleware_from_tool_models(
170
+ tool_models: Sequence[ToolModel],
171
+ description_prefix: str = "Tool execution pending approval",
172
+ ) -> HumanInTheLoopMiddleware | None:
173
+ """
174
+ Create HumanInTheLoopMiddleware from ToolModel configurations.
175
+
176
+ Scans tool_models for those with human_in_the_loop configured and
177
+ creates the appropriate middleware. This is the primary entry point
178
+ used by the agent node creation.
179
+
180
+ Args:
181
+ tool_models: List of ToolModel configurations from agent config
182
+ description_prefix: Message prefix shown when pausing for review
183
+
184
+ Returns:
185
+ HumanInTheLoopMiddleware if any tools require approval, None otherwise
186
+
187
+ Example:
188
+ from dao_ai.config import ToolModel, PythonFunctionModel, HumanInTheLoopModel
189
+
190
+ tool_models = [
191
+ ToolModel(
192
+ name="email_tool",
193
+ function=PythonFunctionModel(
194
+ name="send_email",
195
+ human_in_the_loop=HumanInTheLoopModel(
196
+ review_prompt="Review this email",
197
+ ),
198
+ ),
199
+ ),
200
+ ]
201
+
202
+ middleware = create_hitl_middleware_from_tool_models(tool_models)
203
+ """
204
+ from dao_ai.config import BaseFunctionModel
205
+
206
+ interrupt_on: dict[str, HumanInTheLoopModel] = {}
207
+
208
+ for tool_model in tool_models:
209
+ function = tool_model.function
210
+
211
+ if not isinstance(function, BaseFunctionModel):
212
+ continue
213
+
214
+ hitl_config: HumanInTheLoopModel | None = function.human_in_the_loop
215
+ if not hitl_config:
216
+ continue
217
+
218
+ # Get tool names created by this function
219
+ for func_tool in function.as_tools():
220
+ tool_name: str | None = getattr(func_tool, "name", None)
221
+ if tool_name:
222
+ interrupt_on[tool_name] = hitl_config
223
+ logger.trace("Tool configured for HITL", tool_name=tool_name)
224
+
225
+ if not interrupt_on:
226
+ logger.trace("No tools require HITL - returning None")
227
+ return None
228
+
229
+ return create_human_in_the_loop_middleware(
230
+ interrupt_on=interrupt_on,
231
+ description_prefix=description_prefix,
232
+ )