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,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tool retry middleware for DAO AI agents.
|
|
3
|
+
|
|
4
|
+
Automatically retries failed tool calls with configurable exponential backoff.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
from dao_ai.middleware import create_tool_retry_middleware
|
|
8
|
+
|
|
9
|
+
# Retry failed tool calls with exponential backoff
|
|
10
|
+
middleware = create_tool_retry_middleware(
|
|
11
|
+
max_retries=3,
|
|
12
|
+
backoff_factor=2.0,
|
|
13
|
+
initial_delay=1.0,
|
|
14
|
+
)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from typing import Any, Callable, Literal
|
|
20
|
+
|
|
21
|
+
from langchain.agents.middleware import ToolRetryMiddleware
|
|
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
|
+
"ToolRetryMiddleware",
|
|
29
|
+
"create_tool_retry_middleware",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _resolve_tools(
|
|
34
|
+
tools: list[str | ToolModel | dict[str, Any]] | None,
|
|
35
|
+
) -> list[str] | None:
|
|
36
|
+
"""
|
|
37
|
+
Resolve tool specs to a list of tool name strings.
|
|
38
|
+
|
|
39
|
+
Returns None if tools is None (apply to all tools).
|
|
40
|
+
"""
|
|
41
|
+
if tools is None:
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
result: list[str] = []
|
|
45
|
+
for tool in tools:
|
|
46
|
+
if isinstance(tool, str):
|
|
47
|
+
result.append(tool)
|
|
48
|
+
elif isinstance(tool, dict):
|
|
49
|
+
try:
|
|
50
|
+
tool_model = ToolModel(**tool)
|
|
51
|
+
result.extend(_extract_tool_names(tool_model))
|
|
52
|
+
except Exception as e:
|
|
53
|
+
raise ValueError(f"Failed to construct ToolModel from dict: {e}") from e
|
|
54
|
+
elif isinstance(tool, ToolModel):
|
|
55
|
+
result.extend(_extract_tool_names(tool))
|
|
56
|
+
else:
|
|
57
|
+
raise TypeError(
|
|
58
|
+
f"Tool must be str, ToolModel, or dict, got {type(tool).__name__}"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
return result if result else None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _extract_tool_names(tool_model: ToolModel) -> list[str]:
|
|
65
|
+
"""Extract tool names from ToolModel, falling back to ToolModel.name."""
|
|
66
|
+
function = tool_model.function
|
|
67
|
+
|
|
68
|
+
if not isinstance(function, BaseFunctionModel):
|
|
69
|
+
return [tool_model.name]
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
tool_names = [
|
|
73
|
+
tool.name
|
|
74
|
+
for tool in function.as_tools()
|
|
75
|
+
if isinstance(tool, BaseTool) and tool.name
|
|
76
|
+
]
|
|
77
|
+
return tool_names if tool_names else [tool_model.name]
|
|
78
|
+
except Exception:
|
|
79
|
+
return [tool_model.name]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def create_tool_retry_middleware(
|
|
83
|
+
max_retries: int = 3,
|
|
84
|
+
backoff_factor: float = 2.0,
|
|
85
|
+
initial_delay: float = 1.0,
|
|
86
|
+
max_delay: float | None = None,
|
|
87
|
+
jitter: bool = False,
|
|
88
|
+
tools: list[str | ToolModel | dict[str, Any]] | None = None,
|
|
89
|
+
retry_on: tuple[type[Exception], ...] | Callable[[Exception], bool] | None = None,
|
|
90
|
+
on_failure: Literal["continue", "error"] | Callable[[Exception], str] = "continue",
|
|
91
|
+
) -> ToolRetryMiddleware:
|
|
92
|
+
"""
|
|
93
|
+
Create a ToolRetryMiddleware for automatic tool call retries.
|
|
94
|
+
|
|
95
|
+
Handles transient failures in external API calls with exponential backoff.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
max_retries: Max retry attempts after initial call. Default 3.
|
|
99
|
+
backoff_factor: Multiplier for exponential backoff. Default 2.0.
|
|
100
|
+
Delay = initial_delay * (backoff_factor ** retry_number)
|
|
101
|
+
Set to 0.0 for constant delay.
|
|
102
|
+
initial_delay: Initial delay in seconds before first retry. Default 1.0.
|
|
103
|
+
max_delay: Max delay in seconds (caps exponential growth). None = no cap.
|
|
104
|
+
jitter: Add ±25% random jitter to avoid thundering herd. Default False.
|
|
105
|
+
tools: List of tools to apply retry to. Can be:
|
|
106
|
+
- None: Apply to all tools (default)
|
|
107
|
+
- list of str: Tool names
|
|
108
|
+
- list of ToolModel: DAO AI tool models
|
|
109
|
+
- list of dict: Tool config dicts
|
|
110
|
+
retry_on: When to retry:
|
|
111
|
+
- None: Retry on all errors (default)
|
|
112
|
+
- tuple of Exception types: Retry only on these
|
|
113
|
+
- callable: Function(exception) -> bool
|
|
114
|
+
on_failure: Behavior when all retries exhausted:
|
|
115
|
+
- "continue": Return error message, let agent continue (default)
|
|
116
|
+
- "error": Re-raise exception, stop execution
|
|
117
|
+
- callable: Function(exception) -> str for custom message
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
List containing ToolRetryMiddleware instance
|
|
121
|
+
|
|
122
|
+
Example:
|
|
123
|
+
# Basic retry with defaults
|
|
124
|
+
retry = create_tool_retry_middleware()
|
|
125
|
+
|
|
126
|
+
# Retry specific tools with custom backoff
|
|
127
|
+
retry = create_tool_retry_middleware(
|
|
128
|
+
max_retries=5,
|
|
129
|
+
backoff_factor=1.5,
|
|
130
|
+
initial_delay=0.5,
|
|
131
|
+
tools=["search_web", "query_database"],
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Retry only on specific exceptions
|
|
135
|
+
retry = create_tool_retry_middleware(
|
|
136
|
+
max_retries=3,
|
|
137
|
+
retry_on=(TimeoutError, ConnectionError),
|
|
138
|
+
on_failure="error",
|
|
139
|
+
)
|
|
140
|
+
"""
|
|
141
|
+
tool_names = _resolve_tools(tools)
|
|
142
|
+
|
|
143
|
+
logger.debug(
|
|
144
|
+
"Creating tool retry middleware",
|
|
145
|
+
max_retries=max_retries,
|
|
146
|
+
backoff_factor=backoff_factor,
|
|
147
|
+
initial_delay=initial_delay,
|
|
148
|
+
max_delay=max_delay,
|
|
149
|
+
jitter=jitter,
|
|
150
|
+
tools=tool_names or "all",
|
|
151
|
+
on_failure=on_failure if isinstance(on_failure, str) else "custom",
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Build kwargs
|
|
155
|
+
kwargs: dict[str, Any] = {
|
|
156
|
+
"max_retries": max_retries,
|
|
157
|
+
"backoff_factor": backoff_factor,
|
|
158
|
+
"initial_delay": initial_delay,
|
|
159
|
+
"on_failure": on_failure,
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if tool_names is not None:
|
|
163
|
+
kwargs["tools"] = tool_names
|
|
164
|
+
|
|
165
|
+
if max_delay is not None:
|
|
166
|
+
kwargs["max_delay"] = max_delay
|
|
167
|
+
|
|
168
|
+
if jitter:
|
|
169
|
+
kwargs["jitter"] = jitter
|
|
170
|
+
|
|
171
|
+
if retry_on is not None:
|
|
172
|
+
kwargs["retry_on"] = retry_on
|
|
173
|
+
|
|
174
|
+
return ToolRetryMiddleware(**kwargs)
|