Jatin Mehra commited on
Commit
63ed7c1
·
1 Parent(s): 1ee9743

Refactor retrieval and agent functions for improved chunk handling and error management

Browse files
Files changed (1) hide show
  1. preprocessing.py +93 -38
preprocessing.py CHANGED
@@ -8,6 +8,8 @@ from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
8
  from langchain.memory import ConversationBufferMemory
9
  from sentence_transformers import SentenceTransformer
10
  import dotenv
 
 
11
  dotenv.load_dotenv()
12
  # Initialize LLM and tools globally
13
 
@@ -66,80 +68,133 @@ def build_faiss_index(embeddings):
66
  index.add(embeddings)
67
  return index
68
 
69
- def retrieve_similar_chunks(query, index, chunks, model, k=10, max_chunk_length=1000):
70
  """Retrieve top k similar chunks to the query from the FAISS index."""
71
- query_embedding = model.encode([query], convert_to_tensor=True).cpu().numpy()
72
  distances, indices = index.search(query_embedding, k)
73
- return [(chunks[i]["text"][:max_chunk_length], distances[0][j], chunks[i]["metadata"]) for j, i in enumerate(indices[0])]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
- def agentic_rag(llm, tools, query, context_chunks, memory, Use_Tavily=False):
 
 
76
  # Sort chunks by relevance (lower distance = more relevant)
77
- context_chunks = sorted(context_chunks, key=lambda x: x[1]) # Sort by distance
78
  context = ""
79
  total_tokens = 0
80
  max_tokens = 7000 # Leave room for prompt and response
81
-
82
- # Aggregate relevant chunks until token limit is reached
83
- for chunk, _, _ in context_chunks: # Unpack three elements
84
  chunk_tokens = estimate_tokens(chunk)
85
  if total_tokens + chunk_tokens <= max_tokens:
86
  context += chunk + "\n\n"
87
  total_tokens += chunk_tokens
88
  else:
89
  break
90
-
91
- # Set up the search behavior
92
- search_behavior = (
93
- "If the context is insufficient, *then* use the 'search' tool to find the answer."
94
- if Use_Tavily
95
- else "If the context is insufficient, you *must* state that you don't know."
96
- )
97
-
98
- # Define prompt template
99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  prompt = ChatPromptTemplate.from_messages([
101
- ("system", """
102
- You are an expert Q&A system. Your primary function is to answer questions using a given set of documents (Context).
103
 
104
  **Your Process:**
105
 
106
  1. **Analyze the Question:** Understand exactly what the user is asking.
107
- 2. **Scan the Context:** Thoroughly review the 'Context' provided to find relevant information.
108
  3. **Formulate the Answer:**
109
- * If the context contains a clear answer, synthesize it into a concise response.
110
- * **Always** start your answer with "Based on the Document, ...".
111
- * {search_behavior}
112
- * If, after all steps, you cannot find an answer, respond with: "Based on the Document, I don't know the answer."
113
  4. **Clarity:** Ensure your final answer is clear, direct, and avoids jargon if possible.
114
 
115
  **Important Rules:**
116
 
117
- * **Stick to the Context:** Unless you use the search tool, do *not* use any information outside of the provided 'Context'.
118
  * **No Speculation:** Do not make assumptions or infer information not explicitly present.
119
- * **Cite Sources (If Searching):** If you use the search tool, you MUST include the source links in your response.
120
- """),
121
- ("human", "Context: {context}\n\nQuestion: {input}"),
122
  MessagesPlaceholder(variable_name="chat_history"),
123
  MessagesPlaceholder(variable_name="agent_scratchpad"),
124
  ])
125
 
126
- agent_tools = tools if Use_Tavily else []
127
  try:
128
- agent = create_tool_calling_agent(llm, agent_tools, prompt)
129
- agent_executor = AgentExecutor(agent=agent, tools=agent_tools, memory=memory, verbose=True)
130
- return agent_executor.invoke({
131
  "input": query,
132
  "context": context,
133
- "search_behavior": search_behavior
134
  })
 
135
  except Exception as e:
136
- print(f"Error during agent execution: {str(e)}")
137
- fallback_prompt = ChatPromptTemplate.from_messages([
138
- ("system", "You are a helpful assistant. Use the provided context to answer the user's question."),
139
  ("human", "Context: {context}\n\nQuestion: {input}")
140
  ])
141
- response = llm.invoke(fallback_prompt.format(context=context, input=query))
142
- return {"output": response.content}
 
 
143
 
144
  """if __name__ == "__main__":
145
  # Process PDF and prepare index
 
8
  from langchain.memory import ConversationBufferMemory
9
  from sentence_transformers import SentenceTransformer
10
  import dotenv
11
+ from langchain.tools import tool
12
+ import traceback
13
  dotenv.load_dotenv()
14
  # Initialize LLM and tools globally
15
 
 
68
  index.add(embeddings)
69
  return index
70
 
71
+ def retrieve_similar_chunks(query, index, chunks_with_metadata, embedding_model, k=10, max_chunk_length=1000):
72
  """Retrieve top k similar chunks to the query from the FAISS index."""
73
+ query_embedding = embedding_model.encode([query], convert_to_tensor=True).cpu().numpy()
74
  distances, indices = index.search(query_embedding, k)
75
+
76
+ # Ensure indices are within bounds of chunks_with_metadata
77
+ valid_indices = [i for i in indices[0] if 0 <= i < len(chunks_with_metadata)]
78
+
79
+ return [
80
+ (chunks_with_metadata[i]["text"][:max_chunk_length], distances[0][j], chunks_with_metadata[i]["metadata"])
81
+ for j, i in enumerate(valid_indices) # Use valid_indices
82
+ ]
83
+
84
+
85
+ def create_vector_search_tool(faiss_index, document_chunks_with_metadata, embedding_model, k=3, max_chunk_length=1000):
86
+ @tool
87
+ def vector_database_search(query: str) -> str:
88
+ """
89
+ Searches the currently uploaded PDF document for information semantically similar to the query.
90
+ Use this tool when the user's question is likely answerable from the content of the specific document they provided.
91
+ Input should be the search query.
92
+ """
93
+ # Retrieve similar chunks using the provided session-specific components
94
+ similar_chunks_data = retrieve_similar_chunks(
95
+ query,
96
+ faiss_index,
97
+ document_chunks_with_metadata, # This is the list of dicts {text: ..., metadata: ...}
98
+ embedding_model,
99
+ k=k,
100
+ max_chunk_length=max_chunk_length
101
+ )
102
+ # Format the response
103
+ if not similar_chunks_data:
104
+ return "No relevant information found in the document for that query."
105
+
106
+ context = "\n\n---\n\n".join([chunk_text for chunk_text, _, _ in similar_chunks_data])
107
+ return f"The following information was found in the document regarding '{query}':\n{context}"
108
 
109
+ return vector_database_search
110
+
111
+ def agentic_rag(llm, agent_specific_tools, query, context_chunks, memory, Use_Tavily=False): # Renamed 'tools' to 'agent_specific_tools'
112
  # Sort chunks by relevance (lower distance = more relevant)
113
+ context_chunks = sorted(context_chunks, key=lambda x: x[1]) if context_chunks else []
114
  context = ""
115
  total_tokens = 0
116
  max_tokens = 7000 # Leave room for prompt and response
117
+
118
+ for chunk, _, _ in context_chunks:
 
119
  chunk_tokens = estimate_tokens(chunk)
120
  if total_tokens + chunk_tokens <= max_tokens:
121
  context += chunk + "\n\n"
122
  total_tokens += chunk_tokens
123
  else:
124
  break
 
 
 
 
 
 
 
 
 
125
 
126
+ context = context.strip() if context else "No initial context provided from preliminary search."
127
+
128
+
129
+ # Dynamically build the tool guidance for the prompt
130
+ # Tool names: 'vector_database_search', 'tavily_search_results_json'
131
+ has_document_search = any(t.name == "vector_database_search" for t in agent_specific_tools)
132
+ has_web_search = any(t.name == "tavily_search_results_json" for t in agent_specific_tools)
133
+
134
+ guidance_parts = []
135
+ if has_document_search:
136
+ guidance_parts.append(
137
+ "If the direct context (if any from preliminary search) is insufficient and the question seems answerable from the uploaded document, "
138
+ "use the 'vector_database_search' tool to find relevant information within the document."
139
+ )
140
+ if has_web_search: # Tavily tool would only be in agent_specific_tools if Use_Tavily was true
141
+ guidance_parts.append(
142
+ "If the information is not found in the document (after using 'vector_database_search' if appropriate) "
143
+ "or the question is of a general nature not specific to the document, "
144
+ "use the 'tavily_search_results_json' tool for web searches."
145
+ )
146
+
147
+ if not guidance_parts:
148
+ search_behavior_instructions = "If the context is insufficient, you *must* state that you don't know."
149
+ else:
150
+ search_behavior_instructions = " ".join(guidance_parts)
151
+ search_behavior_instructions += ("\n * If, after all steps and tool use (if any), you cannot find an answer, "
152
+ "respond with: \"Based on the available information, I don't know the answer.\"")
153
+
154
  prompt = ChatPromptTemplate.from_messages([
155
+ ("system", f"""
156
+ You are an expert Q&A system. Your primary function is to answer questions using a given set of documents (Context) and available tools.
157
 
