josondev commited on
Commit
41f9740
·
verified ·
1 Parent(s): 6a4ebb3

Update veryfinal.py

Browse files
Files changed (1) hide show
  1. veryfinal.py +421 -267
veryfinal.py CHANGED
@@ -1,339 +1,493 @@
1
  """
2
- Enhanced LangGraph Agent with Multi-LLM Support and Proper Question Answering
3
- Combines your original LangGraph structure with enhanced response handling
4
  """
5
 
6
  import os
7
  import time
8
  import random
9
- from dotenv import load_dotenv
10
- from typing import List, Dict, Any, TypedDict, Annotated
11
  import operator
 
 
12
 
13
- from langgraph.graph import START, StateGraph, MessagesState, END
14
- from langgraph.prebuilt import tools_condition, ToolNode
15
- from langgraph.checkpoint.memory import MemorySaver
16
-
17
- from langchain_google_genai import ChatGoogleGenerativeAI
18
- from langchain_groq import ChatGroq
19
- from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint, HuggingFaceEmbeddings
20
  from langchain_community.tools.tavily_search import TavilySearchResults
21
- from langchain_community.document_loaders import WikipediaLoader, ArxivLoader
22
- from langchain_community.vectorstores import SupabaseVectorStore
 
23
  from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
24
- from langchain_core.tools import tool
25
- from langchain.tools.retriever import create_retriever_tool
26
- from supabase.client import Client, create_client
27
-
28
- load_dotenv()
29
-
30
- # Enhanced system prompt for better question answering
31
- ENHANCED_SYSTEM_PROMPT = """You are a helpful assistant tasked with answering questions using a set of tools.
32
 
33
- CRITICAL INSTRUCTIONS:
34
- 1. Read the question carefully and understand what specific information is being asked
35
- 2. Use the appropriate tools to find the exact information requested
36
- 3. For factual questions, search for current and accurate information
37
- 4. For calculations, use the math tools provided
38
- 5. Always provide specific, direct answers - never repeat the question as your answer
39
- 6. If you cannot find the information, state "Information not available"
40
- 7. Format your final response as: FINAL ANSWER: [your specific answer]
41
 
42
- ANSWER FORMAT RULES:
43
- - For numbers: provide just the number without commas or units unless specified
44
- - For names/strings: provide the exact name or term without articles
45
- - For lists: provide comma-separated values
46
- - Be concise and specific in your final answer
47
 
48
- Remember: Your job is to ANSWER the question, not repeat it back."""
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
- # ---- Enhanced Tool Definitions ----
51
  @tool
52
  def multiply(a: int, b: int) -> int:
53
- """Multiply two numbers.
54
- Args:
55
- a: first int
56
- b: second int
57
- """
58
  return a * b
59
 
60
  @tool
61
  def add(a: int, b: int) -> int:
62
- """Add two numbers.
63
- Args:
64
- a: first int
65
- b: second int
66
- """
67
  return a + b
68
 
69
  @tool
70
  def subtract(a: int, b: int) -> int:
71
- """Subtract two numbers.
72
- Args:
73
- a: first int
74
- b: second int
75
- """
76
  return a - b
77
 
78
  @tool
79
  def divide(a: int, b: int) -> float:
80
- """Divide two numbers.
81
- Args:
82
- a: first int
83
- b: second int
84
- """
85
  if b == 0:
86
  raise ValueError("Cannot divide by zero.")
87
  return a / b
88
 
89
  @tool
90
  def modulus(a: int, b: int) -> int:
91
- """Get the modulus of two numbers.
92
- Args:
93
- a: first int
94
- b: second int
95
- """
96
  return a % b
97
 
98
  @tool
99
- def wiki_search(query: str) -> str:
100
- """Search Wikipedia for a query and return maximum 2 results.
101
- Args:
102
- query: The search query.
103
- """
104
  try:
105
- time.sleep(random.uniform(0.5, 1.0)) # Rate limiting
106
- search_docs = WikipediaLoader(query=query, load_max_docs=2).load()
107
- if not search_docs:
108
- return "No Wikipedia results found"
109
-
110
- formatted_search_docs = "\n\n---\n\n".join([
111
- f'<Document source="{doc.metadata.get("source", "Wikipedia")}" title="{doc.metadata.get("title", "")}">\n{doc.page_content[:1500]}\n</Document>'
112
- for doc in search_docs
113
- ])
114
- return formatted_search_docs
115
- except Exception as e:
116
- return f"Wikipedia search failed: {e}"
117
-
118
- @tool
119
- def web_search(query: str) -> str:
120
- """Search Tavily for a query and return maximum 3 results.
121
- Args:
122
- query: The search query.
123
- """
124
- try:
125
- time.sleep(random.uniform(0.7, 1.2)) # Rate limiting
126
  search_tool = TavilySearchResults(max_results=3)
127
- search_docs = search_tool.invoke({"query": query})
128
- if not search_docs:
129
- return "No web search results found"
130
-
131
- formatted_search_docs = "\n\n---\n\n".join([
132
- f'<Document source="{doc.get("url", "")}">\n{doc.get("content", "")[:1200]}\n</Document>'
133
- for doc in search_docs
134
- ])
135
- return formatted_search_docs
136
  except Exception as e:
137
  return f"Web search failed: {e}"
138
 
139
  @tool
140
- def arxiv_search(query: str) -> str:
141
- """Search Arxiv for a query and return maximum 3 results.
142
- Args:
143
- query: The search query.
144
- """
145
  try:
146
- time.sleep(random.uniform(0.5, 1.0)) # Rate limiting
147
- search_docs = ArxivLoader(query=query, load_max_docs=3).load()
148
- if not search_docs:
149
- return "No ArXiv results found"
150
-
151
- formatted_search_docs = "\n\n---\n\n".join([
152
- f'<Document source="{doc.metadata.get("source", "ArXiv")}" title="{doc.metadata.get("title", "")}">\n{doc.page_content[:1000]}\n</Document>'
153
- for doc in search_docs
154
- ])
155
- return formatted_search_docs
156
  except Exception as e:
157
- return f"ArXiv search failed: {e}"
158
-
159
- # Initialize tools list
160
- tools = [
161
- multiply, add, subtract, divide, modulus,
162
- wiki_search, web_search, arxiv_search
163
- ]
164
-
165
- # Enhanced State for better tracking
166
- class EnhancedState(MessagesState):
167
- """Enhanced state with additional tracking"""
168
- query: str = ""
169
- tools_used: List[str] = []
170
- search_results: str = ""
171
 
172
- def build_graph(provider: str = "groq"):
173
- """Build the enhanced graph with proper error handling and response formatting"""
 
174
 
175
- # Initialize LLM based on provider
176
- if provider == "google":
177
- llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0)
178
- elif provider == "groq":
179
- llm = ChatGroq(model="llama3-70b-8192", temperature=0) # Using more reliable model
180
- elif provider == "huggingface":
181
- llm = ChatHuggingFace(
182
- llm=HuggingFaceEndpoint(
183
- url="https://api-inference.huggingface.co/models/Meta-DeepLearning/llama-2-7b-chat-hf",
184
- temperature=0,
185
- ),
186
- )
187
- else:
188
- raise ValueError("Invalid provider. Choose 'google', 'groq' or 'huggingface'.")
 
 
 
189
 
190
- # Bind tools to LLM
191
- llm_with_tools = llm.bind_tools(tools)
192
-
193
- # Initialize vector store if available
194
- vector_store = None
195
- try:
196
- if os.getenv("SUPABASE_URL") and os.getenv("SUPABASE_SERVICE_KEY"):
197
- embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2")
198
- supabase: Client = create_client(
199
- os.environ.get("SUPABASE_URL"),
200
- os.environ.get("SUPABASE_SERVICE_KEY")
201
- )
202
- vector_store = SupabaseVectorStore(
203
- client=supabase,
204
- embedding=embeddings,
205
- table_name="documents",
206
- query_name="match_documents_langchain",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  )
208
- except Exception as e:
209
- print(f"Vector store initialization failed: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
 
211
- def retriever(state: MessagesState):
212
- """Enhanced retriever node with fallback"""
213
- messages = state["messages"]
214
- query = messages[-1].content if messages else ""
215
-
216
- # Try to get similar questions from vector store
217
- similar_context = ""
218
- if vector_store:
219
- try:
220
- similar_questions = vector_store.similarity_search(query, k=1)
221
- if similar_questions:
222
- similar_context = f"\n\nSimilar example for reference:\n{similar_questions[0].page_content}"
223
- except Exception as e:
224
- print(f"Vector search failed: {e}")
 
 
 
 
 
 
 
 
 
 
 
225
 
226
- # Enhanced system message with context
227
- enhanced_prompt = ENHANCED_SYSTEM_PROMPT + similar_context
228
- sys_msg = SystemMessage(content=enhanced_prompt)
229
 
230
- return {"messages": [sys_msg] + messages}
231
 
232
- def assistant(state: MessagesState):
233
- """Enhanced assistant node with better response handling"""
234
- try:
235
- response = llm_with_tools.invoke(state["messages"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
 
237
- # Ensure response is properly formatted
238
- if hasattr(response, 'content'):
239
- content = response.content
 
 
 
 
 
 
 
 
 
 
240
 
241
- # Check if this is just repeating the question
242
- original_query = state["messages"][-1].content if state["messages"] else ""
243
- if content.strip() == original_query.strip():
244
- # Force a better response
245
- enhanced_messages = state["messages"] + [
246
- HumanMessage(content=f"Please provide a specific answer to this question, do not repeat the question: {original_query}")
247
- ]
248
- response = llm_with_tools.invoke(enhanced_messages)
249
 
250
- return {"messages": [response]}
251
- except Exception as e:
252
- error_response = AIMessage(content=f"Error processing request: {e}")
253
- return {"messages": [error_response]}
254
 
255
- def format_final_answer(state: MessagesState):
256
- """Format the final answer properly"""
257
- messages = state["messages"]
258
- if not messages:
259
- return {"messages": [AIMessage(content="FINAL ANSWER: Information not available")]}
260
-
261
- last_message = messages[-1]
262
- if hasattr(last_message, 'content'):
263
- content = last_message.content
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
 
265
- # Ensure proper formatting
266
- if "FINAL ANSWER:" not in content:
267
- # Extract the key information and format it
268
- if content.strip():
269
- formatted_content = f"FINAL ANSWER: {content.strip()}"
 
 
 
270
  else:
271
- formatted_content = "FINAL ANSWER: Information not available"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
 
273
- formatted_message = AIMessage(content=formatted_content)
274
- return {"messages": messages[:-1] + [formatted_message]}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275
 
276
- return {"messages": messages}
 
 
 
277
 
278
- # Build the graph
279
- builder = StateGraph(MessagesState)
280
-
281
- # Add nodes
282
- builder.add_node("retriever", retriever)
283
- builder.add_node("assistant", assistant)
284
- builder.add_node("tools", ToolNode(tools))
285
- builder.add_node("formatter", format_final_answer)
286
-
287
- # Add edges
288
- builder.add_edge(START, "retriever")
289
- builder.add_edge("retriever", "assistant")
290
- builder.add_conditional_edges(
291
- "assistant",
292
- tools_condition,
293
- {
294
- "tools": "tools",
295
- "__end__": "formatter"
296
  }
297
- )
298
- builder.add_edge("tools", "assistant")
299
- builder.add_edge("formatter", END)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
300
 
301
- # Compile graph with checkpointer
302
- return builder.compile(checkpointer=MemorySaver())
 
303
 
304
- # Test function
305
- def test_agent():
306
- """Test the agent with sample questions"""
307
- graph = build_graph(provider="groq")
 
 
 
308
 
 
309
  test_questions = [
310
  "How many studio albums were published by Mercedes Sosa between 2000 and 2009?",
311
  "What is 25 multiplied by 17?",
312
- "Who nominated the only Featured Article on English Wikipedia about a dinosaur that was promoted in November 2004?"
313
  ]
314
 
315
  for question in test_questions:
316
- print(f"\nQuestion: {question}")
317
- print("-" * 60)
318
-
319
- try:
320
- messages = [HumanMessage(content=question)]
321
- config = {"configurable": {"thread_id": f"test_{hash(question)}"}}
322
- result = graph.invoke({"messages": messages}, config)
323
-
324
- if result and "messages" in result:
325
- final_message = result["messages"][-1]
326
- if hasattr(final_message, 'content'):
327
- print(f"Answer: {final_message.content}")
328
- else:
329
- print(f"Answer: {final_message}")
330
- else:
331
- print("Answer: No response generated")
332
- except Exception as e:
333
- print(f"Error: {e}")
334
-
335
- print()
336
-
337
- if __name__ == "__main__":
338
- # Run tests
339
- test_agent()
 
1
  """
2
+ Enhanced Multi-LLM Agent System with Supabase FAISS Integration
3
+ Complete system for document insertion, retrieval, and question answering
4
  """
5
 
6
  import os
7
  import time
8
  import random
 
 
9
  import operator
10
+ from typing import List, Dict, Any, TypedDict, Annotated, Optional
11
+ from dotenv import load_dotenv
12
 
13
+ from langchain_core.tools import tool
 
 
 
 
 
 
14
  from langchain_community.tools.tavily_search import TavilySearchResults
15
+ from langchain_community.document_loaders import WikipediaLoader
16
+ from langgraph.graph import StateGraph, END
17
+ from langgraph.checkpoint.memory import MemorySaver
18
  from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
19
+ from langchain_groq import ChatGroq
 
 
 
 
 
 
 
20
 
21
+ # Supabase and FAISS imports
22
+ import faiss
23
+ import numpy as np
24
+ from sentence_transformers import SentenceTransformer
25
+ from supabase import create_client, Client
26
+ import pandas as pd
27
+ import json
28
+ import pickle
29
 
30
+ load_dotenv()
 
 
 
 
31
 
32
+ # Enhanced system prompt for question-answering
33
+ ENHANCED_SYSTEM_PROMPT = (
34
+ "You are a helpful assistant tasked with answering questions using a set of tools. "
35
+ "You must provide accurate, comprehensive answers based on available information. "
36
+ "When answering questions, follow these guidelines:\n"
37
+ "1. Use available tools to gather information when needed\n"
38
+ "2. Provide precise, factual answers\n"
39
+ "3. For numbers: don't use commas or units unless specified\n"
40
+ "4. For strings: don't use articles or abbreviations, write digits in plain text\n"
41
+ "5. For lists: apply above rules based on element type\n"
42
+ "6. Always end with 'FINAL ANSWER: [YOUR ANSWER]'\n"
43
+ "7. Be concise but thorough in your reasoning\n"
44
+ "8. If you cannot find the answer, state that clearly"
45
+ )
46
 
47
+ # ---- Tool Definitions ----
48
  @tool
49
  def multiply(a: int, b: int) -> int:
50
+ """Multiply two integers and return the product."""
 
 
 
 
51
  return a * b
52
 
53
  @tool
54
  def add(a: int, b: int) -> int:
55
+ """Add two integers and return the sum."""
 
 
 
 
56
  return a + b
57
 
58
  @tool
59
  def subtract(a: int, b: int) -> int:
60
+ """Subtract the second integer from the first and return the difference."""
 
 
 
 
61
  return a - b
62
 
63
  @tool
64
  def divide(a: int, b: int) -> float:
65
+ """Divide the first integer by the second and return the quotient."""
 
 
 
 
66
  if b == 0:
67
  raise ValueError("Cannot divide by zero.")
68
  return a / b
69
 
70
  @tool
71
  def modulus(a: int, b: int) -> int:
72
+ """Return the remainder when dividing the first integer by the second."""
 
 
 
 
73
  return a % b
74
 
75
  @tool
76
+ def optimized_web_search(query: str) -> str:
77
+ """Perform an optimized web search using TavilySearchResults."""
 
 
 
78
  try:
79
+ time.sleep(random.uniform(0.7, 1.5))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  search_tool = TavilySearchResults(max_results=3)
81
+ docs = search_tool.invoke({"query": query})
82
+ return "\n\n---\n\n".join(
83
+ f"<Doc url='{d.get('url','')}'>{d.get('content','')[:800]}</Doc>"
84
+ for d in docs
85
+ )
 
 
 
 
86
  except Exception as e:
87
  return f"Web search failed: {e}"
88
 
89
  @tool
90
+ def optimized_wiki_search(query: str) -> str:
91
+ """Perform an optimized Wikipedia search and return content snippets."""
 
 
 
92
  try:
93
+ time.sleep(random.uniform(0.3, 1))
94
+ docs = WikipediaLoader(query=query, load_max_docs=2).load()
95
+ return "\n\n---\n\n".join(
96
+ f"<Doc src='{d.metadata.get('source','Wikipedia')}'>{d.page_content[:1000]}</Doc>"
97
+ for d in docs
98
+ )
 
 
 
 
99
  except Exception as e:
100
+ return f"Wikipedia search failed: {e}"
 
 
 
 
 
 
 
 
 
 
 
 
 
101
 
102
+ # ---- Supabase FAISS Vector Database Integration ----
103
+ class SupabaseFAISSVectorDB:
104
+ """Enhanced vector database combining FAISS with Supabase for persistent storage"""
105
 
106
+ def __init__(self):
107
+ # Initialize Supabase client
108
+ self.supabase_url = os.getenv("SUPABASE_URL")
109
+ self.supabase_key = os.getenv("SUPABASE_SERVICE_KEY")
110
+ if self.supabase_url and self.supabase_key:
111
+ self.supabase: Client = create_client(self.supabase_url, self.supabase_key)
112
+ else:
113
+ self.supabase = None
114
+ print("Supabase credentials not found, running without vector database")
115
+
116
+ # Initialize embedding model
117
+ self.embedding_model = SentenceTransformer('all-MiniLM-L6-v2')
118
+ self.embedding_dim = self.embedding_model.get_sentence_embedding_dimension()
119
+
120
+ # Initialize FAISS index
121
+ self.index = faiss.IndexFlatL2(self.embedding_dim)
122
+ self.document_store = [] # Local cache for documents
123
 
124
+ def insert_question_data(self, data: Dict[str, Any]) -> bool:
125
+ """Insert question data into both Supabase and FAISS"""
126
+ try:
127
+ question_text = data.get("Question", "")
128
+ embedding = self.embedding_model.encode([question_text])[0]
129
+
130
+ # Insert into Supabase if available
131
+ if self.supabase:
132
+ question_data = {
133
+ "task_id": data.get("task_id"),
134
+ "question": question_text,
135
+ "final_answer": data.get("Final answer"),
136
+ "level": data.get("Level"),
137
+ "file_name": data.get("file_name", ""),
138
+ "embedding": embedding.tolist()
139
+ }
140
+ self.supabase.table("questions").insert(question_data).execute()
141
+
142
+ # Add to local FAISS index
143
+ self.index.add(embedding.reshape(1, -1).astype('float32'))
144
+ self.document_store.append({
145
+ "task_id": data.get("task_id"),
146
+ "question": question_text,
147
+ "answer": data.get("Final answer"),
148
+ "level": data.get("Level")
149
+ })
150
+
151
+ return True
152
+ except Exception as e:
153
+ print(f"Error inserting data: {e}")
154
+ return False
155
+
156
+ def search_similar_questions(self, query: str, k: int = 3) -> List[Dict[str, Any]]:
157
+ """Search for similar questions using vector similarity"""
158
+ try:
159
+ if self.index.ntotal == 0:
160
+ return []
161
+
162
+ query_embedding = self.embedding_model.encode([query])[0]
163
+ k = min(k, self.index.ntotal)
164
+ distances, indices = self.index.search(
165
+ query_embedding.reshape(1, -1).astype('float32'), k
166
  )
167
+
168
+ results = []
169
+ for i, idx in enumerate(indices[0]):
170
+ if 0 <= idx < len(self.document_store):
171
+ doc = self.document_store[idx]
172
+ results.append({
173
+ "task_id": doc["task_id"],
174
+ "question": doc["question"],
175
+ "answer": doc["answer"],
176
+ "similarity_score": 1 / (1 + distances[0][i]),
177
+ "distance": float(distances[0][i])
178
+ })
179
+
180
+ return results
181
+ except Exception as e:
182
+ print(f"Error searching similar questions: {e}")
183
+ return []
184
 
185
+ # ---- Enhanced Agent State ----
186
+ class EnhancedAgentState(TypedDict):
187
+ """State structure for the enhanced multi-LLM agent system."""
188
+ messages: Annotated[List[HumanMessage | AIMessage], operator.add]
189
+ query: str
190
+ agent_type: str
191
+ final_answer: str
192
+ perf: Dict[str, Any]
193
+ agno_resp: str
194
+ tools_used: List[str]
195
+ reasoning: str
196
+ similar_questions: List[Dict[str, Any]]
197
+
198
+ # ---- Enhanced Multi-LLM System ----
199
+ class HybridLangGraphMultiLLMSystem:
200
+ """
201
+ Advanced question-answering system with multi-LLM support and vector database integration
202
+ """
203
+
204
+ def __init__(self, provider="groq"):
205
+ self.provider = provider
206
+ self.tools = [
207
+ multiply, add, subtract, divide, modulus,
208
+ optimized_web_search, optimized_wiki_search
209
+ ]
210
 
211
+ # Initialize vector database
212
+ self.vector_db = SupabaseFAISSVectorDB()
 
213
 
214
+ self.graph = self._build_graph()
215
 
216
+ def _llm(self, model_name: str) -> ChatGroq:
217
+ """Create a Groq LLM instance."""
218
+ return ChatGroq(
219
+ model=model_name,
220
+ temperature=0,
221
+ api_key=os.getenv("GROQ_API_KEY")
222
+ )
223
+
224
+ def _build_graph(self) -> StateGraph:
225
+ """Build the LangGraph state machine with enhanced capabilities."""
226
+ # Initialize LLMs
227
+ llama8_llm = self._llm("llama3-8b-8192")
228
+ llama70_llm = self._llm("llama3-70b-8192")
229
+ deepseek_llm = self._llm("deepseek-chat")
230
+
231
+ def router(st: EnhancedAgentState) -> EnhancedAgentState:
232
+ """Route queries to appropriate LLM based on complexity and content analysis."""
233
+ q = st["query"].lower()
234
 
235
+ # Enhanced routing logic
236
+ if any(keyword in q for keyword in ["calculate", "compute", "math", "multiply", "add", "subtract", "divide"]):
237
+ t = "llama70" # Use more powerful model for calculations
238
+ elif any(keyword in q for keyword in ["search", "find", "lookup", "wikipedia", "information about"]):
239
+ t = "search_enhanced" # Use search-enhanced processing
240
+ elif "deepseek" in q or any(keyword in q for keyword in ["analyze", "reasoning", "complex"]):
241
+ t = "deepseek"
242
+ elif "llama-8" in q:
243
+ t = "llama8"
244
+ elif len(q.split()) > 20: # Complex queries
245
+ t = "llama70"
246
+ else:
247
+ t = "llama8" # Default for simple queries
248
 
249
+ # Search for similar questions
250
+ similar_questions = self.vector_db.search_similar_questions(st["query"], k=3)
 
 
 
 
 
 
251
 
252
+ return {**st, "agent_type": t, "tools_used": [], "reasoning": "", "similar_questions": similar_questions}
 
 
 
253
 
254
+ def llama8_node(st: EnhancedAgentState) -> EnhancedAgentState:
255
+ """Process query with Llama-3 8B model."""
256
+ t0 = time.time()
257
+ try:
258
+ # Add similar questions context if available
259
+ context = ""
260
+ if st.get("similar_questions"):
261
+ context = "\n\nSimilar questions for reference:\n"
262
+ for sq in st["similar_questions"][:2]:
263
+ context += f"Q: {sq['question']}\nA: {sq['answer']}\n"
264
+
265
+ enhanced_query = f"""
266
+ Question: {st["query"]}
267
+ {context}
268
+ Please provide a direct, accurate answer to this question.
269
+ """
270
+
271
+ sys = SystemMessage(content=ENHANCED_SYSTEM_PROMPT)
272
+ res = llama8_llm.invoke([sys, HumanMessage(content=enhanced_query)])
273
+
274
+ answer = res.content.strip()
275
+ if "FINAL ANSWER:" in answer:
276
+ answer = answer.split("FINAL ANSWER:")[-1].strip()
277
+
278
+ return {**st,
279
+ "final_answer": answer,
280
+ "reasoning": "Used Llama-3 8B with similar questions context",
281
+ "perf": {"time": time.time() - t0, "prov": "Groq-Llama3-8B"}}
282
+ except Exception as e:
283
+ return {**st, "final_answer": f"Error: {e}", "perf": {"error": str(e)}}
284
+
285
+ def llama70_node(st: EnhancedAgentState) -> EnhancedAgentState:
286
+ """Process query with Llama-3 70B model."""
287
+ t0 = time.time()
288
+ try:
289
+ # Add similar questions context if available
290
+ context = ""
291
+ if st.get("similar_questions"):
292
+ context = "\n\nSimilar questions for reference:\n"
293
+ for sq in st["similar_questions"][:2]:
294
+ context += f"Q: {sq['question']}\nA: {sq['answer']}\n"
295
+
296
+ enhanced_query = f"""
297
+ Question: {st["query"]}
298
+ {context}
299
+ Please provide a direct, accurate answer to this question.
300
+ """
301
+
302
+ sys = SystemMessage(content=ENHANCED_SYSTEM_PROMPT)
303
+ res = llama70_llm.invoke([sys, HumanMessage(content=enhanced_query)])
304
+
305
+ answer = res.content.strip()
306
+ if "FINAL ANSWER:" in answer:
307
+ answer = answer.split("FINAL ANSWER:")[-1].strip()
308
+
309
+ return {**st,
310
+ "final_answer": answer,
311
+ "reasoning": "Used Llama-3 70B for complex reasoning with context",
312
+ "perf": {"time": time.time() - t0, "prov": "Groq-Llama3-70B"}}
313
+ except Exception as e:
314
+ return {**st, "final_answer": f"Error: {e}", "perf": {"error": str(e)}}
315
+
316
+ def deepseek_node(st: EnhancedAgentState) -> EnhancedAgentState:
317
+ """Process query with DeepSeek model."""
318
+ t0 = time.time()
319
+ try:
320
+ # Add similar questions context if available
321
+ context = ""
322
+ if st.get("similar_questions"):
323
+ context = "\n\nSimilar questions for reference:\n"
324
+ for sq in st["similar_questions"][:2]:
325
+ context += f"Q: {sq['question']}\nA: {sq['answer']}\n"
326
+
327
+ enhanced_query = f"""
328
+ Question: {st["query"]}
329
+ {context}
330
+ Please provide a direct, accurate answer to this question.
331
+ """
332
+
333
+ sys = SystemMessage(content=ENHANCED_SYSTEM_PROMPT)
334
+ res = deepseek_llm.invoke([sys, HumanMessage(content=enhanced_query)])
335
+
336
+ answer = res.content.strip()
337
+ if "FINAL ANSWER:" in answer:
338
+ answer = answer.split("FINAL ANSWER:")[-1].strip()
339
+
340
+ return {**st,
341
+ "final_answer": answer,
342
+ "reasoning": "Used DeepSeek for advanced reasoning and analysis",
343
+ "perf": {"time": time.time() - t0, "prov": "Groq-DeepSeek"}}
344
+ except Exception as e:
345
+ return {**st, "final_answer": f"Error: {e}", "perf": {"error": str(e)}}
346
+
347
+ def search_enhanced_node(st: EnhancedAgentState) -> EnhancedAgentState:
348
+ """Process query with search enhancement."""
349
+ t0 = time.time()
350
+ tools_used = []
351
 
352
+ try:
353
+ # Determine search strategy
354
+ query = st["query"]
355
+ search_results = ""
356
+
357
+ if any(keyword in query.lower() for keyword in ["wikipedia", "wiki"]):
358
+ search_results = optimized_wiki_search.invoke({"query": query})
359
+ tools_used.append("wikipedia_search")
360
  else:
361
+ search_results = optimized_web_search.invoke({"query": query})
362
+ tools_used.append("web_search")
363
+
364
+ # Add similar questions context
365
+ context = ""
366
+ if st.get("similar_questions"):
367
+ context = "\n\nSimilar questions for reference:\n"
368
+ for sq in st["similar_questions"][:2]:
369
+ context += f"Q: {sq['question']}\nA: {sq['answer']}\n"
370
+
371
+ enhanced_query = f"""
372
+ Original Question: {query}
373
+
374
+ Search Results:
375
+ {search_results}
376
+ {context}
377
 
378
+ Based on the search results and similar questions above, provide a direct answer to the original question.
379
+ """
380
+
381
+ sys = SystemMessage(content=ENHANCED_SYSTEM_PROMPT)
382
+ res = llama70_llm.invoke([sys, HumanMessage(content=enhanced_query)])
383
+
384
+ answer = res.content.strip()
385
+ if "FINAL ANSWER:" in answer:
386
+ answer = answer.split("FINAL ANSWER:")[-1].strip()
387
+
388
+ return {**st,
389
+ "final_answer": answer,
390
+ "tools_used": tools_used,
391
+ "reasoning": "Used search enhancement with similar questions context",
392
+ "perf": {"time": time.time() - t0, "prov": "Search-Enhanced-Llama70"}}
393
+ except Exception as e:
394
+ return {**st, "final_answer": f"Error: {e}", "perf": {"error": str(e)}}
395
+
396
+ # Build graph
397
+ g = StateGraph(EnhancedAgentState)
398
+ g.add_node("router", router)
399
+ g.add_node("llama8", llama8_node)
400
+ g.add_node("llama70", llama70_node)
401
+ g.add_node("deepseek", deepseek_node)
402
+ g.add_node("search_enhanced", search_enhanced_node)
403
+
404
+ g.set_entry_point("router")
405
+ g.add_conditional_edges("router", lambda s: s["agent_type"], {
406
+ "llama8": "llama8",
407
+ "llama70": "llama70",
408
+ "deepseek": "deepseek",
409
+ "search_enhanced": "search_enhanced"
410
+ })
411
 
412
+ for node in ["llama8", "llama70", "deepseek", "search_enhanced"]:
413
+ g.add_edge(node, END)
414
+
415
+ return g.compile(checkpointer=MemorySaver())
416
 
417
+ def process_query(self, q: str) -> str:
418
+ """Process a query through the enhanced multi-LLM system."""
419
+ state = {
420
+ "messages": [HumanMessage(content=q)],
421
+ "query": q,
422
+ "agent_type": "",
423
+ "final_answer": "",
424
+ "perf": {},
425
+ "agno_resp": "",
426
+ "tools_used": [],
427
+ "reasoning": "",
428
+ "similar_questions": []
 
 
 
 
 
 
429
  }
430
+ cfg = {"configurable": {"thread_id": f"enhanced_qa_{hash(q)}"}}
431
+
432
+ try:
433
+ out = self.graph.invoke(state, cfg)
434
+ answer = out.get("final_answer", "").strip()
435
+
436
+ # Ensure we don't return the question as the answer
437
+ if answer == q or answer.startswith(q):
438
+ return "Information not available"
439
+
440
+ return answer if answer else "No answer generated"
441
+ except Exception as e:
442
+ return f"Error processing query: {e}"
443
+
444
+ def load_metadata_from_jsonl(self, jsonl_file_path: str) -> int:
445
+ """Load question metadata from JSONL file into vector database"""
446
+ success_count = 0
447
+
448
+ try:
449
+ with open(jsonl_file_path, 'r', encoding='utf-8') as file:
450
+ for line_num, line in enumerate(file, 1):
451
+ try:
452
+ data = json.loads(line.strip())
453
+ if self.vector_db.insert_question_data(data):
454
+ success_count += 1
455
+
456
+ if line_num % 10 == 0:
457
+ print(f"Processed {line_num} records, {success_count} successful")
458
+
459
+ except json.JSONDecodeError as e:
460
+ print(f"JSON decode error on line {line_num}: {e}")
461
+ except Exception as e:
462
+ print(f"Error processing line {line_num}: {e}")
463
+
464
+ except FileNotFoundError:
465
+ print(f"File not found: {jsonl_file_path}")
466
+
467
+ print(f"Loaded {success_count} questions into vector database")
468
+ return success_count
469
 
470
+ def build_graph(provider: str | None = None) -> StateGraph:
471
+ """Build and return the graph for the enhanced agent system."""
472
+ return HybridLangGraphMultiLLMSystem(provider or "groq").graph
473
 
474
+ if __name__ == "__main__":
475
+ # Initialize and test the system
476
+ system = HybridLangGraphMultiLLMSystem()
477
+
478
+ # Load metadata if available
479
+ if os.path.exists("metadata.jsonl"):
480
+ system.load_metadata_from_jsonl("metadata.jsonl")
481
 
482
+ # Test queries
483
  test_questions = [
484
  "How many studio albums were published by Mercedes Sosa between 2000 and 2009?",
485
  "What is 25 multiplied by 17?",
486
+ "Find information about artificial intelligence on Wikipedia"
487
  ]
488
 
489
  for question in test_questions:
490
+ print(f"Question: {question}")
491
+ answer = system.process_query(question)
492
+ print(f"Answer: {answer}")
493
+ print("-" * 50)