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.
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/PKG-INFO +38 -38
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/README.md +36 -36
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/graphiti.py +105 -85
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/llm_client/openai_client.py +0 -1
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/prompts/dedupe_edges.py +46 -8
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/prompts/dedupe_nodes.py +61 -13
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/prompts/extract_edges.py +2 -1
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/prompts/extract_nodes.py +2 -0
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/search/search.py +8 -8
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/search/search_utils.py +44 -26
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/utils/bulk_utils.py +138 -20
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/utils/maintenance/edge_operations.py +76 -9
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/utils/maintenance/node_operations.py +98 -40
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/utils/maintenance/temporal_operations.py +3 -4
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/utils/utils.py +22 -1
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/pyproject.toml +8 -4
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/LICENSE +0 -0
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/__init__.py +0 -0
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/edges.py +0 -0
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/helpers.py +0 -0
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/llm_client/__init__.py +0 -0
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/llm_client/anthropic_client.py +0 -0
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/llm_client/client.py +0 -0
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/llm_client/config.py +0 -0
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/llm_client/groq_client.py +0 -0
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/llm_client/utils.py +0 -0
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/nodes.py +0 -0
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/prompts/__init__.py +0 -0
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/prompts/extract_edge_dates.py +0 -0
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/prompts/invalidate_edges.py +0 -0
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/prompts/lib.py +0 -0
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/prompts/models.py +0 -0
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/search/__init__.py +0 -0
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/utils/__init__.py +0 -0
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/utils/maintenance/__init__.py +0 -0
- {graphiti_core-0.1.0 → graphiti_core-0.2.0}/graphiti_core/utils/maintenance/graph_data_operations.py +0 -0
- {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.
|
|
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 (
|
|
20
|
+
Requires-Dist: tenacity (<9.0.0)
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
|
|
23
23
|
<div align="center">
|
|
24
24
|
|
|
25
|
-
#
|
|
25
|
+
# Graphiti
|
|
26
26
|
|
|
27
27
|
## Temporal Knowledge Graphs for Agentic Applications
|
|
28
28
|
|
|
29
29
|
<br />
|
|
30
30
|
|
|
31
31
|
[](https://discord.com/invite/W8Kw6bsgXQ)
|
|
32
|
-
[](https://codespaces.new/getzep/
|
|
32
|
+
[](https://github.com/getzep/Graphiti/actions/workflows/lint.yml)
|
|
33
|
+
[](https://github.com/getzep/Graphiti/actions/workflows/unit_tests.yml)
|
|
34
|
+
[](https://github.com/getzep/Graphiti/actions/workflows/typecheck.yml)
|
|
35
|
+
[](https://codespaces.new/getzep/Graphiti)
|
|
36
36
|
|
|
37
37
|
<br />
|
|
38
38
|
|
|
39
39
|
</div>
|
|
40
40
|
|
|
41
|
-
|
|
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
|
|
46
|
+
<img src="/images/graphiti-graph-intro.gif" alt="Graphiti temporal walkthrough" width="700px">
|
|
48
47
|
</p>
|
|
49
48
|
|
|
50
49
|
<br />
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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
|
-
|
|
60
|
+
## Why Graphiti?
|
|
62
61
|
|
|
63
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
>
|
|
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
|
|
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
|
|
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
|
-
|
|
180
|
+
Graphiti is under active development. We aim to maintain API stability while working on:
|
|
180
181
|
|
|
181
|
-
- [
|
|
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
|
|
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 **#
|
|
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
|
-
#
|
|
3
|
+
# Graphiti
|
|
4
4
|
|
|
5
5
|
## Temporal Knowledge Graphs for Agentic Applications
|
|
6
6
|
|
|
7
7
|
<br />
|
|
8
8
|
|
|
9
9
|
[](https://discord.com/invite/W8Kw6bsgXQ)
|
|
10
|
-
[](https://codespaces.new/getzep/
|
|
10
|
+
[](https://github.com/getzep/Graphiti/actions/workflows/lint.yml)
|
|
11
|
+
[](https://github.com/getzep/Graphiti/actions/workflows/unit_tests.yml)
|
|
12
|
+
[](https://github.com/getzep/Graphiti/actions/workflows/typecheck.yml)
|
|
13
|
+
[](https://codespaces.new/getzep/Graphiti)
|
|
14
14
|
|
|
15
15
|
<br />
|
|
16
16
|
|
|
17
17
|
</div>
|
|
18
18
|
|
|
19
|
-
|
|
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
|
|
24
|
+
<img src="/images/graphiti-graph-intro.gif" alt="Graphiti temporal walkthrough" width="700px">
|
|
26
25
|
</p>
|
|
27
26
|
|
|
28
27
|
<br />
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
|
|
38
|
+
## Why Graphiti?
|
|
40
39
|
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
>
|
|
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
|
|
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
|
|
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
|
-
|
|
158
|
+
Graphiti is under active development. We aim to maintain API stability while working on:
|
|
158
159
|
|
|
159
|
-
- [
|
|
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
|
|
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 **#
|
|
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
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
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
|
-
|
|
292
|
-
|
|
304
|
+
|
|
305
|
+
mentioned_nodes, _ = await resolve_extracted_nodes(
|
|
306
|
+
self.llm_client, extracted_nodes, existing_nodes_lists
|
|
293
307
|
)
|
|
294
|
-
logger.info(f'Adjusted
|
|
295
|
-
nodes.extend(
|
|
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,
|
|
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
|
-
|
|
304
|
-
|
|
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
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
episode,
|
|
384
|
-
now,
|
|
385
|
-
)
|
|
399
|
+
episodic_edges: list[EpisodicEdge] = build_episodic_edges(
|
|
400
|
+
mentioned_nodes,
|
|
401
|
+
episode,
|
|
402
|
+
now,
|
|
386
403
|
)
|
|
387
|
-
|
|
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
|
-
|
|
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
|
-
|
|
411
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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(
|
|
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
|
|