158
  **Your Process:**
159
 
160
  1. **Analyze the Question:** Understand exactly what the user is asking.
161
+ 2. **Scan the Context:** Thoroughly review the 'Context' provided (if any) to find relevant information. This context is derived from a preliminary similarity search in the document.
162
  3. **Formulate the Answer:**
163
+ * If the initially provided context contains a clear answer, synthesize it into a concise response. Start your answer with "Based on the Document, ...".
164
+ * {search_behavior_instructions}
165
+ * When using the 'vector_database_search' tool, the information comes from the document. Prepend your answer with "Based on the Document, ...".
166
+ * When using the 'tavily_search_results_json' tool, the information comes from the web. Prepend your answer with "According to a web search, ...". If no useful information is found, state that.
167
  4. **Clarity:** Ensure your final answer is clear, direct, and avoids jargon if possible.
168
 
169
  **Important Rules:**
170
 
171
+ * **Stick to Sources:** Do *not* use any information outside of the provided 'Context', document search results ('vector_database_search'), or web search results ('tavily_search_results_json').
172
  * **No Speculation:** Do not make assumptions or infer information not explicitly present.
173
+ * **Cite Sources (If Web Searching):** If you use the 'tavily_search_results_json' tool and it provides source links, you MUST include them in your response.
174
+ """),
175
+ ("human", "Context: {{context}}\n\nQuestion: {{input}}"), # Double braces for f-string in f-string
176
  MessagesPlaceholder(variable_name="chat_history"),
177
  MessagesPlaceholder(variable_name="agent_scratchpad"),
178
  ])
179
 
 
180
  try:
181
+ agent = create_tool_calling_agent(llm, agent_specific_tools, prompt)
182
+ agent_executor = AgentExecutor(agent=agent, tools=agent_specific_tools, memory=memory, verbose=True)
183
+ response_payload = agent_executor.invoke({
184
  "input": query,
185
  "context": context,
 
186
  })
187
+ return response_payload # Expecting dict like {'output': '...'}
188
  except Exception as e:
189
+ print(f"Error during agent execution: {str(e)} \nTraceback: {traceback.format_exc()}")
190
+ fallback_prompt_template = ChatPromptTemplate.from_messages([
191
+ ("system", "You are a helpful assistant. Use the provided context to answer the user's question. If the context is insufficient, say you don't know."),
192
  ("human", "Context: {context}\n\nQuestion: {input}")
193
  ])
194
+ # Format the prompt with the actual context and query
195
+ formatted_fallback_prompt = fallback_prompt_template.format_prompt(context=context, input=query).to_messages()
196
+ response = llm.invoke(formatted_fallback_prompt)
197
+ return {"output": response.content if hasattr(response, 'content') else str(response)}
198
 
199
  """if __name__ == "__main__":
200
  # Process PDF and prepare index