agercas commited on
Commit
b6088cd
·
1 Parent(s): 283931c

refactor code

Browse files
src/agent.py DELETED
File without changes
src/agents/langgraph_agent.py ADDED
@@ -0,0 +1,303 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Annotated, Sequence, TypedDict, Literal
2
+ from langchain_core.messages import BaseMessage, SystemMessage, HumanMessage, ToolMessage
3
+ from langchain_core.runnables import RunnableConfig
4
+ from langgraph.graph.message import add_messages
5
+ from langgraph.graph import StateGraph, END
6
+ from pydantic import BaseModel, Field
7
+ from langchain.chat_models import init_chat_model
8
+ import json
9
+
10
+ # Import tools
11
+ from langchain_community.tools import DuckDuckGoSearchRun
12
+ from langchain_community.tools.pubmed.tool import PubmedQueryRun
13
+ from langchain_community.tools.semanticscholar.tool import SemanticScholarQueryRun
14
+ from langchain_community.tools.arxiv import ArxivQueryRun
15
+ from langchain_community.tools.wikidata.tool import WikidataQueryRun
16
+ from langchain_community.tools import WikipediaQueryRun
17
+ from langchain_community.utilities import WikipediaAPIWrapper
18
+ from langchain_experimental.utilities import PythonREPL
19
+ from langchain_core.tools import Tool
20
+
21
+ # Set up tools
22
+ python_repl = PythonREPL()
23
+ repl_tool = Tool(
24
+ name="python_repl",
25
+ description="A Python shell. Use this to execute python commands. Input should be a valid python command. If you want to see the output of a value, you should print it out with `print(...)`.",
26
+ func=python_repl.run,
27
+ )
28
+
29
+ # Initialize all tools
30
+ tools = [
31
+ DuckDuckGoSearchRun(),
32
+ PubmedQueryRun(),
33
+ SemanticScholarQueryRun(),
34
+ ArxivQueryRun(),
35
+ WikidataQueryRun(),
36
+ WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper()),
37
+ repl_tool
38
+ ]
39
+
40
+ # Initialize Gemini model
41
+ model = init_chat_model("gemini-2.0-flash", model_provider="google_genai")
42
+ model_with_tools = model.bind_tools(tools)
43
+
44
+ # Create tools lookup
45
+ tools_by_name = {tool.name: tool for tool in tools}
46
+
47
+ # Pydantic models for structured output
48
+ class ToolSufficiencyResponse(BaseModel):
49
+ """Response for tool sufficiency check"""
50
+ sufficient: bool = Field(description="Whether the available tools are sufficient to answer the question")
51
+ reasoning: str = Field(description="Brief reasoning for the decision")
52
+
53
+ class FinalAnswer(BaseModel):
54
+ """Final answer structure"""
55
+ answer: str = Field(description="The comprehensive answer to the user's question")
56
+ confidence: Literal["high", "medium", "low"] = Field(description="Confidence level in the answer")
57
+ sources_used: list[str] = Field(description="List of tools/sources that were used to generate the answer")
58
+
59
+ # Define graph state
60
+ class AgentState(TypedDict):
61
+ """The state of the agent."""
62
+ messages: Annotated[Sequence[BaseMessage], add_messages]
63
+ llm_call_count: int
64
+ max_llm_calls: int
65
+
66
+ # Node functions
67
+ def check_tool_sufficiency(state: AgentState, config: RunnableConfig):
68
+ """Check if available tools are sufficient to answer the question"""
69
+
70
+ # Get the user's question
71
+ user_message = None
72
+ for msg in state["messages"]:
73
+ if msg.type == "human":
74
+ user_message = msg.content
75
+ break
76
+
77
+ # Create system prompt for sufficiency check
78
+ available_tools_desc = "\n".join([f"- {tool.name}: {tool.description}" for tool in tools])
79
+
80
+ system_prompt = f"""You are an AI assistant that needs to determine if the available tools are sufficient to answer a user's question.
81
+
82
+ Available tools:
83
+ {available_tools_desc}
84
+
85
+ Your task is to analyze the user's question and determine if these tools provide sufficient capability to answer it comprehensively.
86
+
87
+ Consider:
88
+ - Can the question be answered with web search, academic papers, or computational tools?
89
+ - Does the question require real-time data, personal information, or capabilities not available through these tools?
90
+ - Can you break down the question into parts that these tools can handle?
91
+
92
+ Be generous in your assessment - if there's a reasonable path to answer the question using these tools, respond with sufficient=True."""
93
+
94
+ # Use structured output for sufficiency check
95
+ structured_model = model.with_structured_output(ToolSufficiencyResponse)
96
+
97
+ messages = [
98
+ SystemMessage(content=system_prompt),
99
+ HumanMessage(content=f"Question to analyze: {user_message}")
100
+ ]
101
+
102
+ response = structured_model.invoke(messages, config)
103
+
104
+ # Add response to messages for context
105
+ response_message = SystemMessage(
106
+ content=f"Tool sufficiency check: {'Sufficient' if response.sufficient else 'Insufficient'}. Reasoning: {response.reasoning}"
107
+ )
108
+
109
+ return {
110
+ "messages": [response_message],
111
+ "tool_sufficiency": response.sufficient
112
+ }
113
+
114
+ def call_model(state: AgentState, config: RunnableConfig):
115
+ """Call the model (ReAct agent LLM node)"""
116
+
117
+ system_prompt = SystemMessage(
118
+ content="""You are a helpful AI assistant with access to various tools. Use the tools available to you to answer the user's question comprehensively.
119
+
120
+ Think step by step:
121
+ 1. Analyze what information you need
122
+ 2. Use appropriate tools to gather that information
123
+ 3. Synthesize the information to provide a complete answer
124
+
125
+ Be thorough but efficient with your tool usage."""
126
+ )
127
+
128
+ response = model_with_tools.invoke([system_prompt] + state["messages"], config)
129
+
130
+ # Increment LLM call count
131
+ new_count = state.get("llm_call_count", 0) + 1
132
+
133
+ return {
134
+ "messages": [response],
135
+ "llm_call_count": new_count
136
+ }
137
+
138
+ def tool_node(state: AgentState):
139
+ """Execute tools based on the last message's tool calls"""
140
+ outputs = []
141
+ last_message = state["messages"][-1]
142
+
143
+ for tool_call in last_message.tool_calls:
144
+ try:
145
+ tool_result = tools_by_name[tool_call["name"]].invoke(tool_call["args"])
146
+ outputs.append(
147
+ ToolMessage(
148
+ content=str(tool_result),
149
+ name=tool_call["name"],
150
+ tool_call_id=tool_call["id"],
151
+ )
152
+ )
153
+ except Exception as e:
154
+ outputs.append(
155
+ ToolMessage(
156
+ content=f"Error executing tool {tool_call['name']}: {str(e)}",
157
+ name=tool_call["name"],
158
+ tool_call_id=tool_call["id"],
159
+ )
160
+ )
161
+
162
+ return {"messages": outputs}
163
+
164
+ def final_answer_node(state: AgentState, config: RunnableConfig):
165
+ """Generate final structured answer based on conversation history"""
166
+
167
+ system_prompt = SystemMessage(
168
+ content="""You are tasked with providing a final, comprehensive answer based on the conversation history and tool usage.
169
+
170
+ Analyze all the information gathered from the tools and provide:
171
+ 1. A clear, comprehensive answer to the original question
172
+ 2. Your confidence level in this answer
173
+ 3. The sources/tools that were used
174
+
175
+ Be honest about limitations and indicate your confidence level appropriately."""
176
+ )
177
+
178
+ # Get the original user question
179
+ user_question = None
180
+ for msg in state["messages"]:
181
+ if msg.type == "human":
182
+ user_question = msg.content
183
+ break
184
+
185
+ # Create structured output model
186
+ structured_model = model.with_structured_output(FinalAnswer)
187
+
188
+ messages = [
189
+ system_prompt,
190
+ HumanMessage(content=f"Original question: {user_question}"),
191
+ SystemMessage(content="Based on the following conversation history, provide your final answer:")
192
+ ] + state["messages"]
193
+
194
+ response = structured_model.invoke(messages, config)
195
+
196
+ return {
197
+ "messages": [SystemMessage(content=f"Final Answer: {response.answer}")],
198
+ "final_answer": response
199
+ }
200
+
201
+ # Edge functions
202
+ def should_continue_sufficiency(state: AgentState):
203
+ """Decide whether tools are sufficient"""
204
+ # Check if we have a tool sufficiency result
205
+ for msg in reversed(state["messages"]):
206
+ if "Tool sufficiency check: Sufficient" in msg.content:
207
+ return "sufficient"
208
+ elif "Tool sufficiency check: Insufficient" in msg.content:
209
+ return "insufficient"
210
+ return "insufficient" # Default to insufficient if unclear
211
+
212
+ def should_continue_react(state: AgentState):
213
+ """Decide whether to continue with ReAct loop or move to final answer"""
214
+ messages = state["messages"]
215
+ last_message = messages[-1]
216
+ llm_call_count = state.get("llm_call_count", 0)
217
+ max_calls = state.get("max_llm_calls", 4)
218
+
219
+ # If we've reached the maximum number of LLM calls, force stop
220
+ if llm_call_count >= max_calls:
221
+ return "final_answer"
222
+
223
+ # If there are no tool calls, we're done with ReAct loop
224
+ if not hasattr(last_message, 'tool_calls') or not last_message.tool_calls:
225
+ return "final_answer"
226
+
227
+ # Otherwise continue with tools
228
+ return "continue"
229
+
230
+ # Build the graph
231
+ def create_react_agent_graph():
232
+ """Create and return the compiled ReAct agent graph"""
233
+
234
+ workflow = StateGraph(AgentState)
235
+
236
+ # Add nodes
237
+ workflow.add_node("check_sufficiency", check_tool_sufficiency)
238
+ workflow.add_node("agent", call_model)
239
+ workflow.add_node("tools", tool_node)
240
+ workflow.add_node("final_answer", final_answer_node)
241
+
242
+ # Set entry point
243
+ workflow.set_entry_point("check_sufficiency")
244
+
245
+ # Add conditional edge from sufficiency check
246
+ workflow.add_conditional_edges(
247
+ "check_sufficiency",
248
+ should_continue_sufficiency,
249
+ {
250
+ "sufficient": "agent",
251
+ "insufficient": END
252
+ }
253
+ )
254
+
255
+ # Add conditional edge from agent
256
+ workflow.add_conditional_edges(
257
+ "agent",
258
+ should_continue_react,
259
+ {
260
+ "continue": "tools",
261
+ "final_answer": "final_answer"
262
+ }
263
+ )
264
+
265
+ # Add edge from tools back to agent
266
+ workflow.add_edge("tools", "agent")
267
+
268
+ # Add edge from final_answer to END
269
+ workflow.add_edge("final_answer", END)
270
+
271
+ return workflow.compile()
272
+
273
+ # Helper function for running the agent
274
+ def run_agent(question: str, max_llm_calls: int = 4):
275
+ """Run the ReAct agent with a question"""
276
+
277
+ graph = create_react_agent_graph()
278
+
279
+ initial_state = {
280
+ "messages": [HumanMessage(content=question)],
281
+ "llm_call_count": 0,
282
+ "max_llm_calls": max_llm_calls
283
+ }
284
+
285
+ # Stream the execution
286
+ print(f"Question: {question}")
287
+ print("=" * 50)
288
+
289
+ for step in graph.stream(initial_state):
290
+ for node, output in step.items():
291
+ print(f"\n--- {node.upper()} ---")
292
+ if "messages" in output and output["messages"]:
293
+ for msg in output["messages"]:
294
+ if hasattr(msg, 'content'):
295
+ print(f"{msg.__class__.__name__}: {msg.content}")
296
+ elif hasattr(msg, 'tool_calls') and msg.tool_calls:
297
+ print(f"Tool calls: {[tc['name'] for tc in msg.tool_calls]}")
298
+
299
+ if "final_answer" in output:
300
+ print(f"\nFINAL STRUCTURED ANSWER:")
301
+ print(f"Answer: {output['final_answer'].answer}")
302
+ print(f"Confidence: {output['final_answer'].confidence}")
303
+ print(f"Sources: {output['final_answer'].sources_used}")
src/agents/smolagents_agent.py ADDED
@@ -0,0 +1,240 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Multi-Agent System for GAIA Benchmark using smolagents
3
+ Architecture: Coordinator -> Specialized Agents
4
+ """
5
+
6
+ from typing import Any
7
+
8
+ from smolagents import CodeAgent, HfApiModel
9
+
10
+ from src.tools import all_tools
11
+
12
+
13
+ class GAIAMultiAgentSystem:
14
+ """
15
+ Multi-agent system designed for GAIA benchmark tasks.
16
+ Uses a coordinator agent that delegates to specialized agents.
17
+ """
18
+
19
+ def __init__(self, model_config: dict[str, Any] | None = None):
20
+ """
21
+ Initialize the multi-agent system.
22
+
23
+ Args:
24
+ model_config: Configuration for the language model
25
+ e.g., {"model_id": "Qwen/Qwen2.5-Coder-32B-Instruct", "provider": "together"}
26
+ """
27
+ model_config = model_config or {}
28
+ self.model = HfApiModel(**model_config)
29
+ # self.model = InferenceClientModel(**model_config)
30
+ self.agents = {}
31
+ self._setup_agents()
32
+ self._setup_coordinator()
33
+
34
+ def _setup_agents(self):
35
+ """Set up all specialized agents with their respective tools."""
36
+
37
+ # Search Agent - Information retrieval
38
+ search_tools = [
39
+ # Assuming these are your actual tool instances
40
+ # Replace with actual tool references from all_tools
41
+ "wikipedia_search",
42
+ "wikipedia_search_tool",
43
+ "duckduckgo_search",
44
+ "web_search_duckduckgo",
45
+ "arxiv_search",
46
+ "fetch_webpage_content",
47
+ ]
48
+
49
+ self.agents["search_agent"] = CodeAgent(
50
+ model=self.model,
51
+ tools=[tool for tool in all_tools if tool.name in search_tools],
52
+ name="search_agent",
53
+ description="Retrieves factual information and background data from various sources including Wikipedia, web search, and academic papers",
54
+ verbosity_level=1,
55
+ max_steps=10,
56
+ )
57
+
58
+ # Document Agent - Document processing
59
+ document_tools = ["load_csv_file", "load_excel_file", "read_text_file", "transcribe_audio_file"]
60
+
61
+ self.agents["document_agent"] = CodeAgent(
62
+ model=self.model,
63
+ tools=[tool for tool in all_tools if tool.name in document_tools],
64
+ name="document_agent",
65
+ description="Loads and processes structured and unstructured documents including CSV, Excel, text files, and audio transcriptions",
66
+ verbosity_level=1,
67
+ max_steps=8,
68
+ )
69
+
70
+ # Vision Agent - Image processing
71
+ vision_tools = ["ocr_tool", "image_captioning_tool", "visual_qa_tool"]
72
+
73
+ self.agents["vision_agent"] = CodeAgent(
74
+ model=self.model,
75
+ tools=[tool for tool in all_tools if tool.name in vision_tools],
76
+ name="vision_agent",
77
+ description="Extracts text and meaning from images using OCR, captioning, and visual question answering",
78
+ verbosity_level=1,
79
+ max_steps=6,
80
+ )
81
+
82
+ # Reasoning Agent - Logic and analysis
83
+ reasoning_tools = ["analyze_chess_position", "analyze_table_commutativity", "count_items_in_list"]
84
+
85
+ self.agents["reasoning_agent"] = CodeAgent(
86
+ model=self.model,
87
+ tools=[tool for tool in all_tools if tool.name in reasoning_tools],
88
+ name="reasoning_agent",
89
+ description="Performs symbolic reasoning, logical pattern recognition, and analytical tasks",
90
+ verbosity_level=1,
91
+ max_steps=8,
92
+ )
93
+
94
+ # Language Agent - Text processing
95
+ language_tools = ["reverse_string", "reverse_words_in_string"]
96
+
97
+ # Note: Language agent might need additional string manipulation tools
98
+ self.agents["language_agent"] = CodeAgent(
99
+ model=self.model,
100
+ tools=[tool for tool in all_tools if tool.name in language_tools],
101
+ name="language_agent",
102
+ description="Handles low-level text transformations and string manipulations",
103
+ verbosity_level=1,
104
+ max_steps=5,
105
+ )
106
+
107
+ # Coding Agent - Python execution and logic
108
+ self.agents["coding_agent"] = CodeAgent(
109
+ model=self.model,
110
+ tools=[], # Uses implicit code execution capabilities
111
+ name="coding_agent",
112
+ description="Executes Python code and performs computational logic through code interpretation",
113
+ additional_authorized_imports=[
114
+ "pandas",
115
+ "numpy",
116
+ "matplotlib",
117
+ "json",
118
+ "re",
119
+ "datetime",
120
+ "math",
121
+ "statistics",
122
+ "itertools",
123
+ ],
124
+ verbosity_level=1,
125
+ max_steps=10,
126
+ )
127
+
128
+ def _setup_coordinator(self):
129
+ """Set up the coordinator agent that manages other agents."""
130
+
131
+ # Collect all managed agents
132
+ managed_agents = list(self.agents.values())
133
+
134
+ self.coordinator = CodeAgent(
135
+ model=self.model,
136
+ tools=[], # Coordinator has no direct tools
137
+ managed_agents=managed_agents,
138
+ name="coordinator",
139
+ description="Coordinates and delegates tasks to specialized agents based on task requirements",
140
+ planning_interval=3, # Plan every 3 steps
141
+ verbosity_level=2,
142
+ max_steps=20,
143
+ )
144
+
145
+ def analyze_task(self, task: str) -> dict[str, Any]:
146
+ """
147
+ Analyze a GAIA task to determine which agents might be needed.
148
+
149
+ Args:
150
+ task: The task description
151
+
152
+ Returns:
153
+ Dictionary with task analysis
154
+ """
155
+ analysis_prompt = f"""
156
+ Analyze this GAIA benchmark task and determine which types of agents would be most useful:
157
+
158
+ Task: {task}
159
+
160
+ Available agent types:
161
+ - search_agent: For finding factual information online
162
+ - document_agent: For processing files (CSV, Excel, text, audio)
163
+ - vision_agent: For analyzing images
164
+ - reasoning_agent: For logical analysis and pattern recognition
165
+ - language_agent: For text transformations
166
+ - coding_agent: For computational tasks and data processing
167
+
168
+ Provide a brief analysis of what agents would be needed and why.
169
+ """
170
+
171
+ # Use the coordinator's model for analysis
172
+ response = self.model([{"role": "user", "content": analysis_prompt}])
173
+ return {"analysis": response.content, "task": task}
174
+
175
+ def solve_task(self, task: str, context: str | None = None) -> Any:
176
+ """
177
+ Solve a GAIA benchmark task using the multi-agent system.
178
+
179
+ Args:
180
+ task: The task to solve
181
+ context: Optional additional context
182
+
183
+ Returns:
184
+ The result from the coordinator agent
185
+ """
186
+
187
+ # Prepare the enhanced prompt for the coordinator
188
+ enhanced_task = f"""
189
+ You are coordinating a team of specialized agents to solve this GAIA benchmark task.
190
+
191
+ TASK: {task}
192
+
193
+ {f"CONTEXT: {context}" if context else ""}
194
+
195
+ Available agents and their capabilities:
196
+ - search_agent: Retrieves information from Wikipedia, web search, ArXiv
197
+ - document_agent: Processes CSV, Excel, text files, and audio transcriptions
198
+ - vision_agent: Analyzes images with OCR, captioning, and visual QA
199
+ - reasoning_agent: Performs logical analysis and pattern recognition
200
+ - language_agent: Handles text transformations and string operations
201
+ - coding_agent: Executes Python code for computational tasks
202
+
203
+ Strategy:
204
+ 1. Analyze what type of information or processing is needed
205
+ 2. Delegate to appropriate specialized agents
206
+ 3. Combine results from multiple agents if needed
207
+ 4. Provide a final comprehensive answer
208
+
209
+ Be systematic and thorough. Use multiple agents when the task requires different types of expertise.
210
+ """
211
+
212
+ return self.coordinator.run(enhanced_task)
213
+
214
+ def get_agent_info(self) -> dict[str, dict]:
215
+ """Get information about all agents in the system."""
216
+ info = {}
217
+ for name, agent in self.agents.items():
218
+ info[name] = {
219
+ "description": agent.description,
220
+ "tools": [tool.name for tool in agent.tools] if hasattr(agent, "tools") else [],
221
+ "max_steps": agent.max_steps,
222
+ }
223
+
224
+ info["coordinator"] = {
225
+ "description": self.coordinator.description,
226
+ "managed_agents": [agent.name for agent in self.coordinator.managed_agents],
227
+ "max_steps": self.coordinator.max_steps,
228
+ }
229
+
230
+ return info
231
+
232
+ def visualize_system(self):
233
+ """Visualize the multi-agent system structure."""
234
+ if hasattr(self.coordinator, "visualize"):
235
+ return self.coordinator.visualize()
236
+ else:
237
+ print("System Structure:")
238
+ print("Coordinator")
239
+ for agent_name in self.agents.keys():
240
+ print(f" └── {agent_name}")
src/{tools.py → tools/custom_tools.py} RENAMED
File without changes