graphiti-core 0.3.20__py3-none-any.whl → 0.4.0__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.

@@ -1,7 +1,7 @@
1
1
  import asyncio
2
2
  import logging
3
3
  from collections import defaultdict
4
- from datetime import datetime
4
+ from datetime import datetime, timezone
5
5
 
6
6
  from neo4j import AsyncDriver
7
7
  from pydantic import BaseModel
@@ -178,7 +178,7 @@ async def build_community(
178
178
 
179
179
  summary = summaries[0]
180
180
  name = await generate_summary_description(llm_client, summary)
181
- now = datetime.now()
181
+ now = datetime.now(timezone.utc)
182
182
  community_node = CommunityNode(
183
183
  name=name,
184
184
  group_id=community_cluster[0].group_id,
@@ -305,7 +305,7 @@ async def update_community(
305
305
  community.name = new_name
306
306
 
307
307
  if is_new:
308
- community_edge = (build_community_edges([entity], community, datetime.now()))[0]
308
+ community_edge = (build_community_edges([entity], community, datetime.now(timezone.utc)))[0]
309
309
  await community_edge.save(driver)
310
310
 
311
311
  await community.generate_name_embedding(embedder)
@@ -16,11 +16,12 @@ limitations under the License.
16
16
 
17
17
  import asyncio
18
18
  import logging
19
- from datetime import datetime
19
+ from datetime import datetime, timezone
20
20
  from time import time
21
21
  from typing import List
22
22
 
23
23
  from graphiti_core.edges import CommunityEdge, EntityEdge, EpisodicEdge
24
+ from graphiti_core.helpers import MAX_REFLEXION_ITERATIONS
24
25
  from graphiti_core.llm_client import LLMClient
25
26
  from graphiti_core.nodes import CommunityNode, EntityNode, EpisodicNode
26
27
  from graphiti_core.prompts import prompt_library
@@ -77,24 +78,41 @@ async def extract_edges(
77
78
  ) -> list[EntityEdge]:
78
79
  start = time()
79
80
 
81
+ node_uuids_by_name_map = {node.name: node.uuid for node in nodes}
82
+
80
83
  # Prepare context for LLM
81
84
  context = {
82
85
  'episode_content': episode.content,
83
- 'episode_timestamp': (episode.valid_at.isoformat() if episode.valid_at else None),
84
- 'nodes': [
85
- {'uuid': node.uuid, 'name': node.name, 'summary': node.summary} for node in nodes
86
- ],
87
- 'previous_episodes': [
88
- {
89
- 'content': ep.content,
90
- 'timestamp': ep.valid_at.isoformat() if ep.valid_at else None,
91
- }
92
- for ep in previous_episodes
93
- ],
86
+ 'nodes': [node.name for node in nodes],
87
+ 'previous_episodes': [ep.content for ep in previous_episodes],
88
+ 'custom_prompt': '',
94
89
  }
95
90
 
96
- llm_response = await llm_client.generate_response(prompt_library.extract_edges.v2(context))
97
- edges_data = llm_response.get('edges', [])
91
+ facts_missed = True
92
+ reflexion_iterations = 0
93
+ while facts_missed and reflexion_iterations < MAX_REFLEXION_ITERATIONS:
94
+ llm_response = await llm_client.generate_response(
95
+ prompt_library.extract_edges.edge(context)
96
+ )
97
+ edges_data = llm_response.get('edges', [])
98
+
99
+ context['extracted_facts'] = [edge_data.get('fact', '') for edge_data in edges_data]
100
+
101
+ reflexion_iterations += 1
102
+ if reflexion_iterations < MAX_REFLEXION_ITERATIONS:
103
+ reflexion_response = await llm_client.generate_response(
104
+ prompt_library.extract_edges.reflexion(context)
105
+ )
106
+
107
+ missing_facts = reflexion_response.get('missing_facts', [])
108
+
109
+ custom_prompt = 'The following facts were missed in a previous extraction: '
110
+ for fact in missing_facts:
111
+ custom_prompt += f'\n{fact},'
112
+
113
+ context['custom_prompt'] = custom_prompt
114
+
115
+ facts_missed = len(missing_facts) != 0
98
116
 
99
117
  end = time()
100
118
  logger.debug(f'Extracted new edges: {edges_data} in {(end - start) * 1000} ms')
@@ -102,22 +120,25 @@ async def extract_edges(
102
120
  # Convert the extracted data into EntityEdge objects
103
121
  edges = []
104
122
  for edge_data in edges_data:
105
- if edge_data['target_node_uuid'] and edge_data['source_node_uuid']:
106
- edge = EntityEdge(
107
- source_node_uuid=edge_data['source_node_uuid'],
108
- target_node_uuid=edge_data['target_node_uuid'],
109
- name=edge_data['relation_type'],
110
- group_id=group_id,
111
- fact=edge_data['fact'],
112
- episodes=[episode.uuid],
113
- created_at=datetime.now(),
114
- valid_at=None,
115
- invalid_at=None,
116
- )
117
- edges.append(edge)
118
- logger.debug(
119
- f'Created new edge: {edge.name} from (UUID: {edge.source_node_uuid}) to (UUID: {edge.target_node_uuid})'
120
- )
123
+ edge = EntityEdge(
124
+ source_node_uuid=node_uuids_by_name_map.get(
125
+ edge_data.get('source_entity_name', ''), ''
126
+ ),
127
+ target_node_uuid=node_uuids_by_name_map.get(
128
+ edge_data.get('target_entity_name', ''), ''
129
+ ),
130
+ name=edge_data.get('relation_type', ''),
131
+ group_id=group_id,
132
+ fact=edge_data.get('fact', ''),
133
+ episodes=[episode.uuid],
134
+ created_at=datetime.now(timezone.utc),
135
+ valid_at=None,
136
+ invalid_at=None,
137
+ )
138
+ edges.append(edge)
139
+ logger.debug(
140
+ f'Created new edge: {edge.name} from (UUID: {edge.source_node_uuid}) to (UUID: {edge.target_node_uuid})'
141
+ )
121
142
 
122
143
  return edges
123
144
 
@@ -142,7 +163,7 @@ async def dedupe_extracted_edges(
142
163
  ],
143
164
  }
144
165
 
145
- llm_response = await llm_client.generate_response(prompt_library.dedupe_edges.v1(context))
166
+ llm_response = await llm_client.generate_response(prompt_library.dedupe_edges.edge(context))
146
167
  duplicate_data = llm_response.get('duplicates', [])
147
168
  logger.debug(f'Extracted unique edges: {duplicate_data}')
148
169
 
@@ -205,6 +226,38 @@ async def resolve_extracted_edges(
205
226
  return resolved_edges, invalidated_edges
206
227
 
207
228
 
229
+ def resolve_edge_contradictions(
230
+ resolved_edge: EntityEdge, invalidation_candidates: list[EntityEdge]
231
+ ) -> list[EntityEdge]:
232
+ # Determine which contradictory edges need to be expired
233
+ invalidated_edges: list[EntityEdge] = []
234
+ for edge in invalidation_candidates:
235
+ # (Edge invalid before new edge becomes valid) or (new edge invalid before edge becomes valid)
236
+ if (
237
+ edge.invalid_at is not None
238
+ and resolved_edge.valid_at is not None
239
+ and edge.invalid_at <= resolved_edge.valid_at
240
+ ) or (
241
+ edge.valid_at is not None
242
+ and resolved_edge.invalid_at is not None
243
+ and resolved_edge.invalid_at <= edge.valid_at
244
+ ):
245
+ continue
246
+ # New edge invalidates edge
247
+ elif (
248
+ edge.valid_at is not None
249
+ and resolved_edge.valid_at is not None
250
+ and edge.valid_at < resolved_edge.valid_at
251
+ ):
252
+ edge.invalid_at = resolved_edge.valid_at
253
+ edge.expired_at = (
254
+ edge.expired_at if edge.expired_at is not None else datetime.now(timezone.utc)
255
+ )
256
+ invalidated_edges.append(edge)
257
+
258
+ return invalidated_edges
259
+
260
+
208
261
  async def resolve_extracted_edge(
209
262
  llm_client: LLMClient,
210
263
  extracted_edge: EntityEdge,
@@ -219,7 +272,7 @@ async def resolve_extracted_edge(
219
272
  get_edge_contradictions(llm_client, extracted_edge, existing_edges),
220
273
  )
221
274
 
222
- now = datetime.now()
275
+ now = datetime.now(timezone.utc)
223
276
 
224
277
  resolved_edge.valid_at = valid_at if valid_at is not None else resolved_edge.valid_at
225
278
  resolved_edge.invalid_at = invalid_at if invalid_at is not None else resolved_edge.invalid_at
@@ -239,28 +292,7 @@ async def resolve_extracted_edge(
239
292
  break
240
293
 
241
294
  # Determine which contradictory edges need to be expired
242
- invalidated_edges: list[EntityEdge] = []
243
- for edge in invalidation_candidates:
244
- # (Edge invalid before new edge becomes valid) or (new edge invalid before edge becomes valid)
245
- if (
246
- edge.invalid_at is not None
247
- and resolved_edge.valid_at is not None
248
- and edge.invalid_at <= resolved_edge.valid_at
249
- ) or (
250
- edge.valid_at is not None
251
- and resolved_edge.invalid_at is not None
252
- and resolved_edge.invalid_at <= edge.valid_at
253
- ):
254
- continue
255
- # New edge invalidates edge
256
- elif (
257
- edge.valid_at is not None
258
- and resolved_edge.valid_at is not None
259
- and edge.valid_at < resolved_edge.valid_at
260
- ):
261
- edge.invalid_at = resolved_edge.valid_at
262
- edge.expired_at = edge.expired_at if edge.expired_at is not None else now
263
- invalidated_edges.append(edge)
295
+ invalidated_edges = resolve_edge_contradictions(resolved_edge, invalidation_candidates)
264
296
 
265
297
  return resolved_edge, invalidated_edges
266
298
 
@@ -286,7 +318,7 @@ async def dedupe_extracted_edge(
286
318
  'extracted_edges': extracted_edge_context,
287
319
  }
288
320
 
289
- llm_response = await llm_client.generate_response(prompt_library.dedupe_edges.v3(context))
321
+ llm_response = await llm_client.generate_response(prompt_library.dedupe_edges.edge(context))
290
322
 
291
323
  is_duplicate: bool = llm_response.get('is_duplicate', False)
292
324
  uuid: str | None = llm_response.get('uuid', None)
@@ -16,10 +16,10 @@ limitations under the License.
16
16
 
17
17
  import asyncio
18
18
  import logging
19
- from datetime import datetime
19
+ from datetime import datetime, timezone
20
20
  from time import time
21
- from typing import Any
22
21
 
22
+ from graphiti_core.helpers import MAX_REFLEXION_ITERATIONS
23
23
  from graphiti_core.llm_client import LLMClient
24
24
  from graphiti_core.nodes import EntityNode, EpisodeType, EpisodicNode
25
25
  from graphiti_core.prompts import prompt_library
@@ -28,65 +28,84 @@ logger = logging.getLogger(__name__)
28
28
 
29
29
 
30
30
  async def extract_message_nodes(
31
- llm_client: LLMClient, episode: EpisodicNode, previous_episodes: list[EpisodicNode]
32
- ) -> list[dict[str, Any]]:
31
+ llm_client: LLMClient,
32
+ episode: EpisodicNode,
33
+ previous_episodes: list[EpisodicNode],
34
+ custom_prompt='',
35
+ ) -> list[str]:
33
36
  # Prepare context for LLM
34
37
  context = {
35
38
  'episode_content': episode.content,
36
39
  'episode_timestamp': episode.valid_at.isoformat(),
37
- 'previous_episodes': [
38
- {
39
- 'content': ep.content,
40
- 'timestamp': ep.valid_at.isoformat(),
41
- }
42
- for ep in previous_episodes
43
- ],
40
+ 'previous_episodes': [ep.content for ep in previous_episodes],
41
+ 'custom_prompt': custom_prompt,
44
42
  }
45
43
 
46
- llm_response = await llm_client.generate_response(prompt_library.extract_nodes.v2(context))
47
- extracted_node_data = llm_response.get('extracted_nodes', [])
48
- return extracted_node_data
44
+ llm_response = await llm_client.generate_response(
45
+ prompt_library.extract_nodes.extract_message(context)
46
+ )
47
+ extracted_node_names = llm_response.get('extracted_node_names', [])
48
+ return extracted_node_names
49
49
 
50
50
 
51
51
  async def extract_text_nodes(
52
- llm_client: LLMClient, episode: EpisodicNode, previous_episodes: list[EpisodicNode]
53
- ) -> list[dict[str, Any]]:
52
+ llm_client: LLMClient,
53
+ episode: EpisodicNode,
54
+ previous_episodes: list[EpisodicNode],
55
+ custom_prompt='',
56
+ ) -> list[str]:
54
57
  # Prepare context for LLM
55
58
  context = {
56
59
  'episode_content': episode.content,
57
60
  'episode_timestamp': episode.valid_at.isoformat(),
58
- 'previous_episodes': [
59
- {
60
- 'content': ep.content,
61
- 'timestamp': ep.valid_at.isoformat(),
62
- }
63
- for ep in previous_episodes
64
- ],
61
+ 'previous_episodes': [ep.content for ep in previous_episodes],
62
+ 'custom_prompt': custom_prompt,
65
63
  }
66
64
 
67
65
  llm_response = await llm_client.generate_response(
68
66
  prompt_library.extract_nodes.extract_text(context)
69
67
  )
70
- extracted_node_data = llm_response.get('extracted_nodes', [])
71
- return extracted_node_data
68
+ extracted_node_names = llm_response.get('extracted_node_names', [])
69
+ return extracted_node_names
72
70
 
73
71
 
74
72
  async def extract_json_nodes(
75
- llm_client: LLMClient,
76
- episode: EpisodicNode,
77
- ) -> list[dict[str, Any]]:
73
+ llm_client: LLMClient, episode: EpisodicNode, custom_prompt=''
74
+ ) -> list[str]:
78
75
  # Prepare context for LLM
79
76
  context = {
80
77
  'episode_content': episode.content,
81
78
  'episode_timestamp': episode.valid_at.isoformat(),
82
79
  'source_description': episode.source_description,
80
+ 'custom_prompt': custom_prompt,
83
81
  }
84
82
 
85
83
  llm_response = await llm_client.generate_response(
86
84
  prompt_library.extract_nodes.extract_json(context)
87
85
  )
88
- extracted_node_data = llm_response.get('extracted_nodes', [])
89
- return extracted_node_data
86
+ extracted_node_names = llm_response.get('extracted_node_names', [])
87
+ return extracted_node_names
88
+
89
+
90
+ async def extract_nodes_reflexion(
91
+ llm_client: LLMClient,
92
+ episode: EpisodicNode,
93
+ previous_episodes: list[EpisodicNode],
94
+ node_names: list[str],
95
+ ) -> list[str]:
96
+ # Prepare context for LLM
97
+ context = {
98
+ 'episode_content': episode.content,
99
+ 'previous_episodes': [ep.content for ep in previous_episodes],
100
+ 'extracted_entities': node_names,
101
+ }
102
+
103
+ llm_response = await llm_client.generate_response(
104
+ prompt_library.extract_nodes.reflexion(context)
105
+ )
106
+ missed_entities = llm_response.get('missed_entities', [])
107
+
108
+ return missed_entities
90
109
 
91
110
 
92
111
  async def extract_nodes(
@@ -95,25 +114,45 @@ async def extract_nodes(
95
114
  previous_episodes: list[EpisodicNode],
96
115
  ) -> list[EntityNode]:
97
116
  start = time()
98
- extracted_node_data: list[dict[str, Any]] = []
99
- if episode.source == EpisodeType.message:
100
- extracted_node_data = await extract_message_nodes(llm_client, episode, previous_episodes)
101
- elif episode.source == EpisodeType.text:
102
- extracted_node_data = await extract_text_nodes(llm_client, episode, previous_episodes)
103
- elif episode.source == EpisodeType.json:
104
- extracted_node_data = await extract_json_nodes(llm_client, episode)
117
+ extracted_node_names: list[str] = []
118
+ custom_prompt = ''
119
+ entities_missed = True
120
+ reflexion_iterations = 0
121
+ while entities_missed and reflexion_iterations < MAX_REFLEXION_ITERATIONS:
122
+ if episode.source == EpisodeType.message:
123
+ extracted_node_names = await extract_message_nodes(
124
+ llm_client, episode, previous_episodes, custom_prompt
125
+ )
126
+ elif episode.source == EpisodeType.text:
127
+ extracted_node_names = await extract_text_nodes(
128
+ llm_client, episode, previous_episodes, custom_prompt
129
+ )
130
+ elif episode.source == EpisodeType.json:
131
+ extracted_node_names = await extract_json_nodes(llm_client, episode, custom_prompt)
132
+
133
+ reflexion_iterations += 1
134
+ if reflexion_iterations < MAX_REFLEXION_ITERATIONS:
135
+ missing_entities = await extract_nodes_reflexion(
136
+ llm_client, episode, previous_episodes, extracted_node_names
137
+ )
138
+
139
+ entities_missed = len(missing_entities) != 0
140
+
141
+ custom_prompt = 'The following entities were missed in a previous extraction: '
142
+ for entity in missing_entities:
143
+ custom_prompt += f'\n{entity},'
105
144
 
106
145
  end = time()
107
- logger.debug(f'Extracted new nodes: {extracted_node_data} in {(end - start) * 1000} ms')
146
+ logger.debug(f'Extracted new nodes: {extracted_node_names} in {(end - start) * 1000} ms')
108
147
  # Convert the extracted data into EntityNode objects
109
148
  new_nodes = []
110
- for node_data in extracted_node_data:
149
+ for name in extracted_node_names:
111
150
  new_node = EntityNode(
112
- name=node_data['name'],
151
+ name=name,
113
152
  group_id=episode.group_id,
114
- labels=node_data['labels'],
115
- summary=node_data['summary'],
116
- created_at=datetime.now(),
153
+ labels=['Entity'],
154
+ summary='',
155
+ created_at=datetime.now(timezone.utc),
117
156
  )
118
157
  new_nodes.append(new_node)
119
158
  logger.debug(f'Created new node: {new_node.name} (UUID: {new_node.uuid})')
@@ -147,7 +186,7 @@ async def dedupe_extracted_nodes(
147
186
  'extracted_nodes': extracted_nodes_context,
148
187
  }
149
188
 
150
- llm_response = await llm_client.generate_response(prompt_library.dedupe_nodes.v2(context))
189
+ llm_response = await llm_client.generate_response(prompt_library.dedupe_nodes.node(context))
151
190
 
152
191
  duplicate_data = llm_response.get('duplicates', [])
153
192
 
@@ -175,13 +214,17 @@ async def resolve_extracted_nodes(
175
214
  llm_client: LLMClient,
176
215
  extracted_nodes: list[EntityNode],
177
216
  existing_nodes_lists: list[list[EntityNode]],
217
+ episode: EpisodicNode | None = None,
218
+ previous_episodes: list[EpisodicNode] | None = None,
178
219
  ) -> tuple[list[EntityNode], dict[str, str]]:
179
220
  uuid_map: dict[str, str] = {}
180
221
  resolved_nodes: list[EntityNode] = []
181
222
  results: list[tuple[EntityNode, dict[str, str]]] = list(
182
223
  await asyncio.gather(
183
224
  *[
184
- resolve_extracted_node(llm_client, extracted_node, existing_nodes)
225
+ resolve_extracted_node(
226
+ llm_client, extracted_node, existing_nodes, episode, previous_episodes
227
+ )
185
228
  for extracted_node, existing_nodes in zip(extracted_nodes, existing_nodes_lists)
186
229
  ]
187
230
  )
@@ -195,14 +238,16 @@ async def resolve_extracted_nodes(
195
238
 
196
239
 
197
240
  async def resolve_extracted_node(
198
- llm_client: LLMClient, extracted_node: EntityNode, existing_nodes: list[EntityNode]
241
+ llm_client: LLMClient,
242
+ extracted_node: EntityNode,
243
+ existing_nodes: list[EntityNode],
244
+ episode: EpisodicNode | None = None,
245
+ previous_episodes: list[EpisodicNode] | None = None,
199
246
  ) -> tuple[EntityNode, dict[str, str]]:
200
247
  start = time()
201
248
 
202
249
  # Prepare context for LLM
203
- existing_nodes_context = [
204
- {'uuid': node.uuid, 'name': node.name, 'summary': node.summary} for node in existing_nodes
205
- ]
250
+ existing_nodes_context = [{'uuid': node.uuid, 'name': node.name} for node in existing_nodes]
206
251
 
207
252
  extracted_node_context = {
208
253
  'uuid': extracted_node.uuid,
@@ -213,13 +258,32 @@ async def resolve_extracted_node(
213
258
  context = {
214
259
  'existing_nodes': existing_nodes_context,
215
260
  'extracted_nodes': extracted_node_context,
261
+ 'episode_content': episode.content if episode is not None else '',
262
+ 'previous_episodes': [ep.content for ep in previous_episodes]
263
+ if previous_episodes is not None
264
+ else [],
216
265
  }
217
266
 
218
- llm_response = await llm_client.generate_response(prompt_library.dedupe_nodes.v3(context))
267
+ summary_context = {
268
+ 'node_name': extracted_node.name,
269
+ 'episode_content': episode.content if episode is not None else '',
270
+ 'previous_episodes': [ep.content for ep in previous_episodes]
271
+ if previous_episodes is not None
272
+ else [],
273
+ }
274
+
275
+ llm_response, node_summary_response = await asyncio.gather(
276
+ llm_client.generate_response(prompt_library.dedupe_nodes.node(context)),
277
+ llm_client.generate_response(
278
+ prompt_library.summarize_nodes.summarize_context(summary_context)
279
+ ),
280
+ )
281
+
282
+ extracted_node.summary = node_summary_response.get('summary', '')
219
283
 
220
284
  is_duplicate: bool = llm_response.get('is_duplicate', False)
221
285
  uuid: str | None = llm_response.get('uuid', None)
222
- summary = llm_response.get('summary', '')
286
+ name = llm_response.get('name', '')
223
287
 
224
288
  node = extracted_node
225
289
  uuid_map: dict[str, str] = {}
@@ -227,8 +291,14 @@ async def resolve_extracted_node(
227
291
  for existing_node in existing_nodes:
228
292
  if existing_node.uuid != uuid:
229
293
  continue
294
+ summary_response = await llm_client.generate_response(
295
+ prompt_library.summarize_nodes.summarize_pair(
296
+ {'node_summaries': [extracted_node.summary, existing_node.summary]}
297
+ )
298
+ )
230
299
  node = existing_node
231
- node.summary = summary
300
+ node.name = name
301
+ node.summary = summary_response.get('summary', '')
232
302
  uuid_map[extracted_node.uuid] = existing_node.uuid
233
303
 
234
304
  end = time()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: graphiti-core
3
- Version: 0.3.20
3
+ Version: 0.4.0
4
4
  Summary: A temporal graph building library
5
5
  License: Apache-2.0
6
6
  Author: Paul Paliychuk
@@ -14,9 +14,10 @@ Classifier: Programming Language :: Python :: 3.12
14
14
  Requires-Dist: diskcache (>=5.6.3,<6.0.0)
15
15
  Requires-Dist: neo4j (>=5.23.0,<6.0.0)
16
16
  Requires-Dist: numpy (>=1.0.0)
17
- Requires-Dist: openai (>=1.52.2,<2.0.0)
17
+ Requires-Dist: openai (>=1.53.0,<2.0.0)
18
18
  Requires-Dist: pydantic (>=2.8.2,<3.0.0)
19
- Requires-Dist: tenacity (<9.0.0)
19
+ Requires-Dist: python-dotenv (>=1.0.1,<2.0.0)
20
+ Requires-Dist: tenacity (==9.0.0)
20
21
  Description-Content-Type: text/markdown
21
22
 
22
23
  <div align="center">
@@ -129,7 +130,7 @@ poetry add graphiti-core
129
130
  ```python
130
131
  from graphiti_core import Graphiti
131
132
  from graphiti_core.nodes import EpisodeType
132
- from datetime import datetime
133
+ from datetime import datetime, timezone
133
134
 
134
135
  # Initialize Graphiti
135
136
  graphiti = Graphiti("bolt://localhost:7687", "neo4j", "password")
@@ -149,7 +150,7 @@ for i, episode in enumerate(episodes):
149
150
  episode_body=episode,
150
151
  source=EpisodeType.text,
151
152
  source_description="podcast",
152
- reference_time=datetime.now()
153
+ reference_time=datetime.now(timezone.utc)
153
154
  )
154
155
 
155
156
  # Search the graph
@@ -2,15 +2,15 @@ graphiti_core/__init__.py,sha256=e5SWFkRiaUwfprYIeIgVIh7JDedNiloZvd3roU-0aDY,55
2
2
  graphiti_core/cross_encoder/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  graphiti_core/cross_encoder/bge_reranker_client.py,sha256=jsXBUHfFpGsNASHaRnfz1_miQ3x070DdU8QS4J3DciI,1466
4
4
  graphiti_core/cross_encoder/client.py,sha256=PyFYYsALQAD9wu0gL5uquPsulmaBZ0AZkJmLq2DFA-c,1472
5
- graphiti_core/cross_encoder/openai_reranker_client.py,sha256=ij1E1Y5G9GNP3h3h8nSUF-ZJrQ921B54uudZUsCUaDc,4063
5
+ graphiti_core/cross_encoder/openai_reranker_client.py,sha256=F0S9ksusyxdlcRp4yRJuCyAEg-YqgdCwXjZbH8L-Xxo,4063
6
6
  graphiti_core/edges.py,sha256=wKXmNXtu1deT5MfcqUNGHQlBLgwt3MJ_3j35ibNbpi8,13087
7
7
  graphiti_core/embedder/__init__.py,sha256=eWd-0sPxflnYXLoWNT9sxwCIFun5JNO9Fk4E-ZXXf8Y,164
8
8
  graphiti_core/embedder/client.py,sha256=gVr_xdN-d0UQfeE4Nyoa4pL3M8UxllNH4eFqmarxwys,1011
9
9
  graphiti_core/embedder/openai.py,sha256=yYUYPymx_lBlxDTGrlc03yNhPFyGG-etM2sszRK2G2U,1618
10
10
  graphiti_core/embedder/voyage.py,sha256=_eGFI5_NjNG8z7qG3jTWCdE7sAs1Yb8fiSZSJlQLD9o,1879
11
11
  graphiti_core/errors.py,sha256=ddHrHGQxhwkVAtSph4AV84UoOlgwZufMczXPwB7uqPo,1795
12
- graphiti_core/graphiti.py,sha256=MIwkprC9L9FVj7cGrAzrNbpgkpPtUPPofLlfCrtXLHU,27584
13
- graphiti_core/helpers.py,sha256=6N5nNgyXiHWsMXHxm9M_S6Og7_TuejE5GuyV6EtZgVw,2178
12
+ graphiti_core/graphiti.py,sha256=-dJUa_spL0yRbGCUqWaAtZ1hlA2FaIzoHcIMT57KQYY,26919
13
+ graphiti_core/helpers.py,sha256=0kkfmswLxRek27WpeUolTBNyyu3_XQwl8xOLRSkAwqk,2207
14
14
  graphiti_core/llm_client/__init__.py,sha256=PA80TSMeX-sUXITXEAxMDEt3gtfZgcJrGJUcyds1mSo,207
15
15
  graphiti_core/llm_client/anthropic_client.py,sha256=4l2PbCjIoeRr7UJ2DUh2grYLTtE2vNaWlo72IIRQDeI,2405
16
16
  graphiti_core/llm_client/client.py,sha256=WAnX0e4EuCFHXdFHeq_O1HZsW1STSByvDCFUHMAHEFU,3394
@@ -21,37 +21,37 @@ graphiti_core/llm_client/openai_client.py,sha256=xLkbpusRVFRK0zPr3kOqY31HK_XCXrp
21
21
  graphiti_core/llm_client/utils.py,sha256=zKpxXEbKa369m4W7RDEf-m56kH46V1Mx3RowcWZEWWs,1000
22
22
  graphiti_core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  graphiti_core/models/edges/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
- graphiti_core/models/edges/edge_db_queries.py,sha256=0T1OSSQeP63OjoMTrscqVhq0gNRRxh3-n1sNxo0kTJE,2102
24
+ graphiti_core/models/edges/edge_db_queries.py,sha256=2UoLkmazO-FJYqjc3g0LuL-pyjekzQxxed_XHVv_HZE,2671
25
25
  graphiti_core/models/nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
- graphiti_core/models/nodes/node_db_queries.py,sha256=zIEKhodGOEYQ2ZdH766t-QRSk2xZNmtmAaPv60pvepM,1688
27
- graphiti_core/nodes.py,sha256=xlObDAC6Osp6L0wwqREuzBlIFlOzuUs9lN3ZpIv4seM,13844
26
+ graphiti_core/models/nodes/node_db_queries.py,sha256=I0top_N23FN0U5ZbypaS5IXvtfx2zgJmKUCT_7mpUdo,2257
27
+ graphiti_core/nodes.py,sha256=sIEa8QzF2w-qlxUakISoVb8oRsntc4xDU90q0kO9_pk,13866
28
28
  graphiti_core/prompts/__init__.py,sha256=EA-x9xUki9l8wnu2l8ek_oNf75-do5tq5hVq7Zbv8Kw,101
29
- graphiti_core/prompts/dedupe_edges.py,sha256=DUNHdIudj50FAjkla4nc68tSFSD2yjmYHBw-Bb7ph20,6529
30
- graphiti_core/prompts/dedupe_nodes.py,sha256=BZ9S-PB9SSGjc5Oo8ivdgA6rZx3OGOFhKtwrBlQ0bm0,7269
29
+ graphiti_core/prompts/dedupe_edges.py,sha256=7jff4x60p6x8K1Oy7EN8twdOvsTt67_SwoF-lvf2c-8,3539
30
+ graphiti_core/prompts/dedupe_nodes.py,sha256=_2zD0VcygH1Sut3FwRrKSBALFaxhnnO3JOFJkwHhEVo,4130
31
31
  graphiti_core/prompts/eval.py,sha256=9gavc4SKAPdsrhpN8NEUTc632erkaifyOf0hevmdeKY,3657
32
- graphiti_core/prompts/extract_edge_dates.py,sha256=pb5Oe5WTZ468REmWNR2NAEHHYMt5GpiJVUAqpVI3aBI,3622
33
- graphiti_core/prompts/extract_edges.py,sha256=pGmYcl1zKIuu-HmHUkbkThJ5QKED3efMtDlKoT0wBRM,5448
34
- graphiti_core/prompts/extract_nodes.py,sha256=VIr0Nh0mSiodI3iGOQFszh7DOni4mufOKJDuGkMysl8,6889
32
+ graphiti_core/prompts/extract_edge_dates.py,sha256=fg5CPofLIuPR6x15ED4vwZQeRMcdfNrr1GDy_GTnfxg,3785
33
+ graphiti_core/prompts/extract_edges.py,sha256=6dVIKDxht5eQ3jlCRojUj0fZKtF_w-1ONmSlrpZ25uM,3461
34
+ graphiti_core/prompts/extract_nodes.py,sha256=OJ9zzU3VuuQXloNxDnn_BDB9z2lzpy8dL5O02ODNpzI,5494
35
35
  graphiti_core/prompts/invalidate_edges.py,sha256=2vhi9TsL9poAHqApfk_Us0VveG0-T8cZymfBwOgA8tc,4341
36
36
  graphiti_core/prompts/lib.py,sha256=ZOE6nNoI_wQ12Sufx7rQkQtkIm_eTAL7pCiYGU2hcMI,4054
37
37
  graphiti_core/prompts/models.py,sha256=cvx_Bv5RMFUD_5IUawYrbpOKLPHogai7_bm7YXrSz84,867
38
- graphiti_core/prompts/summarize_nodes.py,sha256=FLuZpGTABgcxuIDkx_IKH115nHEw0rIaFhcGlWveAMc,2357
38
+ graphiti_core/prompts/summarize_nodes.py,sha256=lNpagCq6Pz71vipBonQZUxBZG6OY4bQbyGZ5UEgdL4s,3488
39
39
  graphiti_core/py.typed,sha256=vlmmzQOt7bmeQl9L3XJP4W6Ry0iiELepnOrinKz5KQg,79
40
40
  graphiti_core/search/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
- graphiti_core/search/search.py,sha256=Fz45sXToQ6KN_y2k94kg9UyUZeqsS14jfaGfQ5KGifQ,11176
41
+ graphiti_core/search/search.py,sha256=qkiAZCYhKhTkzyGuSH9j_W2Kz60TGyhLM1hZNCaCeUs,11996
42
42
  graphiti_core/search/search_config.py,sha256=UZN8jFA4pBlw2O5N1cuhVRBdTwMLR9N3Oyo6sQ4MDVw,3117
43
- graphiti_core/search/search_config_recipes.py,sha256=20jS7veJExDnXA-ovJSUJfyDHKt7GW-nng-eoiT7ATA,5810
44
- graphiti_core/search/search_utils.py,sha256=gqBlhnb75srhymQ4_yzb7o019UxtE3e7eHHK9NL6nCc,22916
43
+ graphiti_core/search/search_config_recipes.py,sha256=yUqiLnn9vFg39M8eVwjVKfBCL_ptGrfDMQ47m_Blb0g,6885
44
+ graphiti_core/search/search_utils.py,sha256=8Ro90L7fqtw5tsdusIhS_pdYcLX2JVIDiH5dD04G7ug,22968
45
45
  graphiti_core/utils/__init__.py,sha256=cJAcMnBZdHBQmWrZdU1PQ1YmaL75bhVUkyVpIPuOyns,260
46
- graphiti_core/utils/bulk_utils.py,sha256=azn8xlUT9u5-CFDkLSAY4y5zt5uvOvKBzatZZKDvp1g,13994
46
+ graphiti_core/utils/bulk_utils.py,sha256=AMi42deCXjIIgdpLEiD5SYCMZfX8XKoOsVYEwXVTX5A,14016
47
47
  graphiti_core/utils/maintenance/__init__.py,sha256=TRY3wWWu5kn3Oahk_KKhltrWnh0NACw0FskjqF6OtlA,314
48
- graphiti_core/utils/maintenance/community_operations.py,sha256=Ltx8cQJcFDXcQs17lHDkh2r3qmJalzpRdA3Azl2U3m8,9804
49
- graphiti_core/utils/maintenance/edge_operations.py,sha256=htG9h32u0O1g7ZdR-jFSHuzDgiO4MChYJsNuvrRMTu8,11270
48
+ graphiti_core/utils/maintenance/community_operations.py,sha256=a2ICCNwEognGx2oBL43Y6R4nNZYo-6Siia2kDVlwu_U,9838
49
+ graphiti_core/utils/maintenance/edge_operations.py,sha256=3VkyKfMPwH0ziCQRRsnmnCZXMSfOh2vFhBJSL3JEljU,12387
50
50
  graphiti_core/utils/maintenance/graph_data_operations.py,sha256=w66_SLlvPapuG91YGGfR3bG2sM6cJ2XPHIaxM0slAdE,6526
51
- graphiti_core/utils/maintenance/node_operations.py,sha256=h5nlRojbXOGJs-alpv6z6WnZ1UCixVGlAQYBQUqz8Bs,9030
51
+ graphiti_core/utils/maintenance/node_operations.py,sha256=m0q-PPCI5LM0dZMkrcyUENw6WfDBMO61DQ7S6MuNQ8E,11624
52
52
  graphiti_core/utils/maintenance/temporal_operations.py,sha256=MvaRLWrBlDeYw8CQrKish1xbYcY5ovpfdqA2hSX7v5k,3367
53
53
  graphiti_core/utils/maintenance/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
- graphiti_core-0.3.20.dist-info/LICENSE,sha256=KCUwCyDXuVEgmDWkozHyniRyWjnWUWjkuDHfU6o3JlA,11325
55
- graphiti_core-0.3.20.dist-info/METADATA,sha256=8blBjJ96r7tU5KvPdExNKHuIgknP-EGuGlXPHW1UlPk,9990
56
- graphiti_core-0.3.20.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
57
- graphiti_core-0.3.20.dist-info/RECORD,,
54
+ graphiti_core-0.4.0.dist-info/LICENSE,sha256=KCUwCyDXuVEgmDWkozHyniRyWjnWUWjkuDHfU6o3JlA,11325
55
+ graphiti_core-0.4.0.dist-info/METADATA,sha256=URZ0gW5i_Q9aOe5M60tsxFsYbAwqoW7HJc8oVyrzuFk,10058
56
+ graphiti_core-0.4.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
57
+ graphiti_core-0.4.0.dist-info/RECORD,,