How to Build a transaction monitoring Agent Using LlamaIndex in TypeScript for fintech
A transaction monitoring agent watches payment and account activity, flags suspicious patterns, and turns raw events into actionable case notes for analysts. In fintech, that matters because you need fast detection for fraud and AML, but you also need traceability, explainability, and controls that survive audit.
Architecture
- •
Transaction ingestion layer
- •Pulls events from Kafka, SQS, a database stream, or a webhook.
- •Normalizes fields like
amount,currency,merchant_id,counterparty,country, andtimestamp.
- •
Policy and rules engine
- •Applies deterministic checks first: velocity limits, geo anomalies, structuring thresholds, sanctioned country lookups.
- •Keeps obvious cases out of the LLM path.
- •
LlamaIndex retrieval layer
- •Uses
VectorStoreIndexto retrieve internal policies, SAR filing guidance, typology docs, and past analyst notes. - •Grounds the agent in your own compliance knowledge base.
- •Uses
- •
Agent reasoning layer
- •Uses a
ReActAgentor tool-based workflow to classify the event, explain why it is risky, and decide next actions. - •Produces structured output for downstream case management.
- •Uses a
- •
Case management sink
- •Writes alerts into your investigation system with evidence, policy references, and confidence scores.
- •Stores every prompt, retrieved chunk, and decision for audit.
- •
Observability and governance
- •Tracks latency, retrieval quality, false positives, and model drift.
- •Enforces redaction, residency boundaries, and approval gates for regulated workflows.
Implementation
1) Install LlamaIndex for TypeScript and define your transaction shape
You want a typed input contract before the agent sees anything. That makes policy checks explicit and keeps the LLM away from raw payload chaos.
npm install llamaindex zod
import { z } from "zod";
export const TransactionSchema = z.object({
transactionId: z.string(),
accountId: z.string(),
amount: z.number().positive(),
currency: z.string().length(3),
merchantId: z.string(),
country: z.string(),
counterpartyCountry: z.string().optional(),
timestamp: z.string(),
});
export type Transaction = z.infer<typeof TransactionSchema>;
export function basicRiskScore(txn: Transaction): number {
let score = 0;
if (txn.amount >= 10000) score += 30;
if (txn.country !== "US") score += 10;
if (txn.counterpartyCountry && txn.counterpartyCountry !== txn.country) score += 10;
return score;
}
2) Load compliance docs into a VectorStoreIndex
This is where LlamaIndex earns its keep. Put your AML policy PDFs, escalation playbooks, sanctions guidance, and analyst SOPs into an index so the agent can cite internal rules instead of inventing them.
import {
Document,
VectorStoreIndex,
} from "llamaindex";
async function buildComplianceIndex() {
const docs = [
new Document({
text: "If a customer sends multiple transactions just below the reporting threshold within a short window, escalate as possible structuring.",
metadata: { source: "aml_policy_v4", jurisdiction: "US" },
}),
new Document({
text: "Transactions involving sanctioned jurisdictions must be blocked or reviewed per screening policy.",
metadata: { source: "sanctions_playbook", jurisdiction: "US" },
}),
new Document({
text: "Analysts must document reason codes and supporting evidence for every alert closure.",
metadata: { source: "case_management_sop", jurisdiction: "US" },
}),
];
return await VectorStoreIndex.fromDocuments(docs);
}
3) Create tools around deterministic checks and retrieval
Use tools for the parts that should not be “creative.” The LLM should call a risk scorer and retrieve policy snippets; it should not compute compliance logic itself.
import {
FunctionTool,
} from "llamaindex";
const riskTool = FunctionTool.from(
async ({ amount }: { amount: number }) => {
if (amount >= 10000) return "high";
if (amount >= 2500) return "medium";
return "low";
},
{
name: "risk_tool",
description: "Classifies transaction amount severity using fixed thresholds.",
parameters: {
type: "object",
properties: {
amount: { type: "number" },
},
required: ["amount"],
},
}
);
4) Assemble the agent with LlamaIndex classes
The actual pattern is to combine retrieval plus tools inside an agent. In TypeScript LlamaIndex exposes ReActAgent, which works well when you want reasoning traces plus controlled tool use.
import {
OpenAI,
} from "@llamaindex/openai";
import {
ReActAgent,
QueryEngineTool,
ContextChatEngine,
} from "llamaindex";
async function main() {
const index = await buildComplianceIndex();
const queryEngine = index.asQueryEngine({ similarityTopK: 2 });
const complianceTool = QueryEngineTool.fromDefaults({
queryEngine,
name: "compliance_lookup",
description:
"Searches internal AML/sanctions/case management policies for relevant guidance.",
});
const llm = new OpenAI({ model: "gpt-4o-mini" });
const agent = new ReActAgent({
tools: [riskTool, complianceTool],
llm,
});
const txn = TransactionSchema.parse({
transactionId: "txn_123",
accountId: "acct_456",
amount: 9800,
currency: "USD",
merchantId: "m_789",
country: "US",
counterpartyCountry: "NG",
timestamp: new Date().toISOString(),
});
const response = await agent.chat({
message:
`Review this transaction for AML/fraud risk:
${JSON.stringify(txn)}`,
});
console.log(response.toString());
}
main();
If you want a chat-style review interface for analysts instead of a single-shot decision API, wrap the same index in a ContextChatEngine. That gives you grounded follow-up questions without rebuilding retrieval each time.
Production Considerations
- •
Keep PII out of prompts unless it is strictly required
Redact names, full account numbers, addresses, and free-text memo fields before sending data to the model. For fintech workloads this is not optional; it reduces exposure under privacy regimes and shrinks blast radius if logs leak.
- •
Separate policy retrieval by jurisdiction
A US AML policy is not the same as an EU one. Partition indexes by region or business line so the agent does not mix rules across residency boundaries or legal entities.
- •
Log every decision path
Persist input hash, retrieved chunks, tool outputs, final answer, model version, and timestamps. Auditors will ask why an alert was raised or closed; you need replayable evidence.
- •
Put hard gates around automated actions
- •The agent can recommend “escalate,” “hold,” or “review.”
- •It should not freeze funds or file regulatory reports without human approval unless your controls explicitly allow it.
Common Pitfalls
- •
Using the LLM as the first line of defense
Don’t ask the model to detect obvious threshold breaches or sanctions hits from scratch. Run deterministic rules first; use LlamaIndex for context enrichment and explanation.
- •
Building one global knowledge base
The result is bad retrieval and bad governance. Split policies by product line, geography, and entity so answers stay jurisdictionally correct.
- •Skipping evaluation on historical cases
If you do not test against prior fraud alerts and closed SAR/STR cases, you will ship a noisy system. Measure precision at alert level, analyst acceptance rate, and false positive reduction before production rollout.
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
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