Skip to content

Commit 990ac2e

Browse files
committed
Add quickstart for ladybug
After setting up api keys in .env: ``` uv sync --extra ladybug uv run examples/quickstart/quickstart_ladybug.py ```
1 parent ef03dbe commit 990ac2e

File tree

2 files changed

+254
-2
lines changed

2 files changed

+254
-2
lines changed

examples/quickstart/README.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
This example demonstrates the basic functionality of Graphiti, including:
44

5-
1. Connecting to a Neo4j or FalkorDB database
5+
1. Connecting to a Neo4j, FalkorDB, or Ladybug database
66
2. Initializing Graphiti indices and constraints
77
3. Adding episodes to the graph
88
4. Searching the graph with semantic and keyword matching
@@ -18,6 +18,8 @@ This example demonstrates the basic functionality of Graphiti, including:
1818
- A local DBMS created and started in Neo4j Desktop
1919
- **For FalkorDB**:
2020
- FalkorDB server running (see [FalkorDB documentation](https://docs.falkordb.com) for setup)
21+
- **For Ladybug**:
22+
- Ladybug runs in-process; no external server required
2123
- **For Amazon Neptune**:
2224
- Amazon server running (see [Amazon Neptune documentation](https://aws.amazon.com/neptune/developer-resources/) for setup)
2325

@@ -44,6 +46,10 @@ export NEO4J_PASSWORD=password
4446
# Optional FalkorDB connection parameters (defaults shown)
4547
export FALKORDB_URI=falkor://localhost:6379
4648

49+
# Optional Ladybug connection parameters
50+
# Use ':memory:' for ephemeral state, or a filesystem path for persistence
51+
export LADYBUG_DB=graphiti.lbdb
52+
4753
# Optional Amazon Neptune connection parameters
4854
NEPTUNE_HOST=your_neptune_host
4955
NEPTUNE_PORT=your_port_or_8182
@@ -65,13 +71,16 @@ python quickstart_neo4j.py
6571
# For FalkorDB
6672
python quickstart_falkordb.py
6773

74+
# For Ladybug
75+
python quickstart_ladybug.py
76+
6877
# For Amazon Neptune
6978
python quickstart_neptune.py
7079
```
7180

7281
## What This Example Demonstrates
7382

74-
- **Graph Initialization**: Setting up the Graphiti indices and constraints in Neo4j, Amazon Neptune, or FalkorDB
83+
- **Graph Initialization**: Setting up the Graphiti indices and constraints in Neo4j, Amazon Neptune, FalkorDB, or Ladybug
7584
- **Adding Episodes**: Adding text content that will be analyzed and converted into knowledge graph nodes and edges
7685
- **Edge Search Functionality**: Performing hybrid searches that combine semantic similarity and BM25 retrieval to find relationships (edges)
7786
- **Graph-Aware Search**: Using the source node UUID from the top search result to rerank additional search results based on graph distance
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
"""
2+
Copyright 2025, Zep Software, Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
"""
16+
17+
import asyncio
18+
import json
19+
import logging
20+
import os
21+
from datetime import datetime, timezone
22+
from logging import INFO
23+
24+
from dotenv import load_dotenv
25+
26+
from graphiti_core import Graphiti
27+
from graphiti_core.driver.ladybug_driver import LadybugDriver
28+
from graphiti_core.nodes import EpisodeType
29+
from graphiti_core.search.search_config_recipes import NODE_HYBRID_SEARCH_RRF
30+
31+
#################################################
32+
# CONFIGURATION
33+
#################################################
34+
# Set up logging and environment variables for
35+
# connecting to Ladybug database
36+
#################################################
37+
38+
# Configure logging
39+
logging.basicConfig(
40+
level=INFO,
41+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
42+
datefmt='%Y-%m-%d %H:%M:%S',
43+
)
44+
logger = logging.getLogger(__name__)
45+
46+
load_dotenv()
47+
48+
# Ladybug connection parameter
49+
# Ladybug runs in-process and uses an embedded database path.
50+
# Use ':memory:' for ephemeral state, or a filesystem path for persistence.
51+
ladybug_db = os.environ.get('LADYBUG_DB', ':memory:')
52+
53+
54+
async def main():
55+
#################################################
56+
# INITIALIZATION
57+
#################################################
58+
# Connect to Ladybug and set up Graphiti indices
59+
# This is required before using other Graphiti
60+
# functionality
61+
#################################################
62+
63+
# Initialize Graphiti with Ladybug connection
64+
ladybug_driver = LadybugDriver(db=ladybug_db)
65+
#llm_config = LLMConfig(
66+
# api_key=os.environ.get('OPENAI_API_KEY', 'fake_api_key_for_testing'),
67+
# base_url=os.environ.get('OPENAI_BASE_URL', 'http://localhost:11434/v1'),
68+
# model=os.environ.get('LLM_MODEL', 'qwen3.5:9b')
69+
#)
70+
#llm_client = OpenAIGenericClient(config=llm_config)
71+
graphiti = Graphiti(graph_driver=ladybug_driver, llm_client=None)
72+
73+
try:
74+
#################################################
75+
# ADDING EPISODES
76+
#################################################
77+
# Episodes are the primary units of information
78+
# in Graphiti. They can be text or structured JSON
79+
# and are automatically processed to extract entities
80+
# and relationships.
81+
#################################################
82+
83+
# Example: Add Episodes
84+
# Episodes list containing both text and JSON episodes
85+
episodes = [
86+
{
87+
'content': 'Kamala Harris is the Attorney General of California. She was previously '
88+
'the district attorney for San Francisco.',
89+
'type': EpisodeType.text,
90+
'description': 'podcast transcript',
91+
},
92+
{
93+
'content': 'As AG, Harris was in office from January 3, 2011 – January 3, 2017',
94+
'type': EpisodeType.text,
95+
'description': 'podcast transcript',
96+
},
97+
{
98+
'content': {
99+
'name': 'Gavin Newsom',
100+
'position': 'Governor',
101+
'state': 'California',
102+
'previous_role': 'Lieutenant Governor',
103+
'previous_location': 'San Francisco',
104+
},
105+
'type': EpisodeType.json,
106+
'description': 'podcast metadata',
107+
},
108+
{
109+
'content': {
110+
'name': 'Gavin Newsom',
111+
'position': 'Governor',
112+
'term_start': 'January 7, 2019',
113+
'term_end': 'Present',
114+
},
115+
'type': EpisodeType.json,
116+
'description': 'podcast metadata',
117+
},
118+
]
119+
120+
# Add episodes to the graph
121+
for i, episode in enumerate(episodes):
122+
await graphiti.add_episode(
123+
name=f'Freakonomics Radio {i}',
124+
episode_body=episode['content']
125+
if isinstance(episode['content'], str)
126+
else json.dumps(episode['content']),
127+
source=episode['type'],
128+
source_description=episode['description'],
129+
reference_time=datetime.now(timezone.utc),
130+
)
131+
print(f'Added episode: Freakonomics Radio {i} ({episode["type"].value})')
132+
133+
#################################################
134+
# BASIC SEARCH
135+
#################################################
136+
# The simplest way to retrieve relationships (edges)
137+
# from Graphiti is using the search method, which
138+
# performs a hybrid search combining semantic
139+
# similarity and BM25 text retrieval.
140+
#################################################
141+
142+
# Perform a hybrid search combining semantic similarity and BM25 retrieval
143+
print("\nSearching for: 'Who was the California Attorney General?'")
144+
results = await graphiti.search('Who was the California Attorney General?')
145+
146+
# Print search results
147+
print('\nSearch Results:')
148+
for result in results:
149+
print(f'UUID: {result.uuid}')
150+
print(f'Fact: {result.fact}')
151+
if hasattr(result, 'valid_at') and result.valid_at:
152+
print(f'Valid from: {result.valid_at}')
153+
if hasattr(result, 'invalid_at') and result.invalid_at:
154+
print(f'Valid until: {result.invalid_at}')
155+
print('---')
156+
157+
#################################################
158+
# CENTER NODE SEARCH
159+
#################################################
160+
# For more contextually relevant results, you can
161+
# use a center node to rerank search results based
162+
# on their graph distance to a specific node
163+
#################################################
164+
165+
# Use the top search result's UUID as the center node for reranking
166+
if results and len(results) > 0:
167+
# Get the source node UUID from the top result
168+
center_node_uuid = results[0].source_node_uuid
169+
170+
print('\nReranking search results based on graph distance:')
171+
print(f'Using center node UUID: {center_node_uuid}')
172+
173+
reranked_results = await graphiti.search(
174+
'Who was the California Attorney General?', center_node_uuid=center_node_uuid
175+
)
176+
177+
# Print reranked search results
178+
print('\nReranked Search Results:')
179+
for result in reranked_results:
180+
print(f'UUID: {result.uuid}')
181+
print(f'Fact: {result.fact}')
182+
if hasattr(result, 'valid_at') and result.valid_at:
183+
print(f'Valid from: {result.valid_at}')
184+
if hasattr(result, 'invalid_at') and result.invalid_at:
185+
print(f'Valid until: {result.invalid_at}')
186+
print('---')
187+
else:
188+
print('No results found in the initial search to use as center node.')
189+
190+
#################################################
191+
# NODE SEARCH USING SEARCH RECIPES
192+
#################################################
193+
# Graphiti provides predefined search recipes
194+
# optimized for different search scenarios.
195+
# Here we use NODE_HYBRID_SEARCH_RRF for retrieving
196+
# nodes directly instead of edges.
197+
#################################################
198+
199+
# Example: Perform a node search using _search method with standard recipes
200+
print(
201+
'\nPerforming node search using _search method with standard recipe NODE_HYBRID_SEARCH_RRF:'
202+
)
203+
204+
# Use a predefined search configuration recipe and modify its limit
205+
node_search_config = NODE_HYBRID_SEARCH_RRF.model_copy(deep=True)
206+
node_search_config.limit = 5 # Limit to 5 results
207+
208+
# Execute the node search
209+
node_search_results = await graphiti._search(
210+
query='California Governor',
211+
config=node_search_config,
212+
)
213+
214+
# Print node search results
215+
print('\nNode Search Results:')
216+
for node in node_search_results.nodes:
217+
print(f'Node UUID: {node.uuid}')
218+
print(f'Node Name: {node.name}')
219+
node_summary = node.summary[:100] + '...' if len(node.summary) > 100 else node.summary
220+
print(f'Content Summary: {node_summary}')
221+
print(f'Node Labels: {", ".join(node.labels)}')
222+
print(f'Created At: {node.created_at}')
223+
if hasattr(node, 'attributes') and node.attributes:
224+
print('Attributes:')
225+
for key, value in node.attributes.items():
226+
print(f' {key}: {value}')
227+
print('---')
228+
229+
finally:
230+
#################################################
231+
# CLEANUP
232+
#################################################
233+
# Always close the connection to Ladybug when
234+
# finished to properly release resources
235+
#################################################
236+
237+
# Close the connection
238+
await graphiti.close()
239+
print('\nConnection closed')
240+
241+
242+
if __name__ == '__main__':
243+
asyncio.run(main())

0 commit comments

Comments
 (0)