dao-ai 0.1.2__py3-none-any.whl → 0.1.20__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/apps/__init__.py +24 -0
- dao_ai/apps/handlers.py +105 -0
- dao_ai/apps/model_serving.py +29 -0
- dao_ai/apps/resources.py +1122 -0
- dao_ai/apps/server.py +39 -0
- dao_ai/cli.py +546 -37
- dao_ai/config.py +1179 -139
- dao_ai/evaluation.py +543 -0
- dao_ai/genie/__init__.py +55 -7
- dao_ai/genie/cache/__init__.py +34 -7
- dao_ai/genie/cache/base.py +143 -2
- dao_ai/genie/cache/context_aware/__init__.py +31 -0
- dao_ai/genie/cache/context_aware/base.py +1151 -0
- dao_ai/genie/cache/context_aware/in_memory.py +609 -0
- dao_ai/genie/cache/context_aware/persistent.py +802 -0
- dao_ai/genie/cache/context_aware/postgres.py +1166 -0
- dao_ai/genie/cache/core.py +1 -1
- dao_ai/genie/cache/lru.py +257 -75
- dao_ai/genie/cache/optimization.py +890 -0
- dao_ai/genie/core.py +235 -11
- dao_ai/memory/postgres.py +175 -39
- dao_ai/middleware/__init__.py +38 -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/middleware/tool_selector.py +129 -0
- dao_ai/models.py +327 -370
- dao_ai/nodes.py +9 -16
- dao_ai/orchestration/core.py +33 -9
- dao_ai/orchestration/supervisor.py +29 -13
- dao_ai/orchestration/swarm.py +6 -1
- dao_ai/{prompts.py → prompts/__init__.py} +12 -61
- dao_ai/prompts/instructed_retriever_decomposition.yaml +58 -0
- dao_ai/prompts/instruction_reranker.yaml +14 -0
- dao_ai/prompts/router.yaml +37 -0
- dao_ai/prompts/verifier.yaml +46 -0
- dao_ai/providers/base.py +28 -2
- dao_ai/providers/databricks.py +363 -33
- dao_ai/state.py +1 -0
- dao_ai/tools/__init__.py +5 -3
- dao_ai/tools/genie.py +103 -26
- dao_ai/tools/instructed_retriever.py +366 -0
- dao_ai/tools/instruction_reranker.py +202 -0
- dao_ai/tools/mcp.py +539 -97
- dao_ai/tools/router.py +89 -0
- dao_ai/tools/slack.py +13 -2
- dao_ai/tools/sql.py +7 -3
- dao_ai/tools/unity_catalog.py +32 -10
- dao_ai/tools/vector_search.py +493 -160
- dao_ai/tools/verifier.py +159 -0
- dao_ai/utils.py +182 -2
- dao_ai/vector_search.py +46 -1
- {dao_ai-0.1.2.dist-info → dao_ai-0.1.20.dist-info}/METADATA +45 -9
- dao_ai-0.1.20.dist-info/RECORD +89 -0
- dao_ai/agent_as_code.py +0 -22
- dao_ai/genie/cache/semantic.py +0 -970
- dao_ai-0.1.2.dist-info/RECORD +0 -64
- {dao_ai-0.1.2.dist-info → dao_ai-0.1.20.dist-info}/WHEEL +0 -0
- {dao_ai-0.1.2.dist-info → dao_ai-0.1.20.dist-info}/entry_points.txt +0 -0
- {dao_ai-0.1.2.dist-info → dao_ai-0.1.20.dist-info}/licenses/LICENSE +0 -0
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",
|
|
@@ -266,8 +259,6 @@ def create_agent_node(
|
|
|
266
259
|
else:
|
|
267
260
|
logger.debug("No custom prompt configured", agent=agent.name)
|
|
268
261
|
|
|
269
|
-
checkpointer: bool = memory is not None and memory.checkpointer is not None
|
|
270
|
-
|
|
271
262
|
# Get the prompt as middleware (always returns AgentMiddleware or None)
|
|
272
263
|
prompt_middleware: AgentMiddleware | None = make_prompt(agent.prompt)
|
|
273
264
|
|
|
@@ -298,12 +289,14 @@ def create_agent_node(
|
|
|
298
289
|
# Use LangChain v1's create_agent with middleware
|
|
299
290
|
# AgentState extends MessagesState with additional DAO AI fields
|
|
300
291
|
# System prompt is provided via middleware (dynamic_prompt)
|
|
292
|
+
# NOTE: checkpointer=False because these agents are used as subgraphs
|
|
293
|
+
# within the parent orchestration graph (swarm/supervisor) which handles
|
|
294
|
+
# checkpointing at the root level. Subgraphs cannot have checkpointer=True.
|
|
301
295
|
logger.info(
|
|
302
296
|
"Creating LangChain agent",
|
|
303
297
|
agent=agent.name,
|
|
304
298
|
tools_count=len(tools),
|
|
305
299
|
middleware_count=len(middleware_list),
|
|
306
|
-
has_checkpointer=checkpointer,
|
|
307
300
|
)
|
|
308
301
|
|
|
309
302
|
compiled_agent: CompiledStateGraph = create_agent(
|
|
@@ -311,7 +304,7 @@ def create_agent_node(
|
|
|
311
304
|
model=llm,
|
|
312
305
|
tools=tools,
|
|
313
306
|
middleware=middleware_list,
|
|
314
|
-
checkpointer=
|
|
307
|
+
checkpointer=False,
|
|
315
308
|
state_schema=AgentState,
|
|
316
309
|
context_schema=Context,
|
|
317
310
|
response_format=response_format, # Add structured output support
|
dao_ai/orchestration/core.py
CHANGED
|
@@ -9,7 +9,7 @@ This module provides the foundational utilities for multi-agent orchestration:
|
|
|
9
9
|
- Main orchestration graph factory
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
-
from typing import Awaitable, Callable, Literal
|
|
12
|
+
from typing import Any, Awaitable, Callable, Literal
|
|
13
13
|
|
|
14
14
|
from langchain.tools import ToolRuntime, tool
|
|
15
15
|
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, ToolMessage
|
|
@@ -179,8 +179,16 @@ def create_agent_node_handler(
|
|
|
179
179
|
"messages": filtered_messages,
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
#
|
|
183
|
-
|
|
182
|
+
# Build config with configurable from context for langmem compatibility
|
|
183
|
+
# langmem tools expect user_id to be in config.configurable
|
|
184
|
+
config: dict[str, Any] = {}
|
|
185
|
+
if runtime.context:
|
|
186
|
+
config = {"configurable": runtime.context.model_dump()}
|
|
187
|
+
|
|
188
|
+
# Invoke the agent with both context and config
|
|
189
|
+
result: AgentState = await agent.ainvoke(
|
|
190
|
+
agent_state, context=runtime.context, config=config
|
|
191
|
+
)
|
|
184
192
|
|
|
185
193
|
# Extract agent response based on output mode
|
|
186
194
|
result_messages = result.get("messages", [])
|
|
@@ -227,15 +235,31 @@ def create_handoff_tool(
|
|
|
227
235
|
tool_call_id: str = runtime.tool_call_id
|
|
228
236
|
logger.debug("Handoff to agent", target_agent=target_agent_name)
|
|
229
237
|
|
|
238
|
+
# Get the AIMessage that triggered this handoff (required for tool_use/tool_result pairing)
|
|
239
|
+
# LLMs expect tool calls to be paired with their responses, so we must include both
|
|
240
|
+
# the AIMessage containing the tool call and the ToolMessage acknowledging it.
|
|
241
|
+
messages: list[BaseMessage] = runtime.state.get("messages", [])
|
|
242
|
+
last_ai_message: AIMessage | None = None
|
|
243
|
+
for msg in reversed(messages):
|
|
244
|
+
if isinstance(msg, AIMessage) and msg.tool_calls:
|
|
245
|
+
last_ai_message = msg
|
|
246
|
+
break
|
|
247
|
+
|
|
248
|
+
# Build message list with proper pairing
|
|
249
|
+
update_messages: list[BaseMessage] = []
|
|
250
|
+
if last_ai_message:
|
|
251
|
+
update_messages.append(last_ai_message)
|
|
252
|
+
update_messages.append(
|
|
253
|
+
ToolMessage(
|
|
254
|
+
content=f"Transferred to {target_agent_name}",
|
|
255
|
+
tool_call_id=tool_call_id,
|
|
256
|
+
)
|
|
257
|
+
)
|
|
258
|
+
|
|
230
259
|
return Command(
|
|
231
260
|
update={
|
|
232
261
|
"active_agent": target_agent_name,
|
|
233
|
-
"messages":
|
|
234
|
-
ToolMessage(
|
|
235
|
-
content=f"Transferred to {target_agent_name}",
|
|
236
|
-
tool_call_id=tool_call_id,
|
|
237
|
-
)
|
|
238
|
-
],
|
|
262
|
+
"messages": update_messages,
|
|
239
263
|
},
|
|
240
264
|
goto=target_agent_name,
|
|
241
265
|
graph=Command.PARENT,
|
|
@@ -13,7 +13,7 @@ from langchain.agents import create_agent
|
|
|
13
13
|
from langchain.agents.middleware import AgentMiddleware as LangchainAgentMiddleware
|
|
14
14
|
from langchain.tools import ToolRuntime, tool
|
|
15
15
|
from langchain_core.language_models import LanguageModelLike
|
|
16
|
-
from langchain_core.messages import ToolMessage
|
|
16
|
+
from langchain_core.messages import AIMessage, BaseMessage, ToolMessage
|
|
17
17
|
from langchain_core.tools import BaseTool
|
|
18
18
|
from langgraph.checkpoint.base import BaseCheckpointSaver
|
|
19
19
|
from langgraph.graph import StateGraph
|
|
@@ -75,15 +75,30 @@ def _create_handoff_back_to_supervisor_tool() -> BaseTool:
|
|
|
75
75
|
tool_call_id: str = runtime.tool_call_id
|
|
76
76
|
logger.debug("Agent handing back to supervisor", summary_preview=summary[:100])
|
|
77
77
|
|
|
78
|
+
# Get the AIMessage that triggered this handoff (required for tool_use/tool_result pairing)
|
|
79
|
+
# LLMs expect tool calls to be paired with their responses, so we must include both
|
|
80
|
+
# the AIMessage containing the tool call and the ToolMessage acknowledging it.
|
|
81
|
+
messages: list[BaseMessage] = runtime.state.get("messages", [])
|
|
82
|
+
last_ai_message: AIMessage | None = None
|
|
83
|
+
for msg in reversed(messages):
|
|
84
|
+
if isinstance(msg, AIMessage) and msg.tool_calls:
|
|
85
|
+
last_ai_message = msg
|
|
86
|
+
break
|
|
87
|
+
|
|
88
|
+
# Build message list with proper pairing
|
|
89
|
+
update_messages: list[BaseMessage] = []
|
|
90
|
+
if last_ai_message:
|
|
91
|
+
update_messages.append(last_ai_message)
|
|
92
|
+
update_messages.append(
|
|
93
|
+
ToolMessage(
|
|
94
|
+
content=f"Task completed: {summary}",
|
|
95
|
+
tool_call_id=tool_call_id,
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
|
|
78
99
|
return Command(
|
|
79
100
|
update={
|
|
80
|
-
"
|
|
81
|
-
"messages": [
|
|
82
|
-
ToolMessage(
|
|
83
|
-
content=f"Task completed: {summary}",
|
|
84
|
-
tool_call_id=tool_call_id,
|
|
85
|
-
)
|
|
86
|
-
],
|
|
101
|
+
"messages": update_messages,
|
|
87
102
|
},
|
|
88
103
|
goto=SUPERVISOR_NODE,
|
|
89
104
|
graph=Command.PARENT,
|
|
@@ -190,6 +205,7 @@ def create_supervisor_graph(config: AppConfig) -> CompiledStateGraph:
|
|
|
190
205
|
supervisor_tools: list[BaseTool] = list(create_tools(supervisor_config.tools))
|
|
191
206
|
|
|
192
207
|
# Create middleware from configuration
|
|
208
|
+
# All middleware factories return list[AgentMiddleware] for composability
|
|
193
209
|
middlewares: list[AgentMiddleware] = []
|
|
194
210
|
|
|
195
211
|
for middleware_config in supervisor_config.middleware:
|
|
@@ -201,11 +217,11 @@ def create_supervisor_graph(config: AppConfig) -> CompiledStateGraph:
|
|
|
201
217
|
function_name=middleware_config.name,
|
|
202
218
|
args=middleware_config.args,
|
|
203
219
|
)
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
220
|
+
middlewares.append(middleware)
|
|
221
|
+
logger.debug(
|
|
222
|
+
"Created supervisor middleware",
|
|
223
|
+
middleware=middleware_config.name,
|
|
224
|
+
)
|
|
209
225
|
|
|
210
226
|
# Set up memory store and checkpointer
|
|
211
227
|
store: BaseStore | None = create_store(orchestration)
|
dao_ai/orchestration/swarm.py
CHANGED
|
@@ -167,8 +167,13 @@ def create_swarm_graph(config: AppConfig) -> CompiledStateGraph:
|
|
|
167
167
|
default_agent: str
|
|
168
168
|
if isinstance(swarm.default_agent, AgentModel):
|
|
169
169
|
default_agent = swarm.default_agent.name
|
|
170
|
-
|
|
170
|
+
elif swarm.default_agent is not None:
|
|
171
171
|
default_agent = swarm.default_agent
|
|
172
|
+
elif len(config.app.agents) > 0:
|
|
173
|
+
# Fallback to first agent if no default specified
|
|
174
|
+
default_agent = config.app.agents[0].name
|
|
175
|
+
else:
|
|
176
|
+
raise ValueError("Swarm requires at least one agent and a default_agent")
|
|
172
177
|
|
|
173
178
|
logger.info(
|
|
174
179
|
"Creating swarm graph",
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
Prompt utilities for DAO AI agents.
|
|
3
3
|
|
|
4
4
|
This module provides utilities for creating dynamic prompts using
|
|
5
|
-
LangChain v1's @dynamic_prompt middleware decorator pattern
|
|
5
|
+
LangChain v1's @dynamic_prompt middleware decorator pattern, as well as
|
|
6
|
+
paths to prompt template files.
|
|
6
7
|
"""
|
|
7
8
|
|
|
9
|
+
from pathlib import Path
|
|
8
10
|
from typing import Any, Optional
|
|
9
11
|
|
|
10
12
|
from langchain.agents.middleware import (
|
|
@@ -18,6 +20,13 @@ from loguru import logger
|
|
|
18
20
|
from dao_ai.config import PromptModel
|
|
19
21
|
from dao_ai.state import Context
|
|
20
22
|
|
|
23
|
+
PROMPTS_DIR = Path(__file__).parent
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_prompt_path(name: str) -> Path:
|
|
27
|
+
"""Get the path to a prompt template file."""
|
|
28
|
+
return PROMPTS_DIR / name
|
|
29
|
+
|
|
21
30
|
|
|
22
31
|
def make_prompt(
|
|
23
32
|
base_system_prompt: Optional[str | PromptModel],
|
|
@@ -61,19 +70,14 @@ def make_prompt(
|
|
|
61
70
|
@dynamic_prompt
|
|
62
71
|
def dynamic_system_prompt(request: ModelRequest) -> str:
|
|
63
72
|
"""Generate dynamic system prompt based on runtime context."""
|
|
64
|
-
#
|
|
73
|
+
# Initialize parameters for template variables
|
|
65
74
|
params: dict[str, Any] = {
|
|
66
75
|
input_variable: "" for input_variable in prompt_template.input_variables
|
|
67
76
|
}
|
|
68
77
|
|
|
69
|
-
#
|
|
78
|
+
# Apply context fields as template parameters
|
|
70
79
|
context: Context = request.runtime.context
|
|
71
80
|
if context:
|
|
72
|
-
if context.user_id and "user_id" in params:
|
|
73
|
-
params["user_id"] = context.user_id
|
|
74
|
-
if context.thread_id and "thread_id" in params:
|
|
75
|
-
params["thread_id"] = context.thread_id
|
|
76
|
-
# Apply all context fields as template parameters
|
|
77
81
|
context_dict = context.model_dump()
|
|
78
82
|
for key, value in context_dict.items():
|
|
79
83
|
if key in params and value is not None:
|
|
@@ -89,56 +93,3 @@ def make_prompt(
|
|
|
89
93
|
return formatted_prompt
|
|
90
94
|
|
|
91
95
|
return dynamic_system_prompt
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def create_prompt_middleware(
|
|
95
|
-
base_system_prompt: Optional[str | PromptModel],
|
|
96
|
-
) -> AgentMiddleware | None:
|
|
97
|
-
"""
|
|
98
|
-
Create a dynamic prompt middleware from configuration.
|
|
99
|
-
|
|
100
|
-
This always returns an AgentMiddleware suitable for use with
|
|
101
|
-
LangChain v1's middleware system.
|
|
102
|
-
|
|
103
|
-
Args:
|
|
104
|
-
base_system_prompt: The system prompt string or PromptModel
|
|
105
|
-
|
|
106
|
-
Returns:
|
|
107
|
-
An AgentMiddleware created by @dynamic_prompt, or None if no prompt
|
|
108
|
-
"""
|
|
109
|
-
if not base_system_prompt:
|
|
110
|
-
return None
|
|
111
|
-
|
|
112
|
-
# Extract template string from PromptModel or use string directly
|
|
113
|
-
template_str: str
|
|
114
|
-
if isinstance(base_system_prompt, PromptModel):
|
|
115
|
-
template_str = base_system_prompt.template
|
|
116
|
-
else:
|
|
117
|
-
template_str = base_system_prompt
|
|
118
|
-
|
|
119
|
-
prompt_template: PromptTemplate = PromptTemplate.from_template(template_str)
|
|
120
|
-
|
|
121
|
-
@dynamic_prompt
|
|
122
|
-
def prompt_middleware(request: ModelRequest) -> str:
|
|
123
|
-
"""Generate system prompt based on runtime context."""
|
|
124
|
-
# Get parameters from runtime context
|
|
125
|
-
params: dict[str, Any] = {
|
|
126
|
-
input_variable: "" for input_variable in prompt_template.input_variables
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
# Access context from runtime
|
|
130
|
-
context: Context = request.runtime.context
|
|
131
|
-
if context:
|
|
132
|
-
# Apply all context fields as template parameters
|
|
133
|
-
context_dict = context.model_dump()
|
|
134
|
-
for key, value in context_dict.items():
|
|
135
|
-
if key in params and value is not None:
|
|
136
|
-
params[key] = value
|
|
137
|
-
|
|
138
|
-
# Format the prompt
|
|
139
|
-
formatted_prompt: str = prompt_template.format(**params)
|
|
140
|
-
logger.trace("Formatted dynamic prompt with context")
|
|
141
|
-
|
|
142
|
-
return formatted_prompt
|
|
143
|
-
|
|
144
|
-
return prompt_middleware
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
name: instructed_retriever_decomposition
|
|
2
|
+
description: Decomposes user queries into multiple search queries with metadata filters
|
|
3
|
+
|
|
4
|
+
template: |
|
|
5
|
+
You are a search query decomposition expert. Your task is to break down a user query into one or more focused search queries with appropriate metadata filters. Respond with a JSON object.
|
|
6
|
+
|
|
7
|
+
## Current Time
|
|
8
|
+
{current_time}
|
|
9
|
+
|
|
10
|
+
## Database Schema
|
|
11
|
+
{schema_description}
|
|
12
|
+
|
|
13
|
+
## Constraints
|
|
14
|
+
{constraints}
|
|
15
|
+
|
|
16
|
+
## Few-Shot Examples
|
|
17
|
+
{examples}
|
|
18
|
+
|
|
19
|
+
## Instructions
|
|
20
|
+
1. Analyze the user query and identify distinct search intents
|
|
21
|
+
2. For each intent, create a focused search query text
|
|
22
|
+
3. Extract metadata filters from the query using the exact filter syntax above
|
|
23
|
+
4. Resolve relative time references (e.g., "last month", "past year") using the current time
|
|
24
|
+
5. Generate at most {max_subqueries} search queries
|
|
25
|
+
6. If no filters apply, set filters to null
|
|
26
|
+
|
|
27
|
+
## User Query
|
|
28
|
+
{query}
|
|
29
|
+
|
|
30
|
+
Generate search queries that together capture all aspects of the user's information need.
|
|
31
|
+
|
|
32
|
+
variables:
|
|
33
|
+
- current_time
|
|
34
|
+
- schema_description
|
|
35
|
+
- constraints
|
|
36
|
+
- examples
|
|
37
|
+
- max_subqueries
|
|
38
|
+
- query
|
|
39
|
+
|
|
40
|
+
output_format: |
|
|
41
|
+
The output must be a JSON object with a "queries" field containing an array of search query objects.
|
|
42
|
+
Each search query object has:
|
|
43
|
+
- "text": The search query string
|
|
44
|
+
- "filters": An array of filter objects, each with "key" (column + optional operator) and "value", or null if no filters
|
|
45
|
+
|
|
46
|
+
Supported filter operators (append to column name):
|
|
47
|
+
- Equality: {"key": "column", "value": "val"} or {"key": "column", "value": ["val1", "val2"]}
|
|
48
|
+
- Exclusion: {"key": "column NOT", "value": "val"}
|
|
49
|
+
- Comparison: {"key": "column <", "value": 100}, also <=, >, >=
|
|
50
|
+
- Token match: {"key": "column LIKE", "value": "word"}
|
|
51
|
+
- Exclude token: {"key": "column NOT LIKE", "value": "word"}
|
|
52
|
+
|
|
53
|
+
Examples:
|
|
54
|
+
- [{"key": "brand_name", "value": "MILWAUKEE"}]
|
|
55
|
+
- [{"key": "price <", "value": 100}]
|
|
56
|
+
- [{"key": "brand_name NOT", "value": "DEWALT"}]
|
|
57
|
+
- [{"key": "brand_name", "value": ["MILWAUKEE", "DEWALT"]}]
|
|
58
|
+
- [{"key": "description LIKE", "value": "cordless"}]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
name: instruction_aware_reranking
|
|
2
|
+
version: "1.1"
|
|
3
|
+
description: Rerank documents based on user instructions and constraints
|
|
4
|
+
|
|
5
|
+
template: |
|
|
6
|
+
Rerank these search results for the query "{query}".
|
|
7
|
+
|
|
8
|
+
{instructions}
|
|
9
|
+
|
|
10
|
+
## Documents
|
|
11
|
+
|
|
12
|
+
{documents}
|
|
13
|
+
|
|
14
|
+
Score each document 0.0-1.0 based on relevance to the query and instructions. Return results sorted by score (highest first). Only include documents scoring > 0.1.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: router_query_classification
|
|
2
|
+
version: "1.0"
|
|
3
|
+
description: Classify query to determine execution mode (standard vs instructed)
|
|
4
|
+
|
|
5
|
+
template: |
|
|
6
|
+
You are a query classification system. Your task is to determine the best execution mode for a search query.
|
|
7
|
+
|
|
8
|
+
## Execution Modes
|
|
9
|
+
|
|
10
|
+
**standard**: Use for simple keyword or product searches without specific constraints.
|
|
11
|
+
- General questions about products
|
|
12
|
+
- Simple keyword searches
|
|
13
|
+
- Broad category browsing
|
|
14
|
+
|
|
15
|
+
**instructed**: Use for queries with explicit constraints that require metadata filtering.
|
|
16
|
+
- Price constraints ("under $100", "between $50 and $200")
|
|
17
|
+
- Brand preferences ("Milwaukee", "not DeWalt", "excluding Makita")
|
|
18
|
+
- Category filters ("power tools", "paint supplies")
|
|
19
|
+
- Time/recency constraints ("recent", "from last month", "updated this year")
|
|
20
|
+
- Comparison queries ("compare X and Y")
|
|
21
|
+
- Multiple combined constraints
|
|
22
|
+
|
|
23
|
+
## Available Schema for Filtering
|
|
24
|
+
|
|
25
|
+
{schema_description}
|
|
26
|
+
|
|
27
|
+
## Query to Classify
|
|
28
|
+
|
|
29
|
+
"{query}"
|
|
30
|
+
|
|
31
|
+
## Instructions
|
|
32
|
+
|
|
33
|
+
Analyze the query and determine:
|
|
34
|
+
1. Does it contain explicit constraints that can be translated to metadata filters?
|
|
35
|
+
2. Would the query benefit from being decomposed into subqueries?
|
|
36
|
+
|
|
37
|
+
Return your classification as a JSON object with a single field "mode" set to either "standard" or "instructed".
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: result_verification
|
|
2
|
+
version: "1.0"
|
|
3
|
+
description: Verify search results satisfy user constraints
|
|
4
|
+
|
|
5
|
+
template: |
|
|
6
|
+
You are a result verification system. Your task is to determine whether search results satisfy the user's query constraints.
|
|
7
|
+
|
|
8
|
+
## User Query
|
|
9
|
+
|
|
10
|
+
"{query}"
|
|
11
|
+
|
|
12
|
+
## Schema Information
|
|
13
|
+
|
|
14
|
+
{schema_description}
|
|
15
|
+
|
|
16
|
+
## Constraints to Verify
|
|
17
|
+
|
|
18
|
+
{constraints}
|
|
19
|
+
|
|
20
|
+
## Retrieved Results (Top {num_results})
|
|
21
|
+
|
|
22
|
+
{results_summary}
|
|
23
|
+
|
|
24
|
+
## Previous Attempt Feedback (if retry)
|
|
25
|
+
|
|
26
|
+
{previous_feedback}
|
|
27
|
+
|
|
28
|
+
## Instructions
|
|
29
|
+
|
|
30
|
+
Analyze whether the results satisfy the user's explicit and implicit constraints:
|
|
31
|
+
|
|
32
|
+
1. **Intent Match**: Do the results address what the user is looking for?
|
|
33
|
+
2. **Explicit Constraints**: Are price, brand, category, date constraints met?
|
|
34
|
+
3. **Relevance**: Are the results actually useful for the user's needs?
|
|
35
|
+
|
|
36
|
+
If results do NOT satisfy constraints, suggest specific filter relaxations:
|
|
37
|
+
- Use "REMOVE" to drop a filter entirely
|
|
38
|
+
- Use "BROADEN" to widen a range (e.g., price < 100 -> price < 150)
|
|
39
|
+
- Use specific values to change a filter
|
|
40
|
+
|
|
41
|
+
Return a JSON object with:
|
|
42
|
+
- passed: boolean (true if results are satisfactory)
|
|
43
|
+
- confidence: float (0.0-1.0, your confidence in the assessment)
|
|
44
|
+
- feedback: string (brief explanation of issues, if any)
|
|
45
|
+
- suggested_filter_relaxation: object (filter changes for retry, e.g., {{"brand_name": "REMOVE"}})
|
|
46
|
+
- unmet_constraints: array of strings (list of constraints not satisfied)
|
dao_ai/providers/base.py
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
|
-
from typing import Any, Sequence
|
|
2
|
+
from typing import TYPE_CHECKING, Any, Sequence
|
|
3
3
|
|
|
4
4
|
from dao_ai.config import (
|
|
5
5
|
AppModel,
|
|
6
6
|
DatasetModel,
|
|
7
|
+
DeploymentTarget,
|
|
7
8
|
SchemaModel,
|
|
8
9
|
UnityCatalogFunctionSqlModel,
|
|
9
10
|
VectorStoreModel,
|
|
10
11
|
VolumeModel,
|
|
11
12
|
)
|
|
12
13
|
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from dao_ai.config import AppConfig
|
|
16
|
+
|
|
13
17
|
|
|
14
18
|
class ServiceProvider(ABC):
|
|
15
19
|
@abstractmethod
|
|
@@ -52,4 +56,26 @@ class ServiceProvider(ABC):
|
|
|
52
56
|
) -> Any: ...
|
|
53
57
|
|
|
54
58
|
@abstractmethod
|
|
55
|
-
def
|
|
59
|
+
def deploy_model_serving_agent(self, config: "AppConfig") -> Any:
|
|
60
|
+
"""Deploy agent to Databricks Model Serving endpoint."""
|
|
61
|
+
...
|
|
62
|
+
|
|
63
|
+
@abstractmethod
|
|
64
|
+
def deploy_apps_agent(self, config: "AppConfig") -> Any:
|
|
65
|
+
"""Deploy agent as a Databricks App."""
|
|
66
|
+
...
|
|
67
|
+
|
|
68
|
+
@abstractmethod
|
|
69
|
+
def deploy_agent(
|
|
70
|
+
self,
|
|
71
|
+
config: "AppConfig",
|
|
72
|
+
target: DeploymentTarget = DeploymentTarget.MODEL_SERVING,
|
|
73
|
+
) -> Any:
|
|
74
|
+
"""
|
|
75
|
+
Deploy agent to the specified target.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
config: The AppConfig containing deployment configuration
|
|
79
|
+
target: The deployment target (MODEL_SERVING or APPS)
|
|
80
|
+
"""
|
|
81
|
+
...
|