i-dhilip commited on
Commit
0230184
·
verified ·
1 Parent(s): ed922af

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +466 -284
app.py CHANGED
@@ -1,30 +1,31 @@
1
  """LangGraph Agent with Gradio Interface"""
2
  import os
3
- import base64
4
- import gradio as gr
5
- import requests
6
- import pandas as pd
7
  from dotenv import load_dotenv
8
- import google.generativeai as genai
9
  from langgraph.graph import START, StateGraph, MessagesState
10
- from langgraph.prebuilt import tools_condition, ToolNode
 
11
  from langchain_google_genai import ChatGoogleGenerativeAI
12
- from langchain_community.tools import DuckDuckGoSearchResults
13
- from langchain_community.document_loaders import WikipediaLoader, ArxivLoader
14
- from langchain_community.vectorstores import Chroma
 
 
 
15
  from langchain_core.messages import SystemMessage, HumanMessage
16
  from langchain_core.tools import tool
17
  from langchain.tools.retriever import create_retriever_tool
18
- from langchain_community.embeddings import HuggingFaceEmbeddings
19
- from langchain_openai import ChatOpenAI
20
- import ast # Added missing import
21
- from typing import Dict, Any # Added missing import
 
22
 
23
  # Load environment variables
24
  load_dotenv()
25
 
26
  # Setup API keys
27
  GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
 
28
  OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
29
 
30
  if GOOGLE_API_KEY:
@@ -32,328 +33,509 @@ if GOOGLE_API_KEY:
32
  else:
33
  print("Warning: GOOGLE_API_KEY not found")
34
 
35
- # Tool Definitions
36
  @tool
37
  def multiply(a: int, b: int) -> int:
38
- """Multiply two numbers."""
 
 
 
 
39
  return a * b
40
 
41
  @tool
42
  def add(a: int, b: int) -> int:
43
- """Add two numbers."""
 
 
 
 
 
44
  return a + b
45
 
46
- @tool
47
- def modulus(a: int, b: int) -> int:
48
- """Get the modulus of two numbers."""
49
- return a % b
50
-
51
  @tool
52
  def subtract(a: int, b: int) -> int:
53
- """Subtract two numbers."""
 
 
 
 
 
54
  return a - b
55
 
56
  @tool
57
  def divide(a: int, b: int) -> int:
58
- """Divide two numbers."""
 
 
 
 
 
59
  if b == 0:
60
  raise ValueError("Cannot divide by zero.")
61
  return a / b
62
 
63
  @tool
64
- def arvix_search(query: str) -> str:
65
- """Search Arxiv for a query and return maximum 3 results."""
66
- try:
67
- search_docs = ArxivLoader(query=query, load_max_docs=3).load()
68
- formatted_search_docs = "\n\n---\n\n".join(
69
- [f'<Document source="{doc.metadata["source"]}"/>\n{doc.page_content[:1000]}\n</Document>'
70
- for doc in search_docs])
71
- return {"arvix_results": formatted_search_docs}
72
- except Exception as e:
73
- return {"arvix_results": f"Error: {str(e)}"}
74
-
75
- @tool
76
- def execute_python(code: str) -> str:
77
- """Execute Python code securely and return results. Handles calculations and data analysis."""
78
- try:
79
- parsed = ast.parse(code)
80
- if any(isinstance(node, (ast.Import, ast.ImportFrom)) for node in parsed.body):
81
- return "Cannot import modules for security reasons"
82
-
83
- restricted_builtins = {'open', 'eval', 'exec', '__import__'}
84
- for node in ast.walk(parsed):
85
- if isinstance(node, ast.Name) and node.id in restricted_builtins:
86
- return "Restricted function used"
87
-
88
- return str(eval(code, {"__builtins__": {}}, {}))
89
- except Exception as e:
90
- return f"Execution error: {str(e)}"
91
-
92
- @tool
93
- def process_file(file_path: str) -> Dict[str, Any]:
94
- """Process uploaded files (Excel, CSV, TXT) and extract structured data."""
95
- try:
96
- if file_path.endswith(('.xlsx', '.xls')):
97
- df = pd.read_excel(file_path)
98
- return {"data": df.head(10).to_dict(), "summary": df.describe().to_dict()}
99
- elif file_path.endswith('.csv'):
100
- df = pd.read_csv(file_path)
101
- return {"data": df.head(10).to_dict(), "summary": df.describe().to_dict()}
102
- elif file_path.endswith('.txt'):
103
- with open(file_path, 'r') as f:
104
- content = f.read(2000)
105
- return {"content": content}
106
- return {"error": "Unsupported file format"}
107
- except Exception as e:
108
- return {"error": str(e)}
109
 
110
  @tool
111
- def enhanced_wiki_search(query: str) -> str:
112
- """Access full Wikipedia articles with history and specific versions."""
113
- try:
114
- loader = WikipediaLoader(query=query, load_max_docs=2, doc_content_chars_max=4000)
115
- docs = loader.load()
116
- return "\n\n".join([
117
- f"Title: {doc.metadata['title']}\n"
118
- f"URL: {doc.metadata['source']}\n"
119
- f"Content: {doc.page_content[:3000]}..."
120
- for doc in docs
121
  ])
122
- except Exception as e:
123
- return f"Wikipedia Error: {str(e)}"
124
 
125
  @tool
126
- def media_search(query: str) -> str:
127
- """Specialized search for media content (videos, images, celebrities)."""
128
- try:
129
- search = DuckDuckGoSearchResults(max_results=5)
130
- results = search.invoke(f"site:imdb.com OR site:youtube.com {query}")
131
- return "\n".join([
132
- f"Source: {res['link']}\nSnippet: {res['snippet']}"
133
- for res in results[:3]
 
 
134
  ])
135
- except Exception as e:
136
- return f"Media Search Error: {str(e)}"
137
 
138
  @tool
139
- def academic_search(query: str) -> str:
140
- """Search academic databases and educational resources."""
141
- try:
142
- arxiv_docs = ArxivLoader(query=query, load_max_docs=2).load()
143
- web_docs = DuckDuckGoSearchResults(max_results=3).invoke(f"filetype:pdf {query}")
144
- return f"Arxiv Results:\n{arxiv_docs[0].page_content[:1000]}\n\nWeb Results:\n{web_docs[0]['snippet']}"
145
- except Exception as e:
146
- return f"Academic Search Error: {str(e)}"
 
 
 
 
147
 
148
 
149
- @tool
150
- def web_search(query: str) -> str:
151
- """Search DuckDuckGo for a query and return maximum 3 results."""
152
- try:
153
- search = DuckDuckGoSearchResults(max_results=3)
154
- search_docs = search.invoke(query)
155
- formatted_search_docs = "\n\n---\n\n".join(
156
- [f'<Document source="{doc["link"]}"/>\n{doc["snippet"]}\n</Document>'
157
- for doc in search_docs])
158
- return {"web_results": formatted_search_docs}
159
- except Exception as e:
160
- return {"web_results": f"Error: {str(e)}"}
161
 
162
- @tool
163
- def summarize_text(text: str) -> str:
164
- """Summarize a long text into key points."""
165
- if not text or len(text) < 100:
166
- return "Text too short to summarize."
167
- return f"Summary of the provided text with key points."
168
 
169
- @tool
170
- def parse_query(query: str) -> dict:
171
- """Parse a complex query into its key components for better search."""
172
- parts = query.split()
173
- return {
174
- "main_topic": parts[0] if parts else "",
175
- "subtopics": parts[1:3] if len(parts) > 1 else [],
176
- "context": " ".join(parts[3:]) if len(parts) > 3 else ""
177
- }
178
-
179
- # System Prompt Setup
180
- system_prompt = """You are a POWERFUL assistant REQUIRED to answer ALL questions using available tools.
181
- STRICT RULES:
182
- 1. NEVER say you can't answer - ALWAYS use tools
183
- 2. Combine information from multiple tools when needed
184
- 3. For calculations, use execute_python
185
- 4. For files, use process_file
186
- 5. For media/celebrities, use media_search
187
- 6. For academic content, use academic_search
188
- 7. ALWAYS format final answer as: FINAL ANSWER: [your answer]
189
-
190
- AVAILABLE TOOLS:
191
- - execute_python: Math/code execution
192
- - process_file: Analyze uploaded files
193
- - enhanced_wiki_search: Full Wikipedia access
194
- - media_search: Videos/images/celebrities
195
- - academic_search: Textbooks/papers
196
- - web_search: General web search
197
- - vector_store: Previous knowledge
198
-
199
- YOU MUST USE THESE TOOLS TO ANSWER ALL QUESTIONS!"""
200
  sys_msg = SystemMessage(content=system_prompt)
201
 
202
- # Vector Store Setup
203
- try:
204
- embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2")
205
- vector_store = Chroma(
206
- collection_name="documents",
207
- embedding_function=embeddings,
208
- persist_directory="./chroma_db"
209
- )
210
- except Exception as e:
211
- print(f"Error initializing vector store: {e}")
212
- vector_store = None
 
 
 
 
 
213
 
