-
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 (and optionally MCP servers) after app creation | ||
|
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. 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
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. 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;") | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| # Interface: AgentInterface | ||
|
|
||
| Contract that agent implementations must fulfil. | ||
|
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. 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 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. (Or maybe the chat UI plugin should just support running against such an endpoint, i.e. accept
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.
yeah I think we should allow chatUI to be configured to target
|
||
|
|
||
| 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)\> | ||
| 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"; | ||
| ``` |
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