i-dhilip commited on
Commit
bda2844
·
verified ·
1 Parent(s): 81be80a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +236 -643
app.py CHANGED
@@ -1,674 +1,267 @@
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
- import requests
20
- import google.generativeai as genai
21
- import gradio as gr
22
- from typing import Literal
23
- from typing import TypedDict, Annotated, Sequence
24
- import operator
25
-
26
- # Load environment variables
27
- load_dotenv()
28
-
29
- # Setup API keys
30
- GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
31
- HF_API_TOKEN = os.getenv("HF_API_TOKEN")
32
- OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
33
-
34
- if GOOGLE_API_KEY:
35
- genai.configure(api_key=GOOGLE_API_KEY)
36
- else:
37
- print("Warning: GOOGLE_API_KEY not found")
38
-
39
-
40
- @tool
41
- def multiply(a: int, b: int) -> int:
42
- """Multiply two numbers.
43
- Args:
44
- a: first int
45
- b: second int
46
- """
47
- return a * b
48
-
49
- @tool
50
- def add(a: int, b: int) -> int:
51
- """Add two numbers.
52
-
53
- Args:
54
- a: first int
55
- b: second int
56
- """
57
- return a + b
58
-
59
- @tool
60
- def subtract(a: int, b: int) -> int:
61
- """Subtract two numbers.
62
-
63
- Args:
64
- a: first int
65
- b: second int
66
- """
67
- return a - b
68
-
69
- @tool
70
- def divide(a: int, b: int) -> int:
71
- """Divide two numbers.
72
-
73
- Args:
74
- a: first int
75
- b: second int
76
- """
77
- if b == 0:
78
- raise ValueError("Cannot divide by zero.")
79
- return a / b
80
-
81
- @tool
82
- def modulus(a: int, b: int) -> int:
83
- """Get the modulus of two numbers.
84
-
85
- Args:
86
- a: first int
87
- b: second int
88
- """
89
- return a % b
90
-
91
- @tool
92
- def wiki_search(query: str) -> str:
93
- """Search Wikipedia for a query and return maximum 2 results.
94
-
95
- Args:
96
- query: The search query."""
97
- search_docs = WikipediaLoader(query=query, load_max_docs=2).load()
98
- formatted_search_docs = "\n\n---\n\n".join(
99
- [
100
- f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
101
- for doc in search_docs
102
- ])
103
- return {"wiki_results": formatted_search_docs}
104
-
105
- @tool
106
- def web_search(query: str) -> str:
107
- """Search Tavily for a query and return maximum 3 results.
108
-
109
- Args:
110
- query: The search query."""
111
- search_docs = TavilySearchResults(max_results=3).invoke(query=query)
112
- formatted_search_docs = "\n\n---\n\n".join(
113
- [
114
- f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
115
- for doc in search_docs
116
- ])
117
- return {"web_results": formatted_search_docs}
118
-
119
- @tool
120
- def arvix_search(query: str) -> str:
121
- """Search Arxiv for a query and return maximum 3 result.
122
-
123
- Args:
124
- query: The search query."""
125
- search_docs = ArxivLoader(query=query, load_max_docs=3).load()
126
- formatted_search_docs = "\n\n---\n\n".join(
127
- [
128
- f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content[:1000]}\n</Document>'
129
- for doc in search_docs
130
- ])
131
- return {"arvix_results": formatted_search_docs}
132
-
133
-
134
-
135
- # load the system prompt from the file
136
- with open("system_prompt.txt", "r", encoding="utf-8") as f:
137
- system_prompt = f.read()
138
-
139
- # System message
140
- sys_msg = SystemMessage(content=system_prompt)
141
-
142
- # build a retriever
143
- embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2") # dim=768
144
- supabase: Client = create_client(
145
- os.environ.get("SUPABASE_URL"),
146
- os.environ.get("SUPABASE_SERVICE_KEY"))
147
- vector_store = SupabaseVectorStore(
148
- client=supabase,
149
- embedding= embeddings,
150
- table_name="documents",
151
- query_name="match_documents_langchain",
152
- )
153
- create_retriever_tool = create_retriever_tool(
154
- retriever=vector_store.as_retriever(),
155
- name="Question Search",
156
- description="A tool to retrieve similar questions from a vector store.",
157
- )
158
-
159
- tools = [
160
- multiply,
161
- add,
162
- subtract,
163
- divide,
164
- modulus,
165
- wiki_search,
166
- web_search,
167
- arvix_search,
168
- ]
169
-
170
- # Build graph function
171
- def build_graph(provider: str = "google"):
172
- """Build the graph"""
173
- # Load environment variables from .env file
174
- if provider == "google":
175
- # Google Gemini
176
  llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0)
