hemantvirmani commited on
Commit
6e9fb70
·
1 Parent(s): fbfec74

changes for adding support for llamaindex agent

Browse files
Files changed (8) hide show
  1. agents.py +3 -0
  2. config.py +2 -0
  3. gradioapp.py +10 -0
  4. langgraphagent.py +1 -1
  5. llamaindexagent.py +194 -0
  6. reactlanggraphagent.py +1 -1
  7. requirements.txt +3 -0
  8. utils.py +11 -0
agents.py CHANGED
@@ -3,6 +3,7 @@
3
  import config
4
  from langgraphagent import LangGraphAgent
5
  from reactlanggraphagent import ReActLangGraphAgent
 
6
 
7
 
8
  class MyGAIAAgents:
@@ -26,6 +27,8 @@ class MyGAIAAgents:
26
  self.agent = LangGraphAgent()
27
  elif active_agent == config.AGENT_REACT_LANGGRAPH:
28
  self.agent = ReActLangGraphAgent()
 
 
29
  else:
30
  # Default to LangGraph if unknown agent type
31
  print(f"[WARNING] Unknown agent type '{active_agent}', defaulting to {config.AGENT_LANGGRAPH}")
 
3
  import config
4
  from langgraphagent import LangGraphAgent
5
  from reactlanggraphagent import ReActLangGraphAgent
6
+ from llamaindexagent import LlamaIndexAgent
7
 
8
 
9
  class MyGAIAAgents:
 
27
  self.agent = LangGraphAgent()
28
  elif active_agent == config.AGENT_REACT_LANGGRAPH:
29
  self.agent = ReActLangGraphAgent()
30
+ elif active_agent == config.AGENT_LLAMAINDEX:
31
+ self.agent = LlamaIndexAgent()
32
  else:
33
  # Default to LangGraph if unknown agent type
34
  print(f"[WARNING] Unknown agent type '{active_agent}', defaulting to {config.AGENT_LANGGRAPH}")
config.py CHANGED
@@ -42,6 +42,8 @@ GEMINI_MODEL = "gemini-2.5-flash"
42
  GEMINI_TEMPERATURE = 0
43
  GEMINI_MAX_TOKENS = 1024
44
 
 
 
45
  # Retry Configuration for 504 DEADLINE_EXCEEDED errors
46
  MAX_RETRIES = 3
47
  INITIAL_RETRY_DELAY = 2.0 # seconds
 
42
  GEMINI_TEMPERATURE = 0
43
  GEMINI_MAX_TOKENS = 1024
44
 
45
+ ACTIVE_AGENT_LLM_MODEL = GEMINI_MODEL
46
+
47
  # Retry Configuration for 504 DEADLINE_EXCEEDED errors
48
  MAX_RETRIES = 3
49
  INITIAL_RETRY_DELAY = 2.0 # seconds
gradioapp.py CHANGED
@@ -26,6 +26,10 @@ def _run_and_submit_react(profile: gr.OAuthProfile | None = None):
26
  """Run and submit with ReActLangGraph agent."""
27
  return _run_and_submit_all_local(profile, active_agent=config.AGENT_REACT_LANGGRAPH)
28
 
 
 
 
 
29
 
30
  def create_ui(run_and_submit_all, run_test_code):
31
  """Create the Main App with custom layout to include LoginButton"""
@@ -56,6 +60,7 @@ def create_ui(run_and_submit_all, run_test_code):
56
  with gr.Row():
57
  run_button_langgraph = gr.Button("Run with LangGraph Agent", variant="primary")
58
  run_button_react = gr.Button("Run with ReAct Agent", variant="secondary")
 
59
 
60
  status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
61
  # Removed max_rows=10 from DataFrame constructor
@@ -70,6 +75,11 @@ def create_ui(run_and_submit_all, run_test_code):
70
  fn=_run_and_submit_react,
71
  outputs=[status_output, results_table]
72
  )
 
 
 
 
 
73
 
74
  test_button = gr.Button("Run Test Examples")
75
  test_results_table = gr.DataFrame(label="Test Answers from Agent", wrap=True)
 
26
  """Run and submit with ReActLangGraph agent."""
27
  return _run_and_submit_all_local(profile, active_agent=config.AGENT_REACT_LANGGRAPH)
28
 
