graphiti-core 0.2.1__py3-none-any.whl → 0.2.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/graphiti.py CHANGED
@@ -59,11 +59,6 @@ from graphiti_core.utils.maintenance.node_operations import (
59
59
  extract_nodes,
60
60
  resolve_extracted_nodes,
61
61
  )
62
- from graphiti_core.utils.maintenance.temporal_operations import (
63
- extract_edge_dates,
64
- invalidate_edges,
65
- prepare_edges_for_invalidation,
66
- )
67
62
 
68
63
  logger = logging.getLogger(__name__)
69
64
 
@@ -293,7 +288,7 @@ class Graphiti:
293
288
  *[node.generate_name_embedding(embedder) for node in extracted_nodes]
294
289
  )
295
290
 
296
- # Resolve extracted nodes with nodes already in the graph
291
+ # Resolve extracted nodes with nodes already in the graph and extract facts
297
292
  existing_nodes_lists: list[list[EntityNode]] = list(
298
293
  await asyncio.gather(
299
294
  *[get_relevant_nodes([node], self.driver) for node in extracted_nodes]
@@ -302,22 +297,27 @@ class Graphiti:
302
297
 
303
298
  logger.info(f'Extracted nodes: {[(n.name, n.uuid) for n in extracted_nodes]}')
304
299
 
305
- mentioned_nodes, _ = await resolve_extracted_nodes(
306
- self.llm_client, extracted_nodes, existing_nodes_lists
300
+ (mentioned_nodes, uuid_map), extracted_edges = await asyncio.gather(
301
+ resolve_extracted_nodes(self.llm_client, extracted_nodes, existing_nodes_lists),
302
+ extract_edges(self.llm_client, episode, extracted_nodes, previous_episodes),
307
303
  )
308
304
  logger.info(f'Adjusted mentioned nodes: {[(n.name, n.uuid) for n in mentioned_nodes]}')
309
305
  nodes.extend(mentioned_nodes)
310
306
 
311
- # Extract facts as edges given entity nodes
312
- extracted_edges = await extract_edges(
313
- self.llm_client, episode, mentioned_nodes, previous_episodes
307
+ extracted_edges_with_resolved_pointers = resolve_edge_pointers(
308
+ extracted_edges, uuid_map
314
309
  )
315
310
 
316
311
  # calculate embeddings
317
- await asyncio.gather(*[edge.generate_embedding(embedder) for edge in extracted_edges])
312
+ await asyncio.gather(
313
+ *[
314
+ edge.generate_embedding(embedder)
315
+ for edge in extracted_edges_with_resolved_pointers
316
+ ]
317
+ )
318
318
 
319
- # Resolve extracted edges with edges already in the graph
320
- existing_edges_list: list[list[EntityEdge]] = list(
319
+ # Resolve extracted edges with related edges already in the graph
320
+ related_edges_list: list[list[EntityEdge]] = list(
321
321
  await asyncio.gather(
322
322
  *[
323
323
  get_relevant_edges(
@@ -327,74 +327,66 @@ class Graphiti:
327
327
  edge.target_node_uuid,
328
328
  RELEVANT_SCHEMA_LIMIT,
329
329
  )
330
- for edge in extracted_edges
330
+ for edge in extracted_edges_with_resolved_pointers
331
331
  ]
332
332
  )
333
333
  )
334
334
  logger.info(
335
- f'Existing edges lists: {[(e.name, e.uuid) for edges_lst in existing_edges_list for e in edges_lst]}'
335
+ f'Related edges lists: {[(e.name, e.uuid) for edges_lst in related_edges_list for e in edges_lst]}'
336
336
  )
337
- logger.info(f'Extracted edges: {[(e.name, e.uuid) for e in extracted_edges]}')
338
-
339
- deduped_edges: list[EntityEdge] = await resolve_extracted_edges(
340
- self.llm_client, extracted_edges, existing_edges_list
337
+ logger.info(
338
+ f'Extracted edges: {[(e.name, e.uuid) for e in extracted_edges_with_resolved_pointers]}'
341
339
  )
342
340
 
343
- # Extract dates for the newly extracted edges
344
- edge_dates = await asyncio.gather(
345
- *[
346
- extract_edge_dates(
347
- self.llm_client,
348
- edge,
349
- episode,
350
- previous_episodes,
351
- )
352
- for edge in deduped_edges
353
- ]
341
+ existing_source_edges_list: list[list[EntityEdge]] = list(
342
+ await asyncio.gather(
343
+ *[
344
+ get_relevant_edges(
345
+ self.driver,
346
+ [edge],
347
+ edge.source_node_uuid,
348
+ None,
349
+ RELEVANT_SCHEMA_LIMIT,
350
+ )
351
+ for edge in extracted_edges_with_resolved_pointers
352
+ ]
353
+ )
354
354
  )
355
355
 
356
- for i, edge in enumerate(deduped_edges):
357
- valid_at = edge_dates[i][0]
358
- invalid_at = edge_dates[i][1]
359
-
360
- edge.valid_at = valid_at
361
- edge.invalid_at = invalid_at
362
- if edge.invalid_at is not None:
363
- edge.expired_at = now
364
-
365
- entity_edges.extend(deduped_edges)
356
+ existing_target_edges_list: list[list[EntityEdge]] = list(
357
+ await asyncio.gather(
358
+ *[
359
+ get_relevant_edges(
360
+ self.driver,
361
+ [edge],
362
+ None,
363
+ edge.target_node_uuid,
364
+ RELEVANT_SCHEMA_LIMIT,
365
+ )
366
+ for edge in extracted_edges_with_resolved_pointers
367
+ ]
368
+ )
369
+ )
366
370
 
367
- existing_edges: list[EntityEdge] = [
368
- e for edge_lst in existing_edges_list for e in edge_lst
371
+ existing_edges_list: list[list[EntityEdge]] = [
372
+ source_lst + target_lst
373
+ for source_lst, target_lst in zip(
374
+ existing_source_edges_list, existing_target_edges_list
375
+ )
369
376
  ]
370
377
 
371
- (
372
- old_edges_with_nodes_pending_invalidation,
373
- new_edges_with_nodes,
374
- ) = prepare_edges_for_invalidation(
375
- existing_edges=existing_edges, new_edges=deduped_edges, nodes=nodes
376
- )
377
-
378
- invalidated_edges = await invalidate_edges(
378
+ resolved_edges, invalidated_edges = await resolve_extracted_edges(
379
379
  self.llm_client,
380
- old_edges_with_nodes_pending_invalidation,
381
- new_edges_with_nodes,
380
+ extracted_edges_with_resolved_pointers,
381
+ related_edges_list,
382
+ existing_edges_list,
382
383
  episode,
383
384
  previous_episodes,
384
385
  )
385
386
 
386
- for edge in invalidated_edges:
387
- for existing_edge in existing_edges:
388
- if existing_edge.uuid == edge.uuid:
389
- existing_edge.expired_at = edge.expired_at
390
- for deduped_edge in deduped_edges:
391
- if deduped_edge.uuid == edge.uuid:
392
- deduped_edge.expired_at = edge.expired_at
393
- logger.info(f'Invalidated edges: {[(e.name, e.uuid) for e in invalidated_edges]}')
394
-
395
- entity_edges.extend(existing_edges)
387
+ entity_edges.extend(resolved_edges + invalidated_edges)
396
388
 
397
- logger.info(f'Deduped edges: {[(e.name, e.uuid) for e in deduped_edges]}')
389
+ logger.info(f'Resolved edges: {[(e.name, e.uuid) for e in resolved_edges]}')
398
390
 
399
391
  episodic_edges: list[EpisodicEdge] = build_episodic_edges(
400
392
  mentioned_nodes,
graphiti_core/nodes.py CHANGED
@@ -225,7 +225,8 @@ class EntityNode(Node):
225
225
  MATCH (n:Entity {uuid: $uuid})
226
226
  RETURN
227
227
  n.uuid As uuid,
228
- n.name AS name,
228
+ n.name AS name,
229
+ n.name_embedding AS name_embedding,
229
230
  n.created_at AS created_at,
230
231
  n.summary AS summary
231
232
  """,
@@ -239,6 +240,7 @@ class EntityNode(Node):
239
240
  EntityNode(
240
241
  uuid=record['uuid'],
241
242
  name=record['name'],
243
+ name_embedding=record['name_embedding'],
242
244
  labels=['Entity'],
243
245
  created_at=record['created_at'].to_native(),
244
246
  summary=record['summary'],
@@ -248,3 +250,6 @@ class EntityNode(Node):
248
250
  logger.info(f'Found Node: {uuid}')
249
251
 
250
252
  return nodes[0]
253
+
254
+
255
+ # Node helpers
@@ -129,7 +129,7 @@ def v3(context: dict[str, Any]) -> list[Message]:
129
129
  Given the following context, determine whether the New Edge represents any of the edges in the list of Existing Edges.
130
130
 
131
131
  Existing Edges:
132
- {json.dumps(context['existing_edges'], indent=2)}
132
+ {json.dumps(context['related_edges'], indent=2)}
133
133
 
134
134
  New Edge:
135
135
  {json.dumps(context['extracted_edges'], indent=2)}
@@ -21,10 +21,12 @@ from .models import Message, PromptFunction, PromptVersion
21
21
 
22
22
  class Prompt(Protocol):
23
23
  v1: PromptVersion
24
+ v2: PromptVersion
24
25
 
25
26
 
26
27
  class Versions(TypedDict):
27
28
  v1: PromptFunction
29
+ v2: PromptFunction
28
30
 
29
31
 
30
32
  def v1(context: dict[str, Any]) -> list[Message]:
@@ -71,4 +73,38 @@ def v1(context: dict[str, Any]) -> list[Message]:
71
73
  ]
72
74
 
73
75
 
74
- versions: Versions = {'v1': v1}
76
+ def v2(context: dict[str, Any]) -> list[Message]:
77
+ return [
78
+ Message(
79
+ role='system',
80
+ content='You are an AI assistant that helps determine which relationships in a knowledge graph should be invalidated based solely on explicit contradictions in newer information.',
81
+ ),
82
+ Message(
83
+ role='user',
84
+ content=f"""
85
+ Based on the provided Existing Edges and a New Edge, determine which existing edges, if any, should be marked as invalidated due to contradictions with the New Edge.
86
+
87
+ Existing Edges:
88
+ {context['existing_edges']}
89
+
90
+ New Edge:
91
+ {context['new_edge']}
92
+
93
+
94
+ For each existing edge that should be invalidated, respond with a JSON object in the following format:
95
+ {{
96
+ "invalidated_edges": [
97
+ {{
98
+ "uuid": "The UUID of the edge to be invalidated",
99
+ "fact": "Updated fact of the edge"
100
+ }}
101
+ ]
102
+ }}
103
+
104
+ If no relationships need to be invalidated based on these strict criteria, return an empty list for "invalidated_edges".
105
+ """,
106
+ ),
107
+ ]
108
+
109
+
110
+ versions: Versions = {'v1': v1, 'v2': v2}
@@ -23,7 +23,8 @@ async def get_mentioned_nodes(driver: AsyncDriver, episodes: list[EpisodicNode])
23
23
  MATCH (episode:Episodic)-[:MENTIONS]->(n:Entity) WHERE episode.uuid IN $uuids
24
24
  RETURN DISTINCT
25
25
  n.uuid As uuid,
26
- n.name AS name,
26
+ n.name AS name,
27
+ n.name_embedding AS name_embedding
27
28
  n.created_at AS created_at,
28
29
  n.summary AS summary
29
30
  """,
@@ -37,6 +38,7 @@ async def get_mentioned_nodes(driver: AsyncDriver, episodes: list[EpisodicNode])
37
38
  EntityNode(
38
39
  uuid=record['uuid'],
39
40
  name=record['name'],
41
+ name_embedding=record['name_embedding'],
40
42
  labels=['Entity'],
41
43
  created_at=record['created_at'].to_native(),
42
44
  summary=record['summary'],
@@ -96,11 +98,11 @@ async def bfs(node_ids: list[str], driver: AsyncDriver):
96
98
 
97
99
 
98
100
  async def edge_similarity_search(
99
- driver: AsyncDriver,
100
- search_vector: list[float],
101
- source_node_uuid: str | None,
102
- target_node_uuid: str | None,
103
- limit: int = RELEVANT_SCHEMA_LIMIT,
101
+ driver: AsyncDriver,
102
+ search_vector: list[float],
103
+ source_node_uuid: str | None,
104
+ target_node_uuid: str | None,
105
+ limit: int = RELEVANT_SCHEMA_LIMIT,
104
106
  ) -> list[EntityEdge]:
105
107
  # vector similarity search over embedded facts
106
108
  query = Query("""
@@ -211,7 +213,7 @@ async def edge_similarity_search(
211
213
 
212
214
 
213
215
  async def entity_similarity_search(
214
- search_vector: list[float], driver: AsyncDriver, limit=RELEVANT_SCHEMA_LIMIT
216
+ search_vector: list[float], driver: AsyncDriver, limit=RELEVANT_SCHEMA_LIMIT
215
217
  ) -> list[EntityNode]:
216
218
  # vector similarity search over entity names
217
219
  records, _, _ = await driver.execute_query(
@@ -221,7 +223,7 @@ async def entity_similarity_search(
221
223
  RETURN
222
224
  n.uuid As uuid,
223
225
  n.name AS name,
224
- n.name_embeddings AS name_embedding,
226
+ n.name_embedding AS name_embedding,
225
227
  n.created_at AS created_at,
226
228
  n.summary AS summary
227
229
  ORDER BY score DESC
@@ -247,7 +249,7 @@ async def entity_similarity_search(
247
249
 
248
250
 
249
251
  async def entity_fulltext_search(
250
- query: str, driver: AsyncDriver, limit=RELEVANT_SCHEMA_LIMIT
252
+ query: str, driver: AsyncDriver, limit=RELEVANT_SCHEMA_LIMIT
251
253
  ) -> list[EntityNode]:
252
254
  # BM25 search to get top nodes
253
255
  fuzzy_query = re.sub(r'[^\w\s]', '', query) + '~'
@@ -257,7 +259,7 @@ async def entity_fulltext_search(
257
259
  RETURN
258
260
  node.uuid AS uuid,
259
261
  node.name AS name,
260
- node.name_embeddings AS name_embedding,
262
+ node.name_embedding AS name_embedding,
261
263
  node.created_at AS created_at,
262
264
  node.summary AS summary
263
265
  ORDER BY score DESC
@@ -284,11 +286,11 @@ async def entity_fulltext_search(
284
286
 
285
287
 
286
288
  async def edge_fulltext_search(
287
- driver: AsyncDriver,
288
- query: str,
289
- source_node_uuid: str | None,
290
- target_node_uuid: str | None,
291
- limit=RELEVANT_SCHEMA_LIMIT,
289
+ driver: AsyncDriver,
290
+ query: str,
291
+ source_node_uuid: str | None,
292
+ target_node_uuid: str | None,
293
+ limit=RELEVANT_SCHEMA_LIMIT,
292
294
  ) -> list[EntityEdge]:
293
295
  # fulltext search over facts
294
296
  cypher_query = Query("""
@@ -401,10 +403,10 @@ async def edge_fulltext_search(
401
403
 
402
404
 
403
405
  async def hybrid_node_search(
404
- queries: list[str],
405
- embeddings: list[list[float]],
406
- driver: AsyncDriver,
407
- limit: int = RELEVANT_SCHEMA_LIMIT,
406
+ queries: list[str],
407
+ embeddings: list[list[float]],
408
+ driver: AsyncDriver,
409
+ limit: int = RELEVANT_SCHEMA_LIMIT,
408
410
  ) -> list[EntityNode]:
409
411
  """
410
412
  Perform a hybrid search for nodes using both text queries and embeddings.
@@ -466,8 +468,8 @@ async def hybrid_node_search(
466
468
 
467
469
 
468
470
  async def get_relevant_nodes(
469
- nodes: list[EntityNode],
470
- driver: AsyncDriver,
471
+ nodes: list[EntityNode],
472
+ driver: AsyncDriver,
471
473
  ) -> list[EntityNode]:
472
474
  """
473
475
  Retrieve relevant nodes based on the provided list of EntityNodes.
@@ -503,11 +505,11 @@ async def get_relevant_nodes(
503
505
 
504
506
 
505
507
  async def get_relevant_edges(
506
- driver: AsyncDriver,
507
- edges: list[EntityEdge],
508
- source_node_uuid: str | None,
509
- target_node_uuid: str | None,
510
- limit: int = RELEVANT_SCHEMA_LIMIT,
508
+ driver: AsyncDriver,
509
+ edges: list[EntityEdge],
510
+ source_node_uuid: str | None,
511
+ target_node_uuid: str | None,
512
+ limit: int = RELEVANT_SCHEMA_LIMIT,
511
513
  ) -> list[EntityEdge]:
512
514
  start = time()
513
515
  relevant_edges: list[EntityEdge] = []
@@ -557,7 +559,7 @@ def rrf(results: list[list[str]], rank_const=1) -> list[str]:
557
559
 
558
560
 
559
561
  async def node_distance_reranker(
560
- driver: AsyncDriver, results: list[list[str]], center_node_uuid: str
562
+ driver: AsyncDriver, results: list[list[str]], center_node_uuid: str
561
563
  ) -> list[str]:
562
564
  # use rrf as a preliminary ranker
563
565
  sorted_uuids = rrf(results)
@@ -579,8 +581,8 @@ async def node_distance_reranker(
579
581
 
580
582
  for record in records:
581
583
  if (
582
- record['source_uuid'] == center_node_uuid
583
- or record['target_uuid'] == center_node_uuid
584
+ record['source_uuid'] == center_node_uuid
585
+ or record['target_uuid'] == center_node_uuid
584
586
  ):
585
587
  continue
586
588
  distance = record['score']
@@ -24,6 +24,10 @@ from graphiti_core.edges import EntityEdge, EpisodicEdge
24
24
  from graphiti_core.llm_client import LLMClient
25
25
  from graphiti_core.nodes import EntityNode, EpisodicNode
26
26
  from graphiti_core.prompts import prompt_library
27
+ from graphiti_core.utils.maintenance.temporal_operations import (
28
+ extract_edge_dates,
29
+ get_edge_contradictions,
30
+ )
27
31
 
28
32
  logger = logging.getLogger(__name__)
29
33
 
@@ -149,28 +153,110 @@ async def dedupe_extracted_edges(
149
153
  async def resolve_extracted_edges(
150
154
  llm_client: LLMClient,
151
155
  extracted_edges: list[EntityEdge],
156
+ related_edges_lists: list[list[EntityEdge]],
152
157
  existing_edges_lists: list[list[EntityEdge]],
153
- ) -> list[EntityEdge]:
154
- resolved_edges: list[EntityEdge] = list(
158
+ current_episode: EpisodicNode,
159
+ previous_episodes: list[EpisodicNode],
160
+ ) -> tuple[list[EntityEdge], list[EntityEdge]]:
161
+ # resolve edges with related edges in the graph, extract temporal information, and find invalidation candidates
162
+ results: list[tuple[EntityEdge, list[EntityEdge]]] = list(
155
163
  await asyncio.gather(
156
164
  *[
157
- resolve_extracted_edge(llm_client, extracted_edge, existing_edges)
158
- for extracted_edge, existing_edges in zip(extracted_edges, existing_edges_lists)
165
+ resolve_extracted_edge(
166
+ llm_client,
167
+ extracted_edge,
168
+ related_edges,
169
+ existing_edges,
170
+ current_episode,
171
+ previous_episodes,
172
+ )
173
+ for extracted_edge, related_edges, existing_edges in zip(
174
+ extracted_edges, related_edges_lists, existing_edges_lists
175
+ )
159
176
  ]
160
177
  )
161
178
  )
162
179
 
163
- return resolved_edges
180
+ resolved_edges: list[EntityEdge] = []
181
+ invalidated_edges: list[EntityEdge] = []
182
+ for result in results:
183
+ resolved_edge = result[0]
184
+ invalidated_edge_chunk = result[1]
185
+
186
+ resolved_edges.append(resolved_edge)
187
+ invalidated_edges.extend(invalidated_edge_chunk)
188
+
189
+ return resolved_edges, invalidated_edges
164
190
 
165
191
 
166
192
  async def resolve_extracted_edge(
167
- llm_client: LLMClient, extracted_edge: EntityEdge, existing_edges: list[EntityEdge]
193
+ llm_client: LLMClient,
194
+ extracted_edge: EntityEdge,
195
+ related_edges: list[EntityEdge],
196
+ existing_edges: list[EntityEdge],
197
+ current_episode: EpisodicNode,
198
+ previous_episodes: list[EpisodicNode],
199
+ ) -> tuple[EntityEdge, list[EntityEdge]]:
200
+ resolved_edge, (valid_at, invalid_at), invalidation_candidates = await asyncio.gather(
201
+ dedupe_extracted_edge(llm_client, extracted_edge, related_edges),
202
+ extract_edge_dates(llm_client, extracted_edge, current_episode, previous_episodes),
203
+ get_edge_contradictions(llm_client, extracted_edge, existing_edges),
204
+ )
205
+
206
+ now = datetime.now()
207
+
208
+ resolved_edge.valid_at = valid_at if valid_at is not None else resolved_edge.valid_at
209
+ resolved_edge.invalid_at = invalid_at if invalid_at is not None else resolved_edge.invalid_at
210
+ if invalid_at is not None and resolved_edge.expired_at is None:
211
+ resolved_edge.expired_at = now
212
+
213
+ # Determine if the new_edge needs to be expired
214
+ if resolved_edge.expired_at is None:
215
+ invalidation_candidates.sort(key=lambda c: (c.valid_at is None, c.valid_at))
216
+ for candidate in invalidation_candidates:
217
+ if (
218
+ candidate.valid_at is not None and resolved_edge.valid_at is not None
219
+ ) and candidate.valid_at > resolved_edge.valid_at:
220
+ # Expire new edge since we have information about more recent events
221
+ resolved_edge.invalid_at = candidate.valid_at
222
+ resolved_edge.expired_at = now
223
+ break
224
+
225
+ # Determine which contradictory edges need to be expired
226
+ invalidated_edges: list[EntityEdge] = []
227
+ for edge in invalidation_candidates:
228
+ # (Edge invalid before new edge becomes valid) or (new edge invalid before edge becomes valid)
229
+ if (
230
+ edge.invalid_at is not None
231
+ and resolved_edge.valid_at is not None
232
+ and edge.invalid_at < resolved_edge.valid_at
233
+ ) or (
234
+ edge.valid_at is not None
235
+ and resolved_edge.invalid_at is not None
236
+ and resolved_edge.invalid_at < edge.valid_at
237
+ ):
238
+ continue
239
+ # New edge invalidates edge
240
+ elif (
241
+ edge.valid_at is not None
242
+ and resolved_edge.valid_at is not None
243
+ and edge.valid_at < resolved_edge.valid_at
244
+ ):
245
+ edge.invalid_at = resolved_edge.valid_at
246
+ edge.expired_at = edge.expired_at if edge.expired_at is not None else now
247
+ invalidated_edges.append(edge)
248
+
249
+ return resolved_edge, invalidated_edges
250
+
251
+
252
+ async def dedupe_extracted_edge(
253
+ llm_client: LLMClient, extracted_edge: EntityEdge, related_edges: list[EntityEdge]
168
254
  ) -> EntityEdge:
169
255
  start = time()
170
256
 
171
257
  # Prepare context for LLM
172
- existing_edges_context = [
173
- {'uuid': edge.uuid, 'name': edge.name, 'fact': edge.fact} for edge in existing_edges
258
+ related_edges_context = [
259
+ {'uuid': edge.uuid, 'name': edge.name, 'fact': edge.fact} for edge in related_edges
174
260
  ]
175
261
 
176
262
  extracted_edge_context = {
@@ -180,7 +266,7 @@ async def resolve_extracted_edge(
180
266
  }
181
267
 
182
268
  context = {
183
- 'existing_edges': existing_edges_context,
269
+ 'related_edges': related_edges_context,
184
270
  'extracted_edges': extracted_edge_context,
185
271
  }
186
272
 
@@ -191,14 +277,14 @@ async def resolve_extracted_edge(
191
277
 
192
278
  edge = extracted_edge
193
279
  if is_duplicate:
194
- for existing_edge in existing_edges:
280
+ for existing_edge in related_edges:
195
281
  if existing_edge.uuid != uuid:
196
282
  continue
197
283
  edge = existing_edge
198
284
 
199
285
  end = time()
200
286
  logger.info(
201
- f'Resolved node: {extracted_edge.name} is {edge.name}, in {(end - start) * 1000} ms'
287
+ f'Resolved Edge: {extracted_edge.name} is {edge.name}, in {(end - start) * 1000} ms'
202
288
  )
203
289
 
204
290
  return edge
@@ -16,6 +16,7 @@ limitations under the License.
16
16
 
17
17
  import logging
18
18
  from datetime import datetime
19
+ from time import time
19
20
  from typing import List
20
21
 
21
22
  from graphiti_core.edges import EntityEdge
@@ -181,3 +182,36 @@ async def extract_edge_dates(
181
182
  logger.info(f'Edge date extraction explanation: {explanation}')
182
183
 
183
184
  return valid_at_datetime, invalid_at_datetime
185
+
186
+
187
+ async def get_edge_contradictions(
188
+ llm_client: LLMClient, new_edge: EntityEdge, existing_edges: list[EntityEdge]
189
+ ) -> list[EntityEdge]:
190
+ start = time()
191
+ existing_edge_map = {edge.uuid: edge for edge in existing_edges}
192
+
193
+ new_edge_context = {'uuid': new_edge.uuid, 'name': new_edge.name, 'fact': new_edge.fact}
194
+ existing_edge_context = [
195
+ {'uuid': existing_edge.uuid, 'name': existing_edge.name, 'fact': existing_edge.fact}
196
+ for existing_edge in existing_edges
197
+ ]
198
+
199
+ context = {'new_edge': new_edge_context, 'existing_edges': existing_edge_context}
200
+
201
+ llm_response = await llm_client.generate_response(prompt_library.invalidate_edges.v2(context))
202
+
203
+ contradicted_edge_data = llm_response.get('invalidated_edges', [])
204
+
205
+ contradicted_edges: list[EntityEdge] = []
206
+ for edge_data in contradicted_edge_data:
207
+ if edge_data['uuid'] in existing_edge_map:
208
+ contradicted_edge = existing_edge_map[edge_data['uuid']]
209
+ contradicted_edge.fact = edge_data['fact']
210
+ contradicted_edges.append(contradicted_edge)
211
+
212
+ end = time()
213
+ logger.info(
214
+ f'Found invalidated edge candidates from {new_edge.fact}, in {(end - start) * 1000} ms'
215
+ )
216
+
217
+ return contradicted_edges
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: graphiti-core
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: A temporal graph building library
5
5
  License: Apache-2.0
6
6
  Author: Paul Paliychuk
@@ -173,7 +173,7 @@ graphiti.close()
173
173
 
174
174
  ## Documentation
175
175
 
176
- - [Guides and API documentation](https://help.getzep.com/Graphiti/Graphiti).
176
+ - [Guides and API documentation](https://help.getzep.com/graphiti).
177
177
  - [Quick Start](https://help.getzep.com/graphiti/graphiti/quick-start)
178
178
  - [Building an agent with LangChain's LangGraph and Graphiti](https://help.getzep.com/graphiti/graphiti/lang-graph-agent)
179
179
 
@@ -1,6 +1,6 @@
1
1
  graphiti_core/__init__.py,sha256=e5SWFkRiaUwfprYIeIgVIh7JDedNiloZvd3roU-0aDY,55
2
2
  graphiti_core/edges.py,sha256=Sxsqw7WZAC6YJKftMaF9t69o7HV_GM6m6ULjtLhZg0M,7484
3
- graphiti_core/graphiti.py,sha256=2skhhx7pgDotUXaXadJ-YGzaZWYmOoSpjOH10PwyBj4,23374
3
+ graphiti_core/graphiti.py,sha256=hLIDjvbdvgQPPi1-HVyiQ1gw67jUdiaKqWRBZhtxqFc,23106
4
4
  graphiti_core/helpers.py,sha256=EAeC3RrcecjiTGN2vxergN5RHTy2_jhFXA5PQVT3toU,200
5
5
  graphiti_core/llm_client/__init__.py,sha256=f4OSk82jJ70wZ2HOuQu6-RQWkkf7HIB0FCT6xOuxZkQ,154
6
6
  graphiti_core/llm_client/anthropic_client.py,sha256=C8lOLm7in_eNfOP7s8gjMM0Y99-TzKWlGaPuVGceX68,2180
@@ -9,29 +9,29 @@ graphiti_core/llm_client/config.py,sha256=d1oZ9tt7QBQlbph7v-0HjItb6otK9_-IwF8kkR
9
9
  graphiti_core/llm_client/groq_client.py,sha256=qscr5-190wBTUCBL31EAjQTLytK9AF75-y9GsVRvGJU,2206
10
10
  graphiti_core/llm_client/openai_client.py,sha256=Bkrp_mKzAxK6kgPzv1UtVUgr1ZvvJhE2H39hgAwWrsI,2211
11
11
  graphiti_core/llm_client/utils.py,sha256=H8-Kwa5SyvIYDNIas8O4bHJ6jsOL49li44VoDEMyauY,555
12
- graphiti_core/nodes.py,sha256=lUSGkWs7EN88qQ1kwwun-t1SWNmTL4z8fOg1dOCqwl0,7879
12
+ graphiti_core/nodes.py,sha256=gB2HxaLHeLAo_wthSI8kRonTdz-BR_GJ4f6JMrxXd0c,8004
13
13
  graphiti_core/prompts/__init__.py,sha256=EA-x9xUki9l8wnu2l8ek_oNf75-do5tq5hVq7Zbv8Kw,101
14
- graphiti_core/prompts/dedupe_edges.py,sha256=FuZQVZlXTYjskaRUYblToLw4cFjyDp4ECrSf-Y8Z4sU,6530
14
+ graphiti_core/prompts/dedupe_edges.py,sha256=DUNHdIudj50FAjkla4nc68tSFSD2yjmYHBw-Bb7ph20,6529
15
15
  graphiti_core/prompts/dedupe_nodes.py,sha256=BZ9S-PB9SSGjc5Oo8ivdgA6rZx3OGOFhKtwrBlQ0bm0,7269
16
16
  graphiti_core/prompts/extract_edge_dates.py,sha256=G-Gnsyt8pYx9lFJEwlIsTdADF3ESDe26WSsrAGmvlYk,3086
17
17
  graphiti_core/prompts/extract_edges.py,sha256=AQ8xYbAv_RKXAT6WMwXs1_GvUdLtM_lhLNbt3SkOAmk,5348
18
18
  graphiti_core/prompts/extract_nodes.py,sha256=isYly1Yq9tpD-Dlj2JNvKMdsJUqjWMSO16ZFinFxWic,5304
19
- graphiti_core/prompts/invalidate_edges.py,sha256=-BJ5j73fDAhRJa1abs35rKYyo-_OSZYTlQNphfo5Kuk,2993
19
+ graphiti_core/prompts/invalidate_edges.py,sha256=8SHt3iPTdmqk8A52LxgdMtI39w4USKqVDMOS2i6lRQ4,4342
20
20
  graphiti_core/prompts/lib.py,sha256=RR8f8DQfioUK5bJonMzn02pKLxJlaENv1VocpvRJ488,3532
21
21
  graphiti_core/prompts/models.py,sha256=cvx_Bv5RMFUD_5IUawYrbpOKLPHogai7_bm7YXrSz84,867
22
22
  graphiti_core/search/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  graphiti_core/search/search.py,sha256=IUqAdWub2mg-j9Mz-NacJVLequsG5fxqx2SySKnQtXA,4438
24
- graphiti_core/search/search_utils.py,sha256=0tC_TEPCxB6w8uCUXwpqC27nDog-9bjBNrtwXlyFjIk,21138
24
+ graphiti_core/search/search_utils.py,sha256=MPzYTp0ybEZjDH92_1Bxwm7dz8CKHkTBcgPWDIXapg0,21135
25
25
  graphiti_core/utils/__init__.py,sha256=cJAcMnBZdHBQmWrZdU1PQ1YmaL75bhVUkyVpIPuOyns,260
26
26
  graphiti_core/utils/bulk_utils.py,sha256=xwKgHDNiGDt3-jG_YfN4vrHfG-SUxfuBnsFnBANal98,11683
27
27
  graphiti_core/utils/maintenance/__init__.py,sha256=4b9sfxqyFZMLwxxS2lnQ6_wBr3xrJRIqfAWOidK8EK0,388
28
- graphiti_core/utils/maintenance/edge_operations.py,sha256=Z9t9Rwnpjcc2obcOG6kAxeqrfQCNak54hT8do2RrITs,7201
28
+ graphiti_core/utils/maintenance/edge_operations.py,sha256=JMrMAinkGaGTzaiiCFG-HACOTnoGfJa2hhTQKhujqgM,10782
29
29
  graphiti_core/utils/maintenance/graph_data_operations.py,sha256=ggzCWezFyLC29VZBiYHvanOpSRLaPtcmbgHgcl-qHy8,5321
30
30
  graphiti_core/utils/maintenance/node_operations.py,sha256=1Iswwoqy7HDH_CQACQUq3oQKrX7cNZb1kdkSQOawj84,7956
31
- graphiti_core/utils/maintenance/temporal_operations.py,sha256=XIo3xSYQ_4LFiRDBJ-V03l2-rYeIcq6Id0O1o53yWWY,6865
31
+ graphiti_core/utils/maintenance/temporal_operations.py,sha256=BzfGDm96w4HcUEsaWTHUBt5S8dNmDQL1eX6AuBL-XFM,8135
32
32
  graphiti_core/utils/maintenance/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
33
  graphiti_core/utils/utils.py,sha256=LguHvEDi9JruXKWXXHaz2f4vpezdfgY-rpxjPq0dao8,1959
34
- graphiti_core-0.2.1.dist-info/LICENSE,sha256=KCUwCyDXuVEgmDWkozHyniRyWjnWUWjkuDHfU6o3JlA,11325
35
- graphiti_core-0.2.1.dist-info/METADATA,sha256=udtA4SQ8-GKtxObQB6dBBrR0t102mla5Dqv3MMUEO5Y,9193
36
- graphiti_core-0.2.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
37
- graphiti_core-0.2.1.dist-info/RECORD,,
34
+ graphiti_core-0.2.2.dist-info/LICENSE,sha256=KCUwCyDXuVEgmDWkozHyniRyWjnWUWjkuDHfU6o3JlA,11325
35
+ graphiti_core-0.2.2.dist-info/METADATA,sha256=HOn2oMZZFhh5Tz4v0fNPO45AbEp4muF4QXXFhZOb45o,9184
36
+ graphiti_core-0.2.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
37
+ graphiti_core-0.2.2.dist-info/RECORD,,