Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Fraud Investigation Agent — Spring Boot + Spring AI

An LLM agent that investigates a flagged fraud case, decides a disposition, and — before any irreversible action — hands the decision to a human. Built with Spring Boot and Spring AI, running 100% locally and for free on Ollama with a small open model. No API key, no cloud, no cost.

The domain (fraud triage) is just a vehicle. What it teaches transfers to any agent: using an LLM with tools, turning its output into a structured decision, and putting a guardrail on a dangerous action.

What it does

Given a case like "Customer C-9: 8× ~$1,200 gift cards to RU in 5 minutes, card-not-present, 2-week-old account", the service:

  1. investigates — a mandatory watchlist/sanctions screen runs in code for every case, then the model calls read tools (customer history, recent transactions, velocity, geo risk) and Spring AI runs the tool-call loop; every result is recorded as ground-truth evidence;
  2. decides — a structured Disposition (approve / monitor / block) is produced via Spring AI's typed output — reliable, not free text;
  3. gates the block — if the disposition is block, your code calls a human Approver, and the account is blocked only on approval.
flowchart TB
  CASE["Flagged case"]:::io --> INV

  subgraph INV["1 · investigate (Spring AI ChatClient + tools)"]
    direction TB
    W["mandatory watchlist screen<br/>(code — runs every case)"]:::code
    A["LLM · qwen2.5:3b"]:::llm
    T["@Tool methods: history · recent txns · velocity · geo"]:::code
    A -->|tool calls| T
    T -->|results recorded as Evidence| A
  end

  INV -->|evidence| DEC["2 · decide<br/>structured Disposition<br/>approve · monitor · block"]:::llm
  DEC -->|approve / monitor| DONE["disposition returned"]:::io
  DEC -->|block| GATE{"3 · human approval gate<br/>(your code calls the Approver)"}:::gate
  GATE -->|approve| BLOCK["ACCOUNT BLOCKED"]:::danger
  GATE -->|deny| ACTIVE["block denied · account active"]:::io

  classDef io fill:#16202b,stroke:#16202b,color:#ffffff;
  classDef llm fill:#0f766e,stroke:#0b5c56,color:#ffffff;
  classDef code fill:#334155,stroke:#1e293b,color:#ffffff;
  classDef gate fill:#ffffff,stroke:#94a3b8,color:#334155;
  classDef danger fill:#b91c1c,stroke:#7f1d1d,color:#ffffff;
Loading

The key design decision: the model recommends, code enforces

The agent does not get to call the block itself. A small model can't be trusted to reliably invoke an irreversible action — it will write "block" in prose without actually doing anything. So the model only produces a recommendation (the structured Disposition), and the code enforces the human gate off that decision. Same case, human approves → account blocked; human denies → account stays active — deterministically, every time. That's the robust production pattern, and it's the heart of this project.

Prerequisites

  • JDK 17+ (built and tested on JDK 17 bytecode; verified running on JDK 25).
  • Maven 3.9+ (or use your IDE's bundled Maven).
  • Ollama running locally with the model pulled.
  • A GPU is optional — everything works on CPU (slower; a full investigation is several model calls).

Setup

# 1. Install Ollama (https://ollama.com/download), then start it:
ollama serve
# 2. Pull the model (one time):
ollama pull qwen2.5:3b

For stronger multi-step tool use, pull qwen2.5:7b and set the model in src/main/resources/application.properties.

Run it

# The demo — one case, human approves vs. denies:
mvn -q -DskipTests package
java -jar target/fraud-investigation-agent-0.1.0.jar
# (or: mvn spring-boot:run)

# The smoke test — real calls; auto-skips if Ollama isn't running:
mvn test

Expected demo output:

[HUMAN APPROVES]
  || APPROVAL REQUIRED: block_account(C-9) — ...watchlist MATCH... geo_risk 85/100...
  || human decision: APPROVE
  disposition = block  (...)
  blocked     = true
  outcome     = ACCOUNT BLOCKED for C-9 (human-approved). ...

[HUMAN DENIES]
  ...
  blocked     = false
  outcome     = BLOCK DENIED by human reviewer for C-9; account remains active.

How it maps to Spring AI

Concept Spring AI
Local model spring-ai-starter-model-ollama auto-configures a ChatModel bean
Agentic tool loop chatClient.prompt().tools(fraudTools)... — Spring AI runs the request → tool → result loop
Agent tools plain methods annotated @Tool / @ToolParam on FraudTools
Mandatory screen screenWatchlist(...) — a plain method the service calls in code (not a @Tool), so compliance never depends on the model
Structured decision .call().entity(Disposition.class) maps the response onto a typed record
Human gate a @FunctionalInterface Approver the service calls in code

Configuration lives in src/main/resources/application.properties (spring.ai.ollama.base-url, ...chat.options.model, temperature).

Project layout

.
├── pom.xml                         # Spring Boot 4 parent + Spring AI 2 BOM
├── src/main/java/com/example/investigator/
│   ├── InvestigatorApplication.java  # app + demo runner (approve vs. deny)
│   ├── InvestigationService.java     # investigate → decide → gate
│   ├── FraudTools.java               # @Tool read tools; records evidence
│   ├── Disposition.java              # structured decision (record + enum)
│   ├── Evidence.java                 # one recorded tool result
│   ├── Approver.java                 # human-in-the-loop injection point
│   └── InvestigationResult.java      # disposition + evidence + outcome
├── src/main/resources/application.properties
└── src/test/java/.../InvestigationServiceTest.java   # smoke test (skips if Ollama down)

Where to go next

  • Expose it over HTTP — add spring-boot-starter-web and a controller: an /investigate endpoint that returns the disposition, and an /approve endpoint for the human gate. Spring Boot makes this a few lines.
  • Add MCP tools — Spring AI has MCP client/server starters; move a tool (e.g. watchlist screening) to a standalone Model Context Protocol server.
  • Add evals — a labeled set plus a rationale-vs-evidence check; the highest- leverage habit for taking an agent from demo to trustworthy.
  • Swap the model — point application.properties at any Ollama model, or at a hosted provider (Spring AI supports many) — the service code doesn't change.

License

MIT — see LICENSE. Use it, fork it, teach with it.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages