# LangGraph Stockbroker Demo
URL: /examples/stockbroker

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

> For AI agents: a documentation index is available at [llms.txt](/llms.txt). Use `.md` for canonical markdown pages; `.mdx` is kept as a backwards-compatible alias on supported URL paths.

## 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

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](https://github.com/assistant-ui/assistant-ui-stockbroker)