graphiti-core 0.4.2__py3-none-any.whl → 0.5.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.

Files changed (41) hide show
  1. graphiti_core/cross_encoder/bge_reranker_client.py +1 -2
  2. graphiti_core/cross_encoder/client.py +3 -4
  3. graphiti_core/cross_encoder/openai_reranker_client.py +2 -2
  4. graphiti_core/edges.py +56 -7
  5. graphiti_core/embedder/client.py +3 -3
  6. graphiti_core/embedder/openai.py +2 -2
  7. graphiti_core/embedder/voyage.py +3 -3
  8. graphiti_core/graphiti.py +39 -37
  9. graphiti_core/helpers.py +26 -0
  10. graphiti_core/llm_client/anthropic_client.py +4 -1
  11. graphiti_core/llm_client/client.py +45 -5
  12. graphiti_core/llm_client/errors.py +8 -0
  13. graphiti_core/llm_client/groq_client.py +4 -1
  14. graphiti_core/llm_client/openai_client.py +71 -7
  15. graphiti_core/llm_client/openai_generic_client.py +163 -0
  16. graphiti_core/nodes.py +58 -8
  17. graphiti_core/prompts/dedupe_edges.py +20 -17
  18. graphiti_core/prompts/dedupe_nodes.py +15 -1
  19. graphiti_core/prompts/eval.py +17 -14
  20. graphiti_core/prompts/extract_edge_dates.py +15 -7
  21. graphiti_core/prompts/extract_edges.py +18 -19
  22. graphiti_core/prompts/extract_nodes.py +11 -21
  23. graphiti_core/prompts/invalidate_edges.py +13 -25
  24. graphiti_core/prompts/lib.py +5 -1
  25. graphiti_core/prompts/prompt_helpers.py +1 -0
  26. graphiti_core/prompts/summarize_nodes.py +17 -16
  27. graphiti_core/search/search.py +5 -5
  28. graphiti_core/search/search_utils.py +55 -14
  29. graphiti_core/utils/__init__.py +0 -15
  30. graphiti_core/utils/bulk_utils.py +22 -15
  31. graphiti_core/utils/datetime_utils.py +42 -0
  32. graphiti_core/utils/maintenance/community_operations.py +13 -9
  33. graphiti_core/utils/maintenance/edge_operations.py +32 -26
  34. graphiti_core/utils/maintenance/graph_data_operations.py +3 -4
  35. graphiti_core/utils/maintenance/node_operations.py +19 -13
  36. graphiti_core/utils/maintenance/temporal_operations.py +17 -9
  37. {graphiti_core-0.4.2.dist-info → graphiti_core-0.5.0.dist-info}/METADATA +1 -1
  38. graphiti_core-0.5.0.dist-info/RECORD +60 -0
  39. graphiti_core-0.4.2.dist-info/RECORD +0 -57
  40. {graphiti_core-0.4.2.dist-info → graphiti_core-0.5.0.dist-info}/LICENSE +0 -0
  41. {graphiti_core-0.4.2.dist-info → graphiti_core-0.5.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,42 @@
1
+ """
2
+ Copyright 2024, Zep Software, Inc.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ """
16
+
17
+ from datetime import datetime, timezone
18
+
19
+
20
+ def utc_now() -> datetime:
21
+ """Returns the current UTC datetime with timezone information."""
22
+ return datetime.now(timezone.utc)
23
+
24
+
25
+ def ensure_utc(dt: datetime | None) -> datetime | None:
26
+ """
27
+ Ensures a datetime is timezone-aware and in UTC.
28
+ If the datetime is naive (no timezone), assumes it's in UTC.
29
+ If the datetime has a different timezone, converts it to UTC.
30
+ Returns None if input is None.
31
+ """
32
+ if dt is None:
33
+ return None
34
+
35
+ if dt.tzinfo is None:
36
+ # If datetime is naive, assume it's UTC
37
+ return dt.replace(tzinfo=timezone.utc)
38
+ elif dt.tzinfo != timezone.utc:
39
+ # If datetime has a different timezone, convert to UTC
40
+ return dt.astimezone(timezone.utc)
41
+
42
+ return dt
@@ -1,14 +1,13 @@
1
1
  import asyncio
2
2
  import logging
3
3
  from collections import defaultdict
4
- from datetime import datetime, timezone
5
4
 
6
5
  from neo4j import AsyncDriver
7
6
  from pydantic import BaseModel
8
7
 
9
8
  from graphiti_core.edges import CommunityEdge
10
9
  from graphiti_core.embedder import EmbedderClient
11
- from graphiti_core.helpers import DEFAULT_DATABASE
10
+ from graphiti_core.helpers import DEFAULT_DATABASE, semaphore_gather
12
11
  from graphiti_core.llm_client import LLMClient
13
12
  from graphiti_core.nodes import (
14
13
  CommunityNode,
@@ -16,6 +15,8 @@ from graphiti_core.nodes import (
16
15
  get_community_node_from_record,
17
16
  )
18
17
  from graphiti_core.prompts import prompt_library
18
+ from graphiti_core.prompts.summarize_nodes import Summary, SummaryDescription
19
+ from graphiti_core.utils.datetime_utils import utc_now
19
20
  from graphiti_core.utils.maintenance.edge_operations import build_community_edges
20
21
 
21
22
  MAX_COMMUNITY_BUILD_CONCURRENCY = 10
@@ -70,7 +71,7 @@ async def get_community_clusters(
70
71
 
71
72
  community_clusters.extend(
72
73
  list(
73
- await asyncio.gather(
74
+ await semaphore_gather(
74
75
  *[EntityNode.get_by_uuids(driver, cluster) for cluster in cluster_uuids]
75
76
  )
76
77
  )
@@ -131,7 +132,7 @@ async def summarize_pair(llm_client: LLMClient, summary_pair: tuple[str, str]) -
131
132
  context = {'node_summaries': [{'summary': summary} for summary in summary_pair]}
132
133
 
133
134
  llm_response = await llm_client.generate_response(
134
- prompt_library.summarize_nodes.summarize_pair(context)
135
+ prompt_library.summarize_nodes.summarize_pair(context), response_model=Summary
135
136
  )
136
137
 
137
138
  pair_summary = llm_response.get('summary', '')
@@ -143,7 +144,8 @@ async def generate_summary_description(llm_client: LLMClient, summary: str) -> s
143
144
  context = {'summary': summary}
144
145
 
145
146
  llm_response = await llm_client.generate_response(
146
- prompt_library.summarize_nodes.summary_description(context)
147
+ prompt_library.summarize_nodes.summary_description(context),
148
+ response_model=SummaryDescription,
147
149
  )
148
150
 
149
151
  description = llm_response.get('description', '')
@@ -162,7 +164,7 @@ async def build_community(
162
164
  odd_one_out = summaries.pop()
163
165
  length -= 1
164
166
  new_summaries: list[str] = list(
165
- await asyncio.gather(
167
+ await semaphore_gather(
166
168
  *[
167
169
  summarize_pair(llm_client, (str(left_summary), str(right_summary)))
168
170
  for left_summary, right_summary in zip(
@@ -178,7 +180,7 @@ async def build_community(
178
180
 
179
181
  summary = summaries[0]
180
182
  name = await generate_summary_description(llm_client, summary)
181
- now = datetime.now(timezone.utc)
183
+ now = utc_now()
182
184
  community_node = CommunityNode(
183
185
  name=name,
184
186
  group_id=community_cluster[0].group_id,
@@ -205,7 +207,9 @@ async def build_communities(
205
207
  return await build_community(llm_client, cluster)
206
208
 
207
209
  communities: list[tuple[CommunityNode, list[CommunityEdge]]] = list(
208
- await asyncio.gather(*[limited_build_community(cluster) for cluster in community_clusters])
210
+ await semaphore_gather(
211
+ *[limited_build_community(cluster) for cluster in community_clusters]
212
+ )
209
213
  )
210
214
 
211
215
  community_nodes: list[CommunityNode] = []
@@ -305,7 +309,7 @@ async def update_community(
305
309
  community.name = new_name
306
310
 
307
311
  if is_new:
308
- community_edge = (build_community_edges([entity], community, datetime.now(timezone.utc)))[0]
312
+ community_edge = (build_community_edges([entity], community, utc_now()))[0]
309
313
  await community_edge.save(driver)
310
314
 
311
315
  await community.generate_name_embedding(embedder)
@@ -14,17 +14,18 @@ See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  """
16
16
 
17
- import asyncio
18
17
  import logging
19
- from datetime import datetime, timezone
18
+ from datetime import datetime
20
19
  from time import time
21
- from typing import List
22
20
 
23
21
  from graphiti_core.edges import CommunityEdge, EntityEdge, EpisodicEdge
24
- from graphiti_core.helpers import MAX_REFLEXION_ITERATIONS
22
+ from graphiti_core.helpers import MAX_REFLEXION_ITERATIONS, semaphore_gather
25
23
  from graphiti_core.llm_client import LLMClient
26
24
  from graphiti_core.nodes import CommunityNode, EntityNode, EpisodicNode
27
25
  from graphiti_core.prompts import prompt_library
26
+ from graphiti_core.prompts.dedupe_edges import EdgeDuplicate, UniqueFacts
27
+ from graphiti_core.prompts.extract_edges import ExtractedEdges, MissingFacts
28
+ from graphiti_core.utils.datetime_utils import utc_now
28
29
  from graphiti_core.utils.maintenance.temporal_operations import (
29
30
  extract_edge_dates,
30
31
  get_edge_contradictions,
@@ -34,11 +35,11 @@ logger = logging.getLogger(__name__)
34
35
 
35
36
 
36
37
  def build_episodic_edges(
37
- entity_nodes: List[EntityNode],
38
+ entity_nodes: list[EntityNode],
38
39
  episode: EpisodicNode,
39
40
  created_at: datetime,
40
- ) -> List[EpisodicEdge]:
41
- edges: List[EpisodicEdge] = [
41
+ ) -> list[EpisodicEdge]:
42
+ edges: list[EpisodicEdge] = [
42
43
  EpisodicEdge(
43
44
  source_node_uuid=episode.uuid,
44
45
  target_node_uuid=node.uuid,
@@ -52,11 +53,11 @@ def build_episodic_edges(
52
53
 
53
54
 
54
55
  def build_community_edges(
55
- entity_nodes: List[EntityNode],
56
+ entity_nodes: list[EntityNode],
56
57
  community_node: CommunityNode,
57
58
  created_at: datetime,
58
- ) -> List[CommunityEdge]:
59
- edges: List[CommunityEdge] = [
59
+ ) -> list[CommunityEdge]:
60
+ edges: list[CommunityEdge] = [
60
61
  CommunityEdge(
61
62
  source_node_uuid=community_node.uuid,
62
63
  target_node_uuid=node.uuid,
@@ -92,7 +93,7 @@ async def extract_edges(
92
93
  reflexion_iterations = 0
93
94
  while facts_missed and reflexion_iterations < MAX_REFLEXION_ITERATIONS:
94
95
  llm_response = await llm_client.generate_response(
95
- prompt_library.extract_edges.edge(context)
96
+ prompt_library.extract_edges.edge(context), response_model=ExtractedEdges
96
97
  )
97
98
  edges_data = llm_response.get('edges', [])
98
99
 
@@ -101,7 +102,7 @@ async def extract_edges(
101
102
  reflexion_iterations += 1
102
103
  if reflexion_iterations < MAX_REFLEXION_ITERATIONS:
103
104
  reflexion_response = await llm_client.generate_response(
104
- prompt_library.extract_edges.reflexion(context)
105
+ prompt_library.extract_edges.reflexion(context), response_model=MissingFacts
105
106
  )
106
107
 
107
108
  missing_facts = reflexion_response.get('missing_facts', [])
@@ -131,7 +132,7 @@ async def extract_edges(
131
132
  group_id=group_id,
132
133
  fact=edge_data.get('fact', ''),
133
134
  episodes=[episode.uuid],
134
- created_at=datetime.now(timezone.utc),
135
+ created_at=utc_now(),
135
136
  valid_at=None,
136
137
  invalid_at=None,
137
138
  )
@@ -197,7 +198,7 @@ async def resolve_extracted_edges(
197
198
  ) -> tuple[list[EntityEdge], list[EntityEdge]]:
198
199
  # resolve edges with related edges in the graph, extract temporal information, and find invalidation candidates
199
200
  results: list[tuple[EntityEdge, list[EntityEdge]]] = list(
200
- await asyncio.gather(
201
+ await semaphore_gather(
201
202
  *[
202
203
  resolve_extracted_edge(
203
204
  llm_client,
@@ -250,9 +251,7 @@ def resolve_edge_contradictions(
250
251
  and edge.valid_at < resolved_edge.valid_at
251
252
  ):
252
253
  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
- )
254
+ edge.expired_at = edge.expired_at if edge.expired_at is not None else utc_now()
256
255
  invalidated_edges.append(edge)
257
256
 
258
257
  return invalidated_edges
@@ -266,17 +265,18 @@ async def resolve_extracted_edge(
266
265
  current_episode: EpisodicNode,
267
266
  previous_episodes: list[EpisodicNode],
268
267
  ) -> tuple[EntityEdge, list[EntityEdge]]:
269
- resolved_edge, (valid_at, invalid_at), invalidation_candidates = await asyncio.gather(
268
+ resolved_edge, (valid_at, invalid_at), invalidation_candidates = await semaphore_gather(
270
269
  dedupe_extracted_edge(llm_client, extracted_edge, related_edges),
271
270
  extract_edge_dates(llm_client, extracted_edge, current_episode, previous_episodes),
272
271
  get_edge_contradictions(llm_client, extracted_edge, existing_edges),
273
272
  )
274
273
 
275
- now = datetime.now(timezone.utc)
274
+ now = utc_now()
275
+
276
+ resolved_edge.valid_at = valid_at if valid_at else resolved_edge.valid_at
277
+ resolved_edge.invalid_at = invalid_at if invalid_at else resolved_edge.invalid_at
276
278
 
277
- resolved_edge.valid_at = valid_at if valid_at is not None else resolved_edge.valid_at
278
- resolved_edge.invalid_at = invalid_at if invalid_at is not None else resolved_edge.invalid_at
279
- if invalid_at is not None and resolved_edge.expired_at is None:
279
+ if invalid_at and not resolved_edge.expired_at:
280
280
  resolved_edge.expired_at = now
281
281
 
282
282
  # Determine if the new_edge needs to be expired
@@ -284,8 +284,12 @@ async def resolve_extracted_edge(
284
284
  invalidation_candidates.sort(key=lambda c: (c.valid_at is None, c.valid_at))
285
285
  for candidate in invalidation_candidates:
286
286
  if (
287
- candidate.valid_at is not None and resolved_edge.valid_at is not None
288
- ) and candidate.valid_at > resolved_edge.valid_at:
287
+ candidate.valid_at
288
+ and resolved_edge.valid_at
289
+ and candidate.valid_at.tzinfo
290
+ and resolved_edge.valid_at.tzinfo
291
+ and candidate.valid_at > resolved_edge.valid_at
292
+ ):
289
293
  # Expire new edge since we have information about more recent events
290
294
  resolved_edge.invalid_at = candidate.valid_at
291
295
  resolved_edge.expired_at = now
@@ -318,7 +322,9 @@ async def dedupe_extracted_edge(
318
322
  'extracted_edges': extracted_edge_context,
319
323
  }
320
324
 
321
- llm_response = await llm_client.generate_response(prompt_library.dedupe_edges.edge(context))
325
+ llm_response = await llm_client.generate_response(
326
+ prompt_library.dedupe_edges.edge(context), response_model=EdgeDuplicate
327
+ )
322
328
 
323
329
  is_duplicate: bool = llm_response.get('is_duplicate', False)
324
330
  uuid: str | None = llm_response.get('uuid', None)
@@ -353,7 +359,7 @@ async def dedupe_edge_list(
353
359
  context = {'edges': [{'uuid': edge.uuid, 'fact': edge.fact} for edge in edges]}
354
360
 
355
361
  llm_response = await llm_client.generate_response(
356
- prompt_library.dedupe_edges.edge_list(context)
362
+ prompt_library.dedupe_edges.edge_list(context), response_model=UniqueFacts
357
363
  )
358
364
  unique_edges_data = llm_response.get('unique_facts', [])
359
365
 
@@ -14,14 +14,13 @@ See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  """
16
16
 
17
- import asyncio
18
17
  import logging
19
18
  from datetime import datetime, timezone
20
19
 
21
20
  from neo4j import AsyncDriver
22
21
  from typing_extensions import LiteralString
23
22
 
24
- from graphiti_core.helpers import DEFAULT_DATABASE
23
+ from graphiti_core.helpers import DEFAULT_DATABASE, semaphore_gather
25
24
  from graphiti_core.nodes import EpisodeType, EpisodicNode
26
25
 
27
26
  EPISODE_WINDOW_LEN = 3
@@ -38,7 +37,7 @@ async def build_indices_and_constraints(driver: AsyncDriver, delete_existing: bo
38
37
  database_=DEFAULT_DATABASE,
39
38
  )
40
39
  index_names = [record['name'] for record in records]
41
- await asyncio.gather(
40
+ await semaphore_gather(
42
41
  *[
43
42
  driver.execute_query(
44
43
  """DROP INDEX $name""",
@@ -82,7 +81,7 @@ async def build_indices_and_constraints(driver: AsyncDriver, delete_existing: bo
82
81
 
83
82
  index_queries: list[LiteralString] = range_indices + fulltext_indices
84
83
 
85
- await asyncio.gather(
84
+ await semaphore_gather(
86
85
  *[
87
86
  driver.execute_query(
88
87
  query,
@@ -14,15 +14,17 @@ See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  """
16
16
 
17
- import asyncio
18
17
  import logging
19
- from datetime import datetime, timezone
20
18
  from time import time
21
19
 
22
- from graphiti_core.helpers import MAX_REFLEXION_ITERATIONS
20
+ from graphiti_core.helpers import MAX_REFLEXION_ITERATIONS, semaphore_gather
23
21
  from graphiti_core.llm_client import LLMClient
24
22
  from graphiti_core.nodes import EntityNode, EpisodeType, EpisodicNode
25
23
  from graphiti_core.prompts import prompt_library
24
+ from graphiti_core.prompts.dedupe_nodes import NodeDuplicate
25
+ from graphiti_core.prompts.extract_nodes import ExtractedNodes, MissedEntities
26
+ from graphiti_core.prompts.summarize_nodes import Summary
27
+ from graphiti_core.utils.datetime_utils import utc_now
26
28
 
27
29
  logger = logging.getLogger(__name__)
28
30
 
@@ -42,7 +44,7 @@ async def extract_message_nodes(
42
44
  }
43
45
 
44
46
  llm_response = await llm_client.generate_response(
45
- prompt_library.extract_nodes.extract_message(context)
47
+ prompt_library.extract_nodes.extract_message(context), response_model=ExtractedNodes
46
48
  )
47
49
  extracted_node_names = llm_response.get('extracted_node_names', [])
48
50
  return extracted_node_names
@@ -63,7 +65,7 @@ async def extract_text_nodes(
63
65
  }
64
66
 
65
67
  llm_response = await llm_client.generate_response(
66
- prompt_library.extract_nodes.extract_text(context)
68
+ prompt_library.extract_nodes.extract_text(context), ExtractedNodes
67
69
  )
68
70
  extracted_node_names = llm_response.get('extracted_node_names', [])
69
71
  return extracted_node_names
@@ -81,7 +83,7 @@ async def extract_json_nodes(
81
83
  }
82
84
 
83
85
  llm_response = await llm_client.generate_response(
84
- prompt_library.extract_nodes.extract_json(context)
86
+ prompt_library.extract_nodes.extract_json(context), ExtractedNodes
85
87
  )
86
88
  extracted_node_names = llm_response.get('extracted_node_names', [])
87
89
  return extracted_node_names
@@ -101,7 +103,7 @@ async def extract_nodes_reflexion(
101
103
  }
102
104
 
103
105
  llm_response = await llm_client.generate_response(
104
- prompt_library.extract_nodes.reflexion(context)
106
+ prompt_library.extract_nodes.reflexion(context), MissedEntities
105
107
  )
106
108
  missed_entities = llm_response.get('missed_entities', [])
107
109
 
@@ -152,7 +154,7 @@ async def extract_nodes(
152
154
  group_id=episode.group_id,
153
155
  labels=['Entity'],
154
156
  summary='',
155
- created_at=datetime.now(timezone.utc),
157
+ created_at=utc_now(),
156
158
  )
157
159
  new_nodes.append(new_node)
158
160
  logger.debug(f'Created new node: {new_node.name} (UUID: {new_node.uuid})')
@@ -220,7 +222,7 @@ async def resolve_extracted_nodes(
220
222
  uuid_map: dict[str, str] = {}
221
223
  resolved_nodes: list[EntityNode] = []
222
224
  results: list[tuple[EntityNode, dict[str, str]]] = list(
223
- await asyncio.gather(
225
+ await semaphore_gather(
224
226
  *[
225
227
  resolve_extracted_node(
226
228
  llm_client, extracted_node, existing_nodes, episode, previous_episodes
@@ -272,10 +274,13 @@ async def resolve_extracted_node(
272
274
  else [],
273
275
  }
274
276
 
275
- llm_response, node_summary_response = await asyncio.gather(
276
- llm_client.generate_response(prompt_library.dedupe_nodes.node(context)),
277
+ llm_response, node_summary_response = await semaphore_gather(
277
278
  llm_client.generate_response(
278
- prompt_library.summarize_nodes.summarize_context(summary_context)
279
+ prompt_library.dedupe_nodes.node(context), response_model=NodeDuplicate
280
+ ),
281
+ llm_client.generate_response(
282
+ prompt_library.summarize_nodes.summarize_context(summary_context),
283
+ response_model=Summary,
279
284
  ),
280
285
  )
281
286
 
@@ -294,7 +299,8 @@ async def resolve_extracted_node(
294
299
  summary_response = await llm_client.generate_response(
295
300
  prompt_library.summarize_nodes.summarize_pair(
296
301
  {'node_summaries': [extracted_node.summary, existing_node.summary]}
297
- )
302
+ ),
303
+ response_model=Summary,
298
304
  )
299
305
  node = existing_node
300
306
  node.name = name
@@ -9,7 +9,7 @@ You may obtain a copy of the License at
9
9
 
10
10
  Unless required by applicable law or agreed to in writing, software
11
11
  distributed under the License is distributed on an "AS IS" BASIS,
12
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  """
@@ -17,12 +17,14 @@ limitations under the License.
17
17
  import logging
18
18
  from datetime import datetime
19
19
  from time import time
20
- from typing import List
21
20
 
22
21
  from graphiti_core.edges import EntityEdge
23
22
  from graphiti_core.llm_client import LLMClient
24
23
  from graphiti_core.nodes import EpisodicNode
25
24
  from graphiti_core.prompts import prompt_library
25
+ from graphiti_core.prompts.extract_edge_dates import EdgeDates
26
+ from graphiti_core.prompts.invalidate_edges import InvalidatedEdges
27
+ from graphiti_core.utils.datetime_utils import ensure_utc
26
28
 
27
29
  logger = logging.getLogger(__name__)
28
30
 
@@ -31,7 +33,7 @@ async def extract_edge_dates(
31
33
  llm_client: LLMClient,
32
34
  edge: EntityEdge,
33
35
  current_episode: EpisodicNode,
34
- previous_episodes: List[EpisodicNode],
36
+ previous_episodes: list[EpisodicNode],
35
37
  ) -> tuple[datetime | None, datetime | None]:
36
38
  context = {
37
39
  'edge_fact': edge.fact,
@@ -39,7 +41,9 @@ async def extract_edge_dates(
39
41
  'previous_episodes': [ep.content for ep in previous_episodes],
40
42
  'reference_timestamp': current_episode.valid_at.isoformat(),
41
43
  }
42
- llm_response = await llm_client.generate_response(prompt_library.extract_edge_dates.v1(context))
44
+ llm_response = await llm_client.generate_response(
45
+ prompt_library.extract_edge_dates.v1(context), response_model=EdgeDates
46
+ )
43
47
 
44
48
  valid_at = llm_response.get('valid_at')
45
49
  invalid_at = llm_response.get('invalid_at')
@@ -49,15 +53,17 @@ async def extract_edge_dates(
49
53
 
50
54
  if valid_at:
51
55
  try:
52
- valid_at_datetime = datetime.fromisoformat(valid_at.replace('Z', '+00:00'))
56
+ valid_at_datetime = ensure_utc(datetime.fromisoformat(valid_at.replace('Z', '+00:00')))
53
57
  except ValueError as e:
54
- logger.error(f'Error parsing valid_at date: {e}. Input: {valid_at}')
58
+ logger.warning(f'WARNING: Error parsing valid_at date: {e}. Input: {valid_at}')
55
59
 
56
60
  if invalid_at:
57
61
  try:
58
- invalid_at_datetime = datetime.fromisoformat(invalid_at.replace('Z', '+00:00'))
62
+ invalid_at_datetime = ensure_utc(
63
+ datetime.fromisoformat(invalid_at.replace('Z', '+00:00'))
64
+ )
59
65
  except ValueError as e:
60
- logger.error(f'Error parsing invalid_at date: {e}. Input: {invalid_at}')
66
+ logger.warning(f'WARNING: Error parsing invalid_at date: {e}. Input: {invalid_at}')
61
67
 
62
68
  return valid_at_datetime, invalid_at_datetime
63
69
 
@@ -76,7 +82,9 @@ async def get_edge_contradictions(
76
82
 
77
83
  context = {'new_edge': new_edge_context, 'existing_edges': existing_edge_context}
78
84
 
79
- llm_response = await llm_client.generate_response(prompt_library.invalidate_edges.v2(context))
85
+ llm_response = await llm_client.generate_response(
86
+ prompt_library.invalidate_edges.v2(context), response_model=InvalidatedEdges
87
+ )
80
88
 
81
89
  contradicted_edge_data = llm_response.get('invalidated_edges', [])
82
90
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: graphiti-core
3
- Version: 0.4.2
3
+ Version: 0.5.0
4
4
  Summary: A temporal graph building library
5
5
  License: Apache-2.0
6
6
  Author: Paul Paliychuk
@@ -0,0 +1,60 @@
1
+ graphiti_core/__init__.py,sha256=e5SWFkRiaUwfprYIeIgVIh7JDedNiloZvd3roU-0aDY,55
2
+ graphiti_core/cross_encoder/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ graphiti_core/cross_encoder/bge_reranker_client.py,sha256=xgXZqB_qoaWQPjnmuf1ne38YPyhhvApySKcQDaHc9R4,1435
4
+ graphiti_core/cross_encoder/client.py,sha256=KLsbfWKOEaAV3adFe3XZlAeb-gje9_sVKCVZTaJP3ac,1441
5
+ graphiti_core/cross_encoder/openai_reranker_client.py,sha256=DECh36QdkM4mE5qgg158ss6G4si2DjgOTqrzGOz5C9s,4089
6
+ graphiti_core/edges.py,sha256=A9tlOtSIVYy_OOKfOk5fAmZ13g8euuAe76im6nKJs0o,14766
7
+ graphiti_core/embedder/__init__.py,sha256=eWd-0sPxflnYXLoWNT9sxwCIFun5JNO9Fk4E-ZXXf8Y,164
8
+ graphiti_core/embedder/client.py,sha256=HKIlpPLnzFT81jurPkry6z8F8nxfZVfejdcfxHVUSFU,995
9
+ graphiti_core/embedder/openai.py,sha256=FzEM9rtSDK1wTb4iYKjNjjdFf8BEBTDxG2vM_E-5W-8,1621
10
+ graphiti_core/embedder/voyage.py,sha256=7kqrLG75J3Q6cdA2Nlx1JSYtpk2141ckdl3OtDDw0vU,1882
11
+ graphiti_core/errors.py,sha256=ddHrHGQxhwkVAtSph4AV84UoOlgwZufMczXPwB7uqPo,1795
12
+ graphiti_core/graphiti.py,sha256=qS3q9FmygplN2H7JL0jyO0VGvAc8IOl89xyipyJc84o,27039
13
+ graphiti_core/helpers.py,sha256=P7Y93pNaVxKip06EWLyzoCnG69czLqqvdMuXUV5aesM,2976
14
+ graphiti_core/llm_client/__init__.py,sha256=PA80TSMeX-sUXITXEAxMDEt3gtfZgcJrGJUcyds1mSo,207
15
+ graphiti_core/llm_client/anthropic_client.py,sha256=4hU_PXObkdiT_gUNj9G-Cj6vHFPQ0QpolwaRnJwDiL4,2497
16
+ graphiti_core/llm_client/client.py,sha256=3eidjHWxTQvCHTUOztSm2SN4-VQhgO0BghLwnHK6u7Y,4801
17
+ graphiti_core/llm_client/config.py,sha256=VwtvD0B7TNqE6Cl-rvH5v-bAfmjMLhEUuFmHSPt10EI,2339
18
+ graphiti_core/llm_client/errors.py,sha256=Vk0mj2SgNDg8E8p7m1UyUaerqLPNLCDKPVsMEnOSBdQ,1028
19
+ graphiti_core/llm_client/groq_client.py,sha256=A4TcbBGXyF5Br5Ggm7qnvso76L1ERO4JoCA2HlzDEyI,2421
20
+ graphiti_core/llm_client/openai_client.py,sha256=RGq2emM1CioOGFTNfulT3nNdMAKHTXlI7kNp7vQJe9c,6555
21
+ graphiti_core/llm_client/openai_generic_client.py,sha256=SkuQs3jlJAbeFc5MMPUdGcnvACeavd1FAZoj5p0TAI4,6441
22
+ graphiti_core/llm_client/utils.py,sha256=zKpxXEbKa369m4W7RDEf-m56kH46V1Mx3RowcWZEWWs,1000
23
+ graphiti_core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
+ graphiti_core/models/edges/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
+ graphiti_core/models/edges/edge_db_queries.py,sha256=2UoLkmazO-FJYqjc3g0LuL-pyjekzQxxed_XHVv_HZE,2671
26
+ graphiti_core/models/nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
+ graphiti_core/models/nodes/node_db_queries.py,sha256=I0top_N23FN0U5ZbypaS5IXvtfx2zgJmKUCT_7mpUdo,2257
28
+ graphiti_core/nodes.py,sha256=W5E7Tce02yF8fgo_rimVmPTrwYJ7qoqlfAtlcBs9jjk,15921
29
+ graphiti_core/prompts/__init__.py,sha256=EA-x9xUki9l8wnu2l8ek_oNf75-do5tq5hVq7Zbv8Kw,101
30
+ graphiti_core/prompts/dedupe_edges.py,sha256=EuX8ngeItBzrlMBOgeHrpExzxIFHD2aoDyaX1ZniF6I,3556
31
+ graphiti_core/prompts/dedupe_nodes.py,sha256=mqvNATL-4Vo33vaxUEZfOq6hXXOiL-ftY0zcx2G-82I,4624
32
+ graphiti_core/prompts/eval.py,sha256=csW494kKBMvWSm2SYLIRuGgNghhwNR3YwGn3veo3g_Y,3691
33
+ graphiti_core/prompts/extract_edge_dates.py,sha256=td2yx2wnX-nLioMa0mtla3WcRyO71_wSjemT79YZGQ0,4096
34
+ graphiti_core/prompts/extract_edges.py,sha256=vyEdW7JAPOT_eLWUi6nRmxbvucyVoyoYX2SxXfknRUg,3467
35
+ graphiti_core/prompts/extract_nodes.py,sha256=JXLHeL1VcFo0auGf2roVnoWu1CyZJDWxBCu6BXE9fUQ,5289
36
+ graphiti_core/prompts/invalidate_edges.py,sha256=DV2mEyIhhjc0hdKEMFLQMeG0FiUCkv_X0ctCliYjQ2c,3577
37
+ graphiti_core/prompts/lib.py,sha256=oxhlpGEgV15VOLEZiwirxmIJBIdfzfiyL58iyzFDskE,4254
38
+ graphiti_core/prompts/models.py,sha256=cvx_Bv5RMFUD_5IUawYrbpOKLPHogai7_bm7YXrSz84,867
39
+ graphiti_core/prompts/prompt_helpers.py,sha256=-9TABwIcIQUVHcNANx6wIZd-FT2DgYKyGTfx4IGYq2I,64
40
+ graphiti_core/prompts/summarize_nodes.py,sha256=5J_IONG7fHYiQZWnCaUyw7w2zunEaN7V89nEluRP-qY,3461
41
+ graphiti_core/py.typed,sha256=vlmmzQOt7bmeQl9L3XJP4W6Ry0iiELepnOrinKz5KQg,79
42
+ graphiti_core/search/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
+ graphiti_core/search/search.py,sha256=_-N8ztFv9_OpfNci-AwImhgw8NOLj5FDV_7Cv5GSiRo,12028
44
+ graphiti_core/search/search_config.py,sha256=UZN8jFA4pBlw2O5N1cuhVRBdTwMLR9N3Oyo6sQ4MDVw,3117
45
+ graphiti_core/search/search_config_recipes.py,sha256=yUqiLnn9vFg39M8eVwjVKfBCL_ptGrfDMQ47m_Blb0g,6885
46
+ graphiti_core/search/search_utils.py,sha256=lL6CsFFn65tEVlfBLBZu4ynZ9cyBW5SWqc0ss9mlF0Y,23485
47
+ graphiti_core/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
+ graphiti_core/utils/bulk_utils.py,sha256=FYal4tSspGVohNsnDoyW_YjMiscySuYPuQLPSwVCy24,14110
49
+ graphiti_core/utils/datetime_utils.py,sha256=Ti-2tnrDFRzBsbfblzsHybsM3jaDLP4-VT2t0VhpIzU,1357
50
+ graphiti_core/utils/maintenance/__init__.py,sha256=TRY3wWWu5kn3Oahk_KKhltrWnh0NACw0FskjqF6OtlA,314
51
+ graphiti_core/utils/maintenance/community_operations.py,sha256=gIw1M5HGgc2c3TXag5ygPPpAv5WsG-yoC8Lhmfr6FMs,10011
52
+ graphiti_core/utils/maintenance/edge_operations.py,sha256=8Ajq6GS9xH3LRHHw4UAiCeYsWmqJAPYdwfJ_HBGYq2Q,12680
53
+ graphiti_core/utils/maintenance/graph_data_operations.py,sha256=qds9ALk9PhpQs1CNZTZGpi70mqJ93Y2KhIh9X2r8MUI,6533
54
+ graphiti_core/utils/maintenance/node_operations.py,sha256=A-6H2ohqcGJRA_sg_-0m_AA7syiP_gVBsyY7VTTbfuA,12036
55
+ graphiti_core/utils/maintenance/temporal_operations.py,sha256=RdNtubCyYhOVrvcOIq2WppHls1Q-BEjtsN8r38l-Rtc,3691
56
+ graphiti_core/utils/maintenance/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
+ graphiti_core-0.5.0.dist-info/LICENSE,sha256=KCUwCyDXuVEgmDWkozHyniRyWjnWUWjkuDHfU6o3JlA,11325
58
+ graphiti_core-0.5.0.dist-info/METADATA,sha256=m6awcew9uUMiD9oHx4ShnmReFe9XgVw4F4yYFuSGPmM,10058
59
+ graphiti_core-0.5.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
60
+ graphiti_core-0.5.0.dist-info/RECORD,,
@@ -1,57 +0,0 @@
1
- graphiti_core/__init__.py,sha256=e5SWFkRiaUwfprYIeIgVIh7JDedNiloZvd3roU-0aDY,55
2
- graphiti_core/cross_encoder/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- graphiti_core/cross_encoder/bge_reranker_client.py,sha256=jsXBUHfFpGsNASHaRnfz1_miQ3x070DdU8QS4J3DciI,1466
4
- graphiti_core/cross_encoder/client.py,sha256=PyFYYsALQAD9wu0gL5uquPsulmaBZ0AZkJmLq2DFA-c,1472
5
- graphiti_core/cross_encoder/openai_reranker_client.py,sha256=F0S9ksusyxdlcRp4yRJuCyAEg-YqgdCwXjZbH8L-Xxo,4063
6
- graphiti_core/edges.py,sha256=x4aC6RFAbtidXAlOJvG8iumQdomrTICQMNiA5wIKXN4,13321
7
- graphiti_core/embedder/__init__.py,sha256=eWd-0sPxflnYXLoWNT9sxwCIFun5JNO9Fk4E-ZXXf8Y,164
8
- graphiti_core/embedder/client.py,sha256=gVr_xdN-d0UQfeE4Nyoa4pL3M8UxllNH4eFqmarxwys,1011
9
- graphiti_core/embedder/openai.py,sha256=yYUYPymx_lBlxDTGrlc03yNhPFyGG-etM2sszRK2G2U,1618
10
- graphiti_core/embedder/voyage.py,sha256=_eGFI5_NjNG8z7qG3jTWCdE7sAs1Yb8fiSZSJlQLD9o,1879
11
- graphiti_core/errors.py,sha256=ddHrHGQxhwkVAtSph4AV84UoOlgwZufMczXPwB7uqPo,1795
12
- graphiti_core/graphiti.py,sha256=-dJUa_spL0yRbGCUqWaAtZ1hlA2FaIzoHcIMT57KQYY,26919
13
- graphiti_core/helpers.py,sha256=0kkfmswLxRek27WpeUolTBNyyu3_XQwl8xOLRSkAwqk,2207
14
- graphiti_core/llm_client/__init__.py,sha256=PA80TSMeX-sUXITXEAxMDEt3gtfZgcJrGJUcyds1mSo,207
15
- graphiti_core/llm_client/anthropic_client.py,sha256=4l2PbCjIoeRr7UJ2DUh2grYLTtE2vNaWlo72IIRQDeI,2405
16
- graphiti_core/llm_client/client.py,sha256=WAnX0e4EuCFHXdFHeq_O1HZsW1STSByvDCFUHMAHEFU,3394
17
- graphiti_core/llm_client/config.py,sha256=VwtvD0B7TNqE6Cl-rvH5v-bAfmjMLhEUuFmHSPt10EI,2339
18
- graphiti_core/llm_client/errors.py,sha256=-qlWwv1X-UjfsFIiNl-7yJIYvPwi7z8srVRfX4-s6uk,814
19
- graphiti_core/llm_client/groq_client.py,sha256=5uGWeQ903EuNxuRiaeH-_J1U2Le_b7Q1UGV_K8bQAiw,2329
20
- graphiti_core/llm_client/openai_client.py,sha256=xLkbpusRVFRK0zPr3kOqY31HK_XCXrpO5rqUSpcEqEU,3825
21
- graphiti_core/llm_client/utils.py,sha256=zKpxXEbKa369m4W7RDEf-m56kH46V1Mx3RowcWZEWWs,1000
22
- graphiti_core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- graphiti_core/models/edges/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
- graphiti_core/models/edges/edge_db_queries.py,sha256=2UoLkmazO-FJYqjc3g0LuL-pyjekzQxxed_XHVv_HZE,2671
25
- graphiti_core/models/nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
- graphiti_core/models/nodes/node_db_queries.py,sha256=I0top_N23FN0U5ZbypaS5IXvtfx2zgJmKUCT_7mpUdo,2257
27
- graphiti_core/nodes.py,sha256=2OnpQRbxmOWxmYJnLmirZ7Qoo9oNaSpRcVdMhnoCXhg,14481
28
- graphiti_core/prompts/__init__.py,sha256=EA-x9xUki9l8wnu2l8ek_oNf75-do5tq5hVq7Zbv8Kw,101
29
- graphiti_core/prompts/dedupe_edges.py,sha256=7jff4x60p6x8K1Oy7EN8twdOvsTt67_SwoF-lvf2c-8,3539
30
- graphiti_core/prompts/dedupe_nodes.py,sha256=_2zD0VcygH1Sut3FwRrKSBALFaxhnnO3JOFJkwHhEVo,4130
31
- graphiti_core/prompts/eval.py,sha256=9gavc4SKAPdsrhpN8NEUTc632erkaifyOf0hevmdeKY,3657
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
- graphiti_core/prompts/invalidate_edges.py,sha256=2vhi9TsL9poAHqApfk_Us0VveG0-T8cZymfBwOgA8tc,4341
36
- graphiti_core/prompts/lib.py,sha256=ZOE6nNoI_wQ12Sufx7rQkQtkIm_eTAL7pCiYGU2hcMI,4054
37
- graphiti_core/prompts/models.py,sha256=cvx_Bv5RMFUD_5IUawYrbpOKLPHogai7_bm7YXrSz84,867
38
- graphiti_core/prompts/summarize_nodes.py,sha256=lNpagCq6Pz71vipBonQZUxBZG6OY4bQbyGZ5UEgdL4s,3488
39
- graphiti_core/py.typed,sha256=vlmmzQOt7bmeQl9L3XJP4W6Ry0iiELepnOrinKz5KQg,79
40
- graphiti_core/search/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
- graphiti_core/search/search.py,sha256=5r7HpIR4cQvwH24YN_DJB6LVNN9hizviE0QXGM6swz8,11984
42
- graphiti_core/search/search_config.py,sha256=UZN8jFA4pBlw2O5N1cuhVRBdTwMLR9N3Oyo6sQ4MDVw,3117
43
- graphiti_core/search/search_config_recipes.py,sha256=yUqiLnn9vFg39M8eVwjVKfBCL_ptGrfDMQ47m_Blb0g,6885
44
- graphiti_core/search/search_utils.py,sha256=C9qzdRMP2WFW7O0CivoGHYyEviaCZEGDu4HAAXonxzo,22459
45
- graphiti_core/utils/__init__.py,sha256=cJAcMnBZdHBQmWrZdU1PQ1YmaL75bhVUkyVpIPuOyns,260
46
- graphiti_core/utils/bulk_utils.py,sha256=AMi42deCXjIIgdpLEiD5SYCMZfX8XKoOsVYEwXVTX5A,14016
47
- graphiti_core/utils/maintenance/__init__.py,sha256=TRY3wWWu5kn3Oahk_KKhltrWnh0NACw0FskjqF6OtlA,314
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
- graphiti_core/utils/maintenance/graph_data_operations.py,sha256=w66_SLlvPapuG91YGGfR3bG2sM6cJ2XPHIaxM0slAdE,6526
51
- graphiti_core/utils/maintenance/node_operations.py,sha256=m0q-PPCI5LM0dZMkrcyUENw6WfDBMO61DQ7S6MuNQ8E,11624
52
- graphiti_core/utils/maintenance/temporal_operations.py,sha256=MvaRLWrBlDeYw8CQrKish1xbYcY5ovpfdqA2hSX7v5k,3367
53
- graphiti_core/utils/maintenance/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
- graphiti_core-0.4.2.dist-info/LICENSE,sha256=KCUwCyDXuVEgmDWkozHyniRyWjnWUWjkuDHfU6o3JlA,11325
55
- graphiti_core-0.4.2.dist-info/METADATA,sha256=fTPDkPlXZt1mxSs3sALlSNJgUoho99K9FGvcjcmsizI,10058
56
- graphiti_core-0.4.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
57
- graphiti_core-0.4.2.dist-info/RECORD,,