Rulga commited on
Commit
b88df70
·
1 Parent(s): d8a9f5f

used Gradio

Browse files
Files changed (6) hide show
  1. Dockerfile +1 -0
  2. README.md +15 -1
  3. app - Copy.py +0 -417
  4. app.py +56 -399
  5. fastapi_server.py +432 -0
  6. requirements.txt +3 -1
Dockerfile CHANGED
@@ -54,3 +54,4 @@ EXPOSE 8000
54
 
55
  # Use a startup script with debug output
56
  CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000", "--log-level", "debug"]
 
 
54
 
55
  # Use a startup script with debug output
56
  CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000", "--log-level", "debug"]
57
+
README.md CHANGED
@@ -12,4 +12,18 @@ short_description: It is a chat built with an AI model about www.Status.law
12
 
13
  # LS DOC Chatbot Log
14
 
15
- It is a chat app built using Hugging Face and Docker Space that allows users to interact with an AI model to communicate about www.Status.law
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
  # LS DOC Chatbot Log
14
 
15
+ It is a chat app built using Hugging Face and Docker Space that allows users to interact with an AI model to communicate about www.Status.law
16
+
17
+ This application provides two interfaces:
18
+ 1. Web Interface (accessible via /web endpoint)
19
+ 2. Hugging Face Spaces Interface (using Gradio)
20
+
21
+ ## Access Points
22
+ - Web Interface: http://localhost:8000/web
23
+ - Gradio Interface: http://localhost:7860
24
+ - API Endpoints: http://localhost:8000/docs
25
+
26
+ ## Environment Variables
27
+ Required environment variables:
28
+ - GROQ_API_KEY
29
+ - HF_TOKEN (optional, for Hugging Face integration)
app - Copy.py DELETED
@@ -1,417 +0,0 @@
1
- import os
2
- import time
3
- from dotenv import load_dotenv
4
- from langchain_groq import ChatGroq
5
- from langchain_huggingface import HuggingFaceEmbeddings
6
- from langchain_community.vectorstores import FAISS
7
- from langchain_text_splitters import RecursiveCharacterTextSplitter
8
- from langchain_community.document_loaders import WebBaseLoader
9
- from langchain_core.prompts import PromptTemplate
10
- from langchain_core.output_parsers import StrOutputParser
11
- from datetime import datetime
12
- import json
13
- import traceback
14
- from fastapi import FastAPI, HTTPException, Request
15
- from fastapi.responses import JSONResponse
16
- from pydantic import BaseModel
17
- from api import router as analysis_router
18
- from utils import ChatAnalyzer, setup_chat_analysis
19
- import requests.exceptions
20
- import aiohttp
21
- from typing import Union
22
- import uvicorn
23
- import logging
24
- from rich import print as rprint
25
- from rich.console import Console
26
- from rich.panel import Panel
27
- from rich.table import Table
28
-
29
- console = Console()
30
-
31
- # Базовая настройка логирования
32
- logging.basicConfig(level=logging.DEBUG)
33
- logger = logging.getLogger(__name__)
34
-
35
- # Определение путей
36
- VECTOR_STORE_PATH = os.path.join(os.getcwd(), "vector_store")
37
- CHAT_HISTORY_PATH = os.path.join(os.getcwd(), "chat_history")
38
-
39
- app = FastAPI(title="Status Law Assistant API")
40
-
41
- class ChatRequest(BaseModel):
42
- message: str
43
-
44
- class ChatResponse(BaseModel):
45
- response: str
46
-
47
- def check_vector_store():
48
- """Проверка наличия векторной базы"""
49
- index_path = os.path.join(VECTOR_STORE_PATH, "index.faiss")
50
- return os.path.exists(index_path)
51
-
52
- @app.get("/")
53
- async def root():
54
- """Базовый эндпоинт с информацией о состоянии"""
55
- return {
56
- "status": "ok",
57
- "vector_store_ready": check_vector_store(),
58
- "timestamp": datetime.now().isoformat()
59
- }
60
-
61
- @app.get("/status")
62
- async def get_status():
63
- """Получение статуса векторной базы"""
64
- return {
65
- "vector_store_exists": check_vector_store(),
66
- "can_chat": check_vector_store(),
67
- "vector_store_path": VECTOR_STORE_PATH
68
- }
69
-
70
- @app.post("/build-knowledge-base")
71
- async def build_kb():
72
- """Эндпоинт для построения базы знаний"""
73
- try:
74
- if check_vector_store():
75
- return {
76
- "status": "exists",
77
- "message": "Knowledge base already exists"
78
- }
79
-
80
- # Инициализируем embeddings только когда нужно построить базу
81
- embeddings = HuggingFaceEmbeddings(
82
- model_name="sentence-transformers/all-MiniLM-L6-v2"
83
- )
84
- vector_store = build_knowledge_base(embeddings)
85
-
86
- return {
87
- "status": "success",
88
- "message": "Knowledge base built successfully"
89
- }
90
- except Exception as e:
91
- logger.error(f"Failed to build knowledge base: {str(e)}")
92
- raise HTTPException(
93
- status_code=500,
94
- detail=f"Failed to build knowledge base: {str(e)}"
95
- )
96
-
97
- @app.post("/chat", response_model=ChatResponse)
98
- async def chat_endpoint(request: ChatRequest):
99
- """Эндпоинт чата"""
100
- if not check_vector_store():
101
- raise HTTPException(
102
- status_code=400,
103
- detail="Knowledge base not found. Please build it first using /build-knowledge-base endpoint"
104
- )
105
-
106
- try:
107
- # Инициализируем компоненты только при необходимости
108
- llm = ChatGroq(
109
- model_name="llama-3.3-70b-versatile",
110
- temperature=0.6,
111
- api_key=os.getenv("GROQ_API_KEY")
112
- )
113
-
114
- embeddings = HuggingFaceEmbeddings(
115
- model_name="sentence-transformers/all-MiniLM-L6-v2"
116
- )
117
-
118
- vector_store = FAISS.load_local(
119
- VECTOR_STORE_PATH,
120
- embeddings,
121
- allow_dangerous_deserialization=True
122
- )
123
-
124
- # Остальная логика чата...
125
- context_docs = vector_store.similarity_search(request.message)
126
- context_text = "\n".join([d.page_content for d in context_docs])
127
-
128
- prompt_template = PromptTemplate.from_template('''
129
- You are a helpful and polite legal assistant at Status Law.
130
- Answer the question based on the context provided.
131
- Context: {context}
132
- Question: {question}
133
- ''')
134
-
135
- chain = prompt_template | llm | StrOutputParser()
136
- response = chain.invoke({
137
- "context": context_text,
138
- "question": request.message
139
- })
140
-
141
- return ChatResponse(response=response)
142
-
143
- except Exception as e:
144
- logger.error(f"Chat error: {str(e)}")
145
- raise HTTPException(
146
- status_code=500,
147
- detail=f"Chat error: {str(e)}"
148
- )
149
-
150
- # --------------- Knowledge Base Management ---------------
151
- URLS = [
152
- "https://status.law",
153
- "https://status.law/about",
154
- "https://status.law/careers",
155
- "https://status.law/tariffs-for-services-against-extradition-en",
156
- "https://status.law/challenging-sanctions",
157
- "https://status.law/law-firm-contact-legal-protection"
158
- "https://status.law/cross-border-banking-legal-issues",
159
- "https://status.law/extradition-defense",
160
- "https://status.law/international-prosecution-protection",
161
- "https://status.law/interpol-red-notice-removal",
162
- "https://status.law/practice-areas",
163
- "https://status.law/reputation-protection",
164
- "https://status.law/faq"
165
- ]
166
-
167
- def build_knowledge_base(_embeddings):
168
- """Build or update the knowledge base"""
169
- try:
170
- start_time = time.time()
171
- documents = []
172
-
173
- # Ensure vector store directory exists
174
- if not os.path.exists(VECTOR_STORE_PATH):
175
- os.makedirs(VECTOR_STORE_PATH, exist_ok=True)
176
-
177
- for url in URLS:
178
- try:
179
- loader = WebBaseLoader(url)
180
- docs = loader.load()
181
- documents.extend(docs)
182
- except Exception as e:
183
- print(f"Failed to load {url}: {str(e)}")
184
- continue
185
-
186
- if not documents:
187
- raise HTTPException(status_code=500, detail="No documents loaded")
188
-
189
- text_splitter = RecursiveCharacterTextSplitter(
190
- chunk_size=500,
191
- chunk_overlap=100
192
- )
193
- chunks = text_splitter.split_documents(documents)
194
-
195
- vector_store = FAISS.from_documents(chunks, _embeddings)
196
- vector_store.save_local(
197
- folder_path=VECTOR_STORE_PATH,
198
- index_name="index"
199
- )
200
-
201
- if not os.path.exists(os.path.join(VECTOR_STORE_PATH, "index.faiss")):
202
- raise HTTPException(status_code=500, detail="FAISS index file not created")
203
-
204
- return vector_store
205
-
206
- except Exception as e:
207
- raise HTTPException(status_code=500, detail=f"Knowledge base creation failed: {str(e)}")
208
-
209
- # --------------- API Models ---------------
210
- class ChatRequest(BaseModel):
211
- message: str
212
-
213
- class ChatResponse(BaseModel):
214
- response: str
215
-
216
- # --------------- API Routes ---------------
217
- @app.post("/chat", response_model=ChatResponse)
218
- async def chat_endpoint(request: ChatRequest):
219
- try:
220
- llm, embeddings = init_models()
221
-
222
- if not os.path.exists(VECTOR_STORE_PATH):
223
- vector_store = build_knowledge_base(embeddings)
224
- else:
225
- vector_store = FAISS.load_local(
226
- VECTOR_STORE_PATH,
227
- embeddings,
228
- allow_dangerous_deserialization=True
229
- )
230
-
231
- # Add retry logic for network operations
232
- max_retries = 3
233
- retry_count = 0
234
-
235
- while retry_count < max_retries:
236
- try:
237
- context_docs = vector_store.similarity_search(request.message)
238
- context_text = "\n".join([d.page_content for d in context_docs])
239
-
240
- prompt_template = PromptTemplate.from_template('''
241
- You are a helpful and polite legal assistant at Status Law.
242
- You answer in the language in which the question was asked.
243
- Answer the question based on the context provided.
244
-
245
- # ... остальной текст промпта ...
246
-
247
- Context: {context}
248
- Question: {question}
249
-
250
- Response Guidelines:
251
- 1. Answer in the user's language
252
- 2. Cite sources when possible
253
- 3. Offer contact options if unsure
254
- ''')
255
-
256
- chain = prompt_template | llm | StrOutputParser()
257
- response = chain.invoke({
258
- "context": context_text,
259
- "question": request.message
260
- })
261
-
262
- log_interaction(request.message, response, context_text)
263
- return ChatResponse(response=response)
264
-
265
- except (requests.exceptions.RequestException, aiohttp.ClientError) as e:
266
- retry_count += 1
267
- if retry_count == max_retries:
268
- raise HTTPException(
269
- status_code=503,
270
- detail={
271
- "error": "Network error after maximum retries",
272
- "detail": str(e),
273
- "type": "network_error"
274
- }
275
- )
276
- await asyncio.sleep(1 * retry_count) # Exponential backoff
277
-
278
- except Exception as e:
279
- if isinstance(e, (requests.exceptions.RequestException, aiohttp.ClientError)):
280
- raise HTTPException(
281
- status_code=503,
282
- detail={
283
- "error": "Network error occurred",
284
- "detail": str(e),
285
- "type": "network_error"
286
- }
287
- )
288
- raise HTTPException(status_code=500, detail=str(e))
289
-
290
- # --------------- Logging ---------------
291
- def log_interaction(user_input: str, bot_response: str, context: str):
292
- try:
293
- log_entry = {
294
- "timestamp": datetime.now().isoformat(),
295
- "user_input": user_input,
296
- "bot_response": bot_response,
297
- "context": context[:500],
298
- "kb_version": datetime.now().strftime("%Y%m%d-%H%M%S")
299
- }
300
-
301
- os.makedirs("chat_history", exist_ok=True)
302
- log_path = os.path.join("chat_history", "chat_logs.json")
303
-
304
- with open(log_path, "a", encoding="utf-8") as f:
305
- f.write(json.dumps(log_entry, ensure_ascii=False) + "\n")
306
-
307
- except Exception as e:
308
- print(f"Logging error: {str(e)}")
309
- print(traceback.format_exc())
310
-
311
- # Add health check endpoint
312
- @app.get("/health")
313
- async def health_check():
314
- try:
315
- # Check if models can be initialized
316
- llm, embeddings = init_models()
317
-
318
- # Check if vector store is accessible
319
- if os.path.exists(VECTOR_STORE_PATH):
320
- vector_store = FAISS.load_local(
321
- VECTOR_STORE_PATH,
322
- embeddings,
323
- allow_dangerous_deserialization=True
324
- )
325
-
326
- return {
327
- "status": "healthy",
328
- "vector_store": "available" if os.path.exists(VECTOR_STORE_PATH) else "not_found"
329
- }
330
-
331
- except Exception as e:
332
- return JSONResponse(
333
- status_code=503,
334
- content={
335
- "status": "unhealthy",
336
- "error": str(e)
337
- }
338
- )
339
-
340
- # Add diagnostic endpoint
341
- @app.get("/directory-status")
342
- async def check_directory_status():
343
- """Check status of required directories"""
344
- return {
345
- "vector_store": {
346
- "exists": os.path.exists(VECTOR_STORE_PATH),
347
- "path": os.path.abspath(VECTOR_STORE_PATH),
348
- "contents": os.listdir(VECTOR_STORE_PATH) if os.path.exists(VECTOR_STORE_PATH) else []
349
- },
350
- "chat_history": {
351
- "exists": os.path.exists(CHAT_HISTORY_PATH),
352
- "path": os.path.abspath(CHAT_HISTORY_PATH),
353
- "contents": os.listdir(CHAT_HISTORY_PATH) if os.path.exists(CHAT_HISTORY_PATH) else []
354
- }
355
- }
356
-
357
- # Добавим функцию для вывода статуса
358
- def print_startup_status():
359
- """Print application startup status with rich formatting"""
360
- try:
361
- # Create status table
362
- table = Table(show_header=True, header_style="bold magenta")
363
- table.add_column("Component", style="cyan")
364
- table.add_column("Status", style="green")
365
-
366
- # Check directories
367
- vector_store_exists = os.path.exists(VECTOR_STORE_PATH)
368
- chat_history_exists = os.path.exists(CHAT_HISTORY_PATH)
369
-
370
- table.add_row(
371
- "Vector Store Directory",
372
- "✅ Created" if vector_store_exists else "❌ Missing"
373
- )
374
- table.add_row(
375
- "Chat History Directory",
376
- "✅ Created" if chat_history_exists else "❌ Missing"
377
- )
378
-
379
- # Check environment variables
380
- table.add_row(
381
- "GROQ API Key",
382
- "✅ Set" if os.getenv("GROQ_API_KEY") else "❌ Missing"
383
- )
384
-
385
- # Create status panel
386
- status_panel = Panel(
387
- table,
388
- title="[bold blue]Status Law Assistant API Status[/bold blue]",
389
- border_style="blue"
390
- )
391
-
392
- # Print startup message and status
393
- console.print("\n")
394
- console.print("[bold green]🚀 Server started successfully![/bold green]")
395
- console.print(status_panel)
396
- console.print("\n[bold yellow]API Documentation:[/bold yellow]")
397
- console.print("📚 Swagger UI: http://0.0.0.0:8000/docs")
398
- console.print("📘 ReDoc: http://0.0.0.0:8000/redoc\n")
399
-
400
- except Exception as e:
401
- console.print(f"[bold red]Error printing status: {str(e)}[/bold red]")
402
-
403
- if __name__ == "__main__":
404
- import uvicorn
405
-
406
- port = int(os.getenv("PORT", 8000))
407
- logger.info(f"Starting server on port {port}")
408
-
409
- config = uvicorn.Config(
410
- app,
411
- host="0.0.0.0",
412
- port=port,
413
- log_level="debug"
414
- )
415
-
416
- server = uvicorn.Server(config)
417
- server.run()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py CHANGED
@@ -1,423 +1,80 @@
1
  import os
