How to Build a transaction monitoring Agent Using LlamaIndex in TypeScript for wealth management

By Cyprian AaronsUpdated 2026-04-21
transaction-monitoringllamaindextypescriptwealth-management

A transaction monitoring agent for wealth management watches client activity, flags behavior that looks inconsistent with mandate, suitability, or policy, and turns raw events into reviewable cases. It matters because the cost of missing suspicious activity is not just financial loss; it is regulatory exposure, audit pain, and broken trust with high-net-worth clients.

Architecture

  • Transaction ingestion layer

    • Pulls trades, cash movements, transfers, and corporate actions from OMS/PMS/core banking APIs.
    • Normalizes records into a consistent schema with client, account, instrument, timestamp, amount, and jurisdiction.
  • Policy and rules context

    • Stores firm-specific surveillance rules: velocity checks, structuring patterns, unusual destination accounts, concentration drift.
    • Keeps compliance thresholds versioned so every alert can be traced back to the rule set in force.
  • LlamaIndex retrieval layer

    • Uses VectorStoreIndex to retrieve relevant policies, client profile notes, suitability documents, and prior case summaries.
    • Grounds the agent in firm knowledge instead of asking an LLM to infer from transaction data alone.
  • Agent orchestration

    • Uses ReActAgent or a tool-based workflow to inspect transactions, query policy context, and produce an escalation decision.
    • Returns structured outputs for review queues and downstream case management systems.
  • Audit and evidence store

    • Persists the input payloads, retrieved context, model output, and final decision.
    • Required for explainability during compliance reviews and regulator exams.
  • Case management integration

    • Opens cases in the AML/surveillance queue when risk exceeds threshold.
    • Sends analyst-ready summaries with supporting evidence and links back to source records.

Implementation

1. Install the TypeScript packages

Use the TypeScript SDK plus a local embedding model or hosted embeddings. For production in wealth management, keep embeddings and raw data inside your approved region.

npm install llamaindex zod

2. Build the indexed compliance knowledge base

The agent needs policy text and prior case notes available through retrieval. VectorStoreIndex.fromDocuments() is the core pattern here.

import {
  Document,
  VectorStoreIndex,
  Settings,
} from "llamaindex";

async function buildComplianceIndex() {
  const docs = [
    new Document({
      text: `
        Policy: Flag outbound transfers over $250k to new beneficiaries within 30 days of account opening.
        Escalate if transfer destination is outside approved jurisdictions.
      `,
      metadata: { type: "policy", version: "2025.01" },
    }),
    new Document({
      text: `
        Case note: A client moved funds from managed portfolio cash into an external account,
        then purchased illiquid private assets inconsistent with stated liquidity needs.
      `,
      metadata: { type: "case_note", caseId: "CASE-1842" },
    }),
  ];

  // In production configure your embedding model here.
  const index = await VectorStoreIndex.fromDocuments(docs);

  return index;
}

3. Expose tools for transaction inspection and policy retrieval

The agent should not guess. Give it explicit tools for looking up policy context and evaluating transaction features. Use FunctionTool.from() so the agent can call deterministic code.

import {
  FunctionTool,
  ReActAgent,
} from "llamaindex";

type Transaction = {
  id: string;
  clientId: string;
  amount: number;
  currency: string;
  direction: "inbound" | "outbound";
  destinationCountry?: string;
  accountAgeDays: number;
};

function scoreTransaction(txn: Transaction) {
  let score = 0;

  if (txn.direction === "outbound" && txn.amount >= 250000) score += 40;
  if (txn.accountAgeDays < 30 && txn.direction === "outbound") score += 25;
  if (txn.destinationCountry && !["US", "GB", "CA", "SG"].includes(txn.destinationCountry)) score += 20;

   return {
    riskScore: score,
    reasons: [
      txn.amount >= 250000 ? "High-value transfer" : null,
      txn.accountAgeDays < 30 ? "New account activity" : null,
      txn.destinationCountry && !["US", "GB", "CA", "SG"].includes(txn.destinationCountry)
        ? `Destination outside approved jurisdictions (${txn.destinationCountry})`
        : null,
    ].filter(Boolean),
   };
}

