Build an AI Wealth Management Advisor using Qdrant, Gemini API, and LLMs. Enable users to query stocks, investments, and live market data with natural, human-like financial insights.
The AI Wealth Management Advisor is essentially a personal financial assistant powered by artificial intelligence. Its main purpose is to let users ask natural questions about stocks, investments, and finance, and then return answers that feel like they came from a real advisor. The system works by combining two worlds: a local knowledge base of stock data stored in Qdrant, and real-time market data fetched from the Gemini API.
When a user types a query such as “What is the PE ratio of Infosys?”, the query first goes through an LLM, which acts as the brain of the system. This LLM understands what the user is asking, extracts key details like the company name or stock symbol, and then decides where the answer should come from. If the information is already available in the preloaded dataset stored in Qdrant, the system retrieves and returns it. If the data isn’t available locally, or if it requires live updates such as the latest stock price, the system instead queries the Gemini finance API to fetch the latest figures.
Once the data is found, the LLM formats it into a clear, human-readable response, so instead of just showing raw numbers, the system provides a well-explained summary that sounds like financial advice. All of this is exposed to the user through a simple chat interface built with React, while the backend logic and orchestration happen through FastAPI.
In essence, this project behaves like an AI-powered wealth manager. It blends static knowledge with live financial insights, uses smart reasoning to figure out the best source of truth, and delivers answers in plain English, making finance and stock analysis more accessible to everyday users.
The entry point is simply asking the user for input.
# Ask user
user_query = input("Enter your query: ")
Here the system waits for the user’s natural language question, like “What is the PE ratio of Infosys?”
The system must interpret what the user actually means before deciding where to fetch the data.
parsed_query = parse_query(user_query)
stocks = parsed_query["stocks_mentioned"]
date_range = parsed_query["date_range"]
routing = parsed_query["if_vector_or_live"]
print(f"\nParsed Query: {parsed_query}")
This calls parse_query() from llm.py.
Inside that, the LLM extracts structured details:
So here the raw natural text is translated into structured instructions.
Now the system decides: Should I look in the dataset or fetch live info?
if routing == "live":
print("\n> Fetching live data...\n")
print(get_finance_response(user_query))
If the parsed result says "if_vector_or_live": "live", then the query is directly routed to live data retrieval.
This is where get_finance_response() in llm.py fetches the latest data and formats it into a natural response.
If the system decides to use the stored dataset, the query gets embedded and searched in Qdrant.
else:
# Vector search
query_vector = model.encode(user_query).tolist()
query_filter = None
if date_range != "none":
query_filter = {"must": [{"key": "date", "match": {"value": date_range}}]}
query_points = client.query_points(
collection_name="stock",
query=query_vector,
limit=5,
with_payload=True,
query_filter=query_filter
)
👉 What happens here:
After Qdrant returns results, the code picks the best-scoring one.
best_response = None
best_score = float('-inf')
for point in query_points.points:
score = point.score
payload = point.payload
if score > best_score:
best_score = score
best_response = payload
Here the system looks at all matches, compares similarity scores, and keeps only the best candidate result.
Sometimes even the best match isn’t good enough.
So the system asks again: “Does this result actually answer the query?”
if not best_response or not is_relevant(user_query, best_response):
print("\n> No good match in database. Fetching live data...\n")
print(get_finance_response(user_query))
If the match is weak or irrelevant, the system falls back to live data retrieval.
If the match is good, the system generates a natural language answer from the stored payload.
else:
print("\n> Found in database:\n")
print(get_response(best_response))
print(f"\n(Qdrant match score: {best_score:.2f})")
This uses get_response() from llm.py to take the raw stock payload and turn it into a nicely formatted text for the user.
It also shows the match score so you know how close the result was.
End-to-end flow:
The work flow: