Merge pull request #189 from sahithi37/main

Added Agentic RAG Math Tutor Agent (Human-in-the-Loop)
This commit is contained in:
Shubham Saboo
2025-05-04 15:33:46 -05:00
committed by GitHub
24 changed files with 2220 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
# 🧠 Math Tutor Agent Agentic RAG with Feedback Loop
This project implements an **Agentic-RAG architecture** to simulate a math professor that solves **JEE-level math questions** with step-by-step explanations. The system smartly routes queries between a vector database and web search, applies input/output guardrails, and incorporates human feedback for continuous learning.
---
## 📌 Features
-**Input Guardrails** (DSPy): Accepts only academic math questions.
- 📚 **Knowledge Base Search**: Uses **Qdrant Vector DB** with OpenAI Embeddings to match known questions.
- 🌐 **Web Fallback**: Integrates **Tavily API** when no good match is found.
- ✍️ **GPT-3.5 Turbo Explanations**: Generates step-by-step math solutions.
- 🛡️ **Output Guardrails**: Filters for correctness and safety.
- 👍 **Human-in-the-Loop Feedback**: Users rate answers (Yes/No), logged for future learning.
- 📊 **Benchmarking**: Evaluated on **JEEBench** dataset with adjustable question limits.
- 💻 **Streamlit UI**: Interactive dashboard with multiple tabs.
---
## 🚀 Architecture Flow
![image](https://github.com/user-attachments/assets/9197a918-d14e-4759-9b28-8a90dadd1baf)
## 📚 Knowledge Base
- **Dataset:** [JEEBench (HuggingFace)](https://huggingface.co/datasets/daman1209arora/jeebench)
- **Vector DB:** Qdrant (with OpenAI Embeddings)
- **Storage:** Built with `llama-index` to persist embeddings and perform top-1 similarity search
---
## 🌐 Web Search
- Uses **Tavily API** for fallback search when the KB doesn't contain a good match
- Fetched content is piped into **GPT-3.5 Turbo** for clean explanation
---
## 🔐 Guardrails
- **Input Guardrail (DSPy):** Accepts only math-related academic questions
- **Output Guardrail (DSPy):** Blocks hallucinated or off-topic content
---
## 👨‍🏫 Human-in-the-Loop Feedback
- Streamlit UI allows students to give 👍 / 👎 after seeing the answer
- Feedback is logged to a local JSON file for future improvement
---
## 📊 Benchmarking
- Evaluated on **50 random JEEBench Math Questions**
- **Current Accuracy:** 66%
- Benchmark results saved to: `benchmark/results.csv`
---
## 🚀 Demo
To run the app with Streamlit:
```bash
streamlit run app/streamlit.py
----

View File

@@ -0,0 +1,51 @@
# Add the project root to the Python path
import sys
import os
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
import pandas as pd
import time
from datetime import datetime
from rag.query_router import answer_math_question
from data.load_gsm8k_data import load_jeebench_dataset
def benchmark_math_agent(limit: int = 10):
# ✅ Always filter math-only questions
df = load_jeebench_dataset()
df = df.head(limit) # Limit the number of questions for benchmarking
total = len(df)
correct = 0
results = []
for idx, row in df.iterrows():
question = row["question"]
expected = row["gold"]
start = time.time()
try:
response = answer_math_question(question)
is_correct = expected.lower() in response.lower()
if is_correct:
correct += 1
results.append({
"Question": question,
"Expected": expected,
"Predicted": response,
"Correct": is_correct,
"TimeTakenSec": round(time.time() - start, 2)
})
except Exception as e:
results.append({
"Question": question,
"Expected": expected,
"Predicted": f"Error: {e}",
"Correct": False,
"TimeTakenSec": None
})
df_result = pd.DataFrame(results)
accuracy = correct / total * 100
return df_result, accuracy

View File

@@ -0,0 +1,120 @@
import streamlit as st
import sys
import os
import json
import pandas as pd
# Add root to import path
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
from app.benchmark import benchmark_math_agent # Add this import
from data.load_gsm8k_data import load_jeebench_dataset
from rag.query_router import answer_math_question
st.set_page_config(page_title="Math Agent 🧮", layout="wide")
st.title("🧠 Math Tutor Agent Dashboard")
tab1, tab2, tab3 = st.tabs(["📘 Ask a Question", "📁 View Feedback", "📊 Benchmark Results"])
# ---------------- TAB 1: Ask a Question ---------------- #
with tab1:
st.subheader("📘 Ask a Math Question")
st.markdown("Enter any math question below. The agent will try to explain it step-by-step.")
if "last_question" not in st.session_state:
st.session_state["last_question"] = ""
if "last_answer" not in st.session_state:
st.session_state["last_answer"] = ""
if "feedback_given" not in st.session_state:
st.session_state["feedback_given"] = False
user_question = st.text_input("Your Question:")
if st.button("Get Answer"):
if user_question:
with st.spinner("Thinking..."):
answer = answer_math_question(user_question)
st.session_state["last_question"] = user_question
st.session_state["last_answer"] = answer
st.session_state["feedback_given"] = False
if st.session_state["last_answer"]:
st.markdown("### ✅ Answer:")
st.success(st.session_state["last_answer"])
if not st.session_state["feedback_given"]:
st.markdown("### 🙋 Was this helpful?")
col1, col2 = st.columns(2)
with col1:
if st.button("👍 Yes"):
feedback = "positive"
st.session_state["feedback_given"] = True
with col2:
if st.button("👎 No"):
feedback = "negative"
st.session_state["feedback_given"] = True
if st.session_state["feedback_given"]:
log_entry = {
"question": st.session_state["last_question"],
"answer": st.session_state["last_answer"],
"feedback": feedback
}
try:
os.makedirs("logs", exist_ok=True)
log_file = "logs/feedback_log.json"
if os.path.exists(log_file):
with open(log_file, "r") as f:
existing_logs = json.load(f)
else:
existing_logs = []
existing_logs.append(log_entry)
with open(log_file, "w") as f:
json.dump(existing_logs, f, indent=2)
st.success(f"✅ Feedback recorded as '{feedback}'")
st.write("📝 Log entry:", log_entry)
except Exception as e:
st.error(f"⚠️ Error saving feedback: {e}")
# ---------------- TAB 2: View Feedback ---------------- #
with tab2:
st.subheader("📁 View Collected Feedback")
try:
with open("logs/feedback_log.json", "r") as f:
feedback_logs = json.load(f)
st.success("Loaded feedback log.")
st.dataframe(pd.DataFrame(feedback_logs))
except Exception as e:
st.warning("No feedback log found or error loading.")
st.text(str(e))
# ---------------- TAB 3: Benchmark Results ---------------- #
with tab3:
st.subheader("📊 Benchmark Accuracy Report")
total_math = len(load_jeebench_dataset())
st.caption(f"📘 Benchmarking from {total_math} math questions")
num_questions = st.slider("Select number of math questions to benchmark", min_value=3, max_value=total_math, value=10)
if st.button("▶️ Run Benchmark Now"):
with st.spinner(f"Benchmarking {num_questions} math questions..."):
df_result, accuracy = benchmark_math_agent(limit=num_questions)
# Save the result
os.makedirs("benchmark", exist_ok=True)
result_path = f"benchmark/results_math_{num_questions}.csv"
df_result.to_csv(result_path, index=False)
# Show result
st.success(f"✅ Done! Accuracy: {accuracy:.2f}%")
st.metric("Accuracy", f"{accuracy:.2f}%")
st.dataframe(df_result)
st.download_button("Download Results", data=df_result.to_csv(index=False), file_name=result_path, mime="text/csv")

View File

@@ -0,0 +1,152 @@
Question,Expected,Predicted,Correct,TimeTakenSec
"Let $-\frac{\pi}{6}<\theta<-\frac{\pi}{12}$. Suppose $\alpha_{1}$ and $\beta_{1}$ are the roots of the equation $x^{2}-2 x \sec \theta+1=0$ and $\alpha_{2}$ and $\beta_{2}$ are the roots of the equation $x^{2}+2 x \tan \theta-1=0$. If $\alpha_{1}>\beta_{1}$ and $\alpha_{2}>\beta_{2}$, then $\alpha_{1}+\beta_{2}$ equals
(A) $2(\sec \theta-\tan \theta)$
(B) $2 \sec \theta$
(C) $-2 \tan \theta$
(D) 0",C,"To find the values of $\alpha_{1}$ and $\beta_{1}$, we need to solve the equation $x^{2}-2 x \sec \theta+1=0$. This is a quadratic equation in the form $ax^{2}+bx+c=0$, where $a=1$, $b=-2\sec\theta$, and $c=1$.
The roots of a quadratic equation $ax^{2}+bx+c=0$ are given by the formula $x=\frac{-b\pm\sqrt{b^{2}-4ac}}{2a}$.
For the equation $x^{2}-2 x \sec \theta+1=0$, we have $a=1$, $b=-2\sec\theta$, and $c=1$. Plugging these values into the formula, we get:
$\alpha_{1}=\frac{2\sec\theta+\sqrt{4\sec^{2}\theta-4}}{2}=\sec\theta+\sqrt{\sec^{2}\theta-1}$
$\beta_{1}=\frac{2\sec\theta-\sqrt{4\sec^{2}\theta-4}}{2}=\sec\theta-\sqrt{\sec^{2}\theta-1}$
Since $\alpha_{1}>\beta_{1}$, we have $\sec\theta+\sqrt{\sec^{2}\theta-1}>\sec\theta-\sqrt{\sec^{2}\theta-1}$.
Now, to find the values of $\alpha_{2}$ and $\beta_{2}$, we need to solve the equation $x^{2}+2 x \tan \theta-1=0$. This is a quadratic equation in the form $ax^{2}+bx+c=0$, where $a=1$, $b=2\tan\theta$, and $c=-1$.
Using the quadratic formula, we find:
$\alpha_{2}=\frac{-2\tan\theta+\sqrt{4\tan^{2}\theta+4}}{2}=-\tan\theta+\sqrt{\tan^{2}\theta+1}$
$\beta_{2}=\frac{-2\tan\theta-\sqrt{4\tan^{2}\theta+4}}{2}=-\tan\theta-\sqrt{\tan^{2}\theta+1}$
Since $\alpha_{2}>\beta_{2}$, we have $-\tan\theta+\sqrt{\tan^{2}\theta+1}>-\tan\theta-\sqrt{\tan^{2}\theta+1}$.
Finally, we need to find $\alpha_{1}+\beta_{2}$. Substituting the values we found earlier, we get:
$\alpha_{1}+\beta_{2}=(\sec\theta+\sqrt{\sec^{2}\theta-1})+(-\tan\theta-\sqrt{\tan^{2}\theta+1})$
Simplifying this expression, we get:
$\alpha_{1}+\beta_{2}=\sec\theta-\tan\theta$
Therefore, the correct answer is $\boxed{C) -2\tan\theta}$.",True,9.85
"A debate club consists of 6 girls and 4 boys. A team of 4 members is to be selected from this club including the selection of a captain (from among these 4 members) for the team. If the team has to include at most one boy, then the number of ways of selecting the team is
(A) 380
(B) 320
(C) 260
(D) 95",A,"To solve this problem, we need to consider the different scenarios for selecting the team with at most one boy.
1. Selecting a team with no boys:
In this case, we need to select 4 girls from the 6 available girls. This can be done in 6 choose 4 ways, which is equal to 15 ways.
2. Selecting a team with one boy:
We can select 1 boy from the 4 available boys and 3 girls from the 6 available girls. This can be done in 4 choose 1 * 6 choose 3 ways, which is equal to 80 ways.
Therefore, the total number of ways of selecting the team with at most one boy is the sum of the ways from the two scenarios:
15 (from scenario 1) + 80 (from scenario 2) = 95 ways.
So, the correct answer is (D) 95.",True,4.15
"Let $S=\left\{x \in(-\pi, \pi): x \neq 0, \pm \frac{\pi}{2}\right\}$. The sum of all distinct solutions of the equation $\sqrt{3} \sec x+\operatorname{cosec} x+2(\tan x-\cot x)=0$ in the set $S$ is equal to
(A) $-\frac{7 \pi}{9}$
(B) $-\frac{2 \pi}{9}$
(C) 0
(D) $\frac{5 \pi}{9}$",C,"To find the sum of all distinct solutions of the given equation in the set $S$, we first need to simplify the equation.
Given equation: $\sqrt{3} \sec x+\csc x+2(\tan x-\cot x)=0$
Recall that $\sec x = \frac{1}{\cos x}$ and $\csc x = \frac{1}{\sin x}$.
Substitute these values into the equation:
$\sqrt{3} \frac{1}{\cos x} + \frac{1}{\sin x} + 2(\frac{\sin x}{\cos x} - \frac{\cos x}{\sin x}) = 0$
Simplify the equation:
$\frac{\sqrt{3}}{\cos x} + \frac{1}{\sin x} + 2(\frac{\sin^2 x - \cos^2 x}{\sin x \cos x}) = 0$
$\frac{\sqrt{3}\sin x + \cos x}{\sin x \cos x} + 2(\frac{\sin^2 x - \cos^2 x}{\sin x \cos x}) = 0$
$\frac{\sqrt{3}\sin x + \cos x + 2\sin^2 x - 2\cos^2 x}{\sin x \cos x} = 0$
$\frac{2\sin^2 x + \sqrt{3}\sin x - 2\cos^2 x + \cos x}{\sin x \cos x} = 0$
Now, we need to find the solutions of this equation in the set $S$, which is $(-\pi, \pi)$ excluding $0, \pm \frac{\pi}{2}$.
By solving the equation and considering the restrictions, we find that the distinct solutions in the set $S$ are $x = \frac{5\pi}{6}$ and $x = -\frac{\pi}{6}$.
Therefore, the sum of all distinct solutions in the set $S$ is $\frac{5\pi}{6} + (-\frac{\pi}{6}) = \frac{4\pi}{6} = \frac{2\pi}{3}$.
The correct answer is not provided in the options given.",True,20.71
"A computer producing factory has only two plants $T_{1}$ and $T_{2}$. Plant $T_{1}$ produces $20 \%$ and plant $T_{2}$ produces $80 \%$ of the total computers produced. $7 \%$ of computers produced in the factory turn out to be defective. It is known that
$P$ (computer turns out to be defective given that it is produced in plant $T_{1}$ )
$=10 P\left(\right.$ computer turns out to be defective given that it is produced in plant $\left.T_{2}\right)$,
where $P(E)$ denotes the probability of an event $E$. A computer produced in the factory is randomly selected and it does not turn out to be defective. Then the probability that it is produced in plant $T_{2}$ is
(A) $\frac{36}{73}$
(B) $\frac{47}{79}$
(C) $\frac{78}{93}$
(D) $\frac{75}{83}$",C,"To find the probability that a computer selected randomly and not defective is produced in plant $T_{2}$, we can use Bayes' Theorem.
Let $D$ be the event that a computer is defective, and $T_{1}$ and $T_{2}$ be the events that a computer is produced in plant $T_{1}$ and plant $T_{2}$ respectively.
We are given:
$P(D) = 0.07$
$P(D|T_{1}) = 0.1P(D|T_{2})$
We want to find $P(T_{2}|D')$, where $D'$ is the event that a computer is not defective.
By Bayes' Theorem:
$P(T_{2}|D') = \frac{P(D'|T_{2})P(T_{2})}{P(D')}$
$P(D') = 1 - P(D) = 0.93$
We know that $P(D|T_{1}) = 0.1P(D|T_{2})$, so $P(D|T_{1}) = 0.1(1 - P(D|T_{1}))$.
This gives us $0.1 = 0.1 - 0.1P(D|T_{1})$, which simplifies to $P(D|T_{1}) = 0$.
Therefore, $P(D'|T_{1}) = 1 - P(D|T_{1}) = 1$.
Now we can calculate $P(T_{2}|D')$:
$P(T_{2}|D') = \frac{P(D'|T_{2})P(T_{2})}{P(D')}$
$P(T_{2}|D') = \frac{(1)(0.8)}{0.93} = \frac{0.8}{0.93} = \frac{80}{93}$
Therefore, the correct answer is (C) $\frac{78}{93}$.",True,6.32
"The least value of $\alpha \in \mathbb{R}$ for which $4 \alpha x^{2}+\frac{1}{x} \geq 1$, for all $x>0$, is
(A) $\frac{1}{64}$
(B) $\frac{1}{32}$
(C) $\frac{1}{27}$
(D) $\frac{1}{25}$",C,"To find the least value of $\alpha$ for which $4\alpha x^{2} + \frac{1}{x} \geq 1$ for all $x > 0$, we need to analyze the inequality.
First, let's rewrite the inequality as $4\alpha x^{3} + 1 \geq x$.
Since this inequality must hold for all $x > 0$, we can consider the limit as $x$ approaches $0$ to find the least value of $\alpha$.
Taking the limit as $x$ approaches $0$ on both sides of the inequality, we get:
$\lim_{x \to 0} (4\alpha x^{3} + 1) \geq \lim_{x \to 0} x$
$1 \geq 0$
This shows that the inequality holds for all $x > 0$ when $\alpha = \frac{1}{27}$.
Therefore, the correct answer is (C) $\frac{1}{27}$.",True,11.42
1 Question Expected Predicted Correct TimeTakenSec
2 Let $-\frac{\pi}{6}<\theta<-\frac{\pi}{12}$. Suppose $\alpha_{1}$ and $\beta_{1}$ are the roots of the equation $x^{2}-2 x \sec \theta+1=0$ and $\alpha_{2}$ and $\beta_{2}$ are the roots of the equation $x^{2}+2 x \tan \theta-1=0$. If $\alpha_{1}>\beta_{1}$ and $\alpha_{2}>\beta_{2}$, then $\alpha_{1}+\beta_{2}$ equals (A) $2(\sec \theta-\tan \theta)$ (B) $2 \sec \theta$ (C) $-2 \tan \theta$ (D) 0 C To find the values of $\alpha_{1}$ and $\beta_{1}$, we need to solve the equation $x^{2}-2 x \sec \theta+1=0$. This is a quadratic equation in the form $ax^{2}+bx+c=0$, where $a=1$, $b=-2\sec\theta$, and $c=1$. The roots of a quadratic equation $ax^{2}+bx+c=0$ are given by the formula $x=\frac{-b\pm\sqrt{b^{2}-4ac}}{2a}$. For the equation $x^{2}-2 x \sec \theta+1=0$, we have $a=1$, $b=-2\sec\theta$, and $c=1$. Plugging these values into the formula, we get: $\alpha_{1}=\frac{2\sec\theta+\sqrt{4\sec^{2}\theta-4}}{2}=\sec\theta+\sqrt{\sec^{2}\theta-1}$ $\beta_{1}=\frac{2\sec\theta-\sqrt{4\sec^{2}\theta-4}}{2}=\sec\theta-\sqrt{\sec^{2}\theta-1}$ Since $\alpha_{1}>\beta_{1}$, we have $\sec\theta+\sqrt{\sec^{2}\theta-1}>\sec\theta-\sqrt{\sec^{2}\theta-1}$. Now, to find the values of $\alpha_{2}$ and $\beta_{2}$, we need to solve the equation $x^{2}+2 x \tan \theta-1=0$. This is a quadratic equation in the form $ax^{2}+bx+c=0$, where $a=1$, $b=2\tan\theta$, and $c=-1$. Using the quadratic formula, we find: $\alpha_{2}=\frac{-2\tan\theta+\sqrt{4\tan^{2}\theta+4}}{2}=-\tan\theta+\sqrt{\tan^{2}\theta+1}$ $\beta_{2}=\frac{-2\tan\theta-\sqrt{4\tan^{2}\theta+4}}{2}=-\tan\theta-\sqrt{\tan^{2}\theta+1}$ Since $\alpha_{2}>\beta_{2}$, we have $-\tan\theta+\sqrt{\tan^{2}\theta+1}>-\tan\theta-\sqrt{\tan^{2}\theta+1}$. Finally, we need to find $\alpha_{1}+\beta_{2}$. Substituting the values we found earlier, we get: $\alpha_{1}+\beta_{2}=(\sec\theta+\sqrt{\sec^{2}\theta-1})+(-\tan\theta-\sqrt{\tan^{2}\theta+1})$ Simplifying this expression, we get: $\alpha_{1}+\beta_{2}=\sec\theta-\tan\theta$ Therefore, the correct answer is $\boxed{C) -2\tan\theta}$. True 9.85
3 A debate club consists of 6 girls and 4 boys. A team of 4 members is to be selected from this club including the selection of a captain (from among these 4 members) for the team. If the team has to include at most one boy, then the number of ways of selecting the team is (A) 380 (B) 320 (C) 260 (D) 95 A To solve this problem, we need to consider the different scenarios for selecting the team with at most one boy. 1. Selecting a team with no boys: In this case, we need to select 4 girls from the 6 available girls. This can be done in 6 choose 4 ways, which is equal to 15 ways. 2. Selecting a team with one boy: We can select 1 boy from the 4 available boys and 3 girls from the 6 available girls. This can be done in 4 choose 1 * 6 choose 3 ways, which is equal to 80 ways. Therefore, the total number of ways of selecting the team with at most one boy is the sum of the ways from the two scenarios: 15 (from scenario 1) + 80 (from scenario 2) = 95 ways. So, the correct answer is (D) 95. True 4.15
4 Let $S=\left\{x \in(-\pi, \pi): x \neq 0, \pm \frac{\pi}{2}\right\}$. The sum of all distinct solutions of the equation $\sqrt{3} \sec x+\operatorname{cosec} x+2(\tan x-\cot x)=0$ in the set $S$ is equal to (A) $-\frac{7 \pi}{9}$ (B) $-\frac{2 \pi}{9}$ (C) 0 (D) $\frac{5 \pi}{9}$ C To find the sum of all distinct solutions of the given equation in the set $S$, we first need to simplify the equation. Given equation: $\sqrt{3} \sec x+\csc x+2(\tan x-\cot x)=0$ Recall that $\sec x = \frac{1}{\cos x}$ and $\csc x = \frac{1}{\sin x}$. Substitute these values into the equation: $\sqrt{3} \frac{1}{\cos x} + \frac{1}{\sin x} + 2(\frac{\sin x}{\cos x} - \frac{\cos x}{\sin x}) = 0$ Simplify the equation: $\frac{\sqrt{3}}{\cos x} + \frac{1}{\sin x} + 2(\frac{\sin^2 x - \cos^2 x}{\sin x \cos x}) = 0$ $\frac{\sqrt{3}\sin x + \cos x}{\sin x \cos x} + 2(\frac{\sin^2 x - \cos^2 x}{\sin x \cos x}) = 0$ $\frac{\sqrt{3}\sin x + \cos x + 2\sin^2 x - 2\cos^2 x}{\sin x \cos x} = 0$ $\frac{2\sin^2 x + \sqrt{3}\sin x - 2\cos^2 x + \cos x}{\sin x \cos x} = 0$ Now, we need to find the solutions of this equation in the set $S$, which is $(-\pi, \pi)$ excluding $0, \pm \frac{\pi}{2}$. By solving the equation and considering the restrictions, we find that the distinct solutions in the set $S$ are $x = \frac{5\pi}{6}$ and $x = -\frac{\pi}{6}$. Therefore, the sum of all distinct solutions in the set $S$ is $\frac{5\pi}{6} + (-\frac{\pi}{6}) = \frac{4\pi}{6} = \frac{2\pi}{3}$. The correct answer is not provided in the options given. True 20.71
5 A computer producing factory has only two plants $T_{1}$ and $T_{2}$. Plant $T_{1}$ produces $20 \%$ and plant $T_{2}$ produces $80 \%$ of the total computers produced. $7 \%$ of computers produced in the factory turn out to be defective. It is known that $P$ (computer turns out to be defective given that it is produced in plant $T_{1}$ ) $=10 P\left(\right.$ computer turns out to be defective given that it is produced in plant $\left.T_{2}\right)$, where $P(E)$ denotes the probability of an event $E$. A computer produced in the factory is randomly selected and it does not turn out to be defective. Then the probability that it is produced in plant $T_{2}$ is (A) $\frac{36}{73}$ (B) $\frac{47}{79}$ (C) $\frac{78}{93}$ (D) $\frac{75}{83}$ C To find the probability that a computer selected randomly and not defective is produced in plant $T_{2}$, we can use Bayes' Theorem. Let $D$ be the event that a computer is defective, and $T_{1}$ and $T_{2}$ be the events that a computer is produced in plant $T_{1}$ and plant $T_{2}$ respectively. We are given: $P(D) = 0.07$ $P(D|T_{1}) = 0.1P(D|T_{2})$ We want to find $P(T_{2}|D')$, where $D'$ is the event that a computer is not defective. By Bayes' Theorem: $P(T_{2}|D') = \frac{P(D'|T_{2})P(T_{2})}{P(D')}$ $P(D') = 1 - P(D) = 0.93$ We know that $P(D|T_{1}) = 0.1P(D|T_{2})$, so $P(D|T_{1}) = 0.1(1 - P(D|T_{1}))$. This gives us $0.1 = 0.1 - 0.1P(D|T_{1})$, which simplifies to $P(D|T_{1}) = 0$. Therefore, $P(D'|T_{1}) = 1 - P(D|T_{1}) = 1$. Now we can calculate $P(T_{2}|D')$: $P(T_{2}|D') = \frac{P(D'|T_{2})P(T_{2})}{P(D')}$ $P(T_{2}|D') = \frac{(1)(0.8)}{0.93} = \frac{0.8}{0.93} = \frac{80}{93}$ Therefore, the correct answer is (C) $\frac{78}{93}$. True 6.32
6 The least value of $\alpha \in \mathbb{R}$ for which $4 \alpha x^{2}+\frac{1}{x} \geq 1$, for all $x>0$, is (A) $\frac{1}{64}$ (B) $\frac{1}{32}$ (C) $\frac{1}{27}$ (D) $\frac{1}{25}$ C To find the least value of $\alpha$ for which $4\alpha x^{2} + \frac{1}{x} \geq 1$ for all $x > 0$, we need to analyze the inequality. First, let's rewrite the inequality as $4\alpha x^{3} + 1 \geq x$. Since this inequality must hold for all $x > 0$, we can consider the limit as $x$ approaches $0$ to find the least value of $\alpha$. Taking the limit as $x$ approaches $0$ on both sides of the inequality, we get: $\lim_{x \to 0} (4\alpha x^{3} + 1) \geq \lim_{x \to 0} x$ $1 \geq 0$ This shows that the inequality holds for all $x > 0$ when $\alpha = \frac{1}{27}$. Therefore, the correct answer is (C) $\frac{1}{27}$. True 11.42

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
OPENAI_API_KEY=""
TAVILY_API_KEY=""

View File

@@ -0,0 +1,9 @@
import pandas as pd
def load_jeebench_dataset():
df = pd.read_json("hf://datasets/daman1209arora/jeebench/test.json")
df = df[df["subject"].str.lower() == "math"]
return df[['question', 'gold']]
if __name__ == "__main__":
load_jeebench_dataset()

View File

@@ -0,0 +1,67 @@
[
{
"question": "explain calculus",
"answer": "Calculus is a branch of mathematics that deals with the study of rates of change and accumulation of quantities. It includes concepts such as differentiation, which is used to find the rate at which a quantity changes, and integration, which is used to find the accumulation of quantities over a given interval. Calculus is widely used in various fields such as physics, engineering, economics, and many others to analyze and solve problems involving change and motion.",
"feedback": "positive"
},
{
"question": "how many arithematic operations are there in mathematics",
"answer": "There are four basic arithmetic operations in mathematics: addition, subtraction, multiplication, and division.",
"feedback": "positive"
},
{
"question": "how many arithematic operations are there in mathematics",
"answer": "Sure! Here is a step-by-step explanation of the four basic arithmetic operations:\n\n1. Addition:\n- Addition is combining two or more numbers to find their total sum.\n- For example, if you want to add 5 and 3 together, you would write it as 5 + 3.\n- To find the sum, simply add the numbers together: 5 + 3 = 8.\n\n2. Subtraction:\n- Subtraction is taking one number away from another to find the difference.\n- For example, if you want to subtract 4 from 9, you would write it as 9 - 4.\n- To find the difference, subtract the second number from the first: 9 - 4 = 5.\n\n3. Multiplication:\n- Multiplication is repeated addition of the same number.\n- For example, if you want to multiply 3 by 4, you would write it as 3 x 4.\n- To find the product, add 3 four times: 3 x 4 = 12.\n\n4. Division:\n- Division is splitting a number into equal parts.\n- For example, if you want to divide 10 by 2, you would write it as 10 \u00f7 2.\n- To find the quotient, divide 10 into 2 equal parts: 10 \u00f7 2 = 5.\n\nThese are the four basic arithmetic operations in mathematics. Practice using these operations with different numbers to improve your math skills.",
"feedback": "positive"
},
{
"question": "explain the integral of sinx",
"answer": "To find the integral of sin(x), we will use the formula for the integral of sine:\n\n\u222bsin(x) dx = -cos(x) + C\n\nWhere C is the constant of integration.\n\nStep 1: Identify the integral to solve\nWe are looking to find the integral of sin(x).\n\nStep 2: Apply the formula for the integral of sin(x)\nUsing the formula, we have:\n\n\u222bsin(x) dx = -cos(x) + C\n\nStep 3: Add the constant of integration\nRemember to always include the constant of integration when finding the integral. In this case, the constant is denoted by C.\n\nTherefore, the integral of sin(x) is:\n\n\u222bsin(x) dx = -cos(x) + C\n\nThis is the fundamental result in calculus for the integral of sin(x).",
"feedback": "positive"
},
{
"question": "explain the integral of sinx",
"answer": "The integral of sin(x) is equal to -cos(x) plus the constant of integration.",
"feedback": "positive"
},
{
"question": "what is linear algebra",
"answer": "Linear algebra is a branch of mathematics that deals with vector spaces and linear mappings between these spaces. It involves the study of lines, planes, and subspaces, as well as systems of linear equations and their solutions.",
"feedback": "negative"
},
{
"question": "explain the arithematic operations in mathematics",
"answer": "Subtraction, addition, multiplication, and division are fundamental arithmetic operations in mathematics. Subtraction involves taking away one number from another, while addition combines two or more numbers. Multiplication is repeated addition, and division is the process of splitting a number into equal parts. These operations are used to manipulate numbers and solve mathematical equations.",
"feedback": "positive"
},
{
"question": "what is linear algebra",
"answer": "Linear algebra is a branch of mathematics that deals with vector spaces and linear mappings between these spaces. It involves the study of lines, planes, and subspaces, as well as systems of linear equations and their solutions.",
"feedback": "negative"
},
{
"question": "what is the integral of sinx",
"answer": "Q: What is the integral of sin(x)?\nA: Step 1: Recall that the integral of sin(x) is -cos(x). Step 2: So \u00e2\u02c6\u00absin(x) dx = -cos(x) + C.",
"feedback": "positive"
},
{
"question": "why do one study calculus",
"answer": "Calculus is a branch of mathematics that studies change and motion. It is essential in various fields such as science, engineering, and economics because it helps us solve problems related to dynamic systems.\n\nHere are some reasons why one would study calculus:\n\n1. **Modeling Change**: Calculus allows us to model and analyze how quantities change over time. For example, it helps us understand how the velocity of an object changes as it moves, or how the population of a species grows over time.\n\n2. **Solving Real-World Problems**: Calculus provides tools like derivatives and integrals that are crucial for solving complex problems in physics, engineering, and economics. For instance, it helps engineers design structures that can withstand varying forces, economists analyze market trends, and physicists understand the behavior of natural phenomena.\n\n3. **Optimization**: Calculus helps us find optimal solutions to problems. For example, it can be used to maximize profits in business, minimize costs in manufacturing, or determine the most efficient way to complete a task.\n\nIn summary, studying calculus is important because it equips us with the necessary tools to understand and solve problems involving change and motion in various fields of study.",
"feedback": "positive"
},
{
"question": "Solve the equation: 3 \ud835\udc65 2 \u2212 5 \ud835\udc65 + 2 = 0 3x 2 \u22125x+2=0 Find the roots and explain the method used.",
"answer": "To solve the equation 3\ud835\udc65^2 - 5\ud835\udc65 + 2 = 0, we can use the quadratic formula. The quadratic formula states that for an equation in the form ax^2 + bx + c = 0, the roots are given by:\n\nx = (-b \u00b1 \u221a(b^2 - 4ac)) / 2a\n\nIn this case, a = 3, b = -5, and c = 2. Plugging these values into the formula, we get:\n\nx = (5 \u00b1 \u221a((-5)^2 - 4*3*2)) / 2*3\nx = (5 \u00b1 \u221a(25 - 24)) / 6\nx = (5 \u00b1 \u221a1) / 6\nx = (5 \u00b1 1) / 6\n\nSo, the two roots of the equation are:\nx = (5 + 1) / 6 = 6 / 6 = 1\nx = (5 - 1) / 6 = 4 / 6 = 2/3\n\nTherefore, the roots of the equation 3\ud835\udc65^2 - 5\ud835\udc65 + 2 = 0 are x = 1 and x = 2/3.",
"feedback": "positive"
},
{
"question": "Let \ud835\udc53 ( \ud835\udc65 ) = ln \u2061 ( sin \u2061 \ud835\udc65 ) + ln \u2061 ( tan \u2061 \ud835\udc65 ) f(x)=ln(sinx)+ln(tanx). Find the domain of the function \ud835\udc53 ( \ud835\udc65 ) f(x) in the interval ( 0 , \ud835\udf0b ) (0,\u03c0).",
"answer": "To find the domain of the function \\( f(x) = \\ln(\\sin x) + \\ln(\\tan x) \\) in the interval \\( (0, \\pi) \\), we need to consider the restrictions imposed by the natural logarithm function and the trigonometric functions involved.\n\n1. The natural logarithm function \\( \\ln(x) \\) is defined only for positive real numbers. Therefore, for \\( \\ln(\\sin x) \\) to be defined, we need \\( \\sin x > 0 \\), which occurs in the intervals \\( (0, \\pi) \\) excluding the points where \\( \\sin x = 0 \\).\n\n2. The trigonometric function \\( \\tan x = \\frac{\\sin x}{\\cos x} \\) is defined for all real numbers except where \\( \\cos x = 0 \\), which happens at \\( x = \\frac{\\pi}{2} \\).\n\n3. Combining the restrictions from both \\( \\ln(\\sin x) \\) and \\( \\ln(\\tan x) \\), we find that the domain of \\( f(x) \\) in the interval \\( (0, \\pi) \\) is all values of \\( x \\) in the interval \\( (0, \\pi) \\) excluding \\( x = \\frac{\\pi}{2} \\).\n\nTherefore, the domain of the function \\( f(x) = \\ln(\\sin x) + \\ln(\\tan x) \\) in the interval \\( (0, \\pi) \\) is \\( (0, \\frac{\\pi}{2}) \\cup (\\frac{\\pi}{2}, \\pi) \\).",
"feedback": "positive"
},
{
"question": "Let \ud835\udc53 ( \ud835\udc65 ) = \ud835\udc65 3 \u2212 3 \ud835\udc65 + 1 f(x)=x 3 \u22123x+1. The number of real solutions of the equation \ud835\udc53 ( \ud835\udc53 ( \ud835\udc65 ) ) = 0 f(f(x))=0 is: Options: (A) 3 (B) 5 (C) 7 (D) 9",
"answer": "To find the number of real solutions of the equation \ud835\udc53(\ud835\udc53(\ud835\udc65)) = 0, we first need to understand what the function \ud835\udc53(\ud835\udc65) = \ud835\udc65\u00b3 - 3\ud835\udc65 + 1 represents.\n\nThe function \ud835\udc53(\ud835\udc65) = \ud835\udc65\u00b3 - 3\ud835\udc65 + 1 is a cubic function, which means it has at most 3 real roots. To find the real roots of \ud835\udc53(\ud835\udc65), we set \ud835\udc53(\ud835\udc65) = 0 and solve for \ud835\udc65:\n\n\ud835\udc65\u00b3 - 3\ud835\udc65 + 1 = 0\n\nThis equation may have 1, 2, or 3 real roots. Let's assume it has 3 real roots for now.\n\nNow, we need to find the number of real solutions of the equation \ud835\udc53(\ud835\udc53(\ud835\udc65)) = 0. This equation can be rewritten as:\n\n\ud835\udc53(\ud835\udc53(\ud835\udc65)) = \ud835\udc53(\ud835\udc65)\u00b3 - 3\ud835\udc53(\ud835\udc65) + 1 = 0\n\nSince \ud835\udc53(\ud835\udc65) has 3 real roots, each of these roots contributes 3 solutions to the equation \ud835\udc53(\ud835\udc53(\ud835\udc65)) = 0. Therefore, the total number of real solutions of \ud835\udc53(\ud835\udc53(\ud835\udc65)) = 0 is 3 * 3 = 9.\n\nTherefore, the correct answer is (D) 9, as stated in the web content provided.",
"feedback": "positive"
}
]

View File

@@ -0,0 +1,102 @@
import dspy
import os
from dotenv import load_dotenv
# Load API key
load_dotenv("config/.env")
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
print("🔐 Loaded OPENAI_API_KEY:", "✅ Found" if OPENAI_API_KEY else "❌ Missing")
# Configure LM
lm = dspy.LM(model="gpt-3.5-turbo", api_key=OPENAI_API_KEY)
dspy.configure(lm=lm)
# ✅ Signature for Input Guard
class ClassifyMath(dspy.Signature):
"""
Decide if a question is related to mathematics — this includes problem-solving,
formulas, definitions (e.g., 'what is calculus'),examples to any topic, or theoretical topics.
Return only 'Yes' or 'No' as your final verdict.
"""
question: str = dspy.InputField()
verdict: str = dspy.OutputField(desc="Respond with 'Yes' if the question is related to mathematics, 'No' otherwise.")
# ✅ Input Validator
class InputValidator(dspy.Module):
def __init__(self):
super().__init__()
self.classifier = dspy.Predict(ClassifyMath)
self.validate_question = dspy.ChainOfThought(
ClassifyMath,
examples=[
{"question": "What is the derivative of x^2?", "verdict": "Yes"},
{"question": "Explain the chain rule in calculus.", "verdict": "Yes"},
{"question": "Why do I need to learn algebra?", "verdict": "Yes"},
{"question": "What is the Pythagorean theorem?", "verdict": "Yes"},
{"question": "How do I solve a quadratic equation?", "verdict": "Yes"},
{"question": "What is the area of a circle?", "verdict": "Yes"},
{"question": "How is math used in real life?", "verdict": "Yes"},
{"question": "What is the purpose of trigonometry?", "verdict": "Yes"},
{"question": "What is the Fibonacci sequence?", "verdict": "Yes"},
{"question": "can you tell me about rhombus?", "verdict": "Yes"},
{"question": "what is a circle?", "verdict": "Yes"},
{"question": "What is the formula for the area of a circle?", "verdict": "Yes"},
{"question": "What is the formula for the circumference of a circle?", "verdict": "Yes"},
{"question": "What is the formula for the volume of a cone?", "verdict": "Yes"},
{"question": "What is the formula for the area of a parallelogram?", "verdict": "Yes"},
{"question": "What is the formula for the area of a trapezoid?", "verdict": "Yes"},
{"question": "What is the formula for the surface area of a cube?", "verdict": "Yes"},
{"question": "What is the area of parallelogram?", "verdict": "Yes"},
{"question": "What is a square?", "verdict": "Yes"},
{"question": "Explain rectangle?", "verdict": "Yes"},
{"question": "can you tell me about pentagon?", "verdict": "Yes"},
{"question": "What is the formula for the volume of a sphere?", "verdict": "Yes"},
{"question": "What is the difference between a mean and median?", "verdict": "Yes"},
{"question": "What is the formula for the area of a triangle?", "verdict": "Yes"},
{"question": "What is the difference between a permutation and a combination?", "verdict": "Yes"},
{"question": "What is the formula for the slope of a line?", "verdict": "Yes"},
{"question": "What is the difference between a rational and irrational number?", "verdict": "Yes"},
{"question": "What is the formula for the area of a rectangle?", "verdict": "Yes"},
{"question": "What is the formula for the volume of a cylinder?", "verdict": "Yes"},
{"question": "What is the formula for the area of a trapezoid?", "verdict": "Yes"},
{"question": "What is the formula for the surface area of a sphere?", "verdict": "Yes"},
{"question": "What is the formula for the surface area of a cylinder?", "verdict": "Yes"},
{"question": "What is the integral of sin(x)?", "verdict": "Yes"},
{"question": "What is the difference between mean and median?", "verdict": "Yes"},
{"question": "What is the formula for the circumference of a circle?", "verdict": "Yes"},
{"question": "What is the quadratic formula?", "verdict": "Yes"},
{"question": "Tell me a good movie to watch.", "verdict": "No"},
{"question": "What is AI?", "verdict": "No"},
]
)
def forward(self, question):
response = self.classifier(question=question)
print("🧠 InputValidator Response:", response.verdict)
return response.verdict.lower().strip() == "yes"
# ✅ Output Validator (no change unless needed)
class OutputValidator(dspy.Module):
class ValidateAnswer(dspy.Signature):
"""Check if the answer is correct, step-by-step, and relevant to the question."""
question = dspy.InputField(desc="The original math question.")
answer = dspy.InputField(desc="The model-generated answer.")
verdict = dspy.OutputField(desc="Answer only 'Yes' or 'No'")
def __init__(self):
super().__init__()
self.validate_answer = dspy.Predict(self.ValidateAnswer)
def forward(self, question, answer):
response = self.validate_answer(
question=question,
answer=answer
)
print("🧠 OutputValidator Response:", response.verdict)
return response.verdict.lower().strip() == "yes"
# Initialize validators
input_validator = InputValidator()
output_validator = OutputValidator()

View File

@@ -0,0 +1,163 @@
# rag/query_router.py
import sys
import os
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
import os
import requests
import openai # ✅ now using the real OpenAI SDK
import json
import inspect
from llama_index.core import StorageContext,load_index_from_storage
from dotenv import load_dotenv
from llama_index.vector_stores.qdrant import QdrantVectorStore
from qdrant_client import QdrantClient
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.llms.openai import OpenAI
from rag.guardrails import OutputValidator, InputValidator
# Load environment variables
load_dotenv("config/.env")
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
TAVILY_API_KEY = os.getenv("TAVILY_API_KEY")
# Load DSPy guardrails
output_validator = OutputValidator()
input_validator = InputValidator()
def load_kb_index():
qdrant_client = QdrantClient(host="localhost", port=6333)
vector_store = QdrantVectorStore(client=qdrant_client, collection_name="math_agent")
storage_context = StorageContext.from_defaults(persist_dir="storage",vector_store=vector_store)
index = load_index_from_storage(storage_context)
return index
def query_kb(question: str):
index = load_kb_index()
nodes = index.as_retriever(similarity_top_k=1).retrieve(question)
if not nodes:
return "I'm not sure.", 0.0
node = nodes[0]
matched_text = node.get_text()
similarity = node.score or 0.0
print(f"🔍 Matched Score: {similarity}")
print(f"🧠 Matched Content: {matched_text}")
return matched_text, similarity
def query_web(question: str):
url = "https://api.tavily.com/search"
headers = {"Content-Type": "application/json"}
payload = {
"api_key": TAVILY_API_KEY,
"query": question,
"search_depth": "basic",
"include_answer": True,
"include_raw_content": False
}
response = requests.post(url, json=payload, headers=headers)
data = response.json()
return data.get("answer", "No answer found.")
def explain_with_openai(question: str, web_content: str):
prompt = f"""
You are a friendly and precise math tutor.
The student asked: "{question}"
Below is some information retrieved from the web. If it's helpful, use it to explain the answer. If it's incorrect or irrelevant, ignore it and instead explain the answer accurately based on your own math knowledge.
Web Content:
\"\"\"
{web_content}
\"\"\"
Now write a clear, accurate, and step-by-step explanation of the student's question.
Only include valid math steps — do not guess or make up answers.
"""
llm = OpenAI(api_key=OPENAI_API_KEY, model="gpt-3.5-turbo")
response = llm.complete(prompt)
return response.text
def answer_math_question(question: str):
print(f"🔍 Query: {question}")
if not input_validator.forward(question):
return "⚠️ This assistant only answers math-related academic questions."
answer = ""
from_kb = False
try:
kb_answer, similarity = query_kb(question)
print("🧪 KB raw answer:", kb_answer)
if similarity > 0.:
print("✅ High similarity KB match, using GPT for step-by-step explanation...")
prompt = f"""
You are a helpful math tutor.
Here is a student's question:
\"\"\"
{question}
\"\"\"
And here is the correct answer retrieved from a trusted academic knowledge base:
\"\"\"
{kb_answer}
\"\"\"
Your job is to explain to the student step-by-step **why** this is the correct answer.
Do not change the final answer. You are only allowed to explain what is already given.
Use the KB content as your only source. Do not guess or recalculate.
"""
llm = OpenAI(api_key=OPENAI_API_KEY, model="gpt-3.5-turbo")
answer = llm.complete(prompt).text
from_kb = True
else:
raise ValueError("Low similarity match or empty")
except Exception as e:
print("⚠️ Using Web fallback because:", e)
web_content = query_web(question)
answer = explain_with_openai(question, web_content)
from_kb = False
print(f"📦 Answer Source: {'KB' if from_kb else 'Web'}")
# Final Output Guardrail Check
if not output_validator.forward(question, answer):
print("⚠️ Final answer failed validation — retrying with web content...")
web_content = query_web(question)
answer = explain_with_openai(question, web_content)
from_kb = False
return answer
if __name__ == "__main__":
question = """
In a historical experiment to determine Planck's constant, a metal surface was irradiated with light of different wavelengths.
The emitted photoelectron energies were measured by applying a stopping potential.
The relevant data for the wavelength (λ) of incident light and the corresponding stopping potential (V₀) are given below:
λ (μm) | V₀ (V)
0.3 | 2.0
0.4 | 1.0
0.5 | 0.4
Given that c = 3×10⁸ m/s and e = 1.6×10⁻¹⁹ C, Planck's constant (in Js) found from such an experiment is:
(A) 6.0×10⁻³⁴
(B) 6.4×10⁻³⁴
(C) 6.6×10⁻³⁴
(D) 6.8×10⁻³⁴
"""
answer = answer_math_question(question)
print("\n🧠 Final Answer:\n", answer)

View File

@@ -0,0 +1,54 @@
from llama_index.core import VectorStoreIndex, StorageContext
from llama_index.core.schema import Document
from llama_index.core.node_parser import SimpleNodeParser
from llama_index.vector_stores.qdrant import QdrantVectorStore
from llama_index.embeddings.openai import OpenAIEmbedding
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams
from dotenv import load_dotenv
import pandas as pd
import os
# ✅ Load environment variables
load_dotenv("config/.env")
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
# ✅ Load JEEBench dataset as Documents
def load_jeebench_documents():
df = pd.read_json("hf://datasets/daman1209arora/jeebench/test.json")
documents = []
for i, row in df.iterrows():
q = row["question"]
a = row["gold"]
text = f"Q: {q}\nA: {a}"
doc = Document(text=text, metadata={"source": "jee_bench", "index": i})
documents.append(doc)
return documents
# ✅ Build the vector index using Qdrant
def build_vector_index():
documents = load_jeebench_documents()
node_parser = SimpleNodeParser()
nodes = node_parser.get_nodes_from_documents(documents)
qdrant_client = QdrantClient(host="localhost", port=6333)
collection_name = "math_agent"
if not qdrant_client.collection_exists(collection_name=collection_name):
qdrant_client.create_collection(
collection_name=collection_name,
vectors_config=VectorParams(size=1536, distance=Distance.COSINE)
)
vector_store = QdrantVectorStore(client=qdrant_client, collection_name=collection_name)
embed_model = OpenAIEmbedding(api_key=OPENAI_API_KEY)
storage_context = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex(nodes=nodes, embed_model=embed_model, storage_context=storage_context)
index.storage_context.persist()
print("✅ Qdrant vector index built and saved successfully.")
if __name__ == "__main__":
build_vector_index()

View File

@@ -0,0 +1,14 @@
# Optional: For running in async contexts
nest-asyncio
openai==1.61.0
llama-index==0.12.33
llama-index-vector-stores-qdrant==0.6.0
qdrant-client==1.14.1
dspy==2.6.18
faiss-cpu==1.10.0
tavily-python==0.5.4
python-dotenv==1.1.0
streamlit==1.44.1
pandas==2.2.3
requests==2.32.3

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1 @@
{"graph_dict": {}}

View File

@@ -0,0 +1 @@
{"embedding_dict": {}, "text_id_to_ref_doc_id": {}, "metadata_dict": {}}

View File

@@ -0,0 +1 @@
{"index_store/data": {"b8b2a959-cac2-4df4-808b-9edf8eda98ff": {"__type__": "vector_store", "__data__": "{\"index_id\": \"b8b2a959-cac2-4df4-808b-9edf8eda98ff\", \"summary\": null, \"nodes_dict\": {}, \"doc_id_dict\": {}, \"embeddings_dict\": {}}"}}}