214
- # Tool Configuration
215
  tools = [
216
- multiply, add, subtract, divide, modulus,
217
- enhanced_wiki_search, media_search, web_search, arvix_search,
218
- academic_search, summarize_text, parse_query, DuckDuckGoSearchResults(max_results=5)
 
 
 
 
 
219
  ]
220
 
221
- if vector_store:
222
- tools.append(
223
- create_retriever_tool(
224
- vector_store.as_retriever(),
225
- name="Question Search",
226
- description="Retrieves similar questions from vector store"
 
 
 
 
 
 
 
 
227
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
  )
 
229
 
230
- # Model Configuration
231
- MODEL_REGISTRY = {
232
- "gemini-2.0-flash": {
233
- "provider": "google",
234
- "model": "gemini-2.0-flash",
235
- "temperature": 0.2,
236
- "max_tokens": 2048
237
- },
238
- "gemini-1.5-flash": {
239
- "provider": "google",
240
- "model": "gemini-1.5-flash",
241
- "temperature": 0.2,
242
- "max_tokens": 2048
243
- },
244
- "kimi-vl-a3b-thinking": {
245
- "provider": "openrouter",
246
- "model": "moonshotai/kimi-vl-a3b-thinking:free",
247
- "temperature": 0.2,
248
- "max_tokens": 2048
249
- }
250
- }
251
-
252
- def get_llm(model_name: str = "gemini-2.0-flash"):
253
- """Initialize LLM with error handling"""
254
- config = MODEL_REGISTRY.get(model_name, MODEL_REGISTRY["gemini-2.0-flash"])
255
- provider = config.get("provider", "google")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
256
 
257
- try:
258
- if provider == "google":
259
- if not GOOGLE_API_KEY:
260
- print(f"Error initializing {model_name}: GOOGLE_API_KEY not found")
261
- return None
262
- return ChatGoogleGenerativeAI(
263
- model=config["model"],
264
- temperature=config["temperature"],
265
- max_output_tokens=config["max_tokens"],
266
- convert_system_message_to_human=True
267
- )
268
- elif provider == "openrouter":
269
- if not OPENROUTER_API_KEY:
270
- print(f"Error initializing {model_name}: OPENROUTER_API_KEY not found")
271
- return None
272
- return ChatOpenAI(
273
- model=config["model"],
274
- temperature=config["temperature"],
275
- max_tokens=config["max_tokens"],
276
- openai_api_key=OPENROUTER_API_KEY,
277
- openai_api_base="https://openrouter.ai/api/v1",
278
- model_kwargs={
279
- "headers": {
280
- "HTTP-Referer": "https://your-site.com",
281
- "X-Title": "Agent Evaluation"
282
- }
283
- }
284
- )
285
- else:
286
- print(f"Unknown provider {provider} for model {model_name}")
287
- return None
288
- except Exception as e:
289
- print(f"Error initializing {model_name}: {e}")
290
- return None
291
-
292
- # Graph Builder
293
- def build_graph():
294
- """Build LangGraph agent workflow"""
295
- primary_llm = get_llm("gemini-2.0-flash")
296
- fallback_llm = get_llm("gemini-1.5-flash")
297
- kimi_llm = get_llm("kimi-vl-a3b-thinking")
298
 
299
- llms = [llm for llm in [primary_llm, fallback_llm, kimi_llm] if llm is not None]
300
 
301
- if not llms:
302
- raise RuntimeError("Failed to initialize any LLM")
303
 
304
- current_llm_index = 0
305
 
306
- def assistant(state: MessagesState):
307
- nonlocal current_llm_index
308
- for attempt in range(len(llms)):
309
- try:
310
- llm = llms[current_llm_index]
311
- llm_with_tools = llm.bind_tools(tools)
312
 
313
- messages = state["messages"].copy()
314
- if len(messages) > 0 and isinstance(messages[0], HumanMessage):
315
- tool_instruction = HumanMessage(content="Use available tools to answer.")
316
- messages.append(tool_instruction)
317
 
318
- response = llm_with_tools.invoke(messages)
319
- current_llm_index = (current_llm_index + 1) % len(llms)
320
- return {"messages": [response]}
321
- except Exception as e:
322
- print(f"Model {llms[current_llm_index]} failed: {e}")
323
- current_llm_index = (current_llm_index + 1) % len(llms)
324
- if attempt == len(llms) - 1:
325
- error_msg = HumanMessage(content=f"All models failed: {str(e)}")
326
- return {"messages": [error_msg]}
327
-
328
- def retriever(state: MessagesState):
329
- try:
330
- if vector_store:
331
- similar_questions = vector_store.similarity_search(
332
- state["messages"][0].content,
333
- k=1
334
- )
335
- example_content = "Similar question reference: \n\n" + \
336
- (similar_questions[0].page_content if similar_questions
337
- else "No similar questions found")
338
- else:
339
- example_content = "Vector store not available"
340
 
341
- return {"messages": [sys_msg] + state["messages"] + [HumanMessage(content=example_content)]}
342
- except Exception as e:
343
- error_msg = HumanMessage(content=f"Retrieval error: {str(e)}")
344
- return {"messages": [error_msg]}
345
 
346
- builder = StateGraph(MessagesState)
347
- builder.add_node("retriever", retriever)
348
- builder.add_node("assistant", assistant)
349
- builder.add_node("tools", ToolNode(tools))
350
 
351
- builder.add_edge(START, "retriever")
352
- builder.add_edge("retriever", "assistant")
353
- builder.add_conditional_edges("assistant", tools_condition)
354
- builder.add_edge("tools", "assistant")
355
 
356
- return builder.compile()
357
 
358
  # Agent Class
359
  class BasicAgent:
 
1
  """LangGraph Agent with Gradio Interface"""
2
  import os
 
 
 
 
3
  from dotenv import load_dotenv
 
4
  from langgraph.graph import START, StateGraph, MessagesState
5
+ from langgraph.prebuilt import tools_condition
6
+ from langgraph.prebuilt import ToolNode
7
  from langchain_google_genai import ChatGoogleGenerativeAI
8
+ from langchain_groq import ChatGroq
9
+ from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint, HuggingFaceEmbeddings
10
+ from langchain_community.tools.tavily_search import TavilySearchResults
11
+ from langchain_community.document_loaders import WikipediaLoader
12
+ from langchain_community.document_loaders import ArxivLoader
13
+ from langchain_community.vectorstores import SupabaseVectorStore
14
  from langchain_core.messages import SystemMessage, HumanMessage
15
  from langchain_core.tools import tool
16
  from langchain.tools.retriever import create_retriever_tool
17
+ from supabase.client import Client, create_client
18
+ import json
19
+ from typing import Literal
20
+ from typing import TypedDict, Annotated, Sequence
21
+ import operator
22
 
23
  # Load environment variables
24
  load_dotenv()
25
 
26
  # Setup API keys
27
  GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
28
+ HF_API_TOKEN = os.getenv("HF_API_TOKEN")
29
  OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
30
 
31
  if GOOGLE_API_KEY:
 
33
  else:
34
  print("Warning: GOOGLE_API_KEY not found")
35
 
36
+
37
  @tool
38
  def multiply(a: int, b: int) -> int:
39
+ """Multiply two numbers.
40
+ Args:
41
+ a: first int
42
+ b: second int
43
+ """
44
  return a * b
45
 
46
  @tool
47
  def add(a: int, b: int) -> int:
48
+ """Add two numbers.
49
+
50
+ Args:
51
+ a: first int
52
+ b: second int
53
+ """
54
  return a + b
55
 
 
 
 
 
 
56
  @tool
57
  def subtract(a: int, b: int) -> int:
58
+ """Subtract two numbers.
59
+
60
+ Args:
61
+ a: first int
62
+ b: second int
63
+ """
64
  return a - b
65
 
66
  @tool
67
  def divide(a: int, b: int) -> int:
68
+ """Divide two numbers.
69
+
70
+ Args:
71
+ a: first int
72
+ b: second int
73
+ """
74
  if b == 0:
75
  raise ValueError("Cannot divide by zero.")
76
  return a / b
77
 
78
  @tool
79
+ def modulus(a: int, b: int) -> int:
80
+ """Get the modulus of two numbers.
81
+
82
+ Args:
83
+ a: first int
84
+ b: second int
85
+ """
86
+ return a % b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
  @tool
89
+ def wiki_search(query: str) -> str:
90
+ """Search Wikipedia for a query and return maximum 2 results.
91
+
92
+ Args:
93
+ query: The search query."""
94
+ search_docs = WikipediaLoader(query=query, load_max_docs=2).load()
95
+ formatted_search_docs = "\n\n---\n\n".join(
96
+ [
97
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
98
+ for doc in search_docs
99
  ])
100
+ return {"wiki_results": formatted_search_docs}
 
101
 
102
  @tool
103
+ def web_search(query: str) -> str:
104
+ """Search Tavily for a query and return maximum 3 results.
105
+
106
+ Args:
107
+ query: The search query."""
108
+ search_docs = TavilySearchResults(max_results=3).invoke(query=query)
109
+ formatted_search_docs = "\n\n---\n\n".join(
110
+ [
111
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
112
+ for doc in search_docs
113
  ])
114
+ return {"web_results": formatted_search_docs}
 
115
 
116
  @tool
117
+ def arvix_search(query: str) -> str:
118
+ """Search Arxiv for a query and return maximum 3 result.
119
+
120
+ Args:
121
+ query: The search query."""
122
+ search_docs = ArxivLoader(query=query, load_max_docs=3).load()
123
+ formatted_search_docs = "\n\n---\n\n".join(
124
+ [
125
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content[:1000]}\n</Document>'
126
+ for doc in search_docs
127
+ ])
128
+ return {"arvix_results": formatted_search_docs}
129
 
130
 
 
 
 
 
 
 
 
 
 
 
 
 
131
 
132
+ # load the system prompt from the file
133
+ with open("system_prompt.txt", "r", encoding="utf-8") as f:
134
+ system_prompt = f.read()
 
 
 
135
 
136
+ # System message
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  sys_msg = SystemMessage(content=system_prompt)
138
 
139
+ # build a retriever
140
+ embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2") # dim=768
141
+ supabase: Client = create_client(
142
+ os.environ.get("SUPABASE_URL"),
143
+ os.environ.get("SUPABASE_SERVICE_KEY"))
144
+ vector_store = SupabaseVectorStore(
145
+ client=supabase,
146
+ embedding= embeddings,
147
+ table_name="documents",
148
+ query_name="match_documents_langchain",
149
+ )
150
+ create_retriever_tool = create_retriever_tool(
151
+ retriever=vector_store.as_retriever(),
152
+ name="Question Search",
153
+ description="A tool to retrieve similar questions from a vector store.",
154
+ )
155
 
 
156
  tools = [
157
+ multiply,
158
+ add,
159
+ subtract,
160
+ divide,
161
+ modulus,
162
+ wiki_search,
163
+ web_search,
164
+ arvix_search,
165
  ]
166
 
167
+ # Build graph function
168
+ def build_graph(provider: str = "google"):
169
+ """Build the graph"""
170
+ # Load environment variables from .env file
171
+ if provider == "google":
172
+ # Google Gemini
173
+ llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0)
174
+ elif provider == "huggingface":
175
+ # TODO: Add huggingface endpoint
176
+ llm = ChatHuggingFace(
177
+ llm=HuggingFaceEndpoint(
178
+ url="https://api-inference.huggingface.co/models/Meta-DeepLearning/llama-2-7b-chat-hf",
179
+ temperature=0,
180
+ ),
181
  )