2
-
3
- # Установка переменных окружения для кэша HuggingFace
4
- #os.environ["TRANSFORMERS_CACHE"] = "cache/huggingface"
5
- os.environ["HF_HOME"] = "cache/huggingface"
6
- os.environ["HUGGINGFACE_HUB_CACHE"] = "cache/huggingface"
7
- os.environ["XDG_CACHE_HOME"] = "cache"
8
-
9
- # Создание необходимых директорий
10
- os.makedirs("cache/huggingface", exist_ok=True)
11
-
12
  import time
 
13
  import uvicorn
14
- from fastapi import FastAPI, HTTPException, Request
15
- from fastapi.middleware.cors import CORSMiddleware
16
  from fastapi.responses import HTMLResponse
17
  from fastapi.staticfiles import StaticFiles
18
- from fastapi.templating import Jinja2Templates
19
- from dotenv import load_dotenv
20
- from langchain_groq import ChatGroq
21
- from langchain_huggingface import HuggingFaceEmbeddings
22
- from langchain_community.vectorstores import FAISS
23
- from langchain_text_splitters import RecursiveCharacterTextSplitter
24
- from langchain_community.document_loaders import WebBaseLoader
25
- from langchain_core.prompts import PromptTemplate
26
- from langchain_core.output_parsers import StrOutputParser
27
- from datetime import datetime
28
- import json
29
- import traceback
30
- from typing import Dict, List, Optional
31
- from pydantic import BaseModel
32
- from huggingface_hub import Repository, snapshot_download
33
-
34
- # Initialize environment variables
35
- load_dotenv()
36
-
37
- # Constants for paths and URLs
38
- VECTOR_STORE_PATH = "vector_store"
39
- LOCAL_CHAT_HISTORY_PATH = "chat_history"
40
- DATA_SNAPSHOT_PATH = "data_snapshot"
41
- HF_DATASET_REPO = "Rulga/LS_chat"
42
-
43
- URLS = [
44
- "https://status.law",
45
- "https://status.law/about",
46
- "https://status.law/careers",
47
- "https://status.law/tariffs-for-services-of-protection-against-extradition",
48
- "https://status.law/challenging-sanctions",
49
- "https://status.law/law-firm-contact-legal-protection",
50
- "https://status.law/cross-border-banking-legal-issues",
51
- "https://status.law/extradition-defense",
52
- "https://status.law/international-prosecution-protection",
53
- "https://status.law/interpol-red-notice-removal",
54
- "https://status.law/practice-areas",
55
- "https://status.law/reputation-protection",
56
- "https://status.law/faq"
57
- ]
58
-
59
- # Initialize the FastAPI app
60
- app = FastAPI(title="Status Law Assistant API")
61
 
