Examples

LangGraph Stockbroker Demo

Human-in-the-loop AI stockbroker built on LangGraph and assistant-ui. Interrupt handling, tool approval, and an interactive React chat UI.

source

fig. 01live

Overview

A stock trading assistant built with LangGraph that demonstrates human-in-the-loop workflows. The assistant can research stocks, analyze market data, and prepare trades, but requires explicit user approval before executing any transactions.

Features

  • Multi-step Workflows: Complex operations broken into LangGraph nodes
  • Human-in-the-Loop: Critical actions require user confirmation
  • State Persistence: Workflow state maintained across interactions
  • Tool Integration: Connected to stock market APIs
  • Approval UI: Clear interface for reviewing and approving actions
  • Audit Trail: Complete history of AI decisions and user approvals

How It Works

Research Stock Analyze Data Prepare Trade Human Approval(Required) Execute Trade
  1. User asks about a stock (e.g., "Should I buy AAPL?")
  2. AI researches the stock and analyzes market data
  3. AI prepares a trade recommendation
  4. Workflow pauses for user approval
  5. User reviews and approves/rejects the trade
  6. If approved, trade executes

Integration

Uses @assistant-ui/react-langgraph for LangGraph integration:

import { useLangGraphRuntime } from "@assistant-ui/react-langgraph";
import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { Thread } from "@/components/assistant-ui/elements/thread.aui";
import { Client } from "@langchain/langgraph-sdk";

const langGraphClient = new Client({
  apiUrl: process.env.LANGGRAPH_API_URL,
});

export default function StockbrokerChat() {
  const runtime = useLangGraphRuntime({
    stream: async (messages, { abortSignal, initialize }) => {
      const { externalId } = await initialize();
      if (!externalId) throw new Error("Thread not found");
      return langGraphClient.runs.stream(
        externalId,
        "stockbroker-assistant",
        {
          input: { messages },
          streamMode: "messages",
        },
        { signal: abortSignal }
      );
    },
    create: async () => {
      const { thread_id } = await langGraphClient.threads.create();
      return { externalId: thread_id };
    },
  });

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      <Thread />
    </AssistantRuntimeProvider>
  );
}

Human-in-the-Loop Pattern

In your LangGraph workflow, define an interrupt point:

from langgraph.graph import StateGraph
from langgraph.checkpoint.memory import MemorySaver

def prepare_trade(state):
    # Prepare trade details
    return {"trade": trade_details, "needs_approval": True}

def execute_trade(state):
    # Execute the approved trade
    return {"result": "Trade executed"}

# Graph with interrupt
graph = StateGraph(State)
graph.add_node("prepare", prepare_trade)
graph.add_node("execute", execute_trade)

# Interrupt before execute for human approval
graph.add_edge("prepare", "execute")
app = graph.compile(interrupt_before=["execute"], checkpointer=MemorySaver())

Approval UI Component

const toolkit = defineToolkit({
  execute_trade: {
    type: "backend",
    render: ({ args, status, addResult }) => {
    if (status.type === "requires-action") {
      return (
        <div className="rounded-lg border p-4">
          <h3>Trade Approval Required</h3>
          <p>Buy {args.shares} shares of {args.symbol}</p>
          <div className="flex gap-2">
            <Button onClick={() => addResult({ approved: true })}>
              Approve
            </Button>
            <Button onClick={() => addResult({ approved: false })}>
              Reject
            </Button>
          </div>
        </div>
      );
    }
    return <p>Trade {status.type}...</p>;
  },
  },
});

Source

View full source on GitHub