29
+ def _run_and_submit_llamaindex(profile: gr.OAuthProfile | None = None):
30
+ """Run and submit with LlamaIndex agent."""
31
+ return _run_and_submit_all_local(profile, active_agent=config.AGENT_LLAMAINDEX)
32
+
33
 
34
  def create_ui(run_and_submit_all, run_test_code):
35
  """Create the Main App with custom layout to include LoginButton"""
 
60
  with gr.Row():
61
  run_button_langgraph = gr.Button("Run with LangGraph Agent", variant="primary")
62
  run_button_react = gr.Button("Run with ReAct Agent", variant="secondary")
63
+ run_button_llamaindex = gr.Button("Run with LlamaIndex Agent", variant="secondary")
64
 
65
  status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
66
  # Removed max_rows=10 from DataFrame constructor
 
75
  fn=_run_and_submit_react,
76
  outputs=[status_output, results_table]
77
  )
78
+
79
+ run_button_llamaindex.click(
80
+ fn=_run_and_submit_llamaindex,
81
+ outputs=[status_output, results_table]
82
+ )
83
 
84
  test_button = gr.Button("Run Test Examples")
85
  test_results_table = gr.DataFrame(label="Test Answers from Agent", wrap=True)
langgraphagent.py CHANGED
@@ -58,7 +58,7 @@ class LangGraphAgent:
58
  apikey = os.getenv("GOOGLE_API_KEY")
59
 
