-
Notifications
You must be signed in to change notification settings - Fork 5
feat(appkit): add AgentPlugin for LangChain/LangGraph agents #166
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 | ||
| invokeUrl="/invocations" | ||
| placeholder="Type a message..." | ||
| emptyMessage="Send a message to start." | ||
| className="flex-1 min-h-0" | ||
| /> | ||
| </main> | ||
| </div> | ||
| ); | ||
| } | ||
| 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 }; |
| 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"; | ||
|
|
@@ -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.", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible for users to pass There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| tools: [demoTools.weatherTool], | ||
| }), | ||
| ], | ||
| ...(process.env.APPKIT_E2E_TEST && { client: createMockClient() }), | ||
| }).then((appkit) => { | ||
| }).then(async (appkit) => { | ||
| // Add tools after app creation | ||
| await appkit.agent.addTools([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;") | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| # Class: StandardAgent | ||
|
|
||
| Contract that agent implementations must fulfil. | ||
|
|
||
| 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. | ||
|
|
||
| ## Implements | ||
|
|
||
| - [`AgentInterface`](Interface.AgentInterface.md) | ||
|
|
||
| ## Constructors | ||
|
|
||
| ### Constructor | ||
|
|
||
| ```ts | ||
| new StandardAgent(agent: LangGraphAgent, systemPrompt: string): StandardAgent; | ||
| ``` | ||
|
|
||
| #### Parameters | ||
|
|
||
| | Parameter | Type | | ||
| | ------ | ------ | | ||
| | `agent` | `LangGraphAgent` | | ||
| | `systemPrompt` | `string` | | ||
|
|
||
| #### Returns | ||
|
|
||
| `StandardAgent` | ||
|
|
||
| ## Methods | ||
|
|
||
| ### invoke() | ||
|
|
||
| ```ts | ||
| invoke(params: InvokeParams): Promise<ResponseOutputItem[]>; | ||
| ``` | ||
|
|
||
| #### Parameters | ||
|
|
||
| | Parameter | Type | | ||
| | ------ | ------ | | ||
| | `params` | [`InvokeParams`](Interface.InvokeParams.md) | | ||
|
|
||
| #### Returns | ||
|
|
||
| `Promise`\<[`ResponseOutputItem`](TypeAlias.ResponseOutputItem.md)[]\> | ||
|
|
||
| #### Implementation of | ||
|
|
||
| [`AgentInterface`](Interface.AgentInterface.md).[`invoke`](Interface.AgentInterface.md#invoke) | ||
|
|
||
| *** | ||
|
|
||
| ### stream() | ||
|
|
||
| ```ts | ||
| stream(params: InvokeParams): AsyncGenerator<ResponseStreamEvent>; | ||
| ``` | ||
|
|
||
| #### Parameters | ||
|
|
||
| | Parameter | Type | | ||
| | ------ | ------ | | ||
| | `params` | [`InvokeParams`](Interface.InvokeParams.md) | | ||
|
|
||
| #### Returns | ||
|
|
||
| `AsyncGenerator`\<[`ResponseStreamEvent`](TypeAlias.ResponseStreamEvent.md)\> | ||
|
|
||
| #### Implementation of | ||
|
|
||
| [`AgentInterface`](Interface.AgentInterface.md).[`stream`](Interface.AgentInterface.md#stream) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # Function: createInvokeHandler() | ||
|
|
||
| ```ts | ||
| function createInvokeHandler(getAgent: () => AgentInterface): RequestHandler; | ||
| ``` | ||
|
|
||
| Create an Express handler that invokes the agent via the AgentInterface | ||
| and streams/returns the response in Responses API format. | ||
|
|
||
| ## Parameters | ||
|
|
||
| | Parameter | Type | | ||
| | ------ | ------ | | ||
| | `getAgent` | () => [`AgentInterface`](Interface.AgentInterface.md) | | ||
|
|
||
| ## Returns | ||
|
|
||
| `RequestHandler` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| # Function: isFunctionTool() | ||
|
|
||
| ```ts | ||
| function isFunctionTool(t: unknown): t is FunctionTool; | ||
| ``` | ||
|
|
||
| ## Parameters | ||
|
|
||
| | Parameter | Type | | ||
| | ------ | ------ | | ||
| | `t` | `unknown` | | ||
|
|
||
| ## Returns | ||
|
|
||
| `t is FunctionTool` |
There was a problem hiding this comment.
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