182
+ else:
183
+ raise ValueError("Invalid provider. Choose 'google', or 'huggingface'.")
184
+ # Bind tools to LLM
185
+ llm_with_tools = llm.bind_tools(tools)
186
+
187
+ # Node
188
+ def assistant(state: MessagesState):
189
+ """Assistant node"""
190
+ return {"messages": [llm_with_tools.invoke(state["messages"])]}
191
+
192
+ def retriever(state: MessagesState):
193
+ """Retriever node"""
194
+ similar_question = vector_store.similarity_search(state["messages"][0].content)
195
+ example_msg = HumanMessage(
196
+ content=f"Here I provide a similar question and answer for reference: \n\n{similar_question[0].page_content}",
197
+ )
198
+ return {"messages": [sys_msg] + state["messages"] + [example_msg]}
199
+
200
+ builder = StateGraph(MessagesState)
201
+ builder.add_node("retriever", retriever)
202
+ builder.add_node("assistant", assistant)
203
+ builder.add_node("tools", ToolNode(tools))
204
+ builder.add_edge(START, "retriever")
205
+ builder.add_edge("retriever", "assistant")
206
+ builder.add_conditional_edges(
207
+ "assistant",
208
+ tools_condition,
209
  )
210
+ builder.add_edge("tools", "assistant")
211
 