62
- # Add CORS middleware
63
- app.add_middleware(
64
- CORSMiddleware,
65
- allow_origins=["*"],
66
- allow_credentials=True,
67
- allow_methods=["*"],
68
- allow_headers=["*"],
69
- )
70
 
71
- # Define request and response models
72
- class ChatRequest(BaseModel):
73
- message: str
74
- conversation_id: Optional[str] = None
75
-
76
- class ChatResponse(BaseModel):
77
- response: str
78
- conversation_id: str
79
-
80
- class BuildKnowledgeBaseResponse(BaseModel):
81
- status: str
82
- message: str
83
- details: Optional[Dict] = None
84
 
85
- # Global variables for models and knowledge base
86
- llm = None
87
- embeddings = None
88
- vector_store = None
89
- kb_info = {
90
- 'build_time': None,
91
- 'size': None,
92
- 'version': '1.1'
93
- }
94
-
95
- # --------------- Hugging Face Dataset Integration ---------------
96
- def init_hf_dataset_integration():
97
- """Initialize integration with Hugging Face dataset for persistence"""
98
- try:
99
- # Download the latest snapshot of the dataset if it exists
100
- if os.getenv("HF_TOKEN"):
101
- # With authentication if token provided
102
- snapshot_download(
103
- repo_id=HF_DATASET_REPO,
104
- repo_type="dataset",
105
- local_dir="./data_snapshot",
106
- token=os.getenv("HF_TOKEN")
107
- )
108
- else:
109
- # Try without authentication for public datasets
110
- snapshot_download(
111
- repo_id=HF_DATASET_REPO,
112
- repo_type="dataset",
113
- local_dir="./data_snapshot"
114
- )
115
-
116
- # Check if vector store exists in the downloaded data
117
- if os.path.exists("./data_snapshot/vector_store/index.faiss"):
118
- # Copy to the local vector store path
119
- os.makedirs(VECTOR_STORE_PATH, exist_ok=True)
120
- os.system(f"cp -r ./data_snapshot/vector_store/* {VECTOR_STORE_PATH}/")
121
- return True
122
- except Exception as e:
123
- print(f"Error downloading dataset: {e}")
124
-
125
- return False
126
 
