How the Frontend Communicates with the Backend API in the AI Hedge Fund Project

The React frontend communicates with the FastAPI backend exclusively through standard HTTP requests using the browser fetch API, leveraging REST endpoints for CRUD operations and Server-Sent Events (SSE) for real-time streaming of hedge fund simulations.

The virattt/ai-hedge-fund repository implements a clean separation between its React-based Vite frontend and Python FastAPI backend. Understanding how the frontend communicates with the backend API is essential for developers extending the platform's real-time financial analysis capabilities.

Architecture Overview: REST and Server-Sent Events

The communication architecture follows a service-oriented pattern where the frontend encapsulates all network logic in dedicated TypeScript modules. The backend exposes two distinct interaction models:

  • RESTful JSON endpoints for synchronous CRUD operations on flows and configuration
  • SSE streaming endpoints for asynchronous, long-running hedge fund executions and backtests

This dual approach allows the UI to maintain responsive interactions while receiving progressive updates from computationally intensive agent simulations.

Frontend Service Layer

All API interactions originate from the app/frontend/src/services directory, which provides typed abstractions over raw fetch calls.

Flow Management via flow-service.ts

The flow-service.ts module handles CRUD operations for user-defined agent graphs. Each method constructs URLs against ${API_BASE_URL}/flows/ and returns parsed JSON promises:

// app/frontend/src/services/flow-service.ts
export const flowService = {
  async getFlows(): Promise<Flow[]> {
    const r = await fetch(`${API_BASE_URL}/flows/`);
    if (!r.ok) throw new Error('Failed to fetch flows');
    return r.json();  // FastAPI returns List[FlowSummaryResponse]
  },

  async createFlow(data: CreateFlowRequest): Promise<Flow> {
    const r = await fetch(`${API_BASE_URL}/flows/`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data),
    });
    if (!r.ok) throw new Error('Failed to create flow');
    return r.json();  // FastAPI returns FlowResponse
  },
  
  // updateFlow, deleteFlow, duplicateFlow follow identical patterns
};

Hedge Fund Execution via api.ts

The api.ts service contains the critical runHedgeFund method, which initiates simulations and processes SSE streams. This function posts a HedgeFundRequest payload to /hedge-fund/run, then consumes the StreamingResponse using a ReadableStream reader:

// app/frontend/src/services/api.ts
export const api = {
  runHedgeFund: (
    params: HedgeFundRequest,
    nodeContext: ReturnType<typeof useNodeContext>,
    flowId: string | null = null
  ): (() => void) => {
    const controller = new AbortController();
    const { signal } = controller;

    fetch(`${API_BASE_URL}/hedge-fund/run`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(params),
      signal,
    })
      .then(r => {
        if (!r.ok) throw new Error(`HTTP ${r.status}`);
        const reader = r.body?.getReader();
        if (!reader) throw new Error('No reader');
        const decoder = new TextDecoder();
        let buffer = '';

        const process = async () => {
          while (true) {
            const { done, value } = await reader.read();
            if (done) break;
            buffer += decoder.decode(value, { stream: true });
            const events = buffer.split('\n\n');
            buffer = events.pop() || '';

            for (const txt of events) {
              const type = txt.match(/^event: (.+)$/m)?.[1];
              const data = txt.match(/^data: (.+)$/m)?.[1];
              if (!type || !data) continue;

              const ev = JSON.parse(data);
              switch (type) {
                case 'start':
                  nodeContext.resetAllNodes(flowId);
                  break;
                case 'progress':
                  const base = ev.agent.replace('_agent', '');
                  const nodeId = params.graph_nodes
                    .find(n => n.id.replace(/\d+_/, '') === base) || base;
                  nodeContext.updateAgentNode(flowId, nodeId, {
                    status: ev.status === 'Done' ? 'COMPLETE' : 'IN_PROGRESS',
                    ticker: ev.ticker,
                    message: ev.status,
                    analysis: ev.analysis,
                    timestamp: ev.timestamp,
                  });
                  break;
                case 'complete':
                  nodeContext.setOutputNodeData(flowId, ev.data);
                  break;
                case 'error':
                  nodeContext.updateAgentNode(flowId, 'portfolio-start', {
                    status: 'ERROR',
                    message: ev.message,
                  });
                  break;
              }
            }
          }
        };
        process();
      })
      .catch(err => console.error('Run error:', err));

    return () => {
      controller.abort();
      if (flowId) flowConnectionManager.setConnection(flowId, { state: 'idle' });
    };
  },
};

Backtesting via backtest-api.ts

The backtest-api.ts module mirrors the hedge fund execution pattern, posting to /hedge-fund/backtest and consuming SSE streams for progressive backtest updates.

Backend API Implementation

