graphiti-core 0.18.0__py3-none-any.whl → 0.18.2__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/driver/driver.py +20 -2
- graphiti_core/driver/falkordb_driver.py +16 -9
- graphiti_core/driver/neo4j_driver.py +8 -6
- graphiti_core/edges.py +73 -99
- graphiti_core/graph_queries.py +51 -97
- graphiti_core/graphiti.py +27 -12
- graphiti_core/helpers.py +4 -3
- graphiti_core/models/edges/edge_db_queries.py +106 -32
- graphiti_core/models/nodes/node_db_queries.py +101 -20
- graphiti_core/nodes.py +113 -128
- graphiti_core/prompts/dedupe_nodes.py +1 -1
- graphiti_core/prompts/extract_edges.py +4 -4
- graphiti_core/prompts/extract_nodes.py +53 -11
- graphiti_core/search/search_filters.py +5 -5
- graphiti_core/search/search_utils.py +138 -185
- graphiti_core/utils/bulk_utils.py +7 -9
- graphiti_core/utils/maintenance/community_operations.py +11 -7
- graphiti_core/utils/maintenance/edge_operations.py +23 -54
- graphiti_core/utils/maintenance/graph_data_operations.py +14 -29
- graphiti_core/utils/maintenance/node_operations.py +40 -87
- graphiti_core/utils/ontology_utils/entity_types_utils.py +1 -1
- {graphiti_core-0.18.0.dist-info → graphiti_core-0.18.2.dist-info}/METADATA +11 -3
- {graphiti_core-0.18.0.dist-info → graphiti_core-0.18.2.dist-info}/RECORD +25 -25
- {graphiti_core-0.18.0.dist-info → graphiti_core-0.18.2.dist-info}/WHEEL +0 -0
- {graphiti_core-0.18.0.dist-info → graphiti_core-0.18.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -34,7 +34,7 @@ async def get_community_clusters(
|
|
|
34
34
|
group_id_values, _, _ = await driver.execute_query(
|
|
35
35
|
"""
|
|
36
36
|
MATCH (n:Entity WHERE n.group_id IS NOT NULL)
|
|
37
|
-
RETURN
|
|
37
|
+
RETURN
|
|
38
38
|
collect(DISTINCT n.group_id) AS group_ids
|
|
39
39
|
""",
|
|
40
40
|
)
|
|
@@ -233,10 +233,10 @@ async def determine_entity_community(
|
|
|
233
233
|
"""
|
|
234
234
|
MATCH (c:Community)-[:HAS_MEMBER]->(n:Entity {uuid: $entity_uuid})
|
|
235
235
|
RETURN
|
|
236
|
-
c.uuid
|
|
236
|
+
c.uuid AS uuid,
|
|
237
237
|
c.name AS name,
|
|
238
238
|
c.group_id AS group_id,
|
|
239
|
-
c.created_at AS created_at,
|
|
239
|
+
c.created_at AS created_at,
|
|
240
240
|
c.summary AS summary
|
|
241
241
|
""",
|
|
242
242
|
entity_uuid=entity.uuid,
|
|
@@ -250,10 +250,10 @@ async def determine_entity_community(
|
|
|
250
250
|
"""
|
|
251
251
|
MATCH (c:Community)-[:HAS_MEMBER]->(m:Entity)-[:RELATES_TO]-(n:Entity {uuid: $entity_uuid})
|
|
252
252
|
RETURN
|
|
253
|
-
c.uuid
|
|
253
|
+
c.uuid AS uuid,
|
|
254
254
|
c.name AS name,
|
|
255
255
|
c.group_id AS group_id,
|
|
256
|
-
c.created_at AS created_at,
|
|
256
|
+
c.created_at AS created_at,
|
|
257
257
|
c.summary AS summary
|
|
258
258
|
""",
|
|
259
259
|
entity_uuid=entity.uuid,
|
|
@@ -286,11 +286,11 @@ async def determine_entity_community(
|
|
|
286
286
|
|
|
287
287
|
async def update_community(
|
|
288
288
|
driver: GraphDriver, llm_client: LLMClient, embedder: EmbedderClient, entity: EntityNode
|
|
289
|
-
):
|
|
289
|
+
) -> tuple[list[CommunityNode], list[CommunityEdge]]:
|
|
290
290
|
community, is_new = await determine_entity_community(driver, entity)
|
|
291
291
|
|
|
292
292
|
if community is None:
|
|
293
|
-
return
|
|
293
|
+
return [], []
|
|
294
294
|
|
|
295
295
|
new_summary = await summarize_pair(llm_client, (entity.summary, community.summary))
|
|
296
296
|
new_name = await generate_summary_description(llm_client, new_summary)
|
|
@@ -298,10 +298,14 @@ async def update_community(
|
|
|
298
298
|
community.summary = new_summary
|
|
299
299
|
community.name = new_name
|
|
300
300
|
|
|
301
|
+
community_edges = []
|
|
301
302
|
if is_new:
|
|
302
303
|
community_edge = (build_community_edges([entity], community, utc_now()))[0]
|
|
303
304
|
await community_edge.save(driver)
|
|
305
|
+
community_edges.append(community_edge)
|
|
304
306
|
|
|
305
307
|
await community.generate_name_embedding(embedder)
|
|
306
308
|
|
|
307
309
|
await community.save(driver)
|
|
310
|
+
|
|
311
|
+
return [community], community_edges
|
|
@@ -34,7 +34,7 @@ from graphiti_core.llm_client import LLMClient
|
|
|
34
34
|
from graphiti_core.llm_client.config import ModelSize
|
|
35
35
|
from graphiti_core.nodes import CommunityNode, EntityNode, EpisodicNode
|
|
36
36
|
from graphiti_core.prompts import prompt_library
|
|
37
|
-
from graphiti_core.prompts.dedupe_edges import EdgeDuplicate
|
|
37
|
+
from graphiti_core.prompts.dedupe_edges import EdgeDuplicate
|
|
38
38
|
from graphiti_core.prompts.extract_edges import ExtractedEdges, MissingFacts
|
|
39
39
|
from graphiti_core.search.search_filters import SearchFilters
|
|
40
40
|
from graphiti_core.search.search_utils import get_edge_invalidation_candidates, get_relevant_edges
|
|
@@ -114,7 +114,7 @@ async def extract_edges(
|
|
|
114
114
|
previous_episodes: list[EpisodicNode],
|
|
115
115
|
edge_type_map: dict[tuple[str, str], list[str]],
|
|
116
116
|
group_id: str = '',
|
|
117
|
-
edge_types: dict[str, BaseModel] | None = None,
|
|
117
|
+
edge_types: dict[str, type[BaseModel]] | None = None,
|
|
118
118
|
) -> list[EntityEdge]:
|
|
119
119
|
start = time()
|
|
120
120
|
|
|
@@ -161,9 +161,9 @@ async def extract_edges(
|
|
|
161
161
|
response_model=ExtractedEdges,
|
|
162
162
|
max_tokens=extract_edges_max_tokens,
|
|
163
163
|
)
|
|
164
|
-
edges_data = llm_response.
|
|
164
|
+
edges_data = ExtractedEdges(**llm_response).edges
|
|
165
165
|
|
|
166
|
-
context['extracted_facts'] = [edge_data.
|
|
166
|
+
context['extracted_facts'] = [edge_data.fact for edge_data in edges_data]
|
|
167
167
|
|
|
168
168
|
reflexion_iterations += 1
|
|
169
169
|
if reflexion_iterations < MAX_REFLEXION_ITERATIONS:
|
|
@@ -193,20 +193,20 @@ async def extract_edges(
|
|
|
193
193
|
edges = []
|
|
194
194
|
for edge_data in edges_data:
|
|
195
195
|
# Validate Edge Date information
|
|
196
|
-
valid_at = edge_data.
|
|
197
|
-
invalid_at = edge_data.
|
|
196
|
+
valid_at = edge_data.valid_at
|
|
197
|
+
invalid_at = edge_data.invalid_at
|
|
198
198
|
valid_at_datetime = None
|
|
199
199
|
invalid_at_datetime = None
|
|
200
200
|
|
|
201
|
-
source_node_idx = edge_data.
|
|
202
|
-
target_node_idx = edge_data.
|
|
201
|
+
source_node_idx = edge_data.source_entity_id
|
|
202
|
+
target_node_idx = edge_data.target_entity_id
|
|
203
203
|
if not (-1 < source_node_idx < len(nodes) and -1 < target_node_idx < len(nodes)):
|
|
204
204
|
logger.warning(
|
|
205
|
-
f'WARNING: source or target node not filled {edge_data.
|
|
205
|
+
f'WARNING: source or target node not filled {edge_data.relation_type}. source_node_uuid: {source_node_idx} and target_node_uuid: {target_node_idx} '
|
|
206
206
|
)
|
|
207
207
|
continue
|
|
208
208
|
source_node_uuid = nodes[source_node_idx].uuid
|
|
209
|
-
target_node_uuid = nodes[edge_data.
|
|
209
|
+
target_node_uuid = nodes[edge_data.target_entity_id].uuid
|
|
210
210
|
|
|
211
211
|
if valid_at:
|
|
212
212
|
try:
|
|
@@ -226,9 +226,9 @@ async def extract_edges(
|
|
|
226
226
|
edge = EntityEdge(
|
|
227
227
|
source_node_uuid=source_node_uuid,
|
|
228
228
|
target_node_uuid=target_node_uuid,
|
|
229
|
-
name=edge_data.
|
|
229
|
+
name=edge_data.relation_type,
|
|
230
230
|
group_id=group_id,
|
|
231
|
-
fact=edge_data.
|
|
231
|
+
fact=edge_data.fact,
|
|
232
232
|
episodes=[episode.uuid],
|
|
233
233
|
created_at=utc_now(),
|
|
234
234
|
valid_at=valid_at_datetime,
|
|
@@ -249,7 +249,7 @@ async def resolve_extracted_edges(
|
|
|
249
249
|
extracted_edges: list[EntityEdge],
|
|
250
250
|
episode: EpisodicNode,
|
|
251
251
|
entities: list[EntityNode],
|
|
252
|
-
edge_types: dict[str, BaseModel],
|
|
252
|
+
edge_types: dict[str, type[BaseModel]],
|
|
253
253
|
edge_type_map: dict[tuple[str, str], list[str]],
|
|
254
254
|
) -> tuple[list[EntityEdge], list[EntityEdge]]:
|
|
255
255
|
driver = clients.driver
|
|
@@ -272,7 +272,7 @@ async def resolve_extracted_edges(
|
|
|
272
272
|
uuid_entity_map: dict[str, EntityNode] = {entity.uuid: entity for entity in entities}
|
|
273
273
|
|
|
274
274
|
# Determine which edge types are relevant for each edge
|
|
275
|
-
edge_types_lst: list[dict[str, BaseModel]] = []
|
|
275
|
+
edge_types_lst: list[dict[str, type[BaseModel]]] = []
|
|
276
276
|
for extracted_edge in extracted_edges:
|
|
277
277
|
source_node = uuid_entity_map.get(extracted_edge.source_node_uuid)
|
|
278
278
|
target_node = uuid_entity_map.get(extracted_edge.target_node_uuid)
|
|
@@ -381,7 +381,7 @@ async def resolve_extracted_edge(
|
|
|
381
381
|
related_edges: list[EntityEdge],
|
|
382
382
|
existing_edges: list[EntityEdge],
|
|
383
383
|
episode: EpisodicNode,
|
|
384
|
-
edge_types: dict[str, BaseModel] | None = None,
|
|
384
|
+
edge_types: dict[str, type[BaseModel]] | None = None,
|
|
385
385
|
) -> tuple[EntityEdge, list[EntityEdge], list[EntityEdge]]:
|
|
386
386
|
if len(related_edges) == 0 and len(existing_edges) == 0:
|
|
387
387
|
return extracted_edge, [], []
|
|
@@ -422,10 +422,10 @@ async def resolve_extracted_edge(
|
|
|
422
422
|
response_model=EdgeDuplicate,
|
|
423
423
|
model_size=ModelSize.small,
|
|
424
424
|
)
|
|
425
|
+
response_object = EdgeDuplicate(**llm_response)
|
|
426
|
+
duplicate_facts = response_object.duplicate_facts
|
|
425
427
|
|
|
426
|
-
duplicate_fact_ids: list[int] =
|
|
427
|
-
filter(lambda i: 0 <= i < len(related_edges), llm_response.get('duplicate_facts', []))
|
|
428
|
-
)
|
|
428
|
+
duplicate_fact_ids: list[int] = [i for i in duplicate_facts if 0 <= i < len(related_edges)]
|
|
429
429
|
|
|
430
430
|
resolved_edge = extracted_edge
|
|
431
431
|
for duplicate_fact_id in duplicate_fact_ids:
|
|
@@ -435,11 +435,13 @@ async def resolve_extracted_edge(
|
|
|
435
435
|
if duplicate_fact_ids and episode is not None:
|
|
436
436
|
resolved_edge.episodes.append(episode.uuid)
|
|
437
437
|
|
|
438
|
-
contradicted_facts: list[int] =
|
|
438
|
+
contradicted_facts: list[int] = response_object.contradicted_facts
|
|
439
439
|
|
|
440
|
-
invalidation_candidates: list[EntityEdge] = [
|
|
440
|
+
invalidation_candidates: list[EntityEdge] = [
|
|
441
|
+
existing_edges[i] for i in contradicted_facts if 0 <= i < len(existing_edges)
|
|
442
|
+
]
|
|
441
443
|
|
|
442
|
-
fact_type: str =
|
|
444
|
+
fact_type: str = response_object.fact_type
|
|
443
445
|
if fact_type.upper() != 'DEFAULT' and edge_types is not None:
|
|
444
446
|
resolved_edge.name = fact_type
|
|
445
447
|
|
|
@@ -494,39 +496,6 @@ async def resolve_extracted_edge(
|
|
|
494
496
|
return resolved_edge, invalidated_edges, duplicate_edges
|
|
495
497
|
|
|
496
498
|
|
|
497
|
-
async def dedupe_edge_list(
|
|
498
|
-
llm_client: LLMClient,
|
|
499
|
-
edges: list[EntityEdge],
|
|
500
|
-
) -> list[EntityEdge]:
|
|
501
|
-
start = time()
|
|
502
|
-
|
|
503
|
-
# Create edge map
|
|
504
|
-
edge_map = {}
|
|
505
|
-
for edge in edges:
|
|
506
|
-
edge_map[edge.uuid] = edge
|
|
507
|
-
|
|
508
|
-
# Prepare context for LLM
|
|
509
|
-
context = {'edges': [{'uuid': edge.uuid, 'fact': edge.fact} for edge in edges]}
|
|
510
|
-
|
|
511
|
-
llm_response = await llm_client.generate_response(
|
|
512
|
-
prompt_library.dedupe_edges.edge_list(context), response_model=UniqueFacts
|
|
513
|
-
)
|
|
514
|
-
unique_edges_data = llm_response.get('unique_facts', [])
|
|
515
|
-
|
|
516
|
-
end = time()
|
|
517
|
-
logger.debug(f'Extracted edge duplicates: {unique_edges_data} in {(end - start) * 1000} ms ')
|
|
518
|
-
|
|
519
|
-
# Get full edge data
|
|
520
|
-
unique_edges = []
|
|
521
|
-
for edge_data in unique_edges_data:
|
|
522
|
-
uuid = edge_data['uuid']
|
|
523
|
-
edge = edge_map[uuid]
|
|
524
|
-
edge.fact = edge_data['fact']
|
|
525
|
-
unique_edges.append(edge)
|
|
526
|
-
|
|
527
|
-
return unique_edges
|
|
528
|
-
|
|
529
|
-
|
|
530
499
|
async def filter_existing_duplicate_of_edges(
|
|
531
500
|
driver: GraphDriver, duplicates_node_tuples: list[tuple[EntityNode, EntityNode]]
|
|
532
501
|
) -> list[tuple[EntityNode, EntityNode]]:
|
|
@@ -15,14 +15,15 @@ limitations under the License.
|
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
import logging
|
|
18
|
-
from datetime import datetime
|
|
18
|
+
from datetime import datetime
|
|
19
19
|
|
|
20
20
|
from typing_extensions import LiteralString
|
|
21
21
|
|
|
22
22
|
from graphiti_core.driver.driver import GraphDriver
|
|
23
23
|
from graphiti_core.graph_queries import get_fulltext_indices, get_range_indices
|
|
24
|
-
from graphiti_core.helpers import
|
|
25
|
-
from graphiti_core.nodes import
|
|
24
|
+
from graphiti_core.helpers import semaphore_gather
|
|
25
|
+
from graphiti_core.models.nodes.node_db_queries import EPISODIC_NODE_RETURN
|
|
26
|
+
from graphiti_core.nodes import EpisodeType, EpisodicNode, get_episodic_node_from_record
|
|
26
27
|
|
|
27
28
|
EPISODE_WINDOW_LEN = 3
|
|
28
29
|
|
|
@@ -33,8 +34,8 @@ async def build_indices_and_constraints(driver: GraphDriver, delete_existing: bo
|
|
|
33
34
|
if delete_existing:
|
|
34
35
|
records, _, _ = await driver.execute_query(
|
|
35
36
|
"""
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
SHOW INDEXES YIELD name
|
|
38
|
+
""",
|
|
38
39
|
)
|
|
39
40
|
index_names = [record['name'] for record in records]
|
|
40
41
|
await semaphore_gather(
|
|
@@ -108,19 +109,16 @@ async def retrieve_episodes(
|
|
|
108
109
|
|
|
109
110
|
query: LiteralString = (
|
|
110
111
|
"""
|
|
111
|
-
|
|
112
|
-
|
|
112
|
+
MATCH (e:Episodic)
|
|
113
|
+
WHERE e.valid_at <= $reference_time
|
|
114
|
+
"""
|
|
113
115
|
+ group_id_filter
|
|
114
116
|
+ source_filter
|
|
115
117
|
+ """
|
|
116
|
-
RETURN
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
e.group_id AS group_id,
|
|
121
|
-
e.name AS name,
|
|
122
|
-
e.source_description AS source_description,
|
|
123
|
-
e.source AS source
|
|
118
|
+
RETURN
|
|
119
|
+
"""
|
|
120
|
+
+ EPISODIC_NODE_RETURN
|
|
121
|
+
+ """
|
|
124
122
|
ORDER BY e.valid_at DESC
|
|
125
123
|
LIMIT $num_episodes
|
|
126
124
|
"""
|
|
@@ -133,18 +131,5 @@ async def retrieve_episodes(
|
|
|
133
131
|
group_ids=group_ids,
|
|
134
132
|
)
|
|
135
133
|
|
|
136
|
-
episodes = [
|
|
137
|
-
EpisodicNode(
|
|
138
|
-
content=record['content'],
|
|
139
|
-
created_at=parse_db_date(record['created_at'])
|
|
140
|
-
or datetime.min.replace(tzinfo=timezone.utc),
|
|
141
|
-
valid_at=parse_db_date(record['valid_at']) or datetime.min.replace(tzinfo=timezone.utc),
|
|
142
|
-
uuid=record['uuid'],
|
|
143
|
-
group_id=record['group_id'],
|
|
144
|
-
source=EpisodeType.from_str(record['source']),
|
|
145
|
-
name=record['name'],
|
|
146
|
-
source_description=record['source_description'],
|
|
147
|
-
)
|
|
148
|
-
for record in result
|
|
149
|
-
]
|
|
134
|
+
episodes = [get_episodic_node_from_record(record) for record in result]
|
|
150
135
|
return list(reversed(episodes)) # Return in chronological order
|
|
@@ -15,13 +15,10 @@ limitations under the License.
|
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
import logging
|
|
18
|
-
from contextlib import suppress
|
|
19
18
|
from time import time
|
|
20
19
|
from typing import Any
|
|
21
|
-
from uuid import uuid4
|
|
22
20
|
|
|
23
|
-
import
|
|
24
|
-
from pydantic import BaseModel, Field
|
|
21
|
+
from pydantic import BaseModel
|
|
25
22
|
|
|
26
23
|
from graphiti_core.graphiti_types import GraphitiClients
|
|
27
24
|
from graphiti_core.helpers import MAX_REFLEXION_ITERATIONS, semaphore_gather
|
|
@@ -29,8 +26,9 @@ from graphiti_core.llm_client import LLMClient
|
|
|
29
26
|
from graphiti_core.llm_client.config import ModelSize
|
|
30
27
|
from graphiti_core.nodes import EntityNode, EpisodeType, EpisodicNode, create_entity_node_embeddings
|
|
31
28
|
from graphiti_core.prompts import prompt_library
|
|
32
|
-
from graphiti_core.prompts.dedupe_nodes import NodeResolutions
|
|
29
|
+
from graphiti_core.prompts.dedupe_nodes import NodeDuplicate, NodeResolutions
|
|
33
30
|
from graphiti_core.prompts.extract_nodes import (
|
|
31
|
+
EntitySummary,
|
|
34
32
|
ExtractedEntities,
|
|
35
33
|
ExtractedEntity,
|
|
36
34
|
MissedEntities,
|
|
@@ -70,7 +68,7 @@ async def extract_nodes(
|
|
|
70
68
|
clients: GraphitiClients,
|
|
71
69
|
episode: EpisodicNode,
|
|
72
70
|
previous_episodes: list[EpisodicNode],
|
|
73
|
-
entity_types: dict[str, BaseModel] | None = None,
|
|
71
|
+
entity_types: dict[str, type[BaseModel]] | None = None,
|
|
74
72
|
excluded_entity_types: list[str] | None = None,
|
|
75
73
|
) -> list[EntityNode]:
|
|
76
74
|
start = time()
|
|
@@ -125,10 +123,9 @@ async def extract_nodes(
|
|
|
125
123
|
prompt_library.extract_nodes.extract_json(context), response_model=ExtractedEntities
|
|
126
124
|
)
|
|
127
125
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
]
|
|
126
|
+
response_object = ExtractedEntities(**llm_response)
|
|
127
|
+
|
|
128
|
+
extracted_entities: list[ExtractedEntity] = response_object.extracted_entities
|
|
132
129
|
|
|
133
130
|
reflexion_iterations += 1
|
|
134
131
|
if reflexion_iterations < MAX_REFLEXION_ITERATIONS:
|
|
@@ -181,7 +178,7 @@ async def resolve_extracted_nodes(
|
|
|
181
178
|
extracted_nodes: list[EntityNode],
|
|
182
179
|
episode: EpisodicNode | None = None,
|
|
183
180
|
previous_episodes: list[EpisodicNode] | None = None,
|
|
184
|
-
entity_types: dict[str, BaseModel] | None = None,
|
|
181
|
+
entity_types: dict[str, type[BaseModel]] | None = None,
|
|
185
182
|
existing_nodes_override: list[EntityNode] | None = None,
|
|
186
183
|
) -> tuple[list[EntityNode], dict[str, str], list[tuple[EntityNode, EntityNode]]]:
|
|
187
184
|
llm_client = clients.llm_client
|
|
@@ -224,7 +221,7 @@ async def resolve_extracted_nodes(
|
|
|
224
221
|
],
|
|
225
222
|
)
|
|
226
223
|
|
|
227
|
-
entity_types_dict: dict[str, BaseModel] = entity_types if entity_types is not None else {}
|
|
224
|
+
entity_types_dict: dict[str, type[BaseModel]] = entity_types if entity_types is not None else {}
|
|
228
225
|
|
|
229
226
|
# Prepare context for LLM
|
|
230
227
|
extracted_nodes_context = [
|
|
@@ -254,14 +251,14 @@ async def resolve_extracted_nodes(
|
|
|
254
251
|
response_model=NodeResolutions,
|
|
255
252
|
)
|
|
256
253
|
|
|
257
|
-
node_resolutions: list = llm_response.
|
|
254
|
+
node_resolutions: list[NodeDuplicate] = NodeResolutions(**llm_response).entity_resolutions
|
|
258
255
|
|
|
259
256
|
resolved_nodes: list[EntityNode] = []
|
|
260
257
|
uuid_map: dict[str, str] = {}
|
|
261
258
|
node_duplicates: list[tuple[EntityNode, EntityNode]] = []
|
|
262
259
|
for resolution in node_resolutions:
|
|
263
|
-
resolution_id: int = resolution.
|
|
264
|
-
duplicate_idx: int = resolution.
|
|
260
|
+
resolution_id: int = resolution.id
|
|
261
|
+
duplicate_idx: int = resolution.duplicate_idx
|
|
265
262
|
|
|
266
263
|
extracted_node = extracted_nodes[resolution_id]
|
|
267
264
|
|
|
@@ -276,7 +273,7 @@ async def resolve_extracted_nodes(
|
|
|
276
273
|
resolved_nodes.append(resolved_node)
|
|
277
274
|
uuid_map[extracted_node.uuid] = resolved_node.uuid
|
|
278
275
|
|
|
279
|
-
duplicates: list[int] = resolution.
|
|
276
|
+
duplicates: list[int] = resolution.duplicates
|
|
280
277
|
if duplicate_idx not in duplicates and duplicate_idx > -1:
|
|
281
278
|
duplicates.append(duplicate_idx)
|
|
282
279
|
for idx in duplicates:
|
|
@@ -298,7 +295,7 @@ async def extract_attributes_from_nodes(
|
|
|
298
295
|
nodes: list[EntityNode],
|
|
299
296
|
episode: EpisodicNode | None = None,
|
|
300
297
|
previous_episodes: list[EpisodicNode] | None = None,
|
|
301
|
-
entity_types: dict[str, BaseModel] | None = None,
|
|
298
|
+
entity_types: dict[str, type[BaseModel]] | None = None,
|
|
302
299
|
) -> list[EntityNode]:
|
|
303
300
|
llm_client = clients.llm_client
|
|
304
301
|
embedder = clients.embedder
|
|
@@ -327,7 +324,7 @@ async def extract_attributes_from_node(
|
|
|
327
324
|
node: EntityNode,
|
|
328
325
|
episode: EpisodicNode | None = None,
|
|
329
326
|
previous_episodes: list[EpisodicNode] | None = None,
|
|
330
|
-
entity_type: BaseModel | None = None,
|
|
327
|
+
entity_type: type[BaseModel] | None = None,
|
|
331
328
|
) -> EntityNode:
|
|
332
329
|
node_context: dict[str, Any] = {
|
|
333
330
|
'name': node.name,
|
|
@@ -336,25 +333,14 @@ async def extract_attributes_from_node(
|
|
|
336
333
|
'attributes': node.attributes,
|
|
337
334
|
}
|
|
338
335
|
|
|
339
|
-
|
|
340
|
-
'
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
)
|
|
336
|
+
attributes_context: dict[str, Any] = {
|
|
337
|
+
'node': node_context,
|
|
338
|
+
'episode_content': episode.content if episode is not None else '',
|
|
339
|
+
'previous_episodes': [ep.content for ep in previous_episodes]
|
|
340
|
+
if previous_episodes is not None
|
|
341
|
+
else [],
|
|
346
342
|
}
|
|
347
343
|
|
|
348
|
-
if entity_type is not None:
|
|
349
|
-
for field_name, field_info in entity_type.model_fields.items():
|
|
350
|
-
attributes_definitions[field_name] = (
|
|
351
|
-
field_info.annotation,
|
|
352
|
-
Field(description=field_info.description),
|
|
353
|
-
)
|
|
354
|
-
|
|
355
|
-
unique_model_name = f'EntityAttributes_{uuid4().hex}'
|
|
356
|
-
entity_attributes_model = pydantic.create_model(unique_model_name, **attributes_definitions)
|
|
357
|
-
|
|
358
344
|
summary_context: dict[str, Any] = {
|
|
359
345
|
'node': node_context,
|
|
360
346
|
'episode_content': episode.content if episode is not None else '',
|
|
@@ -363,63 +349,30 @@ async def extract_attributes_from_node(
|
|
|
363
349
|
else [],
|
|
364
350
|
}
|
|
365
351
|
|
|
366
|
-
llm_response =
|
|
367
|
-
|
|
368
|
-
|
|
352
|
+
llm_response = (
|
|
353
|
+
(
|
|
354
|
+
await llm_client.generate_response(
|
|
355
|
+
prompt_library.extract_nodes.extract_attributes(attributes_context),
|
|
356
|
+
response_model=entity_type,
|
|
357
|
+
model_size=ModelSize.small,
|
|
358
|
+
)
|
|
359
|
+
)
|
|
360
|
+
if entity_type is not None
|
|
361
|
+
else {}
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
summary_response = await llm_client.generate_response(
|
|
365
|
+
prompt_library.extract_nodes.extract_summary(summary_context),
|
|
366
|
+
response_model=EntitySummary,
|
|
369
367
|
model_size=ModelSize.small,
|
|
370
368
|
)
|
|
371
369
|
|
|
372
|
-
|
|
373
|
-
|
|
370
|
+
if entity_type is not None:
|
|
371
|
+
entity_type(**llm_response)
|
|
374
372
|
|
|
375
|
-
|
|
376
|
-
|
|
373
|
+
node.summary = summary_response.get('summary', '')
|
|
374
|
+
node_attributes = {key: value for key, value in llm_response.items()}
|
|
377
375
|
|
|
378
376
|
node.attributes.update(node_attributes)
|
|
379
377
|
|
|
380
378
|
return node
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
async def dedupe_node_list(
|
|
384
|
-
llm_client: LLMClient,
|
|
385
|
-
nodes: list[EntityNode],
|
|
386
|
-
) -> tuple[list[EntityNode], dict[str, str]]:
|
|
387
|
-
start = time()
|
|
388
|
-
|
|
389
|
-
# build node map
|
|
390
|
-
node_map = {}
|
|
391
|
-
for node in nodes:
|
|
392
|
-
node_map[node.uuid] = node
|
|
393
|
-
|
|
394
|
-
# Prepare context for LLM
|
|
395
|
-
nodes_context = [{'uuid': node.uuid, 'name': node.name, **node.attributes} for node in nodes]
|
|
396
|
-
|
|
397
|
-
context = {
|
|
398
|
-
'nodes': nodes_context,
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
llm_response = await llm_client.generate_response(
|
|
402
|
-
prompt_library.dedupe_nodes.node_list(context)
|
|
403
|
-
)
|
|
404
|
-
|
|
405
|
-
nodes_data = llm_response.get('nodes', [])
|
|
406
|
-
|
|
407
|
-
end = time()
|
|
408
|
-
logger.debug(f'Deduplicated nodes: {nodes_data} in {(end - start) * 1000} ms')
|
|
409
|
-
|
|
410
|
-
# Get full node data
|
|
411
|
-
unique_nodes = []
|
|
412
|
-
uuid_map: dict[str, str] = {}
|
|
413
|
-
for node_data in nodes_data:
|
|
414
|
-
node_instance: EntityNode | None = node_map.get(node_data['uuids'][0])
|
|
415
|
-
if node_instance is None:
|
|
416
|
-
logger.warning(f'Node {node_data["uuids"][0]} not found in node map')
|
|
417
|
-
continue
|
|
418
|
-
node_instance.summary = node_data['summary']
|
|
419
|
-
unique_nodes.append(node_instance)
|
|
420
|
-
|
|
421
|
-
for uuid in node_data['uuids'][1:]:
|
|
422
|
-
uuid_value = node_map[node_data['uuids'][0]].uuid
|
|
423
|
-
uuid_map[uuid] = uuid_value
|
|
424
|
-
|
|
425
|
-
return unique_nodes, uuid_map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: graphiti-core
|
|
3
|
-
Version: 0.18.
|
|
3
|
+
Version: 0.18.2
|
|
4
4
|
Summary: A temporal graph building library
|
|
5
5
|
Project-URL: Homepage, https://help.getzep.com/graphiti/graphiti/overview
|
|
6
6
|
Project-URL: Repository, https://github.com/getzep/graphiti
|
|
@@ -106,9 +106,9 @@ nodes ("Kendra", "Adidas shoes"), and their relationship, or edge ("loves"). Kno
|
|
|
106
106
|
extensively for information retrieval. What makes Graphiti unique is its ability to autonomously build a knowledge graph
|
|
107
107
|
while handling changing relationships and maintaining historical context.
|
|
108
108
|
|
|
109
|
-
## Graphiti and Zep
|
|
109
|
+
## Graphiti and Zep's Context Engineering Platform.
|
|
110
110
|
|
|
111
|
-
Graphiti powers the core of [Zep
|
|
111
|
+
Graphiti powers the core of [Zep](https://www.getzep.com), a turn-key context engineering platform for AI Agents. Zep offers agent memory, Graph RAG for dynamic data, and context retrieval and assembly.
|
|
112
112
|
|
|
113
113
|
Using Graphiti, we've demonstrated Zep is
|
|
114
114
|
the [State of the Art in Agent Memory](https://blog.getzep.com/state-of-the-art-agent-memory/).
|
|
@@ -219,6 +219,14 @@ pip install graphiti-core[anthropic,groq,google-genai]
|
|
|
219
219
|
pip install graphiti-core[falkordb,anthropic,google-genai]
|
|
220
220
|
```
|
|
221
221
|
|
|
222
|
+
## Default to Low Concurrency; LLM Provider 429 Rate Limit Errors
|
|
223
|
+
|
|
224
|
+
Graphiti's ingestion pipelines are designed for high concurrency. By default, concurrency is set low to avoid LLM Provider 429 Rate Limit Errors. If you find Graphiti slow, please increase concurrency as described below.
|
|
225
|
+
|
|
226
|
+
Concurrency controlled by the `SEMAPHORE_LIMIT` environment variable. By default, `SEMAPHORE_LIMIT` is set to `10` concurrent operations to help prevent `429` rate limit errors from your LLM provider. If you encounter such errors, try lowering this value.
|
|
227
|
+
|
|
228
|
+
If your LLM provider allows higher throughput, you can increase `SEMAPHORE_LIMIT` to boost episode ingestion performance.
|
|
229
|
+
|
|
222
230
|
## Quick Start
|
|
223
231
|
|
|
224
232
|
> [!IMPORTANT]
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
graphiti_core/__init__.py,sha256=e5SWFkRiaUwfprYIeIgVIh7JDedNiloZvd3roU-0aDY,55
|
|
2
|
-
graphiti_core/edges.py,sha256
|
|
2
|
+
graphiti_core/edges.py,sha256=KpgCzHlNgEcTTbUE62RAXKKi2YbG_fIwilE_W1TARYI,14864
|
|
3
3
|
graphiti_core/errors.py,sha256=cH_v9TPgEPeQE6GFOHIg5TvejpUCBddGarMY2Whxbwc,2707
|
|
4
|
-
graphiti_core/graph_queries.py,sha256=
|
|
5
|
-
graphiti_core/graphiti.py,sha256=
|
|
4
|
+
graphiti_core/graph_queries.py,sha256=mkSE8JbH4dTysT1quxR_QqCU-P_MUogbj48-BVxAcY4,5316
|
|
5
|
+
graphiti_core/graphiti.py,sha256=u9sDQ4iXxDjbI_G9FSs-xXi2xuNMZo-FvNW_sEBptYo,40129
|
|
6
6
|
graphiti_core/graphiti_types.py,sha256=rL-9bvnLobunJfXU4hkD6mAj14pofKp_wq8QsFDZwDU,1035
|
|
7
|
-
graphiti_core/helpers.py,sha256=
|
|
8
|
-
graphiti_core/nodes.py,sha256
|
|
7
|
+
graphiti_core/helpers.py,sha256=oKcOQE_bvsdhBpPr1Ia2tylBq1svj3X1oBMSR7qdo00,5331
|
|
8
|
+
graphiti_core/nodes.py,sha256=-RnBtqLx1ukbQQqjZval9fDAWIJUTWaKwSMPLJ9AzM4,17866
|
|
9
9
|
graphiti_core/py.typed,sha256=vlmmzQOt7bmeQl9L3XJP4W6Ry0iiELepnOrinKz5KQg,79
|
|
10
10
|
graphiti_core/cross_encoder/__init__.py,sha256=hry59vz21x-AtGZ0MJ7ugw0HTwJkXiddpp_Yqnwsen0,723
|
|
11
11
|
graphiti_core/cross_encoder/bge_reranker_client.py,sha256=y3TfFxZh0Yvj6HUShmfUm6MC7OPXwWUlv1Qe5HF3S3I,1797
|
|
@@ -13,9 +13,9 @@ graphiti_core/cross_encoder/client.py,sha256=KLsbfWKOEaAV3adFe3XZlAeb-gje9_sVKCV
|
|
|
13
13
|
graphiti_core/cross_encoder/gemini_reranker_client.py,sha256=hmITG5YIib52nrKvINwRi4xTfAO1U4jCCaEVIwImHw0,6208
|
|
14
14
|
graphiti_core/cross_encoder/openai_reranker_client.py,sha256=hoaGyu9nCNMJyP8si0Bha5Q9CFszfiHQmLgE9IsX7sY,4653
|
|
15
15
|
graphiti_core/driver/__init__.py,sha256=kCWimqQU19airu5gKwCmZtZuXkDfaQfKSUhMDoL-rTA,626
|
|
16
|
-
graphiti_core/driver/driver.py,sha256=
|
|
17
|
-
graphiti_core/driver/falkordb_driver.py,sha256=
|
|
18
|
-
graphiti_core/driver/neo4j_driver.py,sha256=
|
|
16
|
+
graphiti_core/driver/driver.py,sha256=XFJzkyZKiqhwwW6G9enB2ouWxNB53PU58nhSCW_LOIc,2331
|
|
17
|
+
graphiti_core/driver/falkordb_driver.py,sha256=YLNuPvPBM7Pgr3Pv9gDuTVDGeDgHvEg8xD58uDYNweM,6766
|
|
18
|
+
graphiti_core/driver/neo4j_driver.py,sha256=LxYPJc8vUUBplVKLW9n2mofNzndFV4S2yHdAiT5gUJI,2323
|
|
19
19
|
graphiti_core/embedder/__init__.py,sha256=EL564ZuE-DZjcuKNUK_exMn_XHXm2LdO9fzdXePVKL4,179
|
|
20
20
|
graphiti_core/embedder/azure_openai.py,sha256=OyomPwC1fIsddI-3n6g00kQFdQznZorBhHwkQKCLUok,2384
|
|
21
21
|
graphiti_core/embedder/client.py,sha256=qEpSHceL_Gc4QQPJWIOnuNLemNuR_TYA4r28t2Vldbg,1115
|
|
@@ -36,16 +36,16 @@ graphiti_core/llm_client/openai_generic_client.py,sha256=WElMnPqdb1CxzYH4p2-m_9r
|
|
|
36
36
|
graphiti_core/llm_client/utils.py,sha256=zKpxXEbKa369m4W7RDEf-m56kH46V1Mx3RowcWZEWWs,1000
|
|
37
37
|
graphiti_core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
38
|
graphiti_core/models/edges/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
39
|
-
graphiti_core/models/edges/edge_db_queries.py,sha256=
|
|
39
|
+
graphiti_core/models/edges/edge_db_queries.py,sha256=OBFqT9m6JDmGwpqe5TkcaCT2KxUGdW8h4yIsoacB5Mw,4661
|
|
40
40
|
graphiti_core/models/nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
|
-
graphiti_core/models/nodes/node_db_queries.py,sha256=
|
|
41
|
+
graphiti_core/models/nodes/node_db_queries.py,sha256=ADPkZzS4tq5i-8zvkocoiuOiDi64d5F2ChEY_JwLOwQ,4378
|
|
42
42
|
graphiti_core/prompts/__init__.py,sha256=EA-x9xUki9l8wnu2l8ek_oNf75-do5tq5hVq7Zbv8Kw,101
|
|
43
43
|
graphiti_core/prompts/dedupe_edges.py,sha256=WlW18HjhuKOXjxSbvrmwk0yH0tqA4t89ntG7LOTq_k4,5724
|
|
44
|
-
graphiti_core/prompts/dedupe_nodes.py,sha256=
|
|
44
|
+
graphiti_core/prompts/dedupe_nodes.py,sha256=T0LVocO5nfP4mOYpIcNMztLHjnRcE6zpYgT6OUCoujw,7783
|
|
45
45
|
graphiti_core/prompts/eval.py,sha256=gnBQTmwsCl3Qvwpcm7aieVszzo6y1sMCUT8jQiKTvvE,5317
|
|
46
46
|
graphiti_core/prompts/extract_edge_dates.py,sha256=3Drs3CmvP0gJN5BidWSxrNvLet3HPoTybU3BUIAoc0Y,4218
|
|
47
|
-
graphiti_core/prompts/extract_edges.py,sha256=
|
|
48
|
-
graphiti_core/prompts/extract_nodes.py,sha256=
|
|
47
|
+
graphiti_core/prompts/extract_edges.py,sha256=MY-GhBNxPhO07v6TZPE_dby-VevDHHmVXpyYm-khg04,6622
|
|
48
|
+
graphiti_core/prompts/extract_nodes.py,sha256=v7DMOmscAY2j2eiZr5cNmz_kT3UmjvKpT-OOwmhzUik,11020
|
|
49
49
|
graphiti_core/prompts/invalidate_edges.py,sha256=yfpcs_pyctnoM77ULPZXEtKW0oHr1MeLsJzC5yrE-o4,3547
|
|
50
50
|
graphiti_core/prompts/lib.py,sha256=DCyHePM4_q-CptTpEXGO_dBv9k7xDtclEaB1dGu7EcI,4092
|
|
51
51
|
graphiti_core/prompts/models.py,sha256=NgxdbPHJpBEcpbXovKyScgpBc73Q-GIW-CBDlBtDjto,894
|
|
@@ -55,23 +55,23 @@ graphiti_core/search/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hS
|
|
|
55
55
|
graphiti_core/search/search.py,sha256=u-kTmSu3VlRHYlQhuYsbwDQ-AKKCp3BZ9JZNRv3ttVY,16720
|
|
56
56
|
graphiti_core/search/search_config.py,sha256=v_rUHsu1yo5OuPfEm21lSuXexQs-o8qYwSSemW2QWhU,4165
|
|
57
57
|
graphiti_core/search/search_config_recipes.py,sha256=4GquRphHhJlpXQhAZOySYnCzBWYoTwxlJj44eTOavZQ,7443
|
|
58
|
-
graphiti_core/search/search_filters.py,sha256=
|
|
58
|
+
graphiti_core/search/search_filters.py,sha256=Xc8hNVqbPrCd8-Zql0rtf1ztjzC_0FgAqGj9vxgNwLM,6383
|
|
59
59
|
graphiti_core/search/search_helpers.py,sha256=G5Ceaq5Pfgx0Weelqgeylp_pUHwiBnINaUYsDbURJbE,2636
|
|
60
|
-
graphiti_core/search/search_utils.py,sha256=
|
|
60
|
+
graphiti_core/search/search_utils.py,sha256=D6J93x0ol4Kd_zVo-dX0MRG0BZjLvG-mCdi-vjjDTfE,32104
|
|
61
61
|
graphiti_core/telemetry/__init__.py,sha256=5kALLDlU9bb2v19CdN7qVANsJWyfnL9E60J6FFgzm3o,226
|
|
62
62
|
graphiti_core/telemetry/telemetry.py,sha256=47LrzOVBCcZxsYPsnSxWFiztHoxYKKxPwyRX0hnbDGc,3230
|
|
63
63
|
graphiti_core/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
64
|
-
graphiti_core/utils/bulk_utils.py,sha256=
|
|
64
|
+
graphiti_core/utils/bulk_utils.py,sha256=noYG8MQZK-8_YEVOGT0ci7VUB31Mrx29OHdaGOZXQMA,15049
|
|
65
65
|
graphiti_core/utils/datetime_utils.py,sha256=Ti-2tnrDFRzBsbfblzsHybsM3jaDLP4-VT2t0VhpIzU,1357
|
|
66
66
|
graphiti_core/utils/maintenance/__init__.py,sha256=vW4H1KyapTl-OOz578uZABYcpND4wPx3Vt6aAPaXh78,301
|
|
67
|
-
graphiti_core/utils/maintenance/community_operations.py,sha256=
|
|
68
|
-
graphiti_core/utils/maintenance/edge_operations.py,sha256=
|
|
69
|
-
graphiti_core/utils/maintenance/graph_data_operations.py,sha256=
|
|
70
|
-
graphiti_core/utils/maintenance/node_operations.py,sha256=
|
|
67
|
+
graphiti_core/utils/maintenance/community_operations.py,sha256=jAY_WdmvjxJG4MxAsXzTaETTjvX6G8YuKVq9FzvQQuc,10071
|
|
68
|
+
graphiti_core/utils/maintenance/edge_operations.py,sha256=YRyjWXy9MT3YlmVB9OHUN8R2idYyf-jteq-5tLy4LOs,18574
|
|
69
|
+
graphiti_core/utils/maintenance/graph_data_operations.py,sha256=joNZxL_OIFdXHBGkwtB57ar7nGmbq4Z8ItgqFWL45kw,4530
|
|
70
|
+
graphiti_core/utils/maintenance/node_operations.py,sha256=mNqonVTNLtw0gK7tflhoCFQ9dNrDfJq94cv3_aEwSiI,13129
|
|
71
71
|
graphiti_core/utils/maintenance/temporal_operations.py,sha256=mJkw9xLB4W2BsLfC5POr0r-PHWL9SIfNj_l_xu0B5ug,3410
|
|
72
72
|
graphiti_core/utils/maintenance/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
73
|
-
graphiti_core/utils/ontology_utils/entity_types_utils.py,sha256=
|
|
74
|
-
graphiti_core-0.18.
|
|
75
|
-
graphiti_core-0.18.
|
|
76
|
-
graphiti_core-0.18.
|
|
77
|
-
graphiti_core-0.18.
|
|
73
|
+
graphiti_core/utils/ontology_utils/entity_types_utils.py,sha256=4eVgxLWY6Q8k9cRJ5pW59IYF--U4nXZsZIGOVb_yHfQ,1285
|
|
74
|
+
graphiti_core-0.18.2.dist-info/METADATA,sha256=TLIGRsoZ0nYbqBSvd_qWUkd2sAXBLTyJ4hV4j-S97cY,24621
|
|
75
|
+
graphiti_core-0.18.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
76
|
+
graphiti_core-0.18.2.dist-info/licenses/LICENSE,sha256=KCUwCyDXuVEgmDWkozHyniRyWjnWUWjkuDHfU6o3JlA,11325
|
|
77
|
+
graphiti_core-0.18.2.dist-info/RECORD,,
|
|
File without changes
|