Skip to content

Commit e7b5ba3

Browse files
committed
feat(appkit): add AgentPlugin for LangChain/LangGraph agents
Signed-off-by: Hubert Zub <hubert.zub@databricks.com>
1 parent 0b3eacc commit e7b5ba3

40 files changed

+3422
-3
lines changed

apps/dev-playground/.env.dist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ OTEL_SERVICE_NAME='dev-playground'
99
DATABRICKS_VOLUME_PLAYGROUND=
1010
DATABRICKS_VOLUME_OTHER=
1111
DATABRICKS_GENIE_SPACE_ID=
12+
DATABRICKS_MODEL=
1213
LAKEBASE_ENDPOINT='' # Run: databricks postgres list-endpoints projects/{project-id}/branches/{branch-id} — use the `name` field from the output
1314
PGHOST=
1415
PGUSER=

apps/dev-playground/client/src/routeTree.gen.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { Route as DataVisualizationRouteRouteImport } from './routes/data-visual
2020
import { Route as ChartInferenceRouteRouteImport } from './routes/chart-inference.route'
2121
import { Route as ArrowAnalyticsRouteRouteImport } from './routes/arrow-analytics.route'
2222
import { Route as AnalyticsRouteRouteImport } from './routes/analytics.route'
23+
import { Route as AgentRouteRouteImport } from './routes/agent.route'
2324
import { Route as IndexRouteImport } from './routes/index'
2425