212
+ # Compile graph
213
+ return builder.compile()
214
+
215
+
216
+
217
+ # Tool Definitions
218
+ # @tool
219
+ # def multiply(a: int, b: int) -> int:
220
+ # """Multiply two numbers."""
221
+ # return a * b
222
+
223
+ # @tool
224
+ # def add(a: int, b: int) -> int:
225
+ # """Add two numbers."""
226
+ # return a + b
227
+
228
+ # @tool
229
+ # def modulus(a: int, b: int) -> int:
230
+ # """Get the modulus of two numbers."""
231
+ # return a % b
232
+
233
+ # @tool
234
+ # def subtract(a: int, b: int) -> int:
235
+ # """Subtract two numbers."""
236
+ # return a - b
237
+
238
+ # @tool
239
+ # def divide(a: int, b: int) -> int:
240
+ # """Divide two numbers."""
241
+ # if b == 0:
242
+ # raise ValueError("Cannot divide by zero.")
243
+ # return a / b
244
+
245
+ # @tool
246
+ # def arvix_search(query: str) -> str:
247
+ # """Search Arxiv for a query and return maximum 3 results."""
248
+ # try:
249
+ # search_docs = ArxivLoader(query=query, load_max_docs=3).load()
250
+ # formatted_search_docs = "\n\n---\n\n".join(
251
+ # [f'<Document source="{doc.metadata["source"]}"/>\n{doc.page_content[:1000]}\n</Document>'
252
+ # for doc in search_docs])
253
+ # return {"arvix_results": formatted_search_docs}
254
+ # except Exception as e:
255
+ # return {"arvix_results": f"Error: {str(e)}"}
256
+
257
+ # @tool
258
+ # def execute_python(code: str) -> str:
259
+ # """Execute Python code securely and return results. Handles calculations and data analysis."""
260
+ # try:
261
+ # parsed = ast.parse(code)
262
+ # if any(isinstance(node, (ast.Import, ast.ImportFrom)) for node in parsed.body):
263
+ # return "Cannot import modules for security reasons"
264
+
265
+ # restricted_builtins = {'open', 'eval', 'exec', '__import__'}
266
+ # for node in ast.walk(parsed):
267
+ # if isinstance(node, ast.Name) and node.id in restricted_builtins:
268
+ # return "Restricted function used"
269
+
270
+ # return str(eval(code, {"__builtins__": {}}, {}))
271
+ # except Exception as e:
272
+ # return f"Execution error: {str(e)}"
273
+
274
+ # @tool
275
+ # def process_file(file_path: str) -> Dict[str, Any]:
276
+ # """Process uploaded files (Excel, CSV, TXT) and extract structured data."""
277
+ # try:
278
+ # if file_path.endswith(('.xlsx', '.xls')):
279
+ # df = pd.read_excel(file_path)
280
+ # return {"data": df.head(10).to_dict(), "summary": df.describe().to_dict()}
281
+ # elif file_path.endswith('.csv'):
282
+ # df = pd.read_csv(file_path)
283
+ # return {"data": df.head(10).to_dict(), "summary": df.describe().to_dict()}
284
+ # elif file_path.endswith('.txt'):
285
+ # with open(file_path, 'r') as f:
286
+ # content = f.read(2000)
287
+ # return {"content": content}
288
+ # return {"error": "Unsupported file format"}
289
+ # except Exception as e:
290
+ # return {"error": str(e)}
291
+
292
+ # @tool
293
+ # def enhanced_wiki_search(query: str) -> str:
294
+ # """Access full Wikipedia articles with history and specific versions."""
295
+ # try:
296
+ # loader = WikipediaLoader(query=query, load_max_docs=2, doc_content_chars_max=4000)
297
+ # docs = loader.load()
298
+ # return "\n\n".join([
299
+ # f"Title: {doc.metadata['title']}\n"
300
+ # f"URL: {doc.metadata['source']}\n"
301
+ # f"Content: {doc.page_content[:3000]}..."
302
+ # for doc in docs
303
+ # ])
304
+ # except Exception as e:
305
+ # return f"Wikipedia Error: {str(e)}"
306
+
307
+ # @tool
308
+ # def media_search(query: str) -> str:
309
+ # """Specialized search for media content (videos, images, celebrities)."""
310
+ # try:
311
+ # search = DuckDuckGoSearchResults(max_results=5)
312
+ # results = search.invoke(f"site:imdb.com OR site:youtube.com {query}")
313
+ # return "\n".join([
314
+ # f"Source: {res['link']}\nSnippet: {res['snippet']}"
315
+ # for res in results[:3]
316
+ # ])
317
+ # except Exception as e:
318
+ # return f"Media Search Error: {str(e)}"
319
+
320
+ # @tool
321
+ # def academic_search(query: str) -> str:
322
+ # """Search academic databases and educational resources."""
323
+ # try:
324
+ # arxiv_docs = ArxivLoader(query=query, load_max_docs=2).load()
325
+ # web_docs = DuckDuckGoSearchResults(max_results=3).invoke(f"filetype:pdf {query}")
326
+ # return f"Arxiv Results:\n{arxiv_docs[0].page_content[:1000]}\n\nWeb Results:\n{web_docs[0]['snippet']}"
327
+ # except Exception as e:
328
+ # return f"Academic Search Error: {str(e)}"
329
+
330
+
331
+ # @tool
332
+ # def web_search(query: str) -> str:
333
+ # """Search DuckDuckGo for a query and return maximum 3 results."""
334
+ # try:
335
+ # search = DuckDuckGoSearchResults(max_results=3)
336
+ # search_docs = search.invoke(query)
337
+ # formatted_search_docs = "\n\n---\n\n".join(
338
+ # [f'<Document source="{doc["link"]}"/>\n{doc["snippet"]}\n</Document>'
339
+ # for doc in search_docs])
340
+ # return {"web_results": formatted_search_docs}
341
+ # except Exception as e:
342
+ # return {"web_results": f"Error: {str(e)}"}
343
+
344
+ # @tool
345
+ # def summarize_text(text: str) -> str:
346
+ # """Summarize a long text into key points."""
347
+ # if not text or len(text) < 100:
348
+ # return "Text too short to summarize."
349
+ # return f"Summary of the provided text with key points."
350
+
351
+ # @tool
352
+ # def parse_query(query: str) -> dict:
353
+ # """Parse a complex query into its key components for better search."""
354
+ # parts = query.split()
355
+ # return {
356
+ # "main_topic": parts[0] if parts else "",
357
+ # "subtopics": parts[1:3] if len(parts) > 1 else [],
358
+ # "context": " ".join(parts[3:]) if len(parts) > 3 else ""
359
+ # }
360
+
361
+ # # System Prompt Setup
362
+ # system_prompt = """You are a POWERFUL assistant REQUIRED to answer ALL questions using available tools.
363
+ # STRICT RULES:
364
+ # 1. NEVER say you can't answer - ALWAYS use tools
365
+ # 2. Combine information from multiple tools when needed
366
+ # 3. For calculations, use execute_python
367
+ # 4. For files, use process_file
368
+ # 5. For media/celebrities, use media_search
369
+ # 6. For academic content, use academic_search
370
+ # 7. ALWAYS format final answer as: FINAL ANSWER: [your answer]
371
+
372
+ # AVAILABLE TOOLS:
373
+ # - execute_python: Math/code execution
374
+ # - process_file: Analyze uploaded files
375
+ # - enhanced_wiki_search: Full Wikipedia access
376
+ # - media_search: Videos/images/celebrities
377
+ # - academic_search: Textbooks/papers
378
+ # - web_search: General web search
379
+ # - vector_store: Previous knowledge
380
+
381
+ # YOU MUST USE THESE TOOLS TO ANSWER ALL QUESTIONS!"""
382
+ # sys_msg = SystemMessage(content=system_prompt)
383
+
384
+ # # Vector Store Setup
385
+ # try:
386
+ # embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2")
387
+ # vector_store = Chroma(
388
+ # collection_name="documents",
389
+ # embedding_function=embeddings,
390
+ # persist_directory="./chroma_db"
391
+ # )
392
+ # except Exception as e:
393
+ # print(f"Error initializing vector store: {e}")
394
+ # vector_store = None
395
+
396
+ # # Tool Configuration
397
+ # tools = [
398
+ # multiply, add, subtract, divide, modulus,
399
+ # enhanced_wiki_search, media_search, web_search, arvix_search,
400
+ # academic_search, summarize_text, parse_query, DuckDuckGoSearchResults(max_results=5)
401
+ # ]
402
+
403
+ # if vector_store:
404
+ # tools.append(
405
+ # create_retriever_tool(
406
+ # vector_store.as_retriever(),
407
+ # name="Question Search",
408
+ # description="Retrieves similar questions from vector store"
409
+ # )
410
+ # )
411
+
412
+ # # Model Configuration
413
+ # MODEL_REGISTRY = {
414
+ # "gemini-2.0-flash": {
415
+ # "provider": "google",
416
+ # "model": "gemini-2.0-flash",
417
+ # "temperature": 0.2,
418
+ # "max_tokens": 2048
419
+ # },
420
+ # "gemini-1.5-flash": {
421
+ # "provider": "google",
422
+ # "model": "gemini-1.5-flash",
423
+ # "temperature": 0.2,
424
+ # "max_tokens": 2048
425
+ # },
426
+ # "kimi-vl-a3b-thinking": {
427
+ # "provider": "openrouter",
428
+ # "model": "moonshotai/kimi-vl-a3b-thinking:free",
429
+ # "temperature": 0.2,
430
+ # "max_tokens": 2048
431
+ # }
432
+ # }
433
+
434
+ # def get_llm(model_name: str = "gemini-2.0-flash"):
435
+ # """Initialize LLM with error handling"""
436
+ # config = MODEL_REGISTRY.get(model_name, MODEL_REGISTRY["gemini-2.0-flash"])
437
+ # provider = config.get("provider", "google")
438
 
