dao-ai 0.1.2__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/cli.py +104 -25
- dao_ai/config.py +149 -40
- dao_ai/middleware/__init__.py +33 -0
- dao_ai/middleware/assertions.py +3 -3
- dao_ai/middleware/context_editing.py +230 -0
- dao_ai/middleware/core.py +4 -4
- dao_ai/middleware/guardrails.py +3 -3
- dao_ai/middleware/human_in_the_loop.py +3 -2
- dao_ai/middleware/message_validation.py +4 -4
- 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 +1 -1
- dao_ai/middleware/tool_call_limit.py +210 -0
- dao_ai/middleware/tool_retry.py +174 -0
- dao_ai/nodes.py +5 -12
- dao_ai/orchestration/supervisor.py +6 -5
- dao_ai/providers/databricks.py +11 -0
- dao_ai/vector_search.py +37 -0
- {dao_ai-0.1.2.dist-info → dao_ai-0.1.5.dist-info}/METADATA +36 -2
- {dao_ai-0.1.2.dist-info → dao_ai-0.1.5.dist-info}/RECORD +24 -18
- {dao_ai-0.1.2.dist-info → dao_ai-0.1.5.dist-info}/WHEEL +0 -0
- {dao_ai-0.1.2.dist-info → dao_ai-0.1.5.dist-info}/entry_points.txt +0 -0
- {dao_ai-0.1.2.dist-info → dao_ai-0.1.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -150,7 +150,7 @@ def create_summarization_middleware(
|
|
|
150
150
|
chat_history: ChatHistoryModel configuration for summarization
|
|
151
151
|
|
|
152
152
|
Returns:
|
|
153
|
-
LoggingSummarizationMiddleware configured with the specified parameters
|
|
153
|
+
List containing LoggingSummarizationMiddleware configured with the specified parameters
|
|
154
154
|
|
|
155
155
|
Example:
|
|
156
156
|
from dao_ai.config import ChatHistoryModel, LLMModel
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tool call limit middleware for DAO AI agents.
|
|
3
|
+
|
|
4
|
+
This module provides a factory for creating LangChain's ToolCallLimitMiddleware
|
|
5
|
+
from DAO AI configuration.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
from dao_ai.middleware import create_tool_call_limit_middleware
|
|
9
|
+
|
|
10
|
+
# Global limit across all tools
|
|
11
|
+
middleware = create_tool_call_limit_middleware(
|
|
12
|
+
thread_limit=20,
|
|
13
|
+
run_limit=10,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# Limit specific tool by name
|
|
17
|
+
search_limiter = create_tool_call_limit_middleware(
|
|
18
|
+
tool="search_web",
|
|
19
|
+
run_limit=3,
|
|
20
|
+
exit_behavior="continue",
|
|
21
|
+
)
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from typing import Any, Literal
|
|
27
|
+
|
|
28
|
+
from langchain.agents.middleware import ToolCallLimitMiddleware
|
|
29
|
+
from langchain_core.tools import BaseTool
|
|
30
|
+
from loguru import logger
|
|
31
|
+
|
|
32
|
+
from dao_ai.config import BaseFunctionModel, ToolModel
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"ToolCallLimitMiddleware",
|
|
36
|
+
"create_tool_call_limit_middleware",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _resolve_tool(tool: str | ToolModel | dict[str, Any]) -> list[str]:
|
|
41
|
+
"""
|
|
42
|
+
Resolve tool argument to a list of actual tool names.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
tool: String name, ToolModel, or dict to resolve
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
List of tool name strings
|
|
49
|
+
|
|
50
|
+
Raises:
|
|
51
|
+
ValueError: If dict cannot be converted to ToolModel
|
|
52
|
+
TypeError: If tool is not a supported type
|
|
53
|
+
"""
|
|
54
|
+
# String: return as single-item list
|
|
55
|
+
if isinstance(tool, str):
|
|
56
|
+
return [tool]
|
|
57
|
+
|
|
58
|
+
# Dict: convert to ToolModel first
|
|
59
|
+
if isinstance(tool, dict):
|
|
60
|
+
try:
|
|
61
|
+
tool_model = ToolModel(**tool)
|
|
62
|
+
except Exception as e:
|
|
63
|
+
raise ValueError(
|
|
64
|
+
f"Failed to construct ToolModel from dict: {e}\n"
|
|
65
|
+
f"Dict must have 'name' and 'function' keys."
|
|
66
|
+
) from e
|
|
67
|
+
elif isinstance(tool, ToolModel):
|
|
68
|
+
tool_model = tool
|
|
69
|
+
else:
|
|
70
|
+
raise TypeError(
|
|
71
|
+
f"tool must be str, ToolModel, or dict, got {type(tool).__name__}"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Extract tool names from ToolModel
|
|
75
|
+
return _extract_tool_names(tool_model)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _extract_tool_names(tool_model: ToolModel) -> list[str]:
|
|
79
|
+
"""
|
|
80
|
+
Extract actual tool names from a ToolModel.
|
|
81
|
+
|
|
82
|
+
A single ToolModel can produce multiple tools (e.g., UC functions).
|
|
83
|
+
Falls back to ToolModel.name if extraction fails.
|
|
84
|
+
"""
|
|
85
|
+
function = tool_model.function
|
|
86
|
+
|
|
87
|
+
# String function references can't be introspected
|
|
88
|
+
if not isinstance(function, BaseFunctionModel):
|
|
89
|
+
logger.debug(
|
|
90
|
+
"Cannot extract names from string function, using ToolModel.name",
|
|
91
|
+
tool_model_name=tool_model.name,
|
|
92
|
+
)
|
|
93
|
+
return [tool_model.name]
|
|
94
|
+
|
|
95
|
+
# Try to extract names from created tools
|
|
96
|
+
try:
|
|
97
|
+
tool_names = [
|
|
98
|
+
tool.name
|
|
99
|
+
for tool in function.as_tools()
|
|
100
|
+
if isinstance(tool, BaseTool) and tool.name
|
|
101
|
+
]
|
|
102
|
+
if tool_names:
|
|
103
|
+
logger.trace(
|
|
104
|
+
"Extracted tool names",
|
|
105
|
+
tool_model_name=tool_model.name,
|
|
106
|
+
tool_names=tool_names,
|
|
107
|
+
)
|
|
108
|
+
return tool_names
|
|
109
|
+
except Exception as e:
|
|
110
|
+
logger.warning(
|
|
111
|
+
"Error extracting tool names from ToolModel",
|
|
112
|
+
tool_model_name=tool_model.name,
|
|
113
|
+
error=str(e),
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Fallback to ToolModel.name
|
|
117
|
+
logger.debug(
|
|
118
|
+
"Falling back to ToolModel.name",
|
|
119
|
+
tool_model_name=tool_model.name,
|
|
120
|
+
)
|
|
121
|
+
return [tool_model.name]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def create_tool_call_limit_middleware(
|
|
125
|
+
tool: str | ToolModel | dict[str, Any] | None = None,
|
|
126
|
+
thread_limit: int | None = None,
|
|
127
|
+
run_limit: int | None = None,
|
|
128
|
+
exit_behavior: Literal["continue", "error", "end"] = "continue",
|
|
129
|
+
) -> ToolCallLimitMiddleware:
|
|
130
|
+
"""
|
|
131
|
+
Create a ToolCallLimitMiddleware with graceful termination support.
|
|
132
|
+
|
|
133
|
+
Factory for LangChain's ToolCallLimitMiddleware that supports DAO AI
|
|
134
|
+
configuration types.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
tool: Tool to limit. Can be:
|
|
138
|
+
- None: Global limit on all tools
|
|
139
|
+
- str: Limit specific tool by name
|
|
140
|
+
- ToolModel: Limit tool(s) from DAO AI config
|
|
141
|
+
- dict: Tool config dict (converted to ToolModel)
|
|
142
|
+
thread_limit: Max calls per thread (conversation). Requires checkpointer.
|
|
143
|
+
run_limit: Max calls per run (single invocation).
|
|
144
|
+
exit_behavior: What to do when limit hit:
|
|
145
|
+
- "continue": Block tool with error message, let agent continue
|
|
146
|
+
- "error": Raise ToolCallLimitExceededError immediately
|
|
147
|
+
- "end": Stop execution gracefully (single-tool only)
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
A ToolCallLimitMiddleware instance. If ToolModel produces multiple tools,
|
|
151
|
+
only the first tool is used (with a warning logged).
|
|
152
|
+
|
|
153
|
+
Raises:
|
|
154
|
+
ValueError: If no limits specified, or invalid dict
|
|
155
|
+
TypeError: If tool is unsupported type
|
|
156
|
+
|
|
157
|
+
Example:
|
|
158
|
+
# Global limit
|
|
159
|
+
limiter = create_tool_call_limit_middleware(run_limit=10)
|
|
160
|
+
|
|
161
|
+
# Tool-specific limit
|
|
162
|
+
limiter = create_tool_call_limit_middleware(
|
|
163
|
+
tool="search_web",
|
|
164
|
+
run_limit=3,
|
|
165
|
+
exit_behavior="continue",
|
|
166
|
+
)
|
|
167
|
+
"""
|
|
168
|
+
if thread_limit is None and run_limit is None:
|
|
169
|
+
raise ValueError("At least one of thread_limit or run_limit must be specified.")
|
|
170
|
+
|
|
171
|
+
# Global limit: no tool parameter
|
|
172
|
+
if tool is None:
|
|
173
|
+
logger.debug(
|
|
174
|
+
"Creating global tool call limit",
|
|
175
|
+
thread_limit=thread_limit,
|
|
176
|
+
run_limit=run_limit,
|
|
177
|
+
exit_behavior=exit_behavior,
|
|
178
|
+
)
|
|
179
|
+
return ToolCallLimitMiddleware(
|
|
180
|
+
thread_limit=thread_limit,
|
|
181
|
+
run_limit=run_limit,
|
|
182
|
+
exit_behavior=exit_behavior,
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
# Resolve to list of tool names
|
|
186
|
+
names = _resolve_tool(tool)
|
|
187
|
+
|
|
188
|
+
# Use first tool name (warn if multiple)
|
|
189
|
+
tool_name = names[0]
|
|
190
|
+
if len(names) > 1:
|
|
191
|
+
logger.warning(
|
|
192
|
+
"ToolModel resolved to multiple tool names, using first only",
|
|
193
|
+
tool_names=names,
|
|
194
|
+
using=tool_name,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
logger.debug(
|
|
198
|
+
"Creating tool call limit middleware",
|
|
199
|
+
tool_name=tool_name,
|
|
200
|
+
thread_limit=thread_limit,
|
|
201
|
+
run_limit=run_limit,
|
|
202
|
+
exit_behavior=exit_behavior,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
return ToolCallLimitMiddleware(
|
|
206
|
+
tool_name=tool_name,
|
|
207
|
+
thread_limit=thread_limit,
|
|
208
|
+
run_limit=run_limit,
|
|
209
|
+
exit_behavior=exit_behavior,
|
|
210
|
+
)
|
|
@@ -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)
|
dao_ai/nodes.py
CHANGED
|
@@ -26,11 +26,9 @@ from dao_ai.config import (
|
|
|
26
26
|
from dao_ai.middleware.core import create_factory_middleware
|
|
27
27
|
from dao_ai.middleware.guardrails import GuardrailMiddleware
|
|
28
28
|
from dao_ai.middleware.human_in_the_loop import (
|
|
29
|
-
HumanInTheLoopMiddleware,
|
|
30
29
|
create_hitl_middleware_from_tool_models,
|
|
31
30
|
)
|
|
32
31
|
from dao_ai.middleware.summarization import (
|
|
33
|
-
LoggingSummarizationMiddleware,
|
|
34
32
|
create_summarization_middleware,
|
|
35
33
|
)
|
|
36
34
|
from dao_ai.prompts import make_prompt
|
|
@@ -78,8 +76,7 @@ def _create_middleware_list(
|
|
|
78
76
|
function_name=middleware_config.name,
|
|
79
77
|
args=middleware_config.args,
|
|
80
78
|
)
|
|
81
|
-
|
|
82
|
-
middleware_list.append(middleware)
|
|
79
|
+
middleware_list.append(middleware)
|
|
83
80
|
|
|
84
81
|
# Add guardrails as middleware
|
|
85
82
|
if agent.guardrails:
|
|
@@ -117,16 +114,12 @@ def _create_middleware_list(
|
|
|
117
114
|
max_tokens=chat_history.max_tokens,
|
|
118
115
|
summary_model=chat_history.model.name,
|
|
119
116
|
)
|
|
120
|
-
summarization_middleware
|
|
121
|
-
create_summarization_middleware(chat_history)
|
|
122
|
-
)
|
|
117
|
+
summarization_middleware = create_summarization_middleware(chat_history)
|
|
123
118
|
middleware_list.append(summarization_middleware)
|
|
124
119
|
|
|
125
120
|
# Add human-in-the-loop middleware if any tools require it
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
)
|
|
129
|
-
if hitl_middleware is not None:
|
|
121
|
+
hitl_middlewares = create_hitl_middleware_from_tool_models(tool_models)
|
|
122
|
+
if hitl_middlewares:
|
|
130
123
|
# Log which tools require HITL
|
|
131
124
|
hitl_tool_names: list[str] = [
|
|
132
125
|
tool.name
|
|
@@ -139,7 +132,7 @@ def _create_middleware_list(
|
|
|
139
132
|
agent=agent.name,
|
|
140
133
|
hitl_tools=hitl_tool_names,
|
|
141
134
|
)
|
|
142
|
-
middleware_list.append(
|
|
135
|
+
middleware_list.append(hitl_middlewares)
|
|
143
136
|
|
|
144
137
|
logger.info(
|
|
145
138
|
"Middleware summary",
|
|
@@ -190,6 +190,7 @@ def create_supervisor_graph(config: AppConfig) -> CompiledStateGraph:
|
|
|
190
190
|
supervisor_tools: list[BaseTool] = list(create_tools(supervisor_config.tools))
|
|
191
191
|
|
|
192
192
|
# Create middleware from configuration
|
|
193
|
+
# All middleware factories return list[AgentMiddleware] for composability
|
|
193
194
|
middlewares: list[AgentMiddleware] = []
|
|
194
195
|
|
|
195
196
|
for middleware_config in supervisor_config.middleware:
|
|
@@ -201,11 +202,11 @@ def create_supervisor_graph(config: AppConfig) -> CompiledStateGraph:
|
|
|
201
202
|
function_name=middleware_config.name,
|
|
202
203
|
args=middleware_config.args,
|
|
203
204
|
)
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
205
|
+
middlewares.append(middleware)
|
|
206
|
+
logger.debug(
|
|
207
|
+
"Created supervisor middleware",
|
|
208
|
+
middleware=middleware_config.name,
|
|
209
|
+
)
|
|
209
210
|
|
|
210
211
|
# Set up memory store and checkpointer
|
|
211
212
|
store: BaseStore | None = create_store(orchestration)
|
dao_ai/providers/databricks.py
CHANGED
|
@@ -625,6 +625,17 @@ class DatabricksProvider(ServiceProvider):
|
|
|
625
625
|
df.write.mode("overwrite").saveAsTable(table)
|
|
626
626
|
|
|
627
627
|
def create_vector_store(self, vector_store: VectorStoreModel) -> None:
|
|
628
|
+
"""
|
|
629
|
+
Create a vector search index from a source table.
|
|
630
|
+
|
|
631
|
+
This method expects a VectorStoreModel in provisioning mode with all
|
|
632
|
+
required fields validated. Use VectorStoreModel.create() which handles
|
|
633
|
+
mode detection and validation.
|
|
634
|
+
|
|
635
|
+
Args:
|
|
636
|
+
vector_store: VectorStoreModel configured for provisioning
|
|
637
|
+
"""
|
|
638
|
+
# Ensure endpoint exists
|
|
628
639
|
if not endpoint_exists(self.vsc, vector_store.endpoint.name):
|
|
629
640
|
self.vsc.create_endpoint_and_wait(
|
|
630
641
|
name=vector_store.endpoint.name,
|
dao_ai/vector_search.py
CHANGED
|
@@ -72,3 +72,40 @@ def index_exists(
|
|
|
72
72
|
raise e
|
|
73
73
|
# If we reach here, the index doesn't exist
|
|
74
74
|
return False
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def find_index(
|
|
78
|
+
vsc: VectorSearchClient, index_full_name: str
|
|
79
|
+
) -> tuple[bool, str | None]:
|
|
80
|
+
"""
|
|
81
|
+
Find a Vector Search index across all endpoints.
|
|
82
|
+
|
|
83
|
+
Searches all available endpoints to find where the index is located.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
vsc: Databricks Vector Search client instance
|
|
87
|
+
index_full_name: Fully qualified name of the index (catalog.schema.index)
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Tuple of (exists: bool, endpoint_name: str | None)
|
|
91
|
+
- (True, endpoint_name) if index is found
|
|
92
|
+
- (False, None) if index is not found on any endpoint
|
|
93
|
+
"""
|
|
94
|
+
try:
|
|
95
|
+
endpoints = vsc.list_endpoints().get("endpoints", [])
|
|
96
|
+
except Exception as e:
|
|
97
|
+
if "REQUEST_LIMIT_EXCEEDED" in str(e):
|
|
98
|
+
print("WARN: couldn't list endpoints due to REQUEST_LIMIT_EXCEEDED error.")
|
|
99
|
+
return (False, None)
|
|
100
|
+
raise e
|
|
101
|
+
|
|
102
|
+
for endpoint in endpoints:
|
|
103
|
+
endpoint_name: str = endpoint["name"]
|
|
104
|
+
try:
|
|
105
|
+
vsc.get_index(endpoint_name, index_full_name).describe()
|
|
106
|
+
return (True, endpoint_name)
|
|
107
|
+
except Exception:
|
|
108
|
+
# Index not on this endpoint, try next
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
return (False, None)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dao-ai
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: DAO AI: A modular, multi-agent orchestration framework for complex AI workflows. Supports agent handoff, tool integration, and dynamic configuration via YAML.
|
|
5
5
|
Project-URL: Homepage, https://github.com/natefleming/dao-ai
|
|
6
6
|
Project-URL: Documentation, https://natefleming.github.io/dao-ai
|
|
@@ -79,7 +79,7 @@ Description-Content-Type: text/markdown
|
|
|
79
79
|
|
|
80
80
|
# DAO: Declarative Agent Orchestration
|
|
81
81
|
|
|
82
|
-
[](CHANGELOG.md)
|
|
83
83
|
[](https://www.python.org/)
|
|
84
84
|
[](LICENSE)
|
|
85
85
|
|
|
@@ -293,6 +293,16 @@ This single command:
|
|
|
293
293
|
3. Deploys it to Databricks
|
|
294
294
|
4. Creates a serving endpoint
|
|
295
295
|
|
|
296
|
+
**Deploying to a specific workspace:**
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
# Deploy to AWS workspace
|
|
300
|
+
dao-ai bundle --deploy --run -c config/my_agent.yaml --profile aws-field-eng
|
|
301
|
+
|
|
302
|
+
# Deploy to Azure workspace
|
|
303
|
+
dao-ai bundle --deploy --run -c config/my_agent.yaml --profile azure-retail
|
|
304
|
+
```
|
|
305
|
+
|
|
296
306
|
**Step 5: Interact with your agent**
|
|
297
307
|
|
|
298
308
|
Once deployed, you can chat with your agent using Python:
|
|
@@ -422,10 +432,34 @@ dao-ai graph -c config/my_config.yaml -o workflow.png
|
|
|
422
432
|
# Deploy with Databricks Asset Bundles
|
|
423
433
|
dao-ai bundle --deploy --run -c config/my_config.yaml
|
|
424
434
|
|
|
435
|
+
# Deploy to a specific workspace (multi-cloud support)
|
|
436
|
+
dao-ai bundle --deploy -c config/my_config.yaml --profile aws-field-eng
|
|
437
|
+
dao-ai bundle --deploy -c config/my_config.yaml --profile azure-retail
|
|
438
|
+
|
|
425
439
|
# Interactive chat with agent
|
|
426
440
|
dao-ai chat -c config/my_config.yaml
|
|
427
441
|
```
|
|
428
442
|
|
|
443
|
+
### Multi-Cloud Deployment
|
|
444
|
+
|
|
445
|
+
DAO AI supports deploying to Azure, AWS, and GCP workspaces with automatic cloud detection:
|
|
446
|
+
|
|
447
|
+
```bash
|
|
448
|
+
# Deploy to AWS workspace
|
|
449
|
+
dao-ai bundle --deploy -c config/my_config.yaml --profile aws-prod
|
|
450
|
+
|
|
451
|
+
# Deploy to Azure workspace
|
|
452
|
+
dao-ai bundle --deploy -c config/my_config.yaml --profile azure-prod
|
|
453
|
+
|
|
454
|
+
# Deploy to GCP workspace
|
|
455
|
+
dao-ai bundle --deploy -c config/my_config.yaml --profile gcp-prod
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
The CLI automatically:
|
|
459
|
+
- Detects the cloud provider from your profile's workspace URL
|
|
460
|
+
- Selects appropriate compute node types for each cloud
|
|
461
|
+
- Creates isolated deployment state per profile
|
|
462
|
+
|
|
429
463
|
👉 **Learn more:** [CLI Reference Documentation](docs/cli-reference.md)
|
|
430
464
|
|
|
431
465
|
---
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
dao_ai/__init__.py,sha256=18P98ExEgUaJ1Byw440Ct1ty59v6nxyWtc5S6Uq2m9Q,1062
|
|
2
2
|
dao_ai/agent_as_code.py,sha256=xIlLDpPVfmDVzLvbdY_V_CrC4Jvj2ItCWJ-NzdrszTo,538
|
|
3
3
|
dao_ai/catalog.py,sha256=sPZpHTD3lPx4EZUtIWeQV7VQM89WJ6YH__wluk1v2lE,4947
|
|
4
|
-
dao_ai/cli.py,sha256=
|
|
5
|
-
dao_ai/config.py,sha256=
|
|
4
|
+
dao_ai/cli.py,sha256=FWzddUceOoER2EIMwBx_mIMhCWFFZHoAL3KHy7f0Euc,35377
|
|
5
|
+
dao_ai/config.py,sha256=GWBmrbiixMG0ZszLk_XTRKRIS0QqOk_TIQhauK--MIY,120863
|
|
6
6
|
dao_ai/graph.py,sha256=1-uQlo7iXZQTT3uU8aYu0N5rnhw5_g_2YLwVsAs6M-U,1119
|
|
7
7
|
dao_ai/logging.py,sha256=lYy4BmucCHvwW7aI3YQkQXKJtMvtTnPDu9Hnd7_O4oc,1556
|
|
8
8
|
dao_ai/messages.py,sha256=4ZBzO4iFdktGSLrmhHzFjzMIt2tpaL-aQLHOQJysGnY,6959
|
|
9
9
|
dao_ai/models.py,sha256=AwzwTRTNZF-UOh59HsuXEgFk_YH6q6M-mERNDe64Z8k,81783
|
|
10
|
-
dao_ai/nodes.py,sha256=
|
|
10
|
+
dao_ai/nodes.py,sha256=7W6Ek6Uk9-pKa-H06nVCwuDllCrgX02IYy3rHtuL0aM,10777
|
|
11
11
|
dao_ai/optimization.py,sha256=phK6t4wYmWPObCjGUBHdZzsaFXGhQOjhAek2bAEfwXo,22971
|
|
12
12
|
dao_ai/prompts.py,sha256=G0ng5f2PkzfgdKrSl03Rnd6riZn5APedof0GAzsWQI8,4792
|
|
13
13
|
dao_ai/state.py,sha256=0wbbzfQmldkCu26gdTE5j0Rl-_pfilza-YIHPbSWlvI,6394
|
|
14
14
|
dao_ai/types.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
15
|
dao_ai/utils.py,sha256=_Urd7Nj2VzrgPKf3NS4E6vt0lWRhEUddBqWN9BksqeE,11543
|
|
16
|
-
dao_ai/vector_search.py,sha256=
|
|
16
|
+
dao_ai/vector_search.py,sha256=8d3xROg9zSIYNXjRRl6rSexsJTlufjRl5Fy1ZA8daKA,4019
|
|
17
17
|
dao_ai/genie/__init__.py,sha256=vdEyGhrt6L8GlK75SyYvTnl8QpHKDCJC5hJKLg4DesQ,1063
|
|
18
18
|
dao_ai/genie/core.py,sha256=HPKbocvhnnw_PkQwfoq5bpgQmL9lZyyS6_goTJL8yiY,1073
|
|
19
19
|
dao_ai/genie/cache/__init__.py,sha256=JfgCJl1NYQ1aZvZ4kly4T6uQK6ZCJ6PX_htuq7nJF50,1203
|
|
@@ -28,21 +28,27 @@ dao_ai/memory/base.py,sha256=99nfr2UZJ4jmfTL_KrqUlRSCoRxzkZyWyx5WqeUoMdQ,338
|
|
|
28
28
|
dao_ai/memory/core.py,sha256=38H-JLIyUrRDIECLvpXK3iJlWG35X97E-DTo_4c3Jzc,6317
|
|
29
29
|
dao_ai/memory/databricks.py,sha256=SM6nwLjhSRJO4hLc3GUuht5YydYtTi3BAOae6jPwTm4,14377
|
|
30
30
|
dao_ai/memory/postgres.py,sha256=q9IIAGs0wuaV-3rUIn4dtzOxbkCCoB-yv1Rtod7ohjI,16467
|
|
31
|
-
dao_ai/middleware/__init__.py,sha256=
|
|
32
|
-
dao_ai/middleware/assertions.py,sha256=
|
|
31
|
+
dao_ai/middleware/__init__.py,sha256=scEk9Mt9POJe-j9e9t5sf3FNMNPBsu15_oxrlV9IkVE,4871
|
|
32
|
+
dao_ai/middleware/assertions.py,sha256=C1K-TnNZfBEwWouioHCt6c48i1ux9QKfQaX6AzghhgE,27408
|
|
33
33
|
dao_ai/middleware/base.py,sha256=uG2tpdnjL5xY5jCKvb_m3UTBtl4ZC6fJQUkDsQvV8S4,1279
|
|
34
|
-
dao_ai/middleware/
|
|
35
|
-
dao_ai/middleware/
|
|
36
|
-
dao_ai/middleware/
|
|
37
|
-
dao_ai/middleware/
|
|
38
|
-
dao_ai/middleware/
|
|
34
|
+
dao_ai/middleware/context_editing.py,sha256=5rNKqH1phFFQTVW-4nzlVH5cbqomD-HFEIy2Z841D4I,7687
|
|
35
|
+
dao_ai/middleware/core.py,sha256=XFzL-A1_jS_pUCw7Q-z1WD0gutmpWZhfCMqHI6ifbhA,2096
|
|
36
|
+
dao_ai/middleware/guardrails.py,sha256=wjH4OwDRagkrKpcGNf7_bf3eJhtMpIWK_RIrrvMeqDs,14023
|
|
37
|
+
dao_ai/middleware/human_in_the_loop.py,sha256=YS11oQcRVzHVuZi309tuXfB4ItGuOz-PRT0fXwupdsY,7537
|
|
38
|
+
dao_ai/middleware/message_validation.py,sha256=SGvXow76BB1eW8zDSSuLJgRkIvLU-WOwA1Cd5_DatmQ,19818
|
|
39
|
+
dao_ai/middleware/model_call_limit.py,sha256=sxv15iNOUMjVLTEXwMBR5zAkxkWbnysbSIsBcuJbNUI,2216
|
|
40
|
+
dao_ai/middleware/model_retry.py,sha256=SlWjAcaEmvj6KBOkjUicChYjhlg7bAJM7-e6KLpHS9Q,3908
|
|
41
|
+
dao_ai/middleware/pii.py,sha256=zetfoz1WlJ-V0vjJp37v8NGimXB27EkZfetUHpGCXno,5137
|
|
42
|
+
dao_ai/middleware/summarization.py,sha256=gp2s9uc4DEJat-mWjWEzMaR-zAAeUOXYvu5EEYtqae4,7143
|
|
43
|
+
dao_ai/middleware/tool_call_limit.py,sha256=WQ3NmA3pLo-pNPBmwM7KwkYpT1segEnWqkhgW1xNkCE,6321
|
|
44
|
+
dao_ai/middleware/tool_retry.py,sha256=QfJ7yTHneME8VtnA88QcmnjXIegSFeJztyngy49wTgM,5568
|
|
39
45
|
dao_ai/orchestration/__init__.py,sha256=i85CLfRR335NcCFhaXABcMkn6WZfXnJ8cHH4YZsZN0s,1622
|
|
40
46
|
dao_ai/orchestration/core.py,sha256=qoU7uMXBJCth-sqfu0jRE1L0GOn5H4LoZdRUY1Ib3DI,9585
|
|
41
|
-
dao_ai/orchestration/supervisor.py,sha256=
|
|
47
|
+
dao_ai/orchestration/supervisor.py,sha256=alKMEEo9G5LhdpMvTVdAMel234cZj5_MguWl4wFB7XQ,9873
|
|
42
48
|
dao_ai/orchestration/swarm.py,sha256=8tp1eGmsQqqWpaDcjPoJckddPWohZdmmN0RGRJ_xzOA,9198
|
|
43
49
|
dao_ai/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
44
50
|
dao_ai/providers/base.py,sha256=-fjKypCOk28h6vioPfMj9YZSw_3Kcbi2nMuAyY7vX9k,1383
|
|
45
|
-
dao_ai/providers/databricks.py,sha256=
|
|
51
|
+
dao_ai/providers/databricks.py,sha256=XxYkyoDYkwGV_Xg1IJBpGOl4d7U5HiFP4RtjjSLgenI,61437
|
|
46
52
|
dao_ai/tools/__init__.py,sha256=SRd7W2DOCXKbWWy8lclRtJiCskz7SDAm94qaFF47urQ,1664
|
|
47
53
|
dao_ai/tools/agent.py,sha256=plIWALywRjaDSnot13nYehBsrHRpBUpsVZakoGeajOE,1858
|
|
48
54
|
dao_ai/tools/core.py,sha256=bRIN3BZhRQX8-Kpu3HPomliodyskCqjxynQmYbk6Vjs,3783
|
|
@@ -57,8 +63,8 @@ dao_ai/tools/sql.py,sha256=tKd1gjpLuKdQDyfmyYYtMiNRHDW6MGRbdEVaeqyB8Ok,7632
|
|
|
57
63
|
dao_ai/tools/time.py,sha256=tufJniwivq29y0LIffbgeBTIDE6VgrLpmVf8Qr90qjw,9224
|
|
58
64
|
dao_ai/tools/unity_catalog.py,sha256=AjQfW7bvV8NurqDLIyntYRv2eJuTwNdbvex1L5CRjOk,15534
|
|
59
65
|
dao_ai/tools/vector_search.py,sha256=oe2uBwl2TfeJIXPpwiS6Rmz7wcHczSxNyqS9P3hE6co,14542
|
|
60
|
-
dao_ai-0.1.
|
|
61
|
-
dao_ai-0.1.
|
|
62
|
-
dao_ai-0.1.
|
|
63
|
-
dao_ai-0.1.
|
|
64
|
-
dao_ai-0.1.
|
|
66
|
+
dao_ai-0.1.5.dist-info/METADATA,sha256=Iz_x_25jQut9i8yghsPmQV08iyLBJdrXTAjFmYOjx1Q,16685
|
|
67
|
+
dao_ai-0.1.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
68
|
+
dao_ai-0.1.5.dist-info/entry_points.txt,sha256=Xa-UFyc6gWGwMqMJOt06ZOog2vAfygV_DSwg1AiP46g,43
|
|
69
|
+
dao_ai-0.1.5.dist-info/licenses/LICENSE,sha256=YZt3W32LtPYruuvHE9lGk2bw6ZPMMJD8yLrjgHybyz4,1069
|
|
70
|
+
dao_ai-0.1.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|