177
- elif provider == "huggingface":
178
- # TODO: Add huggingface endpoint
179
- llm = ChatHuggingFace(
180
- llm=HuggingFaceEndpoint(
181
- url="https://api-inference.huggingface.co/models/Meta-DeepLearning/llama-2-7b-chat-hf",
182
- temperature=0,
183
- ),
184
- )
185
- else:
186
- raise ValueError("Invalid provider. Choose 'google', or 'huggingface'.")
187
- # Bind tools to LLM
188
- llm_with_tools = llm.bind_tools(tools)
189
-
190
- # Node
191
- def assistant(state: MessagesState):
192
- """Assistant node"""
193
- return {"messages": [llm_with_tools.invoke(state["messages"])]}
194
-
195
- def retriever(state: MessagesState):
196
- """Retriever node"""
197
- similar_question = vector_store.similarity_search(state["messages"][0].content)
198
- example_msg = HumanMessage(
199
- content=f"Here I provide a similar question and answer for reference: \n\n{similar_question[0].page_content}",
200
- )
201
- return {"messages": [sys_msg] + state["messages"] + [example_msg]}
202
-
203
- builder = StateGraph(MessagesState)
204
- builder.add_node("retriever", retriever)
205
- builder.add_node("assistant", assistant)
206
- builder.add_node("tools", ToolNode(tools))
207
- builder.add_edge(START, "retriever")
208
- builder.add_edge("retriever", "assistant")
209
- builder.add_conditional_edges(
210
- "assistant",
211
- tools_condition,
212
- )
213
- builder.add_edge("tools", "assistant")
214
-
215
- # Compile graph
216
- return builder.compile()
217
-
218
-
219
-
220
- # Tool Definitions
221
- # @tool
222
- # def multiply(a: int, b: int) -> int:
223
- # """Multiply two numbers."""
224
- # return a * b
225
-
226
- # @tool
227
- # def add(a: int, b: int) -> int:
228
- # """Add two numbers."""
229
- # return a + b
230
-
231
- # @tool
232
- # def modulus(a: int, b: int) -> int:
233
- # """Get the modulus of two numbers."""
234
- # return a % b
235
-
236
- # @tool
237
- # def subtract(a: int, b: int) -> int:
238
- # """Subtract two numbers."""
239
- # return a - b
240
-
241
- # @tool
242
- # def divide(a: int, b: int) -> int:
243
- # """Divide two numbers."""
244
- # if b == 0:
245
- # raise ValueError("Cannot divide by zero.")
246
- # return a / b
247
-
248
- # @tool
249
- # def arvix_search(query: str) -> str:
250
- # """Search Arxiv for a query and return maximum 3 results."""
251
- # try:
252
- # search_docs = ArxivLoader(query=query, load_max_docs=3).load()
253
- # formatted_search_docs = "\n\n---\n\n".join(
254
- # [f'<Document source="{doc.metadata["source"]}"/>\n{doc.page_content[:1000]}\n</Document>'
255
- # for doc in search_docs])
256
- # return {"arvix_results": formatted_search_docs}
257
- # except Exception as e:
258
- # return {"arvix_results": f"Error: {str(e)}"}
259
-
260
- # @tool
261
- # def execute_python(code: str) -> str:
262
- # """Execute Python code securely and return results. Handles calculations and data analysis."""
263
- # try:
264
- # parsed = ast.parse(code)
265
- # if any(isinstance(node, (ast.Import, ast.ImportFrom)) for node in parsed.body):
266
- # return "Cannot import modules for security reasons"
267
 