async function createAgent(index: VectorStoreIndex) {
  const policyQueryEngine = index.asQueryEngine();

  const tools = [
    FunctionTool.from(
      async ({ query }: { query: string }) => {
        const response = await policyQueryEngine.query({ query });
        return response.toString();
      },
      {
        name: "search_policy_context",
        description: "Retrieve relevant compliance policies and prior case notes.",
      }
    ),
    FunctionTool.from(
      async ({ transaction }: { transaction: Transaction }) => {
        return JSON.stringify(scoreTransaction(transaction));
      },
      {
        name: "score_transaction",
        description:
          "Apply deterministic surveillance heuristics to a transaction.",
      }
    ),
  ];

  return ReActAgent.fromTools({
    tools,
    systemPrompt:
      "You are a transaction monitoring agent for wealth management. Return concise findings with audit-ready rationale.",
    maxIterations: 4,
    verbose: true,
    llmOptions: {},
   });
}

4. Run the agent on a live transaction and persist the result

Keep the output structured enough for analysts. The point is not just classification; it is evidence-backed escalation.

async function main() {
   const index = await buildComplianceIndex();
   const agent = await createAgent(index);

   const txn = {
     id: "TXN-90021",
     clientId: "CL-7781",
     amount: 375000,
     currency: "USD",
     direction: "outbound",
     destinationCountry: "AE",
     accountAgeDays: false ? false : true ? false : false as any
   } as unknown as Transaction;

   const prompt = `
     Review this transaction for wealth management surveillance:
     ${JSON.stringify(txn)}

     Decide whether to escalate. Include:
     - risk assessment
     - policy references
     - recommended action
   `;

   const result = await agent.chat({ messageTextContentOrChatHistoryOrMessagesArrayOrChatMessageLikeObjectToStringOrAny(prompt) as any });
   console.log(result.toString());
}

main().catch(console.error);

That last call shape will vary by package version; in current LlamaIndex TypeScript builds you typically call agent.chat("...") or pass a chat message object supported by your installed version. The pattern stays the same:

  • retrieve policy context
  • score deterministically
  • let the LLM explain the decision with citations from retrieved material

Production Considerations

  • Data residency

    Keep client PII, portfolio data, and embeddings in-region. Wealth management firms often have strict cross-border controls that apply to both source data and vector stores.

  • Auditability

    Persist every run with transaction payload, retrieved documents, tool outputs, prompt version, model version, and final disposition. If you cannot reproduce an alert later, it is not production-ready.

  • Guardrails

    Do not let the model make autonomous filing decisions. Use it to recommend escalation or no-action; final SAR/STR decisions should remain with compliance staff under documented approval workflows.

  • Monitoring

Measure false positives by desk/client segment/jurisdiction. A surveillance model that floods advisors with noise will be ignored long before it gets tuned properly.

Common Pitfalls

  1. Using only an LLM without deterministic scoring

    • This creates inconsistent alerts and weak audit trails.
    • Fix it by combining rule-based scoring with LlamaIndex retrieval for explanation.
  2. Indexing sensitive data without residency controls

    • Client notes and transaction histories often contain regulated personal data.
    • Fix it by restricting storage region, encrypting vectors at rest, and redacting unnecessary fields before indexing.
  3. Returning free-form prose instead of reviewable cases

    • Analysts need structured reasons tied to policy language.
    • Fix it by standardizing outputs like riskScore, reasons, policyReferences, and recommendedAction.
  4. Treating alerts as generic AML events

    • Wealth management has extra context around mandate drift, liquidity needs, tax-sensitive transfers, offshore structures, and trusted contacts.
    • Fix it by encoding those domain signals into both your rules engine and your retrieval corpus.

Keep learning

By Cyprian Aarons, AI Consultant at Topiax.

Want the complete 8-step roadmap?

Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.

Get the Starter Kit

Related Guides