graphiti-core 0.17.4__py3-none-any.whl → 0.24.3__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.
Files changed (58) hide show
  1. graphiti_core/cross_encoder/gemini_reranker_client.py +1 -1
  2. graphiti_core/cross_encoder/openai_reranker_client.py +1 -1
  3. graphiti_core/decorators.py +110 -0
  4. graphiti_core/driver/driver.py +62 -2
  5. graphiti_core/driver/falkordb_driver.py +215 -23
  6. graphiti_core/driver/graph_operations/graph_operations.py +191 -0
  7. graphiti_core/driver/kuzu_driver.py +182 -0
  8. graphiti_core/driver/neo4j_driver.py +61 -8
  9. graphiti_core/driver/neptune_driver.py +305 -0
  10. graphiti_core/driver/search_interface/search_interface.py +89 -0
  11. graphiti_core/edges.py +264 -132
  12. graphiti_core/embedder/azure_openai.py +10 -3
  13. graphiti_core/embedder/client.py +2 -1
  14. graphiti_core/graph_queries.py +114 -101
  15. graphiti_core/graphiti.py +582 -255
  16. graphiti_core/graphiti_types.py +2 -0
  17. graphiti_core/helpers.py +21 -14
  18. graphiti_core/llm_client/anthropic_client.py +142 -52
  19. graphiti_core/llm_client/azure_openai_client.py +57 -19
  20. graphiti_core/llm_client/client.py +83 -21
  21. graphiti_core/llm_client/config.py +1 -1
  22. graphiti_core/llm_client/gemini_client.py +75 -57
  23. graphiti_core/llm_client/openai_base_client.py +94 -50
  24. graphiti_core/llm_client/openai_client.py +28 -8
  25. graphiti_core/llm_client/openai_generic_client.py +91 -56
  26. graphiti_core/models/edges/edge_db_queries.py +259 -35
  27. graphiti_core/models/nodes/node_db_queries.py +311 -32
  28. graphiti_core/nodes.py +388 -164
  29. graphiti_core/prompts/dedupe_edges.py +42 -31
  30. graphiti_core/prompts/dedupe_nodes.py +56 -39
  31. graphiti_core/prompts/eval.py +4 -4
  32. graphiti_core/prompts/extract_edges.py +23 -14
  33. graphiti_core/prompts/extract_nodes.py +73 -32
  34. graphiti_core/prompts/prompt_helpers.py +39 -0
  35. graphiti_core/prompts/snippets.py +29 -0
  36. graphiti_core/prompts/summarize_nodes.py +23 -25
  37. graphiti_core/search/search.py +154 -74
  38. graphiti_core/search/search_config.py +39 -4
  39. graphiti_core/search/search_filters.py +109 -31
  40. graphiti_core/search/search_helpers.py +5 -6
  41. graphiti_core/search/search_utils.py +1360 -473
  42. graphiti_core/tracer.py +193 -0
  43. graphiti_core/utils/bulk_utils.py +216 -90
  44. graphiti_core/utils/datetime_utils.py +13 -0
  45. graphiti_core/utils/maintenance/community_operations.py +62 -38
  46. graphiti_core/utils/maintenance/dedup_helpers.py +262 -0
  47. graphiti_core/utils/maintenance/edge_operations.py +286 -126
  48. graphiti_core/utils/maintenance/graph_data_operations.py +44 -74
  49. graphiti_core/utils/maintenance/node_operations.py +320 -158
  50. graphiti_core/utils/maintenance/temporal_operations.py +11 -3
  51. graphiti_core/utils/ontology_utils/entity_types_utils.py +1 -1
  52. graphiti_core/utils/text_utils.py +53 -0
  53. {graphiti_core-0.17.4.dist-info → graphiti_core-0.24.3.dist-info}/METADATA +221 -87
  54. graphiti_core-0.24.3.dist-info/RECORD +86 -0
  55. {graphiti_core-0.17.4.dist-info → graphiti_core-0.24.3.dist-info}/WHEEL +1 -1
  56. graphiti_core-0.17.4.dist-info/RECORD +0 -77
  57. /graphiti_core/{utils/maintenance/utils.py → migrations/__init__.py} +0 -0
  58. {graphiti_core-0.17.4.dist-info → graphiti_core-0.24.3.dist-info}/licenses/LICENSE +0 -0
