GraphRAG in Motion: A Easy Agent for Know-Your-Customer Investigations

-

the world of monetary services, Know-Your-Customer (KYC) and Anti-Money Laundering (AML) are critical defense lines against illicit activities. KYC is of course modelled as a graph problem, where customers, accounts, transactions, IP addresses, devices, and locations are all interconnected nodes in an unlimited network of relationships. Investigators sift through these complex webs of connections, attempting to connect seemingly disparate dots to uncover fraud, sanctions violations, and money laundering rings. 

That is a terrific use case for AI grounded by a knowledge graph (GraphRAG). The intricate web of connections requires capabilities beyond standard document-based RAG (typically based on vector similarity search and reranking techniques).

Disclosure

I’m a Senior Product Manager for AI at Neo4j, the graph database featured on this post. Although the snippets give attention to Neo4j, the identical patterns may be applied with any graph database. My foremost aim is to share practical guidance on constructing GraphRAG agents with the AI/ML community. All code within the linked repository is open-source and free so that you can explore, experiment with, and adapt.

All on this blog post were created by the writer.

A GraphRAG KYC Agent

This blog post provides a hands-on guide for AI engineers and developers on the best way to construct an initial KYC agent prototype with the OpenAI Agents SDK. We’ll explore the best way to equip our agent with a collection of tools to uncover and investigate potential fraud patterns.

The diagram below illustrates the agent processing pipeline to reply questions raised during a KYC investigation.

Image by the Writer generated using Napkin AI

Let’s walk through the key components:

  • The KYC Agent: It leverages the OpenAI Agents SDK and acts because the “brain,” deciding which tool to make use of based on the user’s query and the conversation history. It plays the role of MCP Host and MCP client to the Neo4j MCP Cypher Server. Most significantly, it runs a quite simple loop that takes an issue from the user, invokes the agent, and processes the outcomes, while keeping the conversation history.
  • The Toolset. A group of tools available to the agent.
    • GraphRAG Tools: These are Graph data retrieval functions that wrap a really specific Cypher query. For instance:
      • Get Customer Details: A graph retrieval tool that given a Customer ID, it retrieves details about a customer, including their accounts and up to date transaction history.
    • Neo4j MCP Server: A Neo4j MCP Cypher Server exposing tools to interact with a Neo4j database. It provides three essential tools:
      1. Get Schema from the Database.
      2. Run a READ Cypher Query against the database
      3. Run a WRITE Cypher QUery against the database
    • A Text-To-Cypher tool: A python function wrapping a fine-tuned Gemma3-4B model running locally via Ollama. The tool translates natural language questions into Cypher graph queries.
    • A Memory Creation tool: This tool enables investigators to document their findings directly within the knowledge graph. It creates a “memory” (of an investigation) within the knowledge graph and links it to all relevant customers, transactions, and accounts. Over time, this helps construct a useful knowledge base for future investigations.
  • A KYC Knowledge Graph: A Neo4j database storing a knowledge graph of 8,000 fictitious customers, their accounts, transactions, devices and IP addresses. It is usually used because the agent’s long-term memory store.

Need to check out the agent now? Just follow the instructions on the project repo. You may come back and skim how the agent was built later.

Why GraphRAG for KYC?

Traditional RAG systems give attention to finding information inside large bodies of text which are chunked up into fragments. KYC investigations depend on finding interesting patterns in a posh web of interconnected data – customers linked to accounts, accounts connected through transactions, transactions tied to IP addresses and devices, and customers related to personal and employer addresses.

Understanding these relationships is vital to uncovering sophisticated fraud patterns.

  • “Does this customer share an IP address with someone on a watchlist?”
  • “Is that this transaction a part of a circular payment loop designed to obscure the source of funds?”
  • “Are multiple latest accounts being opened by individuals working for a similar, newly-registered, shell company?”

These are questions of connectivity. A knowledge graph, where customers, accounts, transactions, and devices are nodes and their relationships are explicit edges, is the best data structure for this task. GraphRAG (data retrieval) tools make it easy to discover unusual patterns of activity.

Image by the Author generated with Napkin AI
Image by the Writer generated using Napkin AI

A Synthetic KYC Dataset

For the needs of this blog, I actually have created an artificial dataset with 8,000 fictitious customers and their accounts, transactions, registered addresses, devices and IP addresses. 

The image below shows the “schema” of the database after the dataset is loaded into Neo4j. In Neo4j, a schema describes the kind of entities and relationships stored within the database. In our case, the foremost entities are: Customer, Address, Accounts, Device, IP Address, Transactions. The foremost relationships amongst them are as illustrated below.

Image by the Author

The dataset accommodates just a few anomalies. Some customers are involved in suspicious transaction rings. There are just a few isolated devices and IP addresses (not linked to any customer or account). There are some addresses shared by a big number of consumers. Be happy to explore the synthetic dataset generation script, if you would like to understand or modify the dataset to your requirements.

A Basic Agent with OpenAI Agents SDK

Let’s walk through the key parts of our KYC Agent.

The implementation is usually inside kyc_agent.py. The complete source code and step-by-step instructions on the best way to run the agent can be found on Github.

First, let’s define the agent’s core identity with suitable instructions.

import os
from agents import Agent, Runner, function_tool
# ... other imports

# Define the instructions for the agent
instructions = """You might be a KYC analyst with access to a knowledge graph. Use the tools to reply questions on customers, accounts, and suspicious patterns.
You might be also a Neo4j expert and may use the Neo4j MCP server to question the graph.
For those who get an issue concerning the KYC database which you could not answer with GraphRAG tools, you must
- use the Neo4j MCP server to fetch the schema of the graph (if needed)
- use the generate_cypher tool to generate a Cypher query from query and the schema
- use the Neo4j MCP server to question the graph to reply the query
"""

The instructions are crucial. They set the agent’s persona and supply a high-level strategy for the best way to approach problems, especially when a pre-defined tool doesn’t fit the user’s request. 

Now, let’s start with a minimal agent. No tools. Just the instructions.

# Agent Definition, we'll add tools later. 
kyc_agent = Agent(
   name="KYC Analyst",
   instructions=instructions,
   tools=[...],      # We are going to populate this list
   mcp_servers=[...] # And this one
)

Let’s add some tools to our KYC Agent

An agent is just nearly as good as its tools. Let’s examine five tools we’re giving our KYC analyst.

Tool 1 & 2: Pre-defined Cypher Queries

For common and significant queries, it’s best to have optimized, pre-written Cypher queries wrapped in Python functions. You need to use the @function_tool decorator from the OpenAI Agent SDK to make these functions available to the agent.

Tool 1: `find_customer_rings`

This tool is designed to detect recursive patterns characteristic of cash laundering, specifically ‘circular transactions’ where funds cycle through multiple accounts to disguise their origin. 

In KYC graph, this translates on to finding cycles or paths that return to or near their start line inside a directed transaction graph. Implementing such detection involves complex graph traversal algorithms, often utilizing variable-length paths to explore connections as much as a certain ‘hop’ distance.

The code snippet below shows a find_customer_rings function that executes a Cypher Query against the KYC database and returns as much as 10 potential customer rings. For every rings, the next information is returned: the purchasers accounts and transactions involved in those rings.

@function_tool
def find_customer_rings(max_number_rings: int = 10, customer_in_watchlist: bool = True, ...):
   """
   Detects circular transaction patterns (as much as 6 hops) involving high-risk customers.
   Finds account cycles where the accounts are owned by customers matching specified
   risk criteria (watchlisted and/or PEP status).
   Args:
       max_number_rings: Maximum rings to return (default: 10)
       customer_in_watchlist: Filter for watchlisted customers (default: True)
       customer_is_pep: Filter for PEP customers (default: False)
       customer_id: Specific customer to give attention to (not implemented)
   Returns:
       dict: Incorporates ring paths and associated high-risk customers
   """
   logger.info(f"TOOL: FIND_CUSTOMER_RINGS")
   with driver.session() as session:
       result = session.run(
           f"""
           MATCH p=(a:Account)-[:FROM|TO*6]->(a:Account)
           WITH p, [n IN nodes(p) WHERE n:Account] AS accounts
           UNWIND accounts AS acct
           MATCH (cust:Customer)-[r:OWNS]->(acct)
           WHERE cust.on_watchlist = $customer_in_watchlist
           // ... more Cypher to gather results ...
           """,
           max_number_rings=max_number_rings,
           customer_in_watchlist=customer_in_watchlist,
       )
       # ... Python code to process and return results ...

It’s price noting that the documentation string (doc string) is routinely utilized by OpenAI Agents SDK because the tool description! So good Python function documentation pays off!.

Tool 2: `get_customer_and_accounts`

A straightforward, yet essential, tool for retrieving a customer’s profile, including their accounts and most up-to-date transactions. That is the bread-and-butter of any investigation. The code is comparable to our previous tool – a function that takes a customer ID and wraps around a straightforward Cypher query. 

Once more, the function is decorated with @function_tool to make it available to the agent. 

The Cypher query wrapped by this Python is shown below

result = session.run(
           """
           MATCH (c:Customer {id: $customer_id})-[o:OWNS]->(a:Account)
           WITH c, a
           CALL (c,a) FROM]->(t:Transaction)
               ORDER BY t.timestamp DESC
               LIMIT $tx_limit
               RETURN collect(t) as transactions
           
           RETURN c as customer, a as account, transactions
           """,
           customer_id=input.customer_id
       )

A notable aspect of this tool’s design is using Pydantic to specify the function’s output. The OpenAI AgentsSDK uses Pydantic models returned by the function to routinely generate a text description of the output parameters. 

For those who look rigorously, the function returns

return CustomerAccountsOutput(          
 customer=CustomerModel(**customer),
 accounts=[AccountModel(**a) for a in accounts],
)

The and include each of the properties returned for every Customer, its accounts and a listing of recent transactions. You may see their definition in schemas.py.

Tools 3 & 4: Where Neo4j MCP Server meets Text-To-Cypher

That is where our KYC agent gets some more interesting powers.

A major challenge in constructing versatile AI agents is enabling them to interact dynamically with complex data sources, beyond pre-defined, static functions. Agents need the flexibility to perform general-purpose querying where latest insights might require spontaneous data exploration without requiring  Python wrappers for each possible motion.

This section explores a standard architectural pattern to deal with this. A tool to translate natural language query into Cypher coupled with one other tool to permit dynamic query execution.

We exhibit this mechanism using the Neo4 MCP Server to show dynamic graph query execution and a Google Gemma3-4B fine-tuned model for Text-to-Cypher translation.

Tool 3: Adding the Neo4j MCP server toolset

For a sturdy agent to operate effectively with a knowledge graph, it needs to grasp the graph’s structure and to execute Cypher queries. These capabilities enable the agent to introspect the info and execute dynamic ad-hoc queries.

The MCP Neo4j Cypher server provides the fundamental tools: get-neo4j-schema (to retrieve graph schema dynamically), read-neo4j-cypher (for executing arbitrary read queries), and write-neo4j-cypher (for create, update, delete queries).

Fortunately, the OpenAI Agents SDK has support for MCP. The code snippet below shows how easy it’s so as to add the Neo4j MCP Server to our KYC Agent.

# Tool 3: Neo4j MCP server setup
neo4j_mcp_server = MCPServerStdio(
   params={
       "command": "uvx",
       "args": ["[email protected]"],
       "env": {
           "NEO4J_URI": NEO4J_URI,
           "NEO4J_USERNAME": NEO4J_USER,
           "NEO4J_PASSWORD": NEO4J_PASSWORD,
           "NEO4J_DATABASE": NEO4J_DATABASE,
       },
   },
   cache_tools_list=True,
   name="Neo4j MCP Server",
)

You may learn more about how MCP is supported in OpenAI Agents SDK here.

Tool 4: A Text-To-Cypher Tool

The flexibility to dynamically translate natural language into powerful graph queries often relies on specialized Large Language Models (LLMs) – finetuned with schema-aware query generation.

We will use open weights, publicly available Text-to-Cypher models available on Huggingface, resembling neo4j/text-to-cypher-Gemma-3-4B-Instruct-2025.04.0. This model was specifically finetuned to generate accurate Cypher queries from user query and a schema.

So as to run this model on a neighborhood device, we will turn to Ollama. Using Llama.cpp, it is comparatively straightforward to convert any HuggingFace models to GGUF format, which is required to run a model in Ollama. Using the ‘convert-hf-to-GGUF’ python script, I generated a GGUF version of the Gemma3-4B finetuned model and uploaded it to Ollama.

For those who are an Ollama user, you possibly can download this model to your local device with:

ollama pull ed-neo4j/t2c-gemma3-4b-it-q8_0-35k

What happens when a user asks an issue that doesn’t match any of our pre-defined tools?

For instance, “For customer CUST_00001, find his addresses and check in the event that they are shared with other customers”

As an alternative of failing, our agent can generate a Cypher query on the fly…

@function_tool
async def generate_cypher(request: GenerateCypherRequest) -> str:
   """
   Generate a Cypher query from natural language using a neighborhood finetuned text2cypher Ollama model
   """
   USER_INSTRUCTION = """...""" # Detailed prompt instructions

   user_message = USER_INSTRUCTION.format(
       schema=request.database_schema,
       query=request.query
   )
   # Generate Cypher query using the text2cypher model
   model: str = "ed-neo4j/t2c-gemma3-4b-it-q8_0-35k"
   response = await chat(
       model=model,
       messages=[{"role": "user", "content": user_message}]
   )
   return response['message']['content']

The generate_cypher tool addresses the challenge of Cypher query generation, but how does the agent know when to make use of this tool? The reply lies within the agent instructions.

You might keep in mind that initially of the blog, we defined the instructions for the agent as follows:

instructions = """You might be a KYC analyst with access to a knowledge graph. Use the tools to reply questions on customers, accounts, and suspicious patterns.
   You might be also a Neo4j expert and may use the Neo4j MCP server to question the graph.
   For those who get an issue concerning the KYC database which you could not answer with GraphRAG tools, you must
   - use the Neo4j MCP server to get the schema of the graph (if needed)
   - use the generate_cypher tool to generate a Cypher query from query and the schema
   - use the Neo4j MCP server to question the graph to reply the query
   """

This time, note the particular instructions to handle ad-hoc queries that cannot be answered by the graph retrieval based tools.

When the agent goes down this path, it goes through following steps:

  1. The agent gets a novel query.
  2. It first calls `neo4j-mcp-server.get-neo4j-schema` to get the schema of the database.
  3. It then feeds the schema and the user’s query to the `generate_cypher` tool. This may generate a Cypher query.
  4. Finally, it takes the generated Cypher query and run it using `neo4j-mcp-server.read-neo4j-cypher`.

If there are errors, in either the cypher generation or the execution of the cypher, the agent retries to generate Cypher and rerun it. 

As you possibly can see, the above approach shouldn’t be bullet-proof. It relies heavily on the Text-To-Cypher model to provide valid and proper Cypher. Normally, it really works. Nonetheless, in cases where it doesn’t, you must consider:

  • Defining explicit Cypher retrieval tools for the sort of questions.
  • Adding some type of end user feedback (thumbs up / down) in your UI/UX. This may help flag questions that the agent is scuffling with. You may then determine best approach to handle this class of questions. (e.g cypher retrieval tool, higher instructions, improvement to text2cypher model, guardrails or simply get your agent to politely decline to reply the query).

Tool 5 – Adding Memory to the KYC Agent

The subject of agent memory is getting numerous attention recently.

While agents inherently manage  through conversational history, complex, multi-session tasks like financial investigations demand a more persistent and evolving .

This long-term memory isn’t only a log of past interactions; it’s a dynamic knowledge base that may accumulate insights, track ongoing investigations, and supply context across different sessions and even different agents.

The create_memory tool implements a type of explicit knowledge graph memory, where summaries of investigations are stored as dedicated nodes and explicitly linked to relevant entities (customers, accounts, transactions).

@function_tool
def create_memory(content: str, customer_ids: list[str] = [], account_ids: list[str] = [], transaction_ids: list[str] = []) -> str:


   """
   Create a Memory node and link it to specified customers, accounts, and transactions
   """
   logger.info(f"TOOL: CREATE_MEMORY")
   with driver.session() as session:
       result = session.run(
           """
           CREATE (m:Memory {content: $content, created_at: datetime()})
           WITH m
           UNWIND $customer_ids as cid
           MATCH (c:Customer {id: cid})
           MERGE (m)-[:FOR_CUSTOMER]->(c)
           WITH m
           UNWIND $account_ids as aid
           MATCH (a:Account {id: aid})
           MERGE (m)-[:FOR_ACCOUNT]->(a)
           WITH m
           UNWIND $transaction_ids as tid
           MATCH (t:Transaction {id: tid})
           MERGE (m)-[:FOR_TRANSACTION]->(t)
           RETURN m.content as content
           """,
           content=content,
           customer_ids=customer_ids,
           account_ids=account_ids,
           transaction_ids=transaction_ids
           # ...
       )

Additional considerations for implementing “agent memory” include:

  • Memory Architectures: Exploring various kinds of memory (episodic, semantic, procedural) and their common implementations (vector databases for semantic search, relational databases, or knowledge graphs for structured insights).
  • Contextualization: How the knowledge graph structure allows for wealthy contextualization of memories, enabling powerful retrieval based on relationships and patterns, fairly than simply keyword matching.
  • Update and Retrieval Strategies: How memories are updated over time (e.g., appended, summarized, refined) and the way they’re retrieved by the agent (e.g., through graph traversal, semantic similarity, or fixed rules).
  • Challenges: The complexities of managing memory consistency, handling conflicting information, stopping ‘hallucinations’ in memory retrieval, and ensuring the memory stays relevant and up-to-date without becoming overly large or noisy.”

That is an area of energetic development and rapidly evolving with many frameworks addressing a few of the considerations above.

Putting all of it together – An Example Investigation

Let’s see how our agent handles a typical workflow. You may run this yourself (or be happy to follow along step-by-step instructions on the KYC agent github repo

1. “Get me the schema of the database

  • Agent Motion: The agent identifies this as a schema query and uses the Neo4j MCP Server’s tool.

2. “Show me 5 watchlisted customers involved in suspicious rings

  • Agent Motion: This directly matches the aim of our custom tool. The agent calls with .

3. “For every of those customers, find their addresses and discover in the event that they are shared with other customers“.

  • Agent Motion: It is a query that may’t be answered with any of the GraphRAG tools. The agent should follow its instructions:
    • It already has the schema (from our first interaction above).
    • It calls with the query and schema. The tool returns a Cypher query that tries to reply the investigator’s query.
    • It executes this Cypher query using the Neo4j MCP Cypher Server tool.

4. “For the shopper whose address is shared , are you able to get me more details

  • Agent Motion: The agent determines that the tool is the right fit and calls it with the shopper’s ID.

5. “Write a 300-word summary of this investigation. Store it as a memory. Ensure to link it to each account and transaction belonging to this customer“.

  • Agent Motion: The agent first uses its internal LLM capabilities to generate the summary. Then, it calls the tool, passing the summary text and the list of all customer, account, and transaction IDs it has encountered in the course of the conversation.

Key Takeaways

For those who got this far, I hope you enjoyed the journey of getting accustomed to a basic implementation of a KYC GraphRAG Agent. A number of cool technologies here: OpenAI Agent SDK, MCP, Neo4j, Ollama and a Gemma3-4B finetuned Text-To-Cypher model!

I hope you gained some appreciation for:

  • GraphRAG, or more specifically Graph-powered data retrieval as an important for connected-data problems. It allows agents to reply questions on heavily connected data that might be unimaginable to reply with standard RAG.
  • The importance of a balanced toolkit is powerful. Mix MCP Server tools along with your own optimized tools.
  • MCP Servers are a game-changer. They mean you can connect your agents to an increasing set of MCP servers.
    • Experiment with more MCP Servers so that you get a greater sense of the probabilities.
  • Agents should give you the option to jot down back to your data store in a controlled way. 
    • In our example we saw how an analyst can persist its findings (e.g., adding Memory nodes to the knowlege graph) and in the method making a virtuous cycle where the agent improves the underlying knowledge base for entire teams of investigators. 
    • The agent adds information to the knowledge graph and it never updates or deletes existing information. 

The patterns and tools discussed here are usually not limited to KYC. They may be applied to produce chain evaluation, digital twin management, drug discovery, and some other domain where the relationships between data points are as necessary as the info itself.

The era of graph-aware AI agents is here. 

What’s Next?

You’ve built a straightforward AI agent on top of OpenAI Agents SDK with MCP, Neo4j and a Text-to-Cypher model. All running on a single device.

While this initial agent provides a powerful foundation, transitioning to a production-level system involves addressing several additional requirements, resembling:

  • Agent UI/UX: That is the central part on your users to interact along with your agent. This may ultimately be a key driver of the adoption and success of your agent.
    Long running tasks and multiagent systems: Some tasks are worthwhile but take a big period of time to run. In these cases, agents should give you the option to dump parts of their workload to other agents.
    • OpenAI does provide some support for handing off to subagents however it won’t be suitable for long-running agents.
  • Agent Guardrails – OpenAI Agents SDK provides some support for Guardrails.
  • Agent Hosting – It exposes your agent to your users.
  • Securing comms to your agent – End user authentication and authorization to your agent.
  • Database access controls – Managing access control to the info stored within the KYC Knowledge Graph.
  • Conversation History.
  • Agent Observability.
  • Agent Memory.
  • Agent Evaluation – What’s the impact of adjusting agent instruction and or adding/removing a tool?.
  • And more…

Within the meantime, I hope this has inspired you to continue to learn and experimenting!.

Learning Resources

ASK ANA

What are your thoughts on this topic?
Let us know in the comments below.

0 0 votes
Article Rating
guest
0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments

Share this article

Recent posts

0
Would love your thoughts, please comment.x
()
x