참고소스 수정본
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
from neo4j_graphrag.llm import AnthropicLLM, LLMResponse
|
||||
|
||||
# set api key here on in the ANTHROPIC_API_KEY env var
|
||||
api_key = None
|
||||
|
||||
with AnthropicLLM(
|
||||
model_name="claude-3-opus-20240229",
|
||||
model_params={"max_tokens": 1000}, # max_tokens must be specified
|
||||
api_key=api_key,
|
||||
) as llm:
|
||||
res: LLMResponse = llm.invoke("say something")
|
||||
print(res.content)
|
||||
@@ -0,0 +1,22 @@
|
||||
from botocore.exceptions import ClientError, NoCredentialsError, PartialCredentialsError
|
||||
|
||||
from neo4j_graphrag.llm import BedrockLLM
|
||||
|
||||
# AWS credentials are read from environment or ~/.aws/credentials
|
||||
llm = BedrockLLM(
|
||||
model_name="us.anthropic.claude-sonnet-4-20250514-v1:0",
|
||||
model_params={"temperature": 0.7, "maxTokens": 1024},
|
||||
region_name="us-east-1",
|
||||
)
|
||||
|
||||
try:
|
||||
res = llm.invoke("say something")
|
||||
print(res.content)
|
||||
except NoCredentialsError:
|
||||
print(
|
||||
"AWS credentials not found. Run 'aws configure' or set environment variables."
|
||||
)
|
||||
except PartialCredentialsError as e:
|
||||
print(f"Incomplete AWS credentials: {e}")
|
||||
except ClientError as e:
|
||||
print(f"AWS API error: {e}")
|
||||
@@ -0,0 +1,11 @@
|
||||
from neo4j_graphrag.llm import CohereLLM, LLMResponse
|
||||
|
||||
# set api key here on in the CO_API_KEY env var
|
||||
api_key = None
|
||||
|
||||
with CohereLLM(
|
||||
model_name="command-r",
|
||||
api_key=api_key,
|
||||
) as llm:
|
||||
res: LLMResponse = llm.invoke("say something")
|
||||
print(res.content)
|
||||
@@ -0,0 +1,83 @@
|
||||
import random
|
||||
import string
|
||||
from typing import Any, Awaitable, Callable, List, Optional, TypeVar, Union
|
||||
|
||||
from neo4j_graphrag.llm import LLMInterface, LLMResponse
|
||||
from neo4j_graphrag.utils.rate_limit import (
|
||||
RateLimitHandler,
|
||||
# rate_limit_handler,
|
||||
# async_rate_limit_handler,
|
||||
)
|
||||
from neo4j_graphrag.message_history import MessageHistory
|
||||
from neo4j_graphrag.types import LLMMessage
|
||||
from neo4j_graphrag.exceptions import RetryableError
|
||||
|
||||
|
||||
class CustomLLM(LLMInterface):
|
||||
def __init__(
|
||||
self, model_name: str, system_instruction: Optional[str] = None, **kwargs: Any
|
||||
):
|
||||
super().__init__(model_name, **kwargs)
|
||||
|
||||
# Optional: Apply rate limit handling to synchronous invoke method
|
||||
# @rate_limit_handler
|
||||
def invoke(
|
||||
self,
|
||||
input: str,
|
||||
message_history: Optional[Union[List[LLMMessage], MessageHistory]] = None,
|
||||
system_instruction: Optional[str] = None,
|
||||
) -> LLMResponse:
|
||||
content: str = (
|
||||
self.model_name + ": " + "".join(random.choices(string.ascii_letters, k=30))
|
||||
)
|
||||
return LLMResponse(content=content)
|
||||
|
||||
# Optional: Apply rate limit handling to asynchronous ainvoke method
|
||||
# @async_rate_limit_handler
|
||||
async def ainvoke(
|
||||
self,
|
||||
input: str,
|
||||
message_history: Optional[Union[List[LLMMessage], MessageHistory]] = None,
|
||||
system_instruction: Optional[str] = None,
|
||||
) -> LLMResponse:
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
llm = CustomLLM(
|
||||
""
|
||||
) # if rate_limit_handler and async_rate_limit_handler decorators are used, the default rate limit handler will be applied automatically (retry with exponential backoff)
|
||||
res: LLMResponse = llm.invoke("text")
|
||||
print(res.content)
|
||||
|
||||
# If rate_limit_handler and async_rate_limit_handler decorators are used and you want to use a custom rate limit handler
|
||||
# Type variables for function signatures used in rate limit handlers
|
||||
F = TypeVar("F", bound=Callable[..., Any])
|
||||
AF = TypeVar("AF", bound=Callable[..., Awaitable[Any]])
|
||||
|
||||
|
||||
class CustomRateLimitHandler(RateLimitHandler):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
def handle_sync(self, func: F) -> F:
|
||||
# error handling here
|
||||
return func
|
||||
|
||||
def handle_async(self, func: AF) -> AF:
|
||||
# error handling here
|
||||
return func
|
||||
|
||||
def is_retryable_exception(self, exception: Exception) -> bool:
|
||||
# return True if the exception should be retried
|
||||
return True
|
||||
|
||||
def to_retryable_error(self, exception: Exception) -> RetryableError:
|
||||
# convert the exception to a retryable error
|
||||
return RetryableError(exception)
|
||||
|
||||
|
||||
llm_with_custom_rate_limit_handler = CustomLLM(
|
||||
"", rate_limit_handler=CustomRateLimitHandler()
|
||||
)
|
||||
result: LLMResponse = llm_with_custom_rate_limit_handler.invoke("text")
|
||||
print(result.content)
|
||||
@@ -0,0 +1,31 @@
|
||||
"""This example illustrates the message_history feature
|
||||
of the LLMInterface by mocking a conversation between a user
|
||||
and an LLM about Tom Hanks.
|
||||
|
||||
OpenAILLM can be replaced by any supported LLM from this package.
|
||||
"""
|
||||
|
||||
from neo4j_graphrag.llm import LLMResponse, OpenAILLM
|
||||
|
||||
# set api key here on in the OPENAI_API_KEY env var
|
||||
api_key = None
|
||||
|
||||
questions = [
|
||||
"What are some movies Tom Hanks starred in?",
|
||||
"Is he also a director?",
|
||||
"Wow, that's impressive. And what about his personal life, does he have children?",
|
||||
]
|
||||
|
||||
history: list[dict[str, str]] = []
|
||||
with OpenAILLM(model_name="gpt-5", api_key=api_key) as llm:
|
||||
for question in questions:
|
||||
res: LLMResponse = llm.invoke(
|
||||
question,
|
||||
message_history=history, # type: ignore
|
||||
)
|
||||
history.append({"role": "user", "content": question})
|
||||
history.append({"role": "assistant", "content": res.content})
|
||||
|
||||
print("#" * 50, question)
|
||||
print(res.content)
|
||||
print("#" * 50)
|
||||
@@ -0,0 +1,48 @@
|
||||
"""This example illustrates the message_history feature
|
||||
of the LLMInterface by mocking a conversation between a user
|
||||
and an LLM about Tom Hanks.
|
||||
|
||||
Neo4j is used as the database for storing the message history.
|
||||
|
||||
OpenAILLM can be replaced by any supported LLM from this package.
|
||||
"""
|
||||
|
||||
import neo4j
|
||||
from neo4j_graphrag.llm import LLMResponse, OpenAILLM
|
||||
from neo4j_graphrag.message_history import Neo4jMessageHistory
|
||||
|
||||
# Define database credentials
|
||||
URI = "neo4j+s://demo.neo4jlabs.com"
|
||||
AUTH = ("recommendations", "recommendations")
|
||||
DATABASE = "recommendations"
|
||||
INDEX = "moviePlotsEmbedding"
|
||||
|
||||
# set api key here on in the OPENAI_API_KEY env var
|
||||
api_key = None
|
||||
|
||||
questions = [
|
||||
"What are some movies Tom Hanks starred in?",
|
||||
"Is he also a director?",
|
||||
"Wow, that's impressive. And what about his personal life, does he have children?",
|
||||
]
|
||||
|
||||
driver = neo4j.GraphDatabase.driver(
|
||||
URI,
|
||||
auth=AUTH,
|
||||
database=DATABASE,
|
||||
)
|
||||
|
||||
history = Neo4jMessageHistory(session_id="123", driver=driver, window=10)
|
||||
|
||||
with OpenAILLM(model_name="gpt-5", api_key=api_key) as llm:
|
||||
for question in questions:
|
||||
res: LLMResponse = llm.invoke(
|
||||
question,
|
||||
message_history=history,
|
||||
)
|
||||
history.add_message({"role": "user", "content": question})
|
||||
history.add_message({"role": "assistant", "content": res.content})
|
||||
|
||||
print("#" * 50, question)
|
||||
print(res.content)
|
||||
print("#" * 50)
|
||||
@@ -0,0 +1,18 @@
|
||||
"""This example illustrates how to set system instructions for LLM.
|
||||
|
||||
OpenAILLM can be replaced by any supported LLM from this package.
|
||||
"""
|
||||
|
||||
from neo4j_graphrag.llm import LLMResponse, OpenAILLM
|
||||
|
||||
# set api key here on in the OPENAI_API_KEY env var
|
||||
api_key = None
|
||||
|
||||
question = "How fast is Santa Claus during the Christmas eve?"
|
||||
|
||||
with OpenAILLM(model_name="gpt-5", api_key=api_key) as llm:
|
||||
res: LLMResponse = llm.invoke(
|
||||
question,
|
||||
system_instruction="Answer with a serious tone",
|
||||
)
|
||||
print(res.content)
|
||||
@@ -0,0 +1,10 @@
|
||||
from neo4j_graphrag.llm import MistralAILLM
|
||||
|
||||
# set api key here on in the MISTRAL_API_KEY env var
|
||||
api_key = None
|
||||
|
||||
with MistralAILLM(
|
||||
model_name="mistral-small-latest",
|
||||
api_key=api_key,
|
||||
) as llm:
|
||||
llm.invoke("say something")
|
||||
@@ -0,0 +1,13 @@
|
||||
"""This example demonstrate how to invoke an LLM using a local model
|
||||
served by Ollama.
|
||||
"""
|
||||
|
||||
from neo4j_graphrag.llm import LLMResponse, OllamaLLM
|
||||
|
||||
with OllamaLLM(
|
||||
model_name="<model_name>",
|
||||
# model_params={"options": {"temperature": 0}, "format": "json"},
|
||||
# host="...", # if using a remote server
|
||||
) as llm:
|
||||
res: LLMResponse = llm.invoke("What is the additive color model?")
|
||||
print(res.content)
|
||||
@@ -0,0 +1,95 @@
|
||||
"""
|
||||
Example showing how to use Ollama tool calls with parameter extraction.
|
||||
Both synchronous and asynchronous examples are provided.
|
||||
|
||||
To run this example:
|
||||
1. Make sure you have `ollama serve` running
|
||||
2. Run: python examples/tool_calls/ollama_tool_calls.py
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from typing import Dict, Any
|
||||
|
||||
from neo4j_graphrag.llm import OllamaLLM
|
||||
from neo4j_graphrag.llm.types import ToolCallResponse
|
||||
from neo4j_graphrag.tool import (
|
||||
Tool,
|
||||
ObjectParameter,
|
||||
StringParameter,
|
||||
IntegerParameter,
|
||||
)
|
||||
|
||||
|
||||
# Create a custom Tool implementation for person info extraction
|
||||
parameters = ObjectParameter(
|
||||
description="Parameters for extracting person information",
|
||||
properties={
|
||||
"name": StringParameter(description="The person's full name"),
|
||||
"age": IntegerParameter(description="The person's age"),
|
||||
"occupation": StringParameter(description="The person's occupation"),
|
||||
},
|
||||
required_properties=["name"],
|
||||
additional_properties=False,
|
||||
)
|
||||
person_info_tool = Tool(
|
||||
name="extract_person_info",
|
||||
description="Extract information about a person from text",
|
||||
parameters=parameters,
|
||||
execute_func=lambda **kwargs: kwargs,
|
||||
)
|
||||
|
||||
# Create the tool instance
|
||||
TOOLS = [person_info_tool]
|
||||
|
||||
|
||||
def process_tool_calls(response: ToolCallResponse) -> Dict[str, Any]:
|
||||
"""Process all tool calls in the response and return the extracted parameters."""
|
||||
if not response.tool_calls:
|
||||
raise ValueError("No tool calls found in response")
|
||||
|
||||
print(f"\nNumber of tool calls: {len(response.tool_calls)}")
|
||||
print(f"Additional content: {response.content or 'None'}")
|
||||
|
||||
results = []
|
||||
for i, tool_call in enumerate(response.tool_calls):
|
||||
print(f"\nTool call #{i + 1}: {tool_call.name}")
|
||||
print(f"Arguments: {tool_call.arguments}")
|
||||
results.append(tool_call.arguments)
|
||||
|
||||
# For backward compatibility, return the first tool call's arguments
|
||||
return results[0] if results else {}
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with OllamaLLM(
|
||||
model_name="mistral:latest", model_params={"options": {"temperature": 0}}
|
||||
) as llm:
|
||||
# Example text containing information about a person
|
||||
text = "Stella Hane is a 35-year-old software engineer who loves coding."
|
||||
|
||||
print("\n=== Synchronous Tool Call ===")
|
||||
# Make a synchronous tool call
|
||||
sync_response = llm.invoke_with_tools(
|
||||
input=f"Extract information about the person from this text: {text}",
|
||||
tools=TOOLS,
|
||||
)
|
||||
sync_result = process_tool_calls(sync_response)
|
||||
print("\n=== Synchronous Tool Call Result ===")
|
||||
print(json.dumps(sync_result, indent=2))
|
||||
|
||||
print("\n=== Asynchronous Tool Call ===")
|
||||
# Make an asynchronous tool call with a different text
|
||||
text2 = "Molly Hane, 32, works as a data scientist and enjoys machine learning."
|
||||
async_response = await llm.ainvoke_with_tools(
|
||||
input=f"Extract information about the person from this text: {text2}",
|
||||
tools=TOOLS,
|
||||
)
|
||||
async_result = process_tool_calls(async_response)
|
||||
print("\n=== Asynchronous Tool Call Result ===")
|
||||
print(json.dumps(async_result, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run the async main function
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,8 @@
|
||||
from neo4j_graphrag.llm import LLMResponse, OpenAILLM
|
||||
|
||||
# set api key here on in the OPENAI_API_KEY env var
|
||||
api_key = None
|
||||
|
||||
with OpenAILLM(model_name="gpt-5", api_key=api_key) as llm:
|
||||
res: LLMResponse = llm.invoke("say something")
|
||||
print(res.content)
|
||||
@@ -0,0 +1,122 @@
|
||||
# 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.
|
||||
"""
|
||||
Simple example comparing OpenAI LLM V1 (legacy) vs V2 (structured output).
|
||||
|
||||
This demonstrates how V2's structured output provides type-safe, validated responses
|
||||
compared to V1's prompt-based JSON extraction.
|
||||
|
||||
Prerequisites:
|
||||
- OpenAI API key set in OPENAI_API_KEY environment variable
|
||||
"""
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from neo4j_graphrag.llm import OpenAILLM
|
||||
from neo4j_graphrag.types import LLMMessage
|
||||
|
||||
load_dotenv()
|
||||
|
||||
|
||||
# Define a Pydantic model for structured output
|
||||
class Movie(BaseModel):
|
||||
model_config = ConfigDict(
|
||||
extra="forbid"
|
||||
) # This is important to prevent extra properties from being added to the response
|
||||
|
||||
title: str
|
||||
year: int
|
||||
director: str
|
||||
genre: str
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# V1 (Legacy): Manual JSON mode with prompt engineering
|
||||
# =============================================================================
|
||||
print("=" * 60)
|
||||
print("V1 Legacy: Manual JSON extraction with prompt engineering")
|
||||
print("=" * 60)
|
||||
|
||||
with (
|
||||
OpenAILLM(
|
||||
model_name="gpt-5-mini",
|
||||
model_params={"response_format": {"type": "json_object"}, "temperature": 0},
|
||||
) as llm_v1,
|
||||
OpenAILLM(model_name="gpt-5-mini") as llm_v2,
|
||||
):
|
||||
# V1 requires string input and explicit JSON instructions in the prompt
|
||||
v1_prompt = """Extract movie information and respond in JSON format.
|
||||
Include: title, year, director, genre.
|
||||
|
||||
Text: Inception was directed by Christopher Nolan in 2010. It's a science fiction thriller."""
|
||||
|
||||
response_v1 = llm_v1.invoke(v1_prompt)
|
||||
print(f"Response: {response_v1.content}")
|
||||
|
||||
# =============================================================================
|
||||
# V2 (New): Structured output with Pydantic model
|
||||
# =============================================================================
|
||||
print("\n" + "=" * 60)
|
||||
print("V2: Structured output with Pydantic model")
|
||||
print("=" * 60)
|
||||
|
||||
# V2 uses list of LLMMessage for input
|
||||
messages = [
|
||||
LLMMessage(
|
||||
role="user",
|
||||
content="Inception was directed by Christopher Nolan in 2010. It's a science fiction thriller.",
|
||||
)
|
||||
]
|
||||
|
||||
# Pass response_format and temperature directly to invoke()
|
||||
response_v2 = llm_v2.invoke(messages, response_format=Movie, temperature=0)
|
||||
|
||||
# Parse and validate in one step
|
||||
movie = Movie.model_validate_json(response_v2.content)
|
||||
print(f"Response: {response_v2.content}")
|
||||
|
||||
# =============================================================================
|
||||
# V2: Using JSON Schema instead of Pydantic
|
||||
# =============================================================================
|
||||
print("\n" + "=" * 60)
|
||||
print("V2 Alternative: Structured output with JSON Schema")
|
||||
print("=" * 60)
|
||||
|
||||
# Define a JSON schema (equivalent to the Movie Pydantic model)
|
||||
# Note: OpenAI requires JSON schemas to be wrapped in this specific format
|
||||
movie_schema = {
|
||||
"type": "json_schema",
|
||||
"json_schema": {
|
||||
"name": "movie_info",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {"type": "string"},
|
||||
"year": {"type": "integer"},
|
||||
"director": {"type": "string"},
|
||||
"genre": {"type": "string"},
|
||||
},
|
||||
"required": ["title", "year", "director", "genre"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
# Pass JSON schema as response_format
|
||||
response_v2_schema = llm_v2.invoke(
|
||||
messages, response_format=movie_schema, temperature=0
|
||||
)
|
||||
|
||||
print(f"Response: {response_v2_schema.content}")
|
||||
@@ -0,0 +1,104 @@
|
||||
"""
|
||||
Example showing how to use OpenAI tool calls with parameter extraction.
|
||||
Both synchronous and asynchronous examples are provided.
|
||||
|
||||
To run this example:
|
||||
1. Make sure you have the OpenAI API key in your .env file:
|
||||
OPENAI_API_KEY=your-api-key
|
||||
2. Run: python examples/tool_calls/openai_tool_calls.py
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
from typing import Dict, Any
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from neo4j_graphrag.llm import OpenAILLM
|
||||
from neo4j_graphrag.llm.types import ToolCallResponse
|
||||
from neo4j_graphrag.tool import (
|
||||
Tool,
|
||||
ObjectParameter,
|
||||
StringParameter,
|
||||
IntegerParameter,
|
||||
)
|
||||
|
||||
# Load environment variables from .env file (OPENAI_API_KEY required for this example)
|
||||
load_dotenv()
|
||||
|
||||
|
||||
# Create a custom Tool implementation for person info extraction
|
||||
parameters = ObjectParameter(
|
||||
description="Parameters for extracting person information",
|
||||
properties={
|
||||
"name": StringParameter(description="The person's full name"),
|
||||
"age": IntegerParameter(description="The person's age"),
|
||||
"occupation": StringParameter(description="The person's occupation"),
|
||||
},
|
||||
required_properties=["name"],
|
||||
additional_properties=False,
|
||||
)
|
||||
person_info_tool = Tool(
|
||||
name="extract_person_info",
|
||||
description="Extract information about a person from text",
|
||||
parameters=parameters,
|
||||
execute_func=lambda **kwargs: kwargs,
|
||||
)
|
||||
|
||||
# Create the tool instance
|
||||
TOOLS = [person_info_tool]
|
||||
|
||||
|
||||
def process_tool_calls(response: ToolCallResponse) -> Dict[str, Any]:
|
||||
"""Process all tool calls in the response and return the extracted parameters."""
|
||||
if not response.tool_calls:
|
||||
raise ValueError("No tool calls found in response")
|
||||
|
||||
print(f"\nNumber of tool calls: {len(response.tool_calls)}")
|
||||
print(f"Additional content: {response.content or 'None'}")
|
||||
|
||||
results = []
|
||||
for i, tool_call in enumerate(response.tool_calls):
|
||||
print(f"\nTool call #{i + 1}: {tool_call.name}")
|
||||
print(f"Arguments: {tool_call.arguments}")
|
||||
results.append(tool_call.arguments)
|
||||
|
||||
# For backward compatibility, return the first tool call's arguments
|
||||
return results[0] if results else {}
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with OpenAILLM(
|
||||
api_key=os.getenv("OPENAI_API_KEY"),
|
||||
model_name="gpt-5",
|
||||
model_params={"temperature": 0},
|
||||
) as llm:
|
||||
# Example text containing information about a person
|
||||
text = "Stella Hane is a 35-year-old software engineer who loves coding."
|
||||
|
||||
print("\n=== Synchronous Tool Call ===")
|
||||
# Make a synchronous tool call
|
||||
sync_response = llm.invoke_with_tools(
|
||||
input=f"Extract information about the person from this text: {text}",
|
||||
tools=TOOLS,
|
||||
)
|
||||
sync_result = process_tool_calls(sync_response)
|
||||
print("\n=== Synchronous Tool Call Result ===")
|
||||
print(json.dumps(sync_result, indent=2))
|
||||
|
||||
print("\n=== Asynchronous Tool Call ===")
|
||||
# Make an asynchronous tool call with a different text
|
||||
text2 = "Molly Hane, 32, works as a data scientist and enjoys machine learning."
|
||||
async_response = await llm.ainvoke_with_tools(
|
||||
input=f"Extract information about the person from this text: {text2}",
|
||||
tools=TOOLS,
|
||||
)
|
||||
async_result = process_tool_calls(async_response)
|
||||
print("\n=== Asynchronous Tool Call Result ===")
|
||||
print(json.dumps(async_result, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run the async main function
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,15 @@
|
||||
from neo4j_graphrag.llm import LLMResponse, VertexAILLM
|
||||
from vertexai.generative_models import GenerationConfig
|
||||
|
||||
generation_config = GenerationConfig(temperature=1.0)
|
||||
llm = VertexAILLM(
|
||||
model_name="gemini-2.0-flash-001",
|
||||
generation_config=generation_config,
|
||||
# add here any argument that will be passed to the
|
||||
# vertexai.generative_models.GenerativeModel client
|
||||
)
|
||||
res: LLMResponse = llm.invoke(
|
||||
"say something",
|
||||
system_instruction="You are living in 3000 where AI rules the world",
|
||||
)
|
||||
print(res.content)
|
||||
@@ -0,0 +1,119 @@
|
||||
# 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.
|
||||
"""
|
||||
Simple example comparing VertexAI LLM V1 (legacy) vs V2 (structured output).
|
||||
|
||||
This demonstrates how V2's structured output provides type-safe, validated responses
|
||||
compared to V1's prompt-based JSON extraction.
|
||||
|
||||
Prerequisites:
|
||||
- Google Cloud project with Vertex AI API enabled
|
||||
- Either:
|
||||
- GOOGLE_APPLICATION_CREDENTIALS environment variable set, or
|
||||
- Running on GCP with appropriate service account
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel
|
||||
from neo4j_graphrag.llm import VertexAILLM
|
||||
from neo4j_graphrag.types import LLMMessage
|
||||
from vertexai.generative_models import GenerationConfig
|
||||
|
||||
|
||||
# Define a Pydantic model for structured output
|
||||
class Movie(BaseModel):
|
||||
title: str
|
||||
year: int
|
||||
director: str
|
||||
genre: str
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# V1 (Legacy): Manual JSON mode with prompt engineering
|
||||
# =============================================================================
|
||||
print("=" * 60)
|
||||
print("V1 Legacy: Manual JSON extraction with prompt engineering")
|
||||
print("=" * 60)
|
||||
|
||||
# V1: Use generation_config
|
||||
llm_v1 = VertexAILLM(
|
||||
model_name="gemini-2.5-flash",
|
||||
generation_config=GenerationConfig(
|
||||
response_mime_type="application/json", temperature=0
|
||||
),
|
||||
)
|
||||
|
||||
# V1 requires string input
|
||||
v1_prompt = """Extract movie information and respond in JSON format.
|
||||
Include: title, year, director, genre.
|
||||
|
||||
Text: Inception was directed by Christopher Nolan in 2010. It's a science fiction thriller."""
|
||||
|
||||
response_v1 = llm_v1.invoke(v1_prompt)
|
||||
print(f"Response: {response_v1.content}")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# V2 (New): Structured output with Pydantic model
|
||||
# =============================================================================
|
||||
print("\n" + "=" * 60)
|
||||
print("V2: Structured output with Pydantic model")
|
||||
print("=" * 60)
|
||||
|
||||
# V2: Use clean LLM without constructor params
|
||||
llm_v2 = VertexAILLM(model_name="gemini-2.5-flash")
|
||||
|
||||
# V2 uses list of LLMMessage for input
|
||||
messages = [
|
||||
LLMMessage(
|
||||
role="user",
|
||||
content="Inception was directed by Christopher Nolan in 2010. It's a science fiction thriller.",
|
||||
)
|
||||
]
|
||||
|
||||
# Pass response_format and temperature directly to invoke()
|
||||
response_v2 = llm_v2.invoke(messages, response_format=Movie, temperature=0)
|
||||
|
||||
# Parse and validate in one step
|
||||
movie = Movie.model_validate_json(response_v2.content)
|
||||
print(f"Response: {response_v2.content}")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# V2 Alternative: Using JSON Schema instead of Pydantic
|
||||
# =============================================================================
|
||||
print("\n" + "=" * 60)
|
||||
print("V2 Alternative: Structured output with JSON Schema")
|
||||
print("=" * 60)
|
||||
|
||||
# Define a JSON schema (equivalent to the Movie Pydantic model)
|
||||
# Note: VertexAI accepts raw JSON schemas (no wrapping required like OpenAI)
|
||||
movie_schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {"type": "string"},
|
||||
"year": {"type": "integer"},
|
||||
"director": {"type": "string"},
|
||||
"genre": {"type": "string"},
|
||||
},
|
||||
"required": ["title", "year", "director", "genre"],
|
||||
"additionalProperties": False,
|
||||
}
|
||||
|
||||
# Pass JSON schema as response_format
|
||||
response_v2_schema = llm_v2.invoke(
|
||||
messages, response_format=movie_schema, temperature=0
|
||||
)
|
||||
|
||||
print(f"Response: {response_v2_schema.content}")
|
||||
@@ -0,0 +1,141 @@
|
||||
"""
|
||||
Example showing how to use VertexAI tool calls with parameter extraction.
|
||||
Both synchronous and asynchronous examples are provided.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
from typing import Optional
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from vertexai.generative_models import GenerationConfig
|
||||
|
||||
from neo4j_graphrag.llm import VertexAILLM
|
||||
from neo4j_graphrag.llm.types import ToolCallResponse
|
||||
from neo4j_graphrag.tool import (
|
||||
Tool,
|
||||
ObjectParameter,
|
||||
StringParameter,
|
||||
IntegerParameter,
|
||||
)
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
|
||||
# Create a custom Tool implementation for person info extraction
|
||||
person_tool_parameters = ObjectParameter(
|
||||
description="Parameters for extracting person information",
|
||||
properties={
|
||||
"name": StringParameter(description="The person's full name"),
|
||||
"age": IntegerParameter(description="The person's age"),
|
||||
"occupation": StringParameter(description="The person's occupation"),
|
||||
},
|
||||
required_properties=["name"],
|
||||
additional_properties=False,
|
||||
)
|
||||
|
||||
|
||||
def run_person_tool(
|
||||
name: str, age: Optional[int] = None, occupation: Optional[str] = None
|
||||
) -> str:
|
||||
"""A simple function that summarizes person information from input parameters."""
|
||||
return f"Found person {name} with age {age} and occupation {occupation}"
|
||||
|
||||
|
||||
person_info_tool = Tool(
|
||||
name="extract_person_info",
|
||||
description="Extract information about a person from text",
|
||||
parameters=person_tool_parameters,
|
||||
execute_func=run_person_tool,
|
||||
)
|
||||
|
||||
company_tool_parameters = ObjectParameter(
|
||||
description="Parameters for extracting company information",
|
||||
properties={
|
||||
"name": StringParameter(description="The company's full name"),
|
||||
"industry": StringParameter(description="The company's industry"),
|
||||
"creation_year": IntegerParameter(description="The company's creation year"),
|
||||
},
|
||||
required_properties=["name"],
|
||||
additional_properties=False,
|
||||
)
|
||||
|
||||
|
||||
def run_company_tool(
|
||||
name: str, industry: Optional[str] = None, creation_year: Optional[int] = None
|
||||
) -> str:
|
||||
"""A simple function that summarizes company information from input parameters."""
|
||||
return (
|
||||
f"Found company {name} operating in industry {industry} since {creation_year}"
|
||||
)
|
||||
|
||||
|
||||
company_info_tool = Tool(
|
||||
name="extract_company_info",
|
||||
description="Extract information about a company from text",
|
||||
parameters=company_tool_parameters,
|
||||
execute_func=run_company_tool,
|
||||
)
|
||||
|
||||
# Create the tool instance
|
||||
TOOLS = [person_info_tool, company_info_tool]
|
||||
|
||||
|
||||
def process_tool_call(response: ToolCallResponse) -> str:
|
||||
"""Process the tool call response and return the extracted parameters."""
|
||||
if not response.tool_calls:
|
||||
raise ValueError("No tool calls found in response")
|
||||
|
||||
tool_call = response.tool_calls[0]
|
||||
print(f"\nTool called: {tool_call.name}")
|
||||
print(f"Arguments: {tool_call.arguments}")
|
||||
print(f"Additional content: {response.content or 'None'}")
|
||||
if tool_call.name == "extract_person_info":
|
||||
return person_info_tool.execute(**tool_call.arguments) # type: ignore[no-any-return]
|
||||
elif tool_call.name == "extract_company_info":
|
||||
return str(company_info_tool.execute(**tool_call.arguments))
|
||||
else:
|
||||
raise ValueError("Unknown tool call")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
# Initialize the VertexAI LLM
|
||||
generation_config = GenerationConfig(temperature=0.0)
|
||||
llm = VertexAILLM(
|
||||
model_name="gemini-2.0-flash-001",
|
||||
generation_config=generation_config,
|
||||
# tool_config=ToolConfig(
|
||||
# function_calling_config=ToolConfig.FunctionCallingConfig(
|
||||
# mode=ToolConfig.FunctionCallingConfig.Mode.ANY,
|
||||
# # allowed_function_names=["extract_person_info"],
|
||||
# ))
|
||||
)
|
||||
|
||||
# Example text containing information about a company
|
||||
text1 = "Neo4j is a software company created in 2007"
|
||||
|
||||
print("\n=== Synchronous Tool Call ===")
|
||||
# Make a synchronous tool call
|
||||
sync_response = llm.invoke_with_tools(
|
||||
input=f"Extract information about the person from this text: {text1}",
|
||||
tools=TOOLS,
|
||||
)
|
||||
sync_result = process_tool_call(sync_response)
|
||||
print("\n=== Synchronous Tool Call Result ===")
|
||||
print(sync_result)
|
||||
|
||||
print("\n=== Asynchronous Tool Call ===")
|
||||
# Make an asynchronous tool call with a different text about a person
|
||||
text2 = "Molly Hane, 32, works as a data scientist and enjoys machine learning."
|
||||
async_response = await llm.ainvoke_with_tools(
|
||||
input=f"Extract information about the person from this text: {text2}",
|
||||
tools=TOOLS,
|
||||
)
|
||||
async_result = process_tool_call(async_response)
|
||||
print("\n=== Asynchronous Tool Call Result ===")
|
||||
print(async_result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run the async main function
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user