@@ -5,16 +5,9 @@ This module provides database-agnostic query generation for Neo4j and FalkorDB,
5
5
  supporting index creation, fulltext search, and bulk operations.
6
6
  """
7
7
 
8
- from typing import Any
9
-
10
8
  from typing_extensions import LiteralString
11
9
 
12
- from graphiti_core.models.edges.edge_db_queries import (
13
- ENTITY_EDGE_SAVE_BULK,
14
- )
15
- from graphiti_core.models.nodes.node_db_queries import (
16
- ENTITY_NODE_SAVE_BULK,
17
- )
10
+ from graphiti_core.driver.driver import GraphProvider
18
11
 
19
12
  # Mapping from Neo4j fulltext index names to FalkorDB node labels
20
13
  NEO4J_TO_FALKORDB_MAPPING = {
@@ -23,10 +16,17 @@ NEO4J_TO_FALKORDB_MAPPING = {
23
16
  'episode_content': 'Episodic',
24
17
  'edge_name_and_fact': 'RELATES_TO',
25
18
  }
19
+ # Mapping from fulltext index names to Kuzu node labels
20
+ INDEX_TO_LABEL_KUZU_MAPPING = {
21
+ 'node_name_and_summary': 'Entity',
22
+ 'community_name': 'Community',
23
+ 'episode_content': 'Episodic',
24
+ 'edge_name_and_fact': 'RelatesToNode_',
25
+ }
26
26
 
27
27
 
28
- def get_range_indices(db_type: str = 'neo4j') -> list[LiteralString]:
29
- if db_type == 'falkordb':
28
+ def get_range_indices(provider: GraphProvider) -> list[LiteralString]:
29
+ if provider == GraphProvider.FALKORDB:
30
30
  return [
31
31
  # Entity node
32
32
  'CREATE INDEX FOR (n:Entity) ON (n.uuid, n.group_id, n.name, n.created_at)',
@@ -41,109 +41,122 @@ def get_range_indices(db_type: str = 'neo4j') -> list[LiteralString]:
41
41
  # HAS_MEMBER edge
42
42
  'CREATE INDEX FOR ()-[e:HAS_MEMBER]-() ON (e.uuid)',
43
43
  ]
44
- else:
45
- return [
46
- 'CREATE INDEX entity_uuid IF NOT EXISTS FOR (n:Entity) ON (n.uuid)',
47
- 'CREATE INDEX episode_uuid IF NOT EXISTS FOR (n:Episodic) ON (n.uuid)',
48
- 'CREATE INDEX community_uuid IF NOT EXISTS FOR (n:Community) ON (n.uuid)',
49
- 'CREATE INDEX relation_uuid IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.uuid)',
50
- 'CREATE INDEX mention_uuid IF NOT EXISTS FOR ()-[e:MENTIONS]-() ON (e.uuid)',
51
- 'CREATE INDEX has_member_uuid IF NOT EXISTS FOR ()-[e:HAS_MEMBER]-() ON (e.uuid)',
52
- 'CREATE INDEX entity_group_id IF NOT EXISTS FOR (n:Entity) ON (n.group_id)',
53
- 'CREATE INDEX episode_group_id IF NOT EXISTS FOR (n:Episodic) ON (n.group_id)',
54
- 'CREATE INDEX relation_group_id IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.group_id)',
55
- 'CREATE INDEX mention_group_id IF NOT EXISTS FOR ()-[e:MENTIONS]-() ON (e.group_id)',
56
- 'CREATE INDEX name_entity_index IF NOT EXISTS FOR (n:Entity) ON (n.name)',
57
- 'CREATE INDEX created_at_entity_index IF NOT EXISTS FOR (n:Entity) ON (n.created_at)',
58
- 'CREATE INDEX created_at_episodic_index IF NOT EXISTS FOR (n:Episodic) ON (n.created_at)',
59
- 'CREATE INDEX valid_at_episodic_index IF NOT EXISTS FOR (n:Episodic) ON (n.valid_at)',
60
- 'CREATE INDEX name_edge_index IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.name)',
61
- 'CREATE INDEX created_at_edge_index IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.created_at)',
62
- 'CREATE INDEX expired_at_edge_index IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.expired_at)',
63
- 'CREATE INDEX valid_at_edge_index IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.valid_at)',
64
- 'CREATE INDEX invalid_at_edge_index IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.invalid_at)',
65
- ]
66
44
 
67
-
68
- def get_fulltext_indices(db_type: str = 'neo4j') -> list[LiteralString]:
69
- if db_type == 'falkordb':
45
+ if provider == GraphProvider.KUZU:
46
+ return []
47
+
48
+ return [
49
+ 'CREATE INDEX entity_uuid IF NOT EXISTS FOR (n:Entity) ON (n.uuid)',
50
+ 'CREATE INDEX episode_uuid IF NOT EXISTS FOR (n:Episodic) ON (n.uuid)',
51
+ 'CREATE INDEX community_uuid IF NOT EXISTS FOR (n:Community) ON (n.uuid)',
52
+ 'CREATE INDEX relation_uuid IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.uuid)',
53
+ 'CREATE INDEX mention_uuid IF NOT EXISTS FOR ()-[e:MENTIONS]-() ON (e.uuid)',
54
+ 'CREATE INDEX has_member_uuid IF NOT EXISTS FOR ()-[e:HAS_MEMBER]-() ON (e.uuid)',
55
+ 'CREATE INDEX entity_group_id IF NOT EXISTS FOR (n:Entity) ON (n.group_id)',
56
+ 'CREATE INDEX episode_group_id IF NOT EXISTS FOR (n:Episodic) ON (n.group_id)',
57
+ 'CREATE INDEX community_group_id IF NOT EXISTS FOR (n:Community) ON (n.group_id)',
58
+ 'CREATE INDEX relation_group_id IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.group_id)',
59
+ 'CREATE INDEX mention_group_id IF NOT EXISTS FOR ()-[e:MENTIONS]-() ON (e.group_id)',
60
+ 'CREATE INDEX name_entity_index IF NOT EXISTS FOR (n:Entity) ON (n.name)',
61
+ 'CREATE INDEX created_at_entity_index IF NOT EXISTS FOR (n:Entity) ON (n.created_at)',
62
+ 'CREATE INDEX created_at_episodic_index IF NOT EXISTS FOR (n:Episodic) ON (n.created_at)',
63
+ 'CREATE INDEX valid_at_episodic_index IF NOT EXISTS FOR (n:Episodic) ON (n.valid_at)',
64
+ 'CREATE INDEX name_edge_index IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.name)',
65
+ 'CREATE INDEX created_at_edge_index IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.created_at)',
66
+ 'CREATE INDEX expired_at_edge_index IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.expired_at)',
67
+ 'CREATE INDEX valid_at_edge_index IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.valid_at)',
68
+ 'CREATE INDEX invalid_at_edge_index IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.invalid_at)',
69
+ ]
70
+
71
+
72
+ def get_fulltext_indices(provider: GraphProvider) -> list[LiteralString]:
73
+ if provider == GraphProvider.FALKORDB:
74
+ from typing import cast
75
+
76
+ from graphiti_core.driver.falkordb_driver import STOPWORDS
77
+
78
+ # Convert to string representation for embedding in queries
79
+ stopwords_str = str(STOPWORDS)
80
+
81
+ # Use type: ignore to satisfy LiteralString requirement while maintaining single source of truth
82
+ return cast(
83
+ list[LiteralString],
84
+ [
85
+ f"""CALL db.idx.fulltext.createNodeIndex(
86
+ {{
87
+ label: 'Episodic',
88
+ stopwords: {stopwords_str}
89
+ }},
90
+ 'content', 'source', 'source_description', 'group_id'
91
+ )""",
92
+ f"""CALL db.idx.fulltext.createNodeIndex(
93
+ {{
94
+ label: 'Entity',
95
+ stopwords: {stopwords_str}
96
+ }},
97
+ 'name', 'summary', 'group_id'
98
+ )""",
99
+ f"""CALL db.idx.fulltext.createNodeIndex(
100
+ {{
101
+ label: 'Community',
102
+ stopwords: {stopwords_str}
103
+ }},
104
+ 'name', 'group_id'
105
+ )""",
106
+ """CREATE FULLTEXT INDEX FOR ()-[e:RELATES_TO]-() ON (e.name, e.fact, e.group_id)""",
107
+ ],
108
+ )
109
+
110
+ if provider == GraphProvider.KUZU:
70
111
  return [
71
- """CREATE FULLTEXT INDEX FOR (e:Episodic) ON (e.content, e.source, e.source_description, e.group_id)""",
72
- """CREATE FULLTEXT INDEX FOR (n:Entity) ON (n.name, n.summary, n.group_id)""",
73
- """CREATE FULLTEXT INDEX FOR (n:Community) ON (n.name, n.group_id)""",
74
- """CREATE FULLTEXT INDEX FOR ()-[e:RELATES_TO]-() ON (e.name, e.fact, e.group_id)""",
75
- ]
76
- else:
77
- return [
78
- """CREATE FULLTEXT INDEX episode_content IF NOT EXISTS
79
- FOR (e:Episodic) ON EACH [e.content, e.source, e.source_description, e.group_id]""",
80
- """CREATE FULLTEXT INDEX node_name_and_summary IF NOT EXISTS
81
- FOR (n:Entity) ON EACH [n.name, n.summary, n.group_id]""",
82
- """CREATE FULLTEXT INDEX community_name IF NOT EXISTS
83
- FOR (n:Community) ON EACH [n.name, n.group_id]""",
84
- """CREATE FULLTEXT INDEX edge_name_and_fact IF NOT EXISTS
85
- FOR ()-[e:RELATES_TO]-() ON EACH [e.name, e.fact, e.group_id]""",
112
+ "CALL CREATE_FTS_INDEX('Episodic', 'episode_content', ['content', 'source', 'source_description']);",
113
+ "CALL CREATE_FTS_INDEX('Entity', 'node_name_and_summary', ['name', 'summary']);",
114
+ "CALL CREATE_FTS_INDEX('Community', 'community_name', ['name']);",
115
+ "CALL CREATE_FTS_INDEX('RelatesToNode_', 'edge_name_and_fact', ['name', 'fact']);",
86
116
  ]
87
117
 
118
+ return [
119
+ """CREATE FULLTEXT INDEX episode_content IF NOT EXISTS
120
+ FOR (e:Episodic) ON EACH [e.content, e.source, e.source_description, e.group_id]""",
121
+ """CREATE FULLTEXT INDEX node_name_and_summary IF NOT EXISTS
122
+ FOR (n:Entity) ON EACH [n.name, n.summary, n.group_id]""",
123
+ """CREATE FULLTEXT INDEX community_name IF NOT EXISTS
124
+ FOR (n:Community) ON EACH [n.name, n.group_id]""",
125
+ """CREATE FULLTEXT INDEX edge_name_and_fact IF NOT EXISTS
126
+ FOR ()-[e:RELATES_TO]-() ON EACH [e.name, e.fact, e.group_id]""",
127
+ ]
128
+
88
129
 
89
- def get_nodes_query(db_type: str = 'neo4j', name: str = '', query: str | None = None) -> str:
90
- if db_type == 'falkordb':
130
+ def get_nodes_query(name: str, query: str, limit: int, provider: GraphProvider) -> str:
131
+ if provider == GraphProvider.FALKORDB:
91
132
  label = NEO4J_TO_FALKORDB_MAPPING[name]
92
133
  return f"CALL db.idx.fulltext.queryNodes('{label}', {query})"
93
- else:
94
- return f'CALL db.index.fulltext.queryNodes("{name}", {query}, {{limit: $limit}})'
134
+
135
+ if provider == GraphProvider.KUZU:
136
+ label = INDEX_TO_LABEL_KUZU_MAPPING[name]
137
+ return f"CALL QUERY_FTS_INDEX('{label}', '{name}', {query}, TOP := $limit)"
138
+
139
+ return f'CALL db.index.fulltext.queryNodes("{name}", {query}, {{limit: $limit}})'
95
140
 
96
141
 
97
- def get_vector_cosine_func_query(vec1, vec2, db_type: str = 'neo4j') -> str:
98
- if db_type == 'falkordb':
142
+ def get_vector_cosine_func_query(vec1, vec2, provider: GraphProvider) -> str:
143
+ if provider == GraphProvider.FALKORDB:
99
144
  # FalkorDB uses a different syntax for regular cosine similarity and Neo4j uses normalized cosine similarity
100
145
  return f'(2 - vec.cosineDistance({vec1}, vecf32({vec2})))/2'
101
- else:
102
- return f'vector.similarity.cosine({vec1}, {vec2})'
103
146
 
147
+ if provider == GraphProvider.KUZU:
148
+ return f'array_cosine_similarity({vec1}, {vec2})'
104
149
 
105
- def get_relationships_query(name: str, db_type: str = 'neo4j') -> str:
106
- if db_type == 'falkordb':
150
+ return f'vector.similarity.cosine({vec1}, {vec2})'
151
+
152
+
153
+ def get_relationships_query(name: str, limit: int, provider: GraphProvider) -> str:
154
+ if provider == GraphProvider.FALKORDB:
107
155
  label = NEO4J_TO_FALKORDB_MAPPING[name]
108
156
  return f"CALL db.idx.fulltext.queryRelationships('{label}', $query)"
109
- else:
110
- return f'CALL db.index.fulltext.queryRelationships("{name}", $query, {{limit: $limit}})'
111
-
112
-
113
- def get_entity_node_save_bulk_query(nodes, db_type: str = 'neo4j') -> str | Any:
114
- if db_type == 'falkordb':
115
- queries = []
116
- for node in nodes:
117
- for label in node['labels']:
118
- queries.append(
119
- (
120
- f"""
121
- UNWIND $nodes AS node
122
- MERGE (n:Entity {{uuid: node.uuid}})
123
- SET n:{label}
124
- SET n = node
125
- WITH n, node
126
- SET n.name_embedding = vecf32(node.name_embedding)
127
- RETURN n.uuid AS uuid
128
- """,
129
- {'nodes': [node]},
130
- )
131
- )
132
- return queries
133
- else:
134
- return ENTITY_NODE_SAVE_BULK
135
-
136
-
137
- def get_entity_edge_save_bulk_query(db_type: str = 'neo4j') -> str:
138
- if db_type == 'falkordb':
139
- return """
140
- UNWIND $entity_edges AS edge
141
- MATCH (source:Entity {uuid: edge.source_node_uuid})
142
- MATCH (target:Entity {uuid: edge.target_node_uuid})
143
- MERGE (source)-[r:RELATES_TO {uuid: edge.uuid}]->(target)
144
- SET r = {uuid: edge.uuid, name: edge.name, group_id: edge.group_id, fact: edge.fact, episodes: edge.episodes,
145
- created_at: edge.created_at, expired_at: edge.expired_at, valid_at: edge.valid_at, invalid_at: edge.invalid_at, fact_embedding: vecf32(edge.fact_embedding)}
146
- WITH r, edge
147
- RETURN edge.uuid AS uuid"""
148
- else:
149
- return ENTITY_EDGE_SAVE_BULK
157
+
158
+ if provider == GraphProvider.KUZU:
159
+ label = INDEX_TO_LABEL_KUZU_MAPPING[name]
160
+ return f"CALL QUERY_FTS_INDEX('{label}', '{name}', cast($query AS STRING), TOP := $limit)"
161
+
162
+ return f'CALL db.index.fulltext.queryRelationships("{name}", $query, {{limit: $limit}})'