dao-ai 0.1.1__py3-none-any.whl → 0.1.3__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/agent_as_code.py +2 -5
- dao_ai/cli.py +65 -15
- dao_ai/config.py +672 -218
- dao_ai/genie/cache/core.py +6 -2
- dao_ai/genie/cache/lru.py +29 -11
- dao_ai/genie/cache/semantic.py +95 -44
- dao_ai/hooks/core.py +5 -5
- dao_ai/logging.py +56 -0
- dao_ai/memory/core.py +61 -44
- dao_ai/memory/databricks.py +54 -41
- dao_ai/memory/postgres.py +77 -36
- dao_ai/middleware/assertions.py +45 -17
- dao_ai/middleware/core.py +13 -7
- dao_ai/middleware/guardrails.py +30 -25
- dao_ai/middleware/human_in_the_loop.py +9 -5
- dao_ai/middleware/message_validation.py +61 -29
- dao_ai/middleware/summarization.py +16 -11
- dao_ai/models.py +172 -69
- dao_ai/nodes.py +148 -19
- dao_ai/optimization.py +26 -16
- dao_ai/orchestration/core.py +15 -8
- dao_ai/orchestration/supervisor.py +22 -8
- dao_ai/orchestration/swarm.py +57 -12
- dao_ai/prompts.py +17 -17
- dao_ai/providers/databricks.py +365 -155
- dao_ai/state.py +24 -6
- dao_ai/tools/__init__.py +2 -0
- dao_ai/tools/agent.py +1 -3
- dao_ai/tools/core.py +7 -7
- dao_ai/tools/email.py +29 -77
- dao_ai/tools/genie.py +18 -13
- dao_ai/tools/mcp.py +223 -156
- dao_ai/tools/python.py +5 -2
- dao_ai/tools/search.py +1 -1
- dao_ai/tools/slack.py +21 -9
- dao_ai/tools/sql.py +202 -0
- dao_ai/tools/time.py +30 -7
- dao_ai/tools/unity_catalog.py +129 -86
- dao_ai/tools/vector_search.py +318 -244
- dao_ai/utils.py +15 -10
- dao_ai-0.1.3.dist-info/METADATA +455 -0
- dao_ai-0.1.3.dist-info/RECORD +64 -0
- dao_ai-0.1.1.dist-info/METADATA +0 -1878
- dao_ai-0.1.1.dist-info/RECORD +0 -62
- {dao_ai-0.1.1.dist-info → dao_ai-0.1.3.dist-info}/WHEEL +0 -0
- {dao_ai-0.1.1.dist-info → dao_ai-0.1.3.dist-info}/entry_points.txt +0 -0
- {dao_ai-0.1.1.dist-info → dao_ai-0.1.3.dist-info}/licenses/LICENSE +0 -0
dao_ai/state.py
CHANGED
|
@@ -15,7 +15,7 @@ from datetime import datetime
|
|
|
15
15
|
from typing import Any, Optional
|
|
16
16
|
|
|
17
17
|
from langgraph.graph import MessagesState
|
|
18
|
-
from pydantic import BaseModel, Field
|
|
18
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
19
19
|
from typing_extensions import NotRequired
|
|
20
20
|
|
|
21
21
|
|
|
@@ -133,15 +133,18 @@ class Context(BaseModel):
|
|
|
133
133
|
This is passed to tools and middleware via the runtime parameter.
|
|
134
134
|
Access via ToolRuntime[Context] in tools or Runtime[Context] in middleware.
|
|
135
135
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
136
|
+
Additional fields beyond user_id and thread_id can be added dynamically
|
|
137
|
+
and will be available as top-level attributes on the context object.
|
|
138
|
+
These fields are:
|
|
139
|
+
- Used as template parameters in prompts (all fields are applied)
|
|
140
|
+
- Validated by middleware (check for specific fields like "store_num")
|
|
141
|
+
- Accessible as direct attributes (e.g., context.store_num)
|
|
139
142
|
|
|
140
143
|
Example:
|
|
141
144
|
@tool
|
|
142
145
|
def my_tool(runtime: ToolRuntime[Context]) -> str:
|
|
143
146
|
user_id = runtime.context.user_id
|
|
144
|
-
store_num = runtime.context.
|
|
147
|
+
store_num = runtime.context.store_num # Direct attribute access
|
|
145
148
|
return f"Hello, {user_id} at store {store_num}!"
|
|
146
149
|
|
|
147
150
|
class MyMiddleware(AgentMiddleware[AgentState, Context]):
|
|
@@ -151,9 +154,24 @@ class Context(BaseModel):
|
|
|
151
154
|
runtime: Runtime[Context]
|
|
152
155
|
) -> dict[str, Any] | None:
|
|
153
156
|
user_id = runtime.context.user_id
|
|
157
|
+
store_num = getattr(runtime.context, "store_num", None)
|
|
154
158
|
return None
|
|
155
159
|
"""
|
|
156
160
|
|
|
161
|
+
model_config = ConfigDict(
|
|
162
|
+
extra="allow"
|
|
163
|
+
) # Allow extra fields as top-level attributes
|
|
164
|
+
|
|
157
165
|
user_id: str | None = None
|
|
158
166
|
thread_id: str | None = None
|
|
159
|
-
|
|
167
|
+
|
|
168
|
+
@classmethod
|
|
169
|
+
def from_runnable_config(cls, config: dict[str, Any]) -> "Context":
|
|
170
|
+
"""
|
|
171
|
+
Create Context from LangChain RunnableConfig.
|
|
172
|
+
|
|
173
|
+
This method is called by LangChain when context_schema is provided to create_agent.
|
|
174
|
+
It extracts the 'configurable' dict from the config and uses it to instantiate Context.
|
|
175
|
+
"""
|
|
176
|
+
configurable = config.get("configurable", {})
|
|
177
|
+
return cls(**configurable)
|
dao_ai/tools/__init__.py
CHANGED
|
@@ -9,6 +9,7 @@ from dao_ai.tools.memory import create_search_memory_tool
|
|
|
9
9
|
from dao_ai.tools.python import create_factory_tool, create_python_tool
|
|
10
10
|
from dao_ai.tools.search import create_search_tool
|
|
11
11
|
from dao_ai.tools.slack import create_send_slack_message_tool
|
|
12
|
+
from dao_ai.tools.sql import create_execute_statement_tool
|
|
12
13
|
from dao_ai.tools.time import (
|
|
13
14
|
add_time_tool,
|
|
14
15
|
current_time_tool,
|
|
@@ -24,6 +25,7 @@ from dao_ai.tools.vector_search import create_vector_search_tool
|
|
|
24
25
|
__all__ = [
|
|
25
26
|
"add_time_tool",
|
|
26
27
|
"create_agent_endpoint_tool",
|
|
28
|
+
"create_execute_statement_tool",
|
|
27
29
|
"create_factory_tool",
|
|
28
30
|
"create_genie_tool",
|
|
29
31
|
"create_hooks",
|
dao_ai/tools/agent.py
CHANGED
|
@@ -14,9 +14,7 @@ def create_agent_endpoint_tool(
|
|
|
14
14
|
name: Optional[str] = None,
|
|
15
15
|
description: Optional[str] = None,
|
|
16
16
|
) -> Callable[..., Any]:
|
|
17
|
-
logger.debug(
|
|
18
|
-
f"Creating agent endpoint tool with name: {name} and description: {description}"
|
|
19
|
-
)
|
|
17
|
+
logger.debug("Creating agent endpoint tool", name=name, description=description)
|
|
20
18
|
|
|
21
19
|
default_description: str = dedent("""
|
|
22
20
|
This tool allows you to interact with a language model endpoint to answer questions.
|
dao_ai/tools/core.py
CHANGED
|
@@ -36,7 +36,7 @@ def create_tools(tool_models: Sequence[ToolModel]) -> Sequence[RunnableLike]:
|
|
|
36
36
|
Each tool is created according to its type and parameters defined in the configuration.
|
|
37
37
|
|
|
38
38
|
Args:
|
|
39
|
-
|
|
39
|
+
tool_models: A sequence of ToolModel configurations
|
|
40
40
|
|
|
41
41
|
Returns:
|
|
42
42
|
A sequence of BaseTool objects created from the provided configurations
|
|
@@ -47,24 +47,24 @@ def create_tools(tool_models: Sequence[ToolModel]) -> Sequence[RunnableLike]:
|
|
|
47
47
|
for tool_config in tool_models:
|
|
48
48
|
name: str = tool_config.name
|
|
49
49
|
if name in tools:
|
|
50
|
-
logger.warning(
|
|
50
|
+
logger.warning("Tools already registered, skipping", tool_name=name)
|
|
51
51
|
continue
|
|
52
52
|
registered_tools: Sequence[RunnableLike] | None = tool_registry.get(name)
|
|
53
53
|
if registered_tools is None:
|
|
54
|
-
logger.
|
|
54
|
+
logger.trace("Creating tools", tool_name=name)
|
|
55
55
|
function: AnyTool = tool_config.function
|
|
56
56
|
registered_tools = create_hooks(function)
|
|
57
|
-
logger.
|
|
57
|
+
logger.trace("Registering tools", tool_name=name)
|
|
58
58
|
tool_registry[name] = registered_tools
|
|
59
59
|
else:
|
|
60
|
-
logger.
|
|
60
|
+
logger.trace("Tools already registered", tool_name=name)
|
|
61
61
|
|
|
62
62
|
tools[name] = registered_tools
|
|
63
63
|
|
|
64
64
|
all_tools: Sequence[RunnableLike] = [
|
|
65
65
|
t for tool_list in tools.values() for t in tool_list
|
|
66
66
|
]
|
|
67
|
-
logger.debug(
|
|
67
|
+
logger.debug("Tools created", tools_count=len(all_tools))
|
|
68
68
|
return all_tools
|
|
69
69
|
|
|
70
70
|
|
|
@@ -101,7 +101,7 @@ def say_hello_tool(
|
|
|
101
101
|
# Use provided name, or fall back to user_id from context
|
|
102
102
|
if name is None:
|
|
103
103
|
if runtime and runtime.context:
|
|
104
|
-
user_id = runtime.context.user_id
|
|
104
|
+
user_id: str | None = runtime.context.user_id
|
|
105
105
|
if user_id:
|
|
106
106
|
name = user_id
|
|
107
107
|
else:
|
dao_ai/tools/email.py
CHANGED
|
@@ -107,70 +107,41 @@ def create_send_email_tool(
|
|
|
107
107
|
key: smtp_password
|
|
108
108
|
```
|
|
109
109
|
"""
|
|
110
|
-
logger.info("=== Creating send_email_tool ===")
|
|
111
110
|
logger.debug(
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
"Creating send_email_tool",
|
|
112
|
+
config_type=type(smtp_config).__name__,
|
|
113
|
+
tool_name=name,
|
|
114
114
|
)
|
|
115
115
|
|
|
116
116
|
# Convert dict to SMTPConfigModel if needed
|
|
117
117
|
if isinstance(smtp_config, dict):
|
|
118
|
-
logger.debug("Converting dict config to SMTPConfigModel")
|
|
119
118
|
smtp_config = SMTPConfigModel(**smtp_config)
|
|
120
|
-
else:
|
|
121
|
-
logger.debug("Config already is SMTPConfigModel")
|
|
122
119
|
|
|
123
120
|
# Resolve all variable values
|
|
124
|
-
logger.debug("Resolving SMTP configuration variables...")
|
|
125
|
-
|
|
126
|
-
logger.debug(" - Resolving host")
|
|
127
121
|
host: str = value_of(smtp_config.host)
|
|
128
|
-
logger.debug(f" Host resolved: {host}")
|
|
129
|
-
|
|
130
|
-
logger.debug(" - Resolving port")
|
|
131
122
|
port: int = int(value_of(smtp_config.port))
|
|
132
|
-
logger.debug(f" Port resolved: {port}")
|
|
133
|
-
|
|
134
|
-
logger.debug(" - Resolving username")
|
|
135
123
|
username: str = value_of(smtp_config.username)
|
|
136
|
-
logger.debug(f" Username resolved: {username}")
|
|
137
|
-
|
|
138
|
-
logger.debug(" - Resolving password")
|
|
139
124
|
password: str = value_of(smtp_config.password)
|
|
140
|
-
logger.debug(
|
|
141
|
-
f" Password resolved: {'*' * len(password) if password else 'None'}"
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
logger.debug(" - Resolving sender_email")
|
|
145
125
|
sender_email: str = (
|
|
146
126
|
value_of(smtp_config.sender_email) if smtp_config.sender_email else username
|
|
147
127
|
)
|
|
148
|
-
logger.debug(
|
|
149
|
-
f" Sender email resolved: {sender_email} "
|
|
150
|
-
f"({'from sender_email' if smtp_config.sender_email else 'defaulted to username'})"
|
|
151
|
-
)
|
|
152
|
-
|
|
153
128
|
use_tls: bool = smtp_config.use_tls
|
|
154
|
-
logger.debug(f" - TLS enabled: {use_tls}")
|
|
155
129
|
|
|
156
130
|
logger.info(
|
|
157
|
-
|
|
158
|
-
|
|
131
|
+
"SMTP configuration resolved",
|
|
132
|
+
host=host,
|
|
133
|
+
port=port,
|
|
134
|
+
sender=sender_email,
|
|
135
|
+
use_tls=use_tls,
|
|
136
|
+
password_set=bool(password),
|
|
159
137
|
)
|
|
160
138
|
|
|
161
139
|
if name is None:
|
|
162
140
|
name = "send_email"
|
|
163
|
-
logger.debug(f"Tool name defaulted to: {name}")
|
|
164
|
-
else:
|
|
165
|
-
logger.debug(f"Tool name set to: {name}")
|
|
166
|
-
|
|
167
141
|
if description is None:
|
|
168
142
|
description = "Send an email to a recipient with subject and body content"
|
|
169
|
-
logger.debug("Tool description using default")
|
|
170
|
-
else:
|
|
171
|
-
logger.debug(f"Tool description set to: {description}")
|
|
172
143
|
|
|
173
|
-
logger.
|
|
144
|
+
logger.debug("Creating email tool with decorator", tool_name=name)
|
|
174
145
|
|
|
175
146
|
@tool(
|
|
176
147
|
name_or_callable=name,
|
|
@@ -194,87 +165,68 @@ def create_send_email_tool(
|
|
|
194
165
|
Returns:
|
|
195
166
|
str: Success or error message
|
|
196
167
|
"""
|
|
197
|
-
logger.info(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
logger.info(f" Body length: {len(body)} characters")
|
|
201
|
-
logger.info(f" CC: {cc if cc else 'None'}")
|
|
168
|
+
logger.info(
|
|
169
|
+
"Sending email", to=to, subject=subject, body_length=len(body), cc=cc
|
|
170
|
+
)
|
|
202
171
|
|
|
203
172
|
try:
|
|
204
|
-
logger.debug("Constructing email message...")
|
|
205
|
-
|
|
206
173
|
# Create message
|
|
207
174
|
msg = MIMEMultipart()
|
|
208
175
|
msg["From"] = sender_email
|
|
209
176
|
msg["To"] = to
|
|
210
177
|
msg["Subject"] = subject
|
|
211
|
-
logger.debug(f" From: {sender_email}")
|
|
212
|
-
logger.debug(f" To: {to}")
|
|
213
|
-
logger.debug(f" Subject: {subject}")
|
|
214
178
|
|
|
215
179
|
if cc:
|
|
216
180
|
msg["Cc"] = cc
|
|
217
|
-
logger.debug(f" CC: {cc}")
|
|
218
181
|
|
|
219
182
|
# Attach body as plain text
|
|
220
183
|
msg.attach(MIMEText(body, "plain"))
|
|
221
|
-
logger.debug(f" Body attached ({len(body)} chars)")
|
|
222
184
|
|
|
223
185
|
# Send email
|
|
224
|
-
logger.
|
|
186
|
+
logger.debug("Connecting to SMTP server", host=host, port=port)
|
|
225
187
|
with smtplib.SMTP(host, port) as server:
|
|
226
|
-
logger.debug("SMTP connection established")
|
|
227
|
-
|
|
228
188
|
if use_tls:
|
|
229
|
-
logger.
|
|
189
|
+
logger.trace("Upgrading to TLS")
|
|
230
190
|
server.starttls()
|
|
231
|
-
logger.debug("TLS upgrade successful")
|
|
232
191
|
|
|
233
|
-
logger.
|
|
192
|
+
logger.trace("Authenticating", username=username)
|
|
234
193
|
server.login(username, password)
|
|
235
|
-
logger.info("SMTP authentication successful")
|
|
236
194
|
|
|
237
195
|
# Build recipient list
|
|
238
196
|
recipients = [to]
|
|
239
197
|
if cc:
|
|
240
198
|
cc_addresses = [addr.strip() for addr in cc.split(",")]
|
|
241
199
|
recipients.extend(cc_addresses)
|
|
242
|
-
logger.debug(f"Total recipients: {len(recipients)} ({recipients})")
|
|
243
|
-
else:
|
|
244
|
-
logger.debug(f"Single recipient: {to}")
|
|
245
200
|
|
|
246
|
-
logger.
|
|
201
|
+
logger.debug("Sending message", recipients_count=len(recipients))
|
|
247
202
|
server.send_message(msg)
|
|
248
|
-
logger.info("Message sent successfully via SMTP")
|
|
249
203
|
|
|
250
204
|
success_msg = f"✓ Email sent successfully to {to}"
|
|
251
205
|
if cc:
|
|
252
206
|
success_msg += f" (cc: {cc})"
|
|
253
207
|
|
|
254
|
-
logger.
|
|
255
|
-
logger.info("=== send_email completed successfully ===")
|
|
208
|
+
logger.success("Email sent successfully", to=to, cc=cc)
|
|
256
209
|
return success_msg
|
|
257
210
|
|
|
258
211
|
except smtplib.SMTPAuthenticationError as e:
|
|
259
212
|
error_msg = f"✗ SMTP authentication failed: {str(e)}"
|
|
260
|
-
logger.error(
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
213
|
+
logger.error(
|
|
214
|
+
"SMTP authentication failed",
|
|
215
|
+
server=f"{host}:{port}",
|
|
216
|
+
username=username,
|
|
217
|
+
error=str(e),
|
|
218
|
+
)
|
|
264
219
|
return error_msg
|
|
265
220
|
except smtplib.SMTPException as e:
|
|
266
221
|
error_msg = f"✗ SMTP error: {str(e)}"
|
|
267
|
-
logger.error(
|
|
268
|
-
logger.error(f" Server: {host}:{port}")
|
|
269
|
-
logger.error("=== send_email failed (SMTP error) ===")
|
|
222
|
+
logger.error("SMTP error", server=f"{host}:{port}", error=str(e))
|
|
270
223
|
return error_msg
|
|
271
224
|
except Exception as e:
|
|
272
225
|
error_msg = f"✗ Failed to send email: {str(e)}"
|
|
273
|
-
logger.error(
|
|
274
|
-
|
|
275
|
-
|
|
226
|
+
logger.error(
|
|
227
|
+
"Failed to send email", error_type=type(e).__name__, error=str(e)
|
|
228
|
+
)
|
|
276
229
|
return error_msg
|
|
277
230
|
|
|
278
|
-
logger.
|
|
279
|
-
logger.info("=== send_email_tool creation complete ===")
|
|
231
|
+
logger.success("Email tool created", tool_name=name)
|
|
280
232
|
return send_email
|
dao_ai/tools/genie.py
CHANGED
|
@@ -89,15 +89,15 @@ def create_genie_tool(
|
|
|
89
89
|
Returns:
|
|
90
90
|
A LangGraph tool that processes natural language queries through Genie
|
|
91
91
|
"""
|
|
92
|
-
logger.debug(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
92
|
+
logger.debug(
|
|
93
|
+
"Creating Genie tool",
|
|
94
|
+
genie_room_type=type(genie_room).__name__,
|
|
95
|
+
persist_conversation=persist_conversation,
|
|
96
|
+
truncate_results=truncate_results,
|
|
97
|
+
name=name,
|
|
98
|
+
has_lru_cache=lru_cache_parameters is not None,
|
|
99
|
+
has_semantic_cache=semantic_cache_parameters is not None,
|
|
100
|
+
)
|
|
101
101
|
|
|
102
102
|
if isinstance(genie_room, dict):
|
|
103
103
|
genie_room = GenieRoomModel(**genie_room)
|
|
@@ -188,8 +188,10 @@ GenieResponse: A response object containing the conversation ID and result from
|
|
|
188
188
|
existing_conversation_id: str | None = session.genie.get_conversation_id(
|
|
189
189
|
space_id_str
|
|
190
190
|
)
|
|
191
|
-
logger.
|
|
192
|
-
|
|
191
|
+
logger.trace(
|
|
192
|
+
"Using existing conversation ID",
|
|
193
|
+
space_id=space_id_str,
|
|
194
|
+
conversation_id=existing_conversation_id,
|
|
193
195
|
)
|
|
194
196
|
|
|
195
197
|
# Call ask_question which always returns CacheResult with cache metadata
|
|
@@ -202,8 +204,11 @@ GenieResponse: A response object containing the conversation ID and result from
|
|
|
202
204
|
|
|
203
205
|
current_conversation_id: str = genie_response.conversation_id
|
|
204
206
|
logger.debug(
|
|
205
|
-
|
|
206
|
-
|
|
207
|
+
"Genie question answered",
|
|
208
|
+
space_id=space_id_str,
|
|
209
|
+
conversation_id=current_conversation_id,
|
|
210
|
+
cache_hit=cache_hit,
|
|
211
|
+
cache_key=cache_key,
|
|
207
212
|
)
|
|
208
213
|
|
|
209
214
|
# Update session state with cache information
|