# Copyright (c) "Neo4j" # Neo4j Sweden AB [https://neo4j.com] # # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # # https://www.apache.org/licenses/LICENSE-2.0 # # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ Example demonstrating how to use the ToolsRetriever. This example shows: 1. How to create tools from different retrievers 2. How to use the ToolsRetriever to select and execute tools based on a query """ import os from typing import Any, Optional, cast from unittest.mock import MagicMock from dotenv import load_dotenv import requests from datetime import datetime, date import neo4j from neo4j_graphrag.generation import GraphRAG from neo4j_graphrag.retrievers.base import Retriever from neo4j_graphrag.retrievers.tools_retriever import ToolsRetriever from neo4j_graphrag.types import RawSearchResult from neo4j_graphrag.tool import ( ObjectParameter, StringParameter, Tool, ) from neo4j_graphrag.llm.openai_llm import OpenAILLM # Load environment variables from .env file (OPENAI_API_KEY required for this example) load_dotenv() # Create a Retriever that returns static results about Neo4j class Neo4jInfoRetriever(Retriever): """A retriever that returns general information about Neo4j.""" # Disable Neo4j version verification VERIFY_NEO4J_VERSION = False def __init__(self, driver: neo4j.Driver): # Call the parent class constructor with the driver super().__init__(driver) def get_search_results( self, query_text: Optional[str] = None, **kwargs: Any ) -> RawSearchResult: """Return general information about Neo4j.""" # Create formatted Neo4j information neo4j_info = ( "# Neo4j Graph Database\n\n" "Neo4j is a graph database management system developed by Neo4j, Inc. " "It is an ACID-compliant transactional database with native graph storage and processing.\n\n" "## Key Features:\n\n" "- **Cypher Query Language**: Neo4j's intuitive query language designed specifically for working with graph data\n" "- **Property Graphs**: Both nodes and relationships can have properties (key-value pairs)\n" "- **ACID Compliance**: Ensures data integrity with full transaction support\n" "- **Native Graph Storage**: Optimized storage for graph data structures\n" "- **High Availability**: Clustering for enterprise deployments\n" "- **Scalability**: Handles billions of nodes and relationships" ) # Create a Neo4j record with the information records = [neo4j.Record({"result": neo4j_info})] # Return a RawSearchResult with the records and metadata return RawSearchResult(records=records, metadata={"query": query_text}) class CalendarTool(Tool): """A simple tool to get calendar information.""" def __init__(self) -> None: """Initialize the calendar tool.""" # Define parameters for the tool parameters = ObjectParameter( description="Parameters for calendar information retrieval", properties={ "date": StringParameter( description="The date to check events for in YYYY-MM-DD format (e.g., 2025-04-14)", ), }, required_properties=["date"], ) # Sample calendar data with fixed dates self.calendar_data = { "2025-04-15": [ {"time": "10:00", "title": "Dentist Appointment"}, {"time": "14:00", "title": "Conference Call"}, ], "2025-04-16": [], } # Define a wrapper function that handles parameters correctly def execute_func(**kwargs: Any) -> str: return self.execute_calendar(**kwargs) super().__init__( name="calendar_tool", description="Check calendar events for a specific date in YYYY-MM-DD format", parameters=parameters, execute_func=execute_func, ) def execute_calendar(self, **kwargs: Any) -> str: """Execute the calendar tool. Args: **kwargs: Dictionary of parameters, including 'date'. Returns: str: The events for the specified date. """ date = kwargs.get("date") if not date: return "Error: No date provided" # Check for events on the date if date in self.calendar_data: events_list = self.calendar_data[date] if not events_list: return f"No events scheduled for {date}" events_str = "\n".join( f"- {event.get('time', 'All day')}: {event.get('title', 'Untitled event')}" for event in events_list ) return f"Events for {date}:\n{events_str}" else: return f"No events found for {date}" class WeatherTool(Tool): """A tool to fetch weather in Malmö, Sweden for a specific date.""" def __init__(self) -> None: """Initialize the weather tool.""" parameters = ObjectParameter( description="Parameters for fetching weather information about a date.", properties={ "date": StringParameter( description='The date, in YYYY-MM-DD format. Example: "2025-04-25"' ) }, required_properties=["date"], ) super().__init__( name="weather_tool", description="Check for weather for a specific date in YYYY-MM-DD format", parameters=parameters, execute_func=self.execute_weather_retrieval, ) def execute_weather_retrieval(self, **kwargs: Any) -> str: """Fetch historical weather data for a given date in Malmö, Sweden.""" date_str = kwargs.get("date") if not date_str: return "Error: Date not provided for weather lookup." try: input_date = datetime.strptime(date_str, "%Y-%m-%d").date() except ValueError: return f"Error: Invalid date format '{date_str}'. Please use YYYY-MM-DD." today_date = date.today() if input_date < today_date: api_url = "https://archive-api.open-meteo.com/v1/archive" else: # For today or future dates, use the forecast API # Note: Forecast API typically has a limit (e.g., 16 days into the future) api_url = "https://api.open-meteo.com/v1/forecast" params = { "latitude": 55.6059, # Malmö, Sweden "longitude": 13.0007, # Malmö, Sweden "start_date": date_str, "end_date": date_str, "daily": "temperature_2m_max,sunshine_duration", "timezone": "Europe/Stockholm", } headers = {"Accept": "application/json"} try: response = requests.get(api_url, headers=headers, params=params) response.raise_for_status() data = response.json() # Try to access keys directly, relying on the existing broader except block for errors daily = data["daily"] temp_max = daily["temperature_2m_max"][0] sunshine_seconds = daily["sunshine_duration"][0] sunshine_hours = 0 if ( sunshine_seconds is not None ): # API might return null for sunshine_duration sunshine_hours = round(sunshine_seconds / 3600, 1) return ( f"Weather for Malmö, Sweden on this day:\n" f"- Max Temperature: {temp_max}°C\n" f"- Sunshine Duration: {sunshine_hours} hours" ) except requests.exceptions.RequestException as e: return f"API request failed for weather data: {e}" except ( ValueError, KeyError, ) as e: return f"Error parsing weather data for Malmö on {date_str}: {e}" def main() -> None: """Run the example.""" # Create a mock Neo4j driver driver = cast(neo4j.Driver, MagicMock()) # Create retrievers neo4j_retriever = Neo4jInfoRetriever(driver=driver) # Convert retrievers to tools neo4j_tool = neo4j_retriever.convert_to_tool( name="neo4j_info_tool", description="Get information about Neo4j graph database", parameter_descriptions={ "query_text": "The query about Neo4j", }, ) # Create a calendar tool calendar_tool = CalendarTool() # Create a weather tool weather_tool = WeatherTool() # Create an OpenAI LLM llm = OpenAILLM( api_key=os.getenv("OPENAI_API_KEY"), model_name="gpt-5", model_params={ "temperature": 0.2, }, ) # Print tool information for debugging print("\nTool Information:") print(f"Neo4j Tool: {neo4j_tool.get_name()}, {neo4j_tool.get_description()}") print( f"Calendar Tool: {calendar_tool.get_name()}, {calendar_tool.get_description()}" ) parameters_description = ( weather_tool._parameters.description if weather_tool._parameters else "No parameters description" ) print( f"Weather Tool: {weather_tool.get_name()}, {weather_tool.get_description()}: {parameters_description}" ) # Create a ToolsRetriever with the LLM and tools tools_retriever = ToolsRetriever( driver=driver, llm=llm, tools=[neo4j_tool, calendar_tool, weather_tool], ) # Test queries test_queries = [ "What is Neo4j?", "Do I have any meetings the 15th of April 2025?", "Any information about 2025-04-16?", ] # Run just the tools retriever directly to show metadata etc. print(f"\n\n{'=' * 80}") print("Retriever call examples, to show metadata etc.") print(f"{'=' * 80}") for query in test_queries: print(f"Query: {query}") try: # Get search results through the ToolsRetriever result = tools_retriever.get_search_results(query_text=query) # Print metadata if result.metadata is not None: print(f"\nTools selected: {result.metadata.get('tools_selected', [])}") if result.metadata.get("error", ""): print(f"Error: {result.metadata.get('error', '')}") # Print results print("\nRESULTS:") for i, record in enumerate(result.records): print(f"\n--- Result {i + 1} ---") print(f"Content: {record.get('content', 'N/A')}") print(f"Tool: {record.get('tool_name', 'Unknown')}") if record.get("metadata"): print(f"Metadata: {record.get('metadata')}") except Exception as e: print(f"Error: {str(e)}") print(f"{'=' * 80}") # For demo purposes, run the queries through GraphRAG to get text input -> text output print(f"\n\n{'=' * 80}") print("Full GraphRAG examples") print(f"{'=' * 80}") for query in test_queries: print(f"Query: {query}") # Full GraphRAG example graphrag = GraphRAG( llm=llm, retriever=tools_retriever, ) rag_result = graphrag.search(query_text=query, return_context=False) print(f"Answer: {rag_result.answer}") print(f"{'=' * 80}") if __name__ == "__main__": main()