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.
Files changed (69) hide show
  1. dao_ai/apps/__init__.py +24 -0
  2. dao_ai/apps/handlers.py +105 -0
  3. dao_ai/apps/model_serving.py +29 -0
  4. dao_ai/apps/resources.py +1122 -0
  5. dao_ai/apps/server.py +39 -0
  6. dao_ai/cli.py +546 -37
  7. dao_ai/config.py +1179 -139
  8. dao_ai/evaluation.py +543 -0
  9. dao_ai/genie/__init__.py +55 -7
  10. dao_ai/genie/cache/__init__.py +34 -7
  11. dao_ai/genie/cache/base.py +143 -2
  12. dao_ai/genie/cache/context_aware/__init__.py +31 -0
  13. dao_ai/genie/cache/context_aware/base.py +1151 -0
  14. dao_ai/genie/cache/context_aware/in_memory.py +609 -0
  15. dao_ai/genie/cache/context_aware/persistent.py +802 -0
  16. dao_ai/genie/cache/context_aware/postgres.py +1166 -0
  17. dao_ai/genie/cache/core.py +1 -1
  18. dao_ai/genie/cache/lru.py +257 -75
  19. dao_ai/genie/cache/optimization.py +890 -0
  20. dao_ai/genie/core.py +235 -11
  21. dao_ai/memory/postgres.py +175 -39
  22. dao_ai/middleware/__init__.py +38 -0
  23. dao_ai/middleware/assertions.py +3 -3
  24. dao_ai/middleware/context_editing.py +230 -0
  25. dao_ai/middleware/core.py +4 -4
  26. dao_ai/middleware/guardrails.py +3 -3
  27. dao_ai/middleware/human_in_the_loop.py +3 -2
  28. dao_ai/middleware/message_validation.py +4 -4
  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 +1 -1
  33. dao_ai/middleware/tool_call_limit.py +210 -0
  34. dao_ai/middleware/tool_retry.py +174 -0
  35. dao_ai/middleware/tool_selector.py +129 -0
  36. dao_ai/models.py +327 -370
  37. dao_ai/nodes.py +9 -16
  38. dao_ai/orchestration/core.py +33 -9
  39. dao_ai/orchestration/supervisor.py +29 -13
  40. dao_ai/orchestration/swarm.py +6 -1
  41. dao_ai/{prompts.py → prompts/__init__.py} +12 -61
  42. dao_ai/prompts/instructed_retriever_decomposition.yaml +58 -0
  43. dao_ai/prompts/instruction_reranker.yaml +14 -0
  44. dao_ai/prompts/router.yaml +37 -0
  45. dao_ai/prompts/verifier.yaml +46 -0
  46. dao_ai/providers/base.py +28 -2
  47. dao_ai/providers/databricks.py +363 -33
  48. dao_ai/state.py +1 -0
  49. dao_ai/tools/__init__.py +5 -3
  50. dao_ai/tools/genie.py +103 -26
  51. dao_ai/tools/instructed_retriever.py +366 -0
  52. dao_ai/tools/instruction_reranker.py +202 -0
  53. dao_ai/tools/mcp.py +539 -97
  54. dao_ai/tools/router.py +89 -0
  55. dao_ai/tools/slack.py +13 -2
  56. dao_ai/tools/sql.py +7 -3
  57. dao_ai/tools/unity_catalog.py +32 -10
  58. dao_ai/tools/vector_search.py +493 -160
  59. dao_ai/tools/verifier.py +159 -0
  60. dao_ai/utils.py +182 -2
  61. dao_ai/vector_search.py +46 -1
  62. {dao_ai-0.1.2.dist-info → dao_ai-0.1.20.dist-info}/METADATA +45 -9
  63. dao_ai-0.1.20.dist-info/RECORD +89 -0
  64. dao_ai/agent_as_code.py +0 -22
  65. dao_ai/genie/cache/semantic.py +0 -970
  66. dao_ai-0.1.2.dist-info/RECORD +0 -64
  67. {dao_ai-0.1.2.dist-info → dao_ai-0.1.20.dist-info}/WHEEL +0 -0
  68. {dao_ai-0.1.2.dist-info → dao_ai-0.1.20.dist-info}/entry_points.txt +0 -0
  69. {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
- if middleware is not None:
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: LoggingSummarizationMiddleware = (
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
- hitl_middleware: HumanInTheLoopMiddleware | None = (
127
- create_hitl_middleware_from_tool_models(tool_models)
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(hitl_middleware)
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=checkpointer,
307
+ checkpointer=False,
315
308
  state_schema=AgentState,
316
309
  context_schema=Context,
317
310
  response_format=response_format, # Add structured output support
@@ -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
- # Invoke the agent
183
- result: AgentState = await agent.ainvoke(agent_state, context=runtime.context)
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
- "active_agent": None,
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
- if middleware is not None:
205
- middlewares.append(middleware)
206
- logger.debug(
207
- "Created supervisor middleware", middleware=middleware_config.name
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)
@@ -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
- else:
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
- # Get parameters from runtime context
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
- # Access context from runtime
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 deploy_agent(self, config: AppModel) -> Any: ...
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
+ ...