GitHub Actions commited on
Commit
668e007
·
1 Parent(s): 4ee221b

Sync from GitHub Actions

Browse files
Files changed (4) hide show
  1. Dockerfile +0 -1
  2. Lawverse/retrieval/sparse.py +58 -41
  3. api/app.py +59 -54
  4. requirements.txt +0 -0
Dockerfile CHANGED
@@ -10,7 +10,6 @@ RUN pip install -r requirements.txt
10
 
11
  RUN mkdir -p /app/.cache && chmod -R 777 /app/.cache
12
  RUN mkdir -p /app/api/instance && chmod -R 777 /app/api/instance
13
- RUN python -m nltk.downloader punkt stopwords wordnet omw-1.4
14
 
15
  RUN mkdir -p /app/.cache
16
  ENV HF_HOME=/app/.cache
 
10
 
11
  RUN mkdir -p /app/.cache && chmod -R 777 /app/.cache
12
  RUN mkdir -p /app/api/instance && chmod -R 777 /app/api/instance
 
13
 
14
  RUN mkdir -p /app/.cache
15
  ENV HF_HOME=/app/.cache
Lawverse/retrieval/sparse.py CHANGED
@@ -1,46 +1,49 @@
1
- import numpy as np
 
 
 
2
  from rank_bm25 import BM25Okapi
 
3
  from Lawverse.logger import logging
4
  from Lawverse.exception import ExceptionHandle
5
- import sys
6
- import re
7
- import nltk
8
- from nltk.corpus import stopwords
9
- from nltk.stem import WordNetLemmatizer
10
- from nltk.tokenize import word_tokenize
11
 
12
- def _safe_nltk_download(package: str):
13
- try:
14
- nltk.data.find(package)
15
- except LookupError:
16
- nltk.download(package.split('/')[-1], quiet=True)
17
 
18
- _safe_nltk_download('tokenizers/punkt')
19
- _safe_nltk_download('corpora/stopwords')
20
- _safe_nltk_download('corpora/wordnet')
 
 
 
 
 
21
 
22
- try:
23
- stop_words = set(stopwords.words('english'))
24
- except LookupError:
25
- stop_words = set()
26
 
27
- lemmatizer = WordNetLemmatizer()
 
 
28
 
29
- def bm25_tokenizer(text):
30
- text = (text or "").lower()
31
- text = re.sub(r'[^\w\s]', ' ', text)
32
- try:
33
- tokens = word_tokenize(text)
34
- except LookupError:
35
- tokens = text.split()
36
- tokens = [lemmatizer.lemmatize(word) for word in tokens if word and word not in stop_words]
37
- return tokens
38
 
39
- def build_sparse_index(chunks, k1=1.5, b=0.75):
 
 
 
 
 
 
 
 
40
  try:
41
- tokenized_corpus = [bm25_tokenizer(chunk.page_content) for chunk in chunks]
42
- bm25 = BM25Okapi(tokenized_corpus, k1=k1, b=b)
 
 
 
43
 
 
44
  logging.info(f"BM25 sparse index successfully built with {len(chunks)} chunks.")
45
  return bm25
46
 
@@ -48,18 +51,32 @@ def build_sparse_index(chunks, k1=1.5, b=0.75):
48
  logging.error(f"Failed to build BM25 sparse index. Error: {e}")
49
  raise ExceptionHandle(e, sys)
50
 
51
- def bm25_retrieve(bm25, chunks, query, top_k=10):
 
 
 
 
 
 
52
  try:
53
- tokenized_query = bm25_tokenizer(query)
54
- scores = bm25.get_scores(tokenized_query)
 
 
 
 
 
 
55
 
56
- num_docs = len(chunks)
57
- actual_top_k = min(top_k, num_docs)
58
- top_indices = np.argsort(scores)[-actual_top_k:][::-1]
 
 
 
59
 
60
- logging.info(f"Successfully retrieved top {len(top_indices)} relevant chunks using BM25.")
61
- return [(chunks[i], float(scores[i])) for i in top_indices]
62
 
63
  except Exception as e:
64
- logging.error("BM25 retrieval failed")
65
  raise ExceptionHandle(e, sys)
 
1
+ from __future__ import annotations
2
+ import re
3
+ import sys
4
+ from typing import List, Tuple
5
  from rank_bm25 import BM25Okapi
6
+ from langchain_core.documents import Document
7
  from Lawverse.logger import logging
8
  from Lawverse.exception import ExceptionHandle
 
 
 
 
 
 
9
 
 
 
 
 
 
10
 
11
+ STOP_WORDS = {
12
+ "a", "an", "the", "and", "or", "but", "if", "then", "else", "when", "while",
13
+ "is", "are", "am", "was", "were", "be", "been", "being",
14
+ "to", "of", "in", "on", "for", "from", "by", "with", "as", "at", "into",
15
+ "this", "that", "these", "those", "it", "its", "he", "she", "they", "them",
16
+ "his", "her", "their", "we", "you", "your", "i", "me", "my",
17
+ "shall", "may", "under", "section", "subsection",
18
+ }
19
 
 
 
 
 
20
 
21
+ def bm25_tokenizer(text: str) -> List[str]:
22
+ if not text:
23
+ return []
24
 
25
+ text = text.lower()
26
+ english_tokens = re.findall(r"[a-zA-Z0-9]+", text)
27
+ bangla_tokens = re.findall(r"[\u0980-\u09FF]+", text)
28
+ tokens = english_tokens + bangla_tokens
 
 
 
 
 
29
 
30
+ cleaned = [
31
+ token
32
+ for token in tokens
33
+ if len(token) > 1 and token not in STOP_WORDS
34
+ ]
35
+ return cleaned
36
+
37
+
38
+ def build_sparse_index(chunks: List[Document], k1: float = 1.5, b: float = 0.8) -> BM25Okapi:
39
  try:
40
+ logging.info("Building sparse BM25 index...")
41
+ tokenized_corpus = [
42
+ bm25_tokenizer(chunk.page_content)
43
+ for chunk in chunks
44
+ ]
45
 
46
+ bm25 = BM25Okapi(tokenized_corpus, k1=k1, b=b)
47
  logging.info(f"BM25 sparse index successfully built with {len(chunks)} chunks.")
48
  return bm25
49
 
 
51
  logging.error(f"Failed to build BM25 sparse index. Error: {e}")
52
  raise ExceptionHandle(e, sys)
53
 
54
+
55
+ def bm25_retrieve(
56
+ bm25: BM25Okapi,
57
+ query: str,
58
+ chunks: List[Document],
59
+ top_k: int = 10,
60
+ ) -> List[Tuple[Document, float]]:
61
  try:
62
+ query_tokens = bm25_tokenizer(query)
63
+ scores = bm25.get_scores(query_tokens)
64
+
65
+ ranked = sorted(
66
+ enumerate(scores),
67
+ key=lambda item: item[1],
68
+ reverse=True,
69
+ )[:top_k]
70
 
71
+ results = []
72
+ for idx, score in ranked:
73
+ doc = chunks[idx]
74
+ doc.metadata = dict(doc.metadata or {})
75
+ doc.metadata["bm25_score"] = float(score)
76
+ results.append((doc, float(score)))
77
 
78
+ return results
 
79
 
80
  except Exception as e:
81
+ logging.error(f"BM25 retrieval failed. Error: {e}")
82
  raise ExceptionHandle(e, sys)
api/app.py CHANGED
@@ -1,7 +1,8 @@
1
  from flask import Flask, render_template, request, jsonify, session, stream_with_context, Response
2
  from dotenv import load_dotenv
3
  import os
4
- import secrets
 
5
  from Lawverse.pipeline.rag_pipeline import rag_components
6
  from Lawverse.pipeline.llm_loader import llm
7
  from Lawverse.memory.langchain_memory import ChatMemory
@@ -10,25 +11,38 @@ from Lawverse.monitoring.dashboard import monitor_bp
10
  from Lawverse.agents.graph import create_agentic_chain
11
  from Lawverse.storage.factory import get_chat_store
12
  from api.auth import auth_bp, login_required
13
-
 
 
 
 
 
 
 
 
 
14
  load_dotenv()
15
-
16
  app = Flask(__name__, template_folder="../templates")
17
- app.secret_key = os.getenv("SECRET_KEY") or secrets.token_hex(32)
18
 
19
  app.register_blueprint(auth_bp)
20
  app.register_blueprint(monitor_bp)
21
 
22
  BASE_COMPONENTS = None
 
23
  active_chains = {}
24
 
25
  def get_base_components():
26
  global BASE_COMPONENTS
 
 
 
27
 
28
- if BASE_COMPONENTS is None:
29
- logging.info("Loading Lawverse RAG base components...")
30
- BASE_COMPONENTS = rag_components()
31
- logging.info("Lawverse RAG base components loaded successfully.")
 
32
 
33
  return BASE_COMPONENTS
34
 
@@ -113,67 +127,58 @@ def rag_response():
113
  @app.route("/get_chats", methods=["GET"])
114
  @login_required
115
  def get_chats():
116
- chats = []
117
- user_id = session.get("user_id")
118
- os.makedirs(MEMORY_DIR, exist_ok=True)
119
- for file_path in glob.glob(f"{MEMORY_DIR}/*.json"):
120
- try:
121
- with open(file_path, "r", encoding="utf-8") as f:
122
- data = json.load(f)
123
-
124
- if data.get("user_id") == user_id:
125
- chats.append({
126
- "chat_id": data.get("chat_id"),
127
- "last_updated": data.get("last_updated"),
128
- "title": data.get("title", f"Chat-{data.get('chat_id')}")
129
- })
130
-
131
- except Exception as e:
132
- logging.warning(f"Skipping unreadable memory file {file_path}: {e}")
133
 
134
- chats.sort(key=lambda x: x.get("last_updated") or x.get("chat_id"), reverse=True)
135
- return jsonify(chats)
 
136
 
137
 
138
  @app.route("/load_chat/<chat_id>", methods=["POST"])
139
  @login_required
140
  def load_chat(chat_id):
141
- user_id = session.get("user_id")
142
- memory_path = os.path.join(MEMORY_DIR, f"user_{user_id}_{chat_id}.json")
143
- if not os.path.exists(memory_path):
144
- return jsonify({"error": "Chat not found"}), 404
 
145
 
146
- _, memory_manager = create_agent_session(chat_id=chat_id)
147
 
148
- messages_list = memory_manager.memory.chat_memory.messages
149
- messages = []
150
 
151
- for i in range(0, len(messages_list), 2):
152
- user_msg = messages_list[i].content if i < len(messages_list) else None
153
- ai_msg = messages_list[i + 1].content if i + 1 < len(messages_list) else ""
154
- if user_msg:
155
- messages.append({
156
- "user": user_msg,
157
- "ai": ai_msg
158
- })
159
 
160
- return jsonify({
161
- "chat_id": chat_id,
162
- "title": memory_manager._get_title(),
163
- "messages": messages,
164
- })
 
 
 
 
 
 
 
 
 
 
165
 
166
 
167
  @app.route("/delete_chat/<chat_id>", methods=["DELETE"])
168
  @login_required
169
  def delete_chat(chat_id):
170
  try:
171
- user_id = session.get("user_id")
172
- memory_path = os.path.join(MEMORY_DIR, f"user_{user_id}_{chat_id}.json")
173
 
174
- if os.path.exists(memory_path):
175
- os.remove(memory_path)
176
- logging.info(f"Deleted chat for user {user_id}, chat_id: {chat_id}")
177
 
178
  was_active = chat_id in active_chains
179
  if was_active:
@@ -183,12 +188,12 @@ def delete_chat(chat_id):
183
  session.pop("chat_id", None)
184
 
185
  return jsonify({
186
- "success": True,
187
  "was_active": was_active
188
  }), 200
189
 
190
  except Exception as e:
191
- logging.error(f"Error deleting chat {chat_id}: {e}")
192
  return jsonify({"error": "Internal Server Error"}), 500
193
 
194
  if __name__ == "__main__":
 
1
  from flask import Flask, render_template, request, jsonify, session, stream_with_context, Response
2
  from dotenv import load_dotenv
3
  import os
4
+ from threading import Lock
5
+ import logging as py_logging
6
  from Lawverse.pipeline.rag_pipeline import rag_components
7
  from Lawverse.pipeline.llm_loader import llm
8
  from Lawverse.memory.langchain_memory import ChatMemory
 
11
  from Lawverse.agents.graph import create_agentic_chain
12
  from Lawverse.storage.factory import get_chat_store
13
  from api.auth import auth_bp, login_required
14
+ for logger_name in [
15
+ "httpcore",
16
+ "httpx",
17
+ "hpack",
18
+ "filelock",
19
+ "sentence_transformers",
20
+ "urllib3",
21
+ ]:
22
+ py_logging.getLogger(logger_name).setLevel(py_logging.WARNING)
23
+
24
  load_dotenv()
 
25
  app = Flask(__name__, template_folder="../templates")
26
+ app.secret_key = os.getenv("SECRET_KEY")
27
 
28
  app.register_blueprint(auth_bp)
29
  app.register_blueprint(monitor_bp)
30
 
31
  BASE_COMPONENTS = None
32
+ BASE_COMPONENTS_LOCK = Lock()
33
  active_chains = {}
34
 
35
  def get_base_components():
36
  global BASE_COMPONENTS
37
+
38
+ if BASE_COMPONENTS is not None:
39
+ return BASE_COMPONENTS
40
 
41
+ with BASE_COMPONENTS_LOCK:
42
+ if BASE_COMPONENTS is None:
43
+ logging.info("Loading Lawverse RAG base components...")
44
+ BASE_COMPONENTS = rag_components()
45
+ logging.info("Lawverse RAG base components loaded successfully.")
46
 
47
  return BASE_COMPONENTS
48
 
 
127
  @app.route("/get_chats", methods=["GET"])
128
  @login_required
129
  def get_chats():
130
+ try:
131
+ user_id = str(session.get("user_id"))
132
+ chats = get_chat_store().list_chats(user_id)
133
+ return jsonify(chats), 200
 
 
 
 
 
 
 
 
 
 
 
 
 
134
 
135
+ except Exception as e:
136
+ logging.error(f"Failed to list chats from cloud store: {e}")
137
+ return jsonify([]), 200
138
 
139
 
140
  @app.route("/load_chat/<chat_id>", methods=["POST"])
141
  @login_required
142
  def load_chat(chat_id):
143
+ try:
144
+ user_id = str(session.get("user_id"))
145
+ data = get_chat_store().load_chat(user_id, chat_id)
146
+ if not data:
147
+ return jsonify({"error": "Chat not found"}), 404
148
 
149
+ _, memory_manager = create_agent_session(chat_id=chat_id)
150
 
151
+ messages_list = memory_manager.memory.chat_memory.messages
152
+ messages = []
153
 
154
+ for i in range(0, len(messages_list), 2):
155
+ user_msg = messages_list[i].content if i < len(messages_list) else None
156
+ ai_msg = messages_list[i + 1].content if i + 1 < len(messages_list) else ""
 
 
 
 
 
157
 
158
+ if user_msg:
159
+ messages.append({
160
+ "user": user_msg,
161
+ "ai": ai_msg
162
+ })
163
+
164
+ return jsonify({
165
+ "chat_id": chat_id,
166
+ "title": memory_manager._get_title(),
167
+ "messages": messages,
168
+ }), 200
169
+
170
+ except Exception as e:
171
+ logging.error(f"Failed to load chat from cloud store: {e}")
172
+ return jsonify({"error": "Internal Server Error"}), 500
173
 
174
 
175
  @app.route("/delete_chat/<chat_id>", methods=["DELETE"])
176
  @login_required
177
  def delete_chat(chat_id):
178
  try:
179
+ user_id = str(session.get("user_id"))
 
180
 
181
+ deleted = get_chat_store().delete_chat(user_id, chat_id)
 
 
182
 
183
  was_active = chat_id in active_chains
184
  if was_active:
 
188
  session.pop("chat_id", None)
189
 
190
  return jsonify({
191
+ "success": deleted,
192
  "was_active": was_active
193
  }), 200
194
 
195
  except Exception as e:
196
+ logging.error(f"Error deleting cloud chat {chat_id}: {e}")
197
  return jsonify({"error": "Internal Server Error"}), 500
198
 
199
  if __name__ == "__main__":
requirements.txt CHANGED
Binary files a/requirements.txt and b/requirements.txt differ