LamiaYT commited on
Commit
89373ab
·
1 Parent(s): 5c13656
Files changed (3) hide show
  1. agent.py +97 -399
  2. app.py +44 -175
  3. requirements.txt +4 -13
agent.py CHANGED
@@ -1,474 +1,172 @@
1
  import os
 
2
  from dotenv import load_dotenv
3
 
4
- # Load environment variables
5
  load_dotenv()
6
-
7
- # Set protobuf implementation to avoid C++ extension issues
8
  os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python"
9
 
10
- # Load keys from environment
11
  hf_token = os.getenv("HUGGINGFACE_INFERENCE_TOKEN")
12
- serper_api_key = os.getenv("SERPER_API_KEY")
13
 
14
  # ---- Imports ----
15
  from langgraph.graph import START, StateGraph, MessagesState
16
  from langgraph.prebuilt import tools_condition, ToolNode
17
- from langchain_huggingface import HuggingFaceEmbeddings
 
18
  from langchain_community.tools.tavily_search import TavilySearchResults
19
  from langchain_community.document_loaders import WikipediaLoader, ArxivLoader
20
- try:
21
- from langchain_community.vectorstores import Chroma
22
- except ImportError:
23
- from langchain.vectorstores import Chroma
24
- from langchain_core.documents import Document
25
- from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
26
  from langchain_core.tools import tool
27
- from langchain_core.language_models.base import BaseLanguageModel
28
- from langchain.tools.retriever import create_retriever_tool
29
- try:
30
- from langchain.embeddings import HuggingFaceEmbeddings as LegacyHFEmbeddings
31
- except ImportError:
32
- LegacyHFEmbeddings = HuggingFaceEmbeddings
33
- from langchain.schema import Document as LegacyDocument
34
- import json
35
- import requests
36
- from typing import List, Dict, Any
37
- import re
38
- import math
39
- from datetime import datetime
40
 
41
- # Custom HuggingFace LLM wrapper with better error handling
42
- class SimpleHuggingFaceLLM(BaseLanguageModel):
43
- def __init__(self, repo_id: str, hf_token: str):
44
- super().__init__()
45
- self.repo_id = repo_id
46
- self.hf_token = hf_token
47
- self.api_url = f"https://api-inference.huggingface.co/models/{repo_id}"
48
- self.headers = {"Authorization": f"Bearer {hf_token}"}
49
-
50
- # Test the connection
51
- self._test_connection()
52
-
53
- def _test_connection(self):
54
- """Test if the model is accessible"""
55
- payload = {
56
- "inputs": "Hello",
57
- "parameters": {
58
- "max_new_tokens": 10,
59
- "temperature": 0.1,
60
- "return_full_text": False
61
- }
62
- }
63
-
64
- try:
65
- response = requests.post(self.api_url, headers=self.headers, json=payload, timeout=30)
66
- if response.status_code != 200:
67
- print(f"Model {self.repo_id} test failed with status {response.status_code}: {response.text}")
68
- raise Exception(f"Model not accessible: {response.status_code}")
69
- print(f"Model {self.repo_id} test successful")
70
- except Exception as e:
71
- print(f"Model {self.repo_id} connection test failed: {e}")
72
- raise e
73
-
74
- def _generate(self, messages, stop=None, run_manager=None, **kwargs):
75
- # Convert messages to a single prompt
76
- if isinstance(messages, list):
77
- prompt = messages[-1].content if messages else ""
78
- else:
79
- prompt = str(messages)
80
-
81
- payload = {
82
- "inputs": prompt,
83
- "parameters": {
84
- "max_new_tokens": 512,
85
- "temperature": 0.1,
86
- "return_full_text": False,
87
- "do_sample": False
88
- }
89
- }
90
-
91
- try:
92
- response = requests.post(self.api_url, headers=self.headers, json=payload, timeout=60)
93
- if response.status_code == 200:
94
- result = response.json()
95
- if isinstance(result, list) and len(result) > 0:
96
- generated_text = result[0].get('generated_text', '')
97
- elif isinstance(result, dict):
98
- generated_text = result.get('generated_text', str(result))
99
- else:
100
- generated_text = str(result)
101
-
102
- from langchain_core.outputs import LLMResult, Generation
103
- return LLMResult(generations=[[Generation(text=generated_text)]])
104
- else:
105
- error_msg = f"API Error {response.status_code}: {response.text[:200]}"
106
- print(error_msg)
107
- from langchain_core.outputs import LLMResult, Generation
108
- return LLMResult(generations=[[Generation(text=f"Error: {error_msg}")]])
109
- except Exception as e:
110
- error_msg = f"Request failed: {str(e)}"
111
- print(error_msg)
112
- from langchain_core.outputs import LLMResult, Generation
113
- return LLMResult(generations=[[Generation(text=error_msg)]])
114
-
115
- def invoke(self, input, config=None, **kwargs):
116
- if isinstance(input, list):
117
- prompt = input[-1].content if input else ""
118
- else:
119
- prompt = str(input)
120
-
121
- result = self._generate(prompt)
122
- generated_text = result.generations[0][0].text
123
- return AIMessage(content=generated_text)
124
-
125
- @property
126
- def _llm_type(self):
127
- return "huggingface_custom"
128
-
129
- def _call(self, prompt: str, stop=None, run_manager=None, **kwargs):
130
- """Legacy method for compatibility"""
131
- result = self._generate(prompt)
132
- return result.generations[0][0].text
133
-
134
- # ---- Enhanced Tools ----
135
 
