josondev commited on
Commit
4efaf9c
·
verified ·
1 Parent(s): 5ec17ed

Update veryfinal.py

Browse files
Files changed (1) hide show
  1. veryfinal.py +286 -292
veryfinal.py CHANGED
@@ -1,6 +1,6 @@
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
@@ -10,28 +10,38 @@ 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"
@@ -74,21 +84,35 @@ def modulus(a: int, b: int) -> int:
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()
@@ -99,88 +123,120 @@ def optimized_wiki_search(query: str) -> str:
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):
@@ -190,159 +246,61 @@ class EnhancedAgentState(TypedDict):
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."""
@@ -361,73 +319,111 @@ class HybridLangGraphMultiLLMSystem:
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)
@@ -441,53 +437,51 @@ class HybridLangGraphMultiLLMSystem:
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)
 
1
  """
2
+ Open-Source Multi-LLM Agent System
3
+ Uses only free and open-source models - no paid APIs required
4
  """
5
 
6
  import os
 
10
  from typing import List, Dict, Any, TypedDict, Annotated, Optional
11
  from dotenv import load_dotenv
12
 
13
+ # Core LangChain imports
14
  from langchain_core.tools import tool
15
  from langchain_community.tools.tavily_search import TavilySearchResults
16
  from langchain_community.document_loaders import WikipediaLoader
17
  from langgraph.graph import StateGraph, END
18
  from langgraph.checkpoint.memory import MemorySaver
19
  from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
 
20
 
21
+ # Open-source model integrations
22
+ from langchain_groq import ChatGroq # Free tier available
23
+ from langchain_community.llms import Ollama
24
+ from langchain_community.chat_models import ChatOllama
25
+
26
+ # Hugging Face integration for open-source models
27
+ try:
28
+ from langchain_huggingface import HuggingFacePipeline
29
+ from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
30
+ HF_AVAILABLE = True
31
+ except ImportError:
32
+ HF_AVAILABLE = False
33
+
34
+ # Vector database imports
35
  import faiss
36
  import numpy as np
37
  from sentence_transformers import SentenceTransformer
 
 
38
  import json
 
39
 
40
  load_dotenv()
41
 
42
+ # Enhanced system prompt
43
  ENHANCED_SYSTEM_PROMPT = (
44
+ "You are a helpful assistant tasked with answering questions using available tools. "
45
  "You must provide accurate, comprehensive answers based on available information. "
46
  "When answering questions, follow these guidelines:\n"
47
  "1. Use available tools to gather information when needed\n"
 
84
 
85
  @tool
86
  def optimized_web_search(query: str) -> str:
87
+ """Perform web search using free DuckDuckGo (fallback if Tavily not available)."""
88
  try:
89
+ # Try Tavily first (free tier)
90
+ if os.getenv("TAVILY_API_KEY"):
91
+ time.sleep(random.uniform(0.7, 1.5))
92
+ search_tool = TavilySearchResults(max_results=3)
93
+ docs = search_tool.invoke({"query": query})
94
+ return "\n\n---\n\n".join(
95
+ f"<Doc url='{d.get('url','')}'>{d.get('content','')[:800]}</Doc>"
96
+ for d in docs
97
+ )
98
+ else:
99
+ # Fallback to DuckDuckGo (completely free)
100
+ try:
101
+ from duckduckgo_search import DDGS
102
+ with DDGS() as ddgs:
103
+ results = list(ddgs.text(query, max_results=3))
104
+ return "\n\n---\n\n".join(
105
+ f"<Doc url='{r.get('href','')}'>{r.get('body','')[:800]}</Doc>"
106
+ for r in results
107
+ )
108
+ except ImportError:
109
+ return "Web search not available - install duckduckgo-search for free web search"
110
  except Exception as e:
111
  return f"Web search failed: {e}"
112
 
113
  @tool
114
  def optimized_wiki_search(query: str) -> str:
115
+ """Perform Wikipedia search - completely free."""
116
  try:
117
  time.sleep(random.uniform(0.3, 1))
118
  docs = WikipediaLoader(query=query, load_max_docs=2).load()
 
123
  except Exception as e:
124
  return f"Wikipedia search failed: {e}"
125
 
126
+ # ---- Open-Source Model Manager ----
127
+ class OpenSourceModelManager:
128
+ """Manages only open-source and free models"""
129
 
130
  def __init__(self):
131
+ self.available_models = {}
132
+ self._initialize_models()
133
+
134
+ def _initialize_models(self):
135
+ """Initialize only open-source models"""
 
 
 
136
 
137
+ # 1. Groq (Free tier with open-source models)
138
+ if os.getenv("GROQ_API_KEY"):
139
+ try:
140
+ self.available_models['groq_llama3_70b'] = ChatGroq(
141
+ model="llama3-70b-8192",
142
+ temperature=0,
143
+ api_key=os.getenv("GROQ_API_KEY")
144
+ )
145
+ self.available_models['groq_llama3_8b'] = ChatGroq(
146
+ model="llama3-8b-8192",
147
+ temperature=0,
148
+ api_key=os.getenv("GROQ_API_KEY")
149
+ )
150
+ self.available_models['groq_mixtral'] = ChatGroq(
151
+ model="mixtral-8x7b-32768",
152
+ temperature=0,
153
+ api_key=os.getenv("GROQ_API_KEY")
154
+ )
155
+ self.available_models['groq_gemma'] = ChatGroq(
156
+ model="gemma-7b-it",
157
+ temperature=0,
158
+ api_key=os.getenv("GROQ_API_KEY")
159
+ )
160
+ print("Groq models initialized (free tier)")
161
+ except Exception as e:
162
+ print(f"Groq models not available: {e}")
163
 
164
+ # 2. Ollama (Completely free local models)
 
 
 
 
 
165
  try:
166
+ # Test if Ollama is running
167
+ test_model = ChatOllama(model="llama3", base_url="http://localhost:11434")
168
+ # If no error, add Ollama models
169
+ self.available_models['ollama_llama3'] = ChatOllama(model="llama3")
170
+ self.available_models['ollama_llama3_70b'] = ChatOllama(model="llama3:70b")
171
+ self.available_models['ollama_mistral'] = ChatOllama(model="mistral")
172
+ self.available_models['ollama_phi3'] = ChatOllama(model="phi3")
173
+ self.available_models['ollama_codellama'] = ChatOllama(model="codellama")
174
+ self.available_models['ollama_gemma'] = ChatOllama(model="gemma")
175
+ self.available_models['ollama_qwen'] = ChatOllama(model="qwen")
176
+ print("Ollama models initialized (local)")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  except Exception as e:
178
+ print(f"Ollama not available: {e}")
179
+
180
+ # 3. Hugging Face Transformers (Completely free)
181
+ if HF_AVAILABLE:
182
+ try:
183
+ # Small models that can run on CPU
184
+ self.available_models['hf_gpt2'] = self._create_hf_model("gpt2")
185
+ self.available_models['hf_distilgpt2'] = self._create_hf_model("distilgpt2")
186
+ print("Hugging Face models initialized (local)")
187
+ except Exception as e:
188
+ print(f"Hugging Face models not available: {e}")
189
+
190
+ print(f"Total available open-source models: {len(self.available_models)}")
191
 
192
+ def _create_hf_model(self, model_name: str):
193
+ """Create Hugging Face pipeline model"""
194
  try:
195
+ pipe = pipeline(
196
+ "text-generation",
197
+ model=model_name,
198
+ max_length=512,
199
+ do_sample=True,
200
+ temperature=0.7,
201
+ pad_token_id=50256
202
  )
203
+ return HuggingFacePipeline(pipeline=pipe)
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  except Exception as e:
205
+ print(f"Failed to create HF model {model_name}: {e}")
206
+ return None
207
+
208
+ def get_model(self, model_name: str):
209
+ """Get a specific model by name"""
210
+ return self.available_models.get(model_name)
211
+
212
+ def list_available_models(self) -> List[str]:
213
+ """List all available model names"""
214
+ return list(self.available_models.keys())
215
+
216
+ def get_best_model_for_task(self, task_type: str):
217
+ """Get the best available model for a specific task type"""
218
+ if task_type == "reasoning":
219
+ # Prefer larger models for reasoning
220
+ for model_name in ['groq_llama3_70b', 'ollama_llama3_70b', 'groq_mixtral', 'ollama_llama3']:
221
+ if model_name in self.available_models:
222
+ return self.available_models[model_name]
223
+
224
+ elif task_type == "coding":
225
+ # Prefer code-specialized models
226
+ for model_name in ['ollama_codellama', 'groq_llama3_70b', 'ollama_llama3']:
227
+ if model_name in self.available_models:
228
+ return self.available_models[model_name]
229
+
230
+ elif task_type == "fast":
231
+ # Prefer fast, smaller models
232
+ for model_name in ['groq_llama3_8b', 'groq_gemma', 'ollama_phi3', 'hf_distilgpt2']:
233
+ if model_name in self.available_models:
234
+ return self.available_models[model_name]
235
+
236
+ # Default fallback to first available
237
+ if self.available_models:
238
+ return list(self.available_models.values())[0]
239
+ return None
240
 
241
  # ---- Enhanced Agent State ----
242
  class EnhancedAgentState(TypedDict):
 
246
  agent_type: str
247
  final_answer: str
248
  perf: Dict[str, Any]
 
249
  tools_used: List[str]
250
  reasoning: str
251
+ model_used: str
252
 
253
+ # ---- Open-Source Multi-LLM System ----
254
+ class OpenSourceMultiLLMSystem:
255
  """
