graphiti-core 0.11.4__tar.gz → 0.11.6rc1__tar.gz
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-0.11.4 → graphiti_core-0.11.6rc1}/PKG-INFO +1 -2
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/edges.py +18 -4
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/graphiti.py +6 -1
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/llm_client/anthropic_client.py +0 -13
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/nodes.py +30 -7
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/prompts/dedupe_nodes.py +11 -9
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/prompts/extract_nodes.py +1 -1
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/prompts/summarize_nodes.py +4 -4
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/search/search.py +13 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/search/search_utils.py +4 -11
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/utils/maintenance/community_operations.py +0 -2
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/utils/maintenance/edge_operations.py +14 -9
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/utils/maintenance/node_operations.py +18 -13
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/pyproject.toml +1 -2
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/LICENSE +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/README.md +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/__init__.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/cross_encoder/__init__.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/cross_encoder/bge_reranker_client.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/cross_encoder/client.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/cross_encoder/openai_reranker_client.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/embedder/__init__.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/embedder/client.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/embedder/gemini.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/embedder/openai.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/embedder/voyage.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/errors.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/graphiti_types.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/helpers.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/llm_client/__init__.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/llm_client/client.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/llm_client/config.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/llm_client/errors.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/llm_client/gemini_client.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/llm_client/groq_client.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/llm_client/openai_client.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/llm_client/openai_generic_client.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/llm_client/utils.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/models/__init__.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/models/edges/__init__.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/models/edges/edge_db_queries.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/models/nodes/__init__.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/models/nodes/node_db_queries.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/prompts/__init__.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/prompts/dedupe_edges.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/prompts/eval.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/prompts/extract_edge_dates.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/prompts/extract_edges.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/prompts/invalidate_edges.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/prompts/lib.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/prompts/models.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/prompts/prompt_helpers.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/py.typed +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/search/__init__.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/search/search_config.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/search/search_config_recipes.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/search/search_filters.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/search/search_helpers.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/utils/__init__.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/utils/bulk_utils.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/utils/datetime_utils.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/utils/maintenance/__init__.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/utils/maintenance/graph_data_operations.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/utils/maintenance/temporal_operations.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/utils/maintenance/utils.py +0 -0
- {graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/utils/ontology_utils/entity_types_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: graphiti-core
|
|
3
|
-
Version: 0.11.
|
|
3
|
+
Version: 0.11.6rc1
|
|
4
4
|
Summary: A temporal graph building library
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Author: Paul Paliychuk
|
|
@@ -18,7 +18,6 @@ Provides-Extra: groq
|
|
|
18
18
|
Requires-Dist: anthropic (>=0.49.0) ; extra == "anthropic"
|
|
19
19
|
Requires-Dist: diskcache (>=5.6.3)
|
|
20
20
|
Requires-Dist: google-genai (>=1.8.0) ; extra == "google-genai"
|
|
21
|
-
Requires-Dist: graph-service (>=1.0.0.7,<2.0.0.0)
|
|
22
21
|
Requires-Dist: groq (>=0.2.0) ; extra == "groq"
|
|
23
22
|
Requires-Dist: neo4j (>=5.23.0)
|
|
24
23
|
Requires-Dist: numpy (>=1.0.0)
|
|
@@ -46,7 +46,6 @@ ENTITY_EDGE_RETURN: LiteralString = """
|
|
|
46
46
|
e.name AS name,
|
|
47
47
|
e.group_id AS group_id,
|
|
48
48
|
e.fact AS fact,
|
|
49
|
-
e.fact_embedding AS fact_embedding,
|
|
50
49
|
e.episodes AS episodes,
|
|
51
50
|
e.expired_at AS expired_at,
|
|
52
51
|
e.valid_at AS valid_at,
|
|
@@ -222,6 +221,20 @@ class EntityEdge(Edge):
|
|
|
222
221
|
|
|
223
222
|
return self.fact_embedding
|
|
224
223
|
|
|
224
|
+
async def load_fact_embedding(self, driver: AsyncDriver):
|
|
225
|
+
query: LiteralString = """
|
|
226
|
+
MATCH (n:Entity)-[e:RELATES_TO {uuid: $uuid}]->(m:Entity)
|
|
227
|
+
RETURN e.fact_embedding AS fact_embedding
|
|
228
|
+
"""
|
|
229
|
+
records, _, _ = await driver.execute_query(
|
|
230
|
+
query, uuid=self.uuid, database_=DEFAULT_DATABASE, routing_='r'
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
if len(records) == 0:
|
|
234
|
+
raise EdgeNotFoundError(self.uuid)
|
|
235
|
+
|
|
236
|
+
self.fact_embedding = records[0]['fact_embedding']
|
|
237
|
+
|
|
225
238
|
async def save(self, driver: AsyncDriver):
|
|
226
239
|
result = await driver.execute_query(
|
|
227
240
|
ENTITY_EDGE_SAVE,
|
|
@@ -321,8 +334,8 @@ class EntityEdge(Edge):
|
|
|
321
334
|
async def get_by_node_uuid(cls, driver: AsyncDriver, node_uuid: str):
|
|
322
335
|
query: LiteralString = (
|
|
323
336
|
"""
|
|
324
|
-
|
|
325
|
-
|
|
337
|
+
MATCH (n:Entity {uuid: $node_uuid})-[e:RELATES_TO]-(m:Entity)
|
|
338
|
+
"""
|
|
326
339
|
+ ENTITY_EDGE_RETURN
|
|
327
340
|
)
|
|
328
341
|
records, _, _ = await driver.execute_query(
|
|
@@ -452,7 +465,6 @@ def get_entity_edge_from_record(record: Any) -> EntityEdge:
|
|
|
452
465
|
name=record['name'],
|
|
453
466
|
group_id=record['group_id'],
|
|
454
467
|
episodes=record['episodes'],
|
|
455
|
-
fact_embedding=record['fact_embedding'],
|
|
456
468
|
created_at=record['created_at'].to_native(),
|
|
457
469
|
expired_at=parse_db_date(record['expired_at']),
|
|
458
470
|
valid_at=parse_db_date(record['valid_at']),
|
|
@@ -471,6 +483,8 @@ def get_community_edge_from_record(record: Any):
|
|
|
471
483
|
|
|
472
484
|
|
|
473
485
|
async def create_entity_edge_embeddings(embedder: EmbedderClient, edges: list[EntityEdge]):
|
|
486
|
+
if len(edges) == 0:
|
|
487
|
+
return
|
|
474
488
|
fact_embeddings = await embedder.create_batch([edge.fact for edge in edges])
|
|
475
489
|
for edge, fact_embedding in zip(edges, fact_embeddings, strict=True):
|
|
476
490
|
edge.fact_embedding = fact_embedding
|
|
@@ -380,6 +380,7 @@ class Graphiti:
|
|
|
380
380
|
resolve_extracted_edges(
|
|
381
381
|
self.clients,
|
|
382
382
|
edges,
|
|
383
|
+
episode,
|
|
383
384
|
),
|
|
384
385
|
extract_attributes_from_nodes(
|
|
385
386
|
self.clients, nodes, episode, previous_episodes, entity_types
|
|
@@ -682,7 +683,11 @@ class Graphiti:
|
|
|
682
683
|
|
|
683
684
|
related_edges = await get_relevant_edges(self.driver, [updated_edge], SearchFilters(), 0.8)
|
|
684
685
|
|
|
685
|
-
resolved_edge = await dedupe_extracted_edge(
|
|
686
|
+
resolved_edge = await dedupe_extracted_edge(
|
|
687
|
+
self.llm_client,
|
|
688
|
+
updated_edge,
|
|
689
|
+
related_edges[0],
|
|
690
|
+
)
|
|
686
691
|
|
|
687
692
|
contradicting_edges = await get_edge_contradictions(self.llm_client, edge, related_edges[0])
|
|
688
693
|
invalidated_edges = resolve_edge_contradictions(resolved_edge, contradicting_edges)
|
{graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/llm_client/anthropic_client.py
RENAMED
|
@@ -139,15 +139,11 @@ class AnthropicClient(LLMClient):
|
|
|
139
139
|
A list containing a single tool definition for use with the Anthropic API.
|
|
140
140
|
"""
|
|
141
141
|
if response_model is not None:
|
|
142
|
-
# temporary debug log
|
|
143
|
-
logger.info(f'Creating tool for response_model: {response_model}')
|
|
144
142
|
# Use the response_model to define the tool
|
|
145
143
|
model_schema = response_model.model_json_schema()
|
|
146
144
|
tool_name = response_model.__name__
|
|
147
145
|
description = model_schema.get('description', f'Extract {tool_name} information')
|
|
148
146
|
else:
|
|
149
|
-
# temporary debug log
|
|
150
|
-
logger.info('Creating generic JSON output tool')
|
|
151
147
|
# Create a generic JSON output tool
|
|
152
148
|
tool_name = 'generic_json_output'
|
|
153
149
|
description = 'Output data in JSON format'
|
|
@@ -205,8 +201,6 @@ class AnthropicClient(LLMClient):
|
|
|
205
201
|
try:
|
|
206
202
|
# Create the appropriate tool based on whether response_model is provided
|
|
207
203
|
tools, tool_choice = self._create_tool(response_model)
|
|
208
|
-
# temporary debug log
|
|
209
|
-
logger.info(f'using model: {self.model} with max_tokens: {self.max_tokens}')
|
|
210
204
|
result = await self.client.messages.create(
|
|
211
205
|
system=system_message.content,
|
|
212
206
|
max_tokens=max_creation_tokens,
|
|
@@ -227,13 +221,6 @@ class AnthropicClient(LLMClient):
|
|
|
227
221
|
return tool_args
|
|
228
222
|
|
|
229
223
|
# If we didn't get a proper tool_use response, try to extract from text
|
|
230
|
-
# logger.debug(
|
|
231
|
-
# f'Did not get a tool_use response, trying to extract json from text. Result: {result.content}'
|
|
232
|
-
# )
|
|
233
|
-
# temporary debug log
|
|
234
|
-
logger.info(
|
|
235
|
-
f'Did not get a tool_use response, trying to extract json from text. Result: {result.content}'
|
|
236
|
-
)
|
|
237
224
|
for content_item in result.content:
|
|
238
225
|
if content_item.type == 'text':
|
|
239
226
|
return self._extract_json_from_text(content_item.text)
|
|
@@ -42,7 +42,6 @@ ENTITY_NODE_RETURN: LiteralString = """
|
|
|
42
42
|
RETURN
|
|
43
43
|
n.uuid As uuid,
|
|
44
44
|
n.name AS name,
|
|
45
|
-
n.name_embedding AS name_embedding,
|
|
46
45
|
n.group_id AS group_id,
|
|
47
46
|
n.created_at AS created_at,
|
|
48
47
|
n.summary AS summary,
|
|
@@ -305,6 +304,20 @@ class EntityNode(Node):
|
|
|
305
304
|
|
|
306
305
|
return self.name_embedding
|
|
307
306
|
|
|
307
|
+
async def load_name_embedding(self, driver: AsyncDriver):
|
|
308
|
+
query: LiteralString = """
|
|
309
|
+
MATCH (n:Entity {uuid: $uuid})
|
|
310
|
+
RETURN n.name_embedding AS name_embedding
|
|
311
|
+
"""
|
|
312
|
+
records, _, _ = await driver.execute_query(
|
|
313
|
+
query, uuid=self.uuid, database_=DEFAULT_DATABASE, routing_='r'
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
if len(records) == 0:
|
|
317
|
+
raise NodeNotFoundError(self.uuid)
|
|
318
|
+
|
|
319
|
+
self.name_embedding = records[0]['name_embedding']
|
|
320
|
+
|
|
308
321
|
async def save(self, driver: AsyncDriver):
|
|
309
322
|
entity_data: dict[str, Any] = {
|
|
310
323
|
'uuid': self.uuid,
|
|
@@ -332,8 +345,8 @@ class EntityNode(Node):
|
|
|
332
345
|
async def get_by_uuid(cls, driver: AsyncDriver, uuid: str):
|
|
333
346
|
query = (
|
|
334
347
|
"""
|
|
335
|
-
|
|
336
|
-
|
|
348
|
+
MATCH (n:Entity {uuid: $uuid})
|
|
349
|
+
"""
|
|
337
350
|
+ ENTITY_NODE_RETURN
|
|
338
351
|
)
|
|
339
352
|
records, _, _ = await driver.execute_query(
|
|
@@ -428,6 +441,20 @@ class CommunityNode(Node):
|
|
|
428
441
|
|
|
429
442
|
return self.name_embedding
|
|
430
443
|
|
|
444
|
+
async def load_name_embedding(self, driver: AsyncDriver):
|
|
445
|
+
query: LiteralString = """
|
|
446
|
+
MATCH (c:Community {uuid: $uuid})
|
|
447
|
+
RETURN c.name_embedding AS name_embedding
|
|
448
|
+
"""
|
|
449
|
+
records, _, _ = await driver.execute_query(
|
|
450
|
+
query, uuid=self.uuid, database_=DEFAULT_DATABASE, routing_='r'
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
if len(records) == 0:
|
|
454
|
+
raise NodeNotFoundError(self.uuid)
|
|
455
|
+
|
|
456
|
+
self.name_embedding = records[0]['name_embedding']
|
|
457
|
+
|
|
431
458
|
@classmethod
|
|
432
459
|
async def get_by_uuid(cls, driver: AsyncDriver, uuid: str):
|
|
433
460
|
records, _, _ = await driver.execute_query(
|
|
@@ -436,7 +463,6 @@ class CommunityNode(Node):
|
|
|
436
463
|
RETURN
|
|
437
464
|
n.uuid As uuid,
|
|
438
465
|
n.name AS name,
|
|
439
|
-
n.name_embedding AS name_embedding,
|
|
440
466
|
n.group_id AS group_id,
|
|
441
467
|
n.created_at AS created_at,
|
|
442
468
|
n.summary AS summary
|
|
@@ -461,7 +487,6 @@ class CommunityNode(Node):
|
|
|
461
487
|
RETURN
|
|
462
488
|
n.uuid As uuid,
|
|
463
489
|
n.name AS name,
|
|
464
|
-
n.name_embedding AS name_embedding,
|
|
465
490
|
n.group_id AS group_id,
|
|
466
491
|
n.created_at AS created_at,
|
|
467
492
|
n.summary AS summary
|
|
@@ -495,7 +520,6 @@ class CommunityNode(Node):
|
|
|
495
520
|
RETURN
|
|
496
521
|
n.uuid As uuid,
|
|
497
522
|
n.name AS name,
|
|
498
|
-
n.name_embedding AS name_embedding,
|
|
499
523
|
n.group_id AS group_id,
|
|
500
524
|
n.created_at AS created_at,
|
|
501
525
|
n.summary AS summary
|
|
@@ -534,7 +558,6 @@ def get_entity_node_from_record(record: Any) -> EntityNode:
|
|
|
534
558
|
uuid=record['uuid'],
|
|
535
559
|
name=record['name'],
|
|
536
560
|
group_id=record['group_id'],
|
|
537
|
-
name_embedding=record['name_embedding'],
|
|
538
561
|
labels=record['labels'],
|
|
539
562
|
created_at=record['created_at'].to_native(),
|
|
540
563
|
summary=record['summary'],
|
|
@@ -44,7 +44,7 @@ def node(context: dict[str, Any]) -> list[Message]:
|
|
|
44
44
|
return [
|
|
45
45
|
Message(
|
|
46
46
|
role='system',
|
|
47
|
-
content='You are a helpful assistant that
|
|
47
|
+
content='You are a helpful assistant that determines whether or not a NEW ENTITY is a duplicate of any EXISTING ENTITIES.',
|
|
48
48
|
),
|
|
49
49
|
Message(
|
|
50
50
|
role='user',
|
|
@@ -69,19 +69,21 @@ def node(context: dict[str, Any]) -> list[Message]:
|
|
|
69
69
|
Given the above EXISTING ENTITIES and their attributes, MESSAGE, and PREVIOUS MESSAGES; Determine if the NEW ENTITY extracted from the conversation
|
|
70
70
|
is a duplicate entity of one of the EXISTING ENTITIES.
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
Entities should only be considered duplicates if they refer to the *same real-world object or concept*.
|
|
73
|
+
|
|
74
|
+
Do NOT mark entities as duplicates if:
|
|
75
|
+
- They are related but distinct.
|
|
76
|
+
- They have similar names or purposes but refer to separate instances or concepts.
|
|
73
77
|
|
|
74
78
|
Task:
|
|
75
79
|
If the NEW ENTITY represents a duplicate entity of any entity in EXISTING ENTITIES, set duplicate_entity_id to the
|
|
76
|
-
id of the EXISTING ENTITY that is the duplicate.
|
|
80
|
+
id of the EXISTING ENTITY that is the duplicate.
|
|
81
|
+
|
|
82
|
+
If the NEW ENTITY is not a duplicate of any of the EXISTING ENTITIES,
|
|
77
83
|
duplicate_entity_id should be set to -1.
|
|
78
84
|
|
|
79
|
-
Also return the
|
|
80
|
-
|
|
81
|
-
Guidelines:
|
|
82
|
-
1. Entities with the same name should be considered duplicates
|
|
83
|
-
2. Duplicate entities may refer to the same real-world entity even if names differ. Use context clues from the MESSAGES
|
|
84
|
-
to determine if the NEW ENTITY represents a duplicate entity of one of the EXISTING ENTITIES.
|
|
85
|
+
Also return the name that best describes the NEW ENTITY (whether it is the name of the NEW ENTITY, a node it
|
|
86
|
+
is a duplicate of, or a combination of the two).
|
|
85
87
|
""",
|
|
86
88
|
),
|
|
87
89
|
]
|
|
@@ -256,7 +256,7 @@ def extract_attributes(context: dict[str, Any]) -> list[Message]:
|
|
|
256
256
|
1. Do not hallucinate entity property values if they cannot be found in the current context.
|
|
257
257
|
2. Only use the provided MESSAGES and ENTITY to set attribute values.
|
|
258
258
|
3. The summary attribute represents a summary of the ENTITY, and should be updated with new information about the Entity from the MESSAGES.
|
|
259
|
-
Summaries must be no longer than
|
|
259
|
+
Summaries must be no longer than 250 words.
|
|
260
260
|
|
|
261
261
|
<ENTITY>
|
|
262
262
|
{context['node']}
|
|
@@ -25,7 +25,7 @@ from .models import Message, PromptFunction, PromptVersion
|
|
|
25
25
|
class Summary(BaseModel):
|
|
26
26
|
summary: str = Field(
|
|
27
27
|
...,
|
|
28
|
-
description='Summary containing the important information about the entity. Under
|
|
28
|
+
description='Summary containing the important information about the entity. Under 250 words',
|
|
29
29
|
)
|
|
30
30
|
|
|
31
31
|
|
|
@@ -56,7 +56,7 @@ def summarize_pair(context: dict[str, Any]) -> list[Message]:
|
|
|
56
56
|
content=f"""
|
|
57
57
|
Synthesize the information from the following two summaries into a single succinct summary.
|
|
58
58
|
|
|
59
|
-
Summaries must be under
|
|
59
|
+
Summaries must be under 250 words.
|
|
60
60
|
|
|
61
61
|
Summaries:
|
|
62
62
|
{json.dumps(context['node_summaries'], indent=2)}
|
|
@@ -82,7 +82,7 @@ def summarize_context(context: dict[str, Any]) -> list[Message]:
|
|
|
82
82
|
|
|
83
83
|
Given the above MESSAGES and the following ENTITY name, create a summary for the ENTITY. Your summary must only use
|
|
84
84
|
information from the provided MESSAGES. Your summary should also only contain information relevant to the
|
|
85
|
-
provided ENTITY. Summaries must be under
|
|
85
|
+
provided ENTITY. Summaries must be under 250 words.
|
|
86
86
|
|
|
87
87
|
In addition, extract any values for the provided entity properties based on their descriptions.
|
|
88
88
|
If the value of the entity property cannot be found in the current context, set the value of the property to the Python value None.
|
|
@@ -117,7 +117,7 @@ def summary_description(context: dict[str, Any]) -> list[Message]:
|
|
|
117
117
|
role='user',
|
|
118
118
|
content=f"""
|
|
119
119
|
Create a short one sentence description of the summary that explains what kind of information is summarized.
|
|
120
|
-
Summaries must be under
|
|
120
|
+
Summaries must be under 250 words.
|
|
121
121
|
|
|
122
122
|
Summary:
|
|
123
123
|
{json.dumps(context['summary'], indent=2)}
|
|
@@ -209,6 +209,9 @@ async def edge_search(
|
|
|
209
209
|
|
|
210
210
|
reranked_uuids = rrf(search_result_uuids, min_score=reranker_min_score)
|
|
211
211
|
elif config.reranker == EdgeReranker.mmr:
|
|
212
|
+
await semaphore_gather(
|
|
213
|
+
*[edge.load_fact_embedding(driver) for result in search_results for edge in result]
|
|
214
|
+
)
|
|
212
215
|
search_result_uuids_and_vectors = [
|
|
213
216
|
(edge.uuid, edge.fact_embedding if edge.fact_embedding is not None else [0.0] * 1024)
|
|
214
217
|
for result in search_results
|
|
@@ -308,6 +311,9 @@ async def node_search(
|
|
|
308
311
|
if config.reranker == NodeReranker.rrf:
|
|
309
312
|
reranked_uuids = rrf(search_result_uuids, min_score=reranker_min_score)
|
|
310
313
|
elif config.reranker == NodeReranker.mmr:
|
|
314
|
+
await semaphore_gather(
|
|
315
|
+
*[node.load_name_embedding(driver) for result in search_results for node in result]
|
|
316
|
+
)
|
|
311
317
|
search_result_uuids_and_vectors = [
|
|
312
318
|
(node.uuid, node.name_embedding if node.name_embedding is not None else [0.0] * 1024)
|
|
313
319
|
for result in search_results
|
|
@@ -431,6 +437,13 @@ async def community_search(
|
|
|
431
437
|
if config.reranker == CommunityReranker.rrf:
|
|
432
438
|
reranked_uuids = rrf(search_result_uuids, min_score=reranker_min_score)
|
|
433
439
|
elif config.reranker == CommunityReranker.mmr:
|
|
440
|
+
await semaphore_gather(
|
|
441
|
+
*[
|
|
442
|
+
community.load_name_embedding(driver)
|
|
443
|
+
for result in search_results
|
|
444
|
+
for community in result
|
|
445
|
+
]
|
|
446
|
+
)
|
|
434
447
|
search_result_uuids_and_vectors = [
|
|
435
448
|
(
|
|
436
449
|
community.uuid,
|
|
@@ -101,7 +101,6 @@ async def get_mentioned_nodes(
|
|
|
101
101
|
n.uuid As uuid,
|
|
102
102
|
n.group_id AS group_id,
|
|
103
103
|
n.name AS name,
|
|
104
|
-
n.name_embedding AS name_embedding,
|
|
105
104
|
n.created_at AS created_at,
|
|
106
105
|
n.summary AS summary,
|
|
107
106
|
labels(n) AS labels,
|
|
@@ -128,7 +127,6 @@ async def get_communities_by_nodes(
|
|
|
128
127
|
c.uuid As uuid,
|
|
129
128
|
c.group_id AS group_id,
|
|
130
129
|
c.name AS name,
|
|
131
|
-
c.name_embedding AS name_embedding
|
|
132
130
|
c.created_at AS created_at,
|
|
133
131
|
c.summary AS summary
|
|
134
132
|
""",
|
|
@@ -172,7 +170,6 @@ async def edge_fulltext_search(
|
|
|
172
170
|
r.created_at AS created_at,
|
|
173
171
|
r.name AS name,
|
|
174
172
|
r.fact AS fact,
|
|
175
|
-
r.fact_embedding AS fact_embedding,
|
|
176
173
|
r.episodes AS episodes,
|
|
177
174
|
r.expired_at AS expired_at,
|
|
178
175
|
r.valid_at AS valid_at,
|
|
@@ -242,7 +239,6 @@ async def edge_similarity_search(
|
|
|
242
239
|
r.created_at AS created_at,
|
|
243
240
|
r.name AS name,
|
|
244
241
|
r.fact AS fact,
|
|
245
|
-
r.fact_embedding AS fact_embedding,
|
|
246
242
|
r.episodes AS episodes,
|
|
247
243
|
r.expired_at AS expired_at,
|
|
248
244
|
r.valid_at AS valid_at,
|
|
@@ -301,7 +297,6 @@ async def edge_bfs_search(
|
|
|
301
297
|
r.created_at AS created_at,
|
|
302
298
|
r.name AS name,
|
|
303
299
|
r.fact AS fact,
|
|
304
|
-
r.fact_embedding AS fact_embedding,
|
|
305
300
|
r.episodes AS episodes,
|
|
306
301
|
r.expired_at AS expired_at,
|
|
307
302
|
r.valid_at AS valid_at,
|
|
@@ -341,10 +336,10 @@ async def node_fulltext_search(
|
|
|
341
336
|
|
|
342
337
|
query = (
|
|
343
338
|
"""
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
339
|
+
CALL db.index.fulltext.queryNodes("node_name_and_summary", $query, {limit: $limit})
|
|
340
|
+
YIELD node AS n, score
|
|
341
|
+
WHERE n:Entity
|
|
342
|
+
"""
|
|
348
343
|
+ filter_query
|
|
349
344
|
+ ENTITY_NODE_RETURN
|
|
350
345
|
+ """
|
|
@@ -510,7 +505,6 @@ async def community_fulltext_search(
|
|
|
510
505
|
comm.uuid AS uuid,
|
|
511
506
|
comm.group_id AS group_id,
|
|
512
507
|
comm.name AS name,
|
|
513
|
-
comm.name_embedding AS name_embedding,
|
|
514
508
|
comm.created_at AS created_at,
|
|
515
509
|
comm.summary AS summary
|
|
516
510
|
ORDER BY score DESC
|
|
@@ -555,7 +549,6 @@ async def community_similarity_search(
|
|
|
555
549
|
comm.uuid As uuid,
|
|
556
550
|
comm.group_id AS group_id,
|
|
557
551
|
comm.name AS name,
|
|
558
|
-
comm.name_embedding AS name_embedding,
|
|
559
552
|
comm.created_at AS created_at,
|
|
560
553
|
comm.summary AS summary
|
|
561
554
|
ORDER BY score DESC
|
|
@@ -239,7 +239,6 @@ async def determine_entity_community(
|
|
|
239
239
|
RETURN
|
|
240
240
|
c.uuid As uuid,
|
|
241
241
|
c.name AS name,
|
|
242
|
-
c.name_embedding AS name_embedding,
|
|
243
242
|
c.group_id AS group_id,
|
|
244
243
|
c.created_at AS created_at,
|
|
245
244
|
c.summary AS summary
|
|
@@ -258,7 +257,6 @@ async def determine_entity_community(
|
|
|
258
257
|
RETURN
|
|
259
258
|
c.uuid As uuid,
|
|
260
259
|
c.name AS name,
|
|
261
|
-
c.name_embedding AS name_embedding,
|
|
262
260
|
c.group_id AS group_id,
|
|
263
261
|
c.created_at AS created_at,
|
|
264
262
|
c.summary AS summary
|
{graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/utils/maintenance/edge_operations.py
RENAMED
|
@@ -91,7 +91,6 @@ async def extract_edges(
|
|
|
91
91
|
|
|
92
92
|
extract_edges_max_tokens = 16384
|
|
93
93
|
llm_client = clients.llm_client
|
|
94
|
-
embedder = clients.embedder
|
|
95
94
|
|
|
96
95
|
node_uuids_by_name_map = {node.name: node.uuid for node in nodes}
|
|
97
96
|
|
|
@@ -184,8 +183,6 @@ async def extract_edges(
|
|
|
184
183
|
f'Created new edge: {edge.name} from (UUID: {edge.source_node_uuid}) to (UUID: {edge.target_node_uuid})'
|
|
185
184
|
)
|
|
186
185
|
|
|
187
|
-
await create_entity_edge_embeddings(embedder, edges)
|
|
188
|
-
|
|
189
186
|
logger.debug(f'Extracted edges: {[(e.name, e.uuid) for e in edges]}')
|
|
190
187
|
|
|
191
188
|
return edges
|
|
@@ -238,9 +235,13 @@ async def dedupe_extracted_edges(
|
|
|
238
235
|
async def resolve_extracted_edges(
|
|
239
236
|
clients: GraphitiClients,
|
|
240
237
|
extracted_edges: list[EntityEdge],
|
|
238
|
+
episode: EpisodicNode,
|
|
241
239
|
) -> tuple[list[EntityEdge], list[EntityEdge]]:
|
|
242
240
|
driver = clients.driver
|
|
243
241
|
llm_client = clients.llm_client
|
|
242
|
+
embedder = clients.embedder
|
|
243
|
+
|
|
244
|
+
await create_entity_edge_embeddings(embedder, extracted_edges)
|
|
244
245
|
|
|
245
246
|
search_results: tuple[list[list[EntityEdge]], list[list[EntityEdge]]] = await semaphore_gather(
|
|
246
247
|
get_relevant_edges(driver, extracted_edges, SearchFilters()),
|
|
@@ -258,10 +259,7 @@ async def resolve_extracted_edges(
|
|
|
258
259
|
await semaphore_gather(
|
|
259
260
|
*[
|
|
260
261
|
resolve_extracted_edge(
|
|
261
|
-
llm_client,
|
|
262
|
-
extracted_edge,
|
|
263
|
-
related_edges,
|
|
264
|
-
existing_edges,
|
|
262
|
+
llm_client, extracted_edge, related_edges, existing_edges, episode
|
|
265
263
|
)
|
|
266
264
|
for extracted_edge, related_edges, existing_edges in zip(
|
|
267
265
|
extracted_edges, related_edges_lists, edge_invalidation_candidates, strict=True
|
|
@@ -322,9 +320,10 @@ async def resolve_extracted_edge(
|
|
|
322
320
|
extracted_edge: EntityEdge,
|
|
323
321
|
related_edges: list[EntityEdge],
|
|
324
322
|
existing_edges: list[EntityEdge],
|
|
323
|
+
episode: EpisodicNode,
|
|
325
324
|
) -> tuple[EntityEdge, list[EntityEdge]]:
|
|
326
325
|
resolved_edge, invalidation_candidates = await semaphore_gather(
|
|
327
|
-
dedupe_extracted_edge(llm_client, extracted_edge, related_edges),
|
|
326
|
+
dedupe_extracted_edge(llm_client, extracted_edge, related_edges, episode),
|
|
328
327
|
get_edge_contradictions(llm_client, extracted_edge, existing_edges),
|
|
329
328
|
)
|
|
330
329
|
|
|
@@ -356,7 +355,10 @@ async def resolve_extracted_edge(
|
|
|
356
355
|
|
|
357
356
|
|
|
358
357
|
async def dedupe_extracted_edge(
|
|
359
|
-
llm_client: LLMClient,
|
|
358
|
+
llm_client: LLMClient,
|
|
359
|
+
extracted_edge: EntityEdge,
|
|
360
|
+
related_edges: list[EntityEdge],
|
|
361
|
+
episode: EpisodicNode | None = None,
|
|
360
362
|
) -> EntityEdge:
|
|
361
363
|
if len(related_edges) == 0:
|
|
362
364
|
return extracted_edge
|
|
@@ -391,6 +393,9 @@ async def dedupe_extracted_edge(
|
|
|
391
393
|
else extracted_edge
|
|
392
394
|
)
|
|
393
395
|
|
|
396
|
+
if duplicate_fact_id >= 0 and episode is not None:
|
|
397
|
+
edge.episodes += episode.uuid
|
|
398
|
+
|
|
394
399
|
end = time()
|
|
395
400
|
logger.debug(
|
|
396
401
|
f'Resolved Edge: {extracted_edge.name} is {edge.name}, in {(end - start) * 1000} ms'
|
{graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/utils/maintenance/node_operations.py
RENAMED
|
@@ -34,8 +34,10 @@ from graphiti_core.prompts.extract_nodes import (
|
|
|
34
34
|
ExtractedEntity,
|
|
35
35
|
MissedEntities,
|
|
36
36
|
)
|
|
37
|
+
from graphiti_core.search.search import search
|
|
38
|
+
from graphiti_core.search.search_config import SearchResults
|
|
39
|
+
from graphiti_core.search.search_config_recipes import NODE_HYBRID_SEARCH_RRF
|
|
37
40
|
from graphiti_core.search.search_filters import SearchFilters
|
|
38
|
-
from graphiti_core.search.search_utils import get_relevant_nodes
|
|
39
41
|
from graphiti_core.utils.datetime_utils import utc_now
|
|
40
42
|
|
|
41
43
|
logger = logging.getLogger(__name__)
|
|
@@ -70,7 +72,6 @@ async def extract_nodes(
|
|
|
70
72
|
) -> list[EntityNode]:
|
|
71
73
|
start = time()
|
|
72
74
|
llm_client = clients.llm_client
|
|
73
|
-
embedder = clients.embedder
|
|
74
75
|
llm_response = {}
|
|
75
76
|
custom_prompt = ''
|
|
76
77
|
entities_missed = True
|
|
@@ -163,8 +164,6 @@ async def extract_nodes(
|
|
|
163
164
|
extracted_nodes.append(new_node)
|
|
164
165
|
logger.debug(f'Created new node: {new_node.name} (UUID: {new_node.uuid})')
|
|
165
166
|
|
|
166
|
-
await create_entity_node_embeddings(embedder, extracted_nodes)
|
|
167
|
-
|
|
168
167
|
logger.debug(f'Extracted nodes: {[(n.name, n.uuid) for n in extracted_nodes]}')
|
|
169
168
|
return extracted_nodes
|
|
170
169
|
|
|
@@ -227,13 +226,22 @@ async def resolve_extracted_nodes(
|
|
|
227
226
|
entity_types: dict[str, BaseModel] | None = None,
|
|
228
227
|
) -> tuple[list[EntityNode], dict[str, str]]:
|
|
229
228
|
llm_client = clients.llm_client
|
|
230
|
-
driver = clients.driver
|
|
231
229
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
230
|
+
search_results: list[SearchResults] = await semaphore_gather(
|
|
231
|
+
*[
|
|
232
|
+
search(
|
|
233
|
+
clients=clients,
|
|
234
|
+
query=node.name,
|
|
235
|
+
group_ids=[node.group_id],
|
|
236
|
+
search_filter=SearchFilters(),
|
|
237
|
+
config=NODE_HYBRID_SEARCH_RRF,
|
|
238
|
+
)
|
|
239
|
+
for node in extracted_nodes
|
|
240
|
+
]
|
|
235
241
|
)
|
|
236
242
|
|
|
243
|
+
existing_nodes_lists: list[list[EntityNode]] = [result.nodes for result in search_results]
|
|
244
|
+
|
|
237
245
|
resolved_nodes: list[EntityNode] = await semaphore_gather(
|
|
238
246
|
*[
|
|
239
247
|
resolve_extracted_node(
|
|
@@ -375,7 +383,7 @@ async def extract_attributes_from_node(
|
|
|
375
383
|
'summary': (
|
|
376
384
|
str,
|
|
377
385
|
Field(
|
|
378
|
-
description='Summary containing the important information about the entity. Under
|
|
386
|
+
description='Summary containing the important information about the entity. Under 250 words',
|
|
379
387
|
),
|
|
380
388
|
)
|
|
381
389
|
}
|
|
@@ -427,10 +435,7 @@ async def dedupe_node_list(
|
|
|
427
435
|
node_map[node.uuid] = node
|
|
428
436
|
|
|
429
437
|
# Prepare context for LLM
|
|
430
|
-
nodes_context = [
|
|
431
|
-
{'uuid': node.uuid, 'name': node.name, 'summary': node.summary}.update(node.attributes)
|
|
432
|
-
for node in nodes
|
|
433
|
-
]
|
|
438
|
+
nodes_context = [{'uuid': node.uuid, 'name': node.name, **node.attributes} for node in nodes]
|
|
434
439
|
|
|
435
440
|
context = {
|
|
436
441
|
'nodes': nodes_context,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "graphiti-core"
|
|
3
3
|
description = "A temporal graph building library"
|
|
4
|
-
version = "0.11.
|
|
4
|
+
version = "0.11.6pre1"
|
|
5
5
|
authors = [
|
|
6
6
|
{ "name" = "Paul Paliychuk", "email" = "paul@getzep.com" },
|
|
7
7
|
{ "name" = "Preston Rasmussen", "email" = "preston@getzep.com" },
|
|
@@ -19,7 +19,6 @@ dependencies = [
|
|
|
19
19
|
"tenacity>=9.0.0",
|
|
20
20
|
"numpy>=1.0.0",
|
|
21
21
|
"python-dotenv>=1.0.1",
|
|
22
|
-
"graph-service (>=1.0.0.7,<2.0.0.0)",
|
|
23
22
|
]
|
|
24
23
|
|
|
25
24
|
[project.urls]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/cross_encoder/bge_reranker_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/llm_client/openai_generic_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/models/edges/edge_db_queries.py
RENAMED
|
File without changes
|
|
File without changes
|
{graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/models/nodes/node_db_queries.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/prompts/extract_edge_dates.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/search/search_config_recipes.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{graphiti_core-0.11.4 → graphiti_core-0.11.6rc1}/graphiti_core/utils/maintenance/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|