logoassistant-ui

LangGraph Stockbroker

A stockbroker showing human in the loop with LangGraph

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. This pattern is essential for applications where AI assists with high-stakes decisions.

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  │────▶│   Analyze   │────▶│   Prepare   │
│    Stock    │     │    Data     │     │    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, Thread } from "@assistant-ui/react";
import { Client } from "@langchain/langgraph-sdk";

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

export default function StockbrokerChat() {
  const runtime = useLangGraphRuntime({
    threadId,
    stream: async (messages, { abortSignal }) => {
      return langGraphClient.runs.stream(
        threadId,
        "stockbroker-assistant",
        {
          input: { messages },
          streamMode: "messages",
        },
        { signal: abortSignal }
      );
    },
    onSwitchToNewThread: async () => {
      const { thread_id } = await langGraphClient.threads.create();
      return 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 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")
graph.set_interrupt_before(["execute"])

Approval UI Component

const TradeApproval = makeAssistantToolUI({
  toolName: "execute_trade",
  render: ({ args, status, addResult }) => {
    if (status === "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}...</p>;
  },
});

Source

View full source on GitHub