127
- def upload_to_hf_dataset():
128
- """Upload the vector store and chat history to the Hugging Face dataset"""
129
- if not os.getenv("HF_TOKEN"):
130
- print("HF_TOKEN not set, cannot upload to Hugging Face")
131
- return False
132
-
133
- try:
134
- # Clone the repository
135
- repo = Repository(
136
- local_dir="./data_upload",
137
- clone_from=HF_DATASET_REPO,
138
- repo_type="dataset",
139
- token=os.getenv("HF_TOKEN")
140
- )
141
-
142
- # Copy the vector store files
143
- if os.path.exists(f"{VECTOR_STORE_PATH}/index.faiss"):
144
- os.makedirs("./data_upload/vector_store", exist_ok=True)
145
- os.system(f"cp -r {VECTOR_STORE_PATH}/* ./data_upload/vector_store/")
146
-
147
- # Copy the chat history
148
- if os.path.exists(f"{LOCAL_CHAT_HISTORY_PATH}/chat_logs.json"):
149
- os.makedirs("./data_upload/chat_history", exist_ok=True)
150
- os.system(f"cp -r {LOCAL_CHAT_HISTORY_PATH}/* ./data_upload/chat_history/")
151
-
152
- # Push to Hugging Face
153
- repo.push_to_hub(commit_message="Update vector store and chat history")
154
- return True
155
- except Exception as e:
156
- print(f"Error uploading to dataset: {e}")
157
- return False
158
-
159
- # --------------- Enhanced Logging ---------------
160
- def log_interaction(user_input: str, bot_response: str, context: str, conversation_id: str):
161
- """Log interactions with error handling"""
162
- try:
163
- log_entry = {
164
- "timestamp": datetime.now().isoformat(),
165
- "conversation_id": conversation_id,
166
- "user_input": user_input,
167
- "bot_response": bot_response,
168
- "context": context[:500] if context else "",
169
- "kb_version": kb_info['version']
170
- }
171
-
172
- os.makedirs(LOCAL_CHAT_HISTORY_PATH, exist_ok=True)
173
- log_path = os.path.join(LOCAL_CHAT_HISTORY_PATH, "chat_logs.json")
174
-
175
- with open(log_path, "a", encoding="utf-8") as f:
176
- f.write(json.dumps(log_entry, ensure_ascii=False) + "\n")
177
-
178
- # Upload to Hugging Face after logging
179
- upload_to_hf_dataset()
180
-
181
- except Exception as e:
182
- print(f"Logging error: {str(e)}")
183
- print(traceback.format_exc())
184
-
185
- # --------------- Model Initialization ---------------
186
- def init_models():
187
- """Initialize AI models"""
188
- global llm, embeddings
189
-
190
- if not llm:
191
- try:
192
- llm = ChatGroq(
193
- model_name="llama-3.3-70b-versatile",
194
- temperature=0.6,
195
- api_key=os.getenv("GROQ_API_KEY")
196
- )
197
- except Exception as e:
198
- print(f"LLM initialization failed: {str(e)}")
199
- raise HTTPException(status_code=500, detail=f"LLM initialization failed: {str(e)}")
200
-
201
- if not embeddings:
202
- try:
203
- embeddings = HuggingFaceEmbeddings(
204
- model_name="intfloat/multilingual-e5-large-instruct"
205
- )
206
- except Exception as e:
207
- print(f"Embeddings initialization failed: {str(e)}")
208
- raise HTTPException(status_code=500, detail=f"Embeddings initialization failed: {str(e)}")
209
-
210
- return llm, embeddings
211
 
212
- # --------------- Knowledge Base Management ---------------
213
- def build_knowledge_base():
214
- """Build or update the knowledge base"""
215
- global vector_store, kb_info
216
-
217
- _, _embeddings = init_models()
218
-
219
  try:
220
- start_time = time.time()
221
- documents = []
222
-
223
- # Create folder in advance
224
- os.makedirs(VECTOR_STORE_PATH, exist_ok=True)
225
-
226
- # Load documents
227
- for url in URLS:
228
- try:
229
- loader = WebBaseLoader(url)
230
- docs = loader.load()
231
- documents.extend(docs)
232
- print(f"Loaded {url}")
233
- except Exception as e:
234
- print(f"Failed to load {url}: {str(e)}")
235
- continue
236
-
237
- if not documents:
238
- raise HTTPException(status_code=500, detail="No documents loaded!")
239
-
240
- # Split into chunks
241
- text_splitter = RecursiveCharacterTextSplitter(
242
- chunk_size=500,
243
- chunk_overlap=100
244
  )
245
- chunks = text_splitter.split_documents(documents)
246
-
247
- # Create vector store
248
- vector_store = FAISS.from_documents(chunks, _embeddings)
249
- vector_store.save_local(
250
- folder_path=VECTOR_STORE_PATH,
251
- index_name="index"
252
- )
253
-
254
- # Verify file creation
255
- if not os.path.exists(os.path.join(VECTOR_STORE_PATH, "index.faiss")):
256
- raise HTTPException(status_code=500, detail="FAISS index file not created!")
257
-
258
- # Update info
259
- kb_info.update({
260
- 'build_time': time.time() - start_time,
261
- 'size': sum(
262
- os.path.getsize(os.path.join(VECTOR_STORE_PATH, f))
263
- for f in ["index.faiss", "index.pkl"]
264
- ) / (1024 ** 2),
265
- 'version': datetime.now().strftime("%Y%m%d-%H%M%S")
266
- })
267
-
268
- # Upload to Hugging Face
269
- upload_to_hf_dataset()
270
-
271
- return {
272
- "status": "success",
273
- "message": "Knowledge base successfully created!",
274
- "details": kb_info
275
- }
276
-
277
  except Exception as e:
278
- error_msg = f"Knowledge base creation failed: {str(e)}"
279
- print(error_msg)
280
- print(traceback.format_exc())
281
- raise HTTPException(status_code=500, detail=error_msg)
282
 