256
+ Multi-LLM system using only open-source and free models
257
  """
258
 
259
+ def __init__(self):
260
+ self.model_manager = OpenSourceModelManager()
261
  self.tools = [
262
  multiply, add, subtract, divide, modulus,
263
  optimized_web_search, optimized_wiki_search
264
  ]
 
 
 
 
265
  self.graph = self._build_graph()
266
+
 
 
 
 
 
 
 
 
267
  def _build_graph(self) -> StateGraph:
268
+ """Build the LangGraph state machine with open-source models."""
269
+
 
 
 
 
270
  def router(st: EnhancedAgentState) -> EnhancedAgentState:
271
+ """Route queries to appropriate model based on complexity and content analysis."""
272
  q = st["query"].lower()
273
 
274
  # Enhanced routing logic
275
  if any(keyword in q for keyword in ["calculate", "compute", "math", "multiply", "add", "subtract", "divide"]):
276
+ model_type = "reasoning"
277
+ agent_type = "math"
278
  elif any(keyword in q for keyword in ["search", "find", "lookup", "wikipedia", "information about"]):
279
+ model_type = "fast"
280
+ agent_type = "search_enhanced"
281
+ elif any(keyword in q for keyword in ["code", "programming", "function", "algorithm"]):
282
+ model_type = "coding"
283
+ agent_type = "coding"
284
  elif len(q.split()) > 20: # Complex queries
285
+ model_type = "reasoning"
286
+ agent_type = "complex"
287
  else:
288
+ model_type = "fast"
289
+ agent_type = "simple"
 
 
290
 
291
+ # Get the best model for this task
292
+ selected_model = self.model_manager.get_best_model_for_task(model_type)
293
+ model_name = "unknown"
294
+ for name, model in self.model_manager.available_models.items():
295
+ if model == selected_model:
296
+ model_name = name
297
+ break
298
+
299
+ return {**st, "agent_type": agent_type, "tools_used": [], "reasoning": "", "model_used": model_name}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
300
 
301
+ def math_node(st: EnhancedAgentState) -> EnhancedAgentState:
302
+ """Process mathematical queries."""
303
+ return self._process_with_model(st, "reasoning", "Mathematical calculation using open-source model")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
 
305
  def search_enhanced_node(st: EnhancedAgentState) -> EnhancedAgentState:
306
  """Process query with search enhancement."""
 
319
  search_results = optimized_web_search.invoke({"query": query})
320
  tools_used.append("web_search")
321
 
 
 
 
 
 
 
 
322
  enhanced_query = f"""
323
  Original Question: {query}
324
 
325
  Search Results:
326
  {search_results}
 
327
 
328
+ Based on the search results above, provide a direct answer to the original question.
329
  """
330
 
331
+ # Use fast model for search-enhanced queries
332
+ model = self.model_manager.get_best_model_for_task("fast")
333
+ if model:
334
+ sys = SystemMessage(content=ENHANCED_SYSTEM_PROMPT)
335
+ res = model.invoke([sys, HumanMessage(content=enhanced_query)])
336
+
337
+ answer = res.content.strip() if hasattr(res, 'content') else str(res).strip()
338
+ if "FINAL ANSWER:" in answer:
339
+ answer = answer.split("FINAL ANSWER:")[-1].strip()
340
+
341
+ return {**st,
342
+ "final_answer": answer,
343
+ "tools_used": tools_used,
344
+ "reasoning": "Used search enhancement with open-source model",
345
+ "perf": {"time": time.time() - t0, "prov": "Search-Enhanced"}}
346
+ else:
347
+ return {**st, "final_answer": "No models available", "perf": {"error": "No models"}}
348
  except Exception as e:
349
  return {**st, "final_answer": f"Error: {e}", "perf": {"error": str(e)}}
350
 
351
+ def coding_node(st: EnhancedAgentState) -> EnhancedAgentState:
352
+ """Process coding-related queries."""
353
+ return self._process_with_model(st, "coding", "Code generation using open-source model")
354
+
355
+ def complex_node(st: EnhancedAgentState) -> EnhancedAgentState:
356
+ """Process complex queries."""
357
+ return self._process_with_model(st, "reasoning", "Complex reasoning using open-source model")
358
+
359
+ def simple_node(st: EnhancedAgentState) -> EnhancedAgentState:
360
+ """Process simple queries."""
361
+ return self._process_with_model(st, "fast", "Simple query using fast open-source model")
362
+
363
  # Build graph
364
  g = StateGraph(EnhancedAgentState)
365
  g.add_node("router", router)
366
+ g.add_node("math", math_node)
 
 
367
  g.add_node("search_enhanced", search_enhanced_node)
368
+ g.add_node("coding", coding_node)
369
+ g.add_node("complex", complex_node)
370
+ g.add_node("simple", simple_node)
371
 
372
  g.set_entry_point("router")
373
  g.add_conditional_edges("router", lambda s: s["agent_type"], {
374
+ "math": "math",
375
+ "search_enhanced": "search_enhanced",
376
+ "coding": "coding",
377
+ "complex": "complex",
378
+ "simple": "simple"
379
  })
380
 
381
+ for node in ["math", "search_enhanced", "coding", "complex", "simple"]:
382
  g.add_edge(node, END)
383
 
384
  return g.compile(checkpointer=MemorySaver())
385
+
386
+ def _process_with_model(self, st: EnhancedAgentState, model_type: str, reasoning: str) -> EnhancedAgentState:
387
+ """Process query with specified model type"""
388
+ t0 = time.time()
389
+ try:
390
+ model = self.model_manager.get_best_model_for_task(model_type)
391
+ if not model:
392
+ return {**st, "final_answer": "No suitable model available", "perf": {"error": "No model"}}
393
+
394
+ enhanced_query = f"""
395
+ Question: {st["query"]}
396
+
397
+ Please provide a direct, accurate answer to this question.
398
+ """
399
+
400
+ sys = SystemMessage(content=ENHANCED_SYSTEM_PROMPT)
401
+ res = model.invoke([sys, HumanMessage(content=enhanced_query)])
402
+
403
+ answer = res.content.strip() if hasattr(res, 'content') else str(res).strip()
404
+ if "FINAL ANSWER:" in answer:
405
+ answer = answer.split("FINAL ANSWER:")[-1].strip()
406
+
407
+ return {**st,
408
+ "final_answer": answer,
409
+ "reasoning": reasoning,
410
+ "perf": {"time": time.time() - t0, "prov": f"OpenSource-{model_type}"}}
411
+ except Exception as e:
412
+ return {**st, "final_answer": f"Error: {e}", "perf": {"error": str(e)}}
413
 
414
  def process_query(self, q: str) -> str:
415
+ """Process a query through the open-source multi-LLM system."""
416
  state = {
417
  "messages": [HumanMessage(content=q)],
418
  "query": q,
419
  "agent_type": "",
420
  "final_answer": "",
421
  "perf": {},
 
422
  "tools_used": [],
423
  "reasoning": "",
424
+ "model_used": ""
425
  }
426
+ cfg = {"configurable": {"thread_id": f"opensource_qa_{hash(q)}"}}
427
 
428
  try:
429
  out = self.graph.invoke(state, cfg)
 
437
  except Exception as e:
438
  return f"Error processing query: {e}"
439
 
440
+ def get_system_info(self) -> Dict[str, Any]:
441
+ """Get information about available open-source models"""
442
+ return {
443
+ "available_models": self.model_manager.list_available_models(),
444
+ "total_models": len(self.model_manager.available_models),
445
+ "model_types": {
446
+ "groq_free_tier": [m for m in self.model_manager.list_available_models() if m.startswith("groq_")],
447
+ "ollama_local": [m for m in self.model_manager.list_available_models() if m.startswith("ollama_")],
448
+ "huggingface_local": [m for m in self.model_manager.list_available_models() if m.startswith("hf_")]
449
+ }
450
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
451
 
452
+ # ---- Build Graph Function (for compatibility) ----
453
+ def build_graph(provider: str = "opensource"):
454
+ """Build graph using only open-source models"""
455
+ return OpenSourceMultiLLMSystem().graph
456
 
457
+ # ---- Main execution ----
458
  if __name__ == "__main__":
459
+ # Initialize the open-source system
460
+ system = OpenSourceMultiLLMSystem()
461
 
462
+ # Print system information
463
+ info = system.get_system_info()
464
+ print("Open-Source System Information:")
465
+ print(f"Total Models Available: {info['total_models']}")
466
+ for category, models in info['model_types'].items():
467
+ if models:
468
+ print(f" {category}: {models}")
469
 
470
  # Test queries
471
  test_questions = [
 
472
  "What is 25 multiplied by 17?",
473
+ "Find information about Mercedes Sosa albums between 2000-2009",
474
+ "Write a simple Python function to calculate factorial",
475
+ "Explain quantum computing in simple terms",
476
+ "What is the capital of France?"
477
  ]
478
 
479
+ print("\n" + "="*60)
480
+ print("Testing Open-Source Multi-LLM System")
481
+ print("="*60)
482
+
483
+ for i, question in enumerate(test_questions, 1):
484
+ print(f"\nQuestion {i}: {question}")
485
+ print("-" * 50)
486
  answer = system.process_query(question)
487
  print(f"Answer: {answer}")