e30e75b5d4
Code Quality / Oxlint + Oxfmt (push) Waiting to run
Code Quality / Template Sync (push) Waiting to run
Code Quality / Build Changed Packages (push) Waiting to run
Code Quality / Test Changed Packages (push) Waiting to run
Deploy Expo Example / Deploy Production (push) Waiting to run
Deploy Ink Example / Deploy Production (push) Waiting to run
Python Tests / pytest (assistant-stream, 3.10) (push) Waiting to run
Python Tests / pytest (assistant-stream, 3.12) (push) Waiting to run
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Waiting to run
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Waiting to run
Deploy Shadcn Registry / Deploy Production (push) Waiting to run
Template Metrics / LOC + Bundle Size (push) Waiting to run
Changesets / Create Version PR (push) Has been cancelled
142 lines
4.2 KiB
Plaintext
142 lines
4.2 KiB
Plaintext
---
|
|
title: LangGraph Stockbroker Demo
|
|
description: Human-in-the-loop AI stockbroker built on LangGraph and assistant-ui — interrupt handling, tool approval, and an interactive React chat UI.
|
|
---
|
|
|
|
|
|
<div className="not-prose h-[600px]">
|
|
<iframe
|
|
title="Stockbroker example"
|
|
className="h-full w-full border-none"
|
|
src="https://assistant-ui-stockbroker.vercel.app/"
|
|
/>
|
|
</div>
|
|
|
|
## 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
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
research["Research Stock"] --> analyze["Analyze Data"] --> prepare["Prepare Trade"] --> approval["Human Approval<br/>(Required)"] --> execute["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:
|
|
|
|
```tsx
|
|
import { useLangGraphRuntime } from "@assistant-ui/react-langgraph";
|
|
import { AssistantRuntimeProvider } from "@assistant-ui/react";
|
|
import { Thread } from "@/components/assistant-ui/thread";
|
|
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 { remoteId } = await initialize();
|
|
return langGraphClient.runs.stream(
|
|
remoteId,
|
|
"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:
|
|
|
|
```python
|
|
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
|
|
|
|
```tsx
|
|
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}...</p>;
|
|
},
|
|
},
|
|
});
|
|
```
|
|
|
|
## Source
|
|
|
|
<SourceLink href="https://github.com/assistant-ui/assistant-ui-stockbroker" />
|