graphiti-core 0.1.0__tar.gz → 0.2.0__tar.gz

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 (37) hide show
  1. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/PKG-INFO +38 -38
  2. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/README.md +36 -36
  3. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/graphiti.py +105 -85
  4. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/llm_client/openai_client.py +0 -1
  5. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/prompts/dedupe_edges.py +46 -8
  6. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/prompts/dedupe_nodes.py +61 -13
  7. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/prompts/extract_edges.py +2 -1
  8. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/prompts/extract_nodes.py +2 -0
  9. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/search/search.py +8 -8
  10. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/search/search_utils.py +44 -26
  11. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/utils/bulk_utils.py +138 -20
  12. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/utils/maintenance/edge_operations.py +76 -9
  13. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/utils/maintenance/node_operations.py +98 -40
  14. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/utils/maintenance/temporal_operations.py +3 -4
  15. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/utils/utils.py +22 -1
  16. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/pyproject.toml +8 -4
  17. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/LICENSE +0 -0
  18. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/__init__.py +0 -0
  19. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/edges.py +0 -0
  20. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/helpers.py +0 -0
  21. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/llm_client/__init__.py +0 -0
  22. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/llm_client/anthropic_client.py +0 -0
  23. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/llm_client/client.py +0 -0
  24. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/llm_client/config.py +0 -0
  25. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/llm_client/groq_client.py +0 -0
  26. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/llm_client/utils.py +0 -0
  27. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/nodes.py +0 -0
  28. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/prompts/__init__.py +0 -0
  29. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/prompts/extract_edge_dates.py +0 -0
  30. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/prompts/invalidate_edges.py +0 -0
  31. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/prompts/lib.py +0 -0
  32. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/prompts/models.py +0 -0
  33. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/search/__init__.py +0 -0
  34. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/utils/__init__.py +0 -0
  35. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/utils/maintenance/__init__.py +0 -0
  36. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/utils/maintenance/graph_data_operations.py +0 -0
  37. {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/utils/maintenance/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: graphiti-core
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: A temporal graph building library
5
5
  License: Apache-2.0
6
6
  Author: Paul Paliychuk
@@ -17,64 +17,67 @@ Requires-Dist: neo4j (>=5.23.0,<6.0.0)
17
17
  Requires-Dist: openai (>=1.38.0,<2.0.0)
18
18
  Requires-Dist: pydantic (>=2.8.2,<3.0.0)
19
19
  Requires-Dist: sentence-transformers (>=3.0.1,<4.0.0)
20
- Requires-Dist: tenacity (>=9.0.0,<10.0.0)
20
+ Requires-Dist: tenacity (<9.0.0)
21
21
  Description-Content-Type: text/markdown
22
22
 
23
23
  <div align="center">
24
24
 
25
- # graphiti
25
+ # Graphiti
26
26
 
27
27
  ## Temporal Knowledge Graphs for Agentic Applications
28
28
 
29
29
  <br />
30
30
 
31
31
  [![Discord](https://dcbadge.vercel.app/api/server/W8Kw6bsgXQ?style=flat)](https://discord.com/invite/W8Kw6bsgXQ)
32
- [![Lint](https://github.com/getzep/graphiti/actions/workflows/lint.yml/badge.svg)](https://github.com/getzep/graphiti/actions/workflows/lint.yml)
33
- [![Unit Tests](https://github.com/getzep/graphiti/actions/workflows/unit_tests.yml/badge.svg)](https://github.com/getzep/graphiti/actions/workflows/unit_tests.yml)
34
- [![MyPy Check](https://github.com/getzep/graphiti/actions/workflows/typecheck.yml/badge.svg)](https://github.com/getzep/graphiti/actions/workflows/typecheck.yml)
35
- [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/getzep/graphiti)
32
+ [![Lint](https://github.com/getzep/Graphiti/actions/workflows/lint.yml/badge.svg)](https://github.com/getzep/Graphiti/actions/workflows/lint.yml)
33
+ [![Unit Tests](https://github.com/getzep/Graphiti/actions/workflows/unit_tests.yml/badge.svg)](https://github.com/getzep/Graphiti/actions/workflows/unit_tests.yml)
34
+ [![MyPy Check](https://github.com/getzep/Graphiti/actions/workflows/typecheck.yml/badge.svg)](https://github.com/getzep/Graphiti/actions/workflows/typecheck.yml)
35
+ [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/getzep/Graphiti)
36
36
 
37
37
  <br />
38
38
 
39
39
  </div>
40
40
 
41
- graphiti builds dynamic, temporally aware knowledge graphs that represent complex, evolving relationships between entities over time. graphiti ingests both unstructured and structured data and the resulting graph may be queried using a fusion of time, full-text, semantic, and graph algorithm approaches.
41
+ Graphiti builds dynamic, temporally aware Knowledge Graphs that represent complex, evolving relationships between entities over time. Graphiti ingests both unstructured and structured data, and the resulting graph may be queried using a fusion of time, full-text, semantic, and graph algorithm approaches.
42
42
 
43
43
  <br />
44
44
 
45
-
46
45
  <p align="center">
47
- <img src="/images/graphiti-intro-slides-stock-2.gif" alt="graphiti demo slides" width="700px">
46
+ <img src="/images/graphiti-graph-intro.gif" alt="Graphiti temporal walkthrough" width="700px">
48
47
  </p>
49
48
 
50
49
  <br />
51
-
52
- With graphiti, you can build LLM applications such as:
50
+
51
+ Graphiti helps you create and query Knowledge Graphs that evolve over time. A knowledge graph is a network of interconnected facts, such as _“Kendra loves Adidas shoes.”_ Each fact is a “triplet” represented by two entities, or nodes (_”Kendra”_, _“Adidas shoes”_), and their relationship, or edge (_”loves”_). Knowledge Graphs have been explored extensively for information retrieval. What makes Graphiti unique is its ability to autonomously build a knowledge graph while handling changing relationships and maintaining historical context.
52
+
53
+ With Graphiti, you can build LLM applications such as:
53
54
 
54
55
  - Assistants that learn from user interactions, fusing personal knowledge with dynamic data from business systems like CRMs and billing platforms.
55
56
  - Agents that autonomously execute complex tasks, reasoning with state changes from multiple dynamic sources.
56
57
 
57
- graphiti supports a wide range of applications in sales, customer service, health, finance, and more, enabling long-term recall and state-based reasoning for both assistants and agents.
58
-
59
- ## Why graphiti?
58
+ Graphiti supports a wide range of applications in sales, customer service, health, finance, and more, enabling long-term recall and state-based reasoning for both assistants and agents.
60
59
 
61
- graphiti is designed for dynamic data and agentic use:
60
+ ## Why Graphiti?
62
61
 
63
- - **Smart Graph Updates**: Automatically evaluates new entities against the current graph, revising both to reflect the latest context.
64
- - **Rich Edge Semantics**: Generates human-readable, semantic, and full-text searchable representations for edges during graph construction, enabling search and enhancing interpretability.
65
- - **Temporal Awareness**: Extracts and updates time-based edge metadata from input data, enabling reasoning over changing relationships.
66
- - **Hybrid Search**: Offers semantic, BM25, and graph-based search with the ability to fuse results.
67
- - **Fast**: Search results in < 100ms, with latency primarily determined by the 3rd-party embedding API call.
68
- - **Schema Consistency**: Maintains a coherent graph structure by reusing existing schema, preventing unnecessary proliferation of node and edge types.
62
+ We were intrigued by Microsoft’s GraphRAG, which expanded on RAG text chunking by using a graph to better model a document corpus and making this representation available via semantic and graph search techniques. However, GraphRAG did not address our core problem: It's primarily designed for static documents and doesn't inherently handle temporal aspects of data.
69
63
 
64
+ Graphiti is designed from the ground up to handle constantly changing information, hybrid semantic and graph search, and scale:
70
65
 
71
- ## graphiti and Zep Memory
66
+ - **Temporal Awareness:** Tracks changes in facts and relationships over time, enabling point-in-time queries. Graph edges include temporal metadata to record relationship lifecycles.
67
+ - **Episodic Processing:** Ingests data as discrete episodes, maintaining data provenance and allowing incremental entity and relationship extraction.
68
+ - **Hybrid Search:** Combines semantic and BM25 full-text search, with the ability to rerank results by distance from a central node e.g. “Kendra”.
69
+ - **Scalable:** Designed for processing large datasets, with parallelization of LLM calls for bulk processing while preserving the chronology of events.
70
+ - **Supports Varied Sources:** Can ingest both unstructured text and structured JSON data.
72
71
 
73
- graphiti powers the core of [Zep's memory layer](https://www.getzep.com) for LLM-powered Assistants and Agents.
72
+ <p align="center">
73
+ <img src="/images/graphiti-intro-slides-stock-2.gif" alt="Graphiti structured + unstructured demo" width="700px">
74
+ </p>
74
75
 
75
- We're excited to open-source graphiti, believing its potential reaches far beyond memory applications.
76
+ ## Graphiti and Zep Memory
76
77
 
78
+ Graphiti powers the core of [Zep's memory layer](https://www.getzep.com) for LLM-powered Assistants and Agents.
77
79
 
80
+ We're excited to open-source Graphiti, believing its potential reaches far beyond memory applications.
78
81
 
79
82
  ## Installation
80
83
 
@@ -101,12 +104,10 @@ or
101
104
  poetry add graphiti-core
102
105
  ```
103
106
 
104
-
105
-
106
107
  ## Quick Start
107
108
 
108
109
  > [!IMPORTANT]
109
- > graphiti uses OpenAI for LLM inference and embedding. Ensure that an `OPENAI_API_KEY` is set in your environment. Support for Anthropic and Groq LLM inferences is available, too.
110
+ > Graphiti uses OpenAI for LLM inference and embedding. Ensure that an `OPENAI_API_KEY` is set in your environment. Support for Anthropic and Groq LLM inferences is available, too.
110
111
 
111
112
  ```python
112
113
  from graphiti_core import Graphiti
@@ -116,6 +117,9 @@ from datetime import datetime
116
117
  # Initialize Graphiti
117
118
  graphiti = Graphiti("bolt://localhost:7687", "neo4j", "password")
118
119
 
120
+ # Initialize the graph database with Graphiti's indices. This only needs to be done once.
121
+ graphiti.build_indices_and_constraints()
122
+
119
123
  # Add episodes
120
124
  episodes = [
121
125
  "Kamala Harris is the Attorney General of California. She was previously "
@@ -161,24 +165,21 @@ results = await graphiti.search('Who was the California Attorney General?')
161
165
  # Rerank search results based on graph distance
162
166
  # Provide a node UUID to prioritize results closer to that node in the graph.
163
167
  # Results are weighted by their proximity, with distant edges receiving lower scores.
164
- await client.search('Who was the California Attorney General?', center_node_uuid)
168
+ await graphiti.search('Who was the California Attorney General?', center_node_uuid)
165
169
 
166
170
  # Close the connection
167
171
  graphiti.close()
168
172
  ```
169
173
 
170
-
171
-
172
174
  ## Documentation
173
175
 
174
- Visit the Zep knowledge base for graphiti [Guides and API documentation](https://help.getzep.com/graphiti/graphiti).
175
-
176
+ Visit the Zep knowledge base for Graphiti [Guides and API documentation](https://help.getzep.com/Graphiti/Graphiti).
176
177
 
177
178
  ## Status and Roadmap
178
179
 
179
- graphiti is under active development. We aim to maintain API stability while working on:
180
+ Graphiti is under active development. We aim to maintain API stability while working on:
180
181
 
181
- - [ ] Implementing node and edge CRUD operations
182
+ - [x] Implementing node and edge CRUD operations
182
183
  - [ ] Improving performance and scalability
183
184
  - [ ] Achieving good performance with different LLM and embedding models
184
185
  - [ ] Creating a dedicated embedder interface
@@ -188,12 +189,11 @@ graphiti is under active development. We aim to maintain API stability while wor
188
189
  - [ ] Enhancing retrieval capabilities with more robust and configurable options
189
190
  - [ ] Expanding test coverage to ensure reliability and catch edge cases
190
191
 
191
-
192
192
  ## Contributing
193
193
 
194
- We encourage and appreciate all forms of contributions, whether it's code, documentation, addressing GitHub Issues, or answering questions in the graphiti Discord channel. For detailed guidelines on code contributions, please refer to [CONTRIBUTING](CONTRIBUTING.md).
194
+ We encourage and appreciate all forms of contributions, whether it's code, documentation, addressing GitHub Issues, or answering questions in the Graphiti Discord channel. For detailed guidelines on code contributions, please refer to [CONTRIBUTING](CONTRIBUTING.md).
195
195
 
196
196
  ## Support
197
197
 
198
- Join the [Zep Discord server](https://discord.com/invite/W8Kw6bsgXQ) and make your way to the **#graphiti** channel!
198
+ Join the [Zep Discord server](https://discord.com/invite/W8Kw6bsgXQ) and make your way to the **#Graphiti** channel!
199
199
 
@@ -1,58 +1,61 @@
1
1
  <div align="center">
2
2
 
3
- # graphiti
3
+ # Graphiti
4
4
 
5
5
  ## Temporal Knowledge Graphs for Agentic Applications
6
6
 
7
7
  <br />
8
8
 
9
9
  [![Discord](https://dcbadge.vercel.app/api/server/W8Kw6bsgXQ?style=flat)](https://discord.com/invite/W8Kw6bsgXQ)
10
- [![Lint](https://github.com/getzep/graphiti/actions/workflows/lint.yml/badge.svg)](https://github.com/getzep/graphiti/actions/workflows/lint.yml)
11
- [![Unit Tests](https://github.com/getzep/graphiti/actions/workflows/unit_tests.yml/badge.svg)](https://github.com/getzep/graphiti/actions/workflows/unit_tests.yml)
12
- [![MyPy Check](https://github.com/getzep/graphiti/actions/workflows/typecheck.yml/badge.svg)](https://github.com/getzep/graphiti/actions/workflows/typecheck.yml)
13
- [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/getzep/graphiti)
10
+ [![Lint](https://github.com/getzep/Graphiti/actions/workflows/lint.yml/badge.svg)](https://github.com/getzep/Graphiti/actions/workflows/lint.yml)
11
+ [![Unit Tests](https://github.com/getzep/Graphiti/actions/workflows/unit_tests.yml/badge.svg)](https://github.com/getzep/Graphiti/actions/workflows/unit_tests.yml)
12
+ [![MyPy Check](https://github.com/getzep/Graphiti/actions/workflows/typecheck.yml/badge.svg)](https://github.com/getzep/Graphiti/actions/workflows/typecheck.yml)
13
+ [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/getzep/Graphiti)
14
14
 
15
15
  <br />
16
16
 
17
17
  </div>
18
18
 
19
- graphiti builds dynamic, temporally aware knowledge graphs that represent complex, evolving relationships between entities over time. graphiti ingests both unstructured and structured data and the resulting graph may be queried using a fusion of time, full-text, semantic, and graph algorithm approaches.
19
+ Graphiti builds dynamic, temporally aware Knowledge Graphs that represent complex, evolving relationships between entities over time. Graphiti ingests both unstructured and structured data, and the resulting graph may be queried using a fusion of time, full-text, semantic, and graph algorithm approaches.
20
20
 
21
21
  <br />
22
22
 
23
-
24
23
  <p align="center">
25
- <img src="/images/graphiti-intro-slides-stock-2.gif" alt="graphiti demo slides" width="700px">
24
+ <img src="/images/graphiti-graph-intro.gif" alt="Graphiti temporal walkthrough" width="700px">
26
25
  </p>
27
26
 
28
27
  <br />
29
-
30
- With graphiti, you can build LLM applications such as:
28
+
29
+ Graphiti helps you create and query Knowledge Graphs that evolve over time. A knowledge graph is a network of interconnected facts, such as _“Kendra loves Adidas shoes.”_ Each fact is a “triplet” represented by two entities, or nodes (_”Kendra”_, _“Adidas shoes”_), and their relationship, or edge (_”loves”_). Knowledge Graphs have been explored extensively for information retrieval. What makes Graphiti unique is its ability to autonomously build a knowledge graph while handling changing relationships and maintaining historical context.
30
+
31
+ With Graphiti, you can build LLM applications such as:
31
32
 
32
33
  - Assistants that learn from user interactions, fusing personal knowledge with dynamic data from business systems like CRMs and billing platforms.
33
34
  - Agents that autonomously execute complex tasks, reasoning with state changes from multiple dynamic sources.
34
35
 
35
- graphiti supports a wide range of applications in sales, customer service, health, finance, and more, enabling long-term recall and state-based reasoning for both assistants and agents.
36
-
37
- ## Why graphiti?
36
+ Graphiti supports a wide range of applications in sales, customer service, health, finance, and more, enabling long-term recall and state-based reasoning for both assistants and agents.
38
37
 
39
- graphiti is designed for dynamic data and agentic use:
38
+ ## Why Graphiti?
40
39
 
41
- - **Smart Graph Updates**: Automatically evaluates new entities against the current graph, revising both to reflect the latest context.
42
- - **Rich Edge Semantics**: Generates human-readable, semantic, and full-text searchable representations for edges during graph construction, enabling search and enhancing interpretability.
43
- - **Temporal Awareness**: Extracts and updates time-based edge metadata from input data, enabling reasoning over changing relationships.
44
- - **Hybrid Search**: Offers semantic, BM25, and graph-based search with the ability to fuse results.
45
- - **Fast**: Search results in < 100ms, with latency primarily determined by the 3rd-party embedding API call.
46
- - **Schema Consistency**: Maintains a coherent graph structure by reusing existing schema, preventing unnecessary proliferation of node and edge types.
40
+ We were intrigued by Microsoft’s GraphRAG, which expanded on RAG text chunking by using a graph to better model a document corpus and making this representation available via semantic and graph search techniques. However, GraphRAG did not address our core problem: It's primarily designed for static documents and doesn't inherently handle temporal aspects of data.
47
41
 
42
+ Graphiti is designed from the ground up to handle constantly changing information, hybrid semantic and graph search, and scale:
48
43
 
49
- ## graphiti and Zep Memory
44
+ - **Temporal Awareness:** Tracks changes in facts and relationships over time, enabling point-in-time queries. Graph edges include temporal metadata to record relationship lifecycles.
45
+ - **Episodic Processing:** Ingests data as discrete episodes, maintaining data provenance and allowing incremental entity and relationship extraction.
46
+ - **Hybrid Search:** Combines semantic and BM25 full-text search, with the ability to rerank results by distance from a central node e.g. “Kendra”.
47
+ - **Scalable:** Designed for processing large datasets, with parallelization of LLM calls for bulk processing while preserving the chronology of events.
48
+ - **Supports Varied Sources:** Can ingest both unstructured text and structured JSON data.
50
49
 
51
- graphiti powers the core of [Zep's memory layer](https://www.getzep.com) for LLM-powered Assistants and Agents.
50
+ <p align="center">
51
+ <img src="/images/graphiti-intro-slides-stock-2.gif" alt="Graphiti structured + unstructured demo" width="700px">
52
+ </p>
52
53
 
53
- We're excited to open-source graphiti, believing its potential reaches far beyond memory applications.
54
+ ## Graphiti and Zep Memory
54
55
 
56
+ Graphiti powers the core of [Zep's memory layer](https://www.getzep.com) for LLM-powered Assistants and Agents.
55
57
 
58
+ We're excited to open-source Graphiti, believing its potential reaches far beyond memory applications.
56
59
 
57
60
  ## Installation
58
61
 
@@ -79,12 +82,10 @@ or
79
82
  poetry add graphiti-core
80
83
  ```
81
84
 
82
-
83
-
84
85
  ## Quick Start
85
86
 
86
87
  > [!IMPORTANT]
87
- > graphiti uses OpenAI for LLM inference and embedding. Ensure that an `OPENAI_API_KEY` is set in your environment. Support for Anthropic and Groq LLM inferences is available, too.
88
+ > Graphiti uses OpenAI for LLM inference and embedding. Ensure that an `OPENAI_API_KEY` is set in your environment. Support for Anthropic and Groq LLM inferences is available, too.
88
89
 
89
90
  ```python
90
91
  from graphiti_core import Graphiti
@@ -94,6 +95,9 @@ from datetime import datetime
94
95
  # Initialize Graphiti
95
96
  graphiti = Graphiti("bolt://localhost:7687", "neo4j", "password")
96
97
 
98
+ # Initialize the graph database with Graphiti's indices. This only needs to be done once.
99
+ graphiti.build_indices_and_constraints()
100
+
97
101
  # Add episodes
98
102
  episodes = [
99
103
  "Kamala Harris is the Attorney General of California. She was previously "
@@ -139,24 +143,21 @@ results = await graphiti.search('Who was the California Attorney General?')
139
143
  # Rerank search results based on graph distance
140
144
  # Provide a node UUID to prioritize results closer to that node in the graph.
141
145
  # Results are weighted by their proximity, with distant edges receiving lower scores.
142
- await client.search('Who was the California Attorney General?', center_node_uuid)
146
+ await graphiti.search('Who was the California Attorney General?', center_node_uuid)
143
147
 
144
148
  # Close the connection
145
149
  graphiti.close()
146
150
  ```
147
151
 
148
-
149
-
150
152
  ## Documentation
151
153
 
152
- Visit the Zep knowledge base for graphiti [Guides and API documentation](https://help.getzep.com/graphiti/graphiti).
153
-
154
+ Visit the Zep knowledge base for Graphiti [Guides and API documentation](https://help.getzep.com/Graphiti/Graphiti).
154
155
 
155
156
  ## Status and Roadmap
156
157
 
157
- graphiti is under active development. We aim to maintain API stability while working on:
158
+ Graphiti is under active development. We aim to maintain API stability while working on:
158
159
 
159
- - [ ] Implementing node and edge CRUD operations
160
+ - [x] Implementing node and edge CRUD operations
160
161
  - [ ] Improving performance and scalability
161
162
  - [ ] Achieving good performance with different LLM and embedding models
162
163
  - [ ] Creating a dedicated embedder interface
@@ -166,11 +167,10 @@ graphiti is under active development. We aim to maintain API stability while wor
166
167
  - [ ] Enhancing retrieval capabilities with more robust and configurable options
167
168
  - [ ] Expanding test coverage to ensure reliability and catch edge cases
168
169
 
169
-
170
170
  ## Contributing
171
171
 
172
- We encourage and appreciate all forms of contributions, whether it's code, documentation, addressing GitHub Issues, or answering questions in the graphiti Discord channel. For detailed guidelines on code contributions, please refer to [CONTRIBUTING](CONTRIBUTING.md).
172
+ We encourage and appreciate all forms of contributions, whether it's code, documentation, addressing GitHub Issues, or answering questions in the Graphiti Discord channel. For detailed guidelines on code contributions, please refer to [CONTRIBUTING](CONTRIBUTING.md).
173
173
 
174
174
  ## Support
175
175
 
176
- Join the [Zep Discord server](https://discord.com/invite/W8Kw6bsgXQ) and make your way to the **#graphiti** channel!
176
+ Join the [Zep Discord server](https://discord.com/invite/W8Kw6bsgXQ) and make your way to the **#Graphiti** channel!
@@ -29,6 +29,7 @@ from graphiti_core.llm_client.utils import generate_embedding
29
29
  from graphiti_core.nodes import EntityNode, EpisodeType, EpisodicNode
30
30
  from graphiti_core.search.search import Reranker, SearchConfig, SearchMethod, hybrid_search
31
31
  from graphiti_core.search.search_utils import (
32
+ RELEVANT_SCHEMA_LIMIT,
32
33
  get_relevant_edges,
33
34
  get_relevant_nodes,
34
35
  hybrid_node_search,
@@ -41,19 +42,23 @@ from graphiti_core.utils.bulk_utils import (
41
42
  RawEpisode,
42
43
  dedupe_edges_bulk,
43
44
  dedupe_nodes_bulk,
45
+ extract_edge_dates_bulk,
44
46
  extract_nodes_and_edges_bulk,
45
47
  resolve_edge_pointers,
46
48
  retrieve_previous_episodes_bulk,
47
49
  )
48
50
  from graphiti_core.utils.maintenance.edge_operations import (
49
- dedupe_extracted_edges,
50
51
  extract_edges,
52
+ resolve_extracted_edges,
51
53
  )
52
54
  from graphiti_core.utils.maintenance.graph_data_operations import (
53
55
  EPISODE_WINDOW_LEN,
54
56
  build_indices_and_constraints,
55
57
  )
56
- from graphiti_core.utils.maintenance.node_operations import dedupe_extracted_nodes, extract_nodes
58
+ from graphiti_core.utils.maintenance.node_operations import (
59
+ extract_nodes,
60
+ resolve_extracted_nodes,
61
+ )
57
62
  from graphiti_core.utils.maintenance.temporal_operations import (
58
63
  extract_edge_dates,
59
64
  invalidate_edges,
@@ -175,9 +180,9 @@ class Graphiti:
175
180
  await build_indices_and_constraints(self.driver)
176
181
 
177
182
  async def retrieve_episodes(
178
- self,
179
- reference_time: datetime,
180
- last_n: int = EPISODE_WINDOW_LEN,
183
+ self,
184
+ reference_time: datetime,
185
+ last_n: int = EPISODE_WINDOW_LEN,
181
186
  ) -> list[EpisodicNode]:
182
187
  """
183
188
  Retrieve the last n episodic nodes from the graph.
@@ -205,14 +210,14 @@ class Graphiti:
205
210
  return await retrieve_episodes(self.driver, reference_time, last_n)
206
211
 
207
212
  async def add_episode(
208
- self,
209
- name: str,
210
- episode_body: str,
211
- source_description: str,
212
- reference_time: datetime,
213
- source: EpisodeType = EpisodeType.message,
214
- success_callback: Callable | None = None,
215
- error_callback: Callable | None = None,
213
+ self,
214
+ name: str,
215
+ episode_body: str,
216
+ source_description: str,
217
+ reference_time: datetime,
218
+ source: EpisodeType = EpisodeType.message,
219
+ success_callback: Callable | None = None,
220
+ error_callback: Callable | None = None,
216
221
  ):
217
222
  """
218
223
  Process an episode and update the graph.
@@ -263,7 +268,6 @@ class Graphiti:
263
268
 
264
269
  nodes: list[EntityNode] = []
265
270
  entity_edges: list[EntityEdge] = []
266
- episodic_edges: list[EpisodicEdge] = []
267
271
  embedder = self.llm_client.get_embedder()
268
272
  now = datetime.now()
269
273
 
@@ -278,6 +282,8 @@ class Graphiti:
278
282
  valid_at=reference_time,
279
283
  )
280
284
 
285
+ # Extract entities as nodes
286
+
281
287
  extracted_nodes = await extract_nodes(self.llm_client, episode, previous_episodes)
282
288
  logger.info(f'Extracted nodes: {[(n.name, n.uuid) for n in extracted_nodes]}')
283
289
 
@@ -286,59 +292,82 @@ class Graphiti:
286
292
  await asyncio.gather(
287
293
  *[node.generate_name_embedding(embedder) for node in extracted_nodes]
288
294
  )
289
- existing_nodes = await get_relevant_nodes(extracted_nodes, self.driver)
295
+
296
+ # Resolve extracted nodes with nodes already in the graph
297
+ existing_nodes_lists: list[list[EntityNode]] = list(
298
+ await asyncio.gather(
299
+ *[get_relevant_nodes([node], self.driver) for node in extracted_nodes]
300
+ )
301
+ )
302
+
290
303
  logger.info(f'Extracted nodes: {[(n.name, n.uuid) for n in extracted_nodes]}')
291
- touched_nodes, _, brand_new_nodes = await dedupe_extracted_nodes(
292
- self.llm_client, extracted_nodes, existing_nodes
304
+
305
+ mentioned_nodes, _ = await resolve_extracted_nodes(
306
+ self.llm_client, extracted_nodes, existing_nodes_lists
293
307
  )
294
- logger.info(f'Adjusted touched nodes: {[(n.name, n.uuid) for n in touched_nodes]}')
295
- nodes.extend(touched_nodes)
308
+ logger.info(f'Adjusted mentioned nodes: {[(n.name, n.uuid) for n in mentioned_nodes]}')
309
+ nodes.extend(mentioned_nodes)
296
310
 
311
+ # Extract facts as edges given entity nodes
297
312
  extracted_edges = await extract_edges(
298
- self.llm_client, episode, touched_nodes, previous_episodes
313
+ self.llm_client, episode, mentioned_nodes, previous_episodes
299
314
  )
300
315
 
316
+ # calculate embeddings
301
317
  await asyncio.gather(*[edge.generate_embedding(embedder) for edge in extracted_edges])
302
318
 
303
- existing_edges = await get_relevant_edges(extracted_edges, self.driver)
304
- logger.info(f'Existing edges: {[(e.name, e.uuid) for e in existing_edges]}')
319
+ # Resolve extracted edges with edges already in the graph
320
+ existing_edges_list: list[list[EntityEdge]] = list(
321
+ await asyncio.gather(
322
+ *[
323
+ get_relevant_edges(
324
+ [edge],
325
+ self.driver,
326
+ RELEVANT_SCHEMA_LIMIT,
327
+ edge.source_node_uuid,
328
+ edge.target_node_uuid,
329
+ )
330
+ for edge in extracted_edges
331
+ ]
332
+ )
333
+ )
334
+ logger.info(
335
+ f'Existing edges lists: {[(e.name, e.uuid) for edges_lst in existing_edges_list for e in edges_lst]}'
336
+ )
305
337
  logger.info(f'Extracted edges: {[(e.name, e.uuid) for e in extracted_edges]}')
306
338
 
307
- deduped_edges = await dedupe_extracted_edges(
308
- self.llm_client,
309
- extracted_edges,
310
- existing_edges,
339
+ deduped_edges: list[EntityEdge] = await resolve_extracted_edges(
340
+ self.llm_client, extracted_edges, existing_edges_list
311
341
  )
312
342
 
313
- edge_touched_node_uuids = [n.uuid for n in brand_new_nodes]
314
- for edge in deduped_edges:
315
- edge_touched_node_uuids.append(edge.source_node_uuid)
316
- edge_touched_node_uuids.append(edge.target_node_uuid)
317
-
318
- for edge in deduped_edges:
319
- valid_at, invalid_at, _ = await extract_edge_dates(
320
- self.llm_client,
321
- edge,
322
- episode.valid_at,
323
- episode,
324
- previous_episodes,
325
- )
326
- edge.valid_at = valid_at
327
- edge.invalid_at = invalid_at
328
- if edge.invalid_at:
329
- edge.expired_at = datetime.now()
330
- for edge in existing_edges:
331
- valid_at, invalid_at, _ = await extract_edge_dates(
332
- self.llm_client,
333
- edge,
334
- episode.valid_at,
335
- episode,
336
- previous_episodes,
337
- )
343
+ # Extract dates for the newly extracted edges
344
+ edge_dates = await asyncio.gather(
345
+ *[
346
+ extract_edge_dates(
347
+ self.llm_client,
348
+ edge,
349
+ episode,
350
+ previous_episodes,
351
+ )
352
+ for edge in deduped_edges
353
+ ]
354
+ )
355
+
356
+ for i, edge in enumerate(deduped_edges):
357
+ valid_at = edge_dates[i][0]
358
+ invalid_at = edge_dates[i][1]
359
+
338
360
  edge.valid_at = valid_at
339
361
  edge.invalid_at = invalid_at
340
- if edge.invalid_at:
341
- edge.expired_at = datetime.now()
362
+ if edge.invalid_at is not None:
363
+ edge.expired_at = now
364
+
365
+ entity_edges.extend(deduped_edges)
366
+
367
+ existing_edges: list[EntityEdge] = [
368
+ e for edge_lst in existing_edges_list for e in edge_lst
369
+ ]
370
+
342
371
  (
343
372
  old_edges_with_nodes_pending_invalidation,
344
373
  new_edges_with_nodes,
@@ -361,30 +390,18 @@ class Graphiti:
361
390
  for deduped_edge in deduped_edges:
362
391
  if deduped_edge.uuid == edge.uuid:
363
392
  deduped_edge.expired_at = edge.expired_at
364
- edge_touched_node_uuids.append(edge.source_node_uuid)
365
- edge_touched_node_uuids.append(edge.target_node_uuid)
366
393
  logger.info(f'Invalidated edges: {[(e.name, e.uuid) for e in invalidated_edges]}')
367
394
 
368
- edges_to_save = existing_edges + deduped_edges
369
-
370
- entity_edges.extend(edges_to_save)
371
-
372
- edge_touched_node_uuids = list(set(edge_touched_node_uuids))
373
- involved_nodes = [node for node in nodes if node.uuid in edge_touched_node_uuids]
374
-
375
- logger.info(f'Edge touched nodes: {[(n.name, n.uuid) for n in involved_nodes]}')
395
+ entity_edges.extend(existing_edges)
376
396
 
377
397
  logger.info(f'Deduped edges: {[(e.name, e.uuid) for e in deduped_edges]}')
378
398
 
379
- episodic_edges.extend(
380
- build_episodic_edges(
381
- # There may be an overlap between new_nodes and affected_nodes, so we're deduplicating them
382
- involved_nodes,
383
- episode,
384
- now,
385
- )
399
+ episodic_edges: list[EpisodicEdge] = build_episodic_edges(
400
+ mentioned_nodes,
401
+ episode,
402
+ now,
386
403
  )
387
- # Important to append the episode to the nodes at the end so that self referencing episodic edges are not built
404
+
388
405
  logger.info(f'Built episodic edges: {episodic_edges}')
389
406
 
390
407
  # Future optimization would be using batch operations to save nodes and edges
@@ -395,9 +412,7 @@ class Graphiti:
395
412
 
396
413
  end = time()
397
414
  logger.info(f'Completed add_episode in {(end - start) * 1000} ms')
398
- # for node in nodes:
399
- # if isinstance(node, EntityNode):
400
- # await node.update_summary(self.driver)
415
+
401
416
  if success_callback:
402
417
  await success_callback(episode)
403
418
  except Exception as e:
@@ -407,8 +422,8 @@ class Graphiti:
407
422
  raise e
408
423
 
409
424
  async def add_episode_bulk(
410
- self,
411
- bulk_episodes: list[RawEpisode],
425
+ self,
426
+ bulk_episodes: list[RawEpisode],
412
427
  ):
413
428
  """
414
429
  Process multiple episodes in bulk and update the graph.
@@ -481,15 +496,18 @@ class Graphiti:
481
496
  *[edge.generate_embedding(embedder) for edge in extracted_edges],
482
497
  )
483
498
 
484
- # Dedupe extracted nodes
485
- nodes, uuid_map = await dedupe_nodes_bulk(self.driver, self.llm_client, extracted_nodes)
499
+ # Dedupe extracted nodes, compress extracted edges
500
+ (nodes, uuid_map), extracted_edges_timestamped = await asyncio.gather(
501
+ dedupe_nodes_bulk(self.driver, self.llm_client, extracted_nodes),
502
+ extract_edge_dates_bulk(self.llm_client, extracted_edges, episode_pairs),
503
+ )
486
504
 
487
505
  # save nodes to KG
488
506
  await asyncio.gather(*[node.save(self.driver) for node in nodes])
489
507
 
490
508
  # re-map edge pointers so that they don't point to discard dupe nodes
491
509
  extracted_edges_with_resolved_pointers: list[EntityEdge] = resolve_edge_pointers(
492
- extracted_edges, uuid_map
510
+ extracted_edges_timestamped, uuid_map
493
511
  )
494
512
  episodic_edges_with_resolved_pointers: list[EpisodicEdge] = resolve_edge_pointers(
495
513
  episodic_edges, uuid_map
@@ -569,17 +587,19 @@ class Graphiti:
569
587
  return edges
570
588
 
571
589
  async def _search(
572
- self,
573
- query: str,
574
- timestamp: datetime,
575
- config: SearchConfig,
576
- center_node_uuid: str | None = None,
590
+ self,
591
+ query: str,
592
+ timestamp: datetime,
593
+ config: SearchConfig,
594
+ center_node_uuid: str | None = None,
577
595
  ):
578
596
  return await hybrid_search(
579
597
  self.driver, self.llm_client.get_embedder(), query, timestamp, config, center_node_uuid
580
598
  )
581
599
 
582
- async def get_nodes_by_query(self, query: str, limit: int | None = None) -> list[EntityNode]:
600
+ async def get_nodes_by_query(
601
+ self, query: str, limit: int = RELEVANT_SCHEMA_LIMIT
602
+ ) -> list[EntityNode]:
583
603
  """
584
604
  Retrieve nodes from the graph database based on a text query.
585
605
 
@@ -60,6 +60,5 @@ class OpenAIClient(LLMClient):
60
60
  result = response.choices[0].message.content or ''
61
61
  return json.loads(result)
62
62
  except Exception as e:
63
- print(openai_messages)
64
63
  logger.error(f'Error in generating LLM response: {e}')
65
64
  raise