참고소스 수정본

This commit is contained in:
LASTA_DEV01\lasta
2026-05-12 19:40:31 +09:00
parent 0f34a451fc
commit 2e9204243d
8708 changed files with 3259488 additions and 869 deletions

View File

@@ -0,0 +1,174 @@
# 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 with a Cypher template.
This example shows:
1. How to create a tool from a Cypher template
2. How to use the ToolsRetriever to select and execute tools based on a query
"""
import os
from typing import Any, Callable, Dict, Optional
import neo4j
from dotenv import load_dotenv
from neo4j_graphrag.llm.openai_llm import OpenAILLM
from neo4j_graphrag.retrievers.base import Retriever
from neo4j_graphrag.retrievers.tools_retriever import ToolsRetriever
from neo4j_graphrag.types import RawSearchResult, RetrieverResultItem
# Load environment variables from .env file (OPENAI_API_KEY required for this example)
load_dotenv()
# Define database credentials
URI = "neo4j+s://demo.neo4jlabs.com"
AUTH = ("recommendations", "recommendations")
# Cypher template to count actors in a specific movie
CYPHER_TEMPLATE = """
MATCH (m:Movie {title: $title})
OPTIONAL MATCH (m)<-[:ACTED_IN]-(a:Actor)
WITH m, collect(a.name) AS actor_names, count(a) AS actor_count
RETURN m.title AS movie_title,
m.plot AS plot,
m.released AS year,
actor_count,
actor_names
"""
class CypherTemplateRetriever(Retriever):
"""
Custom retriever that executes a parameterized Cypher query template.
"""
def __init__(
self,
driver: neo4j.Driver,
cypher_template: str,
neo4j_database: Optional[str] = None,
result_formatter: Optional[
Callable[[neo4j.Record], RetrieverResultItem]
] = None,
):
"""
Args:
driver: Neo4j driver instance
cypher_template: Cypher query with parameters (e.g., "MATCH (m:Movie {title: $title}) RETURN m")
neo4j_database: Optional database name
result_formatter: Optional function to format results
"""
super().__init__(driver, neo4j_database)
self.cypher_template = cypher_template
self.result_formatter = result_formatter
def get_search_results(
self,
query_vector: Optional[list[float]] = None,
query_text: Optional[str] = None,
top_k: int = 5,
query_params: Optional[Dict[str, Any]] = None,
**kwargs: Any,
) -> RawSearchResult:
"""
Execute the Cypher template with provided parameters.
Args:
query_text: Can be used as a parameter (e.g., for movie title)
query_params: Dictionary of parameters for the Cypher query
**kwargs: Additional parameters
Returns:
RawSearchResult containing neo4j.Record objects
"""
# Prepare parameters for the Cypher query
parameters = query_params or {}
# Optionally use query_text as a parameter
if query_text and "query_text" not in parameters:
parameters["query_text"] = query_text
# Add any additional kwargs as parameters
parameters.update(kwargs)
# Execute the query
try:
records, summary, keys = self.driver.execute_query(
self.cypher_template,
parameters_=parameters,
database_=self.neo4j_database,
routing_=neo4j.RoutingControl.READ,
)
return RawSearchResult(
records=records,
metadata={
"cypher_query": self.cypher_template,
"parameters": parameters,
},
)
except Exception as e:
raise RuntimeError(f"Failed to execute Cypher template: {e}") from e
def main() -> None:
"""Run the example."""
driver = neo4j.GraphDatabase.driver(URI, auth=AUTH)
try:
neo4j_retriever = CypherTemplateRetriever(
driver=driver,
cypher_template=CYPHER_TEMPLATE,
)
# Convert retriever to tool
neo4j_tool = neo4j_retriever.convert_to_tool(
name="movie_info_tool",
description=(
"Retrieves the total number of actors in a specific movie and returns "
"aggregation metrics including actor count, list of actor names, and movie details. "
"Use this when the user asks about cast size, number of actors, or movie cast information."
),
parameter_descriptions={
"query_params": (
"Dictionary containing 'title' key with the movie title to analyze. "
"Example: {'title': 'The Matrix'}"
)
},
)
llm = OpenAILLM(
api_key=os.getenv("OPENAI_API_KEY"),
model_name="gpt-5",
model_params={"temperature": 0.2},
)
tools_retriever = ToolsRetriever(
driver=driver,
llm=llm,
tools=[neo4j_tool],
)
query_text = "How many actors are there in Around the World in 80 Days?"
print("Query:", query_text)
result = tools_retriever.search(query_text=query_text, return_context=True)
print("Result:", result)
finally:
driver.close()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,151 @@
# 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 create multiple domain-specific tools from retrievers.
This example shows:
1. How to create multiple tools from the same retriever type for different use cases
2. How to provide custom parameter descriptions for each tool
3. How type inference works automatically while descriptions are explicit
"""
import neo4j
from typing import cast, Any, Optional
from unittest.mock import MagicMock
from neo4j_graphrag.retrievers.base import Retriever
from neo4j_graphrag.types import RawSearchResult
class MockVectorRetriever(Retriever):
"""A mock vector retriever for demonstration purposes."""
VERIFY_NEO4J_VERSION = False
def __init__(self, driver: neo4j.Driver, index_name: str):
super().__init__(driver)
self.index_name = index_name
def get_search_results(
self,
query_vector: Optional[list[float]] = None,
query_text: Optional[str] = None,
top_k: int = 5,
effective_search_ratio: int = 1,
filters: Optional[dict[str, Any]] = None,
) -> RawSearchResult:
"""Get vector search results (mocked for demonstration)."""
# Return empty results for demo
return RawSearchResult(records=[], metadata={"index": self.index_name})
def main() -> None:
"""Demonstrate creating multiple domain-specific tools from retrievers."""
# Create mock driver (in real usage, this would be actual Neo4j driver)
driver = cast(Any, MagicMock())
# Create retrievers for different domains using the same retriever type
# In practice, these would point to different vector indexes
# Movie recommendations retriever
movie_retriever = MockVectorRetriever(driver=driver, index_name="movie_embeddings")
# Product search retriever
product_retriever = MockVectorRetriever(
driver=driver, index_name="product_embeddings"
)
# Document search retriever
document_retriever = MockVectorRetriever(
driver=driver, index_name="document_embeddings"
)
# Convert each retriever to a domain-specific tool with custom descriptions
# 1. Movie recommendation tool
movie_tool = movie_retriever.convert_to_tool(
name="movie_search",
description="Find movie recommendations based on plot, genre, or actor preferences",
parameter_descriptions={
"query_text": "Movie title, plot description, genre, or actor name",
"query_vector": "Pre-computed embedding vector for movie search",
"top_k": "Number of movie recommendations to return (1-20)",
"filters": "Optional filters for genre, year, rating, etc.",
"effective_search_ratio": "Search pool multiplier for better accuracy",
},
)
# 2. Product search tool
product_tool = product_retriever.convert_to_tool(
name="product_search",
description="Search for products matching customer needs and preferences",
parameter_descriptions={
"query_text": "Product name, description, or customer need",
"query_vector": "Pre-computed embedding for product matching",
"top_k": "Maximum number of product results (1-50)",
"filters": "Filters for price range, brand, category, availability",
"effective_search_ratio": "Breadth vs precision trade-off for search",
},
)
# 3. Document search tool
document_tool = document_retriever.convert_to_tool(
name="document_search",
description="Find relevant documents and knowledge articles",
parameter_descriptions={
"query_text": "Question, keywords, or topic to search for",
"query_vector": "Semantic embedding for document retrieval",
"top_k": "Number of relevant documents to retrieve (1-10)",
"filters": "Document type, date range, or department filters",
},
)
# Demonstrate that each tool has distinct, meaningful descriptions
tools = [movie_tool, product_tool, document_tool]
for tool in tools:
print(f"\n=== {tool.get_name().upper()} ===")
print(f"Description: {tool.get_description()}")
print("Parameters:")
params = tool.get_parameters()
for param_name, param_def in params["properties"].items():
required = (
"required" if param_name in params.get("required", []) else "optional"
)
print(
f" - {param_name} ({param_def['type']}, {required}): {param_def['description']}"
)
# Show how the same parameter type gets different contextual descriptions
print("\n=== PARAMETER COMPARISON ===")
print("Same parameter 'query_text' with different contextual descriptions:")
for tool in tools:
params = tool.get_parameters()
query_text_desc = params["properties"]["query_text"]["description"]
print(f" {tool.get_name()}: {query_text_desc}")
print("\nSame parameter 'top_k' with different contextual descriptions:")
for tool in tools:
params = tool.get_parameters()
top_k_desc = params["properties"]["top_k"]["description"]
print(f" {tool.get_name()}: {top_k_desc}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,114 @@
# 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 convert a retriever to a tool.
This example shows:
1. How to convert a custom StaticRetriever to a Tool using the convert_to_tool method
2. How to define parameters for the tool in the retriever class
3. How to execute the tool
"""
import neo4j
from typing import Optional, Any, cast
from unittest.mock import MagicMock
from neo4j_graphrag.retrievers.base import Retriever
from neo4j_graphrag.types import RawSearchResult
# Create a Retriever that returns static results about Neo4j
# This would illustrate the conversion process of any Retriever (Vector, Hybrid, etc.)
class StaticRetriever(Retriever):
"""A retriever that returns static results 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 static information about Neo4j regardless of the query.
Args:
query_text (Optional[str]): The query about Neo4j (any query will return general Neo4j information)
**kwargs (Any): Additional keyword arguments (not used)
Returns:
RawSearchResult: Static Neo4j information with metadata
"""
# 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})
def main() -> None:
# Convert a StaticRetriever to a tool using the new convert_to_tool method
static_retriever = StaticRetriever(driver=cast(Any, MagicMock()))
# Convert the retriever to a tool with custom parameter descriptions
static_tool = static_retriever.convert_to_tool(
name="Neo4jInfoTool",
description="Get general information about Neo4j graph database",
parameter_descriptions={
"query_text": "Any query about Neo4j (the tool returns general information regardless)"
},
)
# Print tool information
print("Example: StaticRetriever with specific parameters")
print(f"Tool Name: {static_tool.get_name()}")
print(f"Tool Description: {static_tool.get_description()}")
print(f"Tool Parameters: {static_tool.get_parameters()}")
print()
# Execute the tools (in a real application, this would be done by instructions from an LLM)
try:
# Execute the static retriever tool
print("\nExecuting the static retriever tool...")
static_result = static_tool.execute(
query_text="What is Neo4j?",
)
print("Static Search Results:")
for i, item in enumerate(static_result):
print(f"{i + 1}. {str(item)[:100]}...")
except Exception as e:
print(f"Error executing tool: {e}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,335 @@
# 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()