136
  @tool
137
- def multiply(a: float, b: float) -> float:
138
- """Multiply two numbers"""
139
  return a * b
140
 
141
  @tool
142
- def add(a: float, b: float) -> float:
143
- """Add two numbers"""
144
  return a + b
145
 
146
  @tool
147
- def subtract(a: float, b: float) -> float:
148
- """Subtract two numbers"""
149
  return a - b
150
 
151
  @tool
152
- def divide(a: float, b: float) -> float:
153
- """Divide two numbers"""
154
  if b == 0:
155
  raise ValueError("Cannot divide by zero.")
156
  return a / b
157
 
158
  @tool
159
  def modulus(a: int, b: int) -> int:
160
- """Calculate modulus of two integers"""
161
  return a % b
162
 
163
- @tool
164
- def power(a: float, b: float) -> float:
165
- """Calculate a raised to the power of b"""
166
- return a ** b
167
-
168
- @tool
169
- def square_root(a: float) -> float:
170
- """Calculate square root of a number"""
171
- return math.sqrt(a)
172
-
173
- @tool
174
- def factorial(n: int) -> int:
175
- """Calculate factorial of a number"""
176
- if n < 0:
177
- raise ValueError("Factorial is not defined for negative numbers")
178
- if n == 0 or n == 1:
179
- return 1
180
- result = 1
181
- for i in range(2, n + 1):
182
- result *= i
183
- return result
184
-
185
- @tool
186
- def gcd(a: int, b: int) -> int:
187
- """Calculate greatest common divisor"""
188
- while b:
189
- a, b = b, a % b
190
- return a
191
-
192
- @tool
193
- def lcm(a: int, b: int) -> int:
194
- """Calculate least common multiple"""
195
- return abs(a * b) // gcd(a, b)
196
-
197
- @tool
198
- def percentage(part: float, whole: float) -> float:
199
- """Calculate percentage"""
200
- return (part / whole) * 100
201
-
202
- @tool
203
- def compound_interest(principal: float, rate: float, time: float, n: int = 1) -> float:
204
- """Calculate compound interest"""
205
- return principal * (1 + rate/n) ** (n * time)
206
-
207
- @tool
208
- def calculate_average(numbers: str) -> float:
209
- """Calculate average of comma-separated numbers"""
210
- try:
211
- nums = [float(x.strip()) for x in numbers.split(',')]
212
- return sum(nums) / len(nums)
213
- except:
214
- return 0.0
215
-
216
  @tool
217
  def wiki_search(query: str) -> str:
218
- """Search Wikipedia for information"""
219
- try:
220
- search_docs = WikipediaLoader(query=query, load_max_docs=2).load()
221
- if not search_docs:
222
- return "No Wikipedia results found."
223
-
224
- formatted = "\n\n---\n\n".join([
225
- f'Wikipedia: {doc.metadata.get("title", "Unknown")}\n{doc.page_content[:1500]}'
226
  for doc in search_docs
227
- ])
228
- return formatted
229
- except Exception as e:
230
- return f"Wikipedia search error: {str(e)}"
231
 
232
  @tool
233
  def web_search(query: str) -> str:
234
- """Search the web using Tavily"""
235
- try:
236
- search_docs = TavilySearchResults(max_results=2).invoke(query=query)
237
- if not search_docs:
238
- return "No web search results found."
239
-
240
- formatted = "\n\n---\n\n".join([
241
- f'Web: {doc.get("title", "Unknown")}\n{doc.get("content", "")[:1500]}'
242
  for doc in search_docs
243
- ])
244
- return formatted
245
- except Exception as e:
246
- return f"Web search error: {str(e)}"
247
 
248
  @tool
249
- def simple_calculation(expression: str) -> str:
250
- """Safely evaluate simple mathematical expressions"""
251
- try:
252
- # Remove any non-mathematical characters for safety
253
- safe_chars = set('0123456789+-*/.() ')
254
- if not all(c in safe_chars for c in expression):
255
- return "Invalid characters in expression"
256
-
257
- # Evaluate the expression
258
- result = eval(expression)
259
- return str(result)
260
- except Exception as e:
261
- return f"Calculation error: {str(e)}"
262
-
263
- # ---- Embedding & Vector Store Setup with better error handling ----
264
- def setup_vector_store():
265
- try:
266
- # Try different embedding models
267
- embedding_models = [
268
- "sentence-transformers/all-MiniLM-L6-v2",
269
- "sentence-transformers/all-mpnet-base-v2"
270
  ]
