Spaces:
Sleeping
Sleeping
react_removed
Browse files
agent.py
CHANGED
|
@@ -1,13 +1,10 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
import os
|
| 3 |
from langchain_openai import ChatOpenAI
|
| 4 |
-
from
|
| 5 |
-
from
|
| 6 |
-
from state import AgentState
|
| 7 |
from typing import Any, Dict, List, Optional
|
| 8 |
import json
|
| 9 |
-
from langgraph.prebuilt import create_react_agent
|
| 10 |
-
import signal
|
| 11 |
|
| 12 |
# βββββββββββββββββββββββββββ External tools ββββββββββββββββββββββββββββββ
|
| 13 |
from tools import (
|
|
@@ -26,21 +23,43 @@ from tools import (
|
|
| 26 |
# βββββββββββββββββββββββββββ Configuration βββββββββββββββββββββββββββββββ
|
| 27 |
MAX_TOOL_CALLS = 5
|
| 28 |
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
-
#
|
|
|
|
|
|
|
| 36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
-
|
| 39 |
|
| 40 |
def build_graph():
|
| 41 |
-
"""Build and return a
|
| 42 |
-
|
| 43 |
-
|
| 44 |
llm_tools = [
|
| 45 |
wikipedia_search_tool,
|
| 46 |
arxiv_search_tool,
|
|
@@ -54,7 +73,7 @@ def build_graph():
|
|
| 54 |
divide_tool
|
| 55 |
]
|
| 56 |
|
| 57 |
-
# Create the react agent
|
| 58 |
-
agent =
|
| 59 |
|
| 60 |
return agent
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
import os
|
| 3 |
from langchain_openai import ChatOpenAI
|
| 4 |
+
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
|
| 5 |
+
from langgraph.prebuilt import ToolExecutor
|
|
|
|
| 6 |
from typing import Any, Dict, List, Optional
|
| 7 |
import json
|
|
|
|
|
|
|
| 8 |
|
| 9 |
# βββββββββββββββββββββββββββ External tools ββββββββββββββββββββββββββββββ
|
| 10 |
from tools import (
|
|
|
|
| 23 |
# βββββββββββββββββββββββββββ Configuration βββββββββββββββββββββββββββββββ
|
| 24 |
MAX_TOOL_CALLS = 5
|
| 25 |
|
| 26 |
+
class CustomReActAgent:
|
| 27 |
+
def __init__(self, tools: List, model_name="gpt-4o-mini"):
|
| 28 |
+
self.llm = ChatOpenAI(model_name=model_name, temperature=0.3)
|
| 29 |
+
self.tool_executor = ToolExecutor(tools)
|
| 30 |
|
| 31 |
+
def run(self, question: str, task_id: Optional[str] = None, max_turns: int = 15, system_prompt: str = "") -> str:
|
| 32 |
+
messages = [SystemMessage(content=system_prompt)]
|
| 33 |
+
if task_id:
|
| 34 |
+
messages[0].content += f"\n\nIMPORTANT: Your current task_id is: {task_id}."
|
| 35 |
+
messages.append(HumanMessage(content=question))
|
| 36 |
|
| 37 |
+
for _ in range(max_turns):
|
| 38 |
+
ai_response = self.llm.invoke(messages)
|
| 39 |
+
messages.append(ai_response)
|
| 40 |
+
print("AI said:", ai_response.content)
|
| 41 |
|
| 42 |
+
# Final answer?
|
| 43 |
+
if "FINAL ANSWER:" in ai_response.content.upper():
|
| 44 |
+
return ai_response.content.split("FINAL ANSWER:")[-1].strip()
|
| 45 |
|
| 46 |
+
# Tool call expected
|
| 47 |
+
if "Action:" in ai_response.content and "Action Input:" in ai_response.content:
|
| 48 |
+
try:
|
| 49 |
+
tool_name = ai_response.content.split("Action:")[1].split("Action Input:")[0].strip()
|
| 50 |
+
tool_input = ai_response.content.split("Action Input:")[1].strip()
|
| 51 |
+
tool_result = self.tool_executor.run(tool_name, tool_input)
|
| 52 |
+
messages.append(HumanMessage(content=f"Observation: {tool_result}"))
|
| 53 |
+
except Exception as e:
|
| 54 |
+
messages.append(HumanMessage(content=f"Observation: Tool execution failed: {str(e)}"))
|
| 55 |
+
else:
|
| 56 |
+
messages.append(HumanMessage(content="Observation: No valid action. Please clarify."))
|
| 57 |
|
| 58 |
+
return "No FINAL ANSWER found within allowed steps."
|
| 59 |
|
| 60 |
def build_graph():
|
| 61 |
+
"""Build and return a CustomReActAgent."""
|
| 62 |
+
|
|
|
|
| 63 |
llm_tools = [
|
| 64 |
wikipedia_search_tool,
|
| 65 |
arxiv_search_tool,
|
|
|
|
| 73 |
divide_tool
|
| 74 |
]
|
| 75 |
|
| 76 |
+
# Create the custom react agent
|
| 77 |
+
agent = CustomReActAgent(tools=llm_tools)
|
| 78 |
|
| 79 |
return agent
|
app.py
CHANGED
|
@@ -14,20 +14,46 @@ from state import AgentState
|
|
| 14 |
DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
|
| 15 |
|
| 16 |
SYSTEM_PROMPT = """
|
| 17 |
-
You are a general AI assistant
|
| 18 |
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
SEARCH STRATEGY:
|
| 22 |
-
- If wikipedia_search_tool fails
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
You MUST always provide a FINAL ANSWER. If you reach the recursion limit or tools fail, provide the best answer possible with available information.
|
| 31 |
"""
|
| 32 |
|
| 33 |
|
|
@@ -37,48 +63,15 @@ class BasicAgent:
|
|
| 37 |
self.graph = build_graph()
|
| 38 |
|
| 39 |
def __call__(self, question: str, task_id: Optional[str] = None) -> str:
|
| 40 |
-
"""Run the agent and return whatever FINAL_ANSWER the
|
| 41 |
print(f"Agent received question: {question}")
|
| 42 |
|
| 43 |
try:
|
| 44 |
-
#
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
# Initialize the state properly with all required fields
|
| 50 |
-
init_state = {
|
| 51 |
-
"messages": [
|
| 52 |
-
SystemMessage(content=system_prompt_with_task),
|
| 53 |
-
HumanMessage(content=question)
|
| 54 |
-
]
|
| 55 |
-
}
|
| 56 |
-
|
| 57 |
-
# Run the agent with increased steps
|
| 58 |
-
out_state = self.graph.invoke(init_state, {"recursion_limit": 15})
|
| 59 |
-
|
| 60 |
-
# Extract the final answer from the last message
|
| 61 |
-
if out_state and "messages" in out_state:
|
| 62 |
-
last_message = out_state["messages"][-1]
|
| 63 |
-
if hasattr(last_message, 'content') and last_message.content:
|
| 64 |
-
content = last_message.content
|
| 65 |
-
print("content: ", content)
|
| 66 |
-
print("\n\n\n\n")
|
| 67 |
-
|
| 68 |
-
# Look for FINAL ANSWER: pattern (case insensitive)
|
| 69 |
-
if "FINAL ANSWER:" in content.upper():
|
| 70 |
-
# Find the last occurrence of FINAL ANSWER
|
| 71 |
-
parts = content.upper().split("FINAL ANSWER:")
|
| 72 |
-
if len(parts) > 1:
|
| 73 |
-
# Get the original case version
|
| 74 |
-
answer_start = content.upper().rfind("FINAL ANSWER:") + len("FINAL ANSWER:")
|
| 75 |
-
final_answer = content[answer_start:].strip()
|
| 76 |
-
return final_answer
|
| 77 |
-
|
| 78 |
-
# If no FINAL ANSWER found, return the whole content
|
| 79 |
-
return content
|
| 80 |
-
|
| 81 |
-
return "No valid response generated."
|
| 82 |
|
| 83 |
except Exception as e:
|
| 84 |
print(f"Agent execution error: {e}")
|
|
|
|
| 14 |
DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
|
| 15 |
|
| 16 |
SYSTEM_PROMPT = """
|
| 17 |
+
You are a general AI assistant using the ReAct (Reasoning and Acting) approach. I will ask you a question, and you should think step by step and use tools when needed.
|
| 18 |
|
| 19 |
+
FORMAT:
|
| 20 |
+
For each step, use this exact format:
|
| 21 |
+
Thought: [your reasoning about what to do next]
|
| 22 |
+
Action: [tool name]
|
| 23 |
+
Action Input: [input to the tool]
|
| 24 |
+
|
| 25 |
+
After using a tool, you'll receive:
|
| 26 |
+
Observation: [tool result]
|
| 27 |
+
|
| 28 |
+
Continue this cycle until you have enough information, then provide:
|
| 29 |
+
FINAL ANSWER: [YOUR FINAL ANSWER]
|
| 30 |
+
|
| 31 |
+
ANSWER FORMAT:
|
| 32 |
+
YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings. If you are asked for a number, don't use comma to write your number neither use units such as $ or percent sign unless specified otherwise. If you are asked for a string, don't use articles, neither abbreviations (e.g. for cities), and write the digits in plain text unless specified otherwise.
|
| 33 |
+
|
| 34 |
+
IMPORTANT: When using tools that require file access (such as audio_transcriber_tool, excel_tool, analyze_code_tool, or image_tool), ALWAYS use the task_id parameter only. Do NOT use any file names mentioned by the user.
|
| 35 |
+
|
| 36 |
+
AVAILABLE TOOLS:
|
| 37 |
+
- wikipedia_search_tool: For historical, biographical, scientific facts
|
| 38 |
+
- arxiv_search_tool: For academic research papers
|
| 39 |
+
- audio_transcriber_tool: For transcribing audio files
|
| 40 |
+
- excel_tool: For analyzing spreadsheet data
|
| 41 |
+
- analyze_code_tool: For understanding code files
|
| 42 |
+
- image_tool: For describing images
|
| 43 |
+
- add_tool: For adding numbers
|
| 44 |
+
- subtract_tool: For subtracting numbers
|
| 45 |
+
- multiply_tool: For multiplying numbers
|
| 46 |
+
- divide_tool: For dividing numbers
|
| 47 |
|
| 48 |
SEARCH STRATEGY:
|
| 49 |
+
- If wikipedia_search_tool fails, try with broader queries or use arxiv_search_tool for academic topics
|
| 50 |
+
- When you see [END_OF_SEARCH] in results, stop searching and provide your final answer
|
| 51 |
+
- You MUST always provide a FINAL ANSWER, even if tools fail
|
| 52 |
+
|
| 53 |
+
Example:
|
| 54 |
+
Thought: I need to find information about quantum computing.
|
| 55 |
+
Action: wikipedia_search_tool
|
| 56 |
+
Action Input: quantum computing
|
|
|
|
| 57 |
"""
|
| 58 |
|
| 59 |
|
|
|
|
| 63 |
self.graph = build_graph()
|
| 64 |
|
| 65 |
def __call__(self, question: str, task_id: Optional[str] = None) -> str:
|
| 66 |
+
"""Run the agent and return whatever FINAL_ANSWER the agent produces."""
|
| 67 |
print(f"Agent received question: {question}")
|
| 68 |
|
| 69 |
try:
|
| 70 |
+
# Run the custom react agent
|
| 71 |
+
result = self.graph.run(question=question, task_id=task_id, max_turns=15, system_prompt=SYSTEM_PROMPT)
|
| 72 |
+
print("Final result: ", result)
|
| 73 |
+
print("\n\n\n\n")
|
| 74 |
+
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
|
| 76 |
except Exception as e:
|
| 77 |
print(f"Agent execution error: {e}")
|
tools.py
CHANGED
|
@@ -219,18 +219,12 @@ def wikipedia_search_tool(wiki_query: str) -> str:
|
|
| 219 |
- "Explain quantum entanglement"
|
| 220 |
- "Tell me about the French Revolution"
|
| 221 |
"""
|
| 222 |
-
print(f"DEBUG: reached wikipedia_search_tool with query: {wiki_query}")
|
| 223 |
try:
|
| 224 |
docs = WikipediaLoader(query=wiki_query, load_max_docs=3).load() # Reduced from 5 to 3
|
| 225 |
-
print(f"DEBUG: WikipediaLoader returned {len(docs)} documents")
|
| 226 |
|
| 227 |
result = ""
|
| 228 |
counter = 1
|
| 229 |
for doc in docs:
|
| 230 |
-
print(f"DEBUG: Processing Wikipedia document {counter}")
|
| 231 |
-
print(f"DEBUG: Document metadata: {doc.metadata}")
|
| 232 |
-
print(f"DEBUG: Document content length: {len(doc.page_content)}")
|
| 233 |
-
|
| 234 |
# Handle different metadata structures
|
| 235 |
title = "Unknown Title"
|
| 236 |
if hasattr(doc, 'metadata') and doc.metadata:
|
|
@@ -247,8 +241,6 @@ def wikipedia_search_tool(wiki_query: str) -> str:
|
|
| 247 |
first_key = list(doc.metadata.keys())[0]
|
| 248 |
title = f"Wikipedia: {doc.metadata[first_key]}"
|
| 249 |
|
| 250 |
-
print(f"DEBUG: Using Wikipedia title: {title}")
|
| 251 |
-
|
| 252 |
# Trim content to key information only (reduced from 2000 to 800 characters)
|
| 253 |
content = doc.page_content[:800] if len(doc.page_content) > 800 else doc.page_content
|
| 254 |
|
|
@@ -266,12 +258,11 @@ def wikipedia_search_tool(wiki_query: str) -> str:
|
|
| 266 |
# Add clear end marker
|
| 267 |
result += "\n\n[END_OF_SEARCH] - Wikipedia search complete. Use this information to answer the question."
|
| 268 |
|
| 269 |
-
print(
|
| 270 |
return result
|
| 271 |
|
| 272 |
except Exception as e:
|
| 273 |
error_msg = f"Error during Wikipedia search: {str(e)} [END_OF_SEARCH]"
|
| 274 |
-
print(f"DEBUG: {error_msg}")
|
| 275 |
return error_msg
|
| 276 |
|
| 277 |
@tool
|
|
|
|
| 219 |
- "Explain quantum entanglement"
|
| 220 |
- "Tell me about the French Revolution"
|
| 221 |
"""
|
|
|
|
| 222 |
try:
|
| 223 |
docs = WikipediaLoader(query=wiki_query, load_max_docs=3).load() # Reduced from 5 to 3
|
|
|
|
| 224 |
|
| 225 |
result = ""
|
| 226 |
counter = 1
|
| 227 |
for doc in docs:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
# Handle different metadata structures
|
| 229 |
title = "Unknown Title"
|
| 230 |
if hasattr(doc, 'metadata') and doc.metadata:
|
|
|
|
| 241 |
first_key = list(doc.metadata.keys())[0]
|
| 242 |
title = f"Wikipedia: {doc.metadata[first_key]}"
|
| 243 |
|
|
|
|
|
|
|
| 244 |
# Trim content to key information only (reduced from 2000 to 800 characters)
|
| 245 |
content = doc.page_content[:800] if len(doc.page_content) > 800 else doc.page_content
|
| 246 |
|
|
|
|
| 258 |
# Add clear end marker
|
| 259 |
result += "\n\n[END_OF_SEARCH] - Wikipedia search complete. Use this information to answer the question."
|
| 260 |
|
| 261 |
+
print("Wikipedia search completed successfully")
|
| 262 |
return result
|
| 263 |
|
| 264 |
except Exception as e:
|
| 265 |
error_msg = f"Error during Wikipedia search: {str(e)} [END_OF_SEARCH]"
|
|
|
|
| 266 |
return error_msg
|
| 267 |
|
| 268 |
@tool
|