databricks-agentbricks 0.2.0__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.
- databricks_agentbricks/__init__.py +1 -0
- databricks_agentbricks/_group.py +217 -0
- databricks_agentbricks/agent_project.py +657 -0
- databricks_agentbricks/app_resources.py +139 -0
- databricks_agentbricks/cli/__init__.py +12 -0
- databricks_agentbricks/cli/app.py +108 -0
- databricks_agentbricks/cli/app_auth.py +198 -0
- databricks_agentbricks/cli/auth.py +142 -0
- databricks_agentbricks/cli/deploy.py +954 -0
- databricks_agentbricks/cli/dev.py +310 -0
- databricks_agentbricks/cli/endpoint.py +145 -0
- databricks_agentbricks/cli/endpoint_examples.py +33 -0
- databricks_agentbricks/cli/endpoint_output.py +53 -0
- databricks_agentbricks/cli/endpoint_request.py +71 -0
- databricks_agentbricks/cli/endpoint_transport.py +148 -0
- databricks_agentbricks/cli/help.py +467 -0
- databricks_agentbricks/cli/init.py +496 -0
- databricks_agentbricks/cli/mcp.py +75 -0
- databricks_agentbricks/cli/memory.py +520 -0
- databricks_agentbricks/cli/sandbox.py +823 -0
- databricks_agentbricks/cli/sessions.py +588 -0
- databricks_agentbricks/cli/tools.py +456 -0
- databricks_agentbricks/cli/tracing.py +724 -0
- databricks_agentbricks/databricks_cli.py +38 -0
- databricks_agentbricks/errors.py +114 -0
- databricks_agentbricks/lakebase_runtime_store.py +107 -0
- databricks_agentbricks/legacy_lakebase_runtime_store.py +74 -0
- databricks_agentbricks/project_config.py +157 -0
- databricks_agentbricks/project_types.py +55 -0
- databricks_agentbricks/py.typed +0 -0
- databricks_agentbricks/render.py +349 -0
- databricks_agentbricks/templates/agent-bricks-migrate/SKILL.md +101 -0
- databricks_agentbricks/templates/agent-langgraph/.env.example +36 -0
- databricks_agentbricks/templates/agent-langgraph/.gitignore +199 -0
- databricks_agentbricks/templates/agent-langgraph/AGENTKIT_CONTRACT.md +95 -0
- databricks_agentbricks/templates/agent-langgraph/AGENTS.md +31 -0
- databricks_agentbricks/templates/agent-langgraph/CLAUDE.md +1 -0
- databricks_agentbricks/templates/agent-langgraph/README.md +141 -0
- databricks_agentbricks/templates/agent-langgraph/agent/__init__.py +0 -0
- databricks_agentbricks/templates/agent-langgraph/agent/agent.py +177 -0
- databricks_agentbricks/templates/agent-langgraph/agent/mcps.py +27 -0
- databricks_agentbricks/templates/agent-langgraph/agent/tools/__init__.py +24 -0
- databricks_agentbricks/templates/agent-langgraph/agent/tools/sample_tool.py +15 -0
- databricks_agentbricks/templates/agent-langgraph/agent/tools/send_message.py +17 -0
- databricks_agentbricks/templates/agent-langgraph/app.yaml +15 -0
- databricks_agentbricks/templates/agent-langgraph/pyproject.toml +42 -0
- databricks_agentbricks/templates/agent-langgraph/runtime/__init__.py +0 -0
- databricks_agentbricks/templates/agent-langgraph/runtime/adapter.py +144 -0
- databricks_agentbricks/templates/agent-langgraph/runtime/main.py +25 -0
- databricks_agentbricks/templates/agent-langgraph/tests/__init__.py +0 -0
- databricks_agentbricks/templates/agent-langgraph/tests/test_agent.py +211 -0
- databricks_agentbricks/templates/agent-langgraph/tests/test_runtime.py +19 -0
- databricks_agentbricks/templates/agent-langgraph/tests/test_workspace.py +28 -0
- databricks_agentbricks/templates/agent-openai/.env.example +37 -0
- databricks_agentbricks/templates/agent-openai/.gitignore +199 -0
- databricks_agentbricks/templates/agent-openai/AGENTKIT_CONTRACT.md +107 -0
- databricks_agentbricks/templates/agent-openai/AGENTS.md +76 -0
- databricks_agentbricks/templates/agent-openai/CLAUDE.md +1 -0
- databricks_agentbricks/templates/agent-openai/README.md +143 -0
- databricks_agentbricks/templates/agent-openai/agent/__init__.py +0 -0
- databricks_agentbricks/templates/agent-openai/agent/agent.py +205 -0
- databricks_agentbricks/templates/agent-openai/agent/mcps.py +28 -0
- databricks_agentbricks/templates/agent-openai/agent/tools/__init__.py +24 -0
- databricks_agentbricks/templates/agent-openai/agent/tools/sample_tool.py +15 -0
- databricks_agentbricks/templates/agent-openai/agent/tools/send_message.py +18 -0
- databricks_agentbricks/templates/agent-openai/app.yaml +15 -0
- databricks_agentbricks/templates/agent-openai/pyproject.toml +42 -0
- databricks_agentbricks/templates/agent-openai/runtime/__init__.py +0 -0
- databricks_agentbricks/templates/agent-openai/runtime/adapter.py +188 -0
- databricks_agentbricks/templates/agent-openai/runtime/main.py +25 -0
- databricks_agentbricks/templates/agent-openai/tests/__init__.py +0 -0
- databricks_agentbricks/templates/agent-openai/tests/test_agent.py +326 -0
- databricks_agentbricks/templates/agent-openai/tests/test_runtime.py +19 -0
- databricks_agentbricks/templates/agent-openai/tests/test_workspace.py +28 -0
- databricks_agentbricks/templates/custom-agent-langgraph/.env.example +1 -0
- databricks_agentbricks/templates/custom-agent-langgraph/.gitignore +11 -0
- databricks_agentbricks/templates/custom-agent-langgraph/README.md +22 -0
- databricks_agentbricks/templates/custom-agent-langgraph/agent/__init__.py +0 -0
- databricks_agentbricks/templates/custom-agent-langgraph/agent/agent.py +27 -0
- databricks_agentbricks/templates/custom-agent-langgraph/app.yaml +1 -0
- databricks_agentbricks/templates/custom-agent-langgraph/pyproject.toml +38 -0
- databricks_agentbricks/templates/custom-agent-langgraph/runtime/__init__.py +0 -0
- databricks_agentbricks/templates/custom-agent-langgraph/runtime/main.py +31 -0
- databricks_agentbricks/templates/custom-agent-langgraph/tests/test_runtime.py +29 -0
- databricks_agentbricks/templates/custom-agent-openai/.env.example +1 -0
- databricks_agentbricks/templates/custom-agent-openai/.gitignore +11 -0
- databricks_agentbricks/templates/custom-agent-openai/README.md +22 -0
- databricks_agentbricks/templates/custom-agent-openai/agent/__init__.py +0 -0
- databricks_agentbricks/templates/custom-agent-openai/agent/agent.py +29 -0
- databricks_agentbricks/templates/custom-agent-openai/app.yaml +1 -0
- databricks_agentbricks/templates/custom-agent-openai/pyproject.toml +38 -0
- databricks_agentbricks/templates/custom-agent-openai/runtime/__init__.py +0 -0
- databricks_agentbricks/templates/custom-agent-openai/runtime/main.py +31 -0
- databricks_agentbricks/templates/custom-agent-openai/tests/test_runtime.py +29 -0
- databricks_agentbricks/templates/sandbox_mcp.py +39 -0
- databricks_agentbricks/templates/sandbox_mcp_langgraph.py +39 -0
- databricks_agentbricks/templates/ui/agent-langgraph/CHAT_APP.md +49 -0
- databricks_agentbricks/templates/ui/agent-langgraph/runtime/main.py +26 -0
- databricks_agentbricks/templates/ui/agent-langgraph/runtime/ui.py +528 -0
- databricks_agentbricks/templates/ui/agent-langgraph/tests/test_demo_ui.py +542 -0
- databricks_agentbricks/templates/ui/agent-langgraph/ui/agentbricks-mark.png +0 -0
- databricks_agentbricks/templates/ui/agent-langgraph/ui/app.js +1464 -0
- databricks_agentbricks/templates/ui/agent-langgraph/ui/index.html +281 -0
- databricks_agentbricks/templates/ui/agent-langgraph/ui/styles.css +1913 -0
- databricks_agentbricks/templates/ui/agent-openai/CHAT_APP.md +54 -0
- databricks_agentbricks/templates/ui/agent-openai/runtime/main.py +26 -0
- databricks_agentbricks/templates/ui/agent-openai/runtime/ui.py +521 -0
- databricks_agentbricks/templates/ui/agent-openai/tests/test_demo_ui.py +512 -0
- databricks_agentbricks/templates/ui/agent-openai/ui/agentbricks-mark.png +0 -0
- databricks_agentbricks/templates/ui/agent-openai/ui/app.js +1428 -0
- databricks_agentbricks/templates/ui/agent-openai/ui/index.html +281 -0
- databricks_agentbricks/templates/ui/agent-openai/ui/styles.css +1913 -0
- databricks_agentbricks/theme.py +72 -0
- databricks_agentbricks-0.2.0.dist-info/METADATA +897 -0
- databricks_agentbricks-0.2.0.dist-info/RECORD +159 -0
- databricks_agentbricks-0.2.0.dist-info/WHEEL +4 -0
- databricks_agentbricks-0.2.0.dist-info/entry_points.txt +2 -0
- databricks_agentbricks-0.2.0.dist-info/licenses/NOTICE +39 -0
- databricks_agentkit/__init__.py +67 -0
- databricks_agentkit/_api_client.py +673 -0
- databricks_agentkit/_pagination.py +25 -0
- databricks_agentkit/client.py +31 -0
- databricks_agentkit/langgraph/__init__.py +98 -0
- databricks_agentkit/langgraph/genie.py +50 -0
- databricks_agentkit/langgraph/mcp.py +216 -0
- databricks_agentkit/langgraph/memory.py +67 -0
- databricks_agentkit/langgraph/session_store.py +485 -0
- databricks_agentkit/memory_store.py +371 -0
- databricks_agentkit/models.py +330 -0
- databricks_agentkit/openai/__init__.py +93 -0
- databricks_agentkit/openai/genie.py +50 -0
- databricks_agentkit/openai/mcp.py +189 -0
- databricks_agentkit/openai/memory.py +67 -0
- databricks_agentkit/openai/sessions.py +157 -0
- databricks_agentkit/py.typed +0 -0
- databricks_agentkit/runtime/ARCHITECTURE.md +64 -0
- databricks_agentkit/runtime/README.md +184 -0
- databricks_agentkit/runtime/__init__.py +69 -0
- databricks_agentkit/runtime/app.py +316 -0
- databricks_agentkit/runtime/auth.py +149 -0
- databricks_agentkit/runtime/durability/__init__.py +1 -0
- databricks_agentkit/runtime/durability/execution.py +133 -0
- databricks_agentkit/runtime/durability/heartbeat.py +64 -0
- databricks_agentkit/runtime/durability/lakebase_runtime_store.py +648 -0
- databricks_agentkit/runtime/durability/recovery.py +70 -0
- databricks_agentkit/runtime/durability/store.py +33 -0
- databricks_agentkit/runtime/execution.py +160 -0
- databricks_agentkit/runtime/genie.py +224 -0
- databricks_agentkit/runtime/mcp_auth.py +114 -0
- databricks_agentkit/runtime/model_services.py +90 -0
- databricks_agentkit/runtime/runtime.py +273 -0
- databricks_agentkit/runtime/session_store_client.py +120 -0
- databricks_agentkit/runtime/store.py +287 -0
- databricks_agentkit/runtime/tool_manifest.py +223 -0
- databricks_agentkit/runtime/tracing.py +71 -0
- databricks_agentkit/runtime/types.py +118 -0
- databricks_agentkit/runtime/workspace.py +27 -0
- databricks_agentkit/session_store.py +434 -0
- databricks_agentkit/timefmt.py +85 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Ergonomic resource wrapper over the generated service API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Optional
|
|
6
|
+
|
|
7
|
+
from databricks_agentkit._api_client import _AgentBricksApiClient
|
|
8
|
+
from databricks_agentkit.memory_store import MemoryStores
|
|
9
|
+
from databricks_agentkit.session_store import SessionStores
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
# Imported lazily elsewhere: pulling databricks.sdk costs ~0.7s, so keeping it out of the
|
|
13
|
+
# import path lets local CLI commands (help, init, tools, bind, completion) start fast.
|
|
14
|
+
from databricks.sdk import WorkspaceClient
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AgentKitClient:
|
|
18
|
+
"""High-level client for AgentKit memory and session APIs.
|
|
19
|
+
|
|
20
|
+
``WorkspaceClient.mason`` will replace the private transport once the generated SDK is
|
|
21
|
+
released; the resource-oriented public surface remains unchanged.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
workspace_client: An authenticated Databricks workspace client. When omitted,
|
|
25
|
+
the Databricks SDK's default authentication resolution is used.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, workspace_client: Optional[WorkspaceClient] = None) -> None:
|
|
29
|
+
api = _AgentBricksApiClient(workspace_client=workspace_client)
|
|
30
|
+
self.memory_stores = MemoryStores(api)
|
|
31
|
+
self.session_stores = SessionStores(api)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""LangGraph adapter for running an agent on Databricks (installed via ``databricks-agentbricks[langgraph]``).
|
|
2
|
+
|
|
3
|
+
Composable pieces you drop into an existing LangGraph agent — a session-store checkpointer, MCP
|
|
4
|
+
tools declared in ``agent.toml``, long-term memory tools, and MLflow tracing. Each maps onto a slot
|
|
5
|
+
LangGraph already has, so migrating an existing agent is a graft, not a rewrite::
|
|
6
|
+
|
|
7
|
+
from databricks_agentkit.langgraph import (
|
|
8
|
+
checkpointer,
|
|
9
|
+
thread_config,
|
|
10
|
+
mcp_tools,
|
|
11
|
+
configure_tracing,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
configure_tracing()
|
|
15
|
+
agent = create_agent(
|
|
16
|
+
model=...,
|
|
17
|
+
tools=[
|
|
18
|
+
*your_tools,
|
|
19
|
+
*await mcp_tools(),
|
|
20
|
+
], # mcp_tools() includes your agent.toml servers
|
|
21
|
+
checkpointer=checkpointer(), # durable when AGENT_SESSION_STORE is set
|
|
22
|
+
)
|
|
23
|
+
result = await agent.ainvoke(inputs, config=thread_config(session_id))
|
|
24
|
+
|
|
25
|
+
These need the agent stack (databricks-langchain, langgraph, langchain, fastapi, mlflow), so they sit
|
|
26
|
+
behind the ``[langgraph]`` extra to keep a plain ``databricks-agentbricks`` installation independent of agent frameworks.
|
|
27
|
+
|
|
28
|
+
``__all__`` is the curated surface. Other entry points (``mcp_client``, ``DatabricksSessionStoreSaver``)
|
|
29
|
+
are reachable by their submodule paths but not re-exported here.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from typing import TYPE_CHECKING
|
|
33
|
+
|
|
34
|
+
if TYPE_CHECKING:
|
|
35
|
+
from databricks_agentkit.langgraph.genie import genie_tools
|
|
36
|
+
from databricks_agentkit.langgraph.mcp import mcp_tools
|
|
37
|
+
from databricks_agentkit.langgraph.memory import memory_tools
|
|
38
|
+
from databricks_agentkit.langgraph.session_store import checkpointer, thread_config
|
|
39
|
+
from databricks_agentkit.runtime import (
|
|
40
|
+
start_trace,
|
|
41
|
+
workspace_client,
|
|
42
|
+
workspace_headers,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def configure_tracing() -> None:
|
|
47
|
+
"""Enable MLflow tracing with LangChain autologging. Call once at startup.
|
|
48
|
+
|
|
49
|
+
Safe to call unconditionally — tracing turns on only when the MLflow destination and experiment
|
|
50
|
+
are configured in the environment (see :func:`databricks_agentkit.runtime.configure_tracing`).
|
|
51
|
+
"""
|
|
52
|
+
import mlflow
|
|
53
|
+
|
|
54
|
+
from databricks_agentkit.runtime import configure_tracing as _configure_tracing
|
|
55
|
+
|
|
56
|
+
_configure_tracing(autolog=mlflow.langchain.autolog)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
__all__ = [
|
|
60
|
+
"genie_tools",
|
|
61
|
+
# MCP tools from agent.toml (plus any servers you pass) — add them to your agent's tool list.
|
|
62
|
+
"mcp_tools",
|
|
63
|
+
# Long-term memory tools (opt-in via AGENT_MEMORY_STORE) — add to your tool list.
|
|
64
|
+
"memory_tools",
|
|
65
|
+
# Session persistence — pass checkpointer() to create_agent(checkpointer=...) and
|
|
66
|
+
# thread_config(session_id) as the per-request run config.
|
|
67
|
+
"checkpointer",
|
|
68
|
+
"thread_config",
|
|
69
|
+
# MLflow tracing (LangChain autolog bound in) — call configure_tracing() once at startup.
|
|
70
|
+
"configure_tracing",
|
|
71
|
+
# Wrap each invocation in start_trace() so a trace is recorded (pass session_id= to tag it).
|
|
72
|
+
"start_trace",
|
|
73
|
+
# Workspace SDK client construction.
|
|
74
|
+
"workspace_client",
|
|
75
|
+
"workspace_headers",
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
# Re-exports resolved lazily (PEP 562) so importing one submodule (e.g. ``.mcp``) does not eagerly
|
|
79
|
+
# pull in the others' dependencies. ``configure_tracing`` is defined above (binds LangChain autolog).
|
|
80
|
+
_MODULE_BY_NAME = {
|
|
81
|
+
"genie_tools": "databricks_agentkit.langgraph.genie",
|
|
82
|
+
"mcp_tools": "databricks_agentkit.langgraph.mcp",
|
|
83
|
+
"memory_tools": "databricks_agentkit.langgraph.memory",
|
|
84
|
+
"checkpointer": "databricks_agentkit.langgraph.session_store",
|
|
85
|
+
"thread_config": "databricks_agentkit.langgraph.session_store",
|
|
86
|
+
"start_trace": "databricks_agentkit.runtime",
|
|
87
|
+
"workspace_client": "databricks_agentkit.runtime",
|
|
88
|
+
"workspace_headers": "databricks_agentkit.runtime",
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def __getattr__(name: str) -> object:
|
|
93
|
+
module = _MODULE_BY_NAME.get(name)
|
|
94
|
+
if module is None:
|
|
95
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
96
|
+
import importlib
|
|
97
|
+
|
|
98
|
+
return getattr(importlib.import_module(module), name)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Native Genie Agent tools for LangGraph, configured through ``agent.toml``."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from langchain_core.tools import BaseTool, tool
|
|
10
|
+
|
|
11
|
+
from databricks_agentkit.runtime.auth import AuthError
|
|
12
|
+
from databricks_agentkit.runtime.genie import GenieAgent
|
|
13
|
+
from databricks_agentkit.runtime.tool_manifest import load_tools
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from databricks.sdk import WorkspaceClient
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def genie_tools(
|
|
20
|
+
*, workspace_client_for: Callable[[str], WorkspaceClient] | None = None
|
|
21
|
+
) -> list[BaseTool]:
|
|
22
|
+
"""Build ask, poll and query-result tools for each configured Genie Agent.
|
|
23
|
+
|
|
24
|
+
Space IDs are bound from the manifest, not exposed as model arguments. Construction performs
|
|
25
|
+
no authentication or network requests; an agent with no Genie Agent bindings gets no tools.
|
|
26
|
+
"""
|
|
27
|
+
tools = []
|
|
28
|
+
for record in load_tools(expected_framework="langgraph"):
|
|
29
|
+
if record.kind != "genie_agent":
|
|
30
|
+
continue
|
|
31
|
+
if (
|
|
32
|
+
record.auth == "user"
|
|
33
|
+
and workspace_client_for is None
|
|
34
|
+
and os.getenv("DATABRICKS_APP_NAME")
|
|
35
|
+
):
|
|
36
|
+
raise AuthError(
|
|
37
|
+
"MCP_USER_AUTHORIZATION_MISSING",
|
|
38
|
+
"This deployed Genie integration requires request-user authorization.",
|
|
39
|
+
integration_id=record.id,
|
|
40
|
+
)
|
|
41
|
+
agent = GenieAgent(
|
|
42
|
+
record.space_id or "",
|
|
43
|
+
auth=record.auth or "app",
|
|
44
|
+
workspace_client_for=workspace_client_for,
|
|
45
|
+
)
|
|
46
|
+
tools.extend(
|
|
47
|
+
tool(f"{record.id}_{method.__name__}")(method)
|
|
48
|
+
for method in (agent.ask, agent.poll, agent.query_result)
|
|
49
|
+
)
|
|
50
|
+
return tools
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""Build MCP tools for the agent from the servers declared in ``agent.toml`` (plus any the agent adds).
|
|
2
|
+
|
|
3
|
+
``mcp_tools()`` is the entry point: it reads the MCP servers declared in ``agent.toml``
|
|
4
|
+
(sandbox/mcp + uc_function), fetches their LangChain tools with sandbox downscoping applied, and
|
|
5
|
+
returns them. An agent with its own hand-built servers passes them as ``extra_servers``; leaving
|
|
6
|
+
``agent.toml`` empty simply yields no declared servers. Typical agent use::
|
|
7
|
+
|
|
8
|
+
tools = await mcp_tools() # just the agent.toml servers
|
|
9
|
+
tools = await mcp_tools(build_mcp_servers()) # agent.toml servers + the agent's own
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import asyncio
|
|
15
|
+
import logging
|
|
16
|
+
from typing import TYPE_CHECKING, Any
|
|
17
|
+
|
|
18
|
+
from databricks_langchain import DatabricksMCPServer, DatabricksMultiServerMCPClient
|
|
19
|
+
from langchain_mcp_adapters.sessions import create_session
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from databricks_langchain import MCPServer
|
|
23
|
+
|
|
24
|
+
from databricks_agentkit.runtime import mcp_auth
|
|
25
|
+
from databricks_agentkit.runtime.auth import AuthError
|
|
26
|
+
from databricks_agentkit.runtime.tool_manifest import ToolRecord, downscope_wire, load_tools
|
|
27
|
+
from databricks_agentkit.runtime.workspace import workspace_client, workspace_headers
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
_auth_error = mcp_auth.mcp_auth_error
|
|
31
|
+
_tool_error = mcp_auth.mcp_tool_error
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _server_from_tool(
|
|
35
|
+
tool: ToolRecord,
|
|
36
|
+
*,
|
|
37
|
+
workspace_client_for: mcp_auth.WorkspaceClientResolver | None = None,
|
|
38
|
+
) -> DatabricksMCPServer | None:
|
|
39
|
+
if tool.kind not in {"sandbox", "mcp", "uc_function", "genie_one"}:
|
|
40
|
+
return None
|
|
41
|
+
mode = tool.auth or "app"
|
|
42
|
+
client = mcp_auth.resolve_mcp_workspace_client(
|
|
43
|
+
mode, tool.id, workspace_client_for, workspace_client
|
|
44
|
+
)
|
|
45
|
+
host = client.config.host.rstrip("/")
|
|
46
|
+
if tool.kind in {"sandbox", "mcp", "genie_one"}:
|
|
47
|
+
url = (
|
|
48
|
+
f"{host}/api/2.0/mcp/genie"
|
|
49
|
+
if tool.kind == "genie_one"
|
|
50
|
+
else f"{host}/ai-gateway/mcp-services/{tool.service}"
|
|
51
|
+
)
|
|
52
|
+
return DatabricksMCPServer(
|
|
53
|
+
name=tool.id,
|
|
54
|
+
url=url,
|
|
55
|
+
headers=workspace_headers() or None,
|
|
56
|
+
workspace_client=client,
|
|
57
|
+
timeout=120.0,
|
|
58
|
+
)
|
|
59
|
+
if tool.kind == "uc_function":
|
|
60
|
+
catalog, schema, function_name = (tool.function or "").split(".")
|
|
61
|
+
return DatabricksMCPServer.from_uc_function(
|
|
62
|
+
catalog=catalog,
|
|
63
|
+
schema=schema,
|
|
64
|
+
function_name=function_name,
|
|
65
|
+
name=tool.id,
|
|
66
|
+
headers=workspace_headers() or None,
|
|
67
|
+
workspace_client=client,
|
|
68
|
+
timeout=120.0,
|
|
69
|
+
)
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _declared_servers(
|
|
74
|
+
*,
|
|
75
|
+
workspace_client_for: mcp_auth.WorkspaceClientResolver | None = None,
|
|
76
|
+
) -> list[DatabricksMCPServer]:
|
|
77
|
+
"""The MCP servers declared in the agent's ``agent.toml`` (may be empty)."""
|
|
78
|
+
tools = load_tools(expected_framework="langgraph")
|
|
79
|
+
return [
|
|
80
|
+
server
|
|
81
|
+
for tool in tools
|
|
82
|
+
if (server := _server_from_tool(tool, workspace_client_for=workspace_client_for))
|
|
83
|
+
is not None
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _sandbox_interceptor(
|
|
88
|
+
tools: tuple[ToolRecord, ...],
|
|
89
|
+
*,
|
|
90
|
+
workspace_client_for: mcp_auth.WorkspaceClientResolver | None = None,
|
|
91
|
+
):
|
|
92
|
+
declared = {tool.id: tool for tool in tools}
|
|
93
|
+
|
|
94
|
+
async def interceptor(request: Any, handler: Any) -> Any:
|
|
95
|
+
tool = declared.get(request.server_name)
|
|
96
|
+
if tool is None:
|
|
97
|
+
return await handler(request)
|
|
98
|
+
request_user = tool.auth == "user"
|
|
99
|
+
try:
|
|
100
|
+
if tool.kind == "sandbox":
|
|
101
|
+
server = _server_from_tool(tool, workspace_client_for=workspace_client_for)
|
|
102
|
+
if server is None:
|
|
103
|
+
raise RuntimeError(f"Could not build sandbox MCP server {tool.id!r}.")
|
|
104
|
+
async with create_session(server.to_connection_dict()) as session:
|
|
105
|
+
await session.initialize()
|
|
106
|
+
result = await session.call_tool(
|
|
107
|
+
request.name,
|
|
108
|
+
request.args,
|
|
109
|
+
meta={"downscope": downscope_wire(tool)},
|
|
110
|
+
)
|
|
111
|
+
else:
|
|
112
|
+
result = await handler(request)
|
|
113
|
+
except Exception as error:
|
|
114
|
+
if request_user:
|
|
115
|
+
raise _auth_error(error, tool.id) or AuthError(
|
|
116
|
+
"MCP_TOOL_FAILED", "The configured MCP tool failed.", 502, tool.id
|
|
117
|
+
) from None
|
|
118
|
+
raise
|
|
119
|
+
if (
|
|
120
|
+
request_user
|
|
121
|
+
and getattr(result, "isError", False)
|
|
122
|
+
and (error := _tool_error(result, tool.id))
|
|
123
|
+
):
|
|
124
|
+
raise error
|
|
125
|
+
return result
|
|
126
|
+
|
|
127
|
+
return interceptor
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def mcp_client(
|
|
131
|
+
servers: list[DatabricksMCPServer],
|
|
132
|
+
*,
|
|
133
|
+
workspace_client_for: mcp_auth.WorkspaceClientResolver | None = None,
|
|
134
|
+
tools: tuple[ToolRecord, ...] | None = None,
|
|
135
|
+
) -> DatabricksMultiServerMCPClient:
|
|
136
|
+
"""A multi-server MCP client over ``servers`` with the sandbox downscoping interceptor attached.
|
|
137
|
+
|
|
138
|
+
The interceptor is derived from the ``agent.toml`` manifest, so sandbox tools run downscoped
|
|
139
|
+
regardless of how the caller drives the returned client (``get_tools`` or otherwise). Callers who
|
|
140
|
+
build their own client instead take on applying downscoping themselves.
|
|
141
|
+
"""
|
|
142
|
+
snapshot = tuple(load_tools(expected_framework="langgraph")) if tools is None else tools
|
|
143
|
+
interceptors = (
|
|
144
|
+
[_sandbox_interceptor(snapshot, workspace_client_for=workspace_client_for)]
|
|
145
|
+
if snapshot
|
|
146
|
+
else []
|
|
147
|
+
)
|
|
148
|
+
# DatabricksMCPServer is a subclass of MCPServer, so coerce the type for the API
|
|
149
|
+
servers_as_mcp: list[MCPServer] = servers # type: ignore[name-defined,assignment]
|
|
150
|
+
return DatabricksMultiServerMCPClient(
|
|
151
|
+
servers_as_mcp, tool_interceptors=interceptors, handle_tool_errors=True
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
async def mcp_tools(
|
|
156
|
+
extra_servers: list[DatabricksMCPServer] | None = None,
|
|
157
|
+
*,
|
|
158
|
+
workspace_client_for: mcp_auth.WorkspaceClientResolver | None = None,
|
|
159
|
+
) -> list:
|
|
160
|
+
"""Fetch declared tools with request identity and protected sandbox downscoping.
|
|
161
|
+
|
|
162
|
+
Includes the MCP servers declared in ``agent.toml``; pass ``extra_servers`` to add servers the
|
|
163
|
+
agent builds itself. Request-user failures propagate; App, legacy, and optional customer
|
|
164
|
+
servers retain their existing identity and per-server best-effort discovery behavior.
|
|
165
|
+
"""
|
|
166
|
+
snapshot = tuple(load_tools(expected_framework="langgraph"))
|
|
167
|
+
servers = [
|
|
168
|
+
server
|
|
169
|
+
for tool in snapshot
|
|
170
|
+
if (server := _server_from_tool(tool, workspace_client_for=workspace_client_for))
|
|
171
|
+
is not None
|
|
172
|
+
]
|
|
173
|
+
result = []
|
|
174
|
+
if servers:
|
|
175
|
+
client = mcp_client(servers, workspace_client_for=workspace_client_for, tools=snapshot)
|
|
176
|
+
request_user = {tool.id for tool in snapshot if tool.auth == "user"}
|
|
177
|
+
|
|
178
|
+
async def fetch_declared(server: DatabricksMCPServer) -> list:
|
|
179
|
+
try:
|
|
180
|
+
return await client.get_tools(server_name=server.name)
|
|
181
|
+
except Exception as error:
|
|
182
|
+
if server.name in request_user:
|
|
183
|
+
raise _auth_error(error, server.name) or AuthError(
|
|
184
|
+
"MCP_TOOL_FAILED",
|
|
185
|
+
"Could not discover configured MCP tools.",
|
|
186
|
+
502,
|
|
187
|
+
server.name,
|
|
188
|
+
) from None
|
|
189
|
+
logger.warning(
|
|
190
|
+
"Failed to fetch MCP tools from server %r; continuing without it.", server.name
|
|
191
|
+
)
|
|
192
|
+
return []
|
|
193
|
+
|
|
194
|
+
groups = await asyncio.gather(
|
|
195
|
+
*(fetch_declared(server) for server in servers), return_exceptions=True
|
|
196
|
+
)
|
|
197
|
+
for group in groups:
|
|
198
|
+
if isinstance(group, BaseException):
|
|
199
|
+
raise group
|
|
200
|
+
result.extend(group)
|
|
201
|
+
if extra_servers:
|
|
202
|
+
optional_client = mcp_client(extra_servers, tools=())
|
|
203
|
+
|
|
204
|
+
async def fetch_optional(server: DatabricksMCPServer) -> list:
|
|
205
|
+
try:
|
|
206
|
+
return await optional_client.get_tools(server_name=server.name)
|
|
207
|
+
except Exception:
|
|
208
|
+
logger.warning(
|
|
209
|
+
"Failed to fetch optional MCP tools from server %r; continuing without it.",
|
|
210
|
+
server.name,
|
|
211
|
+
)
|
|
212
|
+
return []
|
|
213
|
+
|
|
214
|
+
for group in await asyncio.gather(*(fetch_optional(server) for server in extra_servers)):
|
|
215
|
+
result.extend(group)
|
|
216
|
+
return result
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Long-term memory tools — opt-in, gated on ``AGENT_MEMORY_STORE``.
|
|
2
|
+
|
|
3
|
+
Unlike the session store (short-term transcript for one conversation), long-term memory is exposed
|
|
4
|
+
to the model as two tools — ``remember`` and ``recall`` — over the Databricks managed memory store's
|
|
5
|
+
``2.0/agents`` entries API. Facts persist across conversations.
|
|
6
|
+
|
|
7
|
+
``memory_tools(actor)`` returns the tools when ``AGENT_MEMORY_STORE`` is set, else an empty list, so
|
|
8
|
+
the model never sees them when memory is unconfigured. ``create_agent_graph`` composes them into its
|
|
9
|
+
tool list. This is a stand-in for a future ``databricks-langchain`` memory helper — when the SDK
|
|
10
|
+
provides one, swap the import in ``agent.py`` and delete this file.
|
|
11
|
+
|
|
12
|
+
Memory entries are per-actor. ``actor`` is the identity whose memory these tools read and write — the
|
|
13
|
+
caller (the agent server) supplies it, typically the signed-in user, so each user gets their own
|
|
14
|
+
long-term memory. It is **closed over**, not a tool argument: the model only ever sees ``remember``'s
|
|
15
|
+
and ``recall``'s own parameters and cannot set or spoof the actor. Pass a fixed value for one shared
|
|
16
|
+
memory (e.g. a team knowledge base).
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from langchain_core.tools import BaseTool, tool
|
|
20
|
+
|
|
21
|
+
from databricks_agentkit.runtime.workspace import workspace_client
|
|
22
|
+
|
|
23
|
+
_AGENTS_API = "/api/2.0/agents"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _api():
|
|
27
|
+
# Build the client lazily (needs workspace auth) so importing this module stays cheap.
|
|
28
|
+
return workspace_client().api_client
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def memory_tools(actor: str, store: str | None = None) -> list[BaseTool]:
|
|
32
|
+
"""The long-term-memory tools for ``actor`` when a memory store is configured, else none.
|
|
33
|
+
|
|
34
|
+
The store resolves ``store`` arg → ``AGENT_MEMORY_STORE`` env → the ``[memory_store]`` binding in
|
|
35
|
+
agent.toml (`ab memory bind`) → none (no tools). ``actor`` partitions the store; it is captured
|
|
36
|
+
in the tools' closures (not exposed to the model). Call this per request with the identity whose
|
|
37
|
+
memory to use (e.g. the signed-in user).
|
|
38
|
+
"""
|
|
39
|
+
from databricks_agentkit.runtime.tool_manifest import resolve_memory_store
|
|
40
|
+
|
|
41
|
+
store = resolve_memory_store(store)
|
|
42
|
+
if not store:
|
|
43
|
+
return []
|
|
44
|
+
store_path = f"{_AGENTS_API}/memory-stores/{store}"
|
|
45
|
+
|
|
46
|
+
@tool
|
|
47
|
+
def remember(fact: str, topic: str) -> str:
|
|
48
|
+
"""Persist a durable fact about the user in long-term memory."""
|
|
49
|
+
_api().do(
|
|
50
|
+
"POST",
|
|
51
|
+
f"{store_path}/entries",
|
|
52
|
+
body={"actor_id": actor, "path": f"/{topic}/{fact[:8]}.md", "content": fact},
|
|
53
|
+
)
|
|
54
|
+
return "stored"
|
|
55
|
+
|
|
56
|
+
@tool
|
|
57
|
+
def recall(query: str) -> str:
|
|
58
|
+
"""Search the user's long-term memory for facts relevant to the query."""
|
|
59
|
+
data = _api().do(
|
|
60
|
+
"POST",
|
|
61
|
+
f"{store_path}/entries:search",
|
|
62
|
+
body={"actor_id": actor, "query": query, "limit": 5},
|
|
63
|
+
)
|
|
64
|
+
entries = data.get("managed_memory_entries") or []
|
|
65
|
+
return "\n".join(f"- {e.get('content')}" for e in entries) or "No relevant memories."
|
|
66
|
+
|
|
67
|
+
return [remember, recall]
|