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.
Files changed (70) hide show
  1. dao_ai/__init__.py +29 -0
  2. dao_ai/agent_as_code.py +2 -5
  3. dao_ai/cli.py +342 -58
  4. dao_ai/config.py +1610 -380
  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 +158 -0
  22. dao_ai/middleware/assertions.py +806 -0
  23. dao_ai/middleware/base.py +50 -0
  24. dao_ai/middleware/context_editing.py +230 -0
  25. dao_ai/middleware/core.py +67 -0
  26. dao_ai/middleware/guardrails.py +420 -0
  27. dao_ai/middleware/human_in_the_loop.py +233 -0
  28. dao_ai/middleware/message_validation.py +586 -0
  29. dao_ai/middleware/model_call_limit.py +77 -0
  30. dao_ai/middleware/model_retry.py +121 -0
  31. dao_ai/middleware/pii.py +157 -0
  32. dao_ai/middleware/summarization.py +197 -0
  33. dao_ai/middleware/tool_call_limit.py +210 -0
  34. dao_ai/middleware/tool_retry.py +174 -0
  35. dao_ai/models.py +1306 -114
  36. dao_ai/nodes.py +240 -161
  37. dao_ai/optimization.py +674 -0
  38. dao_ai/orchestration/__init__.py +52 -0
  39. dao_ai/orchestration/core.py +294 -0
  40. dao_ai/orchestration/supervisor.py +279 -0
  41. dao_ai/orchestration/swarm.py +271 -0
  42. dao_ai/prompts.py +128 -31
  43. dao_ai/providers/databricks.py +584 -601
  44. dao_ai/state.py +157 -21
  45. dao_ai/tools/__init__.py +13 -5
  46. dao_ai/tools/agent.py +1 -3
  47. dao_ai/tools/core.py +64 -11
  48. dao_ai/tools/email.py +232 -0
  49. dao_ai/tools/genie.py +144 -294
  50. dao_ai/tools/mcp.py +223 -155
  51. dao_ai/tools/memory.py +50 -0
  52. dao_ai/tools/python.py +9 -14
  53. dao_ai/tools/search.py +14 -0
  54. dao_ai/tools/slack.py +22 -10
  55. dao_ai/tools/sql.py +202 -0
  56. dao_ai/tools/time.py +30 -7
  57. dao_ai/tools/unity_catalog.py +165 -88
  58. dao_ai/tools/vector_search.py +331 -221
  59. dao_ai/utils.py +166 -20
  60. dao_ai/vector_search.py +37 -0
  61. dao_ai-0.1.5.dist-info/METADATA +489 -0
  62. dao_ai-0.1.5.dist-info/RECORD +70 -0
  63. dao_ai/chat_models.py +0 -204
  64. dao_ai/guardrails.py +0 -112
  65. dao_ai/tools/human_in_the_loop.py +0 -100
  66. dao_ai-0.0.28.dist-info/METADATA +0 -1168
  67. dao_ai-0.0.28.dist-info/RECORD +0 -41
  68. {dao_ai-0.0.28.dist-info → dao_ai-0.1.5.dist-info}/WHEEL +0 -0
  69. {dao_ai-0.0.28.dist-info → dao_ai-0.1.5.dist-info}/entry_points.txt +0 -0
  70. {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