271
-
272
- embeddings = None
273
- for model_name in embedding_models:
274
- try:
275
- embeddings = HuggingFaceEmbeddings(model_name=model_name)
276
- print(f"Successfully loaded embeddings: {model_name}")
277
- break
278
- except Exception as e:
279
- print(f"Failed to load embeddings {model_name}: {e}")
280
- continue
281
-
282
- if embeddings is None:
283
- print("Could not load any embedding model, skipping vector store setup")
284
- return None
285
-
286
- # Check if metadata.jsonl exists and load it
287
- if os.path.exists('metadata.jsonl'):
288
- json_QA = []
289
- with open('metadata.jsonl', 'r') as jsonl_file:
290
- for line in jsonl_file:
291
- if line.strip():
292
- try:
293
- json_QA.append(json.loads(line))
294
- except:
295
- continue
296
-
297
- if json_QA:
298
- documents = []
299
- for sample in json_QA:
300
- if sample.get('Question') and sample.get('Final answer'):
301
- doc = Document(
302
- page_content=f"Question: {sample['Question']}\n\nAnswer: {sample['Final answer']}",
303
- metadata={"source": sample.get("task_id", "unknown")}
304
- )
305
- documents.append(doc)
306
-
307
- if documents:
308
- try:
309
- vector_store = Chroma.from_documents(
310
- documents=documents,
311
- embedding=embeddings,
312
- persist_directory="./chroma_db",
313
- collection_name="my_collection"
314
- )
315
- vector_store.persist()
316
- print(f"Vector store created with {len(documents)} documents")
317
- return vector_store
318
- except Exception as e:
319
- print(f"Error creating vector store with documents: {e}")
320
-
321
- # Create empty vector store if no data
322
- try:
323
- vector_store = Chroma(
324
- embedding_function=embeddings,
325
- persist_directory="./chroma_db",
326
- collection_name="my_collection"
327
- )
328
- print("Empty vector store created")
329
- return vector_store
330
- except Exception as e:
331
- print(f"Error creating empty vector store: {e}")
332
- return None
333
-
334
- except Exception as e:
335
- print(f"Vector store setup error: {e}")
336
- return None
337
 
338
- # Try to setup vector store, but don't fail if it doesn't work
339
- vector_store = setup_vector_store()
340
 
341
- @tool
342
- def similar_question_search(query: str) -> str:
343
- """Search for similar questions in the knowledge base"""
344
- if not vector_store:
345
- return "No similar questions available"
346
-
347
- try:
348
- matched_docs = vector_store.similarity_search(query, k=2)
349
- if not matched_docs:
350
- return "No similar questions found"
351
-
352
- formatted = "\n\n".join([
353
- f'Similar Q&A:\n{doc.page_content[:800]}'
354
- for doc in matched_docs
355
- ])
356
- return formatted
357
- except Exception as e:
358
- return f"Similar question search error: {str(e)}"
359
 
360
- # ---- Enhanced System Prompt ----
361
- system_prompt = """
362
- You are an expert assistant that can solve various types of questions using available tools.
 
363
 
364
- Available tools:
365
- - Math: add, subtract, multiply, divide, modulus, power, square_root, factorial, gcd, lcm, percentage, compound_interest, calculate_average, simple_calculation
366
- - Search: wiki_search, web_search, similar_question_search
 
 
 
 
367
 
368
- Instructions:
369
- 1. Read the question carefully
370
- 2. Break down complex problems into steps
371
- 3. Use appropriate tools to gather information or perform calculations
372
- 4. Think step by step and show your reasoning
373
- 5. Provide accurate, concise answers
 
 
374
 
375
- IMPORTANT: Always end your response with:
376
- FINAL ANSWER: [your answer here]
 
 
 
 
 
 
 
 
377
 
378
- For the final answer:
379
- - Numbers: Use plain digits (no commas, units, or symbols unless requested)
380
- - Text: Use exact names without articles
381
- - Lists: Comma-separated values
382
 
383
- Think carefully and use tools when needed.
 
 
 
 
384
  """
385
 
386
  sys_msg = SystemMessage(content=system_prompt)
387
 
388
  # ---- Tool List ----
 
389
  tools = [
390
- # Math tools
391
- multiply, add, subtract, divide, modulus, power, square_root,
392
- factorial, gcd, lcm, percentage, compound_interest, calculate_average, simple_calculation,
393
- # Search tools
394
- wiki_search, web_search, similar_question_search
395
  ]
396
 
397
- # ---- Graph Definition with better error handling ----
 
398
  def build_graph(provider: str = "huggingface"):
399
- """Build the agent graph with custom HuggingFace integration"""
400
-
401
  if provider == "huggingface":
402
- if not hf_token:
403
- raise ValueError("HUGGINGFACE_INFERENCE_TOKEN is required but not found in environment variables")
404
-
405
- # Use custom HuggingFace LLM with better model selection
406
- models_to_try = [
407
- "microsoft/DialoGPT-medium",
408
- "google/flan-t5-base",
409
- "facebook/blenderbot-400M-distill",
410
- "microsoft/DialoGPT-small"
411
- ]
412
-
413
- llm = None
414
- for model_id in models_to_try:
415
- try:
416
- print(f"Trying to initialize model: {model_id}")
417
- llm = SimpleHuggingFaceLLM(repo_id=model_id, hf_token=hf_token)
418
- print(f"Successfully initialized model: {model_id}")
419
- break
420
- except Exception as e:
421
- print(f"Failed to initialize {model_id}: {e}")
422
- continue
423
-
424
- if llm is None:
425
- raise ValueError("Failed to initialize any HuggingFace model. Please check your HUGGINGFACE_INFERENCE_TOKEN and internet connection.")
426
  else:
427
- raise ValueError("Only 'huggingface' provider is supported")
428
 
429
- # Simple tool binding simulation
430
- def llm_with_tools(messages):
431
- return llm.invoke(messages)
432
 
433
  def assistant(state: MessagesState):
434
- """Assistant node with enhanced error handling"""
435
- try:
436
- messages = state["messages"]
437
- response = llm_with_tools(messages)
438
- return {"messages": [response]}
439
- except Exception as e:
440
- print(f"Assistant error: {e}")
441
- fallback_response = AIMessage(content="I encountered an error processing your request. Let me try a simpler approach.")
442
- return {"messages": [fallback_response]}
443
 
444
  def retriever(state: MessagesState):
445
- """Enhanced retriever with context injection"""
446
- messages = state["messages"]
447
- user_query = messages[-1].content if messages else ""
448
-
449
- context_messages = [sys_msg]
450
-
451
- # Add similar question context if available
452
- if vector_store:
453
- try:
454
- similar = vector_store.similarity_search(user_query, k=1)
455
- if similar:
456
- context_msg = HumanMessage(
457
- content=f"Here's a similar example:\n{similar[0].page_content[:500]}"
458
- )
459
- context_messages.append(context_msg)
460
- except Exception as e:
461
- print(f"Retriever error: {e}")
462
-
463
- return {"messages": context_messages + messages}
464
 
465
- # Build simplified graph
466
  builder = StateGraph(MessagesState)
467
  builder.add_node("retriever", retriever)
468
  builder.add_node("assistant", assistant)
469
-
470
- # Simple linear flow
471
  builder.add_edge(START, "retriever")
472
  builder.add_edge("retriever", "assistant")
 
 
473
 
474
- return builder.compile()
 
1
  import os
2
+ import json
3
  from dotenv import load_dotenv
4
 
5
+ # ---- Environment & Setup ----
6
  load_dotenv()
 
 
7
  os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python"
8
 
 
9
  hf_token = os.getenv("HUGGINGFACE_INFERENCE_TOKEN")
 
10
 
11
  # ---- Imports ----
12
  from langgraph.graph import START, StateGraph, MessagesState
13
  from langgraph.prebuilt import tools_condition, ToolNode
14
+ from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint, HuggingFaceEmbeddings
15
+ from langchain_google_genai import ChatGoogleGenerativeAI
16
  from langchain_community.tools.tavily_search import TavilySearchResults
17
  from langchain_community.document_loaders import WikipediaLoader, ArxivLoader
18
+ from langchain_community.vectorstores import Chroma
19
+ from langchain_core.messages import SystemMessage, HumanMessage
 
 
 
 
20
  from langchain_core.tools import tool
21
+ from langchain.schema import Document
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
+ # ---- Tools ----
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
  @tool
26
+ def multiply(a: int, b: int) -> int:
 
27
  return a * b
28
 
29
  @tool
30
+ def add(a: int, b: int) -> int:
 
31
  return a + b
32
 
33
  @tool
34
+ def subtract(a: int, b: int) -> int:
 
35
  return a - b
36
 
37
  @tool
38
+ def divide(a: int, b: int) -> float:
 
39
  if b == 0:
40
  raise ValueError("Cannot divide by zero.")
41
  return a / b
42
 
43
  @tool
44
  def modulus(a: int, b: int) -> int:
 
45
  return a % b
46
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  @tool
48
  def wiki_search(query: str) -> str:
49
+ search_docs = WikipediaLoader(query=query, load_max_docs=2).load()
50
+ formatted = "\n\n---\n\n".join(
51
+ [
52
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
 
 
 
 
53
  for doc in search_docs
54
+ ]
55
+ )
56
+ return {"wiki_results": formatted}
 
57
 
58
  @tool
59
  def web_search(query: str) -> str:
60
+ search_docs = TavilySearchResults(max_results=3).invoke(query=query)
61
+ formatted = "\n\n---\n\n".join(
62
+ [
63
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
 
 
 
 
64
  for doc in search_docs
65
+ ]
66
+ )
67
+ return {"web_results": formatted}
 
68
 
69
  @tool
70
+ def arvix_search(query: str) -> str:
71
+ search_docs = ArxivLoader(query=query, load_max_docs=3).load()
72
+ formatted = "\n\n---\n\n".join(
73
+ [
74
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content[:1000]}\n</Document>'
75
+ for doc in search_docs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  ]
77
+ )
78
+ return {"arvix_results": formatted}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
+ # ---- Embedding & Vector Store ----
 
81
 
82
+ embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
84
+ json_QA = []
85
+ with open('metadata.jsonl', 'r') as jsonl_file:
86
+ for line in jsonl_file:
87
+ json_QA.append(json.loads(line))
88
 
89
+ documents = [
90
+ Document(
91
+ page_content=f"Question : {sample['Question']}\n\nFinal answer : {sample['Final answer']}",
92
+ metadata={"source": sample["task_id"]}
93
+ )
94
+ for sample in json_QA
95
+ ]
96
 
97
+ vector_store = Chroma.from_documents(
98
+ documents=documents,
99
+ embedding=embeddings,
100
+ persist_directory="./chroma_db",
101
+ collection_name="my_collection"
102
+ )
103
+ vector_store.persist()
104
+ print("Documents inserted:", vector_store._collection.count())
105
 
106
+ @tool
107
+ def similar_question_search(query: str) -> str:
108
+ matched_docs = vector_store.similarity_search(query, 3)
109
+ formatted = "\n\n---\n\n".join(
110
+ [
111
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content[:1000]}\n</Document>'
112
+ for doc in matched_docs
113
+ ]
114
+ )
115
+ return {"similar_questions": formatted}
116
 
117
+ # ---- System Prompt ----
 
 
 
118
 
119
+ system_prompt = """
120
+ You are a helpful assistant tasked with answering questions using a set of tools.
121
+ Now, I will ask you a question. Report your thoughts, and finish your answer with the following template:
122
+ FINAL ANSWER: [YOUR FINAL ANSWER].
123
+ YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings...
124
  """
125
 
126
  sys_msg = SystemMessage(content=system_prompt)
127
 
128
  # ---- Tool List ----
129
+
130
  tools = [
131
+ multiply, add, subtract, divide, modulus,
132
+ wiki_search, web_search, arvix_search, similar_question_search
 
 
 
133
  ]
134
 
135
+ # ---- Graph Construction ----
136
+
137
  def build_graph(provider: str = "huggingface"):
 
 
138
  if provider == "huggingface":
139
+ llm = ChatHuggingFace(
140
+ llm=HuggingFaceEndpoint(
141
+ repo_id="mosaicml/mpt-30b",
142
+ temperature=0,
143
+ huggingfacehub_api_token=hf_token
144
+ )
145
+ )
146
+ elif provider == "google":
147
+ llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  else:
149
+ raise ValueError("Invalid provider: choose 'huggingface' or 'google'.")
150
 
151
+ llm_with_tools = llm.bind_tools(tools)
 
 
152
 
153
  def assistant(state: MessagesState):
154
+ return {"messages": [llm_with_tools.invoke(state["messages"])]}
 
 
 
 
 
 
 
 
155
 
156
  def retriever(state: MessagesState):
157
+ similar = vector_store.similarity_search(state["messages"][0].content)
158
+ if similar:
159
+ example_msg = HumanMessage(content=f"Here is a similar question:\n\n{similar[0].page_content}")
160
+ return {"messages": [sys_msg] + state["messages"] + [example_msg]}
161
+ return {"messages": [sys_msg] + state["messages"]}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
 
 
163
  builder = StateGraph(MessagesState)
164
  builder.add_node("retriever", retriever)
165
  builder.add_node("assistant", assistant)
166
+ builder.add_node("tools", ToolNode(tools))
 
167
  builder.add_edge(START, "retriever")
168
  builder.add_edge("retriever", "assistant")
169
+ builder.add_conditional_edges("assistant", tools_condition)
170
+ builder.add_edge("tools", "assistant")
171
 
172
+ return builder.compile()
app.py CHANGED
@@ -1,97 +1,31 @@
1
-
2
  import os
3
  import gradio as gr
4
  import requests
5
  import inspect
6
  import pandas as pd
7
  from agent import build_graph
8
- from langchain_core.messages import HumanMessage
9
- import time
10
 
 
11
  # --- Constants ---
12
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
13
 
14
- # --- Improved Agent Definition ---
 
15
  class BasicAgent:
16
  def __init__(self):
17
- print("Initializing BasicAgent...")
18
- try:
19
- # Add more verbose logging
20
- print("Building graph...")
21
- self.graph = build_graph()
22
- print("Graph built successfully.")
23
- except Exception as e:
24
- print(f"Error building graph: {e}")
25
- print(f"Error type: {type(e).__name__}")
26
- import traceback
27
- traceback.print_exc()
28
- raise e
29
 
30
  def __call__(self, question: str) -> str:
31
- print(f"Agent received question (first 100 chars): {question[:100]}...")
32
-
33
- try:
34
- # Clean the question
35
- question = question.strip()
36
- if not question:
37
- return "Empty question received"
38
-
39
- # Wrap the question in a HumanMessage
40
- messages = [HumanMessage(content=question)]
41
-
42
- # Invoke the graph with retry mechanism
43
- max_retries = 3
44
- for attempt in range(max_retries):
45
- try:
46
- print(f"Attempt {attempt + 1} to process question...")
47
- result = self.graph.invoke({"messages": messages})
48
-
49
- if 'messages' in result and result['messages']:
50
- answer = result['messages'][-1].content
51
-
52
- # Clean up the answer
53
- if isinstance(answer, str):
54
- # Remove the "FINAL ANSWER: " prefix if it exists
55
- if "FINAL ANSWER:" in answer:
56
- answer = answer.split("FINAL ANSWER:")[-1].strip()
57
-
58
- # Additional cleanup
59
- answer = answer.replace("Assistant: ", "").strip()
60
-
61
- # Handle empty or error responses
62
- if not answer or "Error:" in answer or "error" in answer.lower():
63
- if attempt < max_retries - 1:
64
- print(f"Got error response, retrying: {answer[:100]}")
65
- time.sleep(2)
66
- continue
67
- else:
68
- return "Unable to generate answer"
69
-
70
- print(f"Agent answer (first 100 chars): {answer[:100]}...")
71
- return answer
72
- else:
73
- return str(answer)
74
- else:
75
- print("No messages in result")
76
- if attempt < max_retries - 1:
77
- time.sleep(2)
78
- continue
79
- return "No response generated"
80
-
81
- except Exception as e:
82
- print(f"Attempt {attempt + 1} failed: {e}")
83
- if attempt == max_retries - 1:
84
- return f"Error processing question: {str(e)}"
85
- time.sleep(2) # Brief pause before retry
86
-
87
- except Exception as e:
88
- print(f"Error in agent call: {e}")
89
- import traceback
90
- traceback.print_exc()
91
- return f"Agent error: {str(e)}"
92
 
93
 
94
- def run_and_submit_all(profile: gr.OAuthProfile | None):
95
  """
96
  Fetches all questions, runs the BasicAgent on them, submits all answers,
97
  and displays the results.
@@ -100,7 +34,7 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
100
  space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
101
 
102
  if profile:
103
- username = f"{profile.username}"
104
  print(f"User logged in: {username}")
105
  else:
106
  print("User not logged in.")
@@ -110,25 +44,20 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
110
  questions_url = f"{api_url}/questions"
111
  submit_url = f"{api_url}/submit"
112
 
113
- # 1. Instantiate Agent (modify this part to create your agent)
114
  try:
115
- print("Initializing agent...")
116
  agent = BasicAgent()
117
- print("Agent initialized successfully.")
118
  except Exception as e:
119
  print(f"Error instantiating agent: {e}")
120
- import traceback
121
- traceback.print_exc()
122
  return f"Error initializing agent: {e}", None
123
-
124
- # In the case of an app running as a Hugging Face space, this link points toward your codebase
125
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
126
- print(f"Agent code URL: {agent_code}")
127
 
128
  # 2. Fetch Questions
129
  print(f"Fetching questions from: {questions_url}")
130
  try:
131
- response = requests.get(questions_url, timeout=30)
132
  response.raise_for_status()
133
  questions_data = response.json()
134
  if not questions_data:
@@ -146,95 +75,49 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
146
  print(f"An unexpected error occurred fetching questions: {e}")
147
  return f"An unexpected error occurred fetching questions: {e}", None
148
 
149
- # 3. Run your Agent with better error handling
150
  results_log = []
151
  answers_payload = []
152
  print(f"Running agent on {len(questions_data)} questions...")
153
-
154
- for i, item in enumerate(questions_data):
155
  task_id = item.get("task_id")
156
  question_text = item.get("question")
157
-
158
  if not task_id or question_text is None:
159
  print(f"Skipping item with missing task_id or question: {item}")
160
  continue
161
-
162
- print(f"Processing question {i+1}/{len(questions_data)}: {task_id}")
163
-
164
  try:
165
- # Add timeout and better error handling for individual questions
166
- start_time = time.time()
167
  submitted_answer = agent(question_text)
168
- end_time = time.time()
169
-
170
- print(f"Question {i+1} completed in {end_time - start_time:.2f} seconds")
171
-
172
- # Validate the answer
173
- if not submitted_answer or submitted_answer.strip() == "":
174
- submitted_answer = "No answer generated"
175
-
176
- # Clean up the answer further
177
- submitted_answer = str(submitted_answer).strip()
178
- if submitted_answer.startswith("Error:") or submitted_answer.startswith("Agent error:"):
179
- submitted_answer = "Unable to process question"
180
-
181
- answers_payload.append({
182
- "task_id": task_id,
183
- "submitted_answer": submitted_answer
184
- })
185
-
186
- results_log.append({
187
- "Task ID": task_id,
188
- "Question": question_text[:200] + "..." if len(question_text) > 200 else question_text,
189
- "Submitted Answer": submitted_answer
190
- })
191
-
192
  except Exception as e:
193
  print(f"Error running agent on task {task_id}: {e}")
194
- error_answer = "Processing error occurred"
195
- answers_payload.append({
196
- "task_id": task_id,
197
- "submitted_answer": error_answer
198
- })
199
- results_log.append({
200
- "Task ID": task_id,
201
- "Question": question_text[:200] + "..." if len(question_text) > 200 else question_text,
202
- "Submitted Answer": error_answer
203
- })
204
 
205
  if not answers_payload:
206
  print("Agent did not produce any answers to submit.")
207
  return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
208
 
209
  # 4. Prepare Submission
210
- submission_data = {
211
- "username": username.strip(),
212
- "agent_code": agent_code,
213
- "answers": answers_payload
214
- }
215
  status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
216
  print(status_update)
217
 
218
- # 5. Submit with better error handling
219
  print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
220
  try:
221
- response = requests.post(submit_url, json=submission_data, timeout=120)
222
  response.raise_for_status()
223
  result_data = response.json()
224
-
225
  final_status = (
226
  f"Submission Successful!\n"
227
- f"User: {result_data.get('username', 'Unknown')}\n"
228
  f"Overall Score: {result_data.get('score', 'N/A')}% "
229
  f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
230
  f"Message: {result_data.get('message', 'No message received.')}"
231
  )
232
  print("Submission successful.")
233
- print(f"Score: {result_data.get('score', 'N/A')}%")
234
-
235
  results_df = pd.DataFrame(results_log)
236
  return final_status, results_df
237
-
238
  except requests.exceptions.HTTPError as e:
239
  error_detail = f"Server responded with status {e.response.status_code}."
240
  try:
@@ -246,19 +129,16 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
246
  print(status_message)
247
  results_df = pd.DataFrame(results_log)
248
  return status_message, results_df
249
-
250
  except requests.exceptions.Timeout:
251
  status_message = "Submission Failed: The request timed out."
252
  print(status_message)
253
  results_df = pd.DataFrame(results_log)
254
  return status_message, results_df
255
-
256
  except requests.exceptions.RequestException as e:
257
  status_message = f"Submission Failed: Network error - {e}"
258
  print(status_message)
259
  results_df = pd.DataFrame(results_log)
260
  return status_message, results_df
261
-
262
  except Exception as e:
263
  status_message = f"An unexpected error occurred during submission: {e}"
264
  print(status_message)
@@ -268,31 +148,26 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
268
 
269
  # --- Build Gradio Interface using Blocks ---
270
  with gr.Blocks() as demo:
271
- gr.Markdown("# Enhanced Agent Evaluation Runner")
272
  gr.Markdown(
273
  """
274
  **Instructions:**
275
- 1. Please clone this space, then modify the code to define your agent's logic, tools, and necessary packages.
276
- 2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
277
- 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
278
-
279
- **Improvements in this version:**
280
- - Enhanced mathematical tools (factorial, gcd, lcm, compound interest, etc.)
281
- - Better search tools with error handling
282
- - Improved HuggingFace model integration
283
- - Better answer processing and cleanup
284
- - Enhanced error handling and retry mechanisms
285
-
286
  ---
287
- **Note:** The evaluation process may take some time as the agent processes all questions systematically.
 
 
288
  """
289
  )
290
 
291
  gr.LoginButton()
292
 
293
- run_button = gr.Button("Run Evaluation & Submit All Answers", variant="primary")
294
 
295
  status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
 
296
  results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
297
 
298
  run_button.click(
@@ -301,12 +176,10 @@ with gr.Blocks() as demo:
301
  )
302
 
303
  if __name__ == "__main__":
304
- print("\n" + "-"*30 + " Enhanced App Starting " + "-"*30)
305
-
306
- # Check for environment variables
307
  space_host_startup = os.getenv("SPACE_HOST")
308
- space_id_startup = os.getenv("SPACE_ID")
309
- hf_token = os.getenv("HUGGINGFACE_INFERENCE_TOKEN")
310
 
311
  if space_host_startup:
312
  print(f"✅ SPACE_HOST found: {space_host_startup}")
@@ -314,19 +187,15 @@ if __name__ == "__main__":
314
  else:
315
  print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
316
 
317
- if space_id_startup:
318
  print(f"✅ SPACE_ID found: {space_id_startup}")
319
  print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
320
  print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
321
  else:
322
- print("ℹ️ SPACE_ID environment variable not found (running locally?).")
323
-
324
- if hf_token:
325
- print("✅ HUGGINGFACE_INFERENCE_TOKEN found")
326
- else:
327
- print("⚠️ HUGGINGFACE_INFERENCE_TOKEN not found - this may cause issues")
328
 
329
- print("-"*(60 + len(" Enhanced App Starting ")) + "\n")
330
 
331
- print("Launching Enhanced Gradio Interface for Agent Evaluation...")
332
- demo.launch(debug=True, share=False)
 
 
 
1
  import os
2
  import gradio as gr
3
  import requests
4
  import inspect
5
  import pandas as pd
6
  from agent import build_graph
 
 
7
 
8
+ # (Keep Constants as is)
9
  # --- Constants ---
10
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
11
 
12
+ # --- Basic Agent Definition ---
13
+ # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
14
  class BasicAgent:
15
  def __init__(self):
16
+ print("BasicAgent initialized.")
17
+ self.graph = build_graph()
 
 
 
 
 
 
 
 
 
 
18
 
19
  def __call__(self, question: str) -> str:
20
+ print(f"Agent received question (first 50 chars): {question[:50]}...")
21
+ # Wrap the question in a HumanMessage from langchain_core
22
+ messages = [HumanMessage(content=question)]
23
+ messages = self.graph.invoke({"messages": messages})
24
+ answer = messages['messages'][-1].content
25
+ return answer[14:]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
 
28
+ def run_and_submit_all( profile: gr.OAuthProfile | None):
29
  """
30
  Fetches all questions, runs the BasicAgent on them, submits all answers,
31
  and displays the results.
 
34
  space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
35
 
36
  if profile:
37
+ username= f"{profile.username}"
38
  print(f"User logged in: {username}")
39
  else:
40
  print("User not logged in.")
 
44
  questions_url = f"{api_url}/questions"
45
  submit_url = f"{api_url}/submit"
46
 
47
+ # 1. Instantiate Agent ( modify this part to create your agent)
48
  try:
 
49
  agent = BasicAgent()
 
50
  except Exception as e:
51
  print(f"Error instantiating agent: {e}")
 
 
52
  return f"Error initializing agent: {e}", None
53
+ # In the case of an app running as a hugging Face space, this link points toward your codebase ( usefull for others so please keep it public)
 
54
  agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
55
+ print(agent_code)
56
 
57
  # 2. Fetch Questions
58
  print(f"Fetching questions from: {questions_url}")
59
  try:
60
+ response = requests.get(questions_url, timeout=15)
61
  response.raise_for_status()
62
  questions_data = response.json()
63
  if not questions_data:
 
75
  print(f"An unexpected error occurred fetching questions: {e}")
76
  return f"An unexpected error occurred fetching questions: {e}", None
77
 
78
+ # 3. Run your Agent
79
  results_log = []
80
  answers_payload = []
81
  print(f"Running agent on {len(questions_data)} questions...")
82
+ for item in questions_data:
 
83
  task_id = item.get("task_id")
84
  question_text = item.get("question")
 
85
  if not task_id or question_text is None:
86
  print(f"Skipping item with missing task_id or question: {item}")
87
  continue
 
 
 
88
  try:
 
 
89
  submitted_answer = agent(question_text)
90
+ answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
91
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  except Exception as e:
93
  print(f"Error running agent on task {task_id}: {e}")
94
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
 
 
 
 
 
 
 
 
 
95
 
96
  if not answers_payload:
97
  print("Agent did not produce any answers to submit.")
98
  return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
99
 
100
  # 4. Prepare Submission
101
+ submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
 
 
 
 
102
  status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
103
  print(status_update)
104
 
105
+ # 5. Submit
106
  print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
107
  try:
108
+ response = requests.post(submit_url, json=submission_data, timeout=60)
109
  response.raise_for_status()
110
  result_data = response.json()
 
111
  final_status = (
112
  f"Submission Successful!\n"
113
+ f"User: {result_data.get('username')}\n"
114
  f"Overall Score: {result_data.get('score', 'N/A')}% "
115
  f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
116
  f"Message: {result_data.get('message', 'No message received.')}"
117
  )
118
  print("Submission successful.")
 
 
119
  results_df = pd.DataFrame(results_log)
120
  return final_status, results_df
 
121
  except requests.exceptions.HTTPError as e:
122
  error_detail = f"Server responded with status {e.response.status_code}."
123
  try:
 
129
  print(status_message)
130
  results_df = pd.DataFrame(results_log)
131
  return status_message, results_df
 
132
  except requests.exceptions.Timeout:
133
  status_message = "Submission Failed: The request timed out."
134
  print(status_message)
135
  results_df = pd.DataFrame(results_log)
136
  return status_message, results_df
 
137
  except requests.exceptions.RequestException as e:
138
  status_message = f"Submission Failed: Network error - {e}"
139
  print(status_message)
140
  results_df = pd.DataFrame(results_log)
141
  return status_message, results_df
 
142
  except Exception as e:
143
  status_message = f"An unexpected error occurred during submission: {e}"
144
  print(status_message)
 
148
 
149
  # --- Build Gradio Interface using Blocks ---
150
  with gr.Blocks() as demo:
151
+ gr.Markdown("# Basic Agent Evaluation Runner")
152
  gr.Markdown(
153
  """
154
  **Instructions:**
155
+ 1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
156
+ 2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
157
+ 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
 
 
 
 
 
 
 
 
158
  ---
159
+ **Disclaimers:**
160
+ Once clicking on the "submit button, it can take quite some time ( this is the time for the agent to go through all the questions).
161
+ This space provides a basic setup and is intentionally sub-optimal to encourage you to develop your own, more robust solution. For instance for the delay process of the submit button, a solution could be to cache the answers and submit in a seperate action or even to answer the questions in async.
162
  """
163
  )
164
 
165
  gr.LoginButton()
166
 
167
+ run_button = gr.Button("Run Evaluation & Submit All Answers")
168
 
169
  status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
170
+ # Removed max_rows=10 from DataFrame constructor
171
  results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
172
 
173
  run_button.click(
 
176
  )
177
 
178
  if __name__ == "__main__":
179
+ print("\n" + "-"*30 + " App Starting " + "-"*30)
180
+ # Check for SPACE_HOST and SPACE_ID at startup for information
 
181
  space_host_startup = os.getenv("SPACE_HOST")
182
+ space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
 
183
 
184
  if space_host_startup:
185
  print(f"✅ SPACE_HOST found: {space_host_startup}")
 
187
  else:
188
  print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
189
 
190
+ if space_id_startup: # Print repo URLs if SPACE_ID is found
191
  print(f"✅ SPACE_ID found: {space_id_startup}")
192
  print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
193
  print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
194
  else:
195
+ print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
 
 
 
 
 
196
 
197
+ print("-"*(60 + len(" App Starting ")) + "\n")
198
 
199
+ print("Launching Gradio Interface for Basic Agent Evaluation...")
200
+ demo.launch(debug=True, share=False)
201
+
requirements.txt CHANGED
@@ -3,27 +3,18 @@ requests
3
  langchain
4
  langchain-community
5
  langchain-core
 
6
  langchain-huggingface
7
- langchain-chroma
8
  langchain-tavily
 
9
  langgraph
10
  sentence-transformers
11
  huggingface_hub
12
- transformers
13
- torch
14
  supabase
15
  arxiv
16
  pymupdf
17
  wikipedia
18
  pgvector
19
  python-dotenv
20
- protobuf==3.20.3
21
- chromadb
22
- tiktoken
23
- numpy
24
- pandas
25
- scipy
26
- sympy
27
- python-dateutil
28
- beautifulsoup4
29
- lxml
 
3
  langchain
4
  langchain-community
5
  langchain-core
6
+ langchain-google-genai
7
  langchain-huggingface
8
+ langchain-groq
9
  langchain-tavily
10
+ langchain-chroma
11
  langgraph
12
  sentence-transformers
13
  huggingface_hub
 
 
14
  supabase
15
  arxiv
16
  pymupdf
17
  wikipedia
18
  pgvector
19
  python-dotenv
20
+ protobuf==3.20.3