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
|
@@ -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
|
+
)
|