reworked document adding and most of the project

This commit is contained in:
2026-03-25 08:57:10 +01:00
parent 08610e0b64
commit 7d70169ee3
4 changed files with 205 additions and 58 deletions
+34 -12
View File
@@ -12,6 +12,23 @@ from langchain_ollama import ChatOllama
from langchain_openai import ChatOpenAI
from langchain_text_splitters import RecursiveCharacterTextSplitter
# Pipeline tuning: used when adding documents (rebuild index after changing).
CHUNK_SIZE = 2000
CHUNK_OVERLAP = 400
# Retrieval / query tuning
RETRIEVAL_K = 5
# Appended to the embedding query for FAISS (does not affect the answer-generation prompt).
QUERY_ADDITIONAL_INSTRUCTIONS = (
"Do not return a list of references but prioritize meaningful text from abstracts, results and discussion sections."
)
# Opening instructions for the final answer LLM call only. The rest (history, context, question)
# is assembled in query_with_history.
ANSWER_PROMPT = """You are an assistant for question-answering. Use the chat history (if any) and the retrieved context below to answer the current question.
If you don't know the answer, say so. Keep the conversation coherent."""
class LocalRAG:
def __init__(
@@ -19,7 +36,7 @@ class LocalRAG:
vectorstore_path="./vectorstore",
llm_provider="ollama",
ollama_model="gpt-oss:20b",
openai_model="gpt-5.2",
openai_model="gpt-5-mini",
ollama_base_url="http://localhost:11434",
):
"""Initialize local RAG system. llm_provider: 'ollama' or 'openai'."""
@@ -32,11 +49,12 @@ class LocalRAG:
model_name="sentence-transformers/all-MiniLM-L6-v2"
)
# Text splitter
# Text splitter (used when adding documents; tune CHUNK_SIZE / CHUNK_OVERLAP above)
self.text_splitter = RecursiveCharacterTextSplitter(
chunk_size=2000,
chunk_overlap=400
chunk_size=CHUNK_SIZE,
chunk_overlap=CHUNK_OVERLAP,
)
print(f"Text splitter: chunk_size={CHUNK_SIZE}, chunk_overlap={CHUNK_OVERLAP}")
# LLM (Ollama or OpenAI)
if llm_provider == "openai":
@@ -186,15 +204,19 @@ class LocalRAG:
for doc, score in docs_with_scores
]
def query(self, question, k=8):
def query(self, question, k=None):
"""Query the RAG system (no conversation history). Returns dict with 'answer' and 'retrieved'."""
if k is None:
k = RETRIEVAL_K
return self.query_with_history(question, chat_history=[], k=k)
def query_with_history(self, question, chat_history=None, k=8):
def query_with_history(self, question, chat_history=None, k=None):
"""Query the RAG with conversation history: rephrase question using history for retrieval,
then answer with full conversation + retrieved context in the prompt.
Returns dict with 'answer' and 'retrieved' (list of chunks with content, source, page).
"""
if k is None:
k = RETRIEVAL_K
if self.vectorstore is None:
return {
"answer": "Error: No documents loaded. Please add documents first.",
@@ -203,9 +225,6 @@ class LocalRAG:
history_str = self._format_history(chat_history)
search_query = question
rag_query_instruction = (
"Do not return a list of references but prioritize meaningful text from abstracts, results and discussion sections."
)
print(f"[RAG] User question: {question!r}")
@@ -224,7 +243,7 @@ Standalone question:"""
search_query = (rephrase_response.content if hasattr(rephrase_response, "content") else str(rephrase_response)).strip() or question
print(f"[RAG] Standalone search query (rephrased): {search_query!r}")
retrieval_query = f"{search_query}\n\n{rag_query_instruction}"
retrieval_query = f"{search_query}\n\n{QUERY_ADDITIONAL_INSTRUCTIONS}"
print(f"[RAG] Search query: {search_query!r}")
print(f"[RAG] Retrieval query sent to vector store: {retrieval_query!r}")
@@ -239,8 +258,9 @@ Standalone question:"""
# 3) Answer using conversation history + retrieved context
history_block = f"Chat history:\n{history_str}\n\n" if history_str else ""
answer_prompt = f"""You are an assistant for question-answering. Use the chat history (if any) and the retrieved context below to answer the current question.
If you don't know the answer, say so. Keep the conversation coherent.
answer_prompt = (
ANSWER_PROMPT
+ f"""
{history_block}Relevant context from documents:
@@ -249,6 +269,8 @@ Standalone question:"""
Current question: {question}
Answer:"""
)
print(f"[RAG] Composed answer prompt:\n{answer_prompt}")
response = self.llm.invoke(answer_prompt)
answer = response.content if hasattr(response, "content") else str(response)