graphiti-core 0.12.0rc4__py3-none-any.whl → 0.12.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of graphiti-core might be problematic. Click here for more details.
- graphiti_core/cross_encoder/openai_reranker_client.py +1 -1
- graphiti_core/driver/__init__.py +17 -0
- graphiti_core/driver/driver.py +66 -0
- graphiti_core/driver/falkordb_driver.py +131 -0
- graphiti_core/driver/neo4j_driver.py +61 -0
- graphiti_core/edges.py +26 -26
- graphiti_core/embedder/azure_openai.py +64 -0
- graphiti_core/graph_queries.py +149 -0
- graphiti_core/graphiti.py +8 -4
- graphiti_core/graphiti_types.py +2 -2
- graphiti_core/helpers.py +9 -3
- graphiti_core/llm_client/__init__.py +16 -0
- graphiti_core/llm_client/azure_openai_client.py +73 -0
- graphiti_core/nodes.py +31 -31
- graphiti_core/prompts/dedupe_nodes.py +7 -3
- graphiti_core/prompts/extract_edges.py +6 -5
- graphiti_core/search/search.py +6 -10
- graphiti_core/search/search_utils.py +243 -187
- graphiti_core/utils/bulk_utils.py +20 -11
- graphiti_core/utils/maintenance/community_operations.py +6 -7
- graphiti_core/utils/maintenance/edge_operations.py +7 -8
- graphiti_core/utils/maintenance/graph_data_operations.py +13 -42
- graphiti_core/utils/maintenance/node_operations.py +23 -80
- {graphiti_core-0.12.0rc4.dist-info → graphiti_core-0.12.1.dist-info}/METADATA +4 -3
- {graphiti_core-0.12.0rc4.dist-info → graphiti_core-0.12.1.dist-info}/RECORD +27 -20
- {graphiti_core-0.12.0rc4.dist-info → graphiti_core-0.12.1.dist-info}/LICENSE +0 -0
- {graphiti_core-0.12.0rc4.dist-info → graphiti_core-0.12.1.dist-info}/WHEEL +0 -0
|
@@ -20,22 +20,24 @@ from collections import defaultdict
|
|
|
20
20
|
from datetime import datetime
|
|
21
21
|
from math import ceil
|
|
22
22
|
|
|
23
|
-
from neo4j import AsyncDriver, AsyncManagedTransaction
|
|
24
23
|
from numpy import dot, sqrt
|
|
25
24
|
from pydantic import BaseModel
|
|
26
25
|
from typing_extensions import Any
|
|
27
26
|
|
|
27
|
+
from graphiti_core.driver.driver import GraphDriver, GraphDriverSession
|
|
28
28
|
from graphiti_core.edges import Edge, EntityEdge, EpisodicEdge
|
|
29
29
|
from graphiti_core.embedder import EmbedderClient
|
|
30
|
+
from graphiti_core.graph_queries import (
|
|
31
|
+
get_entity_edge_save_bulk_query,
|
|
32
|
+
get_entity_node_save_bulk_query,
|
|
33
|
+
)
|
|
30
34
|
from graphiti_core.graphiti_types import GraphitiClients
|
|
31
35
|
from graphiti_core.helpers import DEFAULT_DATABASE, semaphore_gather
|
|
32
36
|
from graphiti_core.llm_client import LLMClient
|
|
33
37
|
from graphiti_core.models.edges.edge_db_queries import (
|
|
34
|
-
ENTITY_EDGE_SAVE_BULK,
|
|
35
38
|
EPISODIC_EDGE_SAVE_BULK,
|
|
36
39
|
)
|
|
37
40
|
from graphiti_core.models.nodes.node_db_queries import (
|
|
38
|
-
ENTITY_NODE_SAVE_BULK,
|
|
39
41
|
EPISODIC_NODE_SAVE_BULK,
|
|
40
42
|
)
|
|
41
43
|
from graphiti_core.nodes import EntityNode, EpisodeType, EpisodicNode
|
|
@@ -73,7 +75,7 @@ class RawEpisode(BaseModel):
|
|
|
73
75
|
|
|
74
76
|
|
|
75
77
|
async def retrieve_previous_episodes_bulk(
|
|
76
|
-
driver:
|
|
78
|
+
driver: GraphDriver, episodes: list[EpisodicNode]
|
|
77
79
|
) -> list[tuple[EpisodicNode, list[EpisodicNode]]]:
|
|
78
80
|
previous_episodes_list = await semaphore_gather(
|
|
79
81
|
*[
|
|
@@ -91,14 +93,15 @@ async def retrieve_previous_episodes_bulk(
|
|
|
91
93
|
|
|
92
94
|
|
|
93
95
|
async def add_nodes_and_edges_bulk(
|
|
94
|
-
driver:
|
|
96
|
+
driver: GraphDriver,
|
|
95
97
|
episodic_nodes: list[EpisodicNode],
|
|
96
98
|
episodic_edges: list[EpisodicEdge],
|
|
97
99
|
entity_nodes: list[EntityNode],
|
|
98
100
|
entity_edges: list[EntityEdge],
|
|
99
101
|
embedder: EmbedderClient,
|
|
100
102
|
):
|
|
101
|
-
|
|
103
|
+
session = driver.session(database=DEFAULT_DATABASE)
|
|
104
|
+
try:
|
|
102
105
|
await session.execute_write(
|
|
103
106
|
add_nodes_and_edges_bulk_tx,
|
|
104
107
|
episodic_nodes,
|
|
@@ -106,16 +109,20 @@ async def add_nodes_and_edges_bulk(
|
|
|
106
109
|
entity_nodes,
|
|
107
110
|
entity_edges,
|
|
108
111
|
embedder,
|
|
112
|
+
driver=driver,
|
|
109
113
|
)
|
|
114
|
+
finally:
|
|
115
|
+
await session.close()
|
|
110
116
|
|
|
111
117
|
|
|
112
118
|
async def add_nodes_and_edges_bulk_tx(
|
|
113
|
-
tx:
|
|
119
|
+
tx: GraphDriverSession,
|
|
114
120
|
episodic_nodes: list[EpisodicNode],
|
|
115
121
|
episodic_edges: list[EpisodicEdge],
|
|
116
122
|
entity_nodes: list[EntityNode],
|
|
117
123
|
entity_edges: list[EntityEdge],
|
|
118
124
|
embedder: EmbedderClient,
|
|
125
|
+
driver: GraphDriver,
|
|
119
126
|
):
|
|
120
127
|
episodes = [dict(episode) for episode in episodic_nodes]
|
|
121
128
|
for episode in episodes:
|
|
@@ -160,11 +167,13 @@ async def add_nodes_and_edges_bulk_tx(
|
|
|
160
167
|
edges.append(edge_data)
|
|
161
168
|
|
|
162
169
|
await tx.run(EPISODIC_NODE_SAVE_BULK, episodes=episodes)
|
|
163
|
-
|
|
170
|
+
entity_node_save_bulk = get_entity_node_save_bulk_query(nodes, driver.provider)
|
|
171
|
+
await tx.run(entity_node_save_bulk, nodes=nodes)
|
|
164
172
|
await tx.run(
|
|
165
173
|
EPISODIC_EDGE_SAVE_BULK, episodic_edges=[edge.model_dump() for edge in episodic_edges]
|
|
166
174
|
)
|
|
167
|
-
|
|
175
|
+
entity_edge_save_bulk = get_entity_edge_save_bulk_query(driver.provider)
|
|
176
|
+
await tx.run(entity_edge_save_bulk, entity_edges=edges)
|
|
168
177
|
|
|
169
178
|
|
|
170
179
|
async def extract_nodes_and_edges_bulk(
|
|
@@ -211,7 +220,7 @@ async def extract_nodes_and_edges_bulk(
|
|
|
211
220
|
|
|
212
221
|
|
|
213
222
|
async def dedupe_nodes_bulk(
|
|
214
|
-
driver:
|
|
223
|
+
driver: GraphDriver,
|
|
215
224
|
llm_client: LLMClient,
|
|
216
225
|
extracted_nodes: list[EntityNode],
|
|
217
226
|
) -> tuple[list[EntityNode], dict[str, str]]:
|
|
@@ -247,7 +256,7 @@ async def dedupe_nodes_bulk(
|
|
|
247
256
|
|
|
248
257
|
|
|
249
258
|
async def dedupe_edges_bulk(
|
|
250
|
-
driver:
|
|
259
|
+
driver: GraphDriver, llm_client: LLMClient, extracted_edges: list[EntityEdge]
|
|
251
260
|
) -> list[EntityEdge]:
|
|
252
261
|
# First compress edges
|
|
253
262
|
compressed_edges = await compress_edges(llm_client, extracted_edges)
|
|
@@ -2,9 +2,9 @@ import asyncio
|
|
|
2
2
|
import logging
|
|
3
3
|
from collections import defaultdict
|
|
4
4
|
|
|
5
|
-
from neo4j import AsyncDriver
|
|
6
5
|
from pydantic import BaseModel
|
|
7
6
|
|
|
7
|
+
from graphiti_core.driver.driver import GraphDriver
|
|
8
8
|
from graphiti_core.edges import CommunityEdge
|
|
9
9
|
from graphiti_core.embedder import EmbedderClient
|
|
10
10
|
from graphiti_core.helpers import DEFAULT_DATABASE, semaphore_gather
|
|
@@ -26,7 +26,7 @@ class Neighbor(BaseModel):
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
async def get_community_clusters(
|
|
29
|
-
driver:
|
|
29
|
+
driver: GraphDriver, group_ids: list[str] | None
|
|
30
30
|
) -> list[list[EntityNode]]:
|
|
31
31
|
community_clusters: list[list[EntityNode]] = []
|
|
32
32
|
|
|
@@ -95,7 +95,6 @@ def label_propagation(projection: dict[str, list[Neighbor]]) -> list[list[str]]:
|
|
|
95
95
|
community_candidates: dict[int, int] = defaultdict(int)
|
|
96
96
|
for neighbor in neighbors:
|
|
97
97
|
community_candidates[community_map[neighbor.node_uuid]] += neighbor.edge_count
|
|
98
|
-
|
|
99
98
|
community_lst = [
|
|
100
99
|
(count, community) for community, count in community_candidates.items()
|
|
101
100
|
]
|
|
@@ -194,7 +193,7 @@ async def build_community(
|
|
|
194
193
|
|
|
195
194
|
|
|
196
195
|
async def build_communities(
|
|
197
|
-
driver:
|
|
196
|
+
driver: GraphDriver, llm_client: LLMClient, group_ids: list[str] | None
|
|
198
197
|
) -> tuple[list[CommunityNode], list[CommunityEdge]]:
|
|
199
198
|
community_clusters = await get_community_clusters(driver, group_ids)
|
|
200
199
|
|
|
@@ -219,7 +218,7 @@ async def build_communities(
|
|
|
219
218
|
return community_nodes, community_edges
|
|
220
219
|
|
|
221
220
|
|
|
222
|
-
async def remove_communities(driver:
|
|
221
|
+
async def remove_communities(driver: GraphDriver):
|
|
223
222
|
await driver.execute_query(
|
|
224
223
|
"""
|
|
225
224
|
MATCH (c:Community)
|
|
@@ -230,7 +229,7 @@ async def remove_communities(driver: AsyncDriver):
|
|
|
230
229
|
|
|
231
230
|
|
|
232
231
|
async def determine_entity_community(
|
|
233
|
-
driver:
|
|
232
|
+
driver: GraphDriver, entity: EntityNode
|
|
234
233
|
) -> tuple[CommunityNode | None, bool]:
|
|
235
234
|
# Check if the node is already part of a community
|
|
236
235
|
records, _, _ = await driver.execute_query(
|
|
@@ -291,7 +290,7 @@ async def determine_entity_community(
|
|
|
291
290
|
|
|
292
291
|
|
|
293
292
|
async def update_community(
|
|
294
|
-
driver:
|
|
293
|
+
driver: GraphDriver, llm_client: LLMClient, embedder: EmbedderClient, entity: EntityNode
|
|
295
294
|
):
|
|
296
295
|
community, is_new = await determine_entity_community(driver, entity)
|
|
297
296
|
|
|
@@ -92,8 +92,6 @@ async def extract_edges(
|
|
|
92
92
|
extract_edges_max_tokens = 16384
|
|
93
93
|
llm_client = clients.llm_client
|
|
94
94
|
|
|
95
|
-
node_uuids_by_name_map = {node.name: node.uuid for node in nodes}
|
|
96
|
-
|
|
97
95
|
edge_types_context = (
|
|
98
96
|
[
|
|
99
97
|
{
|
|
@@ -109,7 +107,7 @@ async def extract_edges(
|
|
|
109
107
|
# Prepare context for LLM
|
|
110
108
|
context = {
|
|
111
109
|
'episode_content': episode.content,
|
|
112
|
-
'nodes': [node.name for node in nodes],
|
|
110
|
+
'nodes': [{'id': idx, 'name': node.name} for idx, node in enumerate(nodes)],
|
|
113
111
|
'previous_episodes': [ep.content for ep in previous_episodes],
|
|
114
112
|
'reference_time': episode.valid_at,
|
|
115
113
|
'edge_types': edge_types_context,
|
|
@@ -160,14 +158,16 @@ async def extract_edges(
|
|
|
160
158
|
invalid_at = edge_data.get('invalid_at', None)
|
|
161
159
|
valid_at_datetime = None
|
|
162
160
|
invalid_at_datetime = None
|
|
163
|
-
source_node_uuid = node_uuids_by_name_map.get(edge_data.get('source_entity_name', ''), '')
|
|
164
|
-
target_node_uuid = node_uuids_by_name_map.get(edge_data.get('target_entity_name', ''), '')
|
|
165
161
|
|
|
166
|
-
|
|
162
|
+
source_node_idx = edge_data.get('source_entity_id', -1)
|
|
163
|
+
target_node_idx = edge_data.get('target_entity_id', -1)
|
|
164
|
+
if not (-1 < source_node_idx < len(nodes) and -1 < target_node_idx < len(nodes)):
|
|
167
165
|
logger.warning(
|
|
168
|
-
f'WARNING: source or target node not filled {edge_data.get("edge_name")}. source_node_uuid: {
|
|
166
|
+
f'WARNING: source or target node not filled {edge_data.get("edge_name")}. source_node_uuid: {source_node_idx} and target_node_uuid: {target_node_idx} '
|
|
169
167
|
)
|
|
170
168
|
continue
|
|
169
|
+
source_node_uuid = nodes[source_node_idx].uuid
|
|
170
|
+
target_node_uuid = nodes[edge_data.get('target_entity_id')].uuid
|
|
171
171
|
|
|
172
172
|
if valid_at:
|
|
173
173
|
try:
|
|
@@ -260,7 +260,6 @@ async def resolve_extracted_edges(
|
|
|
260
260
|
driver = clients.driver
|
|
261
261
|
llm_client = clients.llm_client
|
|
262
262
|
embedder = clients.embedder
|
|
263
|
-
|
|
264
263
|
await create_entity_edge_embeddings(embedder, extracted_edges)
|
|
265
264
|
|
|
266
265
|
search_results: tuple[list[list[EntityEdge]], list[list[EntityEdge]]] = await semaphore_gather(
|
|
@@ -17,9 +17,10 @@ limitations under the License.
|
|
|
17
17
|
import logging
|
|
18
18
|
from datetime import datetime, timezone
|
|
19
19
|
|
|
20
|
-
from neo4j import AsyncDriver
|
|
21
20
|
from typing_extensions import LiteralString
|
|
22
21
|
|
|
22
|
+
from graphiti_core.driver.driver import GraphDriver
|
|
23
|
+
from graphiti_core.graph_queries import get_fulltext_indices, get_range_indices
|
|
23
24
|
from graphiti_core.helpers import DEFAULT_DATABASE, semaphore_gather
|
|
24
25
|
from graphiti_core.nodes import EpisodeType, EpisodicNode
|
|
25
26
|
|
|
@@ -28,7 +29,7 @@ EPISODE_WINDOW_LEN = 3
|
|
|
28
29
|
logger = logging.getLogger(__name__)
|
|
29
30
|
|
|
30
31
|
|
|
31
|
-
async def build_indices_and_constraints(driver:
|
|
32
|
+
async def build_indices_and_constraints(driver: GraphDriver, delete_existing: bool = False):
|
|
32
33
|
if delete_existing:
|
|
33
34
|
records, _, _ = await driver.execute_query(
|
|
34
35
|
"""
|
|
@@ -47,39 +48,9 @@ async def build_indices_and_constraints(driver: AsyncDriver, delete_existing: bo
|
|
|
47
48
|
for name in index_names
|
|
48
49
|
]
|
|
49
50
|
)
|
|
51
|
+
range_indices: list[LiteralString] = get_range_indices(driver.provider)
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
'CREATE INDEX entity_uuid IF NOT EXISTS FOR (n:Entity) ON (n.uuid)',
|
|
53
|
-
'CREATE INDEX episode_uuid IF NOT EXISTS FOR (n:Episodic) ON (n.uuid)',
|
|
54
|
-
'CREATE INDEX community_uuid IF NOT EXISTS FOR (n:Community) ON (n.uuid)',
|
|
55
|
-
'CREATE INDEX relation_uuid IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.uuid)',
|
|
56
|
-
'CREATE INDEX mention_uuid IF NOT EXISTS FOR ()-[e:MENTIONS]-() ON (e.uuid)',
|
|
57
|
-
'CREATE INDEX has_member_uuid IF NOT EXISTS FOR ()-[e:HAS_MEMBER]-() ON (e.uuid)',
|
|
58
|
-
'CREATE INDEX entity_group_id IF NOT EXISTS FOR (n:Entity) ON (n.group_id)',
|
|
59
|
-
'CREATE INDEX episode_group_id IF NOT EXISTS FOR (n:Episodic) ON (n.group_id)',
|
|
60
|
-
'CREATE INDEX relation_group_id IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.group_id)',
|
|
61
|
-
'CREATE INDEX mention_group_id IF NOT EXISTS FOR ()-[e:MENTIONS]-() ON (e.group_id)',
|
|
62
|
-
'CREATE INDEX name_entity_index IF NOT EXISTS FOR (n:Entity) ON (n.name)',
|
|
63
|
-
'CREATE INDEX created_at_entity_index IF NOT EXISTS FOR (n:Entity) ON (n.created_at)',
|
|
64
|
-
'CREATE INDEX created_at_episodic_index IF NOT EXISTS FOR (n:Episodic) ON (n.created_at)',
|
|
65
|
-
'CREATE INDEX valid_at_episodic_index IF NOT EXISTS FOR (n:Episodic) ON (n.valid_at)',
|
|
66
|
-
'CREATE INDEX name_edge_index IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.name)',
|
|
67
|
-
'CREATE INDEX created_at_edge_index IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.created_at)',
|
|
68
|
-
'CREATE INDEX expired_at_edge_index IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.expired_at)',
|
|
69
|
-
'CREATE INDEX valid_at_edge_index IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.valid_at)',
|
|
70
|
-
'CREATE INDEX invalid_at_edge_index IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.invalid_at)',
|
|
71
|
-
]
|
|
72
|
-
|
|
73
|
-
fulltext_indices: list[LiteralString] = [
|
|
74
|
-
"""CREATE FULLTEXT INDEX episode_content IF NOT EXISTS
|
|
75
|
-
FOR (e:Episodic) ON EACH [e.content, e.source, e.source_description, e.group_id]""",
|
|
76
|
-
"""CREATE FULLTEXT INDEX node_name_and_summary IF NOT EXISTS
|
|
77
|
-
FOR (n:Entity) ON EACH [n.name, n.summary, n.group_id]""",
|
|
78
|
-
"""CREATE FULLTEXT INDEX community_name IF NOT EXISTS
|
|
79
|
-
FOR (n:Community) ON EACH [n.name, n.group_id]""",
|
|
80
|
-
"""CREATE FULLTEXT INDEX edge_name_and_fact IF NOT EXISTS
|
|
81
|
-
FOR ()-[e:RELATES_TO]-() ON EACH [e.name, e.fact, e.group_id]""",
|
|
82
|
-
]
|
|
53
|
+
fulltext_indices: list[LiteralString] = get_fulltext_indices(driver.provider)
|
|
83
54
|
|
|
84
55
|
index_queries: list[LiteralString] = range_indices + fulltext_indices
|
|
85
56
|
|
|
@@ -94,7 +65,7 @@ async def build_indices_and_constraints(driver: AsyncDriver, delete_existing: bo
|
|
|
94
65
|
)
|
|
95
66
|
|
|
96
67
|
|
|
97
|
-
async def clear_data(driver:
|
|
68
|
+
async def clear_data(driver: GraphDriver, group_ids: list[str] | None = None):
|
|
98
69
|
async with driver.session(database=DEFAULT_DATABASE) as session:
|
|
99
70
|
|
|
100
71
|
async def delete_all(tx):
|
|
@@ -113,7 +84,7 @@ async def clear_data(driver: AsyncDriver, group_ids: list[str] | None = None):
|
|
|
113
84
|
|
|
114
85
|
|
|
115
86
|
async def retrieve_episodes(
|
|
116
|
-
driver:
|
|
87
|
+
driver: GraphDriver,
|
|
117
88
|
reference_time: datetime,
|
|
118
89
|
last_n: int = EPISODE_WINDOW_LEN,
|
|
119
90
|
group_ids: list[str] | None = None,
|
|
@@ -123,7 +94,7 @@ async def retrieve_episodes(
|
|
|
123
94
|
Retrieve the last n episodic nodes from the graph.
|
|
124
95
|
|
|
125
96
|
Args:
|
|
126
|
-
driver (
|
|
97
|
+
driver (Driver): The Neo4j driver instance.
|
|
127
98
|
reference_time (datetime): The reference time to filter episodes. Only episodes with a valid_at timestamp
|
|
128
99
|
less than or equal to this reference_time will be retrieved. This allows for
|
|
129
100
|
querying the graph's state at a specific point in time.
|
|
@@ -140,8 +111,8 @@ async def retrieve_episodes(
|
|
|
140
111
|
|
|
141
112
|
query: LiteralString = (
|
|
142
113
|
"""
|
|
143
|
-
|
|
144
|
-
|
|
114
|
+
MATCH (e:Episodic) WHERE e.valid_at <= $reference_time
|
|
115
|
+
"""
|
|
145
116
|
+ group_id_filter
|
|
146
117
|
+ source_filter
|
|
147
118
|
+ """
|
|
@@ -157,8 +128,7 @@ async def retrieve_episodes(
|
|
|
157
128
|
LIMIT $num_episodes
|
|
158
129
|
"""
|
|
159
130
|
)
|
|
160
|
-
|
|
161
|
-
result = await driver.execute_query(
|
|
131
|
+
result, _, _ = await driver.execute_query(
|
|
162
132
|
query,
|
|
163
133
|
reference_time=reference_time,
|
|
164
134
|
source=source.name if source is not None else None,
|
|
@@ -166,6 +136,7 @@ async def retrieve_episodes(
|
|
|
166
136
|
group_ids=group_ids,
|
|
167
137
|
database_=DEFAULT_DATABASE,
|
|
168
138
|
)
|
|
139
|
+
|
|
169
140
|
episodes = [
|
|
170
141
|
EpisodicNode(
|
|
171
142
|
content=record['content'],
|
|
@@ -179,6 +150,6 @@ async def retrieve_episodes(
|
|
|
179
150
|
name=record['name'],
|
|
180
151
|
source_description=record['source_description'],
|
|
181
152
|
)
|
|
182
|
-
for record in result
|
|
153
|
+
for record in result
|
|
183
154
|
]
|
|
184
155
|
return list(reversed(episodes)) # Return in chronological order
|
|
@@ -29,7 +29,7 @@ from graphiti_core.llm_client import LLMClient
|
|
|
29
29
|
from graphiti_core.llm_client.config import ModelSize
|
|
30
30
|
from graphiti_core.nodes import EntityNode, EpisodeType, EpisodicNode, create_entity_node_embeddings
|
|
31
31
|
from graphiti_core.prompts import prompt_library
|
|
32
|
-
from graphiti_core.prompts.dedupe_nodes import
|
|
32
|
+
from graphiti_core.prompts.dedupe_nodes import NodeResolutions
|
|
33
33
|
from graphiti_core.prompts.extract_nodes import (
|
|
34
34
|
ExtractedEntities,
|
|
35
35
|
ExtractedEntity,
|
|
@@ -241,7 +241,25 @@ async def resolve_extracted_nodes(
|
|
|
241
241
|
]
|
|
242
242
|
)
|
|
243
243
|
|
|
244
|
-
|
|
244
|
+
existing_nodes_dict: dict[str, EntityNode] = {
|
|
245
|
+
node.uuid: node for result in search_results for node in result.nodes
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
existing_nodes: list[EntityNode] = list(existing_nodes_dict.values())
|
|
249
|
+
|
|
250
|
+
existing_nodes_context = (
|
|
251
|
+
[
|
|
252
|
+
{
|
|
253
|
+
**{
|
|
254
|
+
'idx': i,
|
|
255
|
+
'name': candidate.name,
|
|
256
|
+
'entity_types': candidate.labels,
|
|
257
|
+
},
|
|
258
|
+
**candidate.attributes,
|
|
259
|
+
}
|
|
260
|
+
for i, candidate in enumerate(existing_nodes)
|
|
261
|
+
],
|
|
262
|
+
)
|
|
245
263
|
|
|
246
264
|
entity_types_dict: dict[str, BaseModel] = entity_types if entity_types is not None else {}
|
|
247
265
|
|
|
@@ -255,23 +273,13 @@ async def resolve_extracted_nodes(
|
|
|
255
273
|
next((item for item in node.labels if item != 'Entity'), '')
|
|
256
274
|
).__doc__
|
|
257
275
|
or 'Default Entity Type',
|
|
258
|
-
'duplication_candidates': [
|
|
259
|
-
{
|
|
260
|
-
**{
|
|
261
|
-
'idx': j,
|
|
262
|
-
'name': candidate.name,
|
|
263
|
-
'entity_types': candidate.labels,
|
|
264
|
-
},
|
|
265
|
-
**candidate.attributes,
|
|
266
|
-
}
|
|
267
|
-
for j, candidate in enumerate(existing_nodes_lists[i])
|
|
268
|
-
],
|
|
269
276
|
}
|
|
270
277
|
for i, node in enumerate(extracted_nodes)
|
|
271
278
|
]
|
|
272
279
|
|
|
273
280
|
context = {
|
|
274
281
|
'extracted_nodes': extracted_nodes_context,
|
|
282
|
+
'existing_nodes': existing_nodes_context,
|
|
275
283
|
'episode_content': episode.content if episode is not None else '',
|
|
276
284
|
'previous_episodes': [ep.content for ep in previous_episodes]
|
|
277
285
|
if previous_episodes is not None
|
|
@@ -294,8 +302,8 @@ async def resolve_extracted_nodes(
|
|
|
294
302
|
extracted_node = extracted_nodes[resolution_id]
|
|
295
303
|
|
|
296
304
|
resolved_node = (
|
|
297
|
-
|
|
298
|
-
if 0 <= duplicate_idx < len(
|
|
305
|
+
existing_nodes[duplicate_idx]
|
|
306
|
+
if 0 <= duplicate_idx < len(existing_nodes)
|
|
299
307
|
else extracted_node
|
|
300
308
|
)
|
|
301
309
|
|
|
@@ -309,70 +317,6 @@ async def resolve_extracted_nodes(
|
|
|
309
317
|
return resolved_nodes, uuid_map
|
|
310
318
|
|
|
311
319
|
|
|
312
|
-
async def resolve_extracted_node(
|
|
313
|
-
llm_client: LLMClient,
|
|
314
|
-
extracted_node: EntityNode,
|
|
315
|
-
existing_nodes: list[EntityNode],
|
|
316
|
-
episode: EpisodicNode | None = None,
|
|
317
|
-
previous_episodes: list[EpisodicNode] | None = None,
|
|
318
|
-
entity_type: BaseModel | None = None,
|
|
319
|
-
) -> EntityNode:
|
|
320
|
-
start = time()
|
|
321
|
-
if len(existing_nodes) == 0:
|
|
322
|
-
return extracted_node
|
|
323
|
-
|
|
324
|
-
# Prepare context for LLM
|
|
325
|
-
existing_nodes_context = [
|
|
326
|
-
{
|
|
327
|
-
**{
|
|
328
|
-
'id': i,
|
|
329
|
-
'name': node.name,
|
|
330
|
-
'entity_types': node.labels,
|
|
331
|
-
},
|
|
332
|
-
**node.attributes,
|
|
333
|
-
}
|
|
334
|
-
for i, node in enumerate(existing_nodes)
|
|
335
|
-
]
|
|
336
|
-
|
|
337
|
-
extracted_node_context = {
|
|
338
|
-
'name': extracted_node.name,
|
|
339
|
-
'entity_type': entity_type.__name__ if entity_type is not None else 'Entity', # type: ignore
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
context = {
|
|
343
|
-
'existing_nodes': existing_nodes_context,
|
|
344
|
-
'extracted_node': extracted_node_context,
|
|
345
|
-
'entity_type_description': entity_type.__doc__
|
|
346
|
-
if entity_type is not None
|
|
347
|
-
else 'Default Entity Type',
|
|
348
|
-
'episode_content': episode.content if episode is not None else '',
|
|
349
|
-
'previous_episodes': [ep.content for ep in previous_episodes]
|
|
350
|
-
if previous_episodes is not None
|
|
351
|
-
else [],
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
llm_response = await llm_client.generate_response(
|
|
355
|
-
prompt_library.dedupe_nodes.node(context),
|
|
356
|
-
response_model=NodeDuplicate,
|
|
357
|
-
model_size=ModelSize.small,
|
|
358
|
-
)
|
|
359
|
-
|
|
360
|
-
duplicate_id: int = llm_response.get('duplicate_node_id', -1)
|
|
361
|
-
|
|
362
|
-
node = (
|
|
363
|
-
existing_nodes[duplicate_id] if 0 <= duplicate_id < len(existing_nodes) else extracted_node
|
|
364
|
-
)
|
|
365
|
-
|
|
366
|
-
node.name = llm_response.get('name', '')
|
|
367
|
-
|
|
368
|
-
end = time()
|
|
369
|
-
logger.debug(
|
|
370
|
-
f'Resolved node: {extracted_node.name} is {node.name}, in {(end - start) * 1000} ms'
|
|
371
|
-
)
|
|
372
|
-
|
|
373
|
-
return node
|
|
374
|
-
|
|
375
|
-
|
|
376
320
|
async def extract_attributes_from_nodes(
|
|
377
321
|
clients: GraphitiClients,
|
|
378
322
|
nodes: list[EntityNode],
|
|
@@ -382,7 +326,6 @@ async def extract_attributes_from_nodes(
|
|
|
382
326
|
) -> list[EntityNode]:
|
|
383
327
|
llm_client = clients.llm_client
|
|
384
328
|
embedder = clients.embedder
|
|
385
|
-
|
|
386
329
|
updated_nodes: list[EntityNode] = await semaphore_gather(
|
|
387
330
|
*[
|
|
388
331
|
extract_attributes_from_node(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: graphiti-core
|
|
3
|
-
Version: 0.12.
|
|
3
|
+
Version: 0.12.1
|
|
4
4
|
Summary: A temporal graph building library
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Author: Paul Paliychuk
|
|
@@ -17,9 +17,10 @@ Provides-Extra: google-genai
|
|
|
17
17
|
Provides-Extra: groq
|
|
18
18
|
Requires-Dist: anthropic (>=0.49.0) ; extra == "anthropic"
|
|
19
19
|
Requires-Dist: diskcache (>=5.6.3)
|
|
20
|
+
Requires-Dist: falkordb (>=1.1.2,<2.0.0)
|
|
20
21
|
Requires-Dist: google-genai (>=1.8.0) ; extra == "google-genai"
|
|
21
22
|
Requires-Dist: groq (>=0.2.0) ; extra == "groq"
|
|
22
|
-
Requires-Dist: neo4j (>=5.
|
|
23
|
+
Requires-Dist: neo4j (>=5.26.0)
|
|
23
24
|
Requires-Dist: numpy (>=1.0.0)
|
|
24
25
|
Requires-Dist: openai (>=1.53.0)
|
|
25
26
|
Requires-Dist: pydantic (>=2.11.5)
|
|
@@ -136,7 +137,7 @@ Graphiti is specifically designed to address the challenges of dynamic and frequ
|
|
|
136
137
|
Requirements:
|
|
137
138
|
|
|
138
139
|
- Python 3.10 or higher
|
|
139
|
-
- Neo4j 5.26 or higher (serves as the embeddings storage backend)
|
|
140
|
+
- Neo4j 5.26 / FalkorDB 1.1.2 or higher (serves as the embeddings storage backend)
|
|
140
141
|
- OpenAI API key (for LLM inference and embedding)
|
|
141
142
|
|
|
142
143
|
> [!IMPORTANT]
|
|
@@ -2,19 +2,26 @@ graphiti_core/__init__.py,sha256=e5SWFkRiaUwfprYIeIgVIh7JDedNiloZvd3roU-0aDY,55
|
|
|
2
2
|
graphiti_core/cross_encoder/__init__.py,sha256=hry59vz21x-AtGZ0MJ7ugw0HTwJkXiddpp_Yqnwsen0,723
|
|
3
3
|
graphiti_core/cross_encoder/bge_reranker_client.py,sha256=sY7RKsCp90vTjYxv6vmIHT4p3oCsFCRYWH-H0Ia0vN0,1449
|
|
4
4
|
graphiti_core/cross_encoder/client.py,sha256=KLsbfWKOEaAV3adFe3XZlAeb-gje9_sVKCVZTaJP3ac,1441
|
|
5
|
-
graphiti_core/cross_encoder/openai_reranker_client.py,sha256=
|
|
6
|
-
graphiti_core/
|
|
5
|
+
graphiti_core/cross_encoder/openai_reranker_client.py,sha256=_Hftiz250HbEkY_26z6A1oxg4pzM8Sbr8CwnbJEsggc,4522
|
|
6
|
+
graphiti_core/driver/__init__.py,sha256=DumfxIEY3z_nkz5YGaYH1GM50HgeAdEowNK189jcdAg,626
|
|
7
|
+
graphiti_core/driver/driver.py,sha256=-FHAA2gM8FA0re-q6udmjQ6pNFdFGRQrMRuAiqX_1A4,1829
|
|
8
|
+
graphiti_core/driver/falkordb_driver.py,sha256=Iz3wnfoJIO7EslqZvG6mduyZ5C-DWxFDPM5Q4QJRCuo,4686
|
|
9
|
+
graphiti_core/driver/neo4j_driver.py,sha256=D8CV5GbhKoHIQ78BA9ozlwdvXPLUbBmFSfT2lww8PJk,1910
|
|
10
|
+
graphiti_core/edges.py,sha256=h67vyXYhZYqlwaOmaqjHiGns6nEjuBVSIAFBMveNVo8,16257
|
|
7
11
|
graphiti_core/embedder/__init__.py,sha256=EL564ZuE-DZjcuKNUK_exMn_XHXm2LdO9fzdXePVKL4,179
|
|
12
|
+
graphiti_core/embedder/azure_openai.py,sha256=OyomPwC1fIsddI-3n6g00kQFdQznZorBhHwkQKCLUok,2384
|
|
8
13
|
graphiti_core/embedder/client.py,sha256=qEpSHceL_Gc4QQPJWIOnuNLemNuR_TYA4r28t2Vldbg,1115
|
|
9
14
|
graphiti_core/embedder/gemini.py,sha256=7En-W46YxqC5qL3vYB5Ed-Xm0hqLxi7-LgZ95c4M7ME,3263
|
|
10
15
|
graphiti_core/embedder/openai.py,sha256=bIThUoLMeGlHG2-3VikzK6JZfOHKn4PKvUMx5sHxJy8,2192
|
|
11
16
|
graphiti_core/embedder/voyage.py,sha256=gQhdcz2IYPSyOcDn3w8aHToVS3KQhyZrUBm4vqr3WcE,2224
|
|
12
17
|
graphiti_core/errors.py,sha256=Nib1uQx2cO_VOizupmRjpFfmuRg-hFAVqTtZAuBehR8,2405
|
|
13
|
-
graphiti_core/
|
|
14
|
-
graphiti_core/
|
|
15
|
-
graphiti_core/
|
|
16
|
-
graphiti_core/
|
|
18
|
+
graphiti_core/graph_queries.py,sha256=KfWDp8xDnPa9bcHskw8NeMpeeHBtZWBCosVdu1Iwv34,7076
|
|
19
|
+
graphiti_core/graphiti.py,sha256=oMLoKs87aoT3WhPVPuKCyd9H0i_ZTXl7FleuCNnBVms,28013
|
|
20
|
+
graphiti_core/graphiti_types.py,sha256=rL-9bvnLobunJfXU4hkD6mAj14pofKp_wq8QsFDZwDU,1035
|
|
21
|
+
graphiti_core/helpers.py,sha256=sfC1M6KefKaZll6FQcpcNvWnCN6iCvBSJksAkGTItT4,3059
|
|
22
|
+
graphiti_core/llm_client/__init__.py,sha256=QgBWUiCeBp6YiA_xqyrDvJ9jIyy1hngH8g7FWahN3nw,776
|
|
17
23
|
graphiti_core/llm_client/anthropic_client.py,sha256=392rtkH_I7yOJUlQvjoOnS8Lz14WBP8egQ3OfRH0nFs,12481
|
|
24
|
+
graphiti_core/llm_client/azure_openai_client.py,sha256=B6EbNIktP9FBqiFrGunVQlego2e3C5zBAbcHI55Y-OY,2680
|
|
18
25
|
graphiti_core/llm_client/client.py,sha256=v_w5TBbDJYYADCXSs2r287g5Ami2Urma-GGEbHSI_Jg,5826
|
|
19
26
|
graphiti_core/llm_client/config.py,sha256=90IgSBxZE_3nWdaEONVLUznI8lytPA7ZyexQz-_c55U,2560
|
|
20
27
|
graphiti_core/llm_client/errors.py,sha256=pn6brRiLW60DAUIXJYKBT6MInrS4ueuH1hNLbn_JbQo,1243
|
|
@@ -28,13 +35,13 @@ graphiti_core/models/edges/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
|
|
|
28
35
|
graphiti_core/models/edges/edge_db_queries.py,sha256=W2-ljKnZOt5MlD9_M4f_823GdyTMRzW2tJX0CezaixY,2284
|
|
29
36
|
graphiti_core/models/nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
37
|
graphiti_core/models/nodes/node_db_queries.py,sha256=AQgRGVO-GgFWfLq1G6k8s86WItwpXruy3Mj4DBli-vM,2145
|
|
31
|
-
graphiti_core/nodes.py,sha256=
|
|
38
|
+
graphiti_core/nodes.py,sha256=kdJY-Ugyk6J2x70w4EF_EoFNgy7D3TMOMVSUfEth6rE,18665
|
|
32
39
|
graphiti_core/prompts/__init__.py,sha256=EA-x9xUki9l8wnu2l8ek_oNf75-do5tq5hVq7Zbv8Kw,101
|
|
33
40
|
graphiti_core/prompts/dedupe_edges.py,sha256=AFVC1EQ0TvNkSp0G7QZmIh3YpGg9FVXo1_sT3TlRqA8,5473
|
|
34
|
-
graphiti_core/prompts/dedupe_nodes.py,sha256=
|
|
41
|
+
graphiti_core/prompts/dedupe_nodes.py,sha256=OIhMkKexRpQQ0dEr4NW_WE1ta7wLO3RibJA7Ge41uDg,7407
|
|
35
42
|
graphiti_core/prompts/eval.py,sha256=gnBQTmwsCl3Qvwpcm7aieVszzo6y1sMCUT8jQiKTvvE,5317
|
|
36
43
|
graphiti_core/prompts/extract_edge_dates.py,sha256=3Drs3CmvP0gJN5BidWSxrNvLet3HPoTybU3BUIAoc0Y,4218
|
|
37
|
-
graphiti_core/prompts/extract_edges.py,sha256=
|
|
44
|
+
graphiti_core/prompts/extract_edges.py,sha256=9NdxAKyXHiFOSuyAzzxRM38BmqtynGEbtmMUr3VTrtM,6513
|
|
38
45
|
graphiti_core/prompts/extract_nodes.py,sha256=EYuX99P8ly7pSOBz87ZA9fJF8V6g6epbVj5Cq0YM8h8,9624
|
|
39
46
|
graphiti_core/prompts/invalidate_edges.py,sha256=yfpcs_pyctnoM77ULPZXEtKW0oHr1MeLsJzC5yrE-o4,3547
|
|
40
47
|
graphiti_core/prompts/lib.py,sha256=DCyHePM4_q-CptTpEXGO_dBv9k7xDtclEaB1dGu7EcI,4092
|
|
@@ -43,24 +50,24 @@ graphiti_core/prompts/prompt_helpers.py,sha256=-9TABwIcIQUVHcNANx6wIZd-FT2DgYKyG
|
|
|
43
50
|
graphiti_core/prompts/summarize_nodes.py,sha256=tbg-AgWlzgFBeImKkZ28h2SpmqfPPqvN2Ol1Q71VF9Y,4146
|
|
44
51
|
graphiti_core/py.typed,sha256=vlmmzQOt7bmeQl9L3XJP4W6Ry0iiELepnOrinKz5KQg,79
|
|
45
52
|
graphiti_core/search/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
|
-
graphiti_core/search/search.py,sha256=
|
|
53
|
+
graphiti_core/search/search.py,sha256=bJCFaNApu5396pXTa-xciu8ORDdRFJqfE3j2ieRVd7Y,15162
|
|
47
54
|
graphiti_core/search/search_config.py,sha256=VvKg6AB_RPhoe56DBBXHRBXHThAVJ_OLFCyq_yKof-A,3765
|
|
48
55
|
graphiti_core/search/search_config_recipes.py,sha256=4GquRphHhJlpXQhAZOySYnCzBWYoTwxlJj44eTOavZQ,7443
|
|
49
56
|
graphiti_core/search/search_filters.py,sha256=jG30nMWX03xoT9ohgyHNu_Xes8GwjIF2eTv6QaiWMqw,6466
|
|
50
57
|
graphiti_core/search/search_helpers.py,sha256=G5Ceaq5Pfgx0Weelqgeylp_pUHwiBnINaUYsDbURJbE,2636
|
|
51
|
-
graphiti_core/search/search_utils.py,sha256=
|
|
58
|
+
graphiti_core/search/search_utils.py,sha256=k9KKN4sYde0Hqw9BKb5T-8q-3hInIPwq9aYoGfheq6E,34877
|
|
52
59
|
graphiti_core/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
53
|
-
graphiti_core/utils/bulk_utils.py,sha256=
|
|
60
|
+
graphiti_core/utils/bulk_utils.py,sha256=8JgxHnp8whiNJEJjd6Pp4wVeNOquymbi4rczV1ygsBs,16176
|
|
54
61
|
graphiti_core/utils/datetime_utils.py,sha256=Ti-2tnrDFRzBsbfblzsHybsM3jaDLP4-VT2t0VhpIzU,1357
|
|
55
62
|
graphiti_core/utils/maintenance/__init__.py,sha256=vW4H1KyapTl-OOz578uZABYcpND4wPx3Vt6aAPaXh78,301
|
|
56
|
-
graphiti_core/utils/maintenance/community_operations.py,sha256=
|
|
57
|
-
graphiti_core/utils/maintenance/edge_operations.py,sha256=
|
|
58
|
-
graphiti_core/utils/maintenance/graph_data_operations.py,sha256=
|
|
59
|
-
graphiti_core/utils/maintenance/node_operations.py,sha256=
|
|
63
|
+
graphiti_core/utils/maintenance/community_operations.py,sha256=2rhRqtL9gDbjXKO4-S0nGpaWvS4ck5rFiazZiogIJao,10088
|
|
64
|
+
graphiti_core/utils/maintenance/edge_operations.py,sha256=74HcCWaq7rfQuUH8RsWPr-OYE-5qe8s2BaiBYfkMNT0,19272
|
|
65
|
+
graphiti_core/utils/maintenance/graph_data_operations.py,sha256=OHuiAyP1Z7dfR90dWVQ87TJQO83P0sQihJyr4WIhOhk,5362
|
|
66
|
+
graphiti_core/utils/maintenance/node_operations.py,sha256=5kouyCkzAzIGYUBtViMhNWYL95tA1__TsSWuLj0_RdY,14795
|
|
60
67
|
graphiti_core/utils/maintenance/temporal_operations.py,sha256=mJkw9xLB4W2BsLfC5POr0r-PHWL9SIfNj_l_xu0B5ug,3410
|
|
61
68
|
graphiti_core/utils/maintenance/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
62
69
|
graphiti_core/utils/ontology_utils/entity_types_utils.py,sha256=QJX5cG0GSSNF_Mm_yrldr69wjVAbN_MxLhOSznz85Hk,1279
|
|
63
|
-
graphiti_core-0.12.
|
|
64
|
-
graphiti_core-0.12.
|
|
65
|
-
graphiti_core-0.12.
|
|
66
|
-
graphiti_core-0.12.
|
|
70
|
+
graphiti_core-0.12.1.dist-info/LICENSE,sha256=KCUwCyDXuVEgmDWkozHyniRyWjnWUWjkuDHfU6o3JlA,11325
|
|
71
|
+
graphiti_core-0.12.1.dist-info/METADATA,sha256=fnlE-myapsbDDM_d23H4FbZ88RBrChm-iF9jhIHGhMo,15590
|
|
72
|
+
graphiti_core-0.12.1.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
|
73
|
+
graphiti_core-0.12.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|