dao-ai 0.0.28__py3-none-any.whl → 0.1.5__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 +342 -58
- dao_ai/config.py +1610 -380
- 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 +158 -0
- dao_ai/middleware/assertions.py +806 -0
- dao_ai/middleware/base.py +50 -0
- dao_ai/middleware/context_editing.py +230 -0
- dao_ai/middleware/core.py +67 -0
- dao_ai/middleware/guardrails.py +420 -0
- dao_ai/middleware/human_in_the_loop.py +233 -0
- dao_ai/middleware/message_validation.py +586 -0
- dao_ai/middleware/model_call_limit.py +77 -0
- dao_ai/middleware/model_retry.py +121 -0
- dao_ai/middleware/pii.py +157 -0
- dao_ai/middleware/summarization.py +197 -0
- dao_ai/middleware/tool_call_limit.py +210 -0
- dao_ai/middleware/tool_retry.py +174 -0
- dao_ai/models.py +1306 -114
- dao_ai/nodes.py +240 -161
- 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 +279 -0
- dao_ai/orchestration/swarm.py +271 -0
- dao_ai/prompts.py +128 -31
- dao_ai/providers/databricks.py +584 -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/vector_search.py +37 -0
- dao_ai-0.1.5.dist-info/METADATA +489 -0
- dao_ai-0.1.5.dist-info/RECORD +70 -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.5.dist-info}/WHEEL +0 -0
- {dao_ai-0.0.28.dist-info → dao_ai-0.1.5.dist-info}/entry_points.txt +0 -0
- {dao_ai-0.0.28.dist-info → dao_ai-0.1.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base classes and types for DAO AI middleware.
|
|
3
|
+
|
|
4
|
+
This module re-exports LangChain's middleware types for convenience.
|
|
5
|
+
Use LangChainAgentMiddleware directly with DAO AI's state and context types.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
from langchain.agents.middleware import AgentMiddleware
|
|
9
|
+
from dao_ai.state import AgentState, Context
|
|
10
|
+
from langgraph.runtime import Runtime
|
|
11
|
+
|
|
12
|
+
class MyMiddleware(AgentMiddleware[AgentState, Context]):
|
|
13
|
+
def before_model(
|
|
14
|
+
self,
|
|
15
|
+
state: AgentState,
|
|
16
|
+
runtime: Runtime[Context]
|
|
17
|
+
) -> dict[str, Any] | None:
|
|
18
|
+
print(f"About to call model with {len(state['messages'])} messages")
|
|
19
|
+
return None
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from langchain.agents.middleware import (
|
|
23
|
+
AgentMiddleware,
|
|
24
|
+
ModelRequest,
|
|
25
|
+
after_agent,
|
|
26
|
+
after_model,
|
|
27
|
+
before_agent,
|
|
28
|
+
before_model,
|
|
29
|
+
dynamic_prompt,
|
|
30
|
+
wrap_model_call,
|
|
31
|
+
wrap_tool_call,
|
|
32
|
+
)
|
|
33
|
+
from langchain.agents.middleware.types import ModelResponse
|
|
34
|
+
|
|
35
|
+
# Re-export LangChain types for convenience
|
|
36
|
+
__all__ = [
|
|
37
|
+
# Base middleware class
|
|
38
|
+
"AgentMiddleware",
|
|
39
|
+
# Types
|
|
40
|
+
"ModelRequest",
|
|
41
|
+
"ModelResponse",
|
|
42
|
+
# Decorators
|
|
43
|
+
"before_agent",
|
|
44
|
+
"before_model",
|
|
45
|
+
"after_agent",
|
|
46
|
+
"after_model",
|
|
47
|
+
"wrap_model_call",
|
|
48
|
+
"wrap_tool_call",
|
|
49
|
+
"dynamic_prompt",
|
|
50
|
+
]
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Context editing middleware for DAO AI agents.
|
|
3
|
+
|
|
4
|
+
Manages conversation context by clearing older tool call outputs when token limits
|
|
5
|
+
are reached, while preserving recent results.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
from dao_ai.middleware import create_context_editing_middleware
|
|
9
|
+
|
|
10
|
+
# Clear old tool outputs when context exceeds 100k tokens
|
|
11
|
+
middleware = create_context_editing_middleware(
|
|
12
|
+
trigger=100000,
|
|
13
|
+
keep=3,
|
|
14
|
+
)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from typing import Any, Literal
|
|
20
|
+
|
|
21
|
+
from langchain.agents.middleware import ClearToolUsesEdit, ContextEditingMiddleware
|
|
22
|
+
from langchain_core.tools import BaseTool
|
|
23
|
+
from loguru import logger
|
|
24
|
+
|
|
25
|
+
from dao_ai.config import BaseFunctionModel, ToolModel
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"ContextEditingMiddleware",
|
|
29
|
+
"ClearToolUsesEdit",
|
|
30
|
+
"create_context_editing_middleware",
|
|
31
|
+
"create_clear_tool_uses_edit",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _resolve_tool_names(
|
|
36
|
+
tools: list[str | ToolModel | dict[str, Any]] | None,
|
|
37
|
+
) -> list[str]:
|
|
38
|
+
"""Resolve tool specs to a list of tool name strings."""
|
|
39
|
+
if tools is None:
|
|
40
|
+
return []
|
|
41
|
+
|
|
42
|
+
result: list[str] = []
|
|
43
|
+
for tool in tools:
|
|
44
|
+
if isinstance(tool, str):
|
|
45
|
+
result.append(tool)
|
|
46
|
+
elif isinstance(tool, dict):
|
|
47
|
+
try:
|
|
48
|
+
tool_model = ToolModel(**tool)
|
|
49
|
+
result.extend(_extract_tool_names(tool_model))
|
|
50
|
+
except Exception as e:
|
|
51
|
+
raise ValueError(f"Failed to construct ToolModel from dict: {e}") from e
|
|
52
|
+
elif isinstance(tool, ToolModel):
|
|
53
|
+
result.extend(_extract_tool_names(tool))
|
|
54
|
+
else:
|
|
55
|
+
raise TypeError(
|
|
56
|
+
f"Tool must be str, ToolModel, or dict, got {type(tool).__name__}"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return result
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _extract_tool_names(tool_model: ToolModel) -> list[str]:
|
|
63
|
+
"""Extract tool names from ToolModel, falling back to ToolModel.name."""
|
|
64
|
+
function = tool_model.function
|
|
65
|
+
|
|
66
|
+
if not isinstance(function, BaseFunctionModel):
|
|
67
|
+
return [tool_model.name]
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
tool_names = [
|
|
71
|
+
tool.name
|
|
72
|
+
for tool in function.as_tools()
|
|
73
|
+
if isinstance(tool, BaseTool) and tool.name
|
|
74
|
+
]
|
|
75
|
+
return tool_names if tool_names else [tool_model.name]
|
|
76
|
+
except Exception:
|
|
77
|
+
return [tool_model.name]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def create_clear_tool_uses_edit(
|
|
81
|
+
trigger: int = 100000,
|
|
82
|
+
keep: int = 3,
|
|
83
|
+
clear_at_least: int = 0,
|
|
84
|
+
clear_tool_inputs: bool = False,
|
|
85
|
+
exclude_tools: list[str | ToolModel | dict[str, Any]] | None = None,
|
|
86
|
+
placeholder: str = "[cleared]",
|
|
87
|
+
) -> ClearToolUsesEdit:
|
|
88
|
+
"""
|
|
89
|
+
Create a ClearToolUsesEdit for use with ContextEditingMiddleware.
|
|
90
|
+
|
|
91
|
+
This edit strategy clears older tool results when the conversation exceeds
|
|
92
|
+
a token threshold, while preserving recent results.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
trigger: Token count that triggers the edit. When conversation exceeds
|
|
96
|
+
this, older tool outputs are cleared. Default 100000.
|
|
97
|
+
keep: Number of most recent tool results to preserve. These are never
|
|
98
|
+
cleared. Default 3.
|
|
99
|
+
clear_at_least: Minimum tokens to reclaim when edit runs.
|
|
100
|
+
0 means clear as much as needed. Default 0.
|
|
101
|
+
clear_tool_inputs: Whether to clear tool call arguments on AI messages.
|
|
102
|
+
When True, tool call arguments are replaced with empty objects.
|
|
103
|
+
Default False.
|
|
104
|
+
exclude_tools: Tools to never clear. Can be:
|
|
105
|
+
- list of str: Tool names
|
|
106
|
+
- list of ToolModel: DAO AI tool models
|
|
107
|
+
- list of dict: Tool config dicts
|
|
108
|
+
Default None (no exclusions).
|
|
109
|
+
placeholder: Text inserted for cleared tool outputs.
|
|
110
|
+
Default "[cleared]".
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
ClearToolUsesEdit instance
|
|
114
|
+
|
|
115
|
+
Example:
|
|
116
|
+
edit = create_clear_tool_uses_edit(
|
|
117
|
+
trigger=50000,
|
|
118
|
+
keep=5,
|
|
119
|
+
clear_tool_inputs=True,
|
|
120
|
+
exclude_tools=["important_tool"],
|
|
121
|
+
)
|
|
122
|
+
"""
|
|
123
|
+
excluded = _resolve_tool_names(exclude_tools) if exclude_tools else []
|
|
124
|
+
|
|
125
|
+
logger.debug(
|
|
126
|
+
"Creating ClearToolUsesEdit",
|
|
127
|
+
trigger=trigger,
|
|
128
|
+
keep=keep,
|
|
129
|
+
clear_at_least=clear_at_least,
|
|
130
|
+
clear_tool_inputs=clear_tool_inputs,
|
|
131
|
+
exclude_tools=excluded or "none",
|
|
132
|
+
placeholder=placeholder,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
return ClearToolUsesEdit(
|
|
136
|
+
trigger=trigger,
|
|
137
|
+
keep=keep,
|
|
138
|
+
clear_at_least=clear_at_least,
|
|
139
|
+
clear_tool_inputs=clear_tool_inputs,
|
|
140
|
+
exclude_tools=excluded,
|
|
141
|
+
placeholder=placeholder,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def create_context_editing_middleware(
|
|
146
|
+
trigger: int = 100000,
|
|
147
|
+
keep: int = 3,
|
|
148
|
+
clear_at_least: int = 0,
|
|
149
|
+
clear_tool_inputs: bool = False,
|
|
150
|
+
exclude_tools: list[str | ToolModel | dict[str, Any]] | None = None,
|
|
151
|
+
placeholder: str = "[cleared]",
|
|
152
|
+
token_count_method: Literal["approximate", "model"] = "approximate",
|
|
153
|
+
) -> ContextEditingMiddleware:
|
|
154
|
+
"""
|
|
155
|
+
Create a ContextEditingMiddleware with ClearToolUsesEdit.
|
|
156
|
+
|
|
157
|
+
Manages conversation context by clearing older tool call outputs when token
|
|
158
|
+
limits are reached. Useful for long conversations with many tool calls that
|
|
159
|
+
exceed context window limits.
|
|
160
|
+
|
|
161
|
+
Use cases:
|
|
162
|
+
- Long conversations with many tool calls exceeding token limits
|
|
163
|
+
- Reducing token costs by removing older irrelevant tool outputs
|
|
164
|
+
- Maintaining only the most recent N tool results in context
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
trigger: Token count that triggers clearing. When conversation exceeds
|
|
168
|
+
this threshold, older tool outputs are cleared. Default 100000.
|
|
169
|
+
keep: Number of most recent tool results to always preserve.
|
|
170
|
+
These are never cleared. Default 3.
|
|
171
|
+
clear_at_least: Minimum tokens to reclaim when edit runs.
|
|
172
|
+
0 means clear as much as needed. Default 0.
|
|
173
|
+
clear_tool_inputs: Whether to also clear tool call arguments on AI
|
|
174
|
+
messages. When True, replaces arguments with empty objects.
|
|
175
|
+
Default False (preserves tool call context).
|
|
176
|
+
exclude_tools: Tools to never clear outputs from. Can be:
|
|
177
|
+
- list of str: Tool names
|
|
178
|
+
- list of ToolModel: DAO AI tool models
|
|
179
|
+
- list of dict: Tool config dicts
|
|
180
|
+
Default None (no exclusions).
|
|
181
|
+
placeholder: Text inserted for cleared tool outputs.
|
|
182
|
+
Default "[cleared]".
|
|
183
|
+
token_count_method: How to count tokens:
|
|
184
|
+
- "approximate": Fast estimation (default)
|
|
185
|
+
- "model": Accurate count using model tokenizer
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
List containing ContextEditingMiddleware instance
|
|
189
|
+
|
|
190
|
+
Example:
|
|
191
|
+
# Basic usage - clear old tool outputs after 100k tokens
|
|
192
|
+
middleware = create_context_editing_middleware(
|
|
193
|
+
trigger=100000,
|
|
194
|
+
keep=3,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# Aggressive clearing with exclusions
|
|
198
|
+
middleware = create_context_editing_middleware(
|
|
199
|
+
trigger=50000,
|
|
200
|
+
keep=5,
|
|
201
|
+
clear_tool_inputs=True,
|
|
202
|
+
exclude_tools=["important_tool", "critical_search"],
|
|
203
|
+
placeholder="[output cleared to save context]",
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Accurate token counting
|
|
207
|
+
middleware = create_context_editing_middleware(
|
|
208
|
+
trigger=100000,
|
|
209
|
+
keep=3,
|
|
210
|
+
token_count_method="model",
|
|
211
|
+
)
|
|
212
|
+
"""
|
|
213
|
+
edit = create_clear_tool_uses_edit(
|
|
214
|
+
trigger=trigger,
|
|
215
|
+
keep=keep,
|
|
216
|
+
clear_at_least=clear_at_least,
|
|
217
|
+
clear_tool_inputs=clear_tool_inputs,
|
|
218
|
+
exclude_tools=exclude_tools,
|
|
219
|
+
placeholder=placeholder,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
logger.debug(
|
|
223
|
+
"Creating ContextEditingMiddleware",
|
|
224
|
+
token_count_method=token_count_method,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
return ContextEditingMiddleware(
|
|
228
|
+
edits=[edit],
|
|
229
|
+
token_count_method=token_count_method,
|
|
230
|
+
)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core middleware utilities for DAO AI.
|
|
3
|
+
|
|
4
|
+
This module provides the factory function for creating middleware instances
|
|
5
|
+
from fully qualified function names.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, Callable
|
|
9
|
+
|
|
10
|
+
from langchain.agents.middleware import AgentMiddleware
|
|
11
|
+
from loguru import logger
|
|
12
|
+
|
|
13
|
+
from dao_ai.state import AgentState, Context
|
|
14
|
+
from dao_ai.utils import load_function
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def create_factory_middleware(
|
|
18
|
+
function_name: str,
|
|
19
|
+
args: dict[str, Any] | None = None,
|
|
20
|
+
) -> AgentMiddleware[AgentState, Context]:
|
|
21
|
+
"""
|
|
22
|
+
Create middleware from a factory function.
|
|
23
|
+
|
|
24
|
+
This factory function dynamically loads a Python function and calls it
|
|
25
|
+
with the provided arguments to create a middleware instance.
|
|
26
|
+
|
|
27
|
+
The factory function should return a middleware object compatible with
|
|
28
|
+
LangChain's create_agent middleware parameter (AgentMiddleware or any
|
|
29
|
+
callable/object that implements the middleware interface).
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
function_name: Fully qualified name of the factory function
|
|
33
|
+
(e.g., 'my_module.create_custom_middleware')
|
|
34
|
+
args: Arguments to pass to the factory function
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
The AgentMiddleware instance returned by the factory function.
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
ImportError: If the function cannot be loaded
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
# Factory function in my_module.py:
|
|
44
|
+
def create_custom_middleware(threshold: float = 0.5) -> AgentMiddleware[AgentState, Context]:
|
|
45
|
+
return MyCustomMiddleware(threshold=threshold)
|
|
46
|
+
|
|
47
|
+
# Usage:
|
|
48
|
+
middleware = create_factory_middleware(
|
|
49
|
+
function_name="my_module.create_custom_middleware",
|
|
50
|
+
args={"threshold": 0.8}
|
|
51
|
+
)
|
|
52
|
+
"""
|
|
53
|
+
if args is None:
|
|
54
|
+
args = {}
|
|
55
|
+
|
|
56
|
+
logger.trace("Creating factory middleware", function_name=function_name, args=args)
|
|
57
|
+
|
|
58
|
+
factory: Callable[..., AgentMiddleware[AgentState, Context]] = load_function(
|
|
59
|
+
function_name=function_name
|
|
60
|
+
)
|
|
61
|
+
middleware = factory(**args)
|
|
62
|
+
|
|
63
|
+
logger.trace(
|
|
64
|
+
"Created middleware from factory",
|
|
65
|
+
middleware_type=type(middleware).__name__,
|
|
66
|
+
)
|
|
67
|
+
return middleware
|