wearevenom commited on
Commit
6f3e11f
·
verified ·
1 Parent(s): 342f5e3

Upload 7 files

Browse files
Files changed (7) hide show
  1. Dockerfile +11 -0
  2. agents.py +39 -0
  3. app.py +8 -0
  4. constants.py +210 -0
  5. fapi.py +171 -0
  6. requirements.txt +13 -0
  7. 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)