283
- def load_knowledge_base():
284
- """Load the knowledge base from disk"""
285
- global vector_store
286
-
287
- if vector_store:
288
- return vector_store
289
-
290
- _, _embeddings = init_models()
291
-
292
  try:
293
- vector_store = FAISS.load_local(
294
- VECTOR_STORE_PATH,
295
- _embeddings,
296
- allow_dangerous_deserialization=True
297
- )
298
- return vector_store
299
  except Exception as e:
300
- error_msg = f"Failed to load knowledge base: {str(e)}"
301
- print(error_msg)
302
- print(traceback.format_exc())
303
- return None
304
 
305
- # --------------- API Endpoints ---------------
306
- @app.get("/")
307
- async def root():
308
- """Root endpoint that shows app status"""
309
- vector_store_exists = os.path.exists(os.path.join(VECTOR_STORE_PATH, "index.faiss"))
310
 
311
- return {
312
- "status": "running",
313
- "knowledge_base_exists": vector_store_exists,
314
- "kb_info": kb_info if vector_store_exists else None
315
- }
316
-
317
- @app.get("/health")
318
- async def health_check():
319
- """Health check endpoint"""
320
- return {"status": "healthy"}
321
-
322
- @app.post("/build-kb", response_model=BuildKnowledgeBaseResponse)
323
- async def build_kb_endpoint():
324
- """Endpoint to build/rebuild the knowledge base"""
325
- return build_knowledge_base()
326
-
327
- @app.post("/chat", response_model=ChatResponse)
328
- async def chat_endpoint(request: ChatRequest):
329
- """Endpoint to chat with the assistant"""
330
- # Check if knowledge base exists
331
- if not os.path.exists(os.path.join(VECTOR_STORE_PATH, "index.faiss")):
332
- raise HTTPException(
333
- status_code=400,
334
- detail="Knowledge base not found. Please build it first with /build-kb"
335
- )
336
 
337
- # Use provided conversation ID or generate a new one
338
- conversation_id = request.conversation_id or f"conv_{datetime.now().strftime('%Y%m%d%H%M%S')}"
339
 
340
- try:
341
- # Load models and knowledge base
342
- _llm, _ = init_models()
343
- _vector_store = load_knowledge_base()
344
-
345
- if not _vector_store:
346
- raise HTTPException(
347
- status_code=500,
348
- detail="Failed to load knowledge base"
349
- )
350
-
351
- # Retrieve context
352
- context_docs = _vector_store.similarity_search(request.message)
353
- context_text = "\n".join([d.page_content for d in context_docs])
354
-
355
- # Generate response
356
- prompt_template = PromptTemplate.from_template('''
357
- You are a helpful and polite legal assistant at Status Law.
358
- You answer in the language in which the question was asked.
359
- Answer the question based on the context provided.
360
- If you cannot answer based on the context, say so politely and offer to contact Status Law directly via the following channels:
361
- - For all users: +32465594521 (landline phone).
362
- - For English and Swedish speakers only: +46728495129 (available on WhatsApp, Telegram, Signal, IMO).
363
- - Provide a link to the contact form: [Contact Form](https://status.law/law-firm-contact-legal-protection/).
364
- If the user has questions about specific services and their costs, suggest they visit the page https://status.law/tariffs-for-services-of-protection-against-extradition-and-international-prosecution/ for detailed information.
365
-
366
- Ask the user additional questions to understand which service to recommend and provide an estimated cost. For example, clarify their situation and needs to suggest the most appropriate options.
367
-
368
- Also, offer free consultations if they are available and suitable for the user's request.
369
- Answer professionally but in a friendly manner.
370
-
371
- Example:
372
- Q: How can I challenge the sanctions?
373
- A: To challenge the sanctions, you should consult with our legal team, who specialize in this area. Please contact us directly for detailed advice. You can fill out our contact form here: [Contact Form](https://status.law/law-firm-contact-legal-protection/).
374
-
375
- Context: {context}
376
- Question: {question}
377
 
378
- Response Guidelines:
379
- 1. Answer in the user's language
380
- 2. Cite sources when possible
381
- 3. Offer contact options if unsure
382
- ''')
383
-
384
- chain = prompt_template | _llm | StrOutputParser()
385
- response = chain.invoke({
386
- "context": context_text,
387
- "question": request.message
388
- })
389
-
390
- # Log the interaction
391
- log_interaction(request.message, response, context_text, conversation_id)
392
-
393
- return {
394
- "response": response,
395
- "conversation_id": conversation_id
396
- }
397
 
398
- except Exception as e:
399
- error_msg = f"Error generating response: {str(e)}"
400
- print(error_msg)
401
- print(traceback.format_exc())
402
- raise HTTPException(status_code=500, detail=error_msg)
403
-
404
- # Initialize dataset integration at startup
405
- @app.on_event("startup")
406
- async def startup_event():
407
- """Initialize on startup"""
408
- # Try to load existing knowledge base from Hugging Face
409
- init_hf_dataset_integration()
410
-
411
- # Preload embeddings model to reduce first-request latency
412
- try:
413
- global embeddings
414
- if not embeddings:
415
- embeddings = HuggingFaceEmbeddings(
416
- model_name="intfloat/multilingual-e5-large-instruct"
417
- )
418
- except Exception as e:
419
- print(f"Warning: Failed to preload embeddings: {e}")
420
 
421
- # Run the application
422
  if __name__ == "__main__":
423
- uvicorn.run("app:app", host="0.0.0.0", port=8000)
 
 
1
  import os
2
+ import threading
 
 
 
 
 
 
 
 
 
3
  import time
4
+ import gradio as gr
5
  import uvicorn
6
+ import requests
7
+ from fastapi import FastAPI
8
  from fastapi.responses import HTMLResponse
9
  from fastapi.staticfiles import StaticFiles
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
+ # Import our main application
12
+ from fastapi_server import app as fastapi_app
 
 
 
 
 
 
13
 
14
+ # Run FastAPI server in a separate thread
15
+ def run_fastapi():
16
+ uvicorn.run(fastapi_app, host="0.0.0.0", port=8000)
 
 
 
 
 
 
 
 
 
 
17
 
18
+ # Start FastAPI in a background thread
19
+ fastapi_thread = threading.Thread(target=run_fastapi, daemon=True)
20
+ fastapi_thread.start()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
+ # Wait for FastAPI to start
23
+ time.sleep(5)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
+ # Create a Gradio interface that will proxy requests to FastAPI
26
+ def chat_with_api(message, conversation_id=None):
 
 
 
 
 
27
  try:
