In the previous step, we set up the frontend to connect to a LangGraph Cloud endpoint.
In this step, we will set up a component to display stock ticker information.
import Image from "next/image";
import price from "./images/acme-price.png";
For reference, this is the corresponding code in the backend:
export const priceSnapshotTool = tool(
async (input) => {
const data = await callFinancialDatasetAPI<SnapshotResponse>({
endpoint: "/prices/snapshot",
params: {
ticker: input.ticker,
},
});
return JSON.stringify(data, null);
},
{
name: "price_snapshot",
description:
"Retrieves the current stock price and related market data for a given company.",
schema: z.object({
ticker: z.string().describe("The ticker of the company. Example: 'AAPL'"),
}),
},
);
PriceSnapshotTool#
We create a new file under /components/tools/price-snapshot/PriceSnapshotTool.tsx to define the tool.
First, we define the tool arguments and result types:
type PriceSnapshotToolArgs = {
ticker: string;
};
type PriceSnapshotToolResult = {
snapshot: {
price: number;
day_change: number;
day_change_percent: number;
time: string;
};
};
Then, we define a tool UI renderer:
"use client";
import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
export const PriceSnapshotToolUI: ToolCallMessagePartComponent<
PriceSnapshotToolArgs,
string
> = function PriceSnapshotUI({ args, result }) {
return (
<div className="mb-4 flex flex-col items-center">
<pre className="whitespace-pre-wrap break-all text-center">
price_snapshot({JSON.stringify(args)})
</pre>
</div>
);
};
This displays the tool name and arguments passed to it, but not the result.
Bind tool UI#
Register the tool UI in a toolkit and pass it to the AssistantRuntimeProvider that the template sets up in app/assistant.tsx:
"use client";
import { AssistantRuntimeProvider, AuiConfig, defineToolkit, Tools } from "@assistant-ui/react";
import { useStreamRuntime } from "@assistant-ui/react-langchain";
import { Thread } from "@/components/assistant-ui/elements/thread.aui";
import { PriceSnapshotToolUI } from "@/components/tools/price-snapshot/PriceSnapshotTool";
const toolkit = defineToolkit({
price_snapshot: {
type: "backend",
render: PriceSnapshotToolUI,
},
});
export function Assistant() {
const apiUrl =
process.env.NEXT_PUBLIC_LANGGRAPH_API_URL ||
(typeof window !== "undefined"
? new URL("/api", window.location.href).href
: undefined);
const runtime = useStreamRuntime({
assistantId: process.env.NEXT_PUBLIC_LANGGRAPH_ASSISTANT_ID!,
apiUrl,
});
const config = AuiConfig({ tools: Tools({ toolkit }) });
return (
<AssistantRuntimeProvider runtime={runtime} config={config}>
<Thread />
</AssistantRuntimeProvider>
);
}
Try it out!#
Ask the assistant for the current stock price of Tesla. You should see the following text appear:
price_snapshot({ticker: "TSLA"})
Next, we will visualize the function's result.
Visualizing tool results#
Install dependencies#
The tool result component relies on shadcn/ui's Card component. We will install it as a dependency.
npx shadcn@latest add card
This installs a card UI component in your project.
Add PriceSnapshot#
We create a new file under /components/tools/price-snapshot/price-snapshot.tsx to define the new tool result UI.
"use client";
import { ArrowDownIcon, ArrowUpIcon } from "lucide-react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
type PriceSnapshotToolArgs = {
ticker: string;
};
type PriceSnapshotToolResult = {
price: number;
day_change: number;
day_change_percent: number;
time: string;
};
export function PriceSnapshot({
ticker,
price,
day_change,
day_change_percent,
time,
}: PriceSnapshotToolArgs & PriceSnapshotToolResult) {
const isPositiveChange = day_change >= 0;
const changeColor = isPositiveChange ? "text-green-600" : "text-red-600";
const ArrowIcon = isPositiveChange ? ArrowUpIcon : ArrowDownIcon;
return (
<Card className="mx-auto w-full max-w-md">
<CardHeader>
<CardTitle className="text-2xl font-bold">{ticker}</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 gap-4">
<div className="col-span-2">
<p className="text-3xl font-semibold">${price?.toFixed(2)}</p>
</div>
<div>
<p className="text-muted-foreground text-sm">Day Change</p>
<p
className={`flex items-center text-lg font-medium ${changeColor}`}
<ArrowIcon className="mr-1 h-4 w-4" />$
{Math.abs(day_change)?.toFixed(2)} (
{Math.abs(day_change_percent)?.toFixed(2)}%)
</p>
</div>
<div>
<p className="text-muted-foreground text-sm">Last Updated</p>
<p className="text-lg font-medium">
{new Date(time).toLocaleTimeString()}
</p>
</div>
</div>
</CardContent>
</Card>
);
}
Update PriceSnapshotTool#
We will import the new <PriceSnapshot /> component and use it in the render function whenever a tool result is available.
"use client";
import { PriceSnapshot } from "./price-snapshot";
import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
type PriceSnapshotToolArgs = {
ticker: string;
};
type PriceSnapshotToolResult = {
snapshot: {
price: number;
day_change: number;
day_change_percent: number;
time: string;
};
};
export const PriceSnapshotToolUI: ToolCallMessagePartComponent<
PriceSnapshotToolArgs,
string
> = function PriceSnapshotUI({ args, result }) {
let resultObj: PriceSnapshotToolResult | { error: string };
try {
resultObj = result ? JSON.parse(result) : {};
} catch (e) {
resultObj = { error: result! };
}
return (
<div className="mb-4 flex flex-col items-center gap-2">
<pre className="whitespace-pre-wrap break-all text-center">
price_snapshot({JSON.stringify(args)})
</pre>
{"snapshot" in resultObj && (
<PriceSnapshot ticker={args.ticker} {...resultObj.snapshot} />
)}
{"error" in resultObj && (
<p className="text-red-500">{resultObj.error}</p>
)}
</div>
);
};
Try it out!#
Ask the assistant for the current stock price of Tesla. You should see the tool result appear:
import price2 from "./images/tsla-price.png";
Fallback tool UI#
Tools without a custom tool UI render with the ToolFallback built into the thread element, so no additional configuration is needed.