Programmer140/Hackathon
0
1"""Chat functionality using OpenAI-compatible API and Qdrant for retrieval."""2 3import os4from openai import OpenAI5from qdrant_client import QdrantClient6from qdrant_client.models import VectorParams, Distance, SearchParams7from qdrant_client.http import models8from config import (9 OPENAI_API_KEY, COHERE_API_KEY, QDRANT_URL, QDRANT_API_KEY,10 COLLECTION_NAME, SEARCH_TOP_K, MAX_TOKENS_RESPONSE, TEMPERATURE11)12import cohere13 14 15# Initialize OpenAI client with OpenRouter as backend16client = OpenAI(17 api_key=OPENAI_API_KEY,18 base_url="https://openrouter.ai/api/v1",19)20 21# Initialize Cohere client for embeddings22cohere_client = cohere.Client(COHERE_API_KEY)23 24# Initialize Qdrant client - using grpc=False to ensure REST API is used25qdrant_client = QdrantClient(url=QDRANT_URL, api_key=QDRANT_API_KEY, prefer_grpc=False)26 27 28def embed_query(query):29 """Generate embeddings for the input query using Cohere."""30 response = cohere_client.embed(31 model="embed-english-v3.0",32 input_type="search_query",33 texts=[query],34 )35 return response.embeddings[0]36 37 38def query_qdrant(query_text):39 """Query the Qdrant database for similar documents to the query."""40 query_embedding = embed_query(query_text)41 42 # Check if search method exists and call it appropriately43 search_results = []44 try:45 # Check if the collection exists before searching46 collection_exists = False47 try:48 collection_info = qdrant_client.get_collection(collection_name=COLLECTION_NAME)49 # Access point_count using dictionary-style access for compatibility50 point_count = collection_info.points_count if hasattr(collection_info, 'points_count') else collection_info.get('points_count', 0) if isinstance(collection_info, dict) else 051 collection_exists = True52 print(f"Collection exists with {point_count} points")53 except Exception as e:54 print(f"Collection {COLLECTION_NAME} does not exist or is inaccessible: {e}")55 return [] # Return empty if collection doesn't exist56 57 # Standard approach for newer qdrant-client versions58 search_results = qdrant_client.search(59 collection_name=COLLECTION_NAME,60 query_vector=query_embedding,61 limit=SEARCH_TOP_K,62 with_payload=True63 )64 65 # Log the number of retrieved book chunks66 print(f"Retrieved {len(search_results)} book chunks for query: '{query_text}'")67 except AttributeError as e:68 if "'QdrantClient' object has no attribute 'search'" in str(e):69 # This error occurs when the client doesn't have the search method70 # Return empty results as a fallback for this specific error71 print(f"Qdrant search method not available: {e}")72 search_results = []73 else:74 # Re-raise other attribute errors75 raise76 except Exception as e:77 # Log other errors and return empty results as fallback78 print(f"Error during Qdrant search: {e}")79 search_results = []80 81 return search_results82 83 84def format_response(query, search_results):85 """Format the response using OpenRouter based on query and search results."""86 # Prepare context from search results87 context_parts = []88 89 # Check if search_results exist and have content90 if search_results:91 for result in search_results:92 # Handle different possible structures of result objects93 if hasattr(result, 'payload'):94 text = result.payload.get('text', '') if result.payload else ''95 source = result.payload.get('source_file', 'Unknown source') if result.payload else 'Unknown source'96 else:97 # If result is a dict or other format98 text = result.get('text', '') if isinstance(result, dict) else ''99 source = result.get('source_file', 'Unknown source') if isinstance(result, dict) else 'Unknown source'100 101 score = getattr(result, 'score', 0) if hasattr(result, 'score') else result.get('score', 0) if isinstance(result, dict) else 0102 103 context_parts.append(f"Source: {source}\nRelevance Score: {score}\nContent: {text}\n---")104 105 combined_context = "\n".join(context_parts)106 107 # Create a prompt for OpenRouter that includes the context108 prompt = f"""109 You are an AI assistant for the Physical AI & Humanoid Robotics Curriculum.110 Answer the user's query based on the provided context from the curriculum.111 If the context doesn't contain relevant information, politely say that you don't have enough information to answer the query.112 113 Context:114 {combined_context}115 116 User Query:117 {query}118 119 Response:120 """121 122 # Use OpenRouter models to generate a response123 # Using specific primary and fallback models for production stability124 model_name = None125 126 # Primary and fallback models for OpenRouter127 primary_model = 'openai/gpt-4o' # Using GPT-4o as primary model128 fallback_model = 'openai/gpt-4o-mini' # Using GPT-4o-mini as fallback129 130 # Try primary model first131 try:132 response = client.chat.completions.create(133 model=primary_model,134 messages=[135 {136 "role": "system",137 "content": "You are an AI assistant for the Physical AI & Humanoid Robotics Curriculum. Answer the user's query based on the provided context from the curriculum. If the context doesn't contain relevant information, politely say that you don't have enough information to answer the query."138 },139 {140 "role": "user",141 "content": prompt142 }143 ],144 max_tokens=MAX_TOKENS_RESPONSE,145 temperature=TEMPERATURE146 )147 model_name = primary_model148 print(f"Using primary model: {primary_model}")149 response_text = response.choices[0].message.content150 except Exception as e:151 print(f"Primary model {primary_model} failed: {e}")152 # Try fallback model153 try:154 response = client.chat.completions.create(155 model=fallback_model,156 messages=[157 {158 "role": "system",159 "content": "You are an AI assistant for the Physical AI & Humanoid Robotics Curriculum. Answer the user's query based on the provided context from the curriculum. If the context doesn't contain relevant information, politely say that you don't have enough information to answer the query."160 },161 {162 "role": "user",163 "content": prompt164 }165 ],166 max_tokens=MAX_TOKENS_RESPONSE,167 temperature=TEMPERATURE168 )169 model_name = fallback_model170 print(f"Fallback to model: {fallback_model}")171 response_text = response.choices[0].message.content172 except Exception as e2:173 print(f"Fallback model {fallback_model} also failed: {e2}")174 response_text = None175 176 # If no model worked, provide a fallback response instead of failing177 if model_name is None or not response_text:178 # Fallback: return the context directly if LLM fails179 if combined_context:180 return f"Based on the curriculum materials:\n\n{combined_context[:1000]}...\n\n(Truncated for brevity)"181 else:182 return "I'm sorry, but I'm currently unable to generate a response. The AI service might be unavailable. Please try again later."183 184 return response_text