test_bot / agents /tool_agent.py
rohitchandra's picture
tract pdfs with git lfs
d6ea378
"""Tool-Using Agent implementation.
This implementation focuses on:
- Converting tools to use with LangGraph
- Using the ReAct pattern for autonomous tool selection
- Handling calculator, datetime, and weather queries
"""
from typing import Dict, List, Optional, Any, Annotated
import io
import contextlib
from langchain_core.tools import tool
from langchain.chat_models import init_chat_model
from langgraph.prebuilt import create_react_agent
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.messages import HumanMessage, AIMessage
from core.chat_interface import ChatInterface
from tools.calculator import Calculator
from dotenv import load_dotenv
load_dotenv()
class ToolUsingAgentChat(ChatInterface):
"""Tool-using agent implementation with calculator, datetime, and weather tools."""
def __init__(self):
self.llm = None
self.tools = []
self.graph = None
def initialize(self) -> None:
"""Initialize components for the tool-using agent.
This sets up:
- The chat model
- Tools for calculator, DateTime, and weather
- The ReAct agent using LangGraph
"""
# Initialize chat model
self.llm = init_chat_model("gpt-4o", model_provider="openai")
# Create tools
self.tools = self._create_tools()
# Create the ReAct agent graph with the tools
# The agent will autonomously decide which tools to use based on the query
self.graph = create_react_agent(
model=self.llm,
tools=self.tools,
)
def _create_tools(self) -> List[Any]:
"""Create and return the list of tools for the agent.
Returns:
List: List of tool objects
"""
# Calculator tool for mathematical operations
@tool
def calculator(expression: Annotated[str, "The mathematical expression to evaluate"]) -> str:
"""Evaluate a mathematical expression using basic arithmetic operations (+, -, *, /, %, //).
Examples: '5 + 3', '10 * (2 + 3)', '15 / 3', '17 % 5', '20 // 3'
"""
result = Calculator.evaluate_expression(expression)
if isinstance(result, str) and result.startswith("Error"):
raise ValueError(result)
return str(result)
# DateTime tool for date/time operations
@tool
def execute_datetime_code(code: Annotated[str, "Python code to execute for datetime operations"]) -> str:
"""Execute Python code for datetime operations. The code should use datetime or time modules.
Examples:
- 'print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))'
- 'print(datetime.datetime.now().strftime("%A, %B %d, %Y"))'
- 'print(datetime.datetime.now().year)'
- 'print((datetime.datetime(2025, 12, 25) - datetime.datetime.now()).days)'
"""
output_buffer = io.StringIO()
code = f"import datetime\nimport time\n{code}"
try:
with contextlib.redirect_stdout(output_buffer):
exec(code)
return output_buffer.getvalue().strip()
except Exception as e:
raise ValueError(f"Error executing datetime code: {str(e)}")
# Weather tool using Tavily search
@tool
def get_weather(location: Annotated[str, "The location to get weather for (city, country)"]) -> str:
"""Get the current weather for a given location using web search.
Examples: 'New York, USA', 'London, UK', 'Tokyo, Japan', 'Paris, France'
"""
search = TavilySearchResults(max_results=3)
query = f"current weather temperature conditions {location} today"
results = search.invoke(query)
if not results:
return f"Could not find weather information for {location}"
# Combine results for better coverage
weather_info = []
for result in results[:2]: # Use top 2 results
content = result.get("content", "")
if content:
weather_info.append(content)
if weather_info:
return " ".join(weather_info)
else:
return f"Could not find detailed weather information for {location}"
return [calculator, execute_datetime_code, get_weather]
def _convert_history_to_messages(self, chat_history: Optional[List[Dict[str, str]]]) -> List:
"""Convert chat history to LangChain message format.
Args:
chat_history: List of dicts with 'role' and 'content' keys
Returns:
List of LangChain message objects
"""
messages = []
if chat_history:
for msg in chat_history:
if msg["role"] == "user":
messages.append(HumanMessage(content=msg["content"]))
elif msg["role"] == "assistant":
messages.append(AIMessage(content=msg["content"]))
return messages
def process_message(self, message: str, chat_history: Optional[List[Dict[str, str]]] = None) -> str:
"""Process a message using the tool-using agent.
The ReAct agent will:
1. Consider the full conversation history
2. Analyze the query in context
3. Decide which tool(s) to use
4. Execute the tool(s)
5. Formulate a response
Args:
message: The user's input message
chat_history: List of previous chat messages
Returns:
str: The assistant's response
"""
try:
# Convert chat history to messages
history_messages = self._convert_history_to_messages(chat_history)
# Add the current message
history_messages.append(HumanMessage(content=message))
# Run the graph with the full conversation history
result = self.graph.invoke({"messages": history_messages})
# Run the graph with the user's message
# result = self.graph.invoke({"messages": [("user", message)]})
# Extract the final response
if result.get("messages"):
final_message = result["messages"][-1]
# Handle different message formats
if hasattr(final_message, 'content'):
return final_message.content
else:
return str(final_message)
else:
return "I couldn't process that request. Please try rephrasing."
except Exception as e:
print(f"Error in tool agent: {e}")
return f"I encountered an error while processing your request: {str(e)}. Please try again."