litellm/tests/integration/providers/test_databricks_agent_wire.py
devin-ai-integration[bot] da2f5a498f
feat(agents): support Databricks Agent Platform agents in the agent gateway (#45681)
* feat(agents): support Databricks Agent Platform agents in the agent gateway

Add a databricks_agent provider that speaks the MLflow ResponsesAgent wire
(Responses-shaped input, output_text items, output_text.delta SSE) to Model
Serving endpoints (/invocations or the unified /serving-endpoints/responses)
and Databricks Apps (/responses), with a PAT or OAuth M2M client credentials

Register it through the agents config block or the Admin UI agent catalog and
call it through /a2a/<agent> (message/send, message/stream) or model:
a2a/<agent> chat completions. The chat route now runs every completion-bridge
agent through its own provider, and attaches the minted Databricks OAuth token
for both bridge and URL agents. Consumed OAuth settings never reach the
provider call

* fix(agents): harden databricks_agent input, output, and OAuth handling

Non-text content parts answer 400 unless drop_params is set, a reply without
an output list answers 502, flat OAuth fields resolve an os.environ api_base,
the bridge debug log no longer echoes the request model, routing tests stub
only the HTTP boundary, and the catalog description fits the dropdown

* fix(agents): merge agent and client extra_headers on the a2a chat route, and reject an empty databricks_oauth block

* fix(agents): bridge only databricks_agent agents on the a2a chat route, keep the card url over a client api_base, and require workspace_url for a Databricks App

* refactor(agents): read the a2a chat bridge providers from a constants set instead of branching on a provider name

* fix(databricks_agent): let the configured credential win, reject malformed client extra_headers, and keep a missing endpoint name empty

* test(agents): drive the flat OAuth a2a send through the real bridge

* fix(agents): keep an empty databricks_oauth block ignored on url agents, as before this change

* fix(agents): accept bare App hosts, env service principals, and os.environ refs on Databricks agents

* fix(databricks_agent): address review caveats on chat bridging, params, and auth

Map max_tokens and max_completion_tokens to max_output_tokens and pass
temperature, top_p, and user, so /v1/messages works without drop_params.
Send A2A message metadata only with string values, answering 400 or
dropping the rest under drop_params. Join message items with a blank line
in both stream and non-stream replies. Cache the env service principal
token until shortly before it expires. Keep databricks_agent replies out
of the response cache.

On the chat bridge, let request fields win over the agent's stored params
while pinning the agent's api_base and api_key, and apply the agent's
static_headers between client and agent extra_headers, as /a2a does.
Resolve os.environ/ only for databricks_agent api_base and api_key instead
of every bridge provider. Ignore an unusable databricks_oauth block on URL
agents on chat, as at the merge base, instead of answering 400.

* test(internal_context): list the databricks agent token cache as process-local

* fix(databricks_agent): keep the agent's endpoint, input, and target out of client control

extra_body model and input answer 400, or are dropped under drop_params.
A client api_base or base_url never replaces the agent's card URL on the
bridge. Chat to a URL agent keeps its merge-base call, and an incomplete
databricks_oauth block on a URL agent keeps its merge-base ValueError.

* test(databricks): expect the merge-base 400 for a client api_base on agent chat

* fix(databricks): keep agent-owned inputs and identity headers out of a chat caller's reach

* fix(databricks): bound the agent-over-caller merge to two levels so it needs no recursion

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-10-15 12:42:06 -07:00

984 lines
44 KiB
Python

import asyncio
import base64
import json
import re
import threading
import uuid
from collections.abc import Callable, Iterator, Sequence
from contextlib import contextmanager
from dataclasses import dataclass
from pathlib import Path
from typing import Final
from urllib.parse import parse_qs
import httpx
import openai
import pytest
import yaml
from integration._support.client import Gateway, eventually
from integration._support.database import read_rows
from integration._support.process import graceful_stop_seconds, owned_proxy_process
from integration._support.wire import Reply, Request, Wire, wire_server
from pydantic import JsonValue, TypeAdapter
_ENDPOINT: Final = "support-agent"
_INVOCATIONS: Final = f"/serving-endpoints/{_ENDPOINT}/invocations"
_UNIFIED: Final = "/serving-endpoints/responses"
_APP: Final = "/responses"
_TOKEN: Final = "/oidc/v1/token"
_PAT: Final = "synthetic-databricks-pat"
_CLIENT_ID: Final = "synthetic-databricks-sp"
_CLIENT_SECRET: Final = "synthetic-databricks-sp-secret"
_MINTED: Final = "synthetic-databricks-minted-token"
_REPLY: Final = "the export job is waiting on a lock"
_PROMPT: Final = "Why does the export job hang?"
_USAGE: Final[dict[str, JsonValue]] = {"input_tokens": 11, "output_tokens": 3, "total_tokens": 14}
_JSON_OBJECT: Final = TypeAdapter(dict[str, JsonValue])
_STARTED_WORKER: Final = re.compile(r"Started server process \[(\d+)\]")
_OWNED_PROXY_BUDGET: Final = int(2 * graceful_stop_seconds() + 120)
def _basic(client_id: str, client_secret: str) -> str:
return "Basic " + base64.b64encode(f"{client_id}:{client_secret}".encode()).decode()
def _agent_reply(text: str = _REPLY) -> bytes:
return json.dumps(
{
"output": [
{
"type": "message",
"id": "msg-1",
"role": "assistant",
"content": [{"type": "output_text", "text": text}],
}
],
"usage": _USAGE,
}
).encode()
def _agent_stream(text: str = _REPLY) -> bytes:
head, tail = text[: len(text) // 2], text[len(text) // 2 :]
events: Final = (
{"type": "response.output_text.delta", "item_id": "msg-1", "delta": head},
{"type": "response.output_text.delta", "item_id": "msg-1", "delta": tail},
{
"type": "response.output_item.done",
"item": {"type": "message", "id": "msg-1", "content": [{"type": "output_text", "text": text}]},
},
)
return "".join(f"data: {json.dumps(event)}\n\n" for event in events).encode()
def _token_reply() -> Reply:
return Reply(body=json.dumps({"access_token": _MINTED, "token_type": "Bearer", "expires_in": 3600}).encode())
@dataclass(frozen=True, slots=True)
class _Databricks:
token_status: Callable[[], int] = lambda: 200
endpoint_reply: Callable[[Request], Reply] | None = None
def __call__(self, request: Request) -> Reply:
if request.target == _TOKEN:
status: Final = self.token_status()
if status != 200:
return Reply(status=status, body=b'{"error":"invalid_client"}')
return _token_reply()
if self.endpoint_reply is not None:
return self.endpoint_reply(request)
if _JSON_OBJECT.validate_json(request.body).get("stream") is True:
return Reply(body=_agent_stream(), content_type="text/event-stream")
return Reply(body=_agent_reply())
def _calls_to(requests: Sequence[Request], target: str) -> tuple[Request, ...]:
return tuple(request for request in requests if request.target == target)
def _body(request: Request) -> dict[str, JsonValue]:
return _JSON_OBJECT.validate_json(request.body)
def _sdk_base_url(gateway: Gateway) -> str:
return f"{str(gateway.client.base_url).rstrip('/')}/v1"
def _register(
gateway: Gateway,
cleanups: Callable[[Callable[[], None]], object],
card_url: str,
static_headers: dict[str, JsonValue] | None = None,
**params: JsonValue,
) -> str:
name: Final = f"dbx-agent-{uuid.uuid4().hex[:12]}"
created: Final = gateway.request(
"POST",
"/v1/agents",
{
"agent_name": name,
"agent_card_params": {
"protocolVersion": "0.3",
"name": name,
"description": "Databricks Agent agent",
"url": card_url,
"version": "1.0.0",
"defaultInputModes": ["text"],
"defaultOutputModes": ["text"],
"capabilities": {"streaming": True},
"skills": [],
},
"litellm_params": {"custom_llm_provider": "databricks_agent", **params},
**({"static_headers": static_headers} if static_headers else {}),
},
)
assert created.status_code == 200, created.text
agent_id: Final = created.json()["agent_id"]
def delete() -> None:
deleted: Final = gateway.request("DELETE", f"/v1/agents/{agent_id}")
assert deleted.status_code == 200, deleted.text
cleanups(delete)
return name
@contextmanager
def _agent(
gateway: Gateway,
databricks: _Databricks,
*,
url_path: str = "",
static_headers: dict[str, JsonValue] | None = None,
**params: JsonValue,
) -> Iterator[tuple[Wire, str]]:
with wire_server(databricks) as wire, gateway.scenario() as scenario:
params_with_base: Final = {"api_base": wire.url + url_path, **params}
yield (
wire,
_register(gateway, scenario.cleanups.callback, wire.url + url_path, static_headers, **params_with_base),
)
def _chat(gateway: Gateway, name: str, **extra: JsonValue) -> httpx.Response:
return gateway.request(
"POST",
"/v1/chat/completions",
{"model": f"a2a/{name}", "messages": [{"role": "user", "content": _PROMPT}], **extra},
)
def _a2a(
gateway: Gateway, name: str, method: str, marker: str, metadata: dict[str, JsonValue] | None = None
) -> httpx.Response:
message: Final[dict[str, JsonValue]] = {
"role": "user",
"messageId": marker,
"parts": [{"kind": "text", "text": _PROMPT}],
**({"metadata": metadata} if metadata is not None else {}),
}
return gateway.request(
"POST",
f"/a2a/{name}",
{"jsonrpc": "2.0", "id": marker, "method": method, "params": {"message": message}},
)
def _sse(text: str) -> tuple[dict[str, JsonValue], ...]:
return tuple(
_JSON_OBJECT.validate_json(line.removeprefix("data: "))
for line in text.splitlines()
if line.startswith("data: ") and line != "data: [DONE]"
)
def _spend_status(request_id: str) -> str:
rows: Final = eventually(
lambda: read_rows('SELECT status FROM "LiteLLM_SpendLogs" WHERE request_id = %s', (request_id,)),
lambda found: len(found) == 1,
seconds=70,
)
return str(rows[0]["status"])
def _assert_invocation(request: Request, bearer: str, *, stream: bool = False) -> None:
assert request.method == "POST" and request.target == _INVOCATIONS
assert request.headers["authorization"] == f"Bearer {bearer}"
assert _body(request) == {
"input": [{"role": "user", "content": _PROMPT}],
**({"stream": True} if stream else {}),
}
def _assert_token_request(request: Request) -> None:
assert request.method == "POST"
assert request.headers["authorization"] == _basic(_CLIENT_ID, _CLIENT_SECRET)
assert parse_qs(request.body.decode()) == {"grant_type": ["client_credentials"], "scope": ["all-apis"]}
def test_chat_on_a_pat_agent_reaches_the_serving_endpoint_and_bills_the_agent_usage(gateway: Gateway) -> None:
with _agent(gateway, _Databricks(), model=_ENDPOINT, api_key=_PAT) as (wire, name):
response: Final = _chat(gateway, name)
assert response.status_code == 200, response.text
payload: Final = response.json()
assert payload["choices"][0]["message"] == {"content": _REPLY, "role": "assistant"}
assert payload["choices"][0]["finish_reason"] == "stop"
assert payload["usage"]["prompt_tokens"] == 11 and payload["usage"]["completion_tokens"] == 3
received: Final = wire.drain()
assert len(received) == 1, received
_assert_invocation(received[0], _PAT)
assert _spend_status(payload["id"]) == "success"
def test_async_chat_stream_on_a_pat_agent_sends_the_reply_once(gateway: Gateway) -> None:
async def stream(name: str) -> str:
client: Final = openai.AsyncOpenAI(base_url=_sdk_base_url(gateway), api_key=gateway.key, max_retries=0)
chunks: Final = await client.chat.completions.create(
model=f"a2a/{name}", messages=[{"role": "user", "content": _PROMPT}], stream=True
)
return "".join([chunk.choices[0].delta.content or "" async for chunk in chunks if chunk.choices])
with _agent(gateway, _Databricks(), model=_ENDPOINT, api_key=_PAT) as (wire, name):
assert asyncio.run(stream(name)) == _REPLY
received: Final = wire.drain()
assert len(received) == 1, received
_assert_invocation(received[0], _PAT, stream=True)
def test_sync_sdk_chat_on_a_pat_agent_returns_the_agent_reply(gateway: Gateway) -> None:
with _agent(gateway, _Databricks(), model=_ENDPOINT, api_key=_PAT) as (wire, name):
client: Final = openai.OpenAI(base_url=_sdk_base_url(gateway), api_key=gateway.key, max_retries=0)
completion: Final = client.chat.completions.create(
model=f"a2a/{name}", messages=[{"role": "user", "content": _PROMPT}]
)
assert completion.choices[0].message.content == _REPLY
assert len(_calls_to(wire.drain(), _INVOCATIONS)) == 1
def test_responses_on_a_pat_agent_returns_the_reply_in_output(gateway: Gateway) -> None:
with _agent(gateway, _Databricks(), model=_ENDPOINT, api_key=_PAT) as (wire, name):
response: Final = gateway.request("POST", "/v1/responses", {"model": f"a2a/{name}", "input": _PROMPT})
assert response.status_code == 200, response.text
texts: Final = tuple(
part["text"]
for item in response.json()["output"]
if item["type"] == "message"
for part in item["content"]
if part["type"] == "output_text"
)
assert texts == (_REPLY,)
received: Final = wire.drain()
assert len(received) == 1, received
_assert_invocation(received[0], _PAT)
@pytest.mark.parametrize("drop_params", (False, True), ids=("strict", "drop-params"))
def test_messages_on_a_pat_agent_sends_max_tokens_as_max_output_tokens(gateway: Gateway, drop_params: bool) -> None:
with _agent(gateway, _Databricks(), model=_ENDPOINT, api_key=_PAT) as (wire, name):
response: Final = gateway.request(
"POST",
"/v1/messages",
{
"model": f"a2a/{name}",
"max_tokens": 64,
"messages": [{"role": "user", "content": _PROMPT}],
**({"drop_params": True} if drop_params else {}),
},
)
assert response.status_code == 200, response.text
assert response.json()["content"] == [{"type": "text", "text": _REPLY}]
invocations: Final = _calls_to(wire.drain(), _INVOCATIONS)
assert len(invocations) == 1, invocations
assert _body(invocations[0]) == {"input": [{"role": "user", "content": _PROMPT}], "max_output_tokens": 64}
def test_chat_sampling_params_reach_the_agent_in_its_own_shape(gateway: Gateway) -> None:
with _agent(gateway, _Databricks(), model=_ENDPOINT, api_key=_PAT) as (wire, name):
response: Final = _chat(gateway, name, max_completion_tokens=32, temperature=0.2, top_p=0.9, user="u-1")
assert response.status_code == 200, response.text
invocations: Final = _calls_to(wire.drain(), _INVOCATIONS)
assert len(invocations) == 1, invocations
assert _body(invocations[0]) == {
"input": [{"role": "user", "content": _PROMPT}],
"max_output_tokens": 32,
"temperature": 0.2,
"top_p": 0.9,
"user": "u-1",
}
def test_a_reply_with_several_message_items_reads_the_same_streamed_or_not(gateway: Gateway) -> None:
first, second = "Let me look at the export queue.", "The export job is waiting on a lock."
def message(item_id: str, text: str) -> dict[str, JsonValue]:
return {"type": "message", "id": item_id, "content": [{"type": "output_text", "text": text}]}
def reply(request: Request) -> Reply:
if _body(request).get("stream") is not True:
output: Final = (message("msg-1", first), {"type": "function_call", "id": "fc-1"}, message("msg-2", second))
return Reply(body=json.dumps({"output": output, "usage": _USAGE}).encode())
events: Final = (
{"type": "response.output_text.delta", "item_id": "msg-1", "delta": first},
{"type": "response.output_item.done", "item": {"type": "function_call", "id": "fc-1"}},
{"type": "response.output_text.delta", "item_id": "msg-2", "delta": second},
{"type": "response.output_item.done", "item": message("msg-2", second)},
)
return Reply(
body="".join(f"data: {json.dumps(event)}\n\n" for event in events).encode(),
content_type="text/event-stream",
)
with _agent(gateway, _Databricks(endpoint_reply=reply), model=_ENDPOINT, api_key=_PAT) as (wire, name):
client: Final = openai.OpenAI(base_url=_sdk_base_url(gateway), api_key=gateway.key, max_retries=0)
messages: Final = [{"role": "user", "content": _PROMPT}]
whole: Final = client.chat.completions.create(model=f"a2a/{name}", messages=messages)
streamed: Final = client.chat.completions.create(model=f"a2a/{name}", messages=messages, stream=True)
expected: Final = f"{first}\n\n{second}"
assert whole.choices[0].message.content == expected
assert "".join(chunk.choices[0].delta.content or "" for chunk in streamed if chunk.choices) == expected
assert len(_calls_to(wire.drain(), _INVOCATIONS)) == 2
def test_identical_chat_requests_on_an_agent_each_reach_the_agent(gateway: Gateway) -> None:
replies: Final = iter(("first answer", "second answer"))
databricks: Final = _Databricks(endpoint_reply=lambda _: Reply(body=_agent_reply(next(replies))))
with _agent(gateway, databricks, model=_ENDPOINT, api_key=_PAT) as (wire, name):
answers: Final = tuple(_chat(gateway, name) for _ in range(2))
assert [answer.json()["choices"][0]["message"]["content"] for answer in answers] == [
"first answer",
"second answer",
], [answer.text for answer in answers]
assert len(_calls_to(wire.drain(), _INVOCATIONS)) == 2
def test_static_headers_sit_between_client_and_agent_extra_headers_on_chat(gateway: Gateway) -> None:
with _agent(
gateway,
_Databricks(),
static_headers={"X-Static": "static", "X-Shared": "from-static", "X-Both": "from-static"},
model=_ENDPOINT,
api_key=_PAT,
extra_headers={"x-both": "from-agent"},
) as (wire, name):
response: Final = _chat(gateway, name, extra_headers={"x-shared": "from-client", "X-Trace": "trace-1"})
assert response.status_code == 200, response.text
invocations: Final = _calls_to(wire.drain(), _INVOCATIONS)
assert len(invocations) == 1, invocations
headers: Final = invocations[0].headers
assert (headers["x-static"], headers["x-shared"], headers["x-both"]) == ("static", "from-static", "from-agent")
assert headers["x-trace"] == "trace-1"
_assert_invocation(invocations[0], _PAT)
def test_chat_never_forwards_client_litellm_identity_headers_to_the_agent(gateway: Gateway) -> None:
with _agent(gateway, _Databricks(), model=_ENDPOINT, api_key=_PAT) as (wire, name):
response: Final = _chat(
gateway,
name,
extra_headers={"X-LiteLLM-User-Id": "spoofed-user", "x-litellm-team-id": "spoofed-team", "X-Trace": "t-1"},
)
assert response.status_code == 200, response.text
invocations: Final = _calls_to(wire.drain(), _INVOCATIONS)
assert len(invocations) == 1, invocations
headers: Final = invocations[0].headers
assert "x-litellm-user-id" not in headers and "x-litellm-team-id" not in headers, headers
assert headers["x-trace"] == "t-1"
_assert_invocation(invocations[0], _PAT)
def test_the_agent_custom_inputs_and_extra_body_keys_win_over_the_callers_on_chat(gateway: Gateway) -> None:
with _agent(
gateway,
_Databricks(),
model=_ENDPOINT,
api_key=_PAT,
custom_inputs={"tenant": "owner"},
extra_body={"metadata": {"tenant": "owner"}},
) as (wire, name):
response: Final = _chat(
gateway,
name,
custom_inputs={"tenant": "caller", "session": "s-1"},
extra_body={"metadata": {"tenant": "caller", "turn": "2"}},
)
assert response.status_code == 200, response.text
invocations: Final = _calls_to(wire.drain(), _INVOCATIONS)
assert len(invocations) == 1, invocations
assert _body(invocations[0]) == {
"input": [{"role": "user", "content": _PROMPT}],
"custom_inputs": {"tenant": "owner", "session": "s-1"},
"metadata": {"tenant": "owner", "turn": "2"},
}
@pytest.mark.parametrize("drop_params", (False, True), ids=("strict", "drop-params"))
def test_a2a_message_metadata_reaches_the_agent_only_as_string_values(gateway: Gateway, drop_params: bool) -> None:
params: Final[dict[str, JsonValue]] = {"drop_params": True} if drop_params else {}
with _agent(gateway, _Databricks(), model=_ENDPOINT, api_key=_PAT, **params) as (wire, name):
strings: Final = _a2a(gateway, name, "message/send", f"meta-{uuid.uuid4().hex}", {"session": "s-1"})
assert strings.status_code == 200 and "error" not in strings.json(), strings.text
assert _body(_calls_to(wire.drain(), _INVOCATIONS)[0])["metadata"] == {"session": "s-1"}
mixed: Final = _a2a(gateway, name, "message/send", f"meta-{uuid.uuid4().hex}", {"session": "s-1", "turn": 1})
sent: Final = _calls_to(wire.drain(), _INVOCATIONS)
if drop_params:
assert mixed.status_code == 200 and "error" not in mixed.json(), mixed.text
assert [_body(request)["metadata"] for request in sent] == [{"session": "s-1"}]
else:
assert "string values" in mixed.text, mixed.text
assert sent == ()
def test_a2a_message_send_on_a_pat_agent_answers_with_the_agent_text(gateway: Gateway) -> None:
marker: Final = f"send-{uuid.uuid4().hex}"
with _agent(gateway, _Databricks(), model=_ENDPOINT, api_key=_PAT) as (wire, name):
response: Final = _a2a(gateway, name, "message/send", marker)
assert response.status_code == 200, response.text
body: Final = response.json()
assert body["id"] == marker and "error" not in body, body
assert body["result"]["parts"] == [{"kind": "text", "text": _REPLY}]
received: Final = wire.drain()
assert len(received) == 1, received
_assert_invocation(received[0], _PAT)
def test_a2a_message_stream_on_a_pat_agent_streams_the_text_and_completes(gateway: Gateway) -> None:
marker: Final = f"stream-{uuid.uuid4().hex}"
with _agent(gateway, _Databricks(), model=_ENDPOINT, api_key=_PAT) as (wire, name):
response: Final = _a2a(gateway, name, "message/stream", marker)
assert response.status_code == 200, response.text
events: Final = tuple(event["result"] for event in _sse(response.text))
artifacts: Final = tuple(
part["text"]
for event in events
if event["kind"] == "artifact-update"
for part in event["artifact"]["parts"]
)
assert artifacts == (_REPLY,)
assert events[-1]["kind"] == "status-update" and events[-1]["final"] is True
assert events[-1]["status"]["state"] == "completed"
received: Final = wire.drain()
assert len(received) == 1, received
_assert_invocation(received[0], _PAT, stream=True)
def test_the_unified_responses_url_names_the_endpoint_in_the_body(gateway: Gateway) -> None:
with _agent(gateway, _Databricks(), url_path=_UNIFIED, model=_ENDPOINT, api_key=_PAT) as (wire, name):
response: Final = _chat(gateway, name)
assert response.status_code == 200, response.text
assert response.json()["choices"][0]["message"]["content"] == _REPLY
received: Final = wire.drain()
assert [(request.method, request.target) for request in received] == [("POST", _UNIFIED)]
assert received[0].headers["authorization"] == f"Bearer {_PAT}"
assert _body(received[0]) == {"model": _ENDPOINT, "input": [{"role": "user", "content": _PROMPT}]}
def test_an_app_agent_mints_its_token_at_the_workspace_and_calls_the_app(gateway: Gateway) -> None:
with (
wire_server(_Databricks()) as workspace,
_agent(
gateway,
_Databricks(),
url_path=_APP,
client_id=_CLIENT_ID,
client_secret=_CLIENT_SECRET,
workspace_url=workspace.url,
) as (app, name),
):
response: Final = _chat(gateway, name)
assert response.status_code == 200, response.text
assert response.json()["choices"][0]["message"]["content"] == _REPLY
minted: Final = workspace.drain()
assert [request.target for request in minted] == [_TOKEN]
_assert_token_request(minted[0])
called: Final = app.drain()
assert [(request.method, request.target) for request in called] == [("POST", _APP)]
assert called[0].headers["authorization"] == f"Bearer {_MINTED}"
assert _body(called[0]) == {"input": [{"role": "user", "content": _PROMPT}]}
def test_a_nested_oauth_block_mints_once_for_chat_and_a2a_calls_on_one_agent(gateway: Gateway) -> None:
def oauth(url: str) -> dict[str, JsonValue]:
return {"client_id": _CLIENT_ID, "client_secret": _CLIENT_SECRET, "workspace_url": url}
with wire_server(_Databricks()) as wire, gateway.scenario() as scenario:
name: Final = _register(
gateway,
scenario.cleanups.callback,
wire.url,
model=_ENDPOINT,
api_base=wire.url,
databricks_oauth=oauth(wire.url),
)
for _ in range(3):
response = _chat(gateway, name)
assert response.status_code == 200, response.text
sent: Final = _a2a(gateway, name, "message/send", f"send-{uuid.uuid4().hex}")
assert sent.status_code == 200 and "error" not in sent.json(), sent.text
received: Final = wire.drain()
tokens: Final = _calls_to(received, _TOKEN)
assert len(tokens) <= 2, [request.target for request in received]
_assert_token_request(tokens[0])
invocations: Final = _calls_to(received, _INVOCATIONS)
assert len(invocations) == 4
for invocation in invocations:
_assert_invocation(invocation, _MINTED)
def test_the_minted_token_outranks_the_agent_pat_and_a_client_authorization(gateway: Gateway) -> None:
with _agent(
gateway,
_Databricks(),
model=_ENDPOINT,
api_key=_PAT,
client_id=_CLIENT_ID,
client_secret=_CLIENT_SECRET,
) as (wire, name):
response: Final = _chat(gateway, name, extra_headers={"Authorization": "Bearer client-supplied"})
assert response.status_code == 200, response.text
received: Final = wire.drain()
assert [request.target for request in received] == [_TOKEN, _INVOCATIONS]
_assert_invocation(received[1], _MINTED)
def test_the_agent_pat_outranks_a_client_authorization(gateway: Gateway) -> None:
with _agent(gateway, _Databricks(), model=_ENDPOINT, api_key=_PAT) as (wire, name):
response: Final = _chat(gateway, name, extra_headers={"Authorization": "Bearer client-supplied"})
assert response.status_code == 200, response.text
received: Final = wire.drain()
assert len(received) == 1, received
_assert_invocation(received[0], _PAT)
def test_empty_oauth_fields_beside_a_pat_keep_the_pat_path(gateway: Gateway) -> None:
with _agent(gateway, _Databricks(), model=_ENDPOINT, api_key=_PAT, client_id="", client_secret="") as (wire, name):
response: Final = _chat(gateway, name)
assert response.status_code == 200, response.text
received: Final = wire.drain()
assert [request.target for request in received] == [_INVOCATIONS]
_assert_invocation(received[0], _PAT)
def test_custom_inputs_reach_the_agent_beside_the_input(gateway: Gateway) -> None:
custom_inputs: Final[dict[str, JsonValue]] = {"session_id": "s-1", "tier": "gold"}
with _agent(gateway, _Databricks(), model=_ENDPOINT, api_key=_PAT) as (wire, name):
response: Final = _chat(gateway, name, custom_inputs=custom_inputs)
assert response.status_code == 200, response.text
received: Final = wire.drain()
assert len(received) == 1, received
assert _body(received[0]) == {"input": [{"role": "user", "content": _PROMPT}], "custom_inputs": custom_inputs}
def test_two_agents_on_one_endpoint_name_never_answer_from_each_other(gateway: Gateway) -> None:
def answering(text: str) -> _Databricks:
return _Databricks(endpoint_reply=lambda _: Reply(body=_agent_reply(text)))
with (
_agent(gateway, answering("from workspace one"), model=_ENDPOINT, api_key=_PAT) as (first_wire, first),
_agent(gateway, answering("from workspace two"), model=_ENDPOINT, api_key=_PAT) as (second_wire, second),
):
responses: Final = tuple(_chat(gateway, name) for name in (first, second, first))
assert all(response.status_code == 200 for response in responses), [r.text for r in responses]
answers: Final = tuple(response.json()["choices"][0]["message"]["content"] for response in responses)
assert answers == ("from workspace one", "from workspace two", "from workspace one")
assert len(_calls_to(first_wire.drain(), _INVOCATIONS)) == 2
assert len(_calls_to(second_wire.drain(), _INVOCATIONS)) == 1
def test_a_workspace_agent_with_no_endpoint_is_refused_before_any_call(gateway: Gateway) -> None:
with _agent(gateway, _Databricks(), api_key=_PAT) as (wire, name):
response: Final = _chat(gateway, name)
assert response.status_code == 400, response.text
assert "model must name the Databricks serving endpoint" in response.text
assert wire.drain() == ()
def test_an_app_agent_without_a_workspace_url_is_refused_before_its_secret_leaves(gateway: Gateway) -> None:
with _agent(gateway, _Databricks(), url_path=_APP, client_id=_CLIENT_ID, client_secret=_CLIENT_SECRET) as (
wire,
name,
):
response: Final = _chat(gateway, name)
assert response.status_code == 400, response.text
assert "workspace_url" in response.text
assert wire.drain() == ()
@pytest.mark.parametrize(("drop_params", "status"), ((False, 400), (True, 200)), ids=("strict", "drop-params"))
def test_malformed_client_extra_headers_follow_drop_params(gateway: Gateway, drop_params: bool, status: int) -> None:
with _agent(gateway, _Databricks(), model=_ENDPOINT, api_key=_PAT) as (wire, name):
response: Final = _chat(
gateway, name, extra_headers=["Authorization", "x"], **({"drop_params": True} if drop_params else {})
)
assert response.status_code == status, response.text
if drop_params:
received: Final = wire.drain()
assert len(received) == 1, received
_assert_invocation(received[0], _PAT)
else:
assert "extra_headers must be an object" in response.text
assert wire.drain() == ()
@pytest.mark.parametrize(("drop_params", "status"), ((False, 400), (True, 200)), ids=("strict", "drop-params"))
def test_an_image_part_follows_drop_params(gateway: Gateway, drop_params: bool, status: int) -> None:
content: Final[list[JsonValue]] = [
{"type": "text", "text": _PROMPT},
{"type": "image_url", "image_url": {"url": "data:image/png;base64,iVBORw0KGgo="}},
]
with _agent(gateway, _Databricks(), model=_ENDPOINT, api_key=_PAT) as (wire, name):
response: Final = gateway.request(
"POST",
"/v1/chat/completions",
{
"model": f"a2a/{name}",
"messages": [{"role": "user", "content": content}],
**({"drop_params": True} if drop_params else {}),
},
)
assert response.status_code == status, response.text
if drop_params:
received: Final = wire.drain()
assert len(received) == 1, received
_assert_invocation(received[0], _PAT)
else:
assert "image_url" in response.text
assert wire.drain() == ()
@pytest.mark.parametrize(
("reply", "status"),
(
(Reply(status=401, body=b'{"error_code":"UNAUTHENTICATED","message":"Invalid token"}'), 401),
(Reply(status=500, body=b'{"error_code":"INTERNAL_ERROR","message":"agent crashed"}'), 500),
(Reply(body=b"{}"), 502),
),
ids=("upstream-401", "upstream-500", "not-a-responses-agent"),
)
def test_a_failing_agent_keeps_its_status_and_unrelated_models_keep_serving(
gateway: Gateway, reply: Reply, status: int
) -> None:
with (
_agent(gateway, _Databricks(endpoint_reply=lambda _: reply), model=_ENDPOINT, api_key=_PAT) as (wire, name),
gateway.scenario() as scenario,
):
response: Final = _chat(gateway, name)
assert response.status_code == status, response.text
assert len(_calls_to(wire.drain(), _INVOCATIONS)) >= 1
unrelated: Final = gateway.chat(scenario.model())
assert unrelated["choices"], unrelated
def test_a_refused_token_request_answers_an_error_without_calling_the_agent(gateway: Gateway) -> None:
with _agent(
gateway,
_Databricks(token_status=lambda: 400),
model=_ENDPOINT,
client_id=_CLIENT_ID,
client_secret=_CLIENT_SECRET,
) as (wire, name):
response: Final = _chat(gateway, name)
assert response.status_code >= 400, response.text
received: Final = wire.drain()
assert [request.target for request in received] == [_TOKEN]
def test_reading_an_agent_back_redacts_its_pat_and_client_secret(gateway: Gateway) -> None:
with _agent(
gateway, _Databricks(), model=_ENDPOINT, api_key=_PAT, client_id=_CLIENT_ID, client_secret=_CLIENT_SECRET
) as (_, name):
listed: Final = gateway.request("GET", "/v1/agents")
assert listed.status_code == 200, listed.text
text: Final = listed.text
assert name in text
assert _PAT not in text and _CLIENT_SECRET not in text
@pytest.mark.parametrize(
"oauth_fields",
(None, (), ("client_id",), ("client_id", "client_secret", "workspace_url")),
ids=(
"plain",
"with-empty-databricks-oauth",
"with-partial-databricks-oauth",
"with-complete-databricks-oauth",
),
)
def test_a_url_agent_keeps_its_a2a_call_to_the_card_url(gateway: Gateway, oauth_fields: tuple[str, ...] | None) -> None:
marker: Final = f"url-agent-{uuid.uuid4().hex}"
def peer(request: Request) -> Reply:
if request.method == "GET":
return Reply(status=404)
outbound: Final = _body(request)
return Reply(
body=json.dumps(
{
"jsonrpc": "2.0",
"id": outbound["id"],
"result": {
"kind": "message",
"role": "agent",
"messageId": marker,
"parts": [{"kind": "text", "text": _REPLY}],
},
}
).encode()
)
with wire_server(peer) as wire, gateway.scenario() as scenario:
complete_block: Final = {"client_id": _CLIENT_ID, "client_secret": _CLIENT_SECRET, "workspace_url": wire.url}
oauth_block: Final = None if oauth_fields is None else {field: complete_block[field] for field in oauth_fields}
created: Final = gateway.request(
"POST",
"/v1/agents",
{
"agent_name": marker,
"agent_card_params": {
"protocolVersion": "0.3",
"name": marker,
"description": "URL agent",
"url": wire.url + "/",
"version": "1.0.0",
"defaultInputModes": ["text"],
"defaultOutputModes": ["text"],
"capabilities": {"streaming": False},
"skills": [],
},
**({"litellm_params": {"databricks_oauth": oauth_block}} if oauth_block is not None else {}),
},
)
assert created.status_code == 200, created.text
agent_id: Final = created.json()["agent_id"]
scenario.cleanups.callback(lambda: gateway.request("DELETE", f"/v1/agents/{agent_id}"))
response: Final = _chat(gateway, marker, extra_headers={"Authorization": "Bearer client-token"})
assert response.status_code == 200, response.text
assert response.json()["choices"][0]["message"]["content"] == _REPLY
posts: Final = tuple(request for request in wire.drain() if request.method == "POST")
assert [request.target for request in posts] == ["/"]
assert _body(posts[0])["method"] == "message/send"
assert posts[0].headers["authorization"] == "Bearer client-token"
def test_a_url_agent_with_an_incomplete_databricks_oauth_answers_a2a_with_an_internal_error(gateway: Gateway) -> None:
marker: Final = f"url-agent-{uuid.uuid4().hex}"
with wire_server(lambda _: Reply(status=404)) as wire, gateway.scenario() as scenario:
created: Final = gateway.request(
"POST",
"/v1/agents",
{
"agent_name": marker,
"agent_card_params": {
"protocolVersion": "0.3",
"name": marker,
"description": "URL agent",
"url": wire.url + "/",
"version": "1.0.0",
"defaultInputModes": ["text"],
"defaultOutputModes": ["text"],
"capabilities": {"streaming": False},
"skills": [],
},
"litellm_params": {"databricks_oauth": {"client_id": _CLIENT_ID}},
},
)
assert created.status_code == 200, created.text
agent_id: Final = created.json()["agent_id"]
scenario.cleanups.callback(lambda: gateway.request("DELETE", f"/v1/agents/{agent_id}"))
response: Final = _a2a(gateway, marker, "message/send", marker)
assert response.status_code == 500, response.text
assert response.json()["error"]["code"] == -32603
assert "missing required field" in response.json()["error"]["message"]
assert tuple(request for request in wire.drain() if request.method == "POST") == ()
@pytest.mark.parametrize(
"pinned",
({"model": "another-endpoint"}, {"input": [{"role": "user", "content": "injected"}]}),
ids=("model", "input"),
)
@pytest.mark.parametrize(("drop_params", "status"), ((False, 400), (True, 200)), ids=("strict", "drop-params"))
def test_extra_body_never_replaces_the_agent_endpoint_or_input(
gateway: Gateway, pinned: dict[str, JsonValue], drop_params: bool, status: int
) -> None:
custom_inputs: Final[dict[str, JsonValue]] = {"tenant": "t-1"}
with _agent(gateway, _Databricks(), url_path=_UNIFIED, model=_ENDPOINT, api_key=_PAT) as (wire, name):
response: Final = _chat(
gateway,
name,
extra_body={**pinned, "custom_inputs": custom_inputs},
**({"drop_params": True} if drop_params else {}),
)
assert response.status_code == status, response.text
received: Final = wire.drain()
if drop_params:
assert [(request.method, request.target) for request in received] == [("POST", _UNIFIED)]
assert _body(received[0]) == {
"model": _ENDPOINT,
"input": [{"role": "user", "content": _PROMPT}],
"custom_inputs": custom_inputs,
}
else:
assert f"cannot set {next(iter(pinned))}" in response.text
assert received == ()
def test_a_token_outage_mid_burst_fails_its_calls_and_one_mint_serves_the_recovery(gateway: Gateway) -> None:
outage: Final = threading.Event()
outage.set()
def token_status() -> int:
return 503 if outage.is_set() else 200
async def burst(name: str, size: int) -> tuple[int, ...]:
async with httpx.AsyncClient(base_url=str(gateway.client.base_url), timeout=60, trust_env=False) as client:
responses: Final = await asyncio.gather(
*(
client.post(
"/v1/chat/completions",
json={"model": f"a2a/{name}", "messages": [{"role": "user", "content": _PROMPT}]},
headers={"Authorization": f"Bearer {gateway.key}"},
)
for _ in range(size)
)
)
return tuple(response.status_code for response in responses)
with _agent(
gateway,
_Databricks(token_status=token_status),
model=_ENDPOINT,
client_id=_CLIENT_ID,
client_secret=_CLIENT_SECRET,
) as (wire, name):
failed: Final = asyncio.run(burst(name, 12))
assert all(status >= 400 for status in failed), failed
assert _calls_to(wire.drain(), _INVOCATIONS) == ()
outage.clear()
recovered: Final = asyncio.run(burst(name, 12))
assert recovered == (200,) * 12, recovered
received: Final = wire.drain()
assert len(_calls_to(received, _TOKEN)) <= 2
for invocation in _calls_to(received, _INVOCATIONS):
_assert_invocation(invocation, _MINTED)
assert len(_calls_to(received, _INVOCATIONS)) == 12
def _owned_config(
directory: Path, wire: Wire, api_key: str | None = _PAT, *, allow_client_side_credentials: bool = False
) -> Path:
base: Final = _JSON_OBJECT.validate_python(yaml.safe_load(Path("tests/integration/proxy_config.yaml").read_text()))
general_settings: Final = _JSON_OBJECT.validate_python(base.get("general_settings") or {})
name: Final = "dbx-owned-agent"
config: Final[dict[str, JsonValue]] = {
**base,
"general_settings": {
**general_settings,
**({"allow_client_side_credentials": True} if allow_client_side_credentials else {}),
},
"agents": [
{
"agent_name": name,
"agent_card_params": {
"protocolVersion": "0.3",
"name": name,
"description": "Databricks Agent agent",
"url": wire.url,
"version": "1.0.0",
"defaultInputModes": ["text"],
"defaultOutputModes": ["text"],
"capabilities": {"streaming": True},
"skills": [],
},
"litellm_params": {
"custom_llm_provider": "databricks_agent",
"model": _ENDPOINT,
"api_base": wire.url,
**({"api_key": api_key} if api_key is not None else {}),
},
}
],
}
path: Final = directory / "databricks-agent-owned.yaml"
path.write_text(yaml.safe_dump(config))
return path
@pytest.mark.timeout(_OWNED_PROXY_BUDGET)
def test_a_client_api_base_or_base_url_never_takes_the_agent_pat_off_its_card_url(
gateway: Gateway, tmp_path: Path
) -> None:
with wire_server(_Databricks()) as wire, wire_server(lambda _: Reply(body=_agent_reply())) as collector:
config: Final = _owned_config(tmp_path, wire, allow_client_side_credentials=True)
with owned_proxy_process(gateway, tmp_path, {}, config=config, workers=2) as owned:
rejected: Final = _chat(owned.gateway, "dbx-owned-agent", api_base=collector.url)
served: Final = _chat(owned.gateway, "dbx-owned-agent", base_url=collector.url)
assert rejected.status_code == 400, rejected.text
assert served.status_code == 200, served.text
assert collector.drain() == ()
invocations: Final = _calls_to(wire.drain(), _INVOCATIONS)
assert len(invocations) == 1
_assert_invocation(invocations[0], _PAT)
@pytest.mark.timeout(_OWNED_PROXY_BUDGET)
def test_a_killed_worker_leaves_the_sibling_serving_the_databricks_agent(gateway: Gateway, tmp_path: Path) -> None:
with wire_server(_Databricks()) as wire:
with owned_proxy_process(gateway, tmp_path, {}, config=_owned_config(tmp_path, wire), workers=2) as owned:
candidate: Final = owned.gateway
workers: Final = eventually(
lambda: tuple(int(pid) for pid in _STARTED_WORKER.findall(owned.log.read_text())),
lambda pids: len(pids) == 2,
seconds=graceful_stop_seconds() + 60,
)
first: Final = _chat(candidate, "dbx-owned-agent")
assert first.status_code == 200, first.text
replacement: Final = owned.kill_worker(workers[0])
assert replacement not in workers
served: Final = tuple(_chat(candidate, "dbx-owned-agent") for _ in range(6))
assert all(response.status_code == 200 for response in served), [r.text for r in served]
assert {response.json()["choices"][0]["message"]["content"] for response in served} == {_REPLY}
invocations: Final = _calls_to(wire.drain(), _INVOCATIONS)
assert len(invocations) == 7
for invocation in invocations:
_assert_invocation(invocation, _PAT)
@pytest.mark.timeout(_OWNED_PROXY_BUDGET)
def test_the_env_service_principal_mints_once_over_an_env_pat_and_env_references_resolve_on_an_api_created_agent(
gateway: Gateway, tmp_path: Path
) -> None:
with wire_server(_Databricks()) as wire:
environment: Final = {
"DATABRICKS_CLIENT_ID": _CLIENT_ID,
"DATABRICKS_CLIENT_SECRET": _CLIENT_SECRET,
"DATABRICKS_HOST": wire.url,
"DATABRICKS_API_KEY": "env-pat",
"DBX_AGENT_HOST": wire.url,
"DBX_AGENT_PAT": _PAT,
}
with owned_proxy_process(gateway, tmp_path, environment, config=_owned_config(tmp_path, wire, None)) as owned:
candidate: Final = owned.gateway
served: Final = tuple(_chat(candidate, "dbx-owned-agent") for _ in range(3))
assert all(response.status_code == 200 for response in served), [r.text for r in served]
received: Final = wire.drain()
assert len(_calls_to(received, _TOKEN)) == 1, received
_assert_token_request(_calls_to(received, _TOKEN)[0])
for invocation in _calls_to(received, _INVOCATIONS):
_assert_invocation(invocation, _MINTED)
assert len(_calls_to(received, _INVOCATIONS)) == 3
with candidate.scenario() as scenario:
name: Final = _register(
candidate,
scenario.cleanups.callback,
wire.url,
model=_ENDPOINT,
api_base="os.environ/DBX_AGENT_HOST",
api_key="os.environ/DBX_AGENT_PAT",
)
response: Final = _chat(candidate, name)
assert response.status_code == 200, response.text
invocations: Final = _calls_to(wire.drain(), _INVOCATIONS)
assert len(invocations) == 1, invocations
_assert_invocation(invocations[0], _PAT)