Merge branch 'main' of https://github.com/langchain-ai/agent-ui-demo into brace/hookup-accommodations

This commit is contained in:
bracesproul
2025-03-05 12:38:02 -08:00
12 changed files with 78 additions and 60 deletions

View File

@@ -1,4 +1,4 @@
import { StockbrokerState } from "../types";
import { StockbrokerState, StockbrokerUpdate } from "../types";
import { ChatOpenAI } from "@langchain/openai";
import { typedUi } from "@langchain/langgraph-sdk/react-ui/server";
import type ComponentMap from "../../uis/index";
@@ -32,7 +32,7 @@ const STOCKBROKER_TOOLS = [
export async function callTools(
state: StockbrokerState,
config: LangGraphRunnableConfig,
): Promise<Partial<StockbrokerState>> {
): Promise<StockbrokerUpdate> {
const ui = typedUi<typeof ComponentMap>(config);
const message = await llm.bindTools(STOCKBROKER_TOOLS).invoke([
@@ -65,8 +65,7 @@ export async function callTools(
return {
messages: [message],
// TODO: Fix the ui return type.
ui: ui.collect as any[],
ui: ui.collect as StockbrokerUpdate["ui"],
timestamp: Date.now(),
};
}

View File

@@ -8,3 +8,4 @@ export const StockbrokerAnnotation = Annotation.Root({
});
export type StockbrokerState = typeof StockbrokerAnnotation.State;
export type StockbrokerUpdate = typeof StockbrokerAnnotation.Update;

View File

@@ -1,11 +1,11 @@
import { ChatOpenAI } from "@langchain/openai";
import { TripPlannerState } from "../types";
import { TripPlannerState, TripPlannerUpdate } from "../types";
import { z } from "zod";
import { formatMessages } from "agent/utils/format-messages";
export async function classify(
state: TripPlannerState,
): Promise<Partial<TripPlannerState>> {
): Promise<TripPlannerUpdate> {
if (!state.tripDetails) {
// Can not classify if tripDetails are undefined
return {};

View File

@@ -1,5 +1,5 @@
import { ChatOpenAI } from "@langchain/openai";
import { TripDetails, TripPlannerState } from "../types";
import { TripDetails, TripPlannerState, TripPlannerUpdate } from "../types";
import { z } from "zod";
import { formatMessages } from "agent/utils/format-messages";
@@ -43,7 +43,7 @@ function calculateDates(
export async function extraction(
state: TripPlannerState,
): Promise<Partial<TripPlannerState>> {
): Promise<TripPlannerUpdate> {
const schema = z.object({
location: z
.string()

View File

@@ -1,4 +1,4 @@
import { TripPlannerState } from "../types";
import { TripPlannerState, TripPlannerUpdate } from "../types";
import { ChatOpenAI } from "@langchain/openai";
import { typedUi } from "@langchain/langgraph-sdk/react-ui/server";
import type ComponentMap from "../../uis/index";
@@ -49,7 +49,7 @@ const schema = z.object({
export async function callTools(
state: TripPlannerState,
config: LangGraphRunnableConfig,
): Promise<Partial<TripPlannerState>> {
): Promise<TripPlannerUpdate> {
if (!state.tripDetails) {
throw new Error("No trip details found");
}
@@ -111,8 +111,7 @@ export async function callTools(
return {
messages: [response],
// TODO: Fix the ui return type.
ui: ui.collect as any[],
ui: ui.collect as TripPlannerUpdate["ui"],
timestamp: Date.now(),
};
}

View File

@@ -16,3 +16,4 @@ export const TripPlannerAnnotation = Annotation.Root({
});
export type TripPlannerState = typeof TripPlannerAnnotation.State;
export type TripPlannerUpdate = typeof TripPlannerAnnotation.Update;

View File

@@ -1,9 +1,16 @@
import { MessagesAnnotation, Annotation } from "@langchain/langgraph";
import { uiMessageReducer } from "@langchain/langgraph-sdk/react-ui/types";
import {
RemoveUIMessage,
UIMessage,
uiMessageReducer,
} from "@langchain/langgraph-sdk/react-ui/server";
export const GenerativeUIAnnotation = Annotation.Root({
messages: MessagesAnnotation.spec["messages"],
ui: Annotation({ default: () => [], reducer: uiMessageReducer }),
ui: Annotation<
UIMessage[],
UIMessage | RemoveUIMessage | (UIMessage | RemoveUIMessage)[]
>({ default: () => [], reducer: uiMessageReducer }),
timestamp: Annotation<number>,
next: Annotation<"stockbroker" | "tripPlanner" | "generalInput">(),
});

View File

@@ -1,12 +1,20 @@
import "./index.css";
import { useStream } from "@langchain/langgraph-sdk/react";
import type { AIMessage, Message } from "@langchain/langgraph-sdk";
import {
useStreamContext,
type UIMessage,
} from "@langchain/langgraph-sdk/react-ui";
import type { AIMessage, Message, ToolMessage } from "@langchain/langgraph-sdk";
import { useState, useEffect, useCallback } from "react";
export default function StockPrice(props: {
instruction: string;
logo: string;
}) {
const thread = useStreamContext<
{ messages: Message[]; ui: UIMessage[] },
{ MetaType: { ui: UIMessage | undefined } }
>();
const [quantity, setQuantity] = useState(1);
const [stockData, setStockData] = useState({
symbol: "AAPL",
@@ -51,23 +59,16 @@ export default function StockPrice(props: {
const [limitPrice, setLimitPrice] = useState(stockData.price.toFixed(2));
const [showOrderSuccess, setShowOrderSuccess] = useState(false);
// useStream should be able to be infered from context
const thread = useStream<{ messages: Message[] }>({
assistantId: "assistant_123",
apiUrl: "http://localhost:3123",
});
const messagesCopy = thread.messages;
const aiTool = messagesCopy
.slice()
.reverse()
.find(
(message): message is AIMessage =>
message.type === "ai" && !!message.tool_calls?.length,
);
const aiTool = thread.messages.findLast(
(message): message is AIMessage =>
message.type === "ai" && !!message.tool_calls?.length,
);
const toolCallId = aiTool?.tool_calls?.[0]?.id;
const toolResponse = thread.messages.findLast(
(message): message is ToolMessage =>
message.type === "tool" && message.tool_call_id === toolCallId,
);
// Simulated price history generation on component mount
useEffect(() => {
@@ -248,6 +249,7 @@ export default function StockPrice(props: {
const range = max - min || 1;
const chartPath = generateChartPath();
if (toolResponse) return <div>Responded</div>;
return (
<div className="w-full max-w-md bg-white rounded-xl shadow-lg overflow-hidden border border-gray-200">
<div

View File

@@ -58,9 +58,9 @@
"zod": "^3.24.2"
},
"resolutions": {
"@langchain/langgraph-api": "next",
"@langchain/langgraph-cli": "next",
"@langchain/langgraph-sdk": "next"
"@langchain/langgraph-api": "0.0.14-experimental.1",
"@langchain/langgraph-cli": "0.0.14-experimental.1",
"@langchain/langgraph-sdk": "0.0.47-experimental.0"
},
"devDependencies": {
"@eslint/js": "^9.19.0",

40
pnpm-lock.yaml generated
View File

@@ -5,9 +5,9 @@ settings:
excludeLinksFromLockfile: false
overrides:
"@langchain/langgraph-api": next
"@langchain/langgraph-cli": next
"@langchain/langgraph-sdk": next
"@langchain/langgraph-api": 0.0.14-experimental.1
"@langchain/langgraph-cli": 0.0.14-experimental.1
"@langchain/langgraph-sdk": 0.0.47-experimental.0
importers:
.:
@@ -34,14 +34,14 @@ importers:
specifier: ^0.2.49
version: 0.2.49(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0)
"@langchain/langgraph-api":
specifier: next
version: 0.0.14-experimental.0(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(@langchain/langgraph-checkpoint@0.0.15(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2))))(@langchain/langgraph@0.2.49(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0))(openai@4.85.4(zod@3.24.2))(typescript@5.7.3)
specifier: 0.0.14-experimental.1
version: 0.0.14-experimental.1(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(@langchain/langgraph-checkpoint@0.0.15(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2))))(@langchain/langgraph@0.2.49(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0))(openai@4.85.4(zod@3.24.2))(typescript@5.7.3)
"@langchain/langgraph-cli":
specifier: next
version: 0.0.14-experimental.0(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(@langchain/langgraph-checkpoint@0.0.15(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2))))(@langchain/langgraph@0.2.49(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0))(openai@4.85.4(zod@3.24.2))(typescript@5.7.3)
specifier: 0.0.14-experimental.1
version: 0.0.14-experimental.1(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(@langchain/langgraph-checkpoint@0.0.15(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2))))(@langchain/langgraph@0.2.49(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0))(openai@4.85.4(zod@3.24.2))(typescript@5.7.3)
"@langchain/langgraph-sdk":
specifier: next
version: 0.0.46-experimental.0(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0)
specifier: 0.0.47-experimental.0
version: 0.0.47-experimental.0(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0)
"@langchain/openai":
specifier: ^0.4.4
version: 0.4.4(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))
@@ -1087,10 +1087,10 @@ packages:
peerDependencies:
"@langchain/core": ">=0.3.17 <0.4.0"
"@langchain/langgraph-api@0.0.14-experimental.0":
"@langchain/langgraph-api@0.0.14-experimental.1":
resolution:
{
integrity: sha512-Cc72BHOXS0DhDvlkF3eW6V9CxvMRm0BI2J+ftpZUDG4s3nJjnp4p5F/fXkCnu9hks7w9IUrp4CGXzK4LUEf2iw==,
integrity: sha512-gSQzZZk9tIrxXMQjudQbYHXPeK7l3Y/YbzCtnH6hWHvETQOZApUn0G18O5hWT9iYaAzZfSS8ExG7y6YM0MsFTQ==,
}
engines: { node: ^18.19.0 || >=20.16.0 }
peerDependencies:
@@ -1108,18 +1108,18 @@ packages:
peerDependencies:
"@langchain/core": ">=0.2.31 <0.4.0"
"@langchain/langgraph-cli@0.0.14-experimental.0":
"@langchain/langgraph-cli@0.0.14-experimental.1":
resolution:
{
integrity: sha512-31rdexCp4tR5849Qe3lstVai6VDm5+CFniGSQT08dghlITm4G0g35/jak9dFksavwLJ1NxeMTLuNJUfB5MocyA==,
integrity: sha512-S8Y7WrBPsNZR7wUyWj3De0sEdTTf+ipJf1lCrJho+moL9TVXUXUE+oFoMb1G/uHvt8Q/FCSE9BfadEg4JUb5MQ==,
}
engines: { node: ^18.19.0 || >=20.16.0 }
hasBin: true
"@langchain/langgraph-sdk@0.0.46-experimental.0":
"@langchain/langgraph-sdk@0.0.47-experimental.0":
resolution:
{
integrity: sha512-ADzrxf/Rx8I2fe2lcXgIsB/3sG0uTLsG8S+HIA749yveWo1SQrlcy9+Smkla16y9PWdQpKeztrhNGI5g0BKxDw==,
integrity: sha512-di60Pi2knQbe/sjOB3gNbNQNuTIhj0Yjls0SfEYeWDHirSN9heumPB/oxvwaxLBA8JKhuHg2h5lKUxAIT4b+aA==,
}
peerDependencies:
"@langchain/core": ">=0.2.31 <0.4.0"
@@ -5996,7 +5996,7 @@ snapshots:
transitivePeerDependencies:
- zod
"@langchain/langgraph-api@0.0.14-experimental.0(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(@langchain/langgraph-checkpoint@0.0.15(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2))))(@langchain/langgraph@0.2.49(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0))(openai@4.85.4(zod@3.24.2))(typescript@5.7.3)":
"@langchain/langgraph-api@0.0.14-experimental.1(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(@langchain/langgraph-checkpoint@0.0.15(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2))))(@langchain/langgraph@0.2.49(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0))(openai@4.85.4(zod@3.24.2))(typescript@5.7.3)":
dependencies:
"@babel/code-frame": 7.26.2
"@hono/node-server": 1.13.8(hono@4.7.2)
@@ -6032,11 +6032,11 @@ snapshots:
"@langchain/core": 0.3.41(openai@4.85.4(zod@3.24.2))
uuid: 10.0.0
"@langchain/langgraph-cli@0.0.14-experimental.0(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(@langchain/langgraph-checkpoint@0.0.15(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2))))(@langchain/langgraph@0.2.49(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0))(openai@4.85.4(zod@3.24.2))(typescript@5.7.3)":
"@langchain/langgraph-cli@0.0.14-experimental.1(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(@langchain/langgraph-checkpoint@0.0.15(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2))))(@langchain/langgraph@0.2.49(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0))(openai@4.85.4(zod@3.24.2))(typescript@5.7.3)":
dependencies:
"@babel/code-frame": 7.26.2
"@commander-js/extra-typings": 13.1.0(commander@13.1.0)
"@langchain/langgraph-api": 0.0.14-experimental.0(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(@langchain/langgraph-checkpoint@0.0.15(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2))))(@langchain/langgraph@0.2.49(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0))(openai@4.85.4(zod@3.24.2))(typescript@5.7.3)
"@langchain/langgraph-api": 0.0.14-experimental.1(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(@langchain/langgraph-checkpoint@0.0.15(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2))))(@langchain/langgraph@0.2.49(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0))(openai@4.85.4(zod@3.24.2))(typescript@5.7.3)
chokidar: 4.0.3
commander: 13.1.0
dedent: 1.5.3
@@ -6061,7 +6061,7 @@ snapshots:
- supports-color
- typescript
"@langchain/langgraph-sdk@0.0.46-experimental.0(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0)":
"@langchain/langgraph-sdk@0.0.47-experimental.0(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0)":
dependencies:
"@types/json-schema": 7.0.15
p-queue: 6.6.2
@@ -6075,7 +6075,7 @@ snapshots:
dependencies:
"@langchain/core": 0.3.41(openai@4.85.4(zod@3.24.2))
"@langchain/langgraph-checkpoint": 0.0.15(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))
"@langchain/langgraph-sdk": 0.0.46-experimental.0(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0)
"@langchain/langgraph-sdk": 0.0.47-experimental.0(@langchain/core@0.3.41(openai@4.85.4(zod@3.24.2)))(react@19.0.0)
uuid: 10.0.0
zod: 3.24.2
transitivePeerDependencies:

View File

@@ -4,7 +4,7 @@ import { getContentString } from "../utils";
import { BranchSwitcher, CommandBar } from "./shared";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { MarkdownText } from "../markdown-text";
import { LoadExternalComponent } from "@langchain/langgraph-sdk/react-ui/client";
import { LoadExternalComponent } from "@langchain/langgraph-sdk/react-ui";
import { cn } from "@/lib/utils";
import { ToolCalls, ToolResult } from "./tool-calls";
@@ -36,6 +36,7 @@ function CustomComponent({
assistantId="agent"
stream={thread}
message={customComponent}
meta={{ ui: customComponent }}
/>
)}
</div>

View File

@@ -4,7 +4,7 @@ import { type Message } from "@langchain/langgraph-sdk";
import type {
UIMessage,
RemoveUIMessage,
} from "@langchain/langgraph-sdk/react-ui/types";
} from "@langchain/langgraph-sdk/react-ui";
import { useQueryParam, StringParam } from "use-query-params";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
@@ -110,7 +110,9 @@ export const StreamProvider: React.FC<{ children: ReactNode }> = ({
className="flex flex-col gap-6 p-6 bg-muted/50"
>
<div className="flex flex-col gap-2">
<Label htmlFor="apiUrl">Deployment URL</Label>
<Label htmlFor="apiUrl">
Deployment URL<span className="text-rose-500">*</span>
</Label>
<p className="text-muted-foreground text-sm">
This is the URL of your LangGraph deployment. Can be a local, or
production deployment.
@@ -120,11 +122,14 @@ export const StreamProvider: React.FC<{ children: ReactNode }> = ({
name="apiUrl"
className="bg-background"
defaultValue={apiUrl ?? "http://localhost:2024"}
required
/>
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="assistantId">Assistant / Graph ID</Label>
<Label htmlFor="assistantId">
Assistant / Graph ID<span className="text-rose-500">*</span>
</Label>
<p className="text-muted-foreground text-sm">
This is the ID of the graph (can be the graph name), or
assistant to fetch threads from, and invoke when actions are
@@ -135,14 +140,17 @@ export const StreamProvider: React.FC<{ children: ReactNode }> = ({
name="assistantId"
className="bg-background"
defaultValue={assistantId ?? "agent"}
required
/>
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="apiKey">LangSmith API Key</Label>
<p className="text-muted-foreground text-sm">
This value is stored in your browser's local storage and is only
used to authenticate requests sent to your LangGraph server.
This is <strong>NOT</strong> required if using a local LangGraph
server. This value is stored in your browser's local storage and
is only used to authenticate requests sent to your LangGraph
server.
</p>
<PasswordInput
id="apiKey"