1- # How to update faiss_index:
2- # 1. Edit the markdown files in server_api/chatbot/file_summaries/ as needed.
3- # These are end-user-focused guides (one per application page/feature) that
4- # serve as the knowledge base for the RAG chatbot.
5- # 2. Run this script:
6- # python server_api/chatbot/update_faiss.py
7- #
8- # You can override the embeddings model and Ollama base URL via:
9- # - Environment variables: OLLAMA_EMBED_MODEL, OLLAMA_BASE_URL
10- # - CLI arguments: --model, --base-url
11-
12- import os
131import argparse
2+ import os
143from pathlib import Path
15- from langchain_core .documents import Document
16- from langchain_text_splitters import RecursiveCharacterTextSplitter
17- from langchain_community .vectorstores import FAISS
18- from langchain_ollama import OllamaEmbeddings
4+ from typing import Optional , Tuple
195
6+ DEFAULT_OLLAMA_BASE_URL = "http://cscigpu08.bc.edu:4443"
7+ DEFAULT_OLLAMA_EMBED_MODEL = "qwen3-embedding:8b"
8+ INDEX_FILENAMES = ("index.faiss" , "index.pkl" )
9+
10+
11+ def get_chatbot_paths (base_dir : Optional [Path ] = None ) -> Tuple [Path , Path ]:
12+ root = (base_dir or Path (__file__ ).parent ).resolve ()
13+ return root / "file_summaries" , root / "faiss_index"
2014
21- def main ():
22- # Parse CLI arguments
23- parser = argparse .ArgumentParser (
24- description = "Update FAISS index for RAG chatbot documentation search"
25- )
26- parser .add_argument (
27- "--model" ,
28- default = None ,
29- help = "Ollama embeddings model (default: from OLLAMA_EMBED_MODEL env or 'qwen3-embedding:8b')" ,
30- )
31- parser .add_argument (
32- "--base-url" ,
33- default = None ,
34- help = "Ollama base URL (default: from OLLAMA_BASE_URL env or 'http://cscigpu08.bc.edu:4443')" ,
35- )
36- args = parser .parse_args ()
3715
38- # Use same defaults as build_chain() in chatbot.py
39- embed_model = args .model or os .getenv ("OLLAMA_EMBED_MODEL" , "qwen3-embedding:8b" )
40- base_url = args .base_url or os .getenv (
41- "OLLAMA_BASE_URL" , "http://cscigpu08.bc.edu:4443"
16+ def resolve_ollama_settings (
17+ model : Optional [str ] = None , base_url : Optional [str ] = None
18+ ) -> Tuple [str , str ]:
19+ embed_model = model or os .getenv ("OLLAMA_EMBED_MODEL" , DEFAULT_OLLAMA_EMBED_MODEL )
20+ resolved_base_url = base_url or os .getenv (
21+ "OLLAMA_BASE_URL" , DEFAULT_OLLAMA_BASE_URL
4222 )
23+ return embed_model , resolved_base_url
4324
44- print (f"Using embeddings model: { embed_model } " )
45- print (f"Using Ollama base URL: { base_url } " )
4625
47- script_directory = Path (__file__ ).parent .resolve ()
48- summaries_directory = script_directory / "file_summaries"
49- faiss_directory = script_directory / "faiss_index"
26+ def faiss_index_exists (faiss_directory : Path ) -> bool :
27+ return all ((faiss_directory / name ).is_file () for name in INDEX_FILENAMES )
28+
29+
30+ def build_faiss_index (
31+ summaries_directory : Path ,
32+ faiss_directory : Path ,
33+ * ,
34+ model : Optional [str ] = None ,
35+ base_url : Optional [str ] = None ,
36+ ):
37+ from langchain_core .documents import Document
38+ from langchain_text_splitters import RecursiveCharacterTextSplitter
39+ from langchain_community .vectorstores import FAISS
40+ from langchain_ollama import OllamaEmbeddings
41+
42+ embed_model , resolved_base_url = resolve_ollama_settings (model , base_url )
43+
44+ print (f"Using embeddings model: { embed_model } " )
45+ print (f"Using Ollama base URL: { resolved_base_url } " )
5046
51- # Load full documents
5247 documents = []
5348 for md_file in summaries_directory .rglob ("*.md" ):
5449 summary = md_file .read_text (encoding = "utf-8" )
@@ -60,7 +55,6 @@ def main():
6055 )
6156 )
6257
63- # Split into chunks for better embedding quality
6458 text_splitter = RecursiveCharacterTextSplitter (
6559 chunk_size = 1000 ,
6660 chunk_overlap = 200 ,
@@ -73,12 +67,66 @@ def main():
7367 f" - { c .metadata ['source' ]} (start={ c .metadata .get ('start_index' , '?' )} , { len (c .page_content )} chars)"
7468 )
7569
76- embeddings = OllamaEmbeddings (model = embed_model , base_url = base_url )
70+ embeddings = OllamaEmbeddings (model = embed_model , base_url = resolved_base_url )
7771 vectorstore = FAISS .from_documents (chunks , embeddings )
7872 faiss_directory .mkdir (parents = True , exist_ok = True )
7973 vectorstore .save_local (str (faiss_directory ))
8074 print (f"FAISS index saved with { vectorstore .index .ntotal } vectors" )
8175
8276
77+ def ensure_faiss_index (
78+ * ,
79+ summaries_directory : Optional [Path ] = None ,
80+ faiss_directory : Optional [Path ] = None ,
81+ model : Optional [str ] = None ,
82+ base_url : Optional [str ] = None ,
83+ ) -> bool :
84+ default_summaries_directory , default_faiss_directory = get_chatbot_paths ()
85+ summaries_directory = summaries_directory or default_summaries_directory
86+ faiss_directory = faiss_directory or default_faiss_directory
87+
88+ if faiss_index_exists (faiss_directory ):
89+ return False
90+
91+ build_faiss_index (
92+ summaries_directory ,
93+ faiss_directory ,
94+ model = model ,
95+ base_url = base_url ,
96+ )
97+ return True
98+
99+
100+ def main ():
101+ parser = argparse .ArgumentParser (
102+ description = "Rebuild the generated FAISS index for chatbot documentation search"
103+ )
104+ parser .add_argument (
105+ "--model" ,
106+ default = None ,
107+ help = (
108+ "Ollama embeddings model "
109+ f"(default: OLLAMA_EMBED_MODEL or '{ DEFAULT_OLLAMA_EMBED_MODEL } ')"
110+ ),
111+ )
112+ parser .add_argument (
113+ "--base-url" ,
114+ default = None ,
115+ help = (
116+ "Ollama base URL "
117+ f"(default: OLLAMA_BASE_URL or '{ DEFAULT_OLLAMA_BASE_URL } ')"
118+ ),
119+ )
120+ args = parser .parse_args ()
121+
122+ summaries_directory , faiss_directory = get_chatbot_paths ()
123+ build_faiss_index (
124+ summaries_directory ,
125+ faiss_directory ,
126+ model = args .model ,
127+ base_url = args .base_url ,
128+ )
129+
130+
83131if __name__ == "__main__" :
84132 main ()
0 commit comments