Introduction
In the financial sector, data is not just information; it is evidence. Whether it is a 50-page loan agreement or a stream of cryptic transaction narratives like "POS PURCHASE STARBUCKS #291 NYC" or "WIRE TRANSFER TO SHELL CO LTD", the way we slice this data determines the success of our AI systems.
Standard "fixed-size" chunking splitting text every 500 tokens is disastrous for financial documents. It breaks legal clauses mid-sentence, separates amounts from their currencies, and severs the link between a transaction ID and its beneficiary. To build robust Graph RAG (Retrieval-Augmented Generation) systems for finance, we must employ advanced chunking strategies that respect semantic boundaries, hierarchical structures, and entity relationships. This article explores these strategies and provides a complete Proof of Concept (POC) using an Enterprise Multi-Agent LangGraph system.
The Challenge: Why Standard Chunking Fails in Finance
Financial data has unique characteristics that break naive NLP pipelines:
Contextual Dependency: A transaction narrative "Payment for Inv #992" is meaningless without the preceding chunk containing the invoice details.
Entity Density: Financial texts are dense with Named Entities (NEs) like IBANs, SWIFT codes, and Legal Entity Identifiers (LEIs). Splitting these across chunks destroys the graph's ability to link nodes.
Hierarchical Structure: Loan agreements have nested clauses (Section 1.2.a). Standard chunking ignores this hierarchy, leading to retrieval of isolated fragments rather than complete logical units.
Strategic Chunking: Semantic, Hierarchical, and Entity-Aware Approaches
To solve these issues, we implement a hybrid chunking strategy:
Semantic Chunking: Uses embedding similarity to detect topic shifts. We only split text when the semantic meaning changes significantly, ensuring that related financial concepts stay together.
Hierarchical/Parent-Child Chunking: We create large "parent" chunks that preserve the document structure (e.g., an entire clause) and smaller "child" chunks for precise vector retrieval. When a child is retrieved, the system returns the parent for full context.
Entity-Aware Boundary Detection: We use Named Entity Recognition (NER) to ensure that critical financial entities (Amounts, Dates, Counterparties) are never split across two chunks. If a split point falls inside an entity, we shift the boundary.
Real-Time Use Case: Anti-Money Laundering (AML) Investigation
The Scenario: An AML analyst is investigating a series of suspicious wire transfers. The data consists of unstructured transaction narratives and structured ledger entries.
The Problem: The narrative "Funds for Project Alpha" is linked to a shell company in the ledger, but the definition of "Project Alpha" (a known front for illicit activity) is buried in a separate 100-page compliance report.
The Solution: Our Graph RAG system uses Entity-Aware Chunking to keep the transaction ID and counterparty together. It then uses Semantic Retrieval to find the "Project Alpha" definition in the compliance report. Finally, it constructs a knowledge graph linking the Transaction -> Counterparty -> Project Alpha -> Illicit Activity.
Enterprise Multi-Agent LangGraph Architecture
Our architecture uses LangGraph to orchestrate agents that handle different stages of the chunking and retrieval process:
Chunking Agent: Applies semantic and entity-aware logic to raw financial documents.
Graph Construction Agent: Extracts entities from chunks to build a local knowledge graph.
RAG Retrieval Agent: Queries the vector store and graph database to find connected risks.
Investigation Agent: Synthesizes the findings into an AML alert.