60
  return ChatGoogleGenerativeAI(
61
- model="gemini-2.5-flash", # Changed from gemini-2.5-flash-lite - better tool calling
62
  temperature=0,
63
  api_key=apikey,
64
  timeout=60 # Add timeout to prevent hanging
 
58
  apikey = os.getenv("GOOGLE_API_KEY")
59
 
60
  return ChatGoogleGenerativeAI(
61
+ model=config.ACTIVE_AGENT_LLM_MODEL,
62
  temperature=0,
63
  api_key=apikey,
64
  timeout=60 # Add timeout to prevent hanging
llamaindexagent.py ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import logging
3
+ import warnings
4
+ import time
5
+ import asyncio
6
+ import nest_asyncio
7
+
8
+ # Apply nest_asyncio to allow nested event loops
9
+ nest_asyncio.apply()
10
+
11
+ # Suppress TensorFlow/Keras warnings
12
+ os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
13
+ logging.getLogger('tensorflow').setLevel(logging.ERROR)
14
+ warnings.filterwarnings('ignore', module='tensorflow')
15
+ warnings.filterwarnings('ignore', module='tf_keras')
16
+
17
+ from llama_index.core.agent import ReActAgent
18
+ from llama_index.llms.gemini import Gemini
19
+ from llama_index.core.tools import FunctionTool
20
+
21
+ from custom_tools import get_custom_tools_list
22
+ from system_prompt import SYSTEM_PROMPT
23
+ from utils import cleanup_answer, extract_text_from_content
24
+ import config
25
+
26
+ # Suppress BeautifulSoup GuessedAtParserWarning
27
+ try:
28
+ from bs4 import GuessedAtParserWarning
29
+ warnings.filterwarnings('ignore', category=GuessedAtParserWarning)
30
+ except ImportError:
31
+ pass
32
+
33
+
34
+ class LlamaIndexAgent:
35
+ """
36
+ LlamaIndex agent implementation using ReActAgent.
37
+
38
+ This agent uses LlamaIndex's ReAct agent pattern which integrates
39
+ with various LLM providers and tools. It provides an alternative
40
+ implementation to LangGraph-based agents.
41
+ """
42
+
43
+ def __init__(self):
44
+ # Validate API keys
45
+ if not os.getenv("GOOGLE_API_KEY"):
46
+ print("WARNING: GOOGLE_API_KEY not found - analyze_youtube_video will fail")
47
+
48
+ self.langchain_tools = get_custom_tools_list()
49
+ self.llm = self._create_llm_client()
50
+ self.tools = self._convert_tools_to_llamaindex()
51
+ self.agent = self._build_agent()
52
+
53
+ def _create_llm_client(self):
54
+ """Create and return the LLM client for LlamaIndex."""
55
+ api_key = os.getenv("GOOGLE_API_KEY")
56
+
57
+ # Create Gemini LLM for LlamaIndex
58
+ llm = Gemini(
59
+ model=config.ACTIVE_AGENT_LLM_MODEL,
60
+ api_key=api_key,
61
+ temperature=config.GEMINI_TEMPERATURE,
62
+ max_tokens=config.GEMINI_MAX_TOKENS,
63
+ )
64
+
65
+ return llm
66
+
67
+ def _convert_tools_to_llamaindex(self) -> list[FunctionTool]:
68
+ """Convert LangChain tools to LlamaIndex FunctionTool format."""
69
+ llamaindex_tools = []
70
+
71
+ for langchain_tool in self.langchain_tools:
72
+ # Extract the function from LangChain tool
73
+ tool_func = langchain_tool.func if hasattr(langchain_tool, 'func') else langchain_tool
74
+
75
+ # Create LlamaIndex FunctionTool
76
+ llamaindex_tool = FunctionTool.from_defaults(
77
+ fn=tool_func,
78
+ name=langchain_tool.name,
79
+ description=langchain_tool.description,
80
+ )
81
+
82
+ llamaindex_tools.append(llamaindex_tool)
83
+
84
+ return llamaindex_tools
85
+
86
+ def _build_agent(self) -> ReActAgent:
87
+ """Build and return the LlamaIndex ReAct agent."""
88
+
89
+ # Create ReAct agent with tools and LLM
90
+ agent = ReActAgent(
91
+ tools=self.tools,
92
+ llm=self.llm,
93
+ verbose=True,
94
+ max_iterations=40, # Match the step limit from other agents
95
+ system_prompt=SYSTEM_PROMPT,
96
+ )
97
+
98
+ return agent
99
+
100
+ def __call__(self, question: str, file_name: str = None) -> str:
101
+ """
102
+ Invoke the LlamaIndex agent with the given question and return the final answer.
103
+
104
+ Args:
105
+ question: The question to answer
106
+ file_name: Optional file name if the question references a file
107
+
108
+ Returns:
109
+ The agent's answer as a string
110
+ """
111
+ print(f"\n{'='*60}")
112
+ print(f"[LLAMAINDEX AGENT START] Question: {question}")
113
+ if file_name:
114
+ print(f"[FILE] {file_name}")
115
+ print(f"{'='*60}")
116
+
117
+ start_time = time.time()
118
+
119
+ try:
120
+ # Build the question with file name if provided
121
+ question_content = question
122
+ if file_name:
123
+ question_content += f'\n\nNote: This question references a file: {file_name}'
124
+
125
+ # Invoke the agent with retry logic for 504 errors
126
+ max_retries = config.MAX_RETRIES
127
+ delay = config.INITIAL_RETRY_DELAY
128
+
129
+ for attempt in range(max_retries + 1):
130
+ try:
131
+ # Create a dedicated async function to run the agent
132
+ async def run_agent_async():
133
+ return await self.agent.run(question_content)
134
+
135
+ # Try different approaches to run the async function
136
+ try:
137
+ # Check if a loop is already running
138
+ asyncio.get_running_loop()
139
+ # If we reach here, a loop is already running
140
+ # Run in a separate thread to avoid "event loop already running" error
141
+ import concurrent.futures
142
+ with concurrent.futures.ThreadPoolExecutor() as executor:
143
+ response = executor.submit(
144
+ lambda: asyncio.run(run_agent_async())
145
+ ).result()
146
+ except RuntimeError:
147
+ # No running loop, we can use asyncio.run directly
148
+ response = asyncio.run(run_agent_async())
149
+
150
+ # Success - break out of retry loop
151
+ break
152
+ except Exception as e:
153
+ error_msg = str(e)
154
+
155
+ # Check if this is a 504 DEADLINE_EXCEEDED error
156
+ if "504" in error_msg and "DEADLINE_EXCEEDED" in error_msg:
157
+ if attempt < max_retries:
158
+ print(f"[RETRY] Attempt {attempt + 1}/{max_retries} failed with 504 DEADLINE_EXCEEDED")
159
+ print(f"[RETRY] Retrying in {delay:.1f} seconds...")
160
+ time.sleep(delay)
161
+ delay *= config.RETRY_BACKOFF_FACTOR
162
+ continue
163
+ else:
164
+ print(f"[RETRY] All {max_retries} retries exhausted for 504 error")
165
+ print(f"[ERROR] Agent invocation failed after retries: {e}")
166
+ return f"Error: Agent failed after {max_retries} retries - {str(e)[:100]}"
167
+ else:
168
+ # Not a 504 error - fail immediately without retry
169
+ print(f"[ERROR] Agent invocation failed: {e}")
170
+ return f"Error: Agent failed - {str(e)[:100]}"
171
+
172
+ elapsed_time = time.time() - start_time
173
+ print(f"[LLAMAINDEX AGENT COMPLETE] Time: {elapsed_time:.2f}s")
174
+ print(f"{'='*60}\n")
175
+
176
+ # Extract the answer from the response using utility function
177
+ # This handles ChatMessage objects, dicts, lists, and strings
178
+ answer = extract_text_from_content(response)
179
+
180
+ if not answer or answer is None:
181
+ print("[WARNING] Agent completed but returned Empty answer")
182
+ return "Error: No answer generated"
183
+
184
+ # Clean up the answer using utility function (includes stripping)
185
+ answer = cleanup_answer(answer)
186
+
187
+ print(f"[FINAL ANSWER] {answer}")
188
+ return answer
189
+
190
+ except Exception as e:
191
+ elapsed_time = time.time() - start_time
192
+ print(f"[LLAMAINDEX AGENT ERROR] Failed after {elapsed_time:.2f}s: {e}")
193
+ print(f"{'='*60}\n")
194
+ return f"Error: {str(e)[:100]}"
reactlanggraphagent.py CHANGED
@@ -49,7 +49,7 @@ class ReActLangGraphAgent:
49
  apikey = os.getenv("GOOGLE_API_KEY")
50
 
51
  return ChatGoogleGenerativeAI(
52
- model=config.GEMINI_MODEL,
53
  temperature=config.GEMINI_TEMPERATURE,
54
  api_key=apikey,
55
  timeout=60
 
49
  apikey = os.getenv("GOOGLE_API_KEY")
50
 
51
  return ChatGoogleGenerativeAI(
52
+ model=config.ACTIVE_AGENT_LLM_MODEL,
53
  temperature=config.GEMINI_TEMPERATURE,
54
  api_key=apikey,
55
  timeout=60
requirements.txt CHANGED
@@ -12,6 +12,9 @@ langchain-core
12
  langchain-google-genai
13
  langchain-huggingface
14
  langchain-community
 
 
 
15
  pypdf
16
  youtube-transcript-api
17
  pytube
 
12
  langchain-google-genai
13
  langchain-huggingface
14
  langchain-community
15
+ llama-index
16
+ llama-index-llms-gemini
17
+ llama-index-core
18
  pypdf
19
  youtube-transcript-api
20
  pytube
utils.py CHANGED
@@ -53,6 +53,7 @@ def extract_text_from_content(content: Any) -> str:
53
  Extract plain text from various content formats returned by LLM agents.
54
 
55
  This function handles multiple content formats:
 
56
  - String: Returns as-is
57
  - Dict with 'text' field: Extracts the text value
58
  - List of content blocks: Extracts text from all blocks with type='text'
@@ -64,6 +65,16 @@ def extract_text_from_content(content: Any) -> str:
64
  Returns:
65
  str: Extracted plain text content
66
  """
 
 
 
 
 
 
 
 
 
 
67
  # Handle dict format (e.g., {'text': 'answer'})
68
  if isinstance(content, dict):
69
  if 'text' in content:
 
53
  Extract plain text from various content formats returned by LLM agents.
54
 
55
  This function handles multiple content formats:
56
+ - ChatMessage objects (LlamaIndex): Extracts the content attribute
57
  - String: Returns as-is
58
  - Dict with 'text' field: Extracts the text value
59
  - List of content blocks: Extracts text from all blocks with type='text'
 
65
  Returns:
66
  str: Extracted plain text content
67
  """
68
+ # Handle LlamaIndex ChatMessage objects
69
+ try:
70
+ from llama_index.core.base.llms.types import ChatMessage
71
+ if isinstance(content, ChatMessage):
72
+ # Extract just the content attribute (without role prefix)
73
+ return str(content.content)
74
+ except ImportError:
75
+ # LlamaIndex not installed, skip this check
76
+ pass
77
+
78
  # Handle dict format (e.g., {'text': 'answer'})
79
  if isinstance(content, dict):
80
  if 'text' in content: