ashishbangwal commited on
Commit
6c7823c
Β·
1 Parent(s): 9daa604
.gitignore ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Ignore ChromaDB storage folder
2
+ **/chroma_storage/*
3
+
4
+ # Python artifacts
5
+ __pycache__/
6
+ *.py[cod]
7
+ *.pkl
8
+ *.db
9
+ *.sqlite3
10
+
11
+ # Environment files
12
+ .env
13
+ *.env
14
+
15
+ # OS-specific
16
+ .DS_Store
17
+ Thumbs.db
18
+
19
+ # Jupyter/IPython
20
+ .ipynb_checkpoints/
21
+
22
+ # Virtual environments
23
+ .venv/
24
+ .env/
25
+
26
+ # Logs and cache
27
+ *.log
28
+ *.cache/
29
+ .cache/
30
+
Dockerfile ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Base Python image
2
+ FROM python:3.10-slim
3
+
4
+ # Set environment variables
5
+ ENV PYTHONDONTWRITEBYTECODE=1 \
6
+ PYTHONUNBUFFERED=1
7
+
8
+ # Create a user to avoid root-level issues with ChromaDB
9
+ RUN adduser --disabled-password --gecos "" appuser
10
+ USER appuser
11
+
12
+ # Create working directory
13
+ WORKDIR /home/appuser/app
14
+
15
+ # Copy project files
16
+ COPY --chown=appuser:appuser . .
17
+
18
+ # Install Python dependencies
19
+ RUN pip install --upgrade pip \
20
+ && pip install -r requirement.txt
21
+
22
+ # If using .env file, install python-dotenv and make sure app reads it
23
+ RUN pip install python-dotenv
24
+
25
+ # Expose FastAPI default port
26
+ EXPOSE 8000
27
+ # Expose Streamlit default port
28
+ EXPOSE 8501
29
+
30
+ # Start FastAPI app in the background, then Streamlit
31
+ CMD uvicorn streamlit_app.main_api:app --host 0.0.0.0 --port 8000 & \
32
+ streamlit run streamlit_app/app.py --server.port 8501
33
+
README.md CHANGED
@@ -1,10 +1,31 @@
 
 
 
1
  ---
2
- title: RagaAI
3
- emoji: 🌍
4
- colorFrom: blue
5
- colorTo: yellow
6
- sdk: docker
7
- pinned: false
8
- ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
1
+ ## Market Brief Agent
2
+
3
+ ### Workflow Diagram
4
  ---
5
+ ![](diagram.jpeg)
6
+
7
+ #### Overview
8
+ * An acyclic workflow where the user interacts through a Streamlit App.
9
+ * User's query is first parsed by the Orchestrator API endpoint that returns the result for what tools to use along with the result of those tools' execution.
10
+ * The original user query and the generated supporting context are then passed to the final response synthesizer.
11
+ * Final response is streamed back to the Streamlit app again via API communication.
12
+ * User can further instruct to listen to the generated response using Deepgram's voice models.
13
+
14
+ ### Tools
15
+
16
+ **All tools are accessible through an API interface**
17
+ * `/data/get_historical_data` : This tool brings historical changes in a particular given stock. Must provide a YFinance ticker as a parameter.
18
+ * `/data/get_earning_metrics` : This tool generates the stock earnings summary over the past 3–4 years using YFinance earning metrics.
19
+ * `/data/get_portfolio_data` : This tool brings a current portfolio snapshot/updates. *Currently only supports IND portfolio*.
20
+ * `/data/get_portfolio_data` : This is a ***RAG*** based tool. It uses a company's prior documents as a knowledge base and uses semantic similarity to provide context on company-related user queries.
21
+ * `/data/get_portfolio_data` : Tool to make orchestration decisionsβ€”i.e., which tool to call with what parameters.
22
+ * `/data/get_portfolio_data` : Tool to generate the final user-friendly response with **guardrails** to avoid giving aggressive financial advice.
23
+
24
+ ### Deployment
25
+
26
+ Fully functional **Docker**-based deployment for maintainability and scalability.
27
+
28
+ ```DOCKER FILE CODE```
29
 
30
+ #### FYIs
31
+ * Voice I/O is slow because of Streamlit voice processing and Deepgram API latency.
data_ingetion/__init__.py ADDED
File without changes
data_ingetion/data_api.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter
2
+ from pydantic import BaseModel
3
+ from .market_data import price_change, earning_summary, portfolio_data
4
+ from .vectroDB import get_relevant_chunks
5
+
6
+ app = APIRouter()
7
+
8
+
9
+ class HistoricalData(BaseModel):
10
+ symbol: str
11
+ period: int
12
+
13
+
14
+ class EarningReq(BaseModel):
15
+ symbol: str
16
+
17
+
18
+ class PortfolioReq(BaseModel):
19
+ region: str
20
+
21
+
22
+ class KnowledgeReq(BaseModel):
23
+ query: str
24
+
25
+
26
+ @app.post("/get_historical_data")
27
+ def get_historical_data(req: HistoricalData):
28
+ symbol = req.symbol
29
+ period = req.period
30
+ return {"response": price_change(symbol, period)}
31
+
32
+
33
+ @app.post("/get_earning_metrics")
34
+ def get_eraning_metrics(req: EarningReq):
35
+ return {"response": earning_summary(req.symbol)}
36
+
37
+
38
+ @app.post("/get_portfolio_data")
39
+ def get_portfolio_data(req: PortfolioReq):
40
+ return {"response": portfolio_data(req.region)}
41
+
42
+
43
+ @app.post("/get_knowledge")
44
+ def get_knowledge(req: KnowledgeReq):
45
+ return {"response": get_relevant_chunks(req.query)}
data_ingetion/firms_report/Investment Strategy & Risk Management Guide.pdf ADDED
Binary file (82.1 kB). View file
 
data_ingetion/firms_report/Quarterly Research Report - Global Equity Markets.pdf ADDED
Binary file (71.5 kB). View file
 
data_ingetion/market_data.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import yfinance as yf
2
+ import datetime
3
+ import pandas as pd
4
+ import os
5
+
6
+
7
+ def portfolio_data(region: str = "IND"):
8
+ if region == "IND":
9
+ portfolio = "/data_ingetion/portfolios/IND.csv"
10
+ else:
11
+ portfolio = "/data_ingetion/portfolios/US.csv"
12
+
13
+ pc_file = os.getcwd() + "/data_ingetion/portfolios/portfolio_change.csv"
14
+
15
+ today = datetime.datetime.today().strftime("%Y-%m-%d")
16
+
17
+ portfolio_change = pd.read_csv(pc_file)
18
+ df = pd.read_csv(os.getcwd() + portfolio)
19
+
20
+ if today not in portfolio_change.columns:
21
+ pc = []
22
+
23
+ for ticker in df["Ticker Symbol"]:
24
+ pc.append(price_change(ticker, 7, True))
25
+
26
+ portfolio_change[today] = pc
27
+
28
+ portfolio_change.to_csv(pc_file, index=False)
29
+
30
+ df["Price Change%"] = portfolio_change[today]
31
+ df.drop("Date of Investment", axis=1, inplace=True)
32
+
33
+ return f"Portfolio and change in prices in part 7 days : \n {str(df)}"
34
+
35
+
36
+ def price_change(symbol, days: int, raw: bool = False):
37
+ stock = yf.Ticker(ticker=symbol)
38
+
39
+ today_price = stock.history(period="1d")
40
+
41
+ # Target date: 1 year and 7 days ago
42
+ target_date = datetime.datetime.today() - datetime.timedelta(days=days)
43
+ start_date = (target_date - datetime.timedelta(days=5)).strftime("%Y-%m-%d")
44
+ end_date = (target_date + datetime.timedelta(days=1)).strftime("%Y-%m-%d")
45
+
46
+ # Fetch range around the target date
47
+ history = stock.history(start=start_date, end=end_date)
48
+
49
+ # Get the latest available price before or on the target date
50
+ past_price = history[history.index <= target_date.strftime("%Y-%m-%d")].iloc[-1]
51
+
52
+ percentage_difference = (
53
+ (today_price["Close"] - past_price["Close"]) / past_price["Close"]
54
+ ).values[0] * 100
55
+
56
+ response = (
57
+ f"Price change for {symbol} in past {days} days is {percentage_difference:.2f}%"
58
+ )
59
+ if raw:
60
+ return percentage_difference
61
+ return response
62
+
63
+
64
+ def earning_summary(symbol):
65
+ stock = yf.Ticker(ticker=symbol)
66
+ metrics = [
67
+ "EBITDA",
68
+ "Total Expenses",
69
+ "Basic EPS",
70
+ "Net Income",
71
+ "Gross Profit",
72
+ "Total Revenue",
73
+ ]
74
+ currency = stock.fast_info.currency
75
+ income_metrics = stock.income_stmt
76
+
77
+ scaler = 1e7 if currency == "INR" else 1e6
78
+ units = "carore" if currency == "INR" else "millions"
79
+
80
+ selected_metric = income_metrics.loc[metrics] / scaler
81
+
82
+ response = f"Earning metrics for {symbol} are following in {currency} currency in {units}: \n {selected_metric}"
83
+
84
+ return response
data_ingetion/portfolios/IND.csv ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Stock Name,Ticker Symbol,Sector,Investment Percentage,Date of Investment
2
+ Reliance Industries Ltd,RELIANCE.NS,Energy,12.5,2023-03-15
3
+ Tata Consultancy Services,TCS.NS,IT,10.8,2023-01-20
4
+ HDFC Bank Ltd,HDFCBANK.NS,Bank,9.2,2022-11-10
5
+ Infosys Ltd,INFY.NS,IT,8.7,2023-02-28
6
+ ICICI Bank Ltd,ICICIBANK.NS,Bank,7.9,2023-04-12
7
+ Hindustan Unilever Ltd,HINDUNILVR.NS,FMCG,6.8,2022-12-05
8
+ State Bank of India,SBIN.NS,Bank,6.5,2023-05-18
9
+ Bharti Airtel Ltd,BHARTIARTL.NS,Telecom,5.9,2023-03-22
10
+ ITC Ltd,ITC.NS,FMCG,5.4,2022-10-30
11
+ Larsen & Toubro Ltd,LT.NS,Infrastructure,5.2,2023-01-08
12
+ Wipro Ltd,WIPRO.NS,IT,4.8,2023-02-14
13
+ Mahindra & Mahindra,M&M.NS,Auto,4.3,2023-04-05
14
+ Dr Reddy's Laboratories,DRREDDY.NS,Pharma,4.1,2022-09-25
15
+ Asian Paints Ltd,ASIANPAINT.NS,Paints,3.9,2023-06-02
16
+ Bajaj Finance Ltd,BAJFINANCE.NS,NBFC,4.0,2023-03-08
data_ingetion/portfolios/US.csv ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Stock Name,Ticker Symbol,Sector,Investment Percentage,Date of Investment
2
+ Apple Inc,AAPL,Technology,11.8,2023-02-15
3
+ Microsoft Corporation,MSFT,Technology,10.5,2023-01-12
4
+ Amazon.com Inc,AMZN,Consumer Discretionary,9.3,2022-12-08
5
+ Alphabet Inc Class A,GOOGL,Technology,8.7,2023-03-22
6
+ Tesla Inc,TSLA,Auto,7.9,2023-04-18
7
+ JPMorgan Chase & Co,JPM,Bank,7.2,2022-11-25
8
+ Johnson & Johnson,JNJ,Healthcare,6.8,2023-01-30
9
+ Berkshire Hathaway Inc Class B,BRK-B,Financial Services,6.5,2022-10-15
10
+ UnitedHealth Group Inc,UNH,Healthcare,5.9,2023-05-10
11
+ Procter & Gamble Co,PG,Consumer Staples,5.4,2023-02-28
12
+ Visa Inc Class A,V,Financial Services,5.1,2023-03-15
13
+ Coca-Cola Company,KO,Consumer Staples,4.8,2022-09-20
14
+ Home Depot Inc,HD,Consumer Discretionary,4.2,2023-04-05
15
+ Mastercard Inc Class A,MA,Financial Services,3.9,2023-06-12
16
+ Walt Disney Company,DIS,Entertainment,2.0,2023-01-25
data_ingetion/portfolios/portfolio_change.csv ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Stock Name,2025-05-27,2025-05-28
2
+ Reliance Industries Ltd,-0.175487859247769,-1.1404930950667433
3
+ Tata Consultancy Services,0.0114313334524853,-0.7969850038883989
4
+ HDFC Bank Ltd,0.595332616257644,-0.17127095414659563
5
+ Infosys Ltd,0.608740237640537,0.2103952074035703
6
+ ICICI Bank Ltd,0.570162252856838,0.6298953390888131
7
+ Hindustan Unilever Ltd,1.67014872857291,-0.08037152257869686
8
+ State Bank of India,1.02462770226125,1.2006114216934662
9
+ Bharti Airtel Ltd,1.92935065194481,1.843620059585048
10
+ ITC Ltd,-0.229937919989491,-1.1680789821939643
11
+ Larsen & Toubro Ltd,2.02365463825782,1.5965463591007245
12
+ Wipro Ltd,-0.584680899688634,-1.3908837552335025
13
+ Mahindra & Mahindra,-0.773623344928187,-3.0057884560591495
14
+ Dr Reddy's Laboratories,1.55203404327962,1.461584480095272
15
+ Asian Paints Ltd,1.3939710157998,-0.3978529476043621
16
+ Bajaj Finance Ltd,1.01332745897125,0.7610763794509378
data_ingetion/vectroDB.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
2
+ import chromadb
3
+ from openai import OpenAI
4
+ import pypdf
5
+ import uuid
6
+ import os
7
+
8
+ VECTOR_NAME = "database"
9
+ EMBEDDING_MODEL = "togethercomputer/m2-bert-80M-2k-retrieval"
10
+
11
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
12
+ CHROMA_PATH = os.path.join(BASE_DIR, "chroma_storage")
13
+
14
+
15
+ api_key = os.getenv("TOGETHER_API")
16
+ ai_client = OpenAI(api_key=api_key, base_url="https://api.together.xyz/v1")
17
+
18
+
19
+ def extract_pdf(pdf_path: str) -> str:
20
+
21
+ text = ""
22
+ with open(pdf_path, "rb") as file:
23
+ reader = pypdf.PdfReader(file)
24
+ for page_num in range(len(reader.pages)):
25
+ page = reader.pages[page_num]
26
+ text += page.extract_text()
27
+ text += "\n--PAGE BREAK--\n"
28
+
29
+ return text
30
+
31
+
32
+ def create_vectorDB():
33
+ docs_paths = os.listdir(os.getcwd() + "/data_ingetion/firms_report/")
34
+
35
+ complete_text = ""
36
+
37
+ for doc_path in docs_paths:
38
+ complete_text += extract_pdf(
39
+ os.getcwd() + "/data_ingetion/firms_report/" + doc_path
40
+ )
41
+ complete_text += "\n\n"
42
+
43
+ splitter = RecursiveCharacterTextSplitter(
44
+ chunk_size=512,
45
+ chunk_overlap=84,
46
+ length_function=len,
47
+ is_separator_regex=False,
48
+ )
49
+
50
+ processed_docs = splitter.split_text(complete_text)
51
+ db_client = chromadb.PersistentClient(path=CHROMA_PATH)
52
+ collection = db_client.create_collection(VECTOR_NAME)
53
+
54
+ response = ai_client.embeddings.create(input=processed_docs, model=EMBEDDING_MODEL)
55
+ embeddings = [item.embedding for item in response.data]
56
+ unique_ids = [str(uuid.uuid4()) for _ in range(len(embeddings))]
57
+ collection.add(documents=processed_docs, embeddings=embeddings, ids=unique_ids)
58
+
59
+ return collection.name
60
+
61
+
62
+ def get_relevant_chunks(query: str):
63
+
64
+ db_client = chromadb.PersistentClient(path=CHROMA_PATH)
65
+ found = VECTOR_NAME in [c.name for c in db_client.list_collections()]
66
+
67
+ if found:
68
+ collection = db_client.get_collection(VECTOR_NAME)
69
+ else:
70
+ collection = db_client.get_collection(create_vectorDB())
71
+
72
+ response = ai_client.embeddings.create(input=query, model=EMBEDDING_MODEL)
73
+ QE = response.data[0].embedding
74
+
75
+ relevant_chunks = collection.query(query_embeddings=QE, n_results=4)
76
+
77
+ processed = ""
78
+
79
+ for idx, doc in enumerate(relevant_chunks["documents"][0], start=1):
80
+ processed += f"Chunks number {idx}\n\n"
81
+ processed += doc + "\n\n"
82
+
83
+ return processed
diagram.jpeg ADDED
main_api.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from data_ingetion.data_api import app as data_app
3
+ from orchestrator.orchestrator_api import app as or_app
4
+
5
+ app = FastAPI()
6
+
7
+ app.include_router(data_app, prefix="/data")
8
+ app.include_router(or_app, prefix="/orchestrator")
orchestrator/__init__.py ADDED
File without changes
orchestrator/orchestrator.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from openai import OpenAI
2
+ from .prompts import ORCHESTRATOR_SYS_PROMPT, FINAL_SYS_PROMPT
3
+ import requests
4
+ import json
5
+ import os
6
+
7
+ api_key = os.getenv("TOGETHER_API")
8
+ client = OpenAI(api_key=api_key, base_url="https://api.together.xyz/v1")
9
+
10
+
11
+ def get_orchertration_resposne(query, history):
12
+
13
+ history = history[::-1][:5]
14
+ response = client.chat.completions.create(
15
+ model="meta-llama/Llama-3.3-70B-Instruct-Turbo-Free",
16
+ messages=[
17
+ {
18
+ "role": "system",
19
+ "content": ORCHESTRATOR_SYS_PROMPT,
20
+ },
21
+ *history,
22
+ {"role": "user", "content": "Query: " + query},
23
+ ],
24
+ )
25
+ r = response.choices[0].message.content
26
+ data = json.loads(str(r))
27
+
28
+ if data["tool"] == "get_change":
29
+ result = requests.post(
30
+ url="http://127.0.0.1:8000/data/get_historical_data",
31
+ json=data["parameters"],
32
+ ).json()
33
+
34
+ elif data["tool"] == "get_earning":
35
+ result = requests.post(
36
+ url="http://127.0.0.1:8000/data/get_earning_metrics",
37
+ json=data["parameters"],
38
+ ).json()
39
+
40
+ elif data["tool"] == "get_portfolio_status":
41
+ result = requests.post(
42
+ url="http://127.0.0.1:8000/data/get_portfolio_data", json=data["parameters"]
43
+ ).json()
44
+
45
+ elif data["tool"] == "get_knowledge":
46
+ result = requests.post(
47
+ url="http://127.0.0.1:8000/data/get_knowledge", json=data["parameters"]
48
+ ).json()
49
+
50
+ elif data["tool"] == None:
51
+ return data["parameters"]
52
+
53
+ else:
54
+ result = {
55
+ "response": "An error occured internally please communicate this to user firmly"
56
+ }
57
+ return result
58
+
59
+
60
+ def final_response(query, context, history):
61
+
62
+ history = history[::-1][:5]
63
+ response = client.chat.completions.create(
64
+ model="meta-llama/Llama-3.3-70B-Instruct-Turbo-Free",
65
+ messages=[
66
+ {
67
+ "role": "system",
68
+ "content": FINAL_SYS_PROMPT,
69
+ },
70
+ *history,
71
+ {"role": "user", "content": f"Query : {query} \n\n Context: {context}"},
72
+ ],
73
+ stream=True,
74
+ )
75
+
76
+ for chunk in response:
77
+ yield chunk.choices[0].delta.content or ""
orchestrator/orchestrator_api.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter
2
+ from fastapi.responses import StreamingResponse
3
+ from pydantic import BaseModel
4
+
5
+ from .orchestrator import get_orchertration_resposne, final_response
6
+
7
+ app = APIRouter()
8
+
9
+
10
+ class ODReq(BaseModel):
11
+ query: str
12
+ history: list = []
13
+
14
+
15
+ class FinalReq(BaseModel):
16
+ query: str
17
+ history: list = []
18
+ context: str = ""
19
+
20
+
21
+ @app.post("/orchestrator_decision")
22
+ def get_OD(req: ODReq):
23
+ return get_orchertration_resposne(req.query, req.history)
24
+
25
+
26
+ @app.post("/final_response")
27
+ def get_final(req: FinalReq):
28
+ return StreamingResponse(
29
+ final_response(req.query, req.context, req.history), media_type="text/plain"
30
+ )
orchestrator/prompts.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ORCHESTRATOR_SYS_PROMPT = """
2
+ You are in role of orchestrator and your task is to orchestrate a LLM based AI agent workflow working with tools to achive the goal of being a helpful Finance Assistant.
3
+
4
+ You are provided with multiple tools and your task is to call these tool based on user request and use the returned response from tools to answer the user query.
5
+
6
+ Tools available:
7
+
8
+ To get information on stocks:
9
+
10
+ get_change() -> use this tool to get infomration on change in stock price in given timeframe ie. Price[today]-price[today-period] percentage change.
11
+ parameters -> symbol: str the symbol of that ticker/stock YFinance style (use .NS for indian stocks), period: int number of days to get change for.
12
+
13
+ get_earning() -> use this tool to get infomration on year on year earning reports on the stock.
14
+ parameters -> symbol: str the symbol of the ticker/stock YFinance style (use .NS for indian stocks) for which we need earning metrics.
15
+
16
+ get_portfolio_status() -> use this tool to get information on current portfolio structure and price changes to make informed decisions.
17
+ parameters -> region:str["IND","US"] Region for which we are fetching portfolio result. Currently only IND (india) and US(USA) supported
18
+
19
+ get_knowledge -> use this tool to get prior knowledge as context about the firm, its a RAG based tool ie. you will give the augmented user query for better retrival results.
20
+ parameters -> query:str User query augmented/expanded by you for better retrival results
21
+
22
+
23
+
24
+ You have to respond in structured json format, mentioning tool name and prameter json.
25
+ If the query is general and can be answered without any tool put "tool"=null (JSON null) and parameters just have one key value pair of "response":"your_response" to the query.
26
+
27
+ For example:
28
+
29
+ Query : What is the change in apple stock in past 1 month.
30
+
31
+ response :
32
+ {
33
+ "tool":"get_change",
34
+ "parameters" : {
35
+ "symbol" : "AAPL",
36
+ "period" : 31
37
+ }
38
+ }
39
+
40
+ Query : Hi, how are you ?
41
+
42
+ response :
43
+ {
44
+ "tool":null,
45
+ "parameters" : {
46
+ "response" : "Hey! I am fine. How can i help you today?"
47
+ }
48
+ }
49
+
50
+
51
+ Dont add any comments around json, you should only respond in valid json format only.
52
+ """
53
+ FINAL_SYS_PROMPT = """
54
+ You task to to generate final response of a long workflow/reseach. You will be provided with Query that is the original query and some context that is derived from different tools your task is to create a condensed output to effectively answer the user query. Try to be consise and to the point. Also add some disclamers if there is any uncertanity.
55
+ """
requirements.txt ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ aenum==3.1.16
2
+ aiofiles==24.1.0
3
+ aiohappyeyeballs==2.6.1
4
+ aiohttp==3.12.2
5
+ aiosignal==1.3.2
6
+ altair==5.5.0
7
+ annotated-types==0.7.0
8
+ anyio==4.9.0
9
+ asgiref==3.8.1
10
+ async-timeout==5.0.1
11
+ asyncio==3.4.3
12
+ attrs==25.3.0
13
+ backoff==2.2.1
14
+ bcrypt==4.3.0
15
+ beautifulsoup4==4.13.4
16
+ blinker==1.9.0
17
+ build==1.2.2.post1
18
+ cachetools==5.5.2
19
+ certifi==2025.4.26
20
+ cffi==1.17.1
21
+ charset-normalizer==3.4.2
22
+ chromadb==1.0.11
23
+ click==8.2.1
24
+ coloredlogs==15.0.1
25
+ curl_cffi==0.11.1
26
+ dataclasses-json==0.6.7
27
+ deepgram-sdk==4.0.0
28
+ Deprecated==1.2.18
29
+ deprecation==2.1.0
30
+ distro==1.9.0
31
+ dnspython==2.7.0
32
+ durationpy==0.10
33
+ email_validator==2.2.0
34
+ exceptiongroup==1.3.0
35
+ fastapi==0.115.9
36
+ fastapi-cli==0.0.7
37
+ filelock==3.18.0
38
+ flatbuffers==25.2.10
39
+ frozendict==2.4.6
40
+ frozenlist==1.6.0
41
+ fsspec==2025.5.1
42
+ gitdb==4.0.12
43
+ GitPython==3.1.44
44
+ google-auth==2.40.2
45
+ googleapis-common-protos==1.70.0
46
+ grpcio==1.71.0
47
+ h11==0.16.0
48
+ hf-xet==1.1.2
49
+ httpcore==1.0.9
50
+ httptools==0.6.4
51
+ httpx==0.28.1
52
+ huggingface-hub==0.32.2
53
+ humanfriendly==10.0
54
+ idna==3.10
55
+ importlib_metadata==8.6.1
56
+ importlib_resources==6.5.2
57
+ Jinja2==3.1.6
58
+ jiter==0.10.0
59
+ jsonpatch==1.33
60
+ jsonpointer==3.0.0
61
+ jsonschema==4.24.0
62
+ jsonschema-specifications==2025.4.1
63
+ kubernetes==32.0.1
64
+ langchain-core==0.3.62
65
+ langchain-text-splitters==0.3.8
66
+ langsmith==0.3.42
67
+ markdown-it-py==3.0.0
68
+ MarkupSafe==3.0.2
69
+ marshmallow==3.26.1
70
+ mdurl==0.1.2
71
+ mmh3==5.1.0
72
+ mpmath==1.3.0
73
+ multidict==6.4.4
74
+ multitasking==0.0.11
75
+ mypy_extensions==1.1.0
76
+ narwhals==1.41.0
77
+ numpy==2.2.6
78
+ oauthlib==3.2.2
79
+ onnxruntime==1.22.0
80
+ openai==1.82.0
81
+ opentelemetry-api==1.33.1
82
+ opentelemetry-exporter-otlp-proto-common==1.33.1
83
+ opentelemetry-exporter-otlp-proto-grpc==1.33.1
84
+ opentelemetry-instrumentation==0.54b1
85
+ opentelemetry-instrumentation-asgi==0.54b1
86
+ opentelemetry-instrumentation-fastapi==0.54b1
87
+ opentelemetry-proto==1.33.1
88
+ opentelemetry-sdk==1.33.1
89
+ opentelemetry-semantic-conventions==0.54b1
90
+ opentelemetry-util-http==0.54b1
91
+ orjson==3.10.18
92
+ overrides==7.7.0
93
+ packaging==24.2
94
+ pandas==2.2.3
95
+ peewee==3.18.1
96
+ pillow==11.2.1
97
+ platformdirs==4.3.8
98
+ posthog==4.2.0
99
+ propcache==0.3.1
100
+ protobuf==5.29.4
101
+ pyarrow==20.0.0
102
+ pyasn1==0.6.1
103
+ pyasn1_modules==0.4.2
104
+ pycparser==2.22
105
+ pydantic==2.11.5
106
+ pydantic_core==2.33.2
107
+ pydeck==0.9.1
108
+ Pygments==2.19.1
109
+ pypdf==5.5.0
110
+ PyPika==0.48.9
111
+ pyproject_hooks==1.2.0
112
+ python-dateutil==2.9.0.post0
113
+ python-dotenv==1.1.0
114
+ python-multipart==0.0.20
115
+ pytz==2025.2
116
+ PyYAML==6.0.2
117
+ referencing==0.36.2
118
+ requests==2.32.3
119
+ requests-oauthlib==2.0.0
120
+ requests-toolbelt==1.0.0
121
+ rich==14.0.0
122
+ rich-toolkit==0.14.6
123
+ rpds-py==0.25.1
124
+ rsa==4.9.1
125
+ shellingham==1.5.4
126
+ six==1.17.0
127
+ smmap==5.0.2
128
+ sniffio==1.3.1
129
+ soupsieve==2.7
130
+ starlette==0.45.3
131
+ streamlit==1.45.1
132
+ sympy==1.14.0
133
+ tenacity==9.1.2
134
+ tokenizers==0.21.1
135
+ toml==0.10.2
136
+ tomli==2.2.1
137
+ tornado==6.5.1
138
+ tqdm==4.67.1
139
+ typer==0.16.0
140
+ typing-inspect==0.9.0
141
+ typing-inspection==0.4.1
142
+ typing_extensions==4.13.2
143
+ tzdata==2025.2
144
+ urllib3==2.4.0
145
+ uvicorn==0.34.2
146
+ uvloop==0.21.0
147
+ verboselogs==1.7
148
+ watchdog==6.0.0
149
+ watchfiles==1.0.5
150
+ websocket-client==1.8.0
151
+ websockets==15.0.1
152
+ wrapt==1.17.2
153
+ yarl==1.20.0
154
+ yfinance==0.2.61
155
+ zipp==3.22.0
156
+ zstandard==0.23.0
streamlit_app/app.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import requests
3
+ from deepgram import (
4
+ DeepgramClient,
5
+ DeepgramClientOptions,
6
+ PrerecordedOptions,
7
+ FileSource,
8
+ )
9
+ from io import BytesIO
10
+ import os
11
+
12
+ # Page configuration
13
+ st.set_page_config(page_title="Market Brief Chat", page_icon="πŸ’¬", layout="wide")
14
+ DG_API = os.getenv("DG_API")
15
+
16
+ # Initialize session state for chat history
17
+ if "messages" not in st.session_state:
18
+ st.session_state.messages = [
19
+ {"role": "assistant", "content": "How can I help you today?"}
20
+ ]
21
+ if "um" not in st.session_state:
22
+ st.session_state.um = None
23
+
24
+ # Initialize other session state variables
25
+ if "is_recording" not in st.session_state:
26
+ st.session_state.is_recording = False
27
+
28
+
29
+ def STT(buffer):
30
+
31
+ config: DeepgramClientOptions = DeepgramClientOptions(api_key=DG_API)
32
+ deepgram: DeepgramClient = DeepgramClient("", config)
33
+ payload: FileSource = {
34
+ "buffer": buffer,
35
+ }
36
+ # STEP 2: Configure Deepgram options for audio analysis
37
+ options = PrerecordedOptions(
38
+ model="nova-3",
39
+ smart_format=True,
40
+ )
41
+ # STEP 3: Call the transcribe_file method with the text payload and options
42
+ response = deepgram.listen.rest.v("1").transcribe_file(payload, options)
43
+
44
+ data = response.to_json()
45
+ transcript = data["results"]["channels"][0]["alternatives"][0]["transcript"]
46
+
47
+ return transcript
48
+
49
+
50
+ def TTS(text):
51
+ DEEPGRAM_URL = "https://api.deepgram.com/v1/speak?model=aura-2-thalia-en"
52
+ DEEPGRAM_API_KEY = DG_API
53
+
54
+ payload = {"text": text}
55
+
56
+ headers = {
57
+ "Authorization": f"Token {DEEPGRAM_API_KEY}",
58
+ "Content-Type": "application/json",
59
+ }
60
+
61
+ # Create a BytesIO buffer to store audio
62
+ audio_buffer = BytesIO()
63
+
64
+ response = requests.post(DEEPGRAM_URL, headers=headers, json=payload)
65
+
66
+ audio_buffer.write(response.content)
67
+
68
+ # Move cursor to the beginning of the buffer
69
+ audio_buffer.seek(0)
70
+ return audio_buffer
71
+
72
+
73
+ # App title
74
+ st.markdown("<h1>πŸ’¬ Market Brief Chat</h1>", unsafe_allow_html=True)
75
+ st.markdown("---")
76
+
77
+
78
+ # Display chat history
79
+ chat_col, audio_col = st.columns([0.85, 0.15])
80
+ with chat_col:
81
+ c = st.container(height=400, border=True)
82
+ with c:
83
+ for message in st.session_state.messages:
84
+ with st.chat_message(message["role"]):
85
+ st.write(message["content"])
86
+
87
+ with audio_col:
88
+ # Voice input buttonif
89
+ data = st.audio_input(label="🎀 Record")
90
+ if data:
91
+ st.session_state.um = STT(data)
92
+ if st.button("πŸ”Š listen"):
93
+ text = st.session_state.messages[-1]["content"]
94
+ buffer = TTS(text)
95
+ st.audio(data=buffer)
96
+ st.success("Playing")
97
+
98
+ # Voice input button (beside text input conceptually)
99
+ # Handle text input
100
+ user_input = st.chat_input("Ask me about market trends...")
101
+ st.session_state.um = user_input
102
+ if st.session_state.um:
103
+ with c:
104
+ with st.chat_message("user"):
105
+ st.markdown(user_input)
106
+ st.session_state.messages.append(
107
+ {"role": "user", "content": st.session_state.um}
108
+ )
109
+
110
+ data = {"query": user_input, "history": []}
111
+ or_response = requests.post(
112
+ url="http://127.0.0.1:8000/orchestrator/orchestrator_decision", json=data
113
+ ).json()
114
+
115
+ print(or_response)
116
+
117
+ agent_response = ""
118
+ data = {"query": user_input, "context": or_response["response"]}
119
+ full_response = requests.post(
120
+ url="http://127.0.0.1:8000/orchestrator/final_response",
121
+ json=data,
122
+ stream=True,
123
+ )
124
+ with st.chat_message("assistant"):
125
+ placeholder = st.empty()
126
+ for chunk in full_response.iter_content(
127
+ decode_unicode=True, chunk_size=None
128
+ ):
129
+ agent_response += chunk
130
+ placeholder.markdown(agent_response + "β–Œ")
131
+
132
+ st.session_state.messages.append(
133
+ {"role": "assistant", "content": agent_response}
134
+ )
135
+ st.session_state.um = None
136
+ st.rerun()