dao-ai 0.1.9__py3-none-any.whl → 0.1.11__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/{app_server.py → apps/handlers.py} +20 -39
- dao_ai/apps/model_serving.py +29 -0
- dao_ai/apps/resources.py +1072 -0
- dao_ai/apps/server.py +39 -0
- dao_ai/cli.py +51 -4
- dao_ai/config.py +34 -4
- dao_ai/memory/postgres.py +29 -4
- dao_ai/models.py +327 -370
- dao_ai/providers/databricks.py +62 -20
- dao_ai/tools/mcp.py +165 -68
- {dao_ai-0.1.9.dist-info → dao_ai-0.1.11.dist-info}/METADATA +2 -2
- {dao_ai-0.1.9.dist-info → dao_ai-0.1.11.dist-info}/RECORD +16 -13
- dao_ai/agent_as_code.py +0 -22
- {dao_ai-0.1.9.dist-info → dao_ai-0.1.11.dist-info}/WHEEL +0 -0
- {dao_ai-0.1.9.dist-info → dao_ai-0.1.11.dist-info}/entry_points.txt +0 -0
- {dao_ai-0.1.9.dist-info → dao_ai-0.1.11.dist-info}/licenses/LICENSE +0 -0
dao_ai/apps/server.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""
|
|
2
|
+
App server module for running dao-ai agents as Databricks Apps.
|
|
3
|
+
|
|
4
|
+
This module provides the entry point for deploying dao-ai agents as Databricks Apps
|
|
5
|
+
using MLflow's AgentServer. It follows the same pattern as model_serving.py but
|
|
6
|
+
uses the AgentServer for the Databricks Apps runtime.
|
|
7
|
+
|
|
8
|
+
Configuration Loading:
|
|
9
|
+
The config path is specified via the DAO_AI_CONFIG_PATH environment variable,
|
|
10
|
+
or defaults to model_config.yaml in the current directory.
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
# With environment variable
|
|
14
|
+
DAO_AI_CONFIG_PATH=/path/to/config.yaml python -m dao_ai.apps.server
|
|
15
|
+
|
|
16
|
+
# With default model_config.yaml in current directory
|
|
17
|
+
python -m dao_ai.apps.server
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from mlflow.genai.agent_server import AgentServer
|
|
21
|
+
|
|
22
|
+
# Import the agent handlers to register the invoke and stream decorators
|
|
23
|
+
# This MUST happen before creating the AgentServer instance
|
|
24
|
+
import dao_ai.apps.handlers # noqa: E402, F401
|
|
25
|
+
|
|
26
|
+
# Create the AgentServer instance
|
|
27
|
+
agent_server = AgentServer("ResponsesAgent", enable_chat_proxy=True)
|
|
28
|
+
|
|
29
|
+
# Define the app as a module level variable to enable multiple workers
|
|
30
|
+
app = agent_server.app
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def main() -> None:
|
|
34
|
+
"""Entry point for running the agent server."""
|
|
35
|
+
agent_server.run(app_import_string="dao_ai.apps.server:app")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
if __name__ == "__main__":
|
|
39
|
+
main()
|
dao_ai/cli.py
CHANGED
|
@@ -285,6 +285,15 @@ Examples:
|
|
|
285
285
|
action="store_true",
|
|
286
286
|
help="Perform a dry run without executing the deployment or run commands",
|
|
287
287
|
)
|
|
288
|
+
bundle_parser.add_argument(
|
|
289
|
+
"--deployment-target",
|
|
290
|
+
type=str,
|
|
291
|
+
choices=["model_serving", "apps"],
|
|
292
|
+
default=None,
|
|
293
|
+
help="Agent deployment target: 'model_serving' or 'apps'. "
|
|
294
|
+
"If not specified, uses app.deployment_target from config file, "
|
|
295
|
+
"or defaults to 'model_serving'. Passed to the deploy notebook.",
|
|
296
|
+
)
|
|
288
297
|
|
|
289
298
|
# Deploy command
|
|
290
299
|
deploy_parser: ArgumentParser = subparsers.add_parser(
|
|
@@ -314,8 +323,10 @@ Examples:
|
|
|
314
323
|
"--target",
|
|
315
324
|
type=str,
|
|
316
325
|
choices=["model_serving", "apps"],
|
|
317
|
-
default=
|
|
318
|
-
help="Deployment target: 'model_serving'
|
|
326
|
+
default=None,
|
|
327
|
+
help="Deployment target: 'model_serving' or 'apps'. "
|
|
328
|
+
"If not specified, uses app.deployment_target from config file, "
|
|
329
|
+
"or defaults to 'model_serving'.",
|
|
319
330
|
)
|
|
320
331
|
|
|
321
332
|
# List MCP tools command
|
|
@@ -743,8 +754,19 @@ def handle_deploy_command(options: Namespace) -> None:
|
|
|
743
754
|
try:
|
|
744
755
|
config: AppConfig = AppConfig.from_file(options.config)
|
|
745
756
|
|
|
746
|
-
#
|
|
747
|
-
|
|
757
|
+
# Hybrid target resolution:
|
|
758
|
+
# 1. CLI --target takes precedence
|
|
759
|
+
# 2. Fall back to config.app.deployment_target
|
|
760
|
+
# 3. Default to MODEL_SERVING (handled in deploy_agent)
|
|
761
|
+
target: DeploymentTarget | None = None
|
|
762
|
+
if options.target is not None:
|
|
763
|
+
target = DeploymentTarget(options.target)
|
|
764
|
+
logger.info(f"Using CLI-specified deployment target: {target.value}")
|
|
765
|
+
elif config.app is not None and config.app.deployment_target is not None:
|
|
766
|
+
target = config.app.deployment_target
|
|
767
|
+
logger.info(f"Using config file deployment target: {target.value}")
|
|
768
|
+
else:
|
|
769
|
+
logger.info("No deployment target specified, defaulting to model_serving")
|
|
748
770
|
|
|
749
771
|
config.create_agent()
|
|
750
772
|
config.deploy_agent(target=target)
|
|
@@ -1097,6 +1119,7 @@ def run_databricks_command(
|
|
|
1097
1119
|
target: Optional[str] = None,
|
|
1098
1120
|
cloud: Optional[str] = None,
|
|
1099
1121
|
dry_run: bool = False,
|
|
1122
|
+
deployment_target: Optional[str] = None,
|
|
1100
1123
|
) -> None:
|
|
1101
1124
|
"""Execute a databricks CLI command with optional profile, target, and cloud.
|
|
1102
1125
|
|
|
@@ -1107,6 +1130,8 @@ def run_databricks_command(
|
|
|
1107
1130
|
target: Optional bundle target name (if not provided, auto-generated from app name and cloud)
|
|
1108
1131
|
cloud: Optional cloud provider ('azure', 'aws', 'gcp'). Auto-detected if not specified.
|
|
1109
1132
|
dry_run: If True, print the command without executing
|
|
1133
|
+
deployment_target: Optional agent deployment target ('model_serving' or 'apps').
|
|
1134
|
+
Passed to the deploy notebook via bundle variable.
|
|
1110
1135
|
"""
|
|
1111
1136
|
config_path = Path(config) if config else None
|
|
1112
1137
|
|
|
@@ -1162,6 +1187,24 @@ def run_databricks_command(
|
|
|
1162
1187
|
|
|
1163
1188
|
cmd.append(f'--var="config_path={relative_config}"')
|
|
1164
1189
|
|
|
1190
|
+
# Add deployment_target variable for notebooks (hybrid resolution)
|
|
1191
|
+
# Priority: CLI arg > config file > default (model_serving)
|
|
1192
|
+
resolved_deployment_target: str = "model_serving"
|
|
1193
|
+
if deployment_target is not None:
|
|
1194
|
+
resolved_deployment_target = deployment_target
|
|
1195
|
+
logger.debug(
|
|
1196
|
+
f"Using CLI-specified deployment target: {resolved_deployment_target}"
|
|
1197
|
+
)
|
|
1198
|
+
elif app_config and app_config.app and app_config.app.deployment_target:
|
|
1199
|
+
resolved_deployment_target = app_config.app.deployment_target.value
|
|
1200
|
+
logger.debug(
|
|
1201
|
+
f"Using config file deployment target: {resolved_deployment_target}"
|
|
1202
|
+
)
|
|
1203
|
+
else:
|
|
1204
|
+
logger.debug("Using default deployment target: model_serving")
|
|
1205
|
+
|
|
1206
|
+
cmd.append(f'--var="deployment_target={resolved_deployment_target}"')
|
|
1207
|
+
|
|
1165
1208
|
logger.debug(f"Executing command: {' '.join(cmd)}")
|
|
1166
1209
|
|
|
1167
1210
|
if dry_run:
|
|
@@ -1204,6 +1247,7 @@ def handle_bundle_command(options: Namespace) -> None:
|
|
|
1204
1247
|
target: Optional[str] = options.target
|
|
1205
1248
|
cloud: Optional[str] = options.cloud
|
|
1206
1249
|
dry_run: bool = options.dry_run
|
|
1250
|
+
deployment_target: Optional[str] = options.deployment_target
|
|
1207
1251
|
|
|
1208
1252
|
if options.deploy:
|
|
1209
1253
|
logger.info("Deploying DAO AI asset bundle...")
|
|
@@ -1214,6 +1258,7 @@ def handle_bundle_command(options: Namespace) -> None:
|
|
|
1214
1258
|
target=target,
|
|
1215
1259
|
cloud=cloud,
|
|
1216
1260
|
dry_run=dry_run,
|
|
1261
|
+
deployment_target=deployment_target,
|
|
1217
1262
|
)
|
|
1218
1263
|
if options.run:
|
|
1219
1264
|
logger.info("Running DAO AI system with current configuration...")
|
|
@@ -1225,6 +1270,7 @@ def handle_bundle_command(options: Namespace) -> None:
|
|
|
1225
1270
|
target=target,
|
|
1226
1271
|
cloud=cloud,
|
|
1227
1272
|
dry_run=dry_run,
|
|
1273
|
+
deployment_target=deployment_target,
|
|
1228
1274
|
)
|
|
1229
1275
|
if options.destroy:
|
|
1230
1276
|
logger.info("Destroying DAO AI system with current configuration...")
|
|
@@ -1235,6 +1281,7 @@ def handle_bundle_command(options: Namespace) -> None:
|
|
|
1235
1281
|
target=target,
|
|
1236
1282
|
cloud=cloud,
|
|
1237
1283
|
dry_run=dry_run,
|
|
1284
|
+
deployment_target=deployment_target,
|
|
1238
1285
|
)
|
|
1239
1286
|
else:
|
|
1240
1287
|
logger.warning("No action specified. Use --deploy, --run or --destroy flags.")
|
dao_ai/config.py
CHANGED
|
@@ -344,7 +344,14 @@ class IsDatabricksResource(ABC, BaseModel):
|
|
|
344
344
|
else None
|
|
345
345
|
)
|
|
346
346
|
|
|
347
|
-
if client_id_value and client_secret_value
|
|
347
|
+
if client_id_value and client_secret_value:
|
|
348
|
+
# If workspace_host is not provided, check DATABRICKS_HOST env var first,
|
|
349
|
+
# then fall back to WorkspaceClient().config.host
|
|
350
|
+
if not workspace_host_value:
|
|
351
|
+
workspace_host_value = os.getenv("DATABRICKS_HOST")
|
|
352
|
+
if not workspace_host_value:
|
|
353
|
+
workspace_host_value = WorkspaceClient().config.host
|
|
354
|
+
|
|
348
355
|
logger.debug(
|
|
349
356
|
f"Creating WorkspaceClient for {self.__class__.__name__} with service principal: "
|
|
350
357
|
f"client_id={client_id_value}, host={workspace_host_value}"
|
|
@@ -2803,6 +2810,11 @@ class AppModel(BaseModel):
|
|
|
2803
2810
|
"which is supported by Databricks Model Serving. This allows deploying from "
|
|
2804
2811
|
"environments with different Python versions (e.g., Databricks Apps with 3.11).",
|
|
2805
2812
|
)
|
|
2813
|
+
deployment_target: Optional[DeploymentTarget] = Field(
|
|
2814
|
+
default=None,
|
|
2815
|
+
description="Default deployment target. If not specified, defaults to MODEL_SERVING. "
|
|
2816
|
+
"Can be overridden via CLI --target flag. Options: 'model_serving' or 'apps'.",
|
|
2817
|
+
)
|
|
2806
2818
|
|
|
2807
2819
|
@model_validator(mode="after")
|
|
2808
2820
|
def set_databricks_env_vars(self) -> Self:
|
|
@@ -3398,7 +3410,7 @@ class AppConfig(BaseModel):
|
|
|
3398
3410
|
|
|
3399
3411
|
def deploy_agent(
|
|
3400
3412
|
self,
|
|
3401
|
-
target: DeploymentTarget =
|
|
3413
|
+
target: DeploymentTarget | None = None,
|
|
3402
3414
|
w: WorkspaceClient | None = None,
|
|
3403
3415
|
vsc: "VectorSearchClient | None" = None,
|
|
3404
3416
|
pat: str | None = None,
|
|
@@ -3409,8 +3421,14 @@ class AppConfig(BaseModel):
|
|
|
3409
3421
|
"""
|
|
3410
3422
|
Deploy the agent to the specified target.
|
|
3411
3423
|
|
|
3424
|
+
Target resolution follows this priority:
|
|
3425
|
+
1. Explicit `target` parameter (if provided)
|
|
3426
|
+
2. `app.deployment_target` from config file (if set)
|
|
3427
|
+
3. Default: MODEL_SERVING
|
|
3428
|
+
|
|
3412
3429
|
Args:
|
|
3413
|
-
target: The deployment target (MODEL_SERVING or APPS).
|
|
3430
|
+
target: The deployment target (MODEL_SERVING or APPS). If None, uses
|
|
3431
|
+
config.app.deployment_target or defaults to MODEL_SERVING.
|
|
3414
3432
|
w: Optional WorkspaceClient instance
|
|
3415
3433
|
vsc: Optional VectorSearchClient instance
|
|
3416
3434
|
pat: Optional personal access token for authentication
|
|
@@ -3421,6 +3439,18 @@ class AppConfig(BaseModel):
|
|
|
3421
3439
|
from dao_ai.providers.base import ServiceProvider
|
|
3422
3440
|
from dao_ai.providers.databricks import DatabricksProvider
|
|
3423
3441
|
|
|
3442
|
+
# Resolve target using hybrid logic:
|
|
3443
|
+
# 1. Explicit parameter takes precedence
|
|
3444
|
+
# 2. Fall back to config.app.deployment_target
|
|
3445
|
+
# 3. Default to MODEL_SERVING
|
|
3446
|
+
resolved_target: DeploymentTarget
|
|
3447
|
+
if target is not None:
|
|
3448
|
+
resolved_target = target
|
|
3449
|
+
elif self.app is not None and self.app.deployment_target is not None:
|
|
3450
|
+
resolved_target = self.app.deployment_target
|
|
3451
|
+
else:
|
|
3452
|
+
resolved_target = DeploymentTarget.MODEL_SERVING
|
|
3453
|
+
|
|
3424
3454
|
provider: ServiceProvider = DatabricksProvider(
|
|
3425
3455
|
w=w,
|
|
3426
3456
|
vsc=vsc,
|
|
@@ -3429,7 +3459,7 @@ class AppConfig(BaseModel):
|
|
|
3429
3459
|
client_secret=client_secret,
|
|
3430
3460
|
workspace_host=workspace_host,
|
|
3431
3461
|
)
|
|
3432
|
-
provider.deploy_agent(self, target=
|
|
3462
|
+
provider.deploy_agent(self, target=resolved_target)
|
|
3433
3463
|
|
|
3434
3464
|
def find_agents(
|
|
3435
3465
|
self, predicate: Callable[[AgentModel], bool] | None = None
|
dao_ai/memory/postgres.py
CHANGED
|
@@ -178,7 +178,20 @@ class AsyncPostgresStoreManager(StoreManagerBase):
|
|
|
178
178
|
def _setup(self):
|
|
179
179
|
if self._setup_complete:
|
|
180
180
|
return
|
|
181
|
-
|
|
181
|
+
try:
|
|
182
|
+
# Check if we're already in an async context
|
|
183
|
+
asyncio.get_running_loop()
|
|
184
|
+
# If we get here, we're in an async context - raise to caller
|
|
185
|
+
raise RuntimeError(
|
|
186
|
+
"Cannot call sync _setup() from async context. "
|
|
187
|
+
"Use await _async_setup() instead."
|
|
188
|
+
)
|
|
189
|
+
except RuntimeError as e:
|
|
190
|
+
if "no running event loop" in str(e).lower():
|
|
191
|
+
# No event loop running - safe to use asyncio.run()
|
|
192
|
+
asyncio.run(self._async_setup())
|
|
193
|
+
else:
|
|
194
|
+
raise
|
|
182
195
|
|
|
183
196
|
async def _async_setup(self):
|
|
184
197
|
if self._setup_complete:
|
|
@@ -237,13 +250,25 @@ class AsyncPostgresCheckpointerManager(CheckpointManagerBase):
|
|
|
237
250
|
|
|
238
251
|
def _setup(self):
|
|
239
252
|
"""
|
|
240
|
-
Run the async setup.
|
|
253
|
+
Run the async setup. For async contexts, use await _async_setup() directly.
|
|
241
254
|
"""
|
|
242
255
|
if self._setup_complete:
|
|
243
256
|
return
|
|
244
257
|
|
|
245
|
-
|
|
246
|
-
|
|
258
|
+
try:
|
|
259
|
+
# Check if we're already in an async context
|
|
260
|
+
asyncio.get_running_loop()
|
|
261
|
+
# If we get here, we're in an async context - raise to caller
|
|
262
|
+
raise RuntimeError(
|
|
263
|
+
"Cannot call sync _setup() from async context. "
|
|
264
|
+
"Use await _async_setup() instead."
|
|
265
|
+
)
|
|
266
|
+
except RuntimeError as e:
|
|
267
|
+
if "no running event loop" in str(e).lower():
|
|
268
|
+
# No event loop running - safe to use asyncio.run()
|
|
269
|
+
asyncio.run(self._async_setup())
|
|
270
|
+
else:
|
|
271
|
+
raise
|
|
247
272
|
|
|
248
273
|
async def _async_setup(self):
|
|
249
274
|
"""
|