graphiti-core 0.11.5__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.

Files changed (66) hide show
  1. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/PKG-INFO +1 -2
  2. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/edges.py +18 -4
  3. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/graphiti.py +6 -1
  4. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/llm_client/anthropic_client.py +0 -13
  5. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/nodes.py +30 -7
  6. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/prompts/dedupe_nodes.py +11 -4
  7. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/prompts/extract_nodes.py +1 -1
  8. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/prompts/summarize_nodes.py +4 -4
  9. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/search/search.py +13 -0
  10. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/search/search_utils.py +4 -11
  11. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/utils/maintenance/community_operations.py +0 -2
  12. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/utils/maintenance/edge_operations.py +14 -9
  13. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/utils/maintenance/node_operations.py +2 -10
  14. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/pyproject.toml +1 -2
  15. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/LICENSE +0 -0
  16. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/README.md +0 -0
  17. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/__init__.py +0 -0
  18. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/cross_encoder/__init__.py +0 -0
  19. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/cross_encoder/bge_reranker_client.py +0 -0
  20. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/cross_encoder/client.py +0 -0
  21. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/cross_encoder/openai_reranker_client.py +0 -0
  22. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/embedder/__init__.py +0 -0
  23. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/embedder/client.py +0 -0
  24. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/embedder/gemini.py +0 -0
  25. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/embedder/openai.py +0 -0
  26. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/embedder/voyage.py +0 -0
  27. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/errors.py +0 -0
  28. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/graphiti_types.py +0 -0
  29. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/helpers.py +0 -0
  30. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/llm_client/__init__.py +0 -0
  31. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/llm_client/client.py +0 -0
  32. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/llm_client/config.py +0 -0
  33. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/llm_client/errors.py +0 -0
  34. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/llm_client/gemini_client.py +0 -0
  35. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/llm_client/groq_client.py +0 -0
  36. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/llm_client/openai_client.py +0 -0
  37. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/llm_client/openai_generic_client.py +0 -0
  38. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/llm_client/utils.py +0 -0
  39. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/models/__init__.py +0 -0
  40. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/models/edges/__init__.py +0 -0
  41. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/models/edges/edge_db_queries.py +0 -0
  42. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/models/nodes/__init__.py +0 -0
  43. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/models/nodes/node_db_queries.py +0 -0
  44. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/prompts/__init__.py +0 -0
  45. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/prompts/dedupe_edges.py +0 -0
  46. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/prompts/eval.py +0 -0
  47. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/prompts/extract_edge_dates.py +0 -0
  48. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/prompts/extract_edges.py +0 -0
  49. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/prompts/invalidate_edges.py +0 -0
  50. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/prompts/lib.py +0 -0
  51. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/prompts/models.py +0 -0
  52. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/prompts/prompt_helpers.py +0 -0
  53. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/py.typed +0 -0
  54. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/search/__init__.py +0 -0
  55. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/search/search_config.py +0 -0
  56. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/search/search_config_recipes.py +0 -0
  57. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/search/search_filters.py +0 -0
  58. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/search/search_helpers.py +0 -0
  59. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/utils/__init__.py +0 -0
  60. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/utils/bulk_utils.py +0 -0
  61. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/utils/datetime_utils.py +0 -0
  62. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/utils/maintenance/__init__.py +0 -0
  63. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/utils/maintenance/graph_data_operations.py +0 -0
  64. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/utils/maintenance/temporal_operations.py +0 -0
  65. {graphiti_core-0.11.5 → graphiti_core-0.11.6rc1}/graphiti_core/utils/maintenance/utils.py +0 -0
  66. {graphiti_core-0.11.5 → 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.5
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
- MATCH (n:Entity {uuid: $node_uuid})-[e:RELATES_TO]-(m:Entity)
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(self.llm_client, updated_edge, related_edges[0])
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)
@@ -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
- MATCH (n:Entity {uuid: $uuid})
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 de-duplicates entities from entity lists.',
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,14 +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
- The ENTITY TYPE DESCRIPTION gives more insight into what the entity type means for the NEW ENTITY.
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. If the NEW ENTITY is not a duplicate of any of the EXISTING ENTITIES,
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 most complete name for the entity.
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).
80
87
  """,
81
88
  ),
82
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 500 words.
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 500 words',
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 500 words.
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 500 words.
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 500 words.
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
- CALL db.index.fulltext.queryNodes("node_name_and_summary", $query, {limit: $limit})
345
- YIELD node AS n, score
346
- WHERE n:Entity
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
@@ -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, extracted_edge: EntityEdge, related_edges: list[EntityEdge]
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'
@@ -72,7 +72,6 @@ async def extract_nodes(
72
72
  ) -> list[EntityNode]:
73
73
  start = time()
74
74
  llm_client = clients.llm_client
75
- embedder = clients.embedder
76
75
  llm_response = {}
77
76
  custom_prompt = ''
78
77
  entities_missed = True
@@ -165,8 +164,6 @@ async def extract_nodes(
165
164
  extracted_nodes.append(new_node)
166
165
  logger.debug(f'Created new node: {new_node.name} (UUID: {new_node.uuid})')
167
166
 
168
- await create_entity_node_embeddings(embedder, extracted_nodes)
169
-
170
167
  logger.debug(f'Extracted nodes: {[(n.name, n.uuid) for n in extracted_nodes]}')
171
168
  return extracted_nodes
172
169
 
@@ -235,7 +232,6 @@ async def resolve_extracted_nodes(
235
232
  search(
236
233
  clients=clients,
237
234
  query=node.name,
238
- query_vector=node.name_embedding,
239
235
  group_ids=[node.group_id],
240
236
  search_filter=SearchFilters(),
241
237
  config=NODE_HYBRID_SEARCH_RRF,
@@ -294,7 +290,6 @@ async def resolve_extracted_node(
294
290
  'id': i,
295
291
  'name': node.name,
296
292
  'entity_types': node.labels,
297
- 'summary': node.summary,
298
293
  },
299
294
  **node.attributes,
300
295
  }
@@ -388,7 +383,7 @@ async def extract_attributes_from_node(
388
383
  'summary': (
389
384
  str,
390
385
  Field(
391
- description='Summary containing the important information about the entity. Under 500 words',
386
+ description='Summary containing the important information about the entity. Under 250 words',
392
387
  ),
393
388
  )
394
389
  }
@@ -440,10 +435,7 @@ async def dedupe_node_list(
440
435
  node_map[node.uuid] = node
441
436
 
442
437
  # Prepare context for LLM
443
- nodes_context = [
444
- {'uuid': node.uuid, 'name': node.name, 'summary': node.summary}.update(node.attributes)
445
- for node in nodes
446
- ]
438
+ nodes_context = [{'uuid': node.uuid, 'name': node.name, **node.attributes} for node in nodes]
447
439
 
448
440
  context = {
449
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.5"
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]