268
- # restricted_builtins = {'open', 'eval', 'exec', '__import__'}
269
- # for node in ast.walk(parsed):
270
- # if isinstance(node, ast.Name) and node.id in restricted_builtins:
271
- # return "Restricted function used"
272
-
273
- # return str(eval(code, {"__builtins__": {}}, {}))
274
- # except Exception as e:
275
- # return f"Execution error: {str(e)}"
276
-
277
- # @tool
278
- # def process_file(file_path: str) -> Dict[str, Any]:
279
- # """Process uploaded files (Excel, CSV, TXT) and extract structured data."""
280
- # try:
281
- # if file_path.endswith(('.xlsx', '.xls')):
282
- # df = pd.read_excel(file_path)
283
- # return {"data": df.head(10).to_dict(), "summary": df.describe().to_dict()}
284
- # elif file_path.endswith('.csv'):
285
- # df = pd.read_csv(file_path)
286
- # return {"data": df.head(10).to_dict(), "summary": df.describe().to_dict()}
287
- # elif file_path.endswith('.txt'):
288
- # with open(file_path, 'r') as f:
289
- # content = f.read(2000)
290
- # return {"content": content}
291
- # return {"error": "Unsupported file format"}
292
- # except Exception as e:
293
- # return {"error": str(e)}
294
-
295
- # @tool
296
- # def enhanced_wiki_search(query: str) -> str:
297
- # """Access full Wikipedia articles with history and specific versions."""
298
- # try:
299
- # loader = WikipediaLoader(query=query, load_max_docs=2, doc_content_chars_max=4000)
300
- # docs = loader.load()
301
- # return "\n\n".join([
302
- # f"Title: {doc.metadata['title']}\n"
303
- # f"URL: {doc.metadata['source']}\n"
304
- # f"Content: {doc.page_content[:3000]}..."
305
- # for doc in docs
306
- # ])
307
- # except Exception as e:
308
- # return f"Wikipedia Error: {str(e)}"
309
-
310
- # @tool
311
- # def media_search(query: str) -> str:
312
- # """Specialized search for media content (videos, images, celebrities)."""
313
- # try:
314
- # search = DuckDuckGoSearchResults(max_results=5)
315
- # results = search.invoke(f"site:imdb.com OR site:youtube.com {query}")
316
- # return "\n".join([
317
- # f"Source: {res['link']}\nSnippet: {res['snippet']}"
318
- # for res in results[:3]
319
- # ])
320
- # except Exception as e:
321
- # return f"Media Search Error: {str(e)}"
322
-
323
- # @tool
324
- # def academic_search(query: str) -> str:
325
- # """Search academic databases and educational resources."""
326
- # try:
327
- # arxiv_docs = ArxivLoader(query=query, load_max_docs=2).load()
328
- # web_docs = DuckDuckGoSearchResults(max_results=3).invoke(f"filetype:pdf {query}")
329
- # return f"Arxiv Results:\n{arxiv_docs[0].page_content[:1000]}\n\nWeb Results:\n{web_docs[0]['snippet']}"
330
- # except Exception as e:
331
- # return f"Academic Search Error: {str(e)}"
332
-
333
-
334
- # @tool
335
- # def web_search(query: str) -> str:
336
- # """Search DuckDuckGo for a query and return maximum 3 results."""
337
- # try:
338
- # search = DuckDuckGoSearchResults(max_results=3)
339
- # search_docs = search.invoke(query)
340
- # formatted_search_docs = "\n\n---\n\n".join(
341
- # [f'<Document source="{doc["link"]}"/>\n{doc["snippet"]}\n</Document>'
342
- # for doc in search_docs])
343
- # return {"web_results": formatted_search_docs}
344
- # except Exception as e:
345
- # return {"web_results": f"Error: {str(e)}"}
346
-
347
- # @tool
348
- # def summarize_text(text: str) -> str:
349
- # """Summarize a long text into key points."""
350
- # if not text or len(text) < 100:
351
- # return "Text too short to summarize."
352
- # return f"Summary of the provided text with key points."
353
-
354
- # @tool
355
- # def parse_query(query: str) -> dict:
356
- # """Parse a complex query into its key components for better search."""
357
- # parts = query.split()
358
- # return {
359
- # "main_topic": parts[0] if parts else "",
360
- # "subtopics": parts[1:3] if len(parts) > 1 else [],
361
- # "context": " ".join(parts[3:]) if len(parts) > 3 else ""
362
- # }
363
-
364
- # # System Prompt Setup
365
- # system_prompt = """You are a POWERFUL assistant REQUIRED to answer ALL questions using available tools.
366
- # STRICT RULES:
367
- # 1. NEVER say you can't answer - ALWAYS use tools
368
- # 2. Combine information from multiple tools when needed
369
- # 3. For calculations, use execute_python
370
- # 4. For files, use process_file
371
- # 5. For media/celebrities, use media_search
372
- # 6. For academic content, use academic_search
373
- # 7. ALWAYS format final answer as: FINAL ANSWER: [your answer]
374
-
375
- # AVAILABLE TOOLS:
376
- # - execute_python: Math/code execution
377
- # - process_file: Analyze uploaded files
378
- # - enhanced_wiki_search: Full Wikipedia access
379
- # - media_search: Videos/images/celebrities
380
- # - academic_search: Textbooks/papers
381
- # - web_search: General web search
382
- # - vector_store: Previous knowledge
383
-
384
- # YOU MUST USE THESE TOOLS TO ANSWER ALL QUESTIONS!"""
385
- # sys_msg = SystemMessage(content=system_prompt)
386
-
387
- # # Vector Store Setup
388
- # try:
389
- # embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2")
390
- # vector_store = Chroma(
391
- # collection_name="documents",
392
- # embedding_function=embeddings,
393
- # persist_directory="./chroma_db"
394
- # )
395
- # except Exception as e:
396
- # print(f"Error initializing vector store: {e}")
397
- # vector_store = None
398
-
399
- # # Tool Configuration
400
- # tools = [
401
- # multiply, add, subtract, divide, modulus,
402
- # enhanced_wiki_search, media_search, web_search, arvix_search,
403
- # academic_search, summarize_text, parse_query, DuckDuckGoSearchResults(max_results=5)
404
- # ]
405
-
406
- # if vector_store:
407
- # tools.append(
408
- # create_retriever_tool(
409
- # vector_store.as_retriever(),
410
- # name="Question Search",
411
- # description="Retrieves similar questions from vector store"
412
- # )
413
- # )
414
-
415
- # # Model Configuration
416
- # MODEL_REGISTRY = {
417
- # "gemini-2.0-flash": {
418
- # "provider": "google",
419
- # "model": "gemini-2.0-flash",
420
- # "temperature": 0.2,
421
- # "max_tokens": 2048
422
- # },
423
- # "gemini-1.5-flash": {
424
- # "provider": "google",
425
- # "model": "gemini-1.5-flash",
426
- # "temperature": 0.2,
427
- # "max_tokens": 2048
428
- # },
429
- # "kimi-vl-a3b-thinking": {
430
- # "provider": "openrouter",
431
- # "model": "moonshotai/kimi-vl-a3b-thinking:free",
432
- # "temperature": 0.2,
433
- # "max_tokens": 2048
434
- # }
435
- # }
436
-
437
- # def get_llm(model_name: str = "gemini-2.0-flash"):
438
- # """Initialize LLM with error handling"""
439
- # config = MODEL_REGISTRY.get(model_name, MODEL_REGISTRY["gemini-2.0-flash"])
440
- # provider = config.get("provider", "google")
441
-
442
- # try:
443
- # if provider == "google":
444
- # if not GOOGLE_API_KEY:
445
- # print(f"Error initializing {model_name}: GOOGLE_API_KEY not found")
446
- # return None
447
- # return ChatGoogleGenerativeAI(
448
- # model=config["model"],
449
- # temperature=config["temperature"],
450
- # max_output_tokens=config["max_tokens"],
451
- # convert_system_message_to_human=True
452
- # )
453
- # elif provider == "openrouter":
454
- # if not OPENROUTER_API_KEY:
455
- # print(f"Error initializing {model_name}: OPENROUTER_API_KEY not found")
456
- # return None
457
- # return ChatOpenAI(
458
- # model=config["model"],
459
- # temperature=config["temperature"],
460
- # max_tokens=config["max_tokens"],
461
- # openai_api_key=OPENROUTER_API_KEY,
462
- # openai_api_base="https://openrouter.ai/api/v1",
463
- # model_kwargs={
464
- # "headers": {
465
- # "HTTP-Referer": "https://your-site.com",
466
- # "X-Title": "Agent Evaluation"
467
- # }
468
- # }
469
- # )
470
- # else:
471
- # print(f"Unknown provider {provider} for model {model_name}")
472
- # return None
473
- # except Exception as e:
474
- # print(f"Error initializing {model_name}: {e}")
475
- # return None
476
-
477
- # # Graph Builder
478
- # def build_graph():
479
- # """Build LangGraph agent workflow"""
480
- # primary_llm = get_llm("gemini-2.0-flash")
481
- # fallback_llm = get_llm("gemini-1.5-flash")
482
- # kimi_llm = get_llm("kimi-vl-a3b-thinking")
483
-
484
- # llms = [llm for llm in [primary_llm, fallback_llm, kimi_llm] if llm is not None]
485
-
486
- # if not llms:
487
- # raise RuntimeError("Failed to initialize any LLM")
488
-
489
- # current_llm_index = 0
490
-
491
- # def assistant(state: MessagesState):
492
- # nonlocal current_llm_index
493
- # for attempt in range(len(llms)):
494
- # try:
495
- # llm = llms[current_llm_index]
496
- # llm_with_tools = llm.bind_tools(tools)
497
-
498
- # messages = state["messages"].copy()
499
- # if len(messages) > 0 and isinstance(messages[0], HumanMessage):
500
- # tool_instruction = HumanMessage(content="Use available tools to answer.")
501
- # messages.append(tool_instruction)
502
-
503
- # response = llm_with_tools.invoke(messages)
504
- # current_llm_index = (current_llm_index + 1) % len(llms)
505
- # return {"messages": [response]}
506
- # except Exception as e:
507
- # print(f"Model {llms[current_llm_index]} failed: {e}")
508
- # current_llm_index = (current_llm_index + 1) % len(llms)
509
- # if attempt == len(llms) - 1:
510
- # error_msg = HumanMessage(content=f"All models failed: {str(e)}")
511
- # return {"messages": [error_msg]}
512
-
513
- # def retriever(state: MessagesState):
514
- # try:
515
- # if vector_store:
516
- # similar_questions = vector_store.similarity_search(
517
- # state["messages"][0].content,
518
- # k=1
519
- # )
520
- # example_content = "Similar question reference: \n\n" + \
521
- # (similar_questions[0].page_content if similar_questions
522
- # else "No similar questions found")
523
- # else:
524
- # example_content = "Vector store not available"
525
-
526
- # return {"messages": [sys_msg] + state["messages"] + [HumanMessage(content=example_content)]}
527
- # except Exception as e:
528
- # error_msg = HumanMessage(content=f"Retrieval error: {str(e)}")
529
- # return {"messages": [error_msg]}
530
-
531
- # builder = StateGraph(MessagesState)
532
- # builder.add_node("retriever", retriever)
533
- # builder.add_node("assistant", assistant)
534
- # builder.add_node("tools", ToolNode(tools))
535
-
536
- # builder.add_edge(START, "retriever")
537
- # builder.add_edge("retriever", "assistant")
538
- # builder.add_conditional_edges("assistant", tools_condition)
539
- # builder.add_edge("tools", "assistant")
540
 
541
- # return builder.compile()
542
-
543
- # Agent Class
544
- class BasicAgent:
545
- def __init__(self):
546
- self.graph = build_graph()
547
-
548
  def __call__(self, question: str) -> str:
 
 
 
 
 
 
 
 
 
 
549
  try:
550
- messages = [HumanMessage(content=question)]
551
  result = self.graph.invoke({"messages": messages})
552
- last_message = result['messages'][-1].content
 
 
 
553
 
554
- if "FINAL ANSWER: " in last_message:
555
- answer_part = last_message.split("FINAL ANSWER: ")[-1].strip()
556
- return answer_part[:-2].strip() if answer_part.endswith('"}') else answer_part
557
- elif "Answer:" in last_message:
558
- answer_part = last_message.split("Answer:")[-1].strip()
559
- return answer_part[:-2].strip() if answer_part.endswith('"}') else answer_part
560
- return last_message
561
  except Exception as e:
562
- return f"Agent error: {str(e)}"
 
563
 
564
-
565
-
566
-
567
- # Updated Agent Class
568
- # class BasicAgent:
569
- # """LangGraph Agent Interface"""
570
- # def __init__(self):
571
- # self.graph = build_graph()
572
-
573
- # def __call__(self, question: str) -> str:
574
- # try:
575
- # messages = [HumanMessage(content=question)]
576
- # result = self.graph.invoke({"messages": messages})
577
- # last_message = result['messages'][-1].content
578
-
579
- # # Improved content extraction
580
- # if "FINAL ANSWER: " in last_message:
581
- # return last_message.split("FINAL ANSWER: ")[-1].strip()
582
- # elif "Answer:" in last_message:
583
- # return last_message.split("Answer:")[-1].strip()
584
- # return last_message
585
- # except Exception as e:
586
- # return f"Agent processing error: {str(e)}"
587
-
588
-
589
-
590
-
591
-
592
- # Gradio Interface Functions
593
  def run_and_submit_all(profile: gr.OAuthProfile | None):
594
- """Evaluation runner function"""
595
- if not profile:
 
 
 
 
 
 
 
 
 
 
596
  return "Please Login to Hugging Face with the button.", None
597
 
598
- space_id = os.getenv("SPACE_ID")
599
- api_url = "https://agents-course-unit4-scoring.hf.space"
600
- username = profile.username
601
- results_log = []
602
 
 
603
  try:
604
- agent = BasicAgent()
605
- agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
606
-
607
- # Fetch questions
608
- response = requests.get(f"{api_url}/questions", timeout=15)
 
 
 
 
 
 
 
 
609
  response.raise_for_status()
610
  questions_data = response.json()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
611
 
612
- # Process questions
613
- answers_payload = []
614
- for item in questions_data:
615
- task_id = item.get("task_id")
616
- question_text = item.get("question")
617
- if not task_id or not question_text:
618
- continue
619
-
620
- try:
621
- answer = agent(question_text)
622
- answers_payload.append({
623
- "task_id": task_id,
624
- "submitted_answer": answer
625
- })
626
- results_log.append({
627
- "Task ID": task_id,
628
- "Question": question_text,
629
- "Submitted Answer": answer
630
- })
631
- except Exception as e:
632
- results_log.append({
633
- "Task ID": task_id,
634
- "Question": question_text,
635
- "Submitted Answer": f"AGENT ERROR: {e}"
636
- })
637
 
638
- # Submit answers
639
- submission_data = {
640
- "username": username.strip(),
641
- "agent_code": agent_code,
642
- "answers": answers_payload
643
- }
644
-
645
- response = requests.post(f"{api_url}/submit", json=submission_data, timeout=60)
 
646
  response.raise_for_status()
647
  result_data = response.json()
648
-
649
  final_status = (
650
- f"Submission Successful!\nOverall Score: {result_data.get('score', 'N/A')}%\n"
651
- f"Correct: {result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')}\n"
652
- f"Message: {result_data.get('message', 'No message')}"
 
 
653
  )
654
- return final_status, pd.DataFrame(results_log)
655
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
656
  except Exception as e:
657
- return f"Error: {str(e)}", pd.DataFrame(results_log)
 
 
 
 
658
 
659
- # Gradio UI Setup
660
  with gr.Blocks() as demo:
661
- gr.Markdown("# Basic Agent Evaluation Runner")
662
  gr.Markdown(
663
  """
664
  **Instructions:**
665
- 1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
666
- 2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
667
- 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
668
  ---
669
- **Disclaimers:**
670
- 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).
671
- 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 separate action or even to answer the questions in async.
672
  """
673
  )
674
 
@@ -694,16 +287,16 @@ if __name__ == "__main__":
694
  print(f"✅ SPACE_HOST found: {space_host_startup}")
695
  print(f" Runtime URL should be: https://{space_host_startup}.hf.space")
696
  else:
697
- print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
698
 
699
  if space_id_startup: # Print repo URLs if SPACE_ID is found
700
  print(f"✅ SPACE_ID found: {space_id_startup}")
701
- print(f" Repo URL: https://huggingface.co/spaces/ {space_id_startup}")
702
- print(f" Repo Tree URL: https://huggingface.co/spaces/ {space_id_startup}/tree/main")
703
  else:
704
- print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
705
 
706
  print("-"*(60 + len(" App Starting ")) + "\n")
707
 
708
- print("Launching Gradio Interface for Basic Agent Evaluation...")
709
  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 dotenv import load_dotenv
7
+ from typing import List, Dict, Any, Tuple, Optional
8
+
9
+ # LangChain imports
10
+ from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
11
+ from langchain_core.messages import BaseMessage
12
+ from langchain.schema import Document
13
  from langchain_google_genai import ChatGoogleGenerativeAI
 
 
14
  from langchain_community.tools.tavily_search import TavilySearchResults
15
+ from langchain_community.tools.wikipedia.tool import WikipediaQueryRun
16
+ from langchain_community.utilities.wikipedia import WikipediaAPIWrapper
17
+ from langchain_community.tools.arxiv.tool import ArxivQueryRun
18
+ from langgraph.graph import StateGraph, END
19
+ from langgraph.graph.nodes.tools import ToolNode
20
+ from langgraph.prebuilt import ToolInvocation, tools_condition
21
+ from langgraph.prebuilt.tool_executor import ToolExecutor
22
+ from dataclasses import dataclass
23
+ from typing import TypedDict, List, Annotated, Literal
24
+ from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
25
+
26
+ # Constants
27
+ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
28
+
29
+ # Define the state for the agent
30
+ class MessagesState(TypedDict):
31
+ messages: List[BaseMessage]
32
+
33
+ # Load system prompt
34
+ try:
35
+ with open("system_prompt.txt", "r", encoding="utf-8") as f:
36
+ system_prompt = f.read()
37
+ except FileNotFoundError:
38
+ system_prompt = """You are a helpful AI assistant that uses tools to find information and answer questions.
39
+ When you don't know something, use the available tools to look up information. Be concise, direct, and provide accurate responses.
40
+ Always cite your sources when using information from searches or reference materials."""
41
+
42
+ # Advanced agent using LangGraph
43
+ class AdvancedAgent:
44
+ def __init__(self):
45
+ print("Initializing AdvancedAgent with LangGraph, Wikipedia, Arxiv, and Gemini 2.0 Flash")
46
+ load_dotenv() # Load environment variables from .env file
47
+
48
+ # Initialize the graph
49
+ self.graph = self.build_graph()
50
+ print("Graph successfully built")
51
+
52
+ def build_graph(self):
53
+ """Build the LangGraph agent with necessary tools"""
54
+ # Initialize LLM
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0)
56
+ print("LLM initialized: Gemini 2.0 Flash")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
+ # Initialize tools
59
+ wikipedia_tool = WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper())
60
+ arxiv_tool = ArxivQueryRun()
61
+ tavily_search = TavilySearchResults(max_results=5)
62
+
63
+ tools = [wikipedia_tool, arxiv_tool, tavily_search]
64
+ print(f"Initialized {len(tools)} tools: Wikipedia, Arxiv, Tavily Search")
65
+
66
+ # Create tool executor
67
+ tool_executor = ToolExecutor(tools)
68
+
69
+ # System message
70
+ sys_msg = SystemMessage(content=system_prompt)
71
+
72
+ # Bind tools to LLM
73
+ llm_with_tools = llm.bind_tools(tools)
74
+
75
+ # Define nodes
76
+ def assistant(state: MessagesState):
77
+ """Assistant node that processes messages and generates responses"""
78
+ messages = state["messages"]
79
+ response = llm_with_tools.invoke(messages)
80
+ return {"messages": state["messages"] + [response]}
81
+
82
+ def tools_node(state: MessagesState, tool_calls: List[ToolInvocation]):
83
+ """Execute tool calls and return results"""
84
+ results = []
85
+ for tool_call in tool_calls:
86
+ result = tool_executor.invoke(tool_call)
87
+ msg = AIMessage(content=str(result), tool_call_id=tool_call.id)
88
+ results.append(msg)
89
+ return {"messages": state["messages"] + results}
90
+
91
+ # Build the graph
92
+ builder = StateGraph(MessagesState)
93
+
94
+ # Add nodes
95
+ builder.add_node("assistant", assistant)
96
+ builder.add_node("tools", tools_node)
97
+
98
+ # Add edges
99
+ builder.add_edge("assistant", "tools", condition=tools_condition)
100
+ builder.add_edge("tools", "assistant")
101
+ builder.add_edge("assistant", END, condition=lambda state: not tools_condition(state))
102
+
103
+ # Compile graph
104
+ return builder.compile()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
 
 
 
 
 
 
 
 
106
  def __call__(self, question: str) -> str:
107
+ """Process a question through the agent graph and return the response"""
108
+ print(f"Agent received question (first 50 chars): {question[:50]}...")
109
+
110
+ # Create initial state with system message and human question
111
+ messages = [
112
+ SystemMessage(content=system_prompt),
113
+ HumanMessage(content=question)
114
+ ]
115
+
116
+ # Run the graph
117
  try:
 
118
  result = self.graph.invoke({"messages": messages})
119
+ # Extract the last AI message as the answer
120
+ for msg in reversed(result["messages"]):
121
+ if isinstance(msg, AIMessage) and not getattr(msg, "tool_call_id", None):
122
+ return msg.content
123
 
124
+ # Fallback if no valid AI message found
125
+ return "I wasn't able to generate a proper response. Please try again."
 
 
 
 
 
126
  except Exception as e:
127
+ print(f"Error running agent graph: {e}")
128
+ return f"Sorry, I encountered an error while processing your question: {str(e)}"
129
 
130
+ # Function to run and submit all questions
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  def run_and_submit_all(profile: gr.OAuthProfile | None):
132
+ """
133
+ Fetches all questions, runs the AdvancedAgent on them, submits all answers,
134
+ and displays the results.
135
+ """
136
+ # --- Determine HF Space Runtime URL and Repo URL ---
137
+ space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
138
+
139
+ if profile:
140
+ username= f"{profile.username}"
141
+ print(f"User logged in: {username}")
142
+ else:
143
+ print("User not logged in.")
144
  return "Please Login to Hugging Face with the button.", None
145
 
146
+ api_url = DEFAULT_API_URL
147
+ questions_url = f"{api_url}/questions"
148
+ submit_url = f"{api_url}/submit"
 
149
 
150
+ # 1. Instantiate Agent
151
  try:
152
+ agent = AdvancedAgent()
153
+ except Exception as e:
154
+ print(f"Error instantiating agent: {e}")
155
+ return f"Error initializing agent: {e}", None
156
+
157
+ # In the case of an app running as a hugging Face space, this link points toward your codebase
158
+ agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
159
+ print(agent_code)
160
+
161
+ # 2. Fetch Questions
162
+ print(f"Fetching questions from: {questions_url}")
163
+ try:
164
+ response = requests.get(questions_url, timeout=15)
165
  response.raise_for_status()
166
  questions_data = response.json()
167
+ if not questions_data:
168
+ print("Fetched questions list is empty.")
169
+ return "Fetched questions list is empty or invalid format.", None
170
+ print(f"Fetched {len(questions_data)} questions.")
171
+ except requests.exceptions.RequestException as e:
172
+ print(f"Error fetching questions: {e}")
173
+ return f"Error fetching questions: {e}", None
174
+ except requests.exceptions.JSONDecodeError as e:
175
+ print(f"Error decoding JSON response from questions endpoint: {e}")
176
+ print(f"Response text: {response.text[:500]}")
177
+ return f"Error decoding server response for questions: {e}", None
178
+ except Exception as e:
179
+ print(f"An unexpected error occurred fetching questions: {e}")
180
+ return f"An unexpected error occurred fetching questions: {e}", None
181
+
182
+ # 3. Run your Agent
183
+ results_log = []
184
+ answers_payload = []
185
+ print(f"Running agent on {len(questions_data)} questions...")
186
+ for item in questions_data:
187
+ task_id = item.get("task_id")
188
+ question_text = item.get("question")
189
+ if not task_id or question_text is None:
190
+ print(f"Skipping item with missing task_id or question: {item}")
191
+ continue
192
+ try:
193
+ submitted_answer = agent(question_text)
194
+ answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
195
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
196
+ except Exception as e:
197
+ print(f"Error running agent on task {task_id}: {e}")
198
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
199
 
200
+ if not answers_payload:
201
+ print("Agent did not produce any answers to submit.")
202
+ return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
 
204
+ # 4. Prepare Submission
205
+ submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
206
+ status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
207
+ print(status_update)
208
+
209
+ # 5. Submit
210
+ print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
211
+ try:
212
+ response = requests.post(submit_url, json=submission_data, timeout=60)
213
  response.raise_for_status()
214
  result_data = response.json()
 
215
  final_status = (
216
+ f"Submission Successful!\n"
217
+ f"User: {result_data.get('username')}\n"
218
+ f"Overall Score: {result_data.get('score', 'N/A')}% "
219
+ f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
220
+ f"Message: {result_data.get('message', 'No message received.')}"
221
  )
222
+ print("Submission successful.")
223
+ results_df = pd.DataFrame(results_log)
224
+ return final_status, results_df
225
+ except requests.exceptions.HTTPError as e:
226
+ error_detail = f"Server responded with status {e.response.status_code}."
227
+ try:
228
+ error_json = e.response.json()
229
+ error_detail += f" Detail: {error_json.get('detail', e.response.text)}"
230
+ except requests.exceptions.JSONDecodeError:
231
+ error_detail += f" Response: {e.response.text[:500]}"
232
+ status_message = f"Submission Failed: {error_detail}"
233
+ print(status_message)
234
+ results_df = pd.DataFrame(results_log)
235
+ return status_message, results_df
236
+ except requests.exceptions.Timeout:
237
+ status_message = "Submission Failed: The request timed out."
238
+ print(status_message)
239
+ results_df = pd.DataFrame(results_log)
240
+ return status_message, results_df
241
+ except requests.exceptions.RequestException as e:
242
+ status_message = f"Submission Failed: Network error - {e}"
243
+ print(status_message)
244
+ results_df = pd.DataFrame(results_log)
245
+ return status_message, results_df
246
  except Exception as e:
247
+ status_message = f"An unexpected error occurred during submission: {e}"
248
+ print(status_message)
249
+ results_df = pd.DataFrame(results_log)
250
+ return status_message, results_df
251
+
252
 
253
+ # --- Build Gradio Interface using Blocks ---
254
  with gr.Blocks() as demo:
255
+ gr.Markdown("# Advanced Agent Evaluation Runner")
256
  gr.Markdown(
257
  """
258
  **Instructions:**
259
+ 1. This space implements an advanced agent using LangGraph with Wikipedia, Arxiv, and Tavily Search tools, powered by Gemini 2.0 Flash LLM.
260
+ 2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
261
+ 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
262
  ---
263
+ **Note:**
264
+ The evaluation might take some time as the agent processes all questions through the tools.
 
265
  """
266
  )
267
 
 
287
  print(f"✅ SPACE_HOST found: {space_host_startup}")
288
  print(f" Runtime URL should be: https://{space_host_startup}.hf.space")
289
  else:
290
+ print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
291
 
292
  if space_id_startup: # Print repo URLs if SPACE_ID is found
293
  print(f"✅ SPACE_ID found: {space_id_startup}")
294
+ print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
295
+ print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
296
  else:
297
+ print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
298
 
299
  print("-"*(60 + len(" App Starting ")) + "\n")
300
 
301
+ print("Launching Gradio Interface for Advanced Agent Evaluation...")
302
  demo.launch(debug=True, share=False)