The FastAPI backend in app/backend/ exposes corresponding endpoints that generate JSON responses or SSE streams.

FastAPI Routers

The backend organizes routes into dedicated modules:

  • app/backend/routes/hedge_fund.py: Defines POST /hedge-fund/run and POST /hedge-fund/backtest. These endpoints receive the request body, construct the agent graph, and return StreamingResponse objects that emit SSE events.
  • app/backend/routes/flows.py: Implements standard REST endpoints (GET /flows/, POST /flows/, PUT /flows/{id}, DELETE /flows/{id}) used by the frontend flow service.

Server-Sent Events Streaming

For long-running operations, the backend utilizes FastAPI's StreamingResponse to push real-time updates. The implementation in hedge_fund.py follows this pattern:

  1. StartEvent: Emitted immediately upon request validation
  2. ProgressUpdateEvent: Pushed as agents complete analysis steps, containing agent, ticker, status, and analysis fields
  3. CompleteEvent: Signifies successful graph execution with final portfolio decisions
  4. ErrorEvent: Transmits exception details if execution fails

The frontend parses these events using the event: and data: SSE protocol fields, updating the React node context to render real-time agent progress.

Real-Time Data Flow Example

When a user initiates a hedge fund simulation, the following communication sequence occurs:

  1. The UI invokes api.runHedgeFund(params, nodeContext, flowId) from app/frontend/src/services/api.ts
  2. The service POSTs the HedgeFundRequest JSON payload to http://localhost:8000/hedge-fund/run
  3. FastAPI in app/backend/routes/hedge_fund.py spawns run_graph_async and yields a StartEvent SSE message
  4. As agents process tickers, the backend pushes ProgressUpdateEvent objects to an async queue, which the SSE generator reads and formats as event: progress messages
  5. The frontend's ReadableStream reader (response.body?.getReader()) consumes chunks, decoding them with TextDecoder and splitting on double newlines to extract individual SSE events
  6. For each progress event, the frontend maps the backend agent name to the corresponding React Flow node ID and calls nodeContext.updateAgentNode() to update the visual status
  7. Upon receiving the complete event, the frontend invokes nodeContext.setOutputNodeData() to render the final portfolio decisions

Error Handling and Connection Management

Both frontend and backend implement robust connection lifecycle management:

  • AbortController: Every streaming request creates an AbortController. If the user navigates away or clicks cancel, the frontend calls controller.abort(), which closes the HTTP connection.
  • Client Disconnect Detection: The backend monitors await request.receive() for http.disconnect messages. When detected, it cancels the background asyncio task to free resources.
  • Error Propagation: Backend exceptions generate event: error SSE messages containing the error details, which the frontend catches in the switch statement to update node statuses to ERROR state.

Summary

  • The React frontend communicates with the FastAPI backend through standard HTTP requests using the browser fetch API.
  • CRUD operations on flows use synchronous JSON endpoints defined in app/backend/routes/flows.py and consumed by app/frontend/src/services/flow-service.ts.
  • Real-time simulations leverage Server-Sent Events (SSE) via StreamingResponse in app/backend/routes/hedge_fund.py, consumed by app/frontend/src/services/api.ts using ReadableStream readers.
  • The frontend maps SSE events (start, progress, complete, error) to React context updates via nodeContext methods, enabling live visualization of agent execution.
  • Both sides implement graceful abort mechanisms using AbortController and FastAPI disconnect detection to manage long-running connection lifecycles.

Frequently Asked Questions

What protocol does the AI Hedge Fund frontend use to receive real-time updates from the backend?

The frontend uses Server-Sent Events (SSE), a standard HTTP-based protocol where the backend streams text events using the text/event-stream content type. The frontend consumes these through the fetch API's ReadableStream interface, parsing event: and data: fields to update the React UI progressively.

How does the frontend handle cancellation of a running hedge fund simulation?

The frontend creates an AbortController for each streaming request in app/frontend/src/services/api.ts. When a user cancels the operation, the code calls controller.abort(), which closes the HTTP connection. The FastAPI backend detects this disconnect via http.disconnect messages and terminates the background asyncio task to prevent resource leaks.

Where are the API endpoints defined in the backend?

The FastAPI routes are modularized under app/backend/routes/. The flows.py router handles CRUD operations for agent graphs at /flows/, while hedge_fund.py defines the streaming endpoints /hedge-fund/run and /hedge-fund/backtest that return StreamingResponse objects for SSE communication.

Can the backend API URL be configured for different environments?

Yes. The frontend defaults to http://localhost:8000 but supports environment-specific configuration through the VITE_API_URL environment variable. This value is exposed via Vite's import.meta.env mechanism and used throughout the service layer in app/frontend/src/services/ to construct full endpoint URLs.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →