Spaces:
Runtime error
Runtime error
File size: 10,678 Bytes
85f4d1b 353025f 85f4d1b 39ff7ae 85f4d1b 39ff7ae 85f4d1b c840d73 85f4d1b 39ff7ae 85f4d1b c840d73 14c4536 942f7d6 c840d73 85f4d1b 353025f 85f4d1b 353025f 85f4d1b 353025f 85f4d1b 353025f 85f4d1b 353025f f86ad4d c840d73 85f4d1b 39ff7ae f86ad4d 39ff7ae 85f4d1b c840d73 85f4d1b 39ff7ae 85f4d1b c840d73 85f4d1b 39ff7ae 85f4d1b c840d73 85f4d1b c840d73 85f4d1b 39ff7ae 85f4d1b 39ff7ae 85f4d1b c840d73 85f4d1b 39ff7ae 85f4d1b c840d73 39ff7ae 85f4d1b c840d73 85f4d1b c840d73 85f4d1b c840d73 85f4d1b c840d73 85f4d1b c840d73 85f4d1b c840d73 85f4d1b c840d73 85f4d1b c840d73 85f4d1b c840d73 39ff7ae 85f4d1b 353025f 85f4d1b c840d73 85f4d1b 353025f 85f4d1b 353025f c840d73 85f4d1b c840d73 39ff7ae 85f4d1b c840d73 85f4d1b c840d73 85f4d1b c840d73 85f4d1b c840d73 85f4d1b c840d73 85f4d1b c840d73 85f4d1b c840d73 85f4d1b c840d73 85f4d1b c840d73 85f4d1b 39ff7ae 85f4d1b 39ff7ae | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 | import os
from flask import Flask, request, jsonify, render_template
from flask_cors import CORS
from pymongo import MongoClient, ReturnDocument
from pymongo.errors import PyMongoError
import chromadb # <-- MENGGANTIKAN HttpClient
from sentence_transformers import SentenceTransformer
from huggingface_hub import InferenceClient
from dotenv import load_dotenv
from bson.objectid import ObjectId
from datetime import datetime
# --- 1. Load .env and Initialize ---
load_dotenv()
app = Flask(__name__)
CORS(app)
# --- 2. Client Initialization (Global) ---
hf_token = os.getenv("HF_TOKEN")
hf_client = InferenceClient(
"meta-llama/Meta-Llama-3-8B-Instruct",
token=hf_token
)
mongo_url = os.getenv("MONGO_URL")
mongo_client = MongoClient(mongo_url)
db = mongo_client.get_database("chatbot_db") # Get DB from MONGO_URL
chat_history_collection = db.get_collection("conversations")
print("✅ Successfully connected to MongoDB Atlas.")
print("Loading embedding model (this may take a while)...")
embedding_model = SentenceTransformer('all-MiniLM-L6-v2',
cache_folder='/tmp/model_cache',
token= hf_token)
print("✅ Embedding model ('all-MiniLM-L6-v2') has been successfully loaded.")
# --- KODE BARU UNTUK KONEKSI HOSTING (CloudClient) ---
try:
# 1. Ambil kredensial Cloud Anda dari .env
CHROMA_API_KEY = os.getenv("CHROMA_API_KEY")
CHROMA_TENANT = os.getenv("CHROMA_TENANT")
CHROMA_DATABASE = os.getenv("CHROMA_DATABASE")
if not CHROMA_API_KEY or not CHROMA_TENANT or not CHROMA_DATABASE:
raise ValueError("CHROMA_API_KEY, CHROMA_TENANT, or CHROMA_DATABASE missing from .env file")
# 2. Gunakan chromadb.CloudClient
chroma_client = chromadb.CloudClient(
tenant=CHROMA_TENANT,
database=CHROMA_DATABASE,
api_key=CHROMA_API_KEY
)
# 3. Ambil collection (data Anda sudah ada di sana dari 'chroma copy')
knowledge_collection = chroma_client.get_collection(
name="website_knowledge"
)
print(f"✅ Successfully connected to ChromaDB Cloud (Tenant: {CHROMA_TENANT}).")
print(f"✅ Found {knowledge_collection.count()} documents in 'website_knowledge' collection.")
except Exception as e:
print(f"❌ FAILED to connect to ChromaDB Cloud. Check your .env variables. Error: {e}")
# --- AKHIR DARI KODE BARU ---
# --- 3. Frontend Endpoints ---
@app.route("/")
def home():
"""Display the landing page (index.html)"""
return render_template("index.html")
@app.route("/chat")
def chat_page():
"""Display the main chat page (chat.html)"""
return render_template("chat.html")
# --- 4. API Endpoint - Get All History (for F5 refresh) ---
@app.route("/api/conversations", methods=["GET"])
def get_conversations():
user_id = request.args.get("userId")
if not user_id:
return jsonify({"error": "userId is required"}), 400
try:
convos = list(chat_history_collection.find(
{"userId": user_id}
).sort("updatedAt", -1)) # Sort by most recent
conversations_list = []
for convo in convos:
conversations_list.append({
"id": str(convo.get('_id')),
"title": convo.get("title", "New Chat"),
"messages": convo.get("messages", []),
"createdAt": convo.get("createdAt")
})
return jsonify(conversations_list)
except Exception as e:
print(f"Error in /api/conversations GET: {e}")
return jsonify({"error": "Failed to retrieve conversations"}), 500
# --- 5. API Endpoint (FOR “CLEAR ALL”) ---
@app.route("/api/conversations", methods=["DELETE"])
def clear_conversations():
"""
Delete ALL conversations for one user ID.
"""
data = request.json
user_id = data.get("userId")
if not user_id:
return jsonify({"error": "userId is required"}), 400
try:
result = chat_history_collection.delete_many({"userId": user_id})
print(f"Successfully deleted {result.deleted_count} conversations for userId {user_id}.")
return jsonify({
"message": "History successfully deleted",
"deleted_count": result.deleted_count
})
except Exception as e:
print(f"Error in /api/conversations DELETE: {e}")
return jsonify({"error": "Failed to delete history"}), 500
# --- 6. Main Chat API Endpoint ---
@app.route("/api/chat", methods = ["POST"])
def handle_chat():
try:
data = request.json
user_message = data.get("message")
user_id = data.get("userId")
conversation_id = data.get("conversationId") # Can be 'null', 'temp-...', or a real ObjectId
if not user_message or not user_id:
return jsonify({"error": "'message' and 'userId' parameters are required."}), 400
history = []
user_message_doc = {"role": "user", "content": user_message, "timestamp": datetime.now()}
real_convo_id_obj = None # Will hold the valid Mongo ObjectId
# --- 1. (CRUD) Validate ID and Get Conversation ---
if conversation_id:
try:
# Try to convert. If successful, it's a real ID.
real_convo_id_obj = ObjectId(conversation_id)
except Exception as e: # bson.errors.InvalidId
# If it Fails (it's 'temp-...' or invalid), treat it as a new chat.
real_convo_id_obj = None
if real_convo_id_obj:
# This is an EXISTING CHAT (valid ID)
current_convo = chat_history_collection.find_one({
"_id": real_convo_id_obj,
"userId": user_id
})
if current_convo:
history = current_convo.get("messages", [])[-6:]
# If real_convo_id_obj is None, history remains [] (empty)
# --- 2. (RAG) - Perform RAG ---
print(f"Searching for context for: \"{user_message}\"")
query_embedding = embedding_model.encode(user_message).tolist()
# Catatan: n_results=5 dapat menyebabkan kontaminasi konteks.
# Pertimbangkan untuk mengganti ke 1 jika jawaban tidak akurat.
results = knowledge_collection.query(
query_embeddings=[query_embedding],
n_results=5
)
# Tambahkan pemeriksaan jika 'results' kosong
if not results['documents'] or not results['documents'][0]:
context = "No relevant context found."
print("Context not found.")
else:
context = "\n\n".join(results['documents'][0])
print("Context found.")
# --- 3. (RAG) Build Prompt (Per New Instructions) ---
system_prompt = """You are the 'Enviro Education Tools Product Selector & System Designer'. Your answers are for technical professionals who use American English.
Your task is to answer the user's question *strictly* and *only* based on the context provided. The context provided IS the information from https://enviroeducationtools.com/.
- Do not use any products, information, or technologies from other websites.
- Do not mention any other websites.
- Do not suggest prices or pricing information.
If the user asks 'about Enviro Education Tools' or 'where are you based', use the following information:
'Enviro Education Tools, based in Atlanta, Georgia, United States, is recognized as one of the leading global suppliers of advanced B2B and B2G (to a lesser degree, B2B2C, B2D) technologies in the world. For four decades, Enviro Education Tools has served its customers in the U.S. and Canada, including many Fortune 500 companies, leading R&D firms, prestigious universities, and U.S. and Canadian government agencies. Asset Track Pro has invested heavily in R&D of its products and systems, has stringent quality assurance processes, and provides top-notch expert support remotely or onsite.'
If the answer is not in the context, say: 'I'm sorry, I do not have specific information on that topic from https://enviroeducationtools.com/'
"""
formatted_history = "\n".join([f"{msg['role']}: {msg['content']}" for msg in history])
user_prompt = f"Context:\n{context}\n\nChat History:\n{formatted_history}\n\nQuestion:\n{user_message}"
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
# --- 4. (RAG) Call Llama 3 API ---
print("Calling Hugging Face API...")
response = hf_client.chat_completion(messages=messages, max_tokens=250, temperature=0.1)
ai_response = response.choices[0].message.content
# --- 4.5. Add Mandatory Disclaimer ---
disclaimer = "\n\nThe above is suggested by Enviro Education Tools AI and may not be as good as what our human experts can provide. Please contact our experts for further."
ai_response += disclaimer # Append disclaimer to AI response
# --- 5. (CRUD) Save AI response to MongoDB ---
ai_message_doc = {"role": "assistant", "content": ai_response, "timestamp": datetime.now()}
final_convo_id_str = ""
if real_convo_id_obj: # Existing chat (ID was valid)
chat_history_collection.update_one(
{"_id": real_convo_id_obj},
{
"$push": {"messages": {"$each": [user_message_doc, ai_message_doc]}},
"$set": {"updatedAt": datetime.now()}
}
)
final_convo_id_str = str(real_convo_id_obj)
else: # New chat (ID was 'null' or 'temp-...')
title = user_message[:30] + "..." if len(user_message) > 30 else user_message
new_convo_doc = {
"userId": user_id,
"title": title,
"messages": [user_message_doc, ai_message_doc], # Add user & AI messages
"createdAt": datetime.now(),
"updatedAt": datetime.now()
}
insert_result = chat_history_collection.insert_one(new_convo_doc)
final_convo_id_str = str(insert_result.inserted_id) # Get the NEW _id
print("Conversation saved to MongoDB.")
# --- 6. Send response ---
return jsonify({"answer": ai_response, "conversationId": final_convo_id_str})
except Exception as e:
print(f"Error in /api/chat: {e}")
return jsonify({"error": "An error occurred on the server"}), 500
# --- 7. Run the Server ---
if __name__ == "__main__":
app.run(port=5000, debug=True) |