Skip to content

Commit e86255e

Browse files
committed
refactor(appkit): restructure supervisor-api adapter per PR #345 review
Address structural feedback from Mario Cadenas' review of PR #345 (sections 1-8). Stacked on top of the §9 defensive fixes commit. API changes (BETA surface only): - Add `DatabricksAdapter.fromSupervisorApi` static factory for discoverability alongside `.fromChatCompletions`. - Shrink `SupervisorApiAdapterOptions` to `{ model, workspaceClient? }`; tools no longer live on the adapter. - Hosted tools (`supervisorTools.*`) now return tagged `HostedSupervisorTool` records and accept named options instead of positional args. - Declare hosted tools on the agent's `tools` map (same place as function tools / sub-agents); the agents plugin and `runAgent` route them to the adapter via the new `AgentInput.extensions[SUPERVISOR_EXTENSION_KEY]`. - Add capability-negotiation fields to `AgentAdapter`: `acceptsExtensions?` + `consumesInputTools?`. The agents plugin and `runAgent` warn at registration when adapter capabilities don't match declared tools. Internals: - Extend `ResolvedToolEntry` / `StandaloneEntry` with a `hosted-supervisor` branch; `classifyTool` matches it before MCP hosted-tool rejection so standalone `runAgent` supports supervisor tools. - Defense-in-depth: both indexers throw if a `hosted-supervisor` entry is ever dispatched as a callable function. - `DatabricksAdapter.fromSupervisorApi` uses a dynamic import to avoid load-time cycles. Docs: - Rewrite supervisor-API section in docs/plugins/agents.md for the new shape. - Add cross-adapter sub-agent composition note (one-directional: chat parents can call supervisor children, not vice-versa, until SA's function-call events are routed back through `context.executeTool`). Playground: - Update dev-playground supervisor agent to the new shape (`DatabricksAdapter.fromSupervisorApi(...)` + tools on `createAgent`). Tests: - Rewrite supervisor-api.test.ts factory + adapter tests for the new shape. - Add `isSupervisorTool` and `DatabricksAdapter.fromSupervisorApi` tests. - New regression tests in `run-agent.test.ts` covering the hosted-supervisor extension-routing path and both capability-mismatch warnings. - New agents-plugin tests covering the same warning paths and the new `hosted-supervisor` tool-index branch. Signed-off-by: Hubert Zub <hubert.zub@databricks.com>
1 parent 6514dc0 commit e86255e

20 files changed

Lines changed: 1512 additions & 161 deletions

apps/dev-playground/server/index.ts

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
import {
1515
agents,
1616
createAgent,
17-
fromSupervisorApi,
17+
DatabricksAdapter,
1818
tool,
1919
} from "@databricks/appkit/beta";
2020
import { WorkspaceClient } from "@databricks/sdk-experimental";
@@ -73,10 +73,12 @@ const helper = createAgent({
7373
},
7474
});
7575

76-
// Supervisor API demo agent. Tools are configured on the adapter (the SA
77-
// endpoint executes them server-side), not on the createAgent definition.
78-
// Uncomment a `supervisorTools.*` entry (and import 'supervisorTools' from
79-
// '@databricks/appkit/beta') to give the model real powers.
76+
// Supervisor API demo agent. The Databricks AI Gateway executes hosted
77+
// tools server-side; declare them via `createAgent({ tools })` like any
78+
// other agent tool — the agents plugin classifies the tagged record and
79+
// routes it to the adapter via AgentInput.extensions. Import
80+
// `supervisorTools` from '@databricks/appkit/beta' and uncomment an
81+
// entry below to give the model real powers.
8082
//
8183
// `createAgent({ model })` accepts an adapter promise, so the factory's
8284
// host/credential resolution is awaited lazily on first dispatch (via
@@ -85,18 +87,18 @@ const helper = createAgent({
8587
const supervisor = createAgent({
8688
instructions:
8789
"You are an assistant powered by the Databricks Supervisor API.",
88-
model: fromSupervisorApi({
90+
model: DatabricksAdapter.fromSupervisorApi({
8991
model: "databricks-claude-sonnet-4-5",
90-
tools: [
91-
// supervisorTools.genieSpace(
92-
// "01ABCDEF12345678",
93-
// "NYC taxi trip records and zones",
94-
// ),
95-
// supervisorTools.ucFunction(
96-
// "main.default.add",
97-
// "Adds two integers and returns the sum.",
98-
// ),
99-
],
92+
}),
93+
tools: () => ({
94+
// nyc: supervisorTools.genieSpace({
95+
// id: "01ABCDEF12345678",
96+
// description: "NYC taxi trip records and zones",
97+
// }),
98+
// add: supervisorTools.ucFunction({
99+
// name: "main.default.add",
100+
// description: "Adds two integers and returns the sum.",
101+
// }),
100102
}),
101103
});
102104

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Class: SupervisorApiAdapter
2+
3+
Adapter that calls the Databricks AI Gateway Responses API
4+
(`/ai-gateway/mlflow/v1/responses`).
5+
6+
Streams SSE events in the OpenAI Responses API wire format and maps them
7+
to the AppKit `AgentEvent` protocol. Tool execution is handled
8+
server-side, so the adapter ignores the agents-plugin tool index.
9+
10+
Authentication is handled via the Databricks SDK credential chain — the
11+
same mechanism used by `DatabricksAdapter.fromModelServing`. The transport
12+
is injected via [SupervisorApiAdapterCtorOptions.streamBody](Interface.SupervisorApiAdapterCtorOptions.md#streambody); the
13+
[fromSupervisorApi](Function.fromSupervisorApi.md) factory wires it through the SDK's
14+
`apiClient.request({ raw: true })`.
15+
16+
Set `DEBUG=appkit:agents:supervisor-api` to log the outbound request
17+
shape (model, instructions length, input shape, tool count) and to be
18+
notified when the recovery path engages (no incremental deltas, text
19+
pulled from `response.completed.output[]`). The no-delta warning includes
20+
a per-turn event-type histogram and the SA-reported status/error/
21+
incomplete_details, so it's already actionable without DEBUG.
22+
23+
## Example
24+
25+
```ts
26+
import { createApp, createAgent } from "@databricks/appkit";
27+
import {
28+
agents,
29+
fromSupervisorApi,
30+
supervisorTools,
31+
} from "@databricks/appkit/beta";
32+
33+
const adapter = await fromSupervisorApi({
34+
model: "databricks-claude-sonnet-4",
35+
tools: [
36+
supervisorTools.genieSpace(
37+
"01ABCDEF12345678",
38+
"NYC taxi trip records and zones",
39+
),
40+
],
41+
});
42+
43+
await createApp({
44+
plugins: [
45+
agents({
46+
agents: {
47+
assistant: createAgent({
48+
instructions: "You are a helpful assistant.",
49+
model: adapter,
50+
}),
51+
},
52+
}),
53+
],
54+
});
55+
```
56+
57+
## Implements
58+
59+
- [`AgentAdapter`](Interface.AgentAdapter.md)
60+
61+
## Constructors
62+
63+
### Constructor
64+
65+
```ts
66+
new SupervisorApiAdapter(options: SupervisorApiAdapterCtorOptions): SupervisorApiAdapter;
67+
```
68+
69+
#### Parameters
70+
71+
| Parameter | Type |
72+
| ------ | ------ |
73+
| `options` | [`SupervisorApiAdapterCtorOptions`](Interface.SupervisorApiAdapterCtorOptions.md) |
74+
75+
#### Returns
76+
77+
`SupervisorApiAdapter`
78+
79+
## Methods
80+
81+
### run()
82+
83+
```ts
84+
run(input: AgentInput, context: AgentRunContext): AsyncGenerator<AgentEvent, void, unknown>;
85+
```
86+
87+
#### Parameters
88+
89+
| Parameter | Type |
90+
| ------ | ------ |
91+
| `input` | [`AgentInput`](Interface.AgentInput.md) |
92+
| `context` | [`AgentRunContext`](Interface.AgentRunContext.md) |
93+
94+
#### Returns
95+
96+
`AsyncGenerator`\<[`AgentEvent`](TypeAlias.AgentEvent.md), `void`, `unknown`\>
97+
98+
#### Implementation of
99+
100+
[`AgentAdapter`](Interface.AgentAdapter.md).[`run`](Interface.AgentAdapter.md#run)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Function: fromSupervisorApi()
2+
3+
```ts
4+
function fromSupervisorApi(options: SupervisorApiAdapterOptions): Promise<SupervisorApiAdapter>;
5+
```
6+
7+
Creates an [AgentAdapter](Interface.AgentAdapter.md) backed by the Databricks AI Gateway
8+
Responses API (`/ai-gateway/mlflow/v1/responses`).
9+
10+
Uses the SDK's default credential chain for auth (reads DATABRICKS_HOST,
11+
DATABRICKS_TOKEN, OAuth config, etc.).
12+
13+
## Parameters
14+
15+
| Parameter | Type |
16+
| ------ | ------ |
17+
| `options` | [`SupervisorApiAdapterOptions`](Interface.SupervisorApiAdapterOptions.md) |
18+
19+
## Returns
20+
21+
`Promise`\<[`SupervisorApiAdapter`](Class.SupervisorApiAdapter.md)\>
22+
23+
## Example
24+
25+
```ts
26+
import {
27+
fromSupervisorApi,
28+
supervisorTools,
29+
} from "@databricks/appkit/beta";
30+
31+
const adapter = await fromSupervisorApi({
32+
model: "databricks-claude-sonnet-4",
33+
tools: [
34+
supervisorTools.genieSpace(
35+
"01ABCDEF12345678",
36+
"NYC taxi trip records and zones",
37+
),
38+
],
39+
});
40+
```
41+
42+
## Remarks
43+
44+
⚠ When passing your own `workspaceClient`, see the warning on
45+
[SupervisorApiAdapterOptions.workspaceClient](Interface.SupervisorApiAdapterOptions.md#workspaceclient) — the client is
46+
captured once and reused, so per-request OBO clients would leak
47+
identity across requests.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Interface: SupervisorApiAdapterCtorOptions
2+
3+
## Properties
4+
5+
### model
6+
7+
```ts
8+
model: string;
9+
```
10+
11+
***
12+
13+
### streamBody
14+
15+
```ts
16+
streamBody: StreamBody;
17+
```
18+
19+
***
20+
21+
### tools?
22+
23+
```ts
24+
optional tools: SupervisorTool[];
25+
```
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Interface: SupervisorApiAdapterOptions
2+
3+
## Properties
4+
5+
### model
6+
7+
```ts
8+
model: string;
9+
```
10+
11+
Model identifier to pass in the request body
12+
(e.g. "databricks-claude-sonnet-4").
13+
14+
***
15+
16+
### tools?
17+
18+
```ts
19+
optional tools: SupervisorTool[];
20+
```
21+
22+
Hosted tools the SA endpoint should expose to the model. Use the
23+
[supervisorTools](Variable.supervisorTools.md) factories for the most common shapes.
24+
25+
***
26+
27+
### workspaceClient?
28+
29+
```ts
30+
optional workspaceClient: WorkspaceClientLike;
31+
```
32+
33+
A WorkspaceClient (or structural equivalent) used for host resolution
34+
and per-request authentication. When omitted, a `WorkspaceClient({})`
35+
is created internally using the default SDK credential chain
36+
(`DATABRICKS_HOST`, OAuth, PAT, etc.).
37+
38+
⚠ The `workspaceClient` is captured at construction and reused across
39+
every request. Passing a per-request OBO (On-Behalf-Of) client here
40+
would silently leak the first request's identity into all subsequent
41+
requests served by this adapter instance. Use the default credential
42+
chain or pass a service-principal client. (CWE-664)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Type Alias: SupervisorTool
2+
3+
```ts
4+
type SupervisorTool =
5+
| {
6+
genie_space: {
7+
description: string;
8+
id: string;
9+
};
10+
type: "genie_space";
11+
}
12+
| {
13+
type: "uc_function";
14+
uc_function: {
15+
description: string;
16+
name: string;
17+
};
18+
}
19+
| {
20+
knowledge_assistant: {
21+
description: string;
22+
knowledge_assistant_id: string;
23+
};
24+
type: "knowledge_assistant";
25+
}
26+
| {
27+
app: {
28+
description: string;
29+
name: string;
30+
};
31+
type: "app";
32+
}
33+
| {
34+
type: "uc_connection";
35+
uc_connection: {
36+
description: string;
37+
name: string;
38+
};
39+
};
40+
```
41+
42+
Tools supported by the Databricks AI Gateway Responses API. The shapes match
43+
the wire format the endpoint expects, so the adapter passes the array
44+
straight into the request body.
45+
46+
Prefer the [supervisorTools](Variable.supervisorTools.md) factories — they fill in the
47+
SA-validation-bug workaround for `description` (must be non-empty).

0 commit comments

Comments
 (0)