graphiti-core 0.18.0__py3-none-any.whl → 0.18.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of graphiti-core might be problematic. Click here for more details.

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