Step-by-Step POC Implementation
Step 1: Defining State, Memory, and Chunking Logic
We define the state to carry raw documents, processed chunks, and graph relationships.
# backend/graph_state.py
from typing import TypedDict, List, Annotated, Dict
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.memory import MemorySaver
class AMLState(TypedDict):
raw_documents: List[str]
processed_chunks: List[Dict] # {content, metadata, entities}
graph_relationships: List[str]
retrieved_context: List[str]
final_alert: str
agent_trace: Annotated[List[str], "Audit trail"]
memory = MemorySaver()
Step 2: Building the Multi-Agent Graph RAG Workflow
We simulate advanced chunking and graph construction.
# backend/agents.py
from .graph_state import AMLState, memory
import re
def chunking_agent(state: AMLState):
"""Applies Entity-Aware and Semantic Chunking."""
docs = state["raw_documents"]
chunks = []
for doc in docs:
# Simulate Entity-Aware Splitting: Keep sentences with entities intact
# In production, use spaCy or NLTK for NER-based boundary detection
sentences = re.split(r'(?<=[.!?])\s+', doc)
current_chunk = ""
for sent in sentences:
# Simple heuristic: if sentence contains an entity (uppercase word > 2 chars), keep it whole
if len(current_chunk) + len(sent) < 200: # Small size for demo
current_chunk += sent + " "
else:
if current_chunk:
# Extract mock entities
entities = [w for w in current_chunk.split() if w.isupper() and len(w) > 3]
chunks.append({"content": current_chunk.strip(), "entities": entities})
current_chunk = sent + " "
if current_chunk:
entities = [w for w in current_chunk.split() if w.isupper() and len(w) > 3]
chunks.append({"content": current_chunk.strip(), "entities": entities})
state["agent_trace"].append(f"Chunking Agent: Created {len(chunks)} entity-aware chunks.")
return {"processed_chunks": chunks}
def graph_construction_agent(state: AMLState):
"""Builds local graph relationships from chunks."""
chunks = state["processed_chunks"]
relationships = []
# Simulate Graph Extraction: Link entities found in same chunk
for i, chunk in enumerate(chunks):
ents = chunk["entities"]
if len(ents) > 1:
for j in range(len(ents) - 1):
relationships.append(f"({ents[j]})--[:RELATED_TO]-->({ents[j+1]})")
state["agent_trace"].append(f"Graph Agent: Extracted {len(relationships)} relationships.")
return {"graph_relationships": relationships}
def rag_retrieval_agent(state: AMLState):
"""Retrieves context based on graph paths and vector similarity."""
# Mock Vector DB & Graph DB lookup
context = [
"Compliance Report: 'Project Alpha' is a flagged high-risk initiative linked to sanctioned entities.",
"Ledger Note: Wire transfer to SHELL CO LTD was authorized under Project Alpha."
]
state["agent_trace"].append("RAG Agent: Retrieved compliance context via graph traversal.")
return {"retrieved_context": context}
def investigation_agent(state: AMLState):
"""Synthesizes findings into an AML alert."""
context = state["retrieved_context"]
relationships = state["graph_relationships"]
alert = " HIGH RISK AML ALERT \n"
alert += f"Detected Relationships: {', '.join(relationships)}\n"
alert += "Contextual Findings:\n"
for c in context:
alert += f"- {c}\n"
alert += "Recommendation: Freeze assets and file SAR (Suspicious Activity Report)."
state["agent_trace"].append("Investigation Agent: Final AML alert generated.")
return {"final_alert": alert}
# Build the Graph
workflow = StateGraph(AMLState)
workflow.add_node("chunker", chunking_agent)
workflow.add_node("graph_builder", graph_construction_agent)
workflow.add_node("rag_retriever", rag_retrieval_agent)
workflow.add_node("investigator", investigation_agent)
workflow.set_entry_point("chunker")
workflow.add_edge("chunker", "graph_builder")
workflow.add_edge("graph_builder", "rag_retriever")
workflow.add_edge("rag_retriever", "investigator")
workflow.add_edge("investigator", END)
app = workflow.compile(checkpointer=memory)
Step 3: The FastAPI Backend
# backend/main.py
from fastapi import FastAPI
from pydantic import BaseModel
from .agents import app
app_api = FastAPI(title="Financial Graph RAG Chunking POC")
class AMLRequest(BaseModel):
documents: List[str]
thread_id: str = "aml_investigation_01"
@app_api.post("/investigate-transaction")
async def investigate_transaction(req: AMLRequest):
config = {"configurable": {"thread_id": req.thread_id}}
initial_state = {
"raw_documents": req.documents,
"processed_chunks": [],
"graph_relationships": [],
"retrieved_context": [],
"final_alert": "",
"agent_trace": []
}
final_state = app.invoke(initial_state, config)
return {
"alert": final_state["final_alert"],
"chunks_created": len(final_state["processed_chunks"]),
"relationships_found": final_state["graph_relationships"],
"agent_trace": final_state["agent_trace"]
}
Step 4: The Streamlit Frontend
# frontend/app.py
import streamlit as st
import requests
st.set_page_config(page_title="Financial Graph RAG Investigator", layout="wide")
st.title(" AML Investigation: Advanced Chunking & Graph RAG")
st.sidebar.header("Input Documents")
doc1 = st.text_area("Transaction Narrative", "Wire transfer to SHELL CO LTD for Project Alpha. Ref: INV-992.")
doc2 = st.text_area("Compliance Excerpt", "Project Alpha is a high-risk initiative involving sanctioned regions. All transfers require manual review.")
if st.sidebar.button("Run Investigation"):
with st.spinner("Chunking, Graph Building, and Retrieving..."):
response = requests.post(
"http://localhost:8000/investigate-transaction",
json={"documents": [doc1, doc2], "thread_id": "aml_thread_01"}
)
if response.status_code == 200:
data = response.json()
col1, col2 = st.columns(2)
with col1:
st.subheader("Final AML Alert")
st.error(data["alert"])
with col2:
st.subheader("Processing Metrics")
st.metric("Entity-Aware Chunks Created", data["chunks_created"])
st.write("**Graph Relationships Found:**")
for rel in data["relationships_found"]:
st.code(rel)
st.subheader("Agent Audit Trail")
for trace in data["agent_trace"]:
st.info(f" {trace}")
else:
st.error("Failed to connect to investigation engine.")Conclusion
In financial AI, the quality of your output is strictly limited by the quality of your input processing. Standard chunking strategies are insufficient for the nuanced, entity-dense nature of financial documents and transaction narratives. By adopting Entity-Aware and Semantic Chunking within a Graph RAG framework, we ensure that critical financial relationships are preserved and retrievable.
Our Multi-Agent LangGraph POC demonstrates how specialized agents can handle the complexity of chunking, graph construction, and contextual retrieval, resulting in highly accurate and explainable AML investigations. This approach transforms raw, fragmented data into a coherent web of financial intelligence.

Join the conversation! Your thoughts help the community grow.