28
+ response = requests.post(
29
+ "http://127.0.0.1:8000/chat",
30
+ json={"message": message, "conversation_id": conversation_id}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  )
32
+ if response.status_code == 200:
33
+ data = response.json()
34
+ return data["response"], data["conversation_id"]
35
+ else:
36
+ return f"Error: {response.status_code} - {response.text}", conversation_id
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  except Exception as e:
38
+ return f"API connection error: {str(e)}", conversation_id
 
 
 
39
 
40
+ def build_kb():
 
 
 
 
 
 
 
 
41
  try:
42
+ response = requests.post("http://127.0.0.1:8000/build-kb")
43
+ if response.status_code == 200:
44
+ return f"Success: {response.json()['message']}"
45
+ else:
46
+ return f"Error: {response.status_code} - {response.text}"
 
47
  except Exception as e:
48
+ return f"API connection error: {str(e)}"
 
 
 
49
 
50
+ # Create the Gradio interface
51
+ with gr.Blocks() as demo:
52
+ gr.Markdown("# Status Law Assistant")
 
 
53
 
54
+ with gr.Row():
55
+ with gr.Column():
56
+ build_kb_btn = gr.Button("Create/Update Knowledge Base")
57
+ kb_status = gr.Textbox(label="Knowledge Base Status")
58
+ build_kb_btn.click(build_kb, inputs=None, outputs=kb_status)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
+ conversation_id = gr.State(None)
 
61
 
62
+ with gr.Row():
63
+ with gr.Column():
64
+ chatbot = gr.Chatbot(label="Chat with Assistant")
65
+ msg = gr.Textbox(label="Your Question")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
 
67
+ def respond(message, chat_history, conv_id):
68
+ if not message.strip():
69
+ return chat_history, conv_id
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
+ chat_history.append([message, ""])
72
+ response, new_conv_id = chat_with_api(message, conv_id)
73
+ chat_history[-1][1] = response
74
+ return chat_history, new_conv_id
75
+
76
+ msg.submit(respond, [msg, chatbot, conversation_id], [chatbot, conversation_id])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
 
78
  if __name__ == "__main__":
79
+ # Launch Gradio interface
80
+ demo.launch(server_name="0.0.0.0", server_port=7860, share=False)
fastapi_server.py ADDED
@@ -0,0 +1,432 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ # Установка переменных окружения для кэша HuggingFace
4
+ #os.environ["TRANSFORMERS_CACHE"] = "cache/huggingface"
5
+ os.environ["HF_HOME"] = "cache/huggingface"
6
+ os.environ["HUGGINGFACE_HUB_CACHE"] = "cache/huggingface"
7
+ os.environ["XDG_CACHE_HOME"] = "cache"
8
+
9
+ # Создание необходимых директорий
10
+ os.makedirs("cache/huggingface", exist_ok=True)
11
+
12
+ import time
13
+ import uvicorn
14
+ from fastapi import FastAPI, HTTPException, Request
15
+ from fastapi.middleware.cors import CORSMiddleware
16
+ from fastapi.responses import HTMLResponse
17
+ from fastapi.staticfiles import StaticFiles
18
+ from fastapi.templating import Jinja2Templates
19
+ from dotenv import load_dotenv
20
+ from langchain_groq import ChatGroq
21
+ from langchain_huggingface import HuggingFaceEmbeddings
22
+ from langchain_community.vectorstores import FAISS
23
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
24
+ from langchain_community.document_loaders import WebBaseLoader
25
+ from langchain_core.prompts import PromptTemplate
26
+ from langchain_core.output_parsers import StrOutputParser
27
+ from datetime import datetime
28
+ import json
29
+ import traceback
30
+ from typing import Dict, List, Optional
31
+ from pydantic import BaseModel
32
+ from huggingface_hub import Repository, snapshot_download
33
+
34
+ # Initialize environment variables
35
+ load_dotenv()
36
+
37
+ # Constants for paths and URLs
38
+ VECTOR_STORE_PATH = "vector_store"
39
+ LOCAL_CHAT_HISTORY_PATH = "chat_history"
40
+ DATA_SNAPSHOT_PATH = "data_snapshot"
41
+ HF_DATASET_REPO = "Rulga/LS_chat"
42
+
43
+ URLS = [
44
+ "https://status.law",
45
+ "https://status.law/about",
46
+ "https://status.law/careers",
47
+ "https://status.law/tariffs-for-services-of-protection-against-extradition",
48
+ "https://status.law/challenging-sanctions",
49
+ "https://status.law/law-firm-contact-legal-protection",
50
+ "https://status.law/cross-border-banking-legal-issues",
51
+ "https://status.law/extradition-defense",
52
+ "https://status.law/international-prosecution-protection",
53
+ "https://status.law/interpol-red-notice-removal",
54
+ "https://status.law/practice-areas",
55
+ "https://status.law/reputation-protection",
56
+ "https://status.law/faq"
57
+ ]
58
+
59
+ # Initialize the FastAPI app
60
+ app = FastAPI(title="Status Law Assistant API")
61
+
62
+ # Support for static files
63
+ app.mount("/static", StaticFiles(directory="static"), name="static")
64
+
65
+ # Web interface route
66
+ @app.get("/web", response_class=HTMLResponse)
67
+ async def web_interface():
68
+ with open("index.html", "r", encoding="utf-8") as f:
69
+ return HTMLResponse(content=f.read())
70
+
71
+ # Add CORS middleware
72
+ app.add_middleware(
73
+ CORSMiddleware,
74
+ allow_origins=["*"],
75
+ allow_credentials=True,
76
+ allow_methods=["*"],
77
+ allow_headers=["*"],
78
+ )
79
+
80
+ # Define request and response models
81
+ class ChatRequest(BaseModel):
82
+ message: str
83
+ conversation_id: Optional[str] = None
84
+
85
+ class ChatResponse(BaseModel):
86
+ response: str
87
+ conversation_id: str
88
+
89
+ class BuildKnowledgeBaseResponse(BaseModel):
90
+ status: str
91
+ message: str
92
+ details: Optional[Dict] = None
93
+
94
+ # Global variables for models and knowledge base
95
+ llm = None
96
+ embeddings = None
97
+ vector_store = None
98
+ kb_info = {
99
+ 'build_time': None,
100
+ 'size': None,
101
+ 'version': '1.1'
102
+ }
103
+
104
+ # --------------- Hugging Face Dataset Integration ---------------
105
+ def init_hf_dataset_integration():
106
+ """Initialize integration with Hugging Face dataset for persistence"""
107
+ try:
108
+ # Download the latest snapshot of the dataset if it exists
109
+ if os.getenv("HF_TOKEN"):
110
+ # With authentication if token provided
111
+ snapshot_download(
112
+ repo_id=HF_DATASET_REPO,
113
+ repo_type="dataset",
114
+ local_dir="./data_snapshot",
115
+ token=os.getenv("HF_TOKEN")
116
+ )
117
+ else:
118
+ # Try without authentication for public datasets
119
+ snapshot_download(
120
+ repo_id=HF_DATASET_REPO,
121
+ repo_type="dataset",
122
+ local_dir="./data_snapshot"
123
+ )
124
+
125
+ # Check if vector store exists in the downloaded data
126
+ if os.path.exists("./data_snapshot/vector_store/index.faiss"):
127
+ # Copy to the local vector store path
128
+ os.makedirs(VECTOR_STORE_PATH, exist_ok=True)
129
+ os.system(f"cp -r ./data_snapshot/vector_store/* {VECTOR_STORE_PATH}/")
130
+ return True
131
+ except Exception as e:
132
+ print(f"Error downloading dataset: {e}")
133
+
134
+ return False
135
+
136
+ def upload_to_hf_dataset():
137
+ """Upload the vector store and chat history to the Hugging Face dataset"""
138
+ if not os.getenv("HF_TOKEN"):
139
+ print("HF_TOKEN not set, cannot upload to Hugging Face")
140
+ return False
141
+
142
+ try:
143
+ # Clone the repository
144
+ repo = Repository(
145
+ local_dir="./data_upload",
146
+ clone_from=HF_DATASET_REPO,
147
+ repo_type="dataset",
148
+ token=os.getenv("HF_TOKEN")
149
+ )
150
+
151
+ # Copy the vector store files
152
+ if os.path.exists(f"{VECTOR_STORE_PATH}/index.faiss"):
153
+ os.makedirs("./data_upload/vector_store", exist_ok=True)
154
+ os.system(f"cp -r {VECTOR_STORE_PATH}/* ./data_upload/vector_store/")
155
+
156
+ # Copy the chat history
157
+ if os.path.exists(f"{LOCAL_CHAT_HISTORY_PATH}/chat_logs.json"):
158
+ os.makedirs("./data_upload/chat_history", exist_ok=True)
159
+ os.system(f"cp -r {LOCAL_CHAT_HISTORY_PATH}/* ./data_upload/chat_history/")
160
+
161
+ # Push to Hugging Face
162
+ repo.push_to_hub(commit_message="Update vector store and chat history")
163
+ return True
164
+ except Exception as e:
165
+ print(f"Error uploading to dataset: {e}")
166
+ return False
167
+
168
+ # --------------- Enhanced Logging ---------------
169
+ def log_interaction(user_input: str, bot_response: str, context: str, conversation_id: str):
170
+ """Log interactions with error handling"""
171
+ try:
172
+ log_entry = {
173
+ "timestamp": datetime.now().isoformat(),
174
+ "conversation_id": conversation_id,
175
+ "user_input": user_input,
176
+ "bot_response": bot_response,
177
+ "context": context[:500] if context else "",
178
+ "kb_version": kb_info['version']
179
+ }
180
+
181
+ os.makedirs(LOCAL_CHAT_HISTORY_PATH, exist_ok=True)
182
+ log_path = os.path.join(LOCAL_CHAT_HISTORY_PATH, "chat_logs.json")
183
+
184
+ with open(log_path, "a", encoding="utf-8") as f:
185
+ f.write(json.dumps(log_entry, ensure_ascii=False) + "\n")
186
+
187
+ # Upload to Hugging Face after logging
188
+ upload_to_hf_dataset()
189
+
190
+ except Exception as e:
191
+ print(f"Logging error: {str(e)}")
192
+ print(traceback.format_exc())
193
+
194
+ # --------------- Model Initialization ---------------
195
+ def init_models():
196
+ """Initialize AI models"""
197
+ global llm, embeddings
198
+
199
+ if not llm:
200
+ try:
201
+ llm = ChatGroq(
202
+ model_name="llama-3.3-70b-versatile",
203
+ temperature=0.6,
204
+ api_key=os.getenv("GROQ_API_KEY")
205
+ )
206
+ except Exception as e:
207
+ print(f"LLM initialization failed: {str(e)}")
208
+ raise HTTPException(status_code=500, detail=f"LLM initialization failed: {str(e)}")
209
+
210
+ if not embeddings:
211
+ try:
212
+ embeddings = HuggingFaceEmbeddings(
213
+ model_name="intfloat/multilingual-e5-large-instruct"
214
+ )
215
+ except Exception as e:
216
+ print(f"Embeddings initialization failed: {str(e)}")
217
+ raise HTTPException(status_code=500, detail=f"Embeddings initialization failed: {str(e)}")
218
+
219
+ return llm, embeddings
220
+
221
+ # --------------- Knowledge Base Management ---------------
222
+ def build_knowledge_base():
223
+ """Build or update the knowledge base"""
224
+ global vector_store, kb_info
225
+
226
+ _, _embeddings = init_models()
227
+
228
+ try:
229
+ start_time = time.time()
230
+ documents = []
231
+
232
+ # Create folder in advance
233
+ os.makedirs(VECTOR_STORE_PATH, exist_ok=True)
234
+
235
+ # Load documents
236
+ for url in URLS:
237
+ try:
238
+ loader = WebBaseLoader(url)
239
+ docs = loader.load()
240
+ documents.extend(docs)
241
+ print(f"Loaded {url}")
242
+ except Exception as e:
243
+ print(f"Failed to load {url}: {str(e)}")
244
+ continue
245
+
246
+ if not documents:
247
+ raise HTTPException(status_code=500, detail="No documents loaded!")
248
+
249
+ # Split into chunks
250
+ text_splitter = RecursiveCharacterTextSplitter(
251
+ chunk_size=500,
252
+ chunk_overlap=100
253
+ )
254
+ chunks = text_splitter.split_documents(documents)
255
+
256
+ # Create vector store
257
+ vector_store = FAISS.from_documents(chunks, _embeddings)
258
+ vector_store.save_local(
259
+ folder_path=VECTOR_STORE_PATH,
260
+ index_name="index"
261
+ )
262
+
263
+ # Verify file creation
264
+ if not os.path.exists(os.path.join(VECTOR_STORE_PATH, "index.faiss")):
265
+ raise HTTPException(status_code=500, detail="FAISS index file not created!")
266
+
267
+ # Update info
268
+ kb_info.update({
269
+ 'build_time': time.time() - start_time,
270
+ 'size': sum(
271
+ os.path.getsize(os.path.join(VECTOR_STORE_PATH, f))
272
+ for f in ["index.faiss", "index.pkl"]
273
+ ) / (1024 ** 2),
274
+ 'version': datetime.now().strftime("%Y%m%d-%H%M%S")
275
+ })
276
+
277
+ # Upload to Hugging Face
278
+ upload_to_hf_dataset()
279
+
280
+ return {
281
+ "status": "success",
282
+ "message": "Knowledge base successfully created!",
283
+ "details": kb_info
284
+ }
285
+
286
+ except Exception as e:
287
+ error_msg = f"Knowledge base creation failed: {str(e)}"
288
+ print(error_msg)
289
+ print(traceback.format_exc())
290
+ raise HTTPException(status_code=500, detail=error_msg)
291
+
292
+ def load_knowledge_base():
293
+ """Load the knowledge base from disk"""
294
+ global vector_store
295
+
296
+ if vector_store:
297
+ return vector_store
298
+
299
+ _, _embeddings = init_models()
300
+
301
+ try:
302
+ vector_store = FAISS.load_local(
303
+ VECTOR_STORE_PATH,
304
+ _embeddings,
305
+ allow_dangerous_deserialization=True
306
+ )
307
+ return vector_store
308
+ except Exception as e:
309
+ error_msg = f"Failed to load knowledge base: {str(e)}"
310
+ print(error_msg)
311
+ print(traceback.format_exc())
312
+ return None
313
+
314
+ # --------------- API Endpoints ---------------
315
+ @app.get("/")
316
+ async def root():
317
+ """Root endpoint that shows app status"""
318
+ vector_store_exists = os.path.exists(os.path.join(VECTOR_STORE_PATH, "index.faiss"))
319
+
320
+ return {
321
+ "status": "running",
322
+ "knowledge_base_exists": vector_store_exists,
323
+ "kb_info": kb_info if vector_store_exists else None
324
+ }
325
+
326
+ @app.get("/health")
327
+ async def health_check():
328
+ """Health check endpoint"""
329
+ return {"status": "healthy"}
330
+
331
+ @app.post("/build-kb", response_model=BuildKnowledgeBaseResponse)
332
+ async def build_kb_endpoint():
333
+ """Endpoint to build/rebuild the knowledge base"""
334
+ return build_knowledge_base()
335
+
336
+ @app.post("/chat", response_model=ChatResponse)
337
+ async def chat_endpoint(request: ChatRequest):
338
+ """Endpoint to chat with the assistant"""
339
+ # Check if knowledge base exists
340
+ if not os.path.exists(os.path.join(VECTOR_STORE_PATH, "index.faiss")):
341
+ raise HTTPException(
342
+ status_code=400,
343
+ detail="Knowledge base not found. Please build it first with /build-kb"
344
+ )
345
+
346
+ # Use provided conversation ID or generate a new one
347
+ conversation_id = request.conversation_id or f"conv_{datetime.now().strftime('%Y%m%d%H%M%S')}"
348
+
349
+ try:
350
+ # Load models and knowledge base
351
+ _llm, _ = init_models()
352
+ _vector_store = load_knowledge_base()
353
+
354
+ if not _vector_store:
355
+ raise HTTPException(
356
+ status_code=500,
357
+ detail="Failed to load knowledge base"
358
+ )
359
+
360
+ # Retrieve context
361
+ context_docs = _vector_store.similarity_search(request.message)
362
+ context_text = "\n".join([d.page_content for d in context_docs])
363
+
364
+ # Generate response
365
+ prompt_template = PromptTemplate.from_template('''
366
+ You are a helpful and polite legal assistant at Status Law.
367
+ You answer in the language in which the question was asked.
368
+ Answer the question based on the context provided.
369
+ If you cannot answer based on the context, say so politely and offer to contact Status Law directly via the following channels:
370
+ - For all users: +32465594521 (landline phone).
371
+ - For English and Swedish speakers only: +46728495129 (available on WhatsApp, Telegram, Signal, IMO).
372
+ - Provide a link to the contact form: [Contact Form](https://status.law/law-firm-contact-legal-protection/).
373
+ If the user has questions about specific services and their costs, suggest they visit the page https://status.law/tariffs-for-services-of-protection-against-extradition-and-international-prosecution/ for detailed information.
374
+
375
+ Ask the user additional questions to understand which service to recommend and provide an estimated cost. For example, clarify their situation and needs to suggest the most appropriate options.
376
+
377
+ Also, offer free consultations if they are available and suitable for the user's request.
378
+ Answer professionally but in a friendly manner.
379
+
380
+ Example:
381
+ Q: How can I challenge the sanctions?
382
+ A: To challenge the sanctions, you should consult with our legal team, who specialize in this area. Please contact us directly for detailed advice. You can fill out our contact form here: [Contact Form](https://status.law/law-firm-contact-legal-protection/).
383
+
384
+ Context: {context}
385
+ Question: {question}
386
+
387
+ Response Guidelines:
388
+ 1. Answer in the user's language
389
+ 2. Cite sources when possible
390
+ 3. Offer contact options if unsure
391
+ ''')
392
+
393
+ chain = prompt_template | _llm | StrOutputParser()
394
+ response = chain.invoke({
395
+ "context": context_text,
396
+ "question": request.message
397
+ })
398
+
399
+ # Log the interaction
400
+ log_interaction(request.message, response, context_text, conversation_id)
401
+
402
+ return {
403
+ "response": response,
404
+ "conversation_id": conversation_id
405
+ }
406
+
407
+ except Exception as e:
408
+ error_msg = f"Error generating response: {str(e)}"
409
+ print(error_msg)
410
+ print(traceback.format_exc())
411
+ raise HTTPException(status_code=500, detail=error_msg)
412
+
413
+ # Initialize dataset integration at startup
414
+ @app.on_event("startup")
415
+ async def startup_event():
416
+ """Initialize on startup"""
417
+ # Try to load existing knowledge base from Hugging Face
418
+ init_hf_dataset_integration()
419
+
420
+ # Preload embeddings model to reduce first-request latency
421
+ try:
422
+ global embeddings
423
+ if not embeddings:
424
+ embeddings = HuggingFaceEmbeddings(
425
+ model_name="intfloat/multilingual-e5-large-instruct"
426
+ )
427
+ except Exception as e:
428
+ print(f"Warning: Failed to preload embeddings: {e}")
429
+
430
+ # Run the application
431
+ if __name__ == "__main__":
432
+ uvicorn.run("app:app", host="0.0.0.0", port=8000)
requirements.txt CHANGED
@@ -1,5 +1,6 @@
1
  fastapi==0.109.2
2
  uvicorn==0.27.1
 
3
  langchain>=0.1.0
4
  langchain_groq>=0.1.0
5
  langchain_huggingface>=0.0.2
@@ -11,4 +12,5 @@ python-dotenv>=1.0.0
11
  huggingface_hub>=0.19.0
12
  jinja2>=3.0.0
13
  aiofiles>=0.8.0
14
- python-multipart>=0.0.6
 
 
1
  fastapi==0.109.2
2
  uvicorn==0.27.1
3
+ gradio>=4.0.0
4
  langchain>=0.1.0
5
  langchain_groq>=0.1.0
6
  langchain_huggingface>=0.0.2
 
12
  huggingface_hub>=0.19.0
13
  jinja2>=3.0.0
14
  aiofiles>=0.8.0
15
+ python-multipart>=0.0.6
16
+ requests