Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/dev-playground/.env.dist
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ OTEL_SERVICE_NAME='dev-playground'
DATABRICKS_VOLUME_PLAYGROUND=
DATABRICKS_VOLUME_OTHER=
DATABRICKS_GENIE_SPACE_ID=
DATABRICKS_MODEL=
LAKEBASE_ENDPOINT='' # Run: databricks postgres list-endpoints projects/{project-id}/branches/{branch-id} — use the `name` field from the output
PGHOST=
PGUSER=
Expand Down
21 changes: 21 additions & 0 deletions apps/dev-playground/client/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Route as DataVisualizationRouteRouteImport } from './routes/data-visual
import { Route as ChartInferenceRouteRouteImport } from './routes/chart-inference.route'
import { Route as ArrowAnalyticsRouteRouteImport } from './routes/arrow-analytics.route'
import { Route as AnalyticsRouteRouteImport } from './routes/analytics.route'
import { Route as AgentRouteRouteImport } from './routes/agent.route'
import { Route as IndexRouteImport } from './routes/index'

const TypeSafetyRouteRoute = TypeSafetyRouteRouteImport.update({
Expand Down Expand Up @@ -77,6 +78,11 @@ const AnalyticsRouteRoute = AnalyticsRouteRouteImport.update({
path: '/analytics',
getParentRoute: () => rootRouteImport,
} as any)
const AgentRouteRoute = AgentRouteRouteImport.update({
id: '/agent',
path: '/agent',
getParentRoute: () => rootRouteImport,
} as any)
const IndexRoute = IndexRouteImport.update({
id: '/',
path: '/',
Expand All @@ -85,6 +91,7 @@ const IndexRoute = IndexRouteImport.update({

export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/agent': typeof AgentRouteRoute
'/analytics': typeof AnalyticsRouteRoute
'/arrow-analytics': typeof ArrowAnalyticsRouteRoute
'/chart-inference': typeof ChartInferenceRouteRoute
Expand All @@ -99,6 +106,7 @@ export interface FileRoutesByFullPath {
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/agent': typeof AgentRouteRoute
'/analytics': typeof AnalyticsRouteRoute
'/arrow-analytics': typeof ArrowAnalyticsRouteRoute
'/chart-inference': typeof ChartInferenceRouteRoute
Expand All @@ -114,6 +122,7 @@ export interface FileRoutesByTo {
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/agent': typeof AgentRouteRoute
'/analytics': typeof AnalyticsRouteRoute
'/arrow-analytics': typeof ArrowAnalyticsRouteRoute
'/chart-inference': typeof ChartInferenceRouteRoute
Expand All @@ -130,6 +139,7 @@ export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths:
| '/'
| '/agent'
| '/analytics'
| '/arrow-analytics'
| '/chart-inference'
Expand All @@ -144,6 +154,7 @@ export interface FileRouteTypes {
fileRoutesByTo: FileRoutesByTo
to:
| '/'
| '/agent'
| '/analytics'
| '/arrow-analytics'
| '/chart-inference'
Expand All @@ -158,6 +169,7 @@ export interface FileRouteTypes {
id:
| '__root__'
| '/'
| '/agent'
| '/analytics'
| '/arrow-analytics'
| '/chart-inference'
Expand All @@ -173,6 +185,7 @@ export interface FileRouteTypes {
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
AgentRouteRoute: typeof AgentRouteRoute
AnalyticsRouteRoute: typeof AnalyticsRouteRoute
ArrowAnalyticsRouteRoute: typeof ArrowAnalyticsRouteRoute
ChartInferenceRouteRoute: typeof ChartInferenceRouteRoute
Expand Down Expand Up @@ -265,6 +278,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AnalyticsRouteRouteImport
parentRoute: typeof rootRouteImport
}
'/agent': {
id: '/agent'
path: '/agent'
fullPath: '/agent'
preLoaderRoute: typeof AgentRouteRouteImport
parentRoute: typeof rootRouteImport
}
'/': {
id: '/'
path: '/'
Expand All @@ -277,6 +297,7 @@ declare module '@tanstack/react-router' {

const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
AgentRouteRoute: AgentRouteRoute,
AnalyticsRouteRoute: AnalyticsRouteRoute,
ArrowAnalyticsRouteRoute: ArrowAnalyticsRouteRoute,
ChartInferenceRouteRoute: ChartInferenceRouteRoute,
Expand Down
8 changes: 8 additions & 0 deletions apps/dev-playground/client/src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@ function RootComponent() {
Files
</Button>
</Link>
<Link to="/agent" className="no-underline">
<Button
variant="ghost"
className="text-foreground hover:text-secondary-foreground"
>
Agent
</Button>
</Link>
<ThemeSelector />
</div>
</nav>
Expand Down
31 changes: 31 additions & 0 deletions apps/dev-playground/client/src/routes/agent.route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { AgentChat } from "@databricks/appkit-ui/react";
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/agent")({
component: AgentChatRoute,
});

function AgentChatRoute() {
return (
<div className="min-h-screen bg-background">
<main className="max-w-2xl mx-auto px-6 py-12 flex flex-col h-[calc(100vh-6rem)]">
<div className="mb-6">
<h1 className="text-3xl font-bold tracking-tight text-foreground">
Agent Chat
</h1>
<p className="text-muted-foreground mt-1">
Chat with the agent via <code>POST /invocations</code> (Responses
API SSE stream).
</p>
</div>

<AgentChat
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

N/B: we're keeping a minimal UI for now just to be able to chat, until we port the "full" chat UI as a separate plugin

invokeUrl="/invocations"
placeholder="Type a message..."
emptyMessage="Send a message to start."
className="flex-1 min-h-0"
/>
</main>
</div>
);
}
26 changes: 26 additions & 0 deletions apps/dev-playground/client/src/routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,32 @@ function IndexRoute() {
</Button>
</div>
</Card>

<Card className="p-6 hover:shadow-lg transition-shadow cursor-pointer">
<div className="flex flex-col h-full">
<h3 className="text-2xl font-semibold text-foreground mb-3">
Agent Chat
</h3>
<p className="text-muted-foreground mb-6 flex-grow">
Chat with a AI agent powered by the AppKit Agent Plugin.
Features{" "}
<a
href="https://www.openresponses.org/"
target="_blank"
rel="noopener noreferrer"
>
OpenResponses
</a>
-compatible SSE streaming and tool call rendering.
</p>
<Button
onClick={() => navigate({ to: "/agent" })}
className="w-full"
>
Try Agent Chat
</Button>
</div>
</Card>
</div>

<div className="text-center pt-12 border-t border-border">
Expand Down
44 changes: 44 additions & 0 deletions apps/dev-playground/server/agent-tools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { FunctionTool } from "@databricks/appkit";

export const weatherTool: FunctionTool = {
type: "function",
name: "get_weather",
description: "Get the current weather for a location",
parameters: {
type: "object",
properties: {
location: {
type: "string",
description: "City name, e.g. 'San Francisco'",
},
},
required: ["location"],
},
execute: async ({ location }) => {
const conditions = ["sunny", "partly cloudy", "rainy", "windy"];
const condition = conditions[Math.floor(Math.random() * conditions.length)];
const temp = Math.floor(Math.random() * 30) + 50;
return `Weather in ${location}: ${condition}, ${temp}°F`;
},
};

export const timeTool: FunctionTool = {
type: "function",
name: "get_current_time",
description: "Get the current date and time in a timezone",
parameters: {
type: "object",
properties: {
timezone: {
type: "string",
description: "IANA timezone, e.g. 'America/New_York'. Defaults to UTC",
},
},
},
execute: async ({ timezone }) => {
const tz = (timezone as string) ?? "UTC";
return `Current time in ${tz}: ${new Date().toLocaleString("en-US", { timeZone: tz })}`;
},
};

export const demoTools = { weatherTool, timeTool };
27 changes: 25 additions & 2 deletions apps/dev-playground/server/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import "reflect-metadata";
import { analytics, createApp, files, genie, server } from "@databricks/appkit";
import {
agent,
analytics,
createApp,
files,
genie,
server,
} from "@databricks/appkit";
import { WorkspaceClient } from "@databricks/sdk-experimental";
import { demoTools } from "./agent-tools";
import { lakebaseExamples } from "./lakebase-examples-plugin";
import { reconnect } from "./reconnect-plugin";
import { telemetryExamples } from "./telemetry-example-plugin";
Expand All @@ -26,11 +34,26 @@ createApp({
}),
lakebaseExamples(),
files(),
agent({
model: process.env.DATABRICKS_MODEL || "databricks-claude-sonnet-4-5",
systemPrompt:
"You are a helpful assistant. Use tools when appropriate — for example, use get_weather for weather questions, and get_current_time for time queries.",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible for users to pass tools here? It seems a little counterintuitive to have to call appkit.agent.addTools later. Or maybe here the user could have the option to pass either a list of tools, or a function that accepts an appkit and returns a list of tools, which would get called in the agent implementation to get the list of tools? In general, I think it'd be better to start with just a list of tools here for simplicity - we can see if it makes sense to support the (appkit) => listOftools type later

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see it is possible to pass a list of tools directly - that makes sense, but I don't think we want to reuse the LangChain tool type (see my comment RE OpenResponses below). In particular I wonder if it makes sense to do something like support passing either typescript function objects (locally-defined tool implementations) or structured OpenResponses-style hosted tool definitions like {"type": "genie", "genie_space": {"id": ...}}. Happy to discuss more how the hosted tool piece should work - it should be possible to translate a spec of that format into a call to a Databricks managed MCP server

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Correct, it's possible to add tools/MCPs either at initialization or later (anticipating for some plugin interop in the future).
  2. let me address in the comment below

tools: [demoTools.weatherTool],
}),
],
...(process.env.APPKIT_E2E_TEST && { client: createMockClient() }),
}).then((appkit) => {
}).then(async (appkit) => {
// Add tools (and optionally MCP servers) after app creation

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally the interface/format for tools would match OpenResponses conventions, as outlined in this doc. AI can probably help make this change given https://github.com/openresponses/openresponses/blob/main/public/openapi/openapi.json#L1225 for reference :P

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redone tools API to use standard openresponses format

await appkit.agent.addCapabilities({ tools: [demoTools.timeTool] });

appkit.server
.extend((app) => {
// Rewrite to use standard Databricks Apps convention: /invocations at root
app.post("/invocations", (req, res) => {
req.url = "/api/agent";
app(req, res);
});

app.get("/sp", (_req, res) => {
appkit.analytics
.query("SELECT * FROM samples.nyctaxi.trips;")
Expand Down
43 changes: 43 additions & 0 deletions docs/docs/api/appkit/Interface.AgentInterface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Interface: AgentInterface

Contract that agent implementations must fulfil.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, AFAICT this allows basically plugging in our existing TypeScript agent implementation from https://github.com/databricks/app-templates/tree/main/agent-langchain-ts, which is great. Later we may want a way to get an agentInstance from an existing OpenResponses-compatible agent endpoint or Agent Brick on Databricks, e.g. for the chat UI. We can add support for that later though

Copy link

@smurching smurching Mar 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Or maybe the chat UI plugin should just support running against such an endpoint, i.e. accept endpointName: agentBrickEndpointName as an option; that's probably cleaner)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Or maybe the chat UI plugin should just support running against such an endpoint, i.e. accept endpointName: agentBrickEndpointName as an option; that's probably cleaner)

yeah I think we should allow chatUI to be configured to target

  • any hosted model on databricks
  • hosted agent bricks
  • agent plugin running in the same app


The plugin calls `invoke()` for non-streaming requests and `stream()` for
SSE streaming. Implementations are responsible for translating their SDK's
output into Responses API types.

## Methods

### invoke()

```ts
invoke(params: InvokeParams): Promise<ResponseOutputItem[]>;
```

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `params` | [`InvokeParams`](Interface.InvokeParams.md) |

#### Returns

`Promise`\<`ResponseOutputItem`[]\>

***

### stream()

```ts
stream(params: InvokeParams): AsyncGenerator<ResponseStreamEvent>;
```

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `params` | [`InvokeParams`](Interface.InvokeParams.md) |

#### Returns

`AsyncGenerator`\<[`ResponseStreamEvent`](TypeAlias.ResponseStreamEvent.md)\>
4 changes: 4 additions & 0 deletions docs/docs/api/appkit/Interface.BasePluginConfig.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Base configuration interface for AppKit plugins

## Extended by

- [`IAgentConfig`](Interface.IAgentConfig.md)

## Indexable

```ts
Expand Down
63 changes: 63 additions & 0 deletions docs/docs/api/appkit/Interface.FunctionTool.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Interface: FunctionTool

## Properties

### description?

```ts
optional description: string | null;
```

***

### execute()

```ts
execute: (args: Record<string, unknown>) => string | Promise<string>;
```

Handler invoked when the model calls this tool.

#### Parameters

| Parameter | Type |
| ------ | ------ |
| `args` | `Record`\<`string`, `unknown`\> |

#### Returns

`string` \| `Promise`\<`string`\>

***

### name

```ts
name: string;
```

***

### parameters?

```ts
optional parameters: Record<string, unknown> | null;
```

JSON Schema object describing the tool's parameters.

***

### strict?

```ts
optional strict: boolean | null;
```

***

### type

```ts
type: "function";
```
Loading
Loading