dao-ai 0.0.26__py3-none-any.whl → 0.0.27__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/config.py CHANGED
@@ -397,6 +397,13 @@ class VectorSearchEndpoint(BaseModel):
397
397
  name: str
398
398
  type: VectorSearchEndpointType = VectorSearchEndpointType.STANDARD
399
399
 
400
+ @field_serializer("type")
401
+ def serialize_type(self, value: VectorSearchEndpointType) -> str:
402
+ """Ensure enum is serialized to string value."""
403
+ if isinstance(value, VectorSearchEndpointType):
404
+ return value.value
405
+ return str(value)
406
+
400
407
 
401
408
  class IndexModel(BaseModel, HasFullName, IsDatabricksResource):
402
409
  model_config = ConfigDict(use_enum_values=True, extra="forbid")
@@ -998,22 +1005,120 @@ class TransportType(str, Enum):
998
1005
  class McpFunctionModel(BaseFunctionModel, HasFullName):
999
1006
  model_config = ConfigDict(use_enum_values=True, extra="forbid")
1000
1007
  type: Literal[FunctionType.MCP] = FunctionType.MCP
1001
-
1002
1008
  transport: TransportType = TransportType.STREAMABLE_HTTP
1003
1009
  command: Optional[str] = "python"
1004
1010
  url: Optional[AnyVariable] = None
1005
- connection: Optional[ConnectionModel] = None
1006
1011
  headers: dict[str, AnyVariable] = Field(default_factory=dict)
1007
1012
  args: list[str] = Field(default_factory=list)
1008
1013
  pat: Optional[AnyVariable] = None
1009
1014
  client_id: Optional[AnyVariable] = None
1010
1015
  client_secret: Optional[AnyVariable] = None
1011
1016
  workspace_host: Optional[AnyVariable] = None
1017
+ connection: Optional[ConnectionModel] = None
1018
+ functions: Optional[SchemaModel] = None
1019
+ genie_room: Optional[GenieRoomModel] = None
1020
+ sql: Optional[bool] = None
1021
+ vector_search: Optional[VectorStoreModel] = None
1012
1022
 
1013
1023
  @property
1014
1024
  def full_name(self) -> str:
1015
1025
  return self.name
1016
1026
 
1027
+ def _get_workspace_host(self) -> str:
1028
+ """
1029
+ Get the workspace host, either from config or from workspace client.
1030
+
1031
+ If connection is provided, uses its workspace client.
1032
+ Otherwise, falls back to creating a new workspace client.
1033
+
1034
+ Returns:
1035
+ str: The workspace host URL without trailing slash
1036
+ """
1037
+ from databricks.sdk import WorkspaceClient
1038
+
1039
+ # Try to get workspace_host from config
1040
+ workspace_host: str | None = (
1041
+ value_of(self.workspace_host) if self.workspace_host else None
1042
+ )
1043
+
1044
+ # If no workspace_host in config, get it from workspace client
1045
+ if not workspace_host:
1046
+ # Use connection's workspace client if available
1047
+ if self.connection:
1048
+ workspace_host = self.connection.workspace_client.config.host
1049
+ else:
1050
+ # Create a default workspace client
1051
+ w: WorkspaceClient = WorkspaceClient()
1052
+ workspace_host = w.config.host
1053
+
1054
+ # Remove trailing slash
1055
+ return workspace_host.rstrip("/")
1056
+
1057
+ @property
1058
+ def mcp_url(self) -> str:
1059
+ """
1060
+ Get the MCP URL for this function.
1061
+
1062
+ Returns the URL based on the configured source:
1063
+ - If url is set, returns it directly
1064
+ - If connection is set, constructs URL from connection
1065
+ - If genie_room is set, constructs Genie MCP URL
1066
+ - If sql is set, constructs DBSQL MCP URL (serverless)
1067
+ - If vector_search is set, constructs Vector Search MCP URL
1068
+ - If functions is set, constructs UC Functions MCP URL
1069
+
1070
+ URL patterns (per https://docs.databricks.com/aws/en/generative-ai/mcp/managed-mcp):
1071
+ - Genie: https://{host}/api/2.0/mcp/genie/{space_id}
1072
+ - DBSQL: https://{host}/api/2.0/mcp/sql (serverless, workspace-level)
1073
+ - Vector Search: https://{host}/api/2.0/mcp/vector-search/{catalog}/{schema}
1074
+ - UC Functions: https://{host}/api/2.0/mcp/functions/{catalog}/{schema}
1075
+ - Connection: https://{host}/api/2.0/mcp/external/{connection_name}
1076
+ """
1077
+ # Direct URL provided
1078
+ if self.url:
1079
+ return self.url
1080
+
1081
+ # Get workspace host (from config, connection, or default workspace client)
1082
+ workspace_host: str = self._get_workspace_host()
1083
+
1084
+ # UC Connection
1085
+ if self.connection:
1086
+ connection_name: str = self.connection.name
1087
+ return f"{workspace_host}/api/2.0/mcp/external/{connection_name}"
1088
+
1089
+ # Genie Room
1090
+ if self.genie_room:
1091
+ space_id: str = value_of(self.genie_room.space_id)
1092
+ return f"{workspace_host}/api/2.0/mcp/genie/{space_id}"
1093
+
1094
+ # DBSQL MCP server (serverless, workspace-level)
1095
+ if self.sql:
1096
+ return f"{workspace_host}/api/2.0/mcp/sql"
1097
+
1098
+ # Vector Search
1099
+ if self.vector_search:
1100
+ if (
1101
+ not self.vector_search.index
1102
+ or not self.vector_search.index.schema_model
1103
+ ):
1104
+ raise ValueError(
1105
+ "vector_search must have an index with a schema (catalog/schema) configured"
1106
+ )
1107
+ catalog: str = self.vector_search.index.schema_model.catalog_name
1108
+ schema: str = self.vector_search.index.schema_model.schema_name
1109
+ return f"{workspace_host}/api/2.0/mcp/vector-search/{catalog}/{schema}"
1110
+
1111
+ # UC Functions MCP server
1112
+ if self.functions:
1113
+ catalog: str = self.functions.catalog_name
1114
+ schema: str = self.functions.schema_name
1115
+ return f"{workspace_host}/api/2.0/mcp/functions/{catalog}/{schema}"
1116
+
1117
+ raise ValueError(
1118
+ "No URL source configured. Provide one of: url, connection, genie_room, "
1119
+ "sql, vector_search, or functions"
1120
+ )
1121
+
1017
1122
  @field_serializer("transport")
1018
1123
  def serialize_transport(self, value) -> str:
1019
1124
  if isinstance(value, TransportType):
@@ -1021,32 +1126,56 @@ class McpFunctionModel(BaseFunctionModel, HasFullName):
1021
1126
  return str(value)
1022
1127
 
1023
1128
  @model_validator(mode="after")
1024
- def validate_mutually_exclusive(self):
1025
- if self.transport == TransportType.STREAMABLE_HTTP and not (
1026
- self.url or self.connection
1027
- ):
1028
- raise ValueError(
1029
- "url or connection must be provided for STREAMABLE_HTTP transport"
1030
- )
1031
- if self.transport == TransportType.STDIO and not self.command:
1032
- raise ValueError("command must not be provided for STDIO transport")
1033
- if self.transport == TransportType.STDIO and not self.args:
1034
- raise ValueError("args must not be provided for STDIO transport")
1129
+ def validate_mutually_exclusive(self) -> "McpFunctionModel":
1130
+ """Validate that exactly one URL source is provided."""
1131
+ # Count how many URL sources are provided
1132
+ url_sources: list[tuple[str, Any]] = [
1133
+ ("url", self.url),
1134
+ ("connection", self.connection),
1135
+ ("genie_room", self.genie_room),
1136
+ ("sql", self.sql),
1137
+ ("vector_search", self.vector_search),
1138
+ ("functions", self.functions),
1139
+ ]
1140
+
1141
+ provided_sources: list[str] = [
1142
+ name for name, value in url_sources if value is not None
1143
+ ]
1144
+
1145
+ if self.transport == TransportType.STREAMABLE_HTTP:
1146
+ if len(provided_sources) == 0:
1147
+ raise ValueError(
1148
+ "For STREAMABLE_HTTP transport, exactly one of the following must be provided: "
1149
+ "url, connection, genie_room, sql, vector_search, or functions"
1150
+ )
1151
+ if len(provided_sources) > 1:
1152
+ raise ValueError(
1153
+ f"For STREAMABLE_HTTP transport, only one URL source can be provided. "
1154
+ f"Found: {', '.join(provided_sources)}. "
1155
+ f"Please provide only one of: url, connection, genie_room, sql, vector_search, or functions"
1156
+ )
1157
+
1158
+ if self.transport == TransportType.STDIO:
1159
+ if not self.command:
1160
+ raise ValueError("command must be provided for STDIO transport")
1161
+ if not self.args:
1162
+ raise ValueError("args must be provided for STDIO transport")
1163
+
1035
1164
  return self
1036
1165
 
1037
1166
  @model_validator(mode="after")
1038
- def update_url(self):
1167
+ def update_url(self) -> "McpFunctionModel":
1039
1168
  self.url = value_of(self.url)
1040
1169
  return self
1041
1170
 
1042
1171
  @model_validator(mode="after")
1043
- def update_headers(self):
1172
+ def update_headers(self) -> "McpFunctionModel":
1044
1173
  for key, value in self.headers.items():
1045
1174
  self.headers[key] = value_of(value)
1046
1175
  return self
1047
1176
 
1048
1177
  @model_validator(mode="after")
1049
- def validate_auth_methods(self):
1178
+ def validate_auth_methods(self) -> "McpFunctionModel":
1050
1179
  oauth_fields: Sequence[Any] = [
1051
1180
  self.client_id,
1052
1181
  self.client_secret,
@@ -1062,10 +1191,7 @@ class McpFunctionModel(BaseFunctionModel, HasFullName):
1062
1191
  "Please provide either OAuth credentials or user credentials."
1063
1192
  )
1064
1193
 
1065
- if (has_oauth or has_user_auth) and not self.workspace_host:
1066
- raise ValueError(
1067
- "Workspace host must be provided when using OAuth or user credentials."
1068
- )
1194
+ # Note: workspace_host is optional - it will be derived from workspace client if not provided
1069
1195
 
1070
1196
  return self
1071
1197
 
dao_ai/graph.py CHANGED
@@ -62,11 +62,19 @@ def _handoffs_for_agent(agent: AgentModel, config: AppConfig) -> Sequence[BaseTo
62
62
  logger.debug(
63
63
  f"Creating handoff tool from agent {agent.name} to {handoff_to_agent.name}"
64
64
  )
65
+
66
+ # Use handoff_prompt if provided, otherwise create default description
67
+ handoff_description = handoff_to_agent.handoff_prompt or (
68
+ handoff_to_agent.description
69
+ if handoff_to_agent.description
70
+ else "general assistance and questions"
71
+ )
72
+
65
73
  handoff_tools.append(
66
74
  swarm_handoff_tool(
67
75
  agent_name=handoff_to_agent.name,
68
76
  description=f"Ask {handoff_to_agent.name} for help with: "
69
- + handoff_to_agent.handoff_prompt,
77
+ + handoff_description,
70
78
  )
71
79
  )
72
80
  return handoff_tools
@@ -87,10 +95,17 @@ def _create_supervisor_graph(config: AppConfig) -> CompiledStateGraph:
87
95
  additional_tools=[],
88
96
  )
89
97
  )
98
+ # Use handoff_prompt if provided, otherwise create default description
99
+ handoff_description = registered_agent.handoff_prompt or (
100
+ registered_agent.description
101
+ if registered_agent.description
102
+ else f"General assistance with {registered_agent.name} related tasks"
103
+ )
104
+
90
105
  tools.append(
91
106
  supervisor_handoff_tool(
92
107
  agent_name=registered_agent.name,
93
- description=registered_agent.handoff_prompt,
108
+ description=handoff_description,
94
109
  )
95
110
  )
96
111
 
@@ -331,7 +331,7 @@ class DatabricksProvider(ServiceProvider):
331
331
  logged_agent_info: ModelInfo = mlflow.pyfunc.log_model(
332
332
  python_model=model_path.as_posix(),
333
333
  code_paths=code_paths,
334
- model_config=config.model_dump(by_alias=True),
334
+ model_config=config.model_dump(mode="json", by_alias=True),
335
335
  name="agent",
336
336
  pip_requirements=pip_requirements,
337
337
  input_example=input_example,
@@ -1280,7 +1280,9 @@ class DatabricksProvider(ServiceProvider):
1280
1280
  # Get agent and prompt (prompt is guaranteed to be set by validator)
1281
1281
  agent_model: AgentModel = optimization.agent
1282
1282
  prompt: PromptModel = optimization.prompt # type: ignore[assignment]
1283
+ agent_model.prompt = prompt.uri
1283
1284
 
1285
+ print(f"prompt={agent_model.prompt}")
1284
1286
  # Log the prompt URI scheme being used
1285
1287
  # Supports three schemes:
1286
1288
  # 1. Specific version: "prompts:/qa/1" (when version is specified)
@@ -1350,20 +1352,8 @@ class DatabricksProvider(ServiceProvider):
1350
1352
  # DO NOT overwrite with prompt_version.uri as that uses fallback logic
1351
1353
  logger.debug(f"Optimizing prompt: {prompt_uri}")
1352
1354
 
1353
- # Create the agent once outside the predict function to avoid repeated construction
1354
- # and to ensure dao_ai modules are available in the current environment
1355
- logger.debug(f"Creating ResponsesAgent for optimization from agent '{agent_model.name}'")
1356
- try:
1357
- agent: ResponsesAgent = agent_model.as_responses_agent()
1358
- logger.info(f"Successfully created ResponsesAgent for '{agent_model.name}'")
1359
- except Exception as e:
1360
- logger.error(f"Failed to create ResponsesAgent: {e}")
1361
- logger.error("Ensure dao_ai package is properly installed and accessible")
1362
- raise RuntimeError(
1363
- f"Failed to create ResponsesAgent for prompt optimization: {e}. "
1364
- "This may indicate dao_ai is not properly installed or accessible."
1365
- ) from e
1366
-
1355
+ agent: ResponsesAgent = agent_model.as_responses_agent()
1356
+
1367
1357
  # Create predict function that will be optimized
1368
1358
  def predict_fn(**inputs: dict[str, Any]) -> str:
1369
1359
  """Prediction function that uses the ResponsesAgent with ChatPayload.
dao_ai/tools/mcp.py CHANGED
@@ -30,24 +30,15 @@ def create_mcp_tools(
30
30
  """
31
31
  logger.debug(f"create_mcp_tools: {function}")
32
32
 
33
+ # Get MCP URL - handles all convenience objects (connection, genie_room, warehouse, etc.)
34
+ mcp_url = function.mcp_url
35
+ logger.debug(f"Using MCP URL: {mcp_url}")
36
+
33
37
  # Check if using UC Connection or direct MCP connection
34
38
  if function.connection:
35
39
  # Use UC Connection approach with DatabricksOAuthClientProvider
36
40
  logger.debug(f"Using UC Connection for MCP: {function.connection.name}")
37
41
 
38
- # Construct URL if not provided
39
- if function.url:
40
- mcp_url = function.url
41
- logger.debug(f"Using provided MCP URL: {mcp_url}")
42
- else:
43
- # Construct URL from workspace host and connection name
44
- # Pattern: https://{workspace_host}/api/2.0/mcp/external/{connection_name}
45
- workspace_client = function.connection.workspace_client
46
- workspace_host = workspace_client.config.host
47
- connection_name = function.connection.name
48
- mcp_url = f"{workspace_host}/api/2.0/mcp/external/{connection_name}"
49
- logger.debug(f"Constructed MCP URL from connection: {mcp_url}")
50
-
51
42
  async def _list_tools_with_connection():
52
43
  """List available tools using DatabricksOAuthClientProvider."""
53
44
  workspace_client = function.connection.workspace_client
@@ -147,7 +138,7 @@ def create_mcp_tools(
147
138
  logger.debug("Using existing authentication token")
148
139
 
149
140
  return {
150
- "url": function.url,
141
+ "url": mcp_url, # Use the resolved MCP URL
151
142
  "transport": function.transport,
152
143
  "headers": headers,
153
144
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dao-ai
3
- Version: 0.0.26
3
+ Version: 0.0.27
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
@@ -41,7 +41,7 @@ Requires-Dist: langgraph>=0.6.10
41
41
  Requires-Dist: langmem>=0.0.29
42
42
  Requires-Dist: loguru>=0.7.3
43
43
  Requires-Dist: mcp>=1.17.0
44
- Requires-Dist: mlflow>=3.5.0
44
+ Requires-Dist: mlflow>=3.5.1
45
45
  Requires-Dist: nest-asyncio>=1.6.0
46
46
  Requires-Dist: openevals>=0.0.19
47
47
  Requires-Dist: openpyxl>=3.1.5
@@ -3,8 +3,8 @@ dao_ai/agent_as_code.py,sha256=kPSeDz2-1jRaed1TMs4LA3VECoyqe9_Ed2beRLB9gXQ,472
3
3
  dao_ai/catalog.py,sha256=sPZpHTD3lPx4EZUtIWeQV7VQM89WJ6YH__wluk1v2lE,4947
4
4
  dao_ai/chat_models.py,sha256=uhwwOTeLyHWqoTTgHrs4n5iSyTwe4EQcLKnh3jRxPWI,8626
5
5
  dao_ai/cli.py,sha256=gq-nsapWxDA1M6Jua3vajBvIwf0Oa6YLcB58lEtMKUo,22503
6
- dao_ai/config.py,sha256=MdX7zGOXf9Fg03lo1vA7G9yoiIzYmJ6kqTJH4aiQ3VQ,65068
7
- dao_ai/graph.py,sha256=RrcYfdyj9lYXUZyx4Ax4VIlCUbOWviwr1t2qGDM69LQ,8318
6
+ dao_ai/config.py,sha256=DRFj_1W5sfGH5f2tGQaeC733pTIDTqvbyAt14v8FQYs,70296
7
+ dao_ai/graph.py,sha256=9kjJx0oFZKq5J9-Kpri4-0VCJILHYdYyhqQnj0_noxQ,8913
8
8
  dao_ai/guardrails.py,sha256=4TKArDONRy8RwHzOT1plZ1rhy3x9GF_aeGpPCRl6wYA,4016
9
9
  dao_ai/messages.py,sha256=xl_3-WcFqZKCFCiov8sZOPljTdM3gX3fCHhxq-xFg2U,7005
10
10
  dao_ai/models.py,sha256=8r8GIG3EGxtVyWsRNI56lVaBjiNrPkzh4HdwMZRq8iw,31689
@@ -22,20 +22,20 @@ dao_ai/memory/core.py,sha256=DnEjQO3S7hXr3CDDd7C2eE7fQUmcCS_8q9BXEgjPH3U,4271
22
22
  dao_ai/memory/postgres.py,sha256=vvI3osjx1EoU5GBA6SCUstTBKillcmLl12hVgDMjfJY,15346
23
23
  dao_ai/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  dao_ai/providers/base.py,sha256=-fjKypCOk28h6vioPfMj9YZSw_3Kcbi2nMuAyY7vX9k,1383
25
- dao_ai/providers/databricks.py,sha256=jfQEEgr9mEIttde1182wPpYm3kHq4sRwXoeMNfkpEmA,66512
25
+ dao_ai/providers/databricks.py,sha256=W_lXSMbPTULMAx-KW7zBJfP7LtkcPGRnEfGcSYuu708,65824
26
26
  dao_ai/tools/__init__.py,sha256=G5-5Yi6zpQOH53b5IzLdtsC6g0Ep6leI5GxgxOmgw7Q,1203
27
27
  dao_ai/tools/agent.py,sha256=WbQnyziiT12TLMrA7xK0VuOU029tdmUBXbUl-R1VZ0Q,1886
28
28
  dao_ai/tools/core.py,sha256=Kei33S8vrmvPOAyrFNekaWmV2jqZ-IPS1QDSvU7RZF0,1984
29
29
  dao_ai/tools/genie.py,sha256=8HSOCzSg6PlBzBYXMmNfUnl-LO03p3Ki3fxLPm_dhPg,15051
30
30
  dao_ai/tools/human_in_the_loop.py,sha256=yk35MO9eNETnYFH-sqlgR-G24TrEgXpJlnZUustsLkI,3681
31
- dao_ai/tools/mcp.py,sha256=gTY2xTInC5sy9ckZubwp1QIyZ6_qaUXGeJM_NlmPnGE,8892
31
+ dao_ai/tools/mcp.py,sha256=5aQoRtx2z4xm6zgRslc78rSfEQe-mfhqov2NsiybYfc,8416
32
32
  dao_ai/tools/python.py,sha256=XcQiTMshZyLUTVR5peB3vqsoUoAAy8gol9_pcrhddfI,1831
33
33
  dao_ai/tools/slack.py,sha256=SCvyVcD9Pv_XXPXePE_fSU1Pd8VLTEkKDLvoGTZWy2Y,4775
34
34
  dao_ai/tools/time.py,sha256=Y-23qdnNHzwjvnfkWvYsE7PoWS1hfeKy44tA7sCnNac,8759
35
35
  dao_ai/tools/unity_catalog.py,sha256=uX_h52BuBAr4c9UeqSMI7DNz3BPRLeai5tBVW4sJqRI,13113
36
36
  dao_ai/tools/vector_search.py,sha256=EDYQs51zIPaAP0ma1D81wJT77GQ-v-cjb2XrFVWfWdg,2621
37
- dao_ai-0.0.26.dist-info/METADATA,sha256=V5Bvxs1b3FJR-rIjfJHqrNyv9r46rZMHeY9ze8xkMX0,42695
38
- dao_ai-0.0.26.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
39
- dao_ai-0.0.26.dist-info/entry_points.txt,sha256=Xa-UFyc6gWGwMqMJOt06ZOog2vAfygV_DSwg1AiP46g,43
40
- dao_ai-0.0.26.dist-info/licenses/LICENSE,sha256=YZt3W32LtPYruuvHE9lGk2bw6ZPMMJD8yLrjgHybyz4,1069
41
- dao_ai-0.0.26.dist-info/RECORD,,
37
+ dao_ai-0.0.27.dist-info/METADATA,sha256=iKbZTl0tFi0S2XUir9uIGh6WyOTvM9yH-4FHCHOzsuE,42695
38
+ dao_ai-0.0.27.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
39
+ dao_ai-0.0.27.dist-info/entry_points.txt,sha256=Xa-UFyc6gWGwMqMJOt06ZOog2vAfygV_DSwg1AiP46g,43
40
+ dao_ai-0.0.27.dist-info/licenses/LICENSE,sha256=YZt3W32LtPYruuvHE9lGk2bw6ZPMMJD8yLrjgHybyz4,1069
41
+ dao_ai-0.0.27.dist-info/RECORD,,