439
+ # try:
440
+ # if provider == "google":
441
+ # if not GOOGLE_API_KEY:
442
+ # print(f"Error initializing {model_name}: GOOGLE_API_KEY not found")
443
+ # return None
444
+ # return ChatGoogleGenerativeAI(
445
+ # model=config["model"],
446
+ # temperature=config["temperature"],
447
+ # max_output_tokens=config["max_tokens"],
448
+ # convert_system_message_to_human=True
449
+ # )
450
+ # elif provider == "openrouter":
451
+ # if not OPENROUTER_API_KEY:
452
+ # print(f"Error initializing {model_name}: OPENROUTER_API_KEY not found")
453
+ # return None
454
+ # return ChatOpenAI(
455
+ # model=config["model"],
456
+ # temperature=config["temperature"],
457
+ # max_tokens=config["max_tokens"],
458
+ # openai_api_key=OPENROUTER_API_KEY,
459
+ # openai_api_base="https://openrouter.ai/api/v1",
460
+ # model_kwargs={
461
+ # "headers": {
462
+ # "HTTP-Referer": "https://your-site.com",
463
+ # "X-Title": "Agent Evaluation"
464
+ # }
465
+ # }
466
+ # )
467
+ # else:
468
+ # print(f"Unknown provider {provider} for model {model_name}")
469
+ # return None
470
+ # except Exception as e:
471
+ # print(f"Error initializing {model_name}: {e}")
472
+ # return None
473
+
474
+ # # Graph Builder
475
+ # def build_graph():
476
+ # """Build LangGraph agent workflow"""
477
+ # primary_llm = get_llm("gemini-2.0-flash")
478
+ # fallback_llm = get_llm("gemini-1.5-flash")
479
+ # kimi_llm = get_llm("kimi-vl-a3b-thinking")
480
 
