dao-ai 0.1.15__py3-none-any.whl → 0.1.17__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/cli.py CHANGED
@@ -64,6 +64,7 @@ def detect_cloud_provider(profile: Optional[str] = None) -> Optional[str]:
64
64
  """
65
65
  try:
66
66
  import os
67
+
67
68
  from databricks.sdk import WorkspaceClient
68
69
 
69
70
  # Check for environment variables that might override profile
@@ -1208,7 +1209,9 @@ def run_databricks_command(
1208
1209
  f"Using CLI-specified deployment target: {resolved_deployment_target}"
1209
1210
  )
1210
1211
  elif app_config and app_config.app and app_config.app.deployment_target:
1211
- resolved_deployment_target = app_config.app.deployment_target.value
1212
+ # deployment_target is DeploymentTarget enum (str subclass) or string
1213
+ # str() works for both since DeploymentTarget inherits from str
1214
+ resolved_deployment_target = str(app_config.app.deployment_target)
1212
1215
  logger.debug(
1213
1216
  f"Using config file deployment target: {resolved_deployment_target}"
1214
1217
  )
dao_ai/config.py CHANGED
@@ -373,15 +373,19 @@ class IsDatabricksResource(ABC, BaseModel):
373
373
  """
374
374
  from dao_ai.utils import normalize_host
375
375
 
376
- logger.trace(f"workspace_client_from called", context=context, on_behalf_of_user=self.on_behalf_of_user)
376
+ logger.trace(
377
+ "workspace_client_from called",
378
+ context=context,
379
+ on_behalf_of_user=self.on_behalf_of_user,
380
+ )
377
381
 
378
382
  # Check if we have headers in context for OBO
379
383
  if context and context.headers and self.on_behalf_of_user:
380
384
  headers = context.headers
381
385
  # Try both lowercase and title-case header names (HTTP headers are case-insensitive)
382
- forwarded_token: str = headers.get("x-forwarded-access-token") or headers.get(
383
- "X-Forwarded-Access-Token"
384
- )
386
+ forwarded_token: str = headers.get(
387
+ "x-forwarded-access-token"
388
+ ) or headers.get("X-Forwarded-Access-Token")
385
389
 
386
390
  if forwarded_token:
387
391
  forwarded_user = headers.get("x-forwarded-user") or headers.get(
@@ -2090,9 +2094,48 @@ class McpFunctionModel(BaseFunctionModel, IsDatabricksResource):
2090
2094
  if self.sql:
2091
2095
  return f"{workspace_host}/api/2.0/mcp/sql"
2092
2096
 
2093
- # Databricks App
2097
+ # Databricks App - MCP endpoint is at {app_url}/mcp
2098
+ # Try McpFunctionModel's workspace_client first (which may have credentials),
2099
+ # then fall back to DatabricksAppModel.url property (which uses its own workspace_client)
2094
2100
  if self.app:
2095
- return self.app.url
2101
+ from databricks.sdk.service.apps import App
2102
+
2103
+ app_url: str | None = None
2104
+
2105
+ # First, try using McpFunctionModel's workspace_client
2106
+ try:
2107
+ app: App = self.workspace_client.apps.get(self.app.name)
2108
+ app_url = app.url
2109
+ logger.trace(
2110
+ "Got app URL using McpFunctionModel workspace_client",
2111
+ app_name=self.app.name,
2112
+ url=app_url,
2113
+ )
2114
+ except Exception as e:
2115
+ logger.debug(
2116
+ "Failed to get app URL using McpFunctionModel workspace_client, "
2117
+ "trying DatabricksAppModel.url property",
2118
+ app_name=self.app.name,
2119
+ error=str(e),
2120
+ )
2121
+
2122
+ # Fall back to DatabricksAppModel.url property
2123
+ if not app_url:
2124
+ try:
2125
+ app_url = self.app.url
2126
+ logger.trace(
2127
+ "Got app URL using DatabricksAppModel.url property",
2128
+ app_name=self.app.name,
2129
+ url=app_url,
2130
+ )
2131
+ except Exception as e:
2132
+ raise RuntimeError(
2133
+ f"Databricks App '{self.app.name}' does not have a URL. "
2134
+ "The app may not be deployed yet, or credentials may be invalid. "
2135
+ f"Error: {e}"
2136
+ ) from e
2137
+
2138
+ return f"{app_url.rstrip('/')}/mcp"
2096
2139
 
2097
2140
  # Vector Search
2098
2141
  if self.vector_search:
@@ -2607,7 +2650,6 @@ class SupervisorModel(BaseModel):
2607
2650
 
2608
2651
  class SwarmModel(BaseModel):
2609
2652
  model_config = ConfigDict(use_enum_values=True, extra="forbid")
2610
- model: LLMModel
2611
2653
  default_agent: Optional[AgentModel | str] = None
2612
2654
  middleware: list[MiddlewareModel] = Field(
2613
2655
  default_factory=list,
@@ -2621,11 +2663,17 @@ class SwarmModel(BaseModel):
2621
2663
  class OrchestrationModel(BaseModel):
2622
2664
  model_config = ConfigDict(use_enum_values=True, extra="forbid")
2623
2665
  supervisor: Optional[SupervisorModel] = None
2624
- swarm: Optional[SwarmModel] = None
2666
+ swarm: Optional[SwarmModel | Literal[True]] = None
2625
2667
  memory: Optional[MemoryModel] = None
2626
2668
 
2627
2669
  @model_validator(mode="after")
2628
- def validate_mutually_exclusive(self) -> Self:
2670
+ def validate_and_normalize(self) -> Self:
2671
+ """Validate orchestration and normalize swarm shorthand."""
2672
+ # Convert swarm: true to SwarmModel()
2673
+ if self.swarm is True:
2674
+ self.swarm = SwarmModel()
2675
+
2676
+ # Validate mutually exclusive
2629
2677
  if self.supervisor is not None and self.swarm is not None:
2630
2678
  raise ValueError("Cannot specify both supervisor and swarm")
2631
2679
  if self.supervisor is None and self.swarm is None:
@@ -2897,9 +2945,7 @@ class AppModel(BaseModel):
2897
2945
  elif len(self.agents) == 1:
2898
2946
  default_agent: AgentModel = self.agents[0]
2899
2947
  self.orchestration = OrchestrationModel(
2900
- swarm=SwarmModel(
2901
- model=default_agent.model, default_agent=default_agent
2902
- )
2948
+ swarm=SwarmModel(default_agent=default_agent)
2903
2949
  )
2904
2950
  else:
2905
2951
  raise ValueError("At least one agent must be specified")
@@ -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",
dao_ai/tools/mcp.py CHANGED
@@ -144,31 +144,42 @@ def _should_include_tool(
144
144
  return True
145
145
 
146
146
 
147
+ def _has_auth_configured(resource: IsDatabricksResource) -> bool:
148
+ """Check if a resource has explicit authentication configured."""
149
+ return bool(
150
+ resource.on_behalf_of_user
151
+ or resource.service_principal
152
+ or resource.client_id
153
+ or resource.pat
154
+ )
155
+
156
+
147
157
  def _get_auth_resource(function: McpFunctionModel) -> IsDatabricksResource:
148
158
  """
149
159
  Get the IsDatabricksResource to use for authentication.
150
160
 
151
161
  Follows a priority hierarchy:
152
- 1. Explicit resource with auth (app, connection, genie_room, vector_search, functions)
162
+ 1. Nested resource with explicit auth (app, connection, genie_room, vector_search)
153
163
  2. McpFunctionModel itself (which also inherits from IsDatabricksResource)
154
164
 
165
+ Only uses a nested resource if it has authentication configured.
166
+ Otherwise falls back to McpFunctionModel which may have credentials set at the tool level.
167
+
155
168
  Returns the resource whose workspace_client should be used for authentication.
156
169
  """
157
- # Check each possible resource source in priority order
158
- # These resources may have their own auth configured
159
- if function.app:
170
+ # Check each possible resource source - only use if it has auth configured
171
+ if function.app and _has_auth_configured(function.app):
160
172
  return function.app
161
- if function.connection:
173
+ if function.connection and _has_auth_configured(function.connection):
162
174
  return function.connection
163
- if function.genie_room:
175
+ if function.genie_room and _has_auth_configured(function.genie_room):
164
176
  return function.genie_room
165
- if function.vector_search:
177
+ if function.vector_search and _has_auth_configured(function.vector_search):
166
178
  return function.vector_search
167
- if function.functions:
168
- # SchemaModel doesn't have auth - fall through to McpFunctionModel
169
- pass
179
+ # SchemaModel (functions) doesn't have auth - always fall through
170
180
 
171
181
  # Fall back to McpFunctionModel itself (it inherits from IsDatabricksResource)
182
+ # This allows credentials to be set at the tool level
172
183
  return function
173
184
 
174
185
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dao-ai
3
- Version: 0.1.15
3
+ Version: 0.1.17
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
@@ -235,7 +235,7 @@ app:
235
235
  - *assistant
236
236
  orchestration:
237
237
  swarm:
238
- model: *default_llm
238
+ default_agent: *assistant
239
239
  ```
240
240
 
241
241
  **💡 What's happening here?**
@@ -1,7 +1,7 @@
1
1
  dao_ai/__init__.py,sha256=18P98ExEgUaJ1Byw440Ct1ty59v6nxyWtc5S6Uq2m9Q,1062
2
2
  dao_ai/catalog.py,sha256=sPZpHTD3lPx4EZUtIWeQV7VQM89WJ6YH__wluk1v2lE,4947
3
- dao_ai/cli.py,sha256=6qwlS07_Tei6iEPXIJ-19cQVnLXd7vJDpuY4Qu0k96E,51634
4
- dao_ai/config.py,sha256=Bpaj1iDuDarAnRnTMvIYjtfbewjOSfBppZ6Sp3Id0CM,130242
3
+ dao_ai/cli.py,sha256=YRFbr7BC721SrNiOHRbr1dpeX1Ona76bGhO5vrDqTfk,51784
4
+ dao_ai/config.py,sha256=w1M4q4vbOQrCdsAlpob_2p7e8OXBi9Om0VpihMB4sH0,132150
5
5
  dao_ai/graph.py,sha256=1-uQlo7iXZQTT3uU8aYu0N5rnhw5_g_2YLwVsAs6M-U,1119
6
6
  dao_ai/logging.py,sha256=lYy4BmucCHvwW7aI3YQkQXKJtMvtTnPDu9Hnd7_O4oc,1556
7
7
  dao_ai/messages.py,sha256=4ZBzO4iFdktGSLrmhHzFjzMIt2tpaL-aQLHOQJysGnY,6959
@@ -50,7 +50,7 @@ dao_ai/middleware/tool_selector.py,sha256=POj72YdzZEiNGfW4AQXPBeVVS1RUBsiG7PBuSE
50
50
  dao_ai/orchestration/__init__.py,sha256=i85CLfRR335NcCFhaXABcMkn6WZfXnJ8cHH4YZsZN0s,1622
51
51
  dao_ai/orchestration/core.py,sha256=qoU7uMXBJCth-sqfu0jRE1L0GOn5H4LoZdRUY1Ib3DI,9585
52
52
  dao_ai/orchestration/supervisor.py,sha256=alKMEEo9G5LhdpMvTVdAMel234cZj5_MguWl4wFB7XQ,9873
53
- dao_ai/orchestration/swarm.py,sha256=8tp1eGmsQqqWpaDcjPoJckddPWohZdmmN0RGRJ_xzOA,9198
53
+ dao_ai/orchestration/swarm.py,sha256=BloDI0TWhGisv9r3-zTgJWZQy9l3hbQ5tXYggovr5i8,9467
54
54
  dao_ai/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
55
  dao_ai/providers/base.py,sha256=cJGo3UjUTPgS91dv38ePOHwQQtYhIa84ebb167CBXjk,2111
56
56
  dao_ai/providers/databricks.py,sha256=bI8lWZ2DkNac9aJWCIJzTG3lCE8MJ8n2BPurEHM1SeE,72791
@@ -59,7 +59,7 @@ dao_ai/tools/agent.py,sha256=plIWALywRjaDSnot13nYehBsrHRpBUpsVZakoGeajOE,1858
59
59
  dao_ai/tools/core.py,sha256=bRIN3BZhRQX8-Kpu3HPomliodyskCqjxynQmYbk6Vjs,3783
60
60
  dao_ai/tools/email.py,sha256=A3TsCoQgJR7UUWR0g45OPRGDpVoYwctFs1MOZMTt_d4,7389
61
61
  dao_ai/tools/genie.py,sha256=b0R51N5D58H1vpOCUCA88ALjLs58KSMn6nl80ap8_c0,11009
62
- dao_ai/tools/mcp.py,sha256=K1yMQ39UgJ0Q4xhMpNWV3AVNx929w9vxZlLoCq_jrws,22016
62
+ dao_ai/tools/mcp.py,sha256=4uvag52OJPInUEnxFLwpE0JRugTrgHeWbkP5lzIx4lg,22620
63
63
  dao_ai/tools/memory.py,sha256=lwObKimAand22Nq3Y63tsv-AXQ5SXUigN9PqRjoWKes,1836
64
64
  dao_ai/tools/python.py,sha256=jWFnZPni2sCdtd8D1CqXnZIPHnWkdK27bCJnBXpzhvo,1879
65
65
  dao_ai/tools/search.py,sha256=cJ3D9FKr1GAR6xz55dLtRkjtQsI0WRueGt9TPDFpOxc,433
@@ -68,8 +68,8 @@ dao_ai/tools/sql.py,sha256=FG-Aa0FAUAnhCuZvao1J-y-cMM6bU5eCujNbsYn0xDw,7864
68
68
  dao_ai/tools/time.py,sha256=tufJniwivq29y0LIffbgeBTIDE6VgrLpmVf8Qr90qjw,9224
69
69
  dao_ai/tools/unity_catalog.py,sha256=oBlW6pH-Ne08g60QW9wVi_tyeVYDiecuNoxQbIIFmN8,16515
70
70
  dao_ai/tools/vector_search.py,sha256=LF_72vlEF6TwUjKVv6nkUetLK766l9Kl6DQQTc9ebJI,15888
71
- dao_ai-0.1.15.dist-info/METADATA,sha256=-cei_FUcN2BBlatDymERXwoag5tuuoJvdOcBBnsF8qU,16830
72
- dao_ai-0.1.15.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
73
- dao_ai-0.1.15.dist-info/entry_points.txt,sha256=Xa-UFyc6gWGwMqMJOt06ZOog2vAfygV_DSwg1AiP46g,43
74
- dao_ai-0.1.15.dist-info/licenses/LICENSE,sha256=YZt3W32LtPYruuvHE9lGk2bw6ZPMMJD8yLrjgHybyz4,1069
75
- dao_ai-0.1.15.dist-info/RECORD,,
71
+ dao_ai-0.1.17.dist-info/METADATA,sha256=fLwALpNC9d6hHemJ8Pt38Sfq-JLi3YGm7xxjm_wljd8,16836
72
+ dao_ai-0.1.17.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
73
+ dao_ai-0.1.17.dist-info/entry_points.txt,sha256=Xa-UFyc6gWGwMqMJOt06ZOog2vAfygV_DSwg1AiP46g,43
74
+ dao_ai-0.1.17.dist-info/licenses/LICENSE,sha256=YZt3W32LtPYruuvHE9lGk2bw6ZPMMJD8yLrjgHybyz4,1069
75
+ dao_ai-0.1.17.dist-info/RECORD,,