Introduction
In the competitive fintech landscape, a "one-size-fits-all" approach to user acquisition is dead. Offering a $20 bonus to every new digital wallet user is not only expensive but often ineffective. A student might be motivated by a fee waiver, while a high-net-worth individual might ignore small cash incentives entirely. But how does a system know what the market demands at any given second?
The answer lies in Retrieval-Augmented Generation (RAG) applied to Market Intelligence. By connecting our pricing engine to a live knowledge base of competitor moves, seasonal trends, and regulatory updates, we can dynamically adjust our wallet creation incentives. This article explores how RAG-based market intelligence influences pricing policy. We will build an end-to-end Proof of Concept (POC) using LangGraph to orchestrate agents that retrieve real-time market context and use it to guide a Reinforcement Learning (RL) pricing agent.
Disclaimer: This POC uses simulated market data for educational purposes. It is not financial advice.
The Limitation of Static Pricing Models
Traditional pricing engines rely on historical transaction history alone. They look backward. If users accepted a $10 bonus last month, the model offers $10 this month. But what if a competitor just launched a $15 bonus yesterday? Or what if a new regulation caps incentives at $5? A static model misses these signals, leading to either lost users (under-pricing) or wasted budget (over-pricing).
What is RAG-Based Market Intelligence?
RAG allows an AI system to fetch external, up-to-date information before making a decision. In our context, Market Intelligence includes:
Competitor Offers: Current sign-up bonuses from rival wallets.
Seasonal Trends: Holiday spending spikes or tax-season liquidity crunches.
Regulatory Constraints: New laws limiting inducements.
Macro-Economic Indicators: Interest rate changes affecting user savings behavior.
By retrieving this data via RAG, our pricing agent doesn't just guess; it reasons based on the current market reality.
Real-Time Use Case: The "Smart" Wallet Onboarding
Scenario: It is "Back-to-School" season. A competitor, "PayFast," has just announced a $10 student bonus. User: A new user signs up, identified as a "Student." System Behavior:
RAG Agent: Queries the vector store for "Student incentives" and "Competitor PayFast." It retrieves: "PayFast offering $10 student bonus. Regulatory cap for students is $15."
Pricing Agent: Receives this context. Instead of its default $5 offer, it calculates that to remain competitive, it must match or beat $10, but stay under the $15 cap.
Decision: The agent offers a "$12 Bonus + Fee Waiver," optimizing for conversion while respecting constraints.

Technology Stack Overview
Backend: Python 3.12, FastAPI, Uvicorn
Orchestration: LangGraph
RAG: LangChain, ChromaDB, HuggingFace Embeddings
Pricing Logic: Simple Q-Learning (NumPy) for demonstration
Memory: SQLite via LangGraph Checkpointer
Frontend: React, Vite, Tailwind CSS
Project Architecture
market-intel-pricing/
├── backend/
│ ├── app/
│ │ ├── main.py
│ │ ├── config.py
│ │ ├── models/
│ │ │ └── state.py
│ │ ├── agents/
│ │ │ ├── market_intel_agent.py
│ │ │ ├── pricing_agent.py
│ │ │ └── response_agent.py
│ │ ├── graph/
│ │ │ └── workflow.py
│ │ └── rag/
│ │ └── ingest.py
│ └── requirements.txt
├── frontend/
│ ├── src/
│ │ ├── App.jsx
│ │ └── components/
│ │ └── PricingDashboard.jsx
│ └── package.json
└── README.mdStep-by-Step POC Implementation
1. Backend Setup and Configuration
backend/requirements.txt
fastapi
uvicorn
langgraph
langchain
langchain-community
chromadb
sentence-transformers
numpy
python-dotenv
pydanticbackend/app/config.py
import os
from dotenv import load_dotenv
load_dotenv()
class Settings:
CHROMA_PATH = "./chroma_db"
LEARNING_RATE = 0.1
DISCOUNT_FACTOR = 0.9
EXPLORATION_RATE = 0.1
settings = Settings()2. Building the Market Intelligence Knowledge Base (RAG)
We simulate a live feed of market news and competitor data.
backend/app/rag/ingest.py
from langchain_core.documents import Document
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
def get_market_intelligence_docs():
return [
Document(
page_content="Competitor Alert: PayFast has increased student sign-up bonus to $10 effective today.",
metadata={"source": "Competitor Watch", "segment": "Student", "type": "Competitor"}
),
Document(
page_content="Regulatory Update: The Financial Conduct Authority has capped cash incentives for new wallets at $15 per user.",
metadata={"source": "Regulatory Body", "segment": "All", "type": "Regulation"}
),
Document(
page_content="Market Trend: Back-to-School season sees a 40% increase in wallet creation among users aged 18-24.",
metadata={"source": "Market Analysis", "segment": "Student", "type": "Trend"}
),
Document(
page_content="Competitor Alert: CoinBase is offering 0% fees for the first 3 months for professional traders.",
metadata={"source": "Competitor Watch", "segment": "Professional", "type": "Competitor"}
)
]
def ingest_data():
docs = get_market_intelligence_docs()
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
split_docs = splitter.split_documents(docs)
embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
vector_store = Chroma.from_documents(
documents=split_docs,
embedding=embeddings,
persist_directory="./chroma_db"
)
print("Market Intelligence ingested.")
return vector_store3. Defining the LangGraph State
backend/app/models/state.py
from typing import TypedDict, List, Optional, Annotated
from langgraph.graph import add_messages
class PricingState(TypedDict):
messages: Annotated[list, add_messages]
user_profile: dict
market_context: List[dict]
retrieved_intelligence: str
suggested_action: str
final_offer: str
reward: float
q_table_updated: bool4. The Market Intelligence Agent (RAG)
This agent retrieves relevant market data based on the user's segment.
backend/app/agents/market_intel_agent.py
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings
def market_intel_node(state):
embeddings = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
db = Chroma(persist_directory="./chroma_db", embedding_function=embeddings)
# Query based on user segment
query = f"incentives for {state['user_profile'].get('type', 'user')}"
docs = db.similarity_search(query, k=3)
# Format context for the pricing agent
context_str = "\n".join([d.page_content for d in docs])
state["retrieved_intelligence"] = context_str
state["market_context"] = [
{"content": d.page_content, "metadata": d.metadata} for d in docs
]
return state5. The Pricing Strategy Agent (RL + Logic)
This agent uses the retrieved intelligence to adjust its Q-Learning strategy.
backend/app/agents/pricing_agent.py
import numpy as np
import random
from app.config import settings
class IntelligentPricingAgent:
def __init__(self):
self.q_table = {}
self.actions = ["No_Incentive", "Small_Bonus ($5)", "Medium_Bonus ($10)", "Large_Bonus ($15)", "Fee_Waiver"]
def get_state_key(self, profile):
return profile.get('type', 'Unknown')
def choose_action(self, state_key, context):
if state_key not in self.q_table:
self.q_table[state_key] = np.zeros(len(self.actions))
# LOGIC INFLUENCED BY RAG:
# If context mentions a competitor bonus > $5, remove 'No_Incentive' from exploration
if "PayFast" in context and "$10" in context:
valid_indices = [1, 2, 3, 4] # Exclude index 0 (No_Incentive)
else:
valid_indices = list(range(len(self.actions)))
if random.uniform(0, 1) < settings.EXPLORATION_RATE:
idx = random.choice(valid_indices)
else:
masked_q = np.full(len(self.actions), -np.inf)
for i in valid_indices:
masked_q[i] = self.q_table[state_key][i]
idx = np.argmax(masked_q)
return self.actions[idx]
def learn(self, state_key, action, reward):
if state_key not in self.q_table:
self.q_table[state_key] = np.zeros(len(self.actions))
idx = self.actions.index(action)
old_value = self.q_table[state_key][idx]
next_max = np.max(self.q_table[state_key])
new_value = old_value + settings.LEARNING_RATE * (reward + settings.DISCOUNT_FACTOR * next_max - old_value)
self.q_table[state_key][idx] = new_value
pricing_agent = IntelligentPricingAgent()
def pricing_node(state):
state_key = pricing_agent.get_state_key(state["user_profile"])
# Learn from previous turn
if state.get("reward") is not None and state.get("prev_action"):
pricing_agent.learn(state_key, state["prev_action"], state["reward"])
state["q_table_updated"] = True
# Choose action based on RAG context
action = pricing_agent.choose_action(state_key, state["retrieved_intelligence"])
state["suggested_action"] = action
state["prev_action"] = action
return state6. Orchestrating the Workflow with LangGraph
backend/app/graph/workflow.py
from langgraph.graph import StateGraph, END
from app.models.state import PricingState
from app.agents.market_intel_agent import market_intel_node
from app.agents.pricing_agent import pricing_node
from langgraph.checkpoint.sqlite import SqliteSaver
def response_node(state):
state["final_offer"] = f"Based on market intel, we offer: {state['suggested_action']}"
return state
def create_workflow():
workflow = StateGraph(PricingState)
workflow.add_node("market_intel", market_intel_node)
workflow.add_node("pricing", pricing_node)
workflow.add_node("response", response_node)
workflow.set_entry_point("market_intel")
workflow.add_edge("market_intel", "pricing")
workflow.add_edge("pricing", "response")
workflow.add_edge("response", END)
memory = SqliteSaver.from_conn_string(":memory:")
return workflow.compile(checkpointer=memory)
pricing_graph = create_workflow()7. FastAPI Endpoints
backend/app/main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from app.graph.workflow import pricing_graph
from app.rag.ingest import ingest_data
import uuid
app = FastAPI(title="Market Intel Pricing Engine")
class PricingRequest(BaseModel):
user_type: str
conversation_id: str = None
previous_reward: float = None
@app.post("/ingest")
def ingest():
ingest_data()
return {"status": "Market Data Updated"}
@app.post("/offer")
def get_offer(req: PricingRequest):
thread_id = req.conversation_id or str(uuid.uuid4())
config = {"configurable": {"thread_id": thread_id}}
initial_state = {
"messages": [{"role": "user", "content": "Get offer"}],
"user_profile": {"type": req.user_type},
"reward": req.previous_reward,
"q_table_updated": False
}
try:
result = pricing_graph.invoke(initial_state, config=config)
return {
"conversation_id": thread_id,
"offer": result["final_offer"],
"market_context": result["market_context"],
"learned": result["q_table_updated"]
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))Frontend Implementation with React
frontend/src/App.jsx
import React, { useState } from 'react';
function App() {
const [userType, setUserType] = useState('Student');
const [result, setResult] = useState(null);
const [convId, setConvId] = useState(null);
const [lastReward, setLastReward] = useState(null);
const handleGetOffer = async () => {
try {
const res = await fetch('http://localhost:8000/offer', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
user_type: userType,
conversation_id: convId,
previous_reward: lastReward
}),
});
const data = await res.json();
setResult(data);
setConvId(data.conversation_id);
setLastReward(null);
} catch (err) {
console.error(err);
}
};
const simulateOutcome = (reward) => {
setLastReward(reward);
alert(`Simulated: ${reward > 0 ? 'Accepted' : 'Rejected'}. Click 'Get Offer' to see adaptation.`);
};
return (
<div className="p-10 max-w-4xl mx-auto">
<h1 className="text-2xl font-bold mb-4">RAG-Driven Wallet Pricing</h1>
<select value={userType} onChange={(e) => setUserType(e.target.value)} className="p-2 border rounded mr-2">
<option value="Student">Student</option>
<option value="Professional">Professional</option>
</select>
<button onClick={handleGetOffer} className="bg-blue-600 text-white px-4 py-2 rounded">
Get Market-Aware Offer
</button>
{result && (
<div className="mt-6 p-4 border rounded bg-gray-50">
<h2 className="font-bold">{result.offer}</h2>
<div className="mt-2">
<h3 className="text-sm font-semibold">Market Intelligence Used:</h3>
<ul className="list-disc pl-5 text-sm text-gray-600">
{result.market_context.map((ctx, idx) => (
<li key={idx}>{ctx.content}</li>
))}
</ul>
</div>
<div className="mt-4 flex gap-2">
<button onClick={() => simulateOutcome(10)} className="bg-green-500 text-white px-3 py-1 rounded">Accept</button>
<button onClick={() => simulateOutcome(-5)} className="bg-red-500 text-white px-3 py-1 rounded">Reject</button>
</div>
</div>
)}
</div>
);
}
export default App;Running the Application
Backend:
cd backend && pip install -r requirements.txt && uvicorn app.main:app --reloadIngest:
curl -X POST http://localhost:8000/ingestFrontend:
cd frontend && npm install && npm run dev
Analyzing the Impact of Market Intelligence
When you select "Student," the RAG agent retrieves the info about "PayFast's $10 bonus." The Pricing Agent then avoids "No_Incentive" because its logic is constrained by the retrieved context. If you were to remove the RAG step, the agent might randomly offer nothing, losing the user to the competitor. This demonstrates how external context directly shapes internal policy.
Enterprise Production Considerations
Data Freshness: Market intelligence must be updated in near real-time. Use Kafka streams to ingest competitor data into the vector store.
Source Verification: Ensure RAG sources are trusted. Bad market data leads to bad pricing.
Latency: RAG adds latency. Use caching for frequently accessed market trends (e.g., "Holiday Season" doesn't change hourly).
Explainability: Log which market document influenced the price. Auditors need to know why a user got a specific offer.
Limitations
Simulated market data does not reflect real-world volatility.
Simple Q-Learning is not sufficient for complex multi-variable pricing.
Regulatory caps are hard-coded in logic for safety, not just learned.
Conclusion
RAG-based market intelligence transforms a pricing engine from a reactive calculator into a proactive strategist. By grounding decisions in real-time external data, we ensure that wallet creation incentives are not just mathematically optimal, but competitively viable. This POC illustrates the power of combining LangGraph, RAG, and RL to build intelligent, context-aware financial systems.

Join the conversation! Your thoughts help the community grow.