481
+ # llms = [llm for llm in [primary_llm, fallback_llm, kimi_llm] if llm is not None]
482
 
483
+ # if not llms:
484
+ # raise RuntimeError("Failed to initialize any LLM")
485
 
486
+ # current_llm_index = 0
487
 
488
+ # def assistant(state: MessagesState):
489
+ # nonlocal current_llm_index
490
+ # for attempt in range(len(llms)):
491
+ # try:
492
+ # llm = llms[current_llm_index]
493
+ # llm_with_tools = llm.bind_tools(tools)
494
 
495
+ # messages = state["messages"].copy()
496
+ # if len(messages) > 0 and isinstance(messages[0], HumanMessage):
497
+ # tool_instruction = HumanMessage(content="Use available tools to answer.")
498
+ # messages.append(tool_instruction)
499
 
500
+ # response = llm_with_tools.invoke(messages)
501
+ # current_llm_index = (current_llm_index + 1) % len(llms)
502
+ # return {"messages": [response]}
503
+ # except Exception as e:
504
+ # print(f"Model {llms[current_llm_index]} failed: {e}")
505
+ # current_llm_index = (current_llm_index + 1) % len(llms)
506
+ # if attempt == len(llms) - 1:
507
+ # error_msg = HumanMessage(content=f"All models failed: {str(e)}")
508
+ # return {"messages": [error_msg]}
509
+
510
+ # def retriever(state: MessagesState):
511
+ # try:
512
+ # if vector_store:
513
+ # similar_questions = vector_store.similarity_search(
514
+ # state["messages"][0].content,
515
+ # k=1
516
+ # )
517
+ # example_content = "Similar question reference: \n\n" + \
518
+ # (similar_questions[0].page_content if similar_questions
519
+ # else "No similar questions found")
520
+ # else:
521
+ # example_content = "Vector store not available"
522
 
523
+ # return {"messages": [sys_msg] + state["messages"] + [HumanMessage(content=example_content)]}
524
+ # except Exception as e:
525
+ # error_msg = HumanMessage(content=f"Retrieval error: {str(e)}")
526
+ # return {"messages": [error_msg]}
527
 
528
+ # builder = StateGraph(MessagesState)
529
+ # builder.add_node("retriever", retriever)
530
+ # builder.add_node("assistant", assistant)
531
+ # builder.add_node("tools", ToolNode(tools))
532
 
533
+ # builder.add_edge(START, "retriever")
534
+ # builder.add_edge("retriever", "assistant")
535
+ # builder.add_conditional_edges("assistant", tools_condition)
536
+ # builder.add_edge("tools", "assistant")
537
 
538
+ # return builder.compile()
539
 
540
  # Agent Class
541
  class BasicAgent: