How to Integrate FastAPI for investment banking with PostgreSQL for RAG
Combining FastAPI for investment banking with PostgreSQL gives you a clean path to build agent-backed banking systems that can answer policy, deal, and portfolio questions from structured and unstructured data. FastAPI handles the request/response layer for your AI agent, while PostgreSQL stores embeddings, transaction context, and retrieval metadata for RAG.
Prerequisites
- •Python 3.10+
- •A running PostgreSQL instance
- •
fastapi - •
uvicorn - •
psycopg2-binaryorpsycopg - •
sqlalchemy - •
pgvectorextension enabled in PostgreSQL - •Access to your embedding model provider
- •A clear schema for banking documents, notes, or deal memos
Install the core packages:
pip install fastapi uvicorn sqlalchemy psycopg2-binary pgvector pydantic
Enable pgvector in your database:
CREATE EXTENSION IF NOT EXISTS vector;
Integration Steps
- •Set up the PostgreSQL schema for RAG storage.
You want one table for source documents and one table for embeddings. Keep metadata explicit so you can filter by desk, client, region, or document type later.
from sqlalchemy import create_engine, Column, Integer, String, Text, MetaData
from sqlalchemy.orm import declarative_base
from pgvector.sqlalchemy import Vector
DATABASE_URL = "postgresql+psycopg2://user:password@localhost:5432/investment_db"
engine = create_engine(DATABASE_URL)
Base = declarative_base()
class BankingDocument(Base):
__tablename__ = "banking_documents"
id = Column(Integer, primary_key=True)
doc_id = Column(String(64), unique=True, nullable=False)
content = Column(Text, nullable=False)
source_type = Column(String(50), nullable=False)
desk = Column(String(50), nullable=True)
embedding = Column(Vector(1536)) # match your embedding model dimension
Base.metadata.create_all(engine)
- •Build a FastAPI service that accepts banking queries and stores retrieval context.
FastAPI gives you a simple way to expose an internal agent endpoint. Use it to receive user questions from chat tools, workflow engines, or analyst portals.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI(title="Investment Banking RAG API")
class QueryRequest(BaseModel):
question: str
desk: str | None = None
@app.post("/query")
async def query_bank_agent(payload: QueryRequest):
return {
"question": payload.question,
"desk": payload.desk,
"status": "received"
}
- •Insert documents and embeddings into PostgreSQL.
In a real system, you generate embeddings from earnings call transcripts, credit memos, pitch books, or compliance policies. Then store them alongside the raw text for later retrieval.
from sqlalchemy.orm import Session
def save_document(doc_id: str, content: str, source_type: str, desk: str | None, embedding: list[float]):
with Session(engine) as session:
doc = BankingDocument(
doc_id=doc_id,
content=content,
source_type=source_type,
desk=desk,
embedding=embedding
)
session.add(doc)
session.commit()
# Example call
save_document(
doc_id="memo_001",
content="Client wants debt financing for acquisition of a regional payments company.",
source_type="deal_memo",
desk="M&A",
embedding=[0.01] * 1536
)
- •Retrieve the most relevant context from PostgreSQL inside your FastAPI route.
This is the RAG part. For production use, query by vector similarity and optionally filter by desk or source type before passing context to your LLM.
from sqlalchemy import text
def search_documents(question_embedding: list[float], desk: str | None = None):
with engine.connect() as conn:
sql = """
SELECT doc_id, content
FROM banking_documents
WHERE (:desk IS NULL OR desk = :desk)
ORDER BY embedding <-> :query_embedding
LIMIT 5
"""
rows = conn.execute(
text(sql),
{"desk": desk, "query_embedding": question_embedding}
).fetchall()
return [{"doc_id": row.doc_id, "content": row.content} for row in rows]
- •Wire retrieval into the FastAPI endpoint.
This is where the agent gets useful. The API receives a question, generates an embedding, fetches context from PostgreSQL, then returns grounded output.
@app.post("/ask")
async def ask_agent(payload: QueryRequest):
# Replace this with your real embedding call
question_embedding = [0.02] * 1536
docs = search_documents(question_embedding=question_embedding, desk=payload.desk)
context = "\n\n".join([f"[{d['doc_id']}] {d['content']}" for d in docs])
return {
"question": payload.question,
"retrieved_context": context,
"answer_hint": "Pass this context to your LLM for final generation"
}
Testing the Integration
Run the app:
uvicorn main:app --reload --port 8000
Test it with a request:
import requests
resp = requests.post(
"http://localhost:8000/ask",
json={
"question": "What financing structure was proposed for the acquisition?",
"desk": "M&A"
}
)
print(resp.status_code)
print(resp.json())
Expected output:
200
{
'question': 'What financing structure was proposed for the acquisition?',
'retrieved_context': '[memo_001] Client wants debt financing for acquisition of a regional payments company.',
'answer_hint': 'Pass this context to your LLM for final generation'
}
Real-World Use Cases
- •
Deal team copilot
Let bankers ask questions over pitch books, CIMs, and internal memos with answers grounded in stored deal context. - •
Compliance and policy assistant
Retrieve relevant policy clauses before drafting client-facing responses or reviewing transaction language. - •
Portfolio intelligence agent
Combine transaction history with research notes so analysts can query exposure themes across sectors and clients.
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