Skip to content

Commit b71a76a

Browse files
Added code for video link
1 parent 1f48d6d commit b71a76a

21 files changed

+1430
-1042
lines changed

AD-HOC/test.py

Lines changed: 35 additions & 1037 deletions
Large diffs are not rendered by default.
Binary file not shown.

FASTAPI-DEPLOYMENT/rhl_fastapi_v2_modify.py

Lines changed: 197 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
from langchain_openai import ChatOpenAI
2424
from langchain_google_genai import ChatGoogleGenerativeAI
2525
from typing import Optional
26+
import pandas as pd
27+
import numpy as np
28+
from sklearn.metrics.pairwise import cosine_similarity
2629

2730

2831
# -------------------- CONFIG --------------------
@@ -471,6 +474,7 @@ async def _background_update_and_save(user_id: str, user_message: str, bot_reply
471474
- Always answer ONLY from <context> provided below which caters to the query.
472475
- NEVER use the web, external sources, or prior knowledge outside the given context.
473476
- Always consider query and answer in relevance to it.
477+
- Be Very PRECISE AND TO THE POINT ABOUT WHAT IS ASKED IN THE QUERY AND ANSWER SHOULD BE STRICTLY IN THE CONTEXT PROVIDED AND NOT ANYTHING ELSE.
474478
- ANSWER STRICTLY IN 150-200 WORDS (USING BULLET AND SUB-BULLET POINTS [MAX 5-6 POINTS]) which encapusaltes the key points and information from the context to answer the query in an APT FLOW
475479
- Always follow below mentioned rules at all times :
476480
• Begin each answer with: **"According to <source>"** (extract filename from metadata if available **DONT MENTION EXTENSIONS** eg, According to abc✅(correct), According to xyz.pdf❌(incorrect)).
@@ -985,6 +989,166 @@ def handle_chitchat(user_message: str, chat_history: str) -> str:
985989
print(f"[chitchat] Gemini LLM failed: {e}")
986990
return "Whoa, let's keep it polite, please! 😊"
987991

992+
# -------------------- VIDEO MATCHING SYSTEM --------------------
993+
class VideoMatchingSystem:
994+
def __init__(self, video_file_path: str = "D:\\RHL-WH\\RHL-FASTAPI\\FILES\\video_link_topic.xlsx"):
995+
"""Initialize the video matching system"""
996+
self.video_file_path = video_file_path
997+
self.topic_dict = {} # topic -> index
998+
self.url_dict = {} # index -> URL
999+
1000+
# Load video data
1001+
self._load_video_data()
1002+
1003+
def _load_video_data(self):
1004+
"""Load and preprocess video data"""
1005+
try:
1006+
df = pd.read_excel(self.video_file_path)
1007+
print(f"[VIDEO_SYSTEM] Loaded {len(df)} videos from {self.video_file_path}")
1008+
1009+
# Create dictionaries
1010+
for idx, row in df.iterrows():
1011+
topic = row['video_topic'].strip()
1012+
url = row['URL'].strip()
1013+
1014+
if topic and url:
1015+
self.topic_dict[topic] = idx
1016+
self.url_dict[idx] = url
1017+
1018+
print(f"[VIDEO_SYSTEM] Created topic_dict with {len(self.topic_dict)} topics")
1019+
1020+
except Exception as e:
1021+
print(f"[VIDEO_SYSTEM] Error loading video data: {e}")
1022+
self.topic_dict = {}
1023+
self.url_dict = {}
1024+
1025+
def pre_filter_topics(self, answer: str, min_matches: int = 4) -> List[Tuple[int, int]]:
1026+
"""Strict word matching to reduce candidates - only highly relevant topics"""
1027+
candidates = []
1028+
answer_words = set(answer.lower().split())
1029+
1030+
# Remove common words that don't add meaning
1031+
stop_words = {'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by', 'is', 'are', 'was', 'were', 'be', 'been', 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could', 'should', 'may', 'might', 'can', 'must', 'this', 'that', 'these', 'those', 'i', 'you', 'he', 'she', 'it', 'we', 'they', 'me', 'him', 'her', 'us', 'them'}
1032+
answer_words = answer_words - stop_words
1033+
1034+
for topic, idx in self.topic_dict.items():
1035+
# Split topic by comma and get individual words
1036+
topic_words = set()
1037+
for term in topic.split(','):
1038+
topic_words.update(term.strip().lower().split())
1039+
1040+
# Remove stop words from topic words too
1041+
topic_words = topic_words - stop_words
1042+
1043+
# Count matches
1044+
matches = len(answer_words.intersection(topic_words))
1045+
1046+
# Much stricter criteria: need at least 4 meaningful word matches
1047+
if matches >= min_matches:
1048+
candidates.append((idx, matches))
1049+
1050+
# Sort by matches (descending)
1051+
candidates.sort(key=lambda x: x[1], reverse=True)
1052+
return candidates
1053+
1054+
def llm_score_candidates(self, answer: str, candidates: List[Tuple[int, int]]) -> Optional[int]:
1055+
"""Use Gemini to score top candidates"""
1056+
if len(candidates) <= 1:
1057+
return candidates[0][0] if candidates else None
1058+
1059+
# Create prompt with top candidates
1060+
topic_list = []
1061+
for idx, matches in candidates[:10]: # Limit to top 10 for efficiency
1062+
topic = list(self.topic_dict.keys())[list(self.topic_dict.values()).index(idx)]
1063+
topic_list.append(f"{idx}: {topic}")
1064+
1065+
prompt = f"""Score these video topics against the medical answer (0-100 each):
1066+
1067+
Answer: {answer}
1068+
1069+
Topics:
1070+
{chr(10).join(topic_list)}
1071+
1072+
IMPORTANT: Only give high scores (80+) if the video topic is DIRECTLY and STRONGLY related to the medical answer.
1073+
- 90-100: Perfect match, video directly addresses the answer topic
1074+
- 80-89: Strong match, video covers the same medical condition/treatment
1075+
- 70-79: Moderate match, video is somewhat related
1076+
- 60-69: Weak match, video has some connection
1077+
- 0-59: No meaningful connection
1078+
1079+
Return JSON: {{"scores": [85, 92, 45, ...]}}"""
1080+
1081+
try:
1082+
response = gemini_llm.invoke([HumanMessage(content=prompt)]).content
1083+
1084+
# Parse JSON response
1085+
try:
1086+
# Extract JSON from response
1087+
json_start = response.find('{')
1088+
json_end = response.rfind('}') + 1
1089+
if json_start != -1 and json_end > json_start:
1090+
json_str = response[json_start:json_end]
1091+
scores_data = json.loads(json_str)
1092+
scores = scores_data.get('scores', [])
1093+
1094+
if scores and len(scores) == len(candidates[:10]):
1095+
# Find best score
1096+
best_score = max(scores)
1097+
best_score_idx = scores.index(best_score)
1098+
best_candidate_idx = candidates[best_score_idx][0]
1099+
1100+
# Only return if score is high enough (80+ for strong relevance)
1101+
if best_score >= 80:
1102+
print(f"[VIDEO_SYSTEM] Best score: {best_score} (meets threshold)")
1103+
return best_candidate_idx
1104+
else:
1105+
print(f"[VIDEO_SYSTEM] Best score: {best_score} (below 80 threshold, no video)")
1106+
return None
1107+
1108+
except Exception as e:
1109+
print(f"[VIDEO_SYSTEM] Error parsing LLM response: {e}")
1110+
1111+
except Exception as e:
1112+
print(f"[VIDEO_SYSTEM] LLM call failed: {e}")
1113+
1114+
# Fallback to first candidate
1115+
return candidates[0][0] if candidates else None
1116+
1117+
def find_relevant_video(self, answer: str) -> Optional[str]:
1118+
"""Find relevant video URL for the answer - STRICT MATCHING ONLY"""
1119+
if not self.topic_dict:
1120+
return None
1121+
1122+
print(f"[VIDEO_SYSTEM] Searching for video for answer: {answer[:100]}...")
1123+
1124+
# Step 1: Pre-filtering (strict - need 4+ meaningful word matches)
1125+
candidates = self.pre_filter_topics(answer, min_matches=4)
1126+
1127+
if not candidates:
1128+
print("[VIDEO_SYSTEM] No candidates found with 4+ meaningful word matches")
1129+
return None
1130+
1131+
print(f"[VIDEO_SYSTEM] Found {len(candidates)} candidates after pre-filtering")
1132+
1133+
# Step 2: LLM scoring (if multiple candidates)
1134+
if len(candidates) == 1:
1135+
# For single candidate, still use LLM to verify relevance
1136+
best_idx = self.llm_score_candidates(answer, candidates)
1137+
else:
1138+
best_idx = self.llm_score_candidates(answer, candidates)
1139+
1140+
# Step 3: Get URL only if we have a valid, high-scoring match
1141+
if best_idx is not None and best_idx in self.url_dict:
1142+
video_url = self.url_dict[best_idx]
1143+
print(f"[VIDEO_SYSTEM] Found relevant video: {video_url}")
1144+
return video_url
1145+
else:
1146+
print("[VIDEO_SYSTEM] No video found - no high-relevance matches")
1147+
return None
1148+
1149+
# Global video matching system
1150+
video_system: VideoMatchingSystem = None
1151+
9881152
# -------------------- MAIN PIPELINE (called by API) --------------------
9891153
async def medical_pipeline_api(user_id: str, user_message: str, background_tasks: BackgroundTasks) -> Dict[str, Any]:
9901154
print(f"[pipeline] Start user_id={user_id}, message={user_message[:50]}")
@@ -1073,14 +1237,36 @@ async def medical_pipeline_api(user_id: str, user_message: str, background_tasks
10731237
if label != "FOLLOW_UP" and correction:
10741238
correction_msg = "I guess you meant " + " and ".join(correction.values())
10751239
answer = correction_msg + "\n" + answer
1240+
1241+
# Find relevant video URL
1242+
print("[pipeline] Step 6: Finding relevant video...")
1243+
video_url = None
1244+
if video_system:
1245+
video_start = time.perf_counter()
1246+
video_url = video_system.find_relevant_video(answer)
1247+
video_end = time.perf_counter()
1248+
print(f"[pipeline] Video matching took {video_end - video_start:.3f} secs")
1249+
if video_url:
1250+
print(f"[pipeline] Found relevant video: {video_url}")
1251+
else:
1252+
print("[pipeline] No relevant video found")
1253+
10761254
# Schedule full update+save in background (do not run update_chat_history in request path)
10771255
print("[pipeline] schedule background save: answer")
10781256
background_tasks.add_task(_background_update_and_save, user_id, user_message, answer, "answer", history_pairs, current_summary)
10791257
print("[pipeline] done with answer")
10801258
t_end = time.perf_counter()
10811259
timer.total("request")
10821260
print(f"total took {t_end - start_time:.2f} secs")
1083-
return {"answer": answer, "intent": "answer", "follow_up": followup_q if followup_q else None}
1261+
1262+
# Return response with video URL
1263+
response = {"answer": answer, "intent": "answer", "follow_up": followup_q if followup_q else None}
1264+
if video_url:
1265+
response["video_url"] = video_url
1266+
else:
1267+
response["video_url"] = None
1268+
1269+
return response
10841270

10851271
else:
10861272
msg = (
@@ -1094,13 +1280,15 @@ async def medical_pipeline_api(user_id: str, user_message: str, background_tasks
10941280
t_end = time.perf_counter()
10951281
timer.total("request")
10961282
print(f"total took {t_end - start_time:.2f} secs")
1097-
return {"answer": msg, "intent": "no_context", "follow_up": None}
1283+
1284+
# Return response with video URL (None for no_context)
1285+
return {"answer": msg, "intent": "no_context", "follow_up": None, "video_url": None}
10981286

10991287

11001288
# -------------------- API ENDPOINTS --------------------
11011289
@app.on_event("startup")
11021290
async def startup_event():
1103-
global embedding_model, reranker, pinecone_index, llm, summarizer_llm, reformulate_llm, classifier_llm, gemini_llm, EMBED_DIM
1291+
global embedding_model, reranker, pinecone_index, llm, summarizer_llm, reformulate_llm, classifier_llm, gemini_llm, EMBED_DIM, video_system
11041292
print("[startup] Initializing models and Pinecone client...")
11051293
t = CheckpointTimer("startup")
11061294
embedding_model = SentenceTransformer("sentence-transformers/all-mpnet-base-v2")
@@ -1136,6 +1324,11 @@ async def startup_event():
11361324
gemini_llm = ChatGoogleGenerativeAI(model="gemini-2.5-flash-lite", api_key=GOOGLE_API_KEY)
11371325
t.mark("init_llms")
11381326

1327+
# Initialize video matching system
1328+
print("[startup] Initializing video matching system...")
1329+
video_system = VideoMatchingSystem()
1330+
t.mark("init_video_system")
1331+
11391332
await init_db()
11401333
t.mark("init_db")
11411334
print("[startup] FastAPI application startup complete.")
@@ -1163,4 +1356,4 @@ async def chat_endpoint(
11631356

11641357
if __name__ == "__main__":
11651358
import uvicorn
1166-
uvicorn.run(app, host="0.0.0.0", port=8000)
1359+
uvicorn.run(app, host="0.0.0.0", port=8000)

FILES/cache_questions.xlsx

16.2 KB
Binary file not shown.

FILES/video_link.xlsx

41.2 KB
Binary file not shown.

0 commit comments

Comments
 (0)