Spaces:
Sleeping
Sleeping
Upload 7 files
Browse files- Dockerfile +11 -0
- agents.py +39 -0
- app.py +8 -0
- constants.py +210 -0
- fapi.py +171 -0
- requirements.txt +13 -0
- tools.py +235 -0
Dockerfile
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.10
|
| 2 |
+
|
| 3 |
+
WORKDIR /code
|
| 4 |
+
|
| 5 |
+
COPY ./requirements.txt /code/requirements.txt
|
| 6 |
+
|
| 7 |
+
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
| 8 |
+
|
| 9 |
+
COPY . /code
|
| 10 |
+
|
| 11 |
+
CMD ["uvicorn", "fapi:app", "--host", "0.0.0.0", "--port", "7860"]
|
agents.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from google.adk.agents import LlmAgent,Agent
|
| 2 |
+
from google.adk.tools import google_search
|
| 3 |
+
from constants import INSTRUCTIONS_ABOUT_COLLEGE, INSTRUCTIONS_COLLEGE_PREDICTOR, INSTRUCTIONS_COLLEGE_SENIOR, INSTRUCTIONS_COORDINATOR
|
| 4 |
+
from tools import db_tool, tavily_tool, predictor_tool, mentor_tool
|
| 5 |
+
|
| 6 |
+
about_college = LlmAgent(
|
| 7 |
+
name="about_college_agent",
|
| 8 |
+
description="An agent to provide information about the college.",
|
| 9 |
+
instruction = INSTRUCTIONS_ABOUT_COLLEGE,
|
| 10 |
+
tools=[db_tool, tavily_tool],
|
| 11 |
+
model="gemini-2.0-flash"
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
college_predictor = LlmAgent(
|
| 15 |
+
name="college_predictor_agent",
|
| 16 |
+
description="An agent to predict college outcomes.",
|
| 17 |
+
instruction = INSTRUCTIONS_COLLEGE_PREDICTOR,
|
| 18 |
+
tools=[predictor_tool],
|
| 19 |
+
model="gemini-2.0-flash"
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
college_senior = LlmAgent(
|
| 23 |
+
name="college_senior_agent",
|
| 24 |
+
description="An agent to assist college seniors.",
|
| 25 |
+
instruction = INSTRUCTIONS_COLLEGE_SENIOR,
|
| 26 |
+
tools=[mentor_tool],
|
| 27 |
+
model="gemini-2.0-flash"
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
coordinator = Agent(
|
| 32 |
+
name="coordinator_agent",
|
| 33 |
+
description="An agent to coordinate with students in college counselling.",
|
| 34 |
+
instruction=INSTRUCTIONS_COORDINATOR,
|
| 35 |
+
sub_agents=[about_college, college_predictor, college_senior],
|
| 36 |
+
model="gemini-2.0-flash"
|
| 37 |
+
)
|
| 38 |
+
|
| 39 |
+
|
app.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Main entry point for Hugging Face Spaces deployment
|
| 3 |
+
"""
|
| 4 |
+
from fapi import app
|
| 5 |
+
|
| 6 |
+
if __name__ == "__main__":
|
| 7 |
+
import uvicorn
|
| 8 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|
constants.py
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
INSTRUCTIONS_COORDINATOR="""
|
| 2 |
+
You are an AI college counsellor for engineering students (JEE Mains) with 10 years of experience, and you will redirect users' queries to one of your sub-agents based on their expertise, providing the appropriate assistance to the user.
|
| 3 |
+
|
| 4 |
+
1. You will receive a query from the user, which will have two sections:
|
| 5 |
+
a. `user_query`: The query from the user.
|
| 6 |
+
b. `user_designation`: This will contain- 'userCrl': int, 'userCategory': str, 'userGender': str, 'userHomeState': str
|
| 7 |
+
|
| 8 |
+
2. If the `user_query` in query is an answer to follow-up question than you will send the query to sub-agent to which you sent the previous query.
|
| 9 |
+
|
| 10 |
+
3. If the `user_query` is a new query than you will analyse the `user_query` and based on the content, you will decide which sub-agent to use:
|
| 11 |
+
a. If the `user_query` is a greeting than greet back the user and ask how you can help them with their college queries.
|
| 12 |
+
b. If the `user_query` is about general college information, use the `about_college_agent` to provide detailed information and you will send the `user_query` to the sub-agent and discard the `user_designation`.
|
| 13 |
+
c. If the `user_query` is about college predictions, use the `college_predictor_agent` to predict college outcomes and you will send the complete user query to the sub-agent.
|
| 14 |
+
d. If the `user_query` is about college seniors, use the `college_senior_agent` and you will only send the `user_query` to the sub-agent and discard the `user_designation`.
|
| 15 |
+
|
| 16 |
+
3. Try to answer in simple and clear language and well formated. For bold letters use `**bold text**` format and for next line use `\n` character. Follow the below given format-
|
| 17 |
+
e.g. Based on your rank of **123**, here are the college predictions:\n **College**: Indian Institute of Information Technology, Allahabad (IIITA)\n **Branch**: Information Technology (4 Years, Bachelor of Technology) \n **Category**: OPEN \n **Closing Rank**: 5870
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
"""
|
| 21 |
+
INSTRUCTIONS_ABOUT_COLLEGE="""
|
| 22 |
+
You are a highly experienced college counselor, specializing in helping high school students choose the right engineering colleges. Please follow these guidelines while assisting students:
|
| 23 |
+
1. Call `db_search` with the user’s query.
|
| 24 |
+
2. If `db_search` returns an empty `results` list, immediately call `tavily_search`.
|
| 25 |
+
3. Do not produce any output until one of those calls returns data.
|
| 26 |
+
4. As soon as you have non‑empty results, stop further searches and craft your answer using only that source.
|
| 27 |
+
5. Return the answer in a well-formatted manner and clear language and keep it short and simple. For bold letters use `**bold text**` format and for next line use `\n` character. Follow the below given format-
|
| 28 |
+
e.g. Here's some information about the Indian Institute of Information Technology (IIIT) Ranchi: \n **Location:** Ranchi, Jharkhand \n **Campus:** It operates from a temporary campus, with a permanent 67-acre campus planned in Kanke, Ranchi. \n **Placements**: Overall Placement Percentage: 88.79%, overall Median CTC: 13.32 LPA, overall Highest CTC: 83.38 LPA \n
|
| 29 |
+
"""
|
| 30 |
+
INSTRUCTIONS_COLLEGE_PREDICTOR="""
|
| 31 |
+
You are a an AI assistant for engineering students (JEE Mains). Follow the below task strictly.
|
| 32 |
+
You will receive a query from the user, which will have two sections:
|
| 33 |
+
1. `user_query`: The query from the user.
|
| 34 |
+
2. `user_designation`: This will contain- 'userCrl': int, 'userCategory': str, 'userGender': str, 'userHomeState': str
|
| 35 |
+
3. You will use the predictor_tool to predict the college based on the values you extracted from the `user_designation` and the `user_query`.
|
| 36 |
+
4. Here is what you will extract.
|
| 37 |
+
a. `userCrl`: This will be extracted from user_designation and it will be an integer value.
|
| 38 |
+
b. `userCategory`: This will be extracted from user_designation and it will be a string value.
|
| 39 |
+
c. `userGender`: This will be extracted from user_designation and it will be a string value.
|
| 40 |
+
d. `userHomeState`: This will be extracted from user_designation and it will be a string value.
|
| 41 |
+
e. `limit` (optional): This will be an integer value and it will be 4 by default.
|
| 42 |
+
f. `collegeName` (optional): This will be extracted from `user_query` and it will be a string value. If the user does not provide a college name, it will be "national institute of technology" by default.
|
| 43 |
+
g. `branchName` (optional): This will be extracted from `user_query` and it will be a string value. If the user does not provide a branch name, it will be "computer science and engineering" by default.
|
| 44 |
+
h. `counsellingName` (optional): This will be alwayas "csab" by default.
|
| 45 |
+
|
| 46 |
+
5. If the user_query doesn't mention any specific college (collegeName) or branch (branchName), ask a follow-up question like:
|
| 47 |
+
"Do you have any preference for a college (like IIIT, NIT) or a branch (like CSE)?"
|
| 48 |
+
|
| 49 |
+
6. If you face some error gracefully handle it and if some fields are required from user end ask it.
|
| 50 |
+
|
| 51 |
+
7. Once API returns the answer, add "For more detailed information, please visit https://www.precollege.in/college-predictor" to the end of the answer.
|
| 52 |
+
|
| 53 |
+
8. Return the final answer in a well-formatted manner and clear language and keep it short and simple. For bold letters use `**bold text**` format and for next line use `\n` character. Follow the below given format-
|
| 54 |
+
e.g. Based on your rank of **123**, here are the college predictions:\n **College**: Indian Institute of Information Technology, Allahabad (IIITA)\n **Branch**: Information Technology (4 Years, Bachelor of Technology) \n **Category**: OPEN \n **Closing Rank**: 5870
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
"""
|
| 58 |
+
INSTRUCTIONS_COLLEGE_SENIOR="""
|
| 59 |
+
You are an AI assistant for engineering students (JEE Mains). Follow the below task strictly.
|
| 60 |
+
|
| 61 |
+
1. From user's query, extract the college name.
|
| 62 |
+
2. If you can't find any college name ask the user for the college name.
|
| 63 |
+
3. if you extracted the college name than pass the college name to the `mentor_tool` to get the college senior's information.
|
| 64 |
+
4. If the `mentor_tool` returns an empty `results` list, return a message saying "No college seniors found for this college. Please try - https://www.precollege.in/search".
|
| 65 |
+
5. If the `mentor_tool` returns a non-empty `results` list, return the top 4 results in a well-formatted manner and clear language and keep it short and simple. For bold letters use `**bold text**` format and for next line use `\n` character. Follow the below given format-
|
| 66 |
+
e.g. Here are some seniors from the Indian Institute of Information Technology, Ranchi that I found: \n **Aditya Gautam**: `https://www.precollege.in/mentor/Adityagautam`\n **SUBARNA MASANTA**: `https://www.precollege.in/mentor/subarna`
|
| 67 |
+
"""
|
| 68 |
+
|
| 69 |
+
INSTITUTE_MAPPING = {
|
| 70 |
+
"iit": ["indian institute of technology"],
|
| 71 |
+
"IIT": ["indian institute of technology"],
|
| 72 |
+
"i.i.t": ["indian institute of technology"],
|
| 73 |
+
"I.I.T": ["indian institute of technology"],
|
| 74 |
+
"i i t": ["indian institute of technology"],
|
| 75 |
+
"I I T": ["indian institute of technology"],
|
| 76 |
+
"indian institute of technology": ["indian institute of technology"],
|
| 77 |
+
"Indian Institute of Technology": ["indian institute of technology"],
|
| 78 |
+
"nit": ["national institute of technology"],
|
| 79 |
+
"NIT": ["national institute of technology"],
|
| 80 |
+
"n.i.t": ["national institute of technology"],
|
| 81 |
+
"N.I.T": ["national institute of technology"],
|
| 82 |
+
"n i t": ["national institute of technology"],
|
| 83 |
+
"N I T": ["national institute of technology"],
|
| 84 |
+
"national institute of technology": ["national institute of technology"],
|
| 85 |
+
"National Institute of Technology": ["national institute of technology"],
|
| 86 |
+
"iiit": ["indian institute of information technology"],
|
| 87 |
+
"IIIT": ["indian institute of information technology"],
|
| 88 |
+
"i.i.i.t": ["indian institute of information technology"],
|
| 89 |
+
"I.I.I.T": ["indian institute of information technology"],
|
| 90 |
+
"i i i t": ["indian institute of information technology"],
|
| 91 |
+
"I I I T": ["indian institute of information technology"],
|
| 92 |
+
"indian institute of information technology": ["indian institute of information technology"],
|
| 93 |
+
"Indian Institute of Information Technology": ["indian institute of information technology"],
|
| 94 |
+
"bit": ["birla institute of technology"],
|
| 95 |
+
"BIT": ["birla institute of technology"],
|
| 96 |
+
"b.i.t": ["birla institute of technology"],
|
| 97 |
+
"B.I.T": ["birla institute of technology"],
|
| 98 |
+
"b i t": ["birla institute of technology"],
|
| 99 |
+
"B I T": ["birla institute of technology"],
|
| 100 |
+
"birla institute of technology": ["birla institute of technology"],
|
| 101 |
+
"Birla Institute of Technology": ["birla institute of technology"],
|
| 102 |
+
"bits": ["birla institute of technology and science"],
|
| 103 |
+
"BITS": ["birla institute of technology and science"],
|
| 104 |
+
"b.i.t.s": ["birla institute of technology and science"],
|
| 105 |
+
"B.I.T.S": ["birla institute of technology and science"],
|
| 106 |
+
"b i t s": ["birla institute of technology and science"],
|
| 107 |
+
"B I T S": ["birla institute of technology and science"],
|
| 108 |
+
"birla institute of technology and science": ["birla institute of technology and science"],
|
| 109 |
+
"Birla Institute of Technology and Science": ["birla institute of technology and science"],
|
| 110 |
+
"dtu": ["delhi technological university"],
|
| 111 |
+
"DTU": ["delhi technological university"],
|
| 112 |
+
"nsut": ["netaji subhas university of technology"],
|
| 113 |
+
"NSUT": ["netaji subhas university of technology"],
|
| 114 |
+
"iiitd": ["indraprastha institute of information technology delhi"],
|
| 115 |
+
"IIITD": ["indraprastha institute of information technology delhi"],
|
| 116 |
+
"iiith": ["international institute of information technology hyderabad"],
|
| 117 |
+
"IIITH": ["international institute of information technology hyderabad"],
|
| 118 |
+
"iiitb": ["international institute of information technology bangalore"],
|
| 119 |
+
"IIITB": ["international institute of information technology bangalore"],
|
| 120 |
+
"vit": ["vellore institute of technology"],
|
| 121 |
+
"VIT": ["vellore institute of technology"],
|
| 122 |
+
"srm": ["srm institute of science and technology"],
|
| 123 |
+
"SRM": ["srm institute of science and technology"],
|
| 124 |
+
"mit": ["manipal institute of technology"],
|
| 125 |
+
"MIT": ["manipal institute of technology"],
|
| 126 |
+
"pes": ["pes university"],
|
| 127 |
+
"PES": ["pes university"],
|
| 128 |
+
"bms": ["bms college of engineering"],
|
| 129 |
+
"BMS": ["bms college of engineering"],
|
| 130 |
+
"ramaiah": ["ms ramaiah institute of technology"],
|
| 131 |
+
"RAMAIAH": ["ms ramaiah institute of technology"],
|
| 132 |
+
"thapar": ["thapar institute of engineering and technology"],
|
| 133 |
+
"THAPAR": ["thapar institute of engineering and technology"],
|
| 134 |
+
"ism": ["indian school of mines"],
|
| 135 |
+
"ISM": ["indian school of mines"],
|
| 136 |
+
"spa": ["school of planning & architecture"],
|
| 137 |
+
"SPA": ["school of planning & architecture"],
|
| 138 |
+
"vnit": ["visvesvaraya national institute of technology"],
|
| 139 |
+
"VNIT": ["visvesvaraya national institute of technology"],
|
| 140 |
+
"manit": ["maulana azad national institute of technology"],
|
| 141 |
+
"MANIT": ["maulana azad national institute of technology"],
|
| 142 |
+
"svnit": ["sardar vallabhbhai national institute of technology"],
|
| 143 |
+
"SVNIT": ["sardar vallabhbhai national institute of technology"],
|
| 144 |
+
"mnit": ["malaviya national institute of technology"],
|
| 145 |
+
"MNIT": ["malaviya national institute of technology"],
|
| 146 |
+
"mnnit": ["motilal nehru national institute of technology"],
|
| 147 |
+
"MNNIT": ["motilal nehru national institute of technology"],
|
| 148 |
+
"sliet": ["sant longowal institute of engineering and technology"],
|
| 149 |
+
"SLIET": ["sant longowal institute of engineering and technology"],
|
| 150 |
+
"iiest": ["indian institute of engineering science and technology"],
|
| 151 |
+
"IIEST": ["indian institute of engineering science and technology"],
|
| 152 |
+
"cusat": ["cochin university of science and technology"],
|
| 153 |
+
"CUSAT": ["cochin university of science and technology"],
|
| 154 |
+
"niftem": ["national institute of food technology entrepreneurship and management"],
|
| 155 |
+
"NIFTEM": ["national institute of food technology entrepreneurship and management"],
|
| 156 |
+
"iiht": ["indian institute of handloom technology"],
|
| 157 |
+
"IIHT": ["indian institute of handloom technology"],
|
| 158 |
+
"ict": ["institute of chemical technology"],
|
| 159 |
+
"ICT": ["institute of chemical technology"],
|
| 160 |
+
"iitram": ["institute of infrastructure, technology, research and management"],
|
| 161 |
+
"IITRAM": ["institute of infrastructure, technology, research and management"],
|
| 162 |
+
"csvtu": ["chhattisgarh swami vivekanada technical university"],
|
| 163 |
+
"CSVTU": ["chhattisgarh swami vivekanada technical university"],
|
| 164 |
+
"gkc": ["ghani khan choudhary institute of engineering and technology"],
|
| 165 |
+
"GKC": ["ghani khan choudhary institute of engineering and technology"],
|
| 166 |
+
"nerist": ["north eastern regional institute of science and technology"],
|
| 167 |
+
"NERIST": ["north eastern regional institute of science and technology"],
|
| 168 |
+
"cit": ["central institute of technology"],
|
| 169 |
+
"CIT": ["central institute of technology"],
|
| 170 |
+
"tezu": ["tezpur university"],
|
| 171 |
+
"TEZU": ["tezpur university"],
|
| 172 |
+
"nehu": ["north-eastern hill university"],
|
| 173 |
+
"NEHU": ["north-eastern hill university"],
|
| 174 |
+
"mizoram": ["mizoram university"],
|
| 175 |
+
"MIZORAM": ["mizoram university"],
|
| 176 |
+
"assam": ["assam university"],
|
| 177 |
+
"ASSAM": ["assam university"],
|
| 178 |
+
"smvdu": ["shri mata vaishno devi university"],
|
| 179 |
+
"SMVDU": ["shri mata vaishno devi university"],
|
| 180 |
+
"ptu": ["puducherry technological university"],
|
| 181 |
+
"PTU": ["puducherry technological university"],
|
| 182 |
+
"jnu": ["jawaharlal nehru university"],
|
| 183 |
+
"JNU": ["jawaharlal nehru university"],
|
| 184 |
+
"uoh": ["university of hyderabad"],
|
| 185 |
+
"UOH": ["university of hyderabad"],
|
| 186 |
+
"ggu": ["guru ghasidas vishwavidyalaya"],
|
| 187 |
+
"GGU": ["guru ghasidas vishwavidyalaya"],
|
| 188 |
+
"gsv": ["gati shakti vishwavidyalaya"],
|
| 189 |
+
"GSV": ["gati shakti vishwavidyalaya"],
|
| 190 |
+
"gkv": ["gurukula kangri vishwavidyalaya"],
|
| 191 |
+
"GKV": ["gurukula kangri vishwavidyalaya"]
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
BRANCH_MAPPING = {
|
| 195 |
+
"cse": "computer science and engineering",
|
| 196 |
+
"CSE": "computer science and engineering",
|
| 197 |
+
"ece": "electronics and communication engineering",
|
| 198 |
+
"ECE": "electronics and communication engineering",
|
| 199 |
+
"che": "chemical engineering",
|
| 200 |
+
"CHE": "chemical engineering",
|
| 201 |
+
"aero": "aerospace engineering",
|
| 202 |
+
"AERO": "aerospace engineering",
|
| 203 |
+
"bio": "biotechnology",
|
| 204 |
+
"BIO": "biotechnology"
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
|
fapi.py
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, HTTPException
|
| 2 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
+
from fastapi.responses import JSONResponse
|
| 4 |
+
from pydantic import BaseModel
|
| 5 |
+
from agents import coordinator
|
| 6 |
+
from google.adk.sessions import InMemorySessionService
|
| 7 |
+
from constants import INSTITUTE_MAPPING, BRANCH_MAPPING
|
| 8 |
+
from google.adk.tools import google_search
|
| 9 |
+
from google.adk.runners import Runner
|
| 10 |
+
from google.genai import types # Add this import for Content and Part
|
| 11 |
+
from dotenv import load_dotenv
|
| 12 |
+
import os
|
| 13 |
+
import re
|
| 14 |
+
import datetime
|
| 15 |
+
import datetime
|
| 16 |
+
|
| 17 |
+
# Load environment variables
|
| 18 |
+
load_dotenv()
|
| 19 |
+
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
app = FastAPI(
|
| 23 |
+
title="PreBot College Counselor API",
|
| 24 |
+
description="AI-powered college counseling system with multi-agent architecture",
|
| 25 |
+
version="1.0.0",
|
| 26 |
+
docs_url="/docs",
|
| 27 |
+
redoc_url="/redoc"
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
# Enable CORS for all origins (adjust for production)
|
| 31 |
+
app.add_middleware(
|
| 32 |
+
CORSMiddleware,
|
| 33 |
+
allow_origins=["*"],
|
| 34 |
+
allow_credentials=True,
|
| 35 |
+
allow_methods=["*"],
|
| 36 |
+
allow_headers=["*"],
|
| 37 |
+
)
|
| 38 |
+
|
| 39 |
+
# Use a shared session service instance
|
| 40 |
+
session_service = InMemorySessionService()
|
| 41 |
+
|
| 42 |
+
class ChatRequest(BaseModel):
|
| 43 |
+
user_id: str
|
| 44 |
+
session_id: str
|
| 45 |
+
question: str
|
| 46 |
+
|
| 47 |
+
class ChatResponse(BaseModel):
|
| 48 |
+
session_id: str
|
| 49 |
+
answer: str
|
| 50 |
+
|
| 51 |
+
def preprocess_query(query: str) -> str:
|
| 52 |
+
sorted_institutes = sorted(INSTITUTE_MAPPING.keys(), key=len, reverse=True)
|
| 53 |
+
for key in sorted_institutes:
|
| 54 |
+
pattern = rf'\b{re.escape(key)}\b'
|
| 55 |
+
query = re.sub(pattern, INSTITUTE_MAPPING[key][0], query, flags=re.IGNORECASE)
|
| 56 |
+
|
| 57 |
+
for key, full_name in BRANCH_MAPPING.items():
|
| 58 |
+
pattern = rf'\b{re.escape(key)}\b'
|
| 59 |
+
query = re.sub(pattern, full_name, query, flags=re.IGNORECASE)
|
| 60 |
+
|
| 61 |
+
return query
|
| 62 |
+
|
| 63 |
+
@app.options("/chat")
|
| 64 |
+
async def chat_options():
|
| 65 |
+
return JSONResponse(
|
| 66 |
+
content={"message": "OK"},
|
| 67 |
+
headers={
|
| 68 |
+
"Access-Control-Allow-Origin": "*",
|
| 69 |
+
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
| 70 |
+
"Access-Control-Allow-Headers": "*",
|
| 71 |
+
}
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
@app.post("/chat", response_model=ChatResponse)
|
| 75 |
+
async def chat_endpoint(req: ChatRequest):
|
| 76 |
+
try:
|
| 77 |
+
print(f"Received request - User ID: {req.user_id}, Session ID: {req.session_id}")
|
| 78 |
+
print(f"Question: {req.question}")
|
| 79 |
+
|
| 80 |
+
# Check if session exists, create if not (methods are NOT async for InMemorySessionService)
|
| 81 |
+
print("Checking for existing session...")
|
| 82 |
+
existing_session = session_service.get_session(
|
| 83 |
+
app_name="coordinator_agent",
|
| 84 |
+
user_id=req.user_id,
|
| 85 |
+
session_id=req.session_id
|
| 86 |
+
)
|
| 87 |
+
|
| 88 |
+
if not existing_session:
|
| 89 |
+
print("Creating new session...")
|
| 90 |
+
session_service.create_session(
|
| 91 |
+
app_name="coordinator_agent",
|
| 92 |
+
user_id=req.user_id,
|
| 93 |
+
session_id=req.session_id
|
| 94 |
+
)
|
| 95 |
+
else:
|
| 96 |
+
print("Using existing session")
|
| 97 |
+
|
| 98 |
+
# Use the shared session service for the Runner
|
| 99 |
+
print("Creating runner...")
|
| 100 |
+
runner = Runner(
|
| 101 |
+
agent=coordinator,
|
| 102 |
+
app_name="coordinator_agent",
|
| 103 |
+
session_service=session_service # Use the shared session service
|
| 104 |
+
)
|
| 105 |
+
|
| 106 |
+
# Create properly formatted message using Google ADK types
|
| 107 |
+
print("Processing query...")
|
| 108 |
+
processed_query = preprocess_query(req.question)
|
| 109 |
+
print(f"Processed query: {processed_query}")
|
| 110 |
+
|
| 111 |
+
user_msg = types.Content(role="user", parts=[types.Part(text=processed_query)])
|
| 112 |
+
|
| 113 |
+
print("Running agent...")
|
| 114 |
+
agent_response = runner.run(
|
| 115 |
+
user_id=req.user_id,
|
| 116 |
+
session_id=req.session_id,
|
| 117 |
+
new_message=user_msg,
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
# Process the generator response to extract the final answer
|
| 121 |
+
print(f"Agent response type: {type(agent_response)}")
|
| 122 |
+
reply_text = ""
|
| 123 |
+
|
| 124 |
+
if hasattr(agent_response, '__iter__') and not isinstance(agent_response, str):
|
| 125 |
+
print("Processing iterable response...")
|
| 126 |
+
for event in agent_response:
|
| 127 |
+
print(f"Processing event: {event}")
|
| 128 |
+
if hasattr(event, 'is_final_response') and event.is_final_response():
|
| 129 |
+
if hasattr(event, 'content') and hasattr(event.content, 'parts'):
|
| 130 |
+
for part in event.content.parts:
|
| 131 |
+
if hasattr(part, 'text') and part.text:
|
| 132 |
+
reply_text = part.text
|
| 133 |
+
break
|
| 134 |
+
if reply_text:
|
| 135 |
+
break
|
| 136 |
+
elif hasattr(event, 'text'):
|
| 137 |
+
reply_text = event.text
|
| 138 |
+
break
|
| 139 |
+
|
| 140 |
+
if not reply_text:
|
| 141 |
+
reply_text = "Sorry, there is an issue in our end :("
|
| 142 |
+
else:
|
| 143 |
+
print("Processing direct response...")
|
| 144 |
+
reply_text = str(agent_response)
|
| 145 |
+
|
| 146 |
+
print(f"Final reply: {reply_text}")
|
| 147 |
+
return ChatResponse(session_id=req.session_id, answer=reply_text)
|
| 148 |
+
except Exception as e:
|
| 149 |
+
print(f"Error occurred: {str(e)}")
|
| 150 |
+
print(f"Error type: {type(e)}")
|
| 151 |
+
import traceback
|
| 152 |
+
print(f"Full traceback: {traceback.format_exc()}")
|
| 153 |
+
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
|
| 154 |
+
|
| 155 |
+
# Add health check endpoint
|
| 156 |
+
@app.get("/")
|
| 157 |
+
async def root():
|
| 158 |
+
return {
|
| 159 |
+
"message": "PreBot College Counselor API is running!",
|
| 160 |
+
"status": "healthy",
|
| 161 |
+
"version": "1.0.0",
|
| 162 |
+
"endpoints": {
|
| 163 |
+
"chat": "/chat",
|
| 164 |
+
"docs": "/docs",
|
| 165 |
+
"redoc": "/redoc"
|
| 166 |
+
}
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
@app.get("/health")
|
| 170 |
+
async def health_check():
|
| 171 |
+
return {"status": "healthy", "timestamp": datetime.datetime.now().isoformat()}
|
requirements.txt
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi==0.104.1
|
| 2 |
+
uvicorn[standard]==0.24.0
|
| 3 |
+
python-dotenv==1.0.0
|
| 4 |
+
requests==2.31.0
|
| 5 |
+
langchain==0.1.0
|
| 6 |
+
langchain-community==0.0.13
|
| 7 |
+
sentence-transformers==2.2.2
|
| 8 |
+
faiss-cpu==1.7.4
|
| 9 |
+
pydantic==2.5.0
|
| 10 |
+
langchain-tavily==0.1.0
|
| 11 |
+
google-adk
|
| 12 |
+
google-genai
|
| 13 |
+
google-cloud-aiplatform
|
tools.py
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
import datetime
|
| 4 |
+
import requests
|
| 5 |
+
import logging
|
| 6 |
+
# import gspread
|
| 7 |
+
from dotenv import load_dotenv
|
| 8 |
+
|
| 9 |
+
# Configure logging
|
| 10 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
| 11 |
+
logger = logging.getLogger(__name__)
|
| 12 |
+
# from huggingface_hub import login as hf_login
|
| 13 |
+
from langchain_community.vectorstores import FAISS
|
| 14 |
+
from langchain.embeddings.base import Embeddings
|
| 15 |
+
from sentence_transformers import SentenceTransformer
|
| 16 |
+
from langchain_tavily import TavilySearch
|
| 17 |
+
from google.adk.tools import FunctionTool
|
| 18 |
+
|
| 19 |
+
# === LOAD ENV ===
|
| 20 |
+
load_dotenv()
|
| 21 |
+
# HF_TOKEN = os.getenv("HUGGINGFACE_TOKEN")
|
| 22 |
+
# GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
|
| 23 |
+
TAVILY_API_KEY = os.getenv("TAVILY_API_KEY")
|
| 24 |
+
# SERVICE_ACCOUNT_JSON = os.getenv("GOOGLE_SERVICE_ACCOUNT_JSON")
|
| 25 |
+
# SHEET_KEY = os.getenv("SHEET_KEY")
|
| 26 |
+
PREDICTOR_API_URL = os.getenv("PREDICTOR_API_URL")
|
| 27 |
+
PREDICTOR_API_KEY = os.getenv("PREDICTOR_API_KEY")
|
| 28 |
+
|
| 29 |
+
# hf_login(token=HF_TOKEN)
|
| 30 |
+
|
| 31 |
+
# === GOOGLE SHEET LOGGING ===
|
| 32 |
+
# service_account_dict = json.loads(SERVICE_ACCOUNT_JSON) if isinstance(SERVICE_ACCOUNT_JSON, str) else SERVICE_ACCOUNT_JSON
|
| 33 |
+
|
| 34 |
+
# def add_query_to_sheet(user_id: str, query: str, response: str):
|
| 35 |
+
# gc = gspread.service_account_from_dict(service_account_dict)
|
| 36 |
+
# sh = gc.open_by_key(SHEET_KEY)
|
| 37 |
+
# ws = sh.worksheet("Sheet1")
|
| 38 |
+
# timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 39 |
+
# ws.append_row([user_id, timestamp, query, response])
|
| 40 |
+
|
| 41 |
+
# === VECTOR STORE ===
|
| 42 |
+
def load_vector_store(data_dir: str):
|
| 43 |
+
texts = []
|
| 44 |
+
for fname in os.listdir(data_dir):
|
| 45 |
+
if fname.lower().endswith(".md"):
|
| 46 |
+
path = os.path.join(data_dir, fname)
|
| 47 |
+
try:
|
| 48 |
+
with open(path, "r", encoding="utf-8") as f:
|
| 49 |
+
texts.append(f.read())
|
| 50 |
+
except UnicodeDecodeError:
|
| 51 |
+
with open(path, "r", encoding="latin-1") as f:
|
| 52 |
+
texts.append(f.read())
|
| 53 |
+
st_model = SentenceTransformer("all-MiniLM-L6-v2")
|
| 54 |
+
class LocalEmbeddings(Embeddings):
|
| 55 |
+
def embed_documents(self, docs):
|
| 56 |
+
return st_model.encode(docs).tolist()
|
| 57 |
+
def embed_query(self, q):
|
| 58 |
+
return st_model.encode([q])[0].tolist()
|
| 59 |
+
return FAISS.from_texts(texts, LocalEmbeddings())
|
| 60 |
+
|
| 61 |
+
vector_store = load_vector_store("College_markdown")
|
| 62 |
+
|
| 63 |
+
# === TOOL DEFINITIONS ===
|
| 64 |
+
def db_search(query: str) -> dict:
|
| 65 |
+
docs = vector_store.similarity_search(query, k=6)
|
| 66 |
+
if not docs: return {"results": []}
|
| 67 |
+
return {"results": [d.page_content for d in docs]}
|
| 68 |
+
|
| 69 |
+
def tavily_search(query: str) -> dict:
|
| 70 |
+
if not TAVILY_API_KEY:
|
| 71 |
+
return {"results": ["Tavily API key not configured"]}
|
| 72 |
+
|
| 73 |
+
tool = TavilySearch(api_key=TAVILY_API_KEY, max_results=6, topic="general", include_raw_content=True)
|
| 74 |
+
result = tool.invoke({"query": query})
|
| 75 |
+
snippets = [item.get('content') for item in result.get('results', [])]
|
| 76 |
+
return {"results": snippets or []}
|
| 77 |
+
|
| 78 |
+
def college_predictor(
|
| 79 |
+
userCrl: int,
|
| 80 |
+
userCategory: str,
|
| 81 |
+
userGender: str,
|
| 82 |
+
userHomeState: str,
|
| 83 |
+
limit: int = 4,
|
| 84 |
+
counsellingName: str = "csab",
|
| 85 |
+
collegeName: str = "national institute of technology",
|
| 86 |
+
branchName: str = "computer science and engineering"
|
| 87 |
+
) -> str:
|
| 88 |
+
# Log the function call with all parameters
|
| 89 |
+
logger.info("=" * 80)
|
| 90 |
+
logger.info("PREDICTOR API CALL STARTED")
|
| 91 |
+
logger.info("=" * 80)
|
| 92 |
+
|
| 93 |
+
logger.info("Input Parameters:")
|
| 94 |
+
logger.info(f" userCrl: {userCrl} (type: {type(userCrl)})")
|
| 95 |
+
logger.info(f" userCategory: {userCategory} (type: {type(userCategory)})")
|
| 96 |
+
logger.info(f" userGender: {userGender} (type: {type(userGender)})")
|
| 97 |
+
logger.info(f" userHomeState: {userHomeState} (type: {type(userHomeState)})")
|
| 98 |
+
logger.info(f" limit: {limit} (type: {type(limit)})")
|
| 99 |
+
logger.info(f" counsellingName: {counsellingName} (type: {type(counsellingName)})")
|
| 100 |
+
logger.info(f" collegeName: {collegeName} (type: {type(collegeName)})")
|
| 101 |
+
logger.info(f" branchName: {branchName} (type: {type(branchName)})")
|
| 102 |
+
|
| 103 |
+
headers = {
|
| 104 |
+
"Content-Type": "application/json",
|
| 105 |
+
"Authorization": f"Bearer {PREDICTOR_API_KEY}"
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
try:
|
| 109 |
+
# Log parameter conversion
|
| 110 |
+
logger.info("Converting parameters...")
|
| 111 |
+
converted_crl = int(userCrl)
|
| 112 |
+
logger.info(f" userCrl converted: {converted_crl} (type: {type(converted_crl)})")
|
| 113 |
+
|
| 114 |
+
params = {
|
| 115 |
+
"userCrl": converted_crl,
|
| 116 |
+
"userCategory": userCategory,
|
| 117 |
+
"userGender": userGender,
|
| 118 |
+
"userHomeState": userHomeState,
|
| 119 |
+
"limit": limit,
|
| 120 |
+
"counsellingName": counsellingName,
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
if collegeName:
|
| 124 |
+
params["collegeQuery"] = collegeName
|
| 125 |
+
logger.info(f" Added collegeQuery: {collegeName}")
|
| 126 |
+
if branchName:
|
| 127 |
+
params["branchQuery"] = branchName
|
| 128 |
+
logger.info(f" Added branchQuery: {branchName}")
|
| 129 |
+
|
| 130 |
+
# Log the final payload
|
| 131 |
+
logger.info("Final API Request:")
|
| 132 |
+
logger.info(f" URL: {PREDICTOR_API_URL}")
|
| 133 |
+
logger.info(f" Headers: {json.dumps(headers, indent=2)}")
|
| 134 |
+
logger.info(f" Payload: {json.dumps(params, indent=2)}")
|
| 135 |
+
|
| 136 |
+
logger.info("Making API request...")
|
| 137 |
+
response = requests.post(PREDICTOR_API_URL, json=params, headers=headers, timeout=30)
|
| 138 |
+
|
| 139 |
+
# Log response details
|
| 140 |
+
logger.info("API Response:")
|
| 141 |
+
logger.info(f" Status Code: {response.status_code}")
|
| 142 |
+
logger.info(f" Response Headers: {dict(response.headers)}")
|
| 143 |
+
logger.info(f" Response Content: {response.text}")
|
| 144 |
+
|
| 145 |
+
response.raise_for_status()
|
| 146 |
+
data = response.json()
|
| 147 |
+
|
| 148 |
+
logger.info("Response parsed successfully")
|
| 149 |
+
logger.info(f" Parsed data keys: {list(data.keys()) if data else 'None'}")
|
| 150 |
+
|
| 151 |
+
if not data or 'data' not in data or 'colleges' not in data['data']:
|
| 152 |
+
logger.warning("No college predictions found in response")
|
| 153 |
+
return "No college predictions found with the given criteria."
|
| 154 |
+
|
| 155 |
+
colleges = data['data']['colleges']
|
| 156 |
+
logger.info(f"Found {len(colleges)} colleges in response")
|
| 157 |
+
|
| 158 |
+
if not colleges:
|
| 159 |
+
logger.warning("Colleges list is empty")
|
| 160 |
+
return "No college predictions found with the given criteria."
|
| 161 |
+
|
| 162 |
+
results = []
|
| 163 |
+
for i, college in enumerate(colleges[:limit], start=1):
|
| 164 |
+
logger.info(f"Processing college {i}: {college.get('Institute', 'N/A')}")
|
| 165 |
+
parts = [f"{i}. College: {college.get('Institute', 'N/A')}"]
|
| 166 |
+
if college.get('Academic_Program_Name'):
|
| 167 |
+
parts.append(f"Branch: {college['Academic_Program_Name']}")
|
| 168 |
+
if college.get('Seat_Type'):
|
| 169 |
+
parts.append(f"Category: {college['Seat_Type']}")
|
| 170 |
+
if college.get('Max_ClosingRank'):
|
| 171 |
+
parts.append(f"Closing Rank: {college['Max_ClosingRank']}")
|
| 172 |
+
results.append(", ".join(parts))
|
| 173 |
+
|
| 174 |
+
final_result = f"Based on your rank {userCrl}, here are college predictions:\n\n" + "\n".join(results)
|
| 175 |
+
|
| 176 |
+
logger.info("=" * 80)
|
| 177 |
+
logger.info("PREDICTOR API CALL COMPLETED SUCCESSFULLY")
|
| 178 |
+
logger.info("=" * 80)
|
| 179 |
+
|
| 180 |
+
return final_result
|
| 181 |
+
|
| 182 |
+
except ValueError as e:
|
| 183 |
+
error_msg = f"Parameter conversion error: {str(e)}"
|
| 184 |
+
logger.error(f"ValueError: {error_msg}")
|
| 185 |
+
logger.error("=" * 80)
|
| 186 |
+
return error_msg
|
| 187 |
+
|
| 188 |
+
except requests.exceptions.HTTPError as e:
|
| 189 |
+
error_msg = f"HTTP Error: {str(e)}"
|
| 190 |
+
logger.error(f"HTTPError: {error_msg}")
|
| 191 |
+
logger.error(f"Response body: {response.text if 'response' in locals() else 'No response'}")
|
| 192 |
+
logger.error("=" * 80)
|
| 193 |
+
return f"Error fetching college predictions: {error_msg}"
|
| 194 |
+
|
| 195 |
+
except requests.exceptions.RequestException as e:
|
| 196 |
+
error_msg = f"Request Error: {str(e)}"
|
| 197 |
+
logger.error(f"RequestException: {error_msg}")
|
| 198 |
+
logger.error("=" * 80)
|
| 199 |
+
return f"Error fetching college predictions: {error_msg}"
|
| 200 |
+
|
| 201 |
+
except Exception as e:
|
| 202 |
+
error_msg = f"Unexpected error: {str(e)}"
|
| 203 |
+
logger.error(f"Exception: {error_msg}")
|
| 204 |
+
logger.error("=" * 80)
|
| 205 |
+
return f"Error fetching college predictions: {error_msg}"
|
| 206 |
+
|
| 207 |
+
def mentor_search(college_query: str) -> str:
|
| 208 |
+
"""Search mentors by college name and return formatted links."""
|
| 209 |
+
url = f"https://api.precollege.in/api/v1/llm/search?limit=3&college={college_query}"
|
| 210 |
+
try:
|
| 211 |
+
response = requests.get(url, timeout=10)
|
| 212 |
+
response.raise_for_status()
|
| 213 |
+
data = response.json()
|
| 214 |
+
|
| 215 |
+
if not data or "data" not in data or not data["data"]:
|
| 216 |
+
return f"No mentors found for '{college_query}'."
|
| 217 |
+
|
| 218 |
+
mentors = data["data"]
|
| 219 |
+
lines = []
|
| 220 |
+
for mentor in mentors:
|
| 221 |
+
name = mentor.get("name", "Unknown")
|
| 222 |
+
username = mentor.get("username", "")
|
| 223 |
+
profile_url = f"https://www.precollege.in/mentor/{username}" if username else "No profile link"
|
| 224 |
+
lines.append(f"{name}: {profile_url}")
|
| 225 |
+
|
| 226 |
+
return f"Mentors for '{college_query}':\n\n" + "\n".join(lines)
|
| 227 |
+
except requests.exceptions.RequestException as e:
|
| 228 |
+
return f"Failed to fetch mentors: {str(e)}"
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
# === FUNCTION TOOL WRAPPERS ===
|
| 232 |
+
db_tool = FunctionTool(db_search)
|
| 233 |
+
tavily_tool = FunctionTool(tavily_search)
|
| 234 |
+
predictor_tool = FunctionTool(college_predictor)
|
| 235 |
+
mentor_tool = FunctionTool(mentor_search)
|