2526
const TypeSafetyRouteRoute = TypeSafetyRouteRouteImport.update({
@@ -77,6 +78,11 @@ const AnalyticsRouteRoute = AnalyticsRouteRouteImport.update({
7778
path: '/analytics',
7879
getParentRoute: () => rootRouteImport,
7980
} as any)
81+
const AgentRouteRoute = AgentRouteRouteImport.update({
82+
id: '/agent',
83+
path: '/agent',
84+
getParentRoute: () => rootRouteImport,
85+
} as any)
8086
const IndexRoute = IndexRouteImport.update({
8187
id: '/',
8288
path: '/',
@@ -85,6 +91,7 @@ const IndexRoute = IndexRouteImport.update({
8591

8692
export interface FileRoutesByFullPath {
8793
'/': typeof IndexRoute
94+
'/agent': typeof AgentRouteRoute
8895
'/analytics': typeof AnalyticsRouteRoute
8996
'/arrow-analytics': typeof ArrowAnalyticsRouteRoute
9097
'/chart-inference': typeof ChartInferenceRouteRoute
@@ -99,6 +106,7 @@ export interface FileRoutesByFullPath {
99106
}
100107
export interface FileRoutesByTo {
101108
'/': typeof IndexRoute
109+
'/agent': typeof AgentRouteRoute
102110
'/analytics': typeof AnalyticsRouteRoute
103111
'/arrow-analytics': typeof ArrowAnalyticsRouteRoute
104112
'/chart-inference': typeof ChartInferenceRouteRoute
@@ -114,6 +122,7 @@ export interface FileRoutesByTo {
114122
export interface FileRoutesById {
115123
__root__: typeof rootRouteImport
116124
'/': typeof IndexRoute
125+
'/agent': typeof AgentRouteRoute
117126
'/analytics': typeof AnalyticsRouteRoute
118127
'/arrow-analytics': typeof ArrowAnalyticsRouteRoute
119128
'/chart-inference': typeof ChartInferenceRouteRoute
@@ -130,6 +139,7 @@ export interface FileRouteTypes {
130139
fileRoutesByFullPath: FileRoutesByFullPath
131140
fullPaths:
132141
| '/'
142+
| '/agent'
133143
| '/analytics'
134144
| '/arrow-analytics'
135145
| '/chart-inference'
@@ -144,6 +154,7 @@ export interface FileRouteTypes {
144154
fileRoutesByTo: FileRoutesByTo
145155
to:
146156
| '/'
157+
| '/agent'
147158
| '/analytics'
148159
| '/arrow-analytics'
149160
| '/chart-inference'
@@ -158,6 +169,7 @@ export interface FileRouteTypes {
158169
id:
159170
| '__root__'
160171
| '/'
172+
| '/agent'
161173
| '/analytics'
162174
| '/arrow-analytics'
163175
| '/chart-inference'
@@ -173,6 +185,7 @@ export interface FileRouteTypes {
173185
}
174186
export interface RootRouteChildren {
175187
IndexRoute: typeof IndexRoute
188+
AgentRouteRoute: typeof AgentRouteRoute
176189
AnalyticsRouteRoute: typeof AnalyticsRouteRoute
177190
ArrowAnalyticsRouteRoute: typeof ArrowAnalyticsRouteRoute
178191
ChartInferenceRouteRoute: typeof ChartInferenceRouteRoute
@@ -265,6 +278,13 @@ declare module '@tanstack/react-router' {
265278
preLoaderRoute: typeof AnalyticsRouteRouteImport
266279
parentRoute: typeof rootRouteImport
267280
}
281+
'/agent': {
282+
id: '/agent'
283+
path: '/agent'
284+
fullPath: '/agent'
285+
preLoaderRoute: typeof AgentRouteRouteImport
286+
parentRoute: typeof rootRouteImport
287+
}
268288
'/': {
269289
id: '/'
270290
path: '/'
@@ -277,6 +297,7 @@ declare module '@tanstack/react-router' {
277297

278298
const rootRouteChildren: RootRouteChildren = {
279299
IndexRoute: IndexRoute,
300+
AgentRouteRoute: AgentRouteRoute,
280301
AnalyticsRouteRoute: AnalyticsRouteRoute,
281302
ArrowAnalyticsRouteRoute: ArrowAnalyticsRouteRoute,
282303
ChartInferenceRouteRoute: ChartInferenceRouteRoute,

apps/dev-playground/client/src/routes/__root.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,14 @@ function RootComponent() {
104104
Files
105105
</Button>
106106
</Link>
107+
<Link to="/agent" className="no-underline">
108+
<Button
109+
variant="ghost"
110+
className="text-foreground hover:text-secondary-foreground"
111+
>
112+
Agent
113+
</Button>
114+
</Link>
107115
<ThemeSelector />
108116
</div>
109117
</nav>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { AgentChat } from "@databricks/appkit-ui/react";
2+
import { createFileRoute } from "@tanstack/react-router";
3+
4+
export const Route = createFileRoute("/agent")({
5+
component: AgentChatRoute,
6+
});
7+
8+
function AgentChatRoute() {
9+
return (
10+
<div className="min-h-screen bg-background">
11+
<main className="max-w-2xl mx-auto px-6 py-12 flex flex-col h-[calc(100vh-6rem)]">
12+
<div className="mb-6">
13+
<h1 className="text-3xl font-bold tracking-tight text-foreground">
14+
Agent Chat
15+
</h1>
16+
<p className="text-muted-foreground mt-1">
17+
Chat with the agent via <code>POST /invocations</code> (Responses
18+
API SSE stream).
19+
</p>
20+
</div>
21+
22+
<AgentChat
23+
invokeUrl="/invocations"
24+
placeholder="Type a message..."
25+
emptyMessage="Send a message to start."
26+
className="flex-1 min-h-0"
27+
/>
28+
</main>
29+
</div>
30+
);
31+
}

apps/dev-playground/client/src/routes/index.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,25 @@ function IndexRoute() {
218218
</Button>
219219
</div>
220220
</Card>
221+
222+
<Card className="p-6 hover:shadow-lg transition-shadow cursor-pointer">
223+
<div className="flex flex-col h-full">
224+
<h3 className="text-2xl font-semibold text-foreground mb-3">
225+
Agent Chat
226+
</h3>
227+
<p className="text-muted-foreground mb-6 flex-grow">
228+
Chat with a LangChain/LangGraph AI agent powered by the AppKit
229+
Agent Plugin. Features Responses API SSE streaming and tool call
230+
rendering.
231+
</p>
232+
<Button
233+
onClick={() => navigate({ to: "/agent" })}
234+
className="w-full"
235+
>
236+
Try Agent Chat
237+
</Button>
238+
</div>
239+
</Card>
221240
</div>
222241

223242
<div className="text-center pt-12 border-t border-border">
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { tool } from "@langchain/core/tools";
2+
import { z } from "zod";
3+
import { toJSONSchema } from "zod/v4/core";
4+
5+
/**
6+
* Workaround: @databricks/langchainjs@0.1.0 calls `schema.toJSONSchema()`
7+
* as a method, but zod v4 only exposes it as a standalone function.
8+
* Patch the schema so ChatDatabricks can convert it.
9+
*/
10+
function patchZodSchema<T extends z.ZodType>(schema: T): T {
11+
(schema as any).toJSONSchema = () => toJSONSchema(schema);
12+
return schema;
13+
}
14+
15+
export const weatherTool = tool(
16+
async ({ location }) => {
17+
const conditions = ["sunny", "partly cloudy", "rainy", "windy"];
18+
const condition = conditions[Math.floor(Math.random() * conditions.length)];
19+
const temp = Math.floor(Math.random() * 30) + 50;
20+
return `Weather in ${location}: ${condition}, ${temp}°F`;
21+
},
22+
{
23+
name: "get_weather",
24+
description: "Get the current weather for a location",
25+
schema: patchZodSchema(
26+
z.object({
27+
location: z.string().describe("City name, e.g. 'San Francisco'"),
28+
}),
29+
),
30+
},
31+
);
32+
33+
export const timeTool = tool(
34+
async ({ timezone }) => {
35+
const tz = timezone ?? "UTC";
36+
return `Current time in ${tz}: ${new Date().toLocaleString("en-US", { timeZone: tz })}`;
37+
},
38+
{
39+
name: "get_current_time",
40+
description: "Get the current date and time in a timezone",
41+
schema: patchZodSchema(
42+
z.object({
43+
timezone: z
44+
.string()
45+
.optional()
46+
.describe("IANA timezone, e.g. 'America/New_York'. Defaults to UTC"),
47+
}),
48+
),
49+
},
50+
);
51+
52+
export const demoTools = [weatherTool, timeTool];

apps/dev-playground/server/index.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
import "reflect-metadata";
2-
import { analytics, createApp, files, genie, server } from "@databricks/appkit";
2+
import {
3+
agent,
4+
analytics,
5+
createApp,
6+
files,
7+
genie,
8+
server,
9+
} from "@databricks/appkit";
310
import { WorkspaceClient } from "@databricks/sdk-experimental";
11+
import { demoTools } from "./agent-tools";
412
import { lakebaseExamples } from "./lakebase-examples-plugin";
513
import { reconnect } from "./reconnect-plugin";
614
import { telemetryExamples } from "./telemetry-example-plugin";
@@ -26,11 +34,25 @@ createApp({
2634
}),
2735
lakebaseExamples(),
2836
files(),
37+
agent({
38+
model: process.env.DATABRICKS_MODEL || "databricks-claude-sonnet-4-5",
39+
systemPrompt:
40+
"You are a helpful assistant. Use tools when appropriate — for example, use get_weather for weather questions, and get_current_time for time queries.",
41+
}),
2942
],
3043
...(process.env.APPKIT_E2E_TEST && { client: createMockClient() }),
31-
}).then((appkit) => {
44+
}).then(async (appkit) => {
45+
// Add tools (and optionally MCP servers) after app creation
46+
await appkit.agent.addTools(demoTools);
47+
3248
appkit.server
3349
.extend((app) => {
50+
// Rewrite to use standard Databricks Apps convention: /invocations at root
51+
app.post("/invocations", (req, res) => {
52+
req.url = "/api/agent";
53+
app(req, res);
54+
});
55+
3456
app.get("/sp", (_req, res) => {
3557
appkit.analytics
3658
.query("SELECT * FROM samples.nyctaxi.trips;")
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Interface: AgentInterface
2+
3+
Contract that agent implementations must fulfil.
4+
5+
The plugin calls `invoke()` for non-streaming requests and `stream()` for
6+
SSE streaming. Implementations are responsible for translating their SDK's
7+
output into Responses API types.
8+
9+
## Methods
10+
11+
### invoke()
12+
13+
```ts
14+
invoke(params: InvokeParams): Promise<ResponseOutputItem[]>;
15+
```
16+
17+
#### Parameters
18+
19+
| Parameter | Type |
20+
| ------ | ------ |
21+
| `params` | [`InvokeParams`](Interface.InvokeParams.md) |
22+
23+
#### Returns
24+
25+
`Promise`\<`ResponseOutputItem`[]\>
26+
27+
***
28+
29+
### stream()
30+
31+
```ts
32+
stream(params: InvokeParams): AsyncGenerator<ResponseStreamEvent>;
33+
```
34+
35+
#### Parameters
36+
37+
| Parameter | Type |
38+
| ------ | ------ |
39+
| `params` | [`InvokeParams`](Interface.InvokeParams.md) |
40+
41+
#### Returns
42+
43+
`AsyncGenerator`\<[`ResponseStreamEvent`](TypeAlias.ResponseStreamEvent.md)\>

docs/docs/api/appkit/Interface.BasePluginConfig.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
Base configuration interface for AppKit plugins
44

5+
## Extended by
6+
7+
- [`IAgentConfig`](Interface.IAgentConfig.md)
8+
59
## Indexable
610

711
```ts

0 commit comments

Comments
 (0)