graphiti-core 0.14.0__py3-none-any.whl → 0.15.1__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.
- graphiti_core/cross_encoder/bge_reranker_client.py +12 -2
- graphiti_core/cross_encoder/gemini_reranker_client.py +158 -0
- graphiti_core/cross_encoder/openai_reranker_client.py +5 -3
- graphiti_core/driver/__init__.py +3 -1
- graphiti_core/driver/falkordb_driver.py +62 -25
- graphiti_core/driver/neo4j_driver.py +5 -3
- graphiti_core/embedder/gemini.py +14 -2
- graphiti_core/embedder/voyage.py +13 -2
- graphiti_core/graphiti.py +7 -2
- graphiti_core/helpers.py +3 -2
- graphiti_core/llm_client/anthropic_client.py +17 -4
- graphiti_core/llm_client/gemini_client.py +150 -25
- graphiti_core/llm_client/groq_client.py +14 -3
- graphiti_core/nodes.py +10 -2
- graphiti_core/search/search_filters.py +4 -5
- graphiti_core/search/search_utils.py +5 -9
- graphiti_core/utils/maintenance/community_operations.py +1 -1
- graphiti_core/utils/maintenance/edge_operations.py +1 -1
- graphiti_core/utils/maintenance/graph_data_operations.py +4 -5
- {graphiti_core-0.14.0.dist-info → graphiti_core-0.15.1.dist-info}/METADATA +66 -24
- {graphiti_core-0.14.0.dist-info → graphiti_core-0.15.1.dist-info}/RECORD +23 -22
- {graphiti_core-0.14.0.dist-info → graphiti_core-0.15.1.dist-info}/WHEEL +0 -0
- {graphiti_core-0.14.0.dist-info → graphiti_core-0.15.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -15,8 +15,18 @@ limitations under the License.
|
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
import asyncio
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
from typing import TYPE_CHECKING
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from sentence_transformers import CrossEncoder
|
|
22
|
+
else:
|
|
23
|
+
try:
|
|
24
|
+
from sentence_transformers import CrossEncoder
|
|
25
|
+
except ImportError:
|
|
26
|
+
raise ImportError(
|
|
27
|
+
'sentence-transformers is required for BGERerankerClient. '
|
|
28
|
+
'Install it with: pip install graphiti-core[sentence-transformers]'
|
|
29
|
+
) from None
|
|
20
30
|
|
|
21
31
|
from graphiti_core.cross_encoder.client import CrossEncoderClient
|
|
22
32
|
|
|
@@ -0,0 +1,158 @@
|
|
|
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
|
+
import logging
|
|
18
|
+
import re
|
|
19
|
+
from typing import TYPE_CHECKING
|
|
20
|
+
|
|
21
|
+
from ..helpers import semaphore_gather
|
|
22
|
+
from ..llm_client import LLMConfig, RateLimitError
|
|
23
|
+
from .client import CrossEncoderClient
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from google import genai
|
|
27
|
+
from google.genai import types
|
|
28
|
+
else:
|
|
29
|
+
try:
|
|
30
|
+
from google import genai
|
|
31
|
+
from google.genai import types
|
|
32
|
+
except ImportError:
|
|
33
|
+
raise ImportError(
|
|
34
|
+
'google-genai is required for GeminiRerankerClient. '
|
|
35
|
+
'Install it with: pip install graphiti-core[google-genai]'
|
|
36
|
+
) from None
|
|
37
|
+
|
|
38
|
+
logger = logging.getLogger(__name__)
|
|
39
|
+
|
|
40
|
+
DEFAULT_MODEL = 'gemini-2.5-flash-lite-preview-06-17'
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class GeminiRerankerClient(CrossEncoderClient):
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
config: LLMConfig | None = None,
|
|
47
|
+
client: 'genai.Client | None' = None,
|
|
48
|
+
):
|
|
49
|
+
"""
|
|
50
|
+
Initialize the GeminiRerankerClient with the provided configuration and client.
|
|
51
|
+
|
|
52
|
+
The Gemini Developer API does not yet support logprobs. Unlike the OpenAI reranker,
|
|
53
|
+
this reranker uses the Gemini API to perform direct relevance scoring of passages.
|
|
54
|
+
Each passage is scored individually on a 0-100 scale.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
config (LLMConfig | None): The configuration for the LLM client, including API key, model, base URL, temperature, and max tokens.
|
|
58
|
+
client (genai.Client | None): An optional async client instance to use. If not provided, a new genai.Client is created.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
if config is None:
|
|
62
|
+
config = LLMConfig()
|
|
63
|
+
|
|
64
|
+
self.config = config
|
|
65
|
+
if client is None:
|
|
66
|
+
self.client = genai.Client(api_key=config.api_key)
|
|
67
|
+
else:
|
|
68
|
+
self.client = client
|
|
69
|
+
|
|
70
|
+
async def rank(self, query: str, passages: list[str]) -> list[tuple[str, float]]:
|
|
71
|
+
"""
|
|
72
|
+
Rank passages based on their relevance to the query using direct scoring.
|
|
73
|
+
|
|
74
|
+
Each passage is scored individually on a 0-100 scale, then normalized to [0,1].
|
|
75
|
+
"""
|
|
76
|
+
if len(passages) <= 1:
|
|
77
|
+
return [(passage, 1.0) for passage in passages]
|
|
78
|
+
|
|
79
|
+
# Generate scoring prompts for each passage
|
|
80
|
+
scoring_prompts = []
|
|
81
|
+
for passage in passages:
|
|
82
|
+
prompt = f"""Rate how well this passage answers or relates to the query. Use a scale from 0 to 100.
|
|
83
|
+
|
|
84
|
+
Query: {query}
|
|
85
|
+
|
|
86
|
+
Passage: {passage}
|
|
87
|
+
|
|
88
|
+
Provide only a number between 0 and 100 (no explanation, just the number):"""
|
|
89
|
+
|
|
90
|
+
scoring_prompts.append(
|
|
91
|
+
[
|
|
92
|
+
types.Content(
|
|
93
|
+
role='user',
|
|
94
|
+
parts=[types.Part.from_text(text=prompt)],
|
|
95
|
+
),
|
|
96
|
+
]
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
# Execute all scoring requests concurrently - O(n) API calls
|
|
101
|
+
responses = await semaphore_gather(
|
|
102
|
+
*[
|
|
103
|
+
self.client.aio.models.generate_content(
|
|
104
|
+
model=self.config.model or DEFAULT_MODEL,
|
|
105
|
+
contents=prompt_messages, # type: ignore
|
|
106
|
+
config=types.GenerateContentConfig(
|
|
107
|
+
system_instruction='You are an expert at rating passage relevance. Respond with only a number from 0-100.',
|
|
108
|
+
temperature=0.0,
|
|
109
|
+
max_output_tokens=3,
|
|
110
|
+
),
|
|
111
|
+
)
|
|
112
|
+
for prompt_messages in scoring_prompts
|
|
113
|
+
]
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Extract scores and create results
|
|
117
|
+
results = []
|
|
118
|
+
for passage, response in zip(passages, responses, strict=True):
|
|
119
|
+
try:
|
|
120
|
+
if hasattr(response, 'text') and response.text:
|
|
121
|
+
# Extract numeric score from response
|
|
122
|
+
score_text = response.text.strip()
|
|
123
|
+
# Handle cases where model might return non-numeric text
|
|
124
|
+
score_match = re.search(r'\b(\d{1,3})\b', score_text)
|
|
125
|
+
if score_match:
|
|
126
|
+
score = float(score_match.group(1))
|
|
127
|
+
# Normalize to [0, 1] range and clamp to valid range
|
|
128
|
+
normalized_score = max(0.0, min(1.0, score / 100.0))
|
|
129
|
+
results.append((passage, normalized_score))
|
|
130
|
+
else:
|
|
131
|
+
logger.warning(
|
|
132
|
+
f'Could not extract numeric score from response: {score_text}'
|
|
133
|
+
)
|
|
134
|
+
results.append((passage, 0.0))
|
|
135
|
+
else:
|
|
136
|
+
logger.warning('Empty response from Gemini for passage scoring')
|
|
137
|
+
results.append((passage, 0.0))
|
|
138
|
+
except (ValueError, AttributeError) as e:
|
|
139
|
+
logger.warning(f'Error parsing score from Gemini response: {e}')
|
|
140
|
+
results.append((passage, 0.0))
|
|
141
|
+
|
|
142
|
+
# Sort by score in descending order (highest relevance first)
|
|
143
|
+
results.sort(reverse=True, key=lambda x: x[1])
|
|
144
|
+
return results
|
|
145
|
+
|
|
146
|
+
except Exception as e:
|
|
147
|
+
# Check if it's a rate limit error based on Gemini API error codes
|
|
148
|
+
error_message = str(e).lower()
|
|
149
|
+
if (
|
|
150
|
+
'rate limit' in error_message
|
|
151
|
+
or 'quota' in error_message
|
|
152
|
+
or 'resource_exhausted' in error_message
|
|
153
|
+
or '429' in str(e)
|
|
154
|
+
):
|
|
155
|
+
raise RateLimitError from e
|
|
156
|
+
|
|
157
|
+
logger.error(f'Error in generating LLM response: {e}')
|
|
158
|
+
raise
|
|
@@ -22,7 +22,7 @@ import openai
|
|
|
22
22
|
from openai import AsyncAzureOpenAI, AsyncOpenAI
|
|
23
23
|
|
|
24
24
|
from ..helpers import semaphore_gather
|
|
25
|
-
from ..llm_client import LLMConfig, RateLimitError
|
|
25
|
+
from ..llm_client import LLMConfig, OpenAIClient, RateLimitError
|
|
26
26
|
from ..prompts import Message
|
|
27
27
|
from .client import CrossEncoderClient
|
|
28
28
|
|
|
@@ -35,7 +35,7 @@ class OpenAIRerankerClient(CrossEncoderClient):
|
|
|
35
35
|
def __init__(
|
|
36
36
|
self,
|
|
37
37
|
config: LLMConfig | None = None,
|
|
38
|
-
client: AsyncOpenAI | AsyncAzureOpenAI | None = None,
|
|
38
|
+
client: AsyncOpenAI | AsyncAzureOpenAI | OpenAIClient | None = None,
|
|
39
39
|
):
|
|
40
40
|
"""
|
|
41
41
|
Initialize the OpenAIRerankerClient with the provided configuration and client.
|
|
@@ -45,7 +45,7 @@ class OpenAIRerankerClient(CrossEncoderClient):
|
|
|
45
45
|
|
|
46
46
|
Args:
|
|
47
47
|
config (LLMConfig | None): The configuration for the LLM client, including API key, model, base URL, temperature, and max tokens.
|
|
48
|
-
client (AsyncOpenAI | AsyncAzureOpenAI | None): An optional async client instance to use. If not provided, a new AsyncOpenAI client is created.
|
|
48
|
+
client (AsyncOpenAI | AsyncAzureOpenAI | OpenAIClient | None): An optional async client instance to use. If not provided, a new AsyncOpenAI client is created.
|
|
49
49
|
"""
|
|
50
50
|
if config is None:
|
|
51
51
|
config = LLMConfig()
|
|
@@ -53,6 +53,8 @@ class OpenAIRerankerClient(CrossEncoderClient):
|
|
|
53
53
|
self.config = config
|
|
54
54
|
if client is None:
|
|
55
55
|
self.client = AsyncOpenAI(api_key=config.api_key, base_url=config.base_url)
|
|
56
|
+
elif isinstance(client, OpenAIClient):
|
|
57
|
+
self.client = client.client
|
|
56
58
|
else:
|
|
57
59
|
self.client = client
|
|
58
60
|
|
graphiti_core/driver/__init__.py
CHANGED
|
@@ -15,12 +15,22 @@ limitations under the License.
|
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
import logging
|
|
18
|
-
from collections.abc import Coroutine
|
|
19
18
|
from datetime import datetime
|
|
20
|
-
from typing import Any
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
from falkordb
|
|
19
|
+
from typing import TYPE_CHECKING, Any
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from falkordb import Graph as FalkorGraph
|
|
23
|
+
from falkordb.asyncio import FalkorDB
|
|
24
|
+
else:
|
|
25
|
+
try:
|
|
26
|
+
from falkordb import Graph as FalkorGraph
|
|
27
|
+
from falkordb.asyncio import FalkorDB
|
|
28
|
+
except ImportError:
|
|
29
|
+
# If falkordb is not installed, raise an ImportError
|
|
30
|
+
raise ImportError(
|
|
31
|
+
'falkordb is required for FalkorDriver. '
|
|
32
|
+
'Install it with: pip install graphiti-core[falkordb]'
|
|
33
|
+
) from None
|
|
24
34
|
|
|
25
35
|
from graphiti_core.driver.driver import GraphDriver, GraphDriverSession
|
|
26
36
|
from graphiti_core.helpers import DEFAULT_DATABASE
|
|
@@ -52,11 +62,11 @@ class FalkorDriverSession(GraphDriverSession):
|
|
|
52
62
|
if isinstance(query, list):
|
|
53
63
|
for cypher, params in query:
|
|
54
64
|
params = convert_datetimes_to_strings(params)
|
|
55
|
-
await self.graph.query(str(cypher), params)
|
|
65
|
+
await self.graph.query(str(cypher), params) # type: ignore[reportUnknownArgumentType]
|
|
56
66
|
else:
|
|
57
67
|
params = dict(kwargs)
|
|
58
68
|
params = convert_datetimes_to_strings(params)
|
|
59
|
-
await self.graph.query(str(query), params)
|
|
69
|
+
await self.graph.query(str(query), params) # type: ignore[reportUnknownArgumentType]
|
|
60
70
|
# Assuming `graph.query` is async (ideal); otherwise, wrap in executor
|
|
61
71
|
return None
|
|
62
72
|
|
|
@@ -66,22 +76,30 @@ class FalkorDriver(GraphDriver):
|
|
|
66
76
|
|
|
67
77
|
def __init__(
|
|
68
78
|
self,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
79
|
+
host: str = 'localhost',
|
|
80
|
+
port: int = 6379,
|
|
81
|
+
username: str | None = None,
|
|
82
|
+
password: str | None = None,
|
|
83
|
+
falkor_db: FalkorDB | None = None,
|
|
72
84
|
):
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
uri = f'{uri_parts[0]}://{user}:{password}@{uri_parts[1]}'
|
|
85
|
+
"""
|
|
86
|
+
Initialize the FalkorDB driver.
|
|
76
87
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
)
|
|
88
|
+
FalkorDB is a multi-tenant graph database.
|
|
89
|
+
To connect, provide the host and port.
|
|
90
|
+
The default parameters assume a local (on-premises) FalkorDB instance.
|
|
91
|
+
"""
|
|
92
|
+
super().__init__()
|
|
93
|
+
if falkor_db is not None:
|
|
94
|
+
# If a FalkorDB instance is provided, use it directly
|
|
95
|
+
self.client = falkor_db
|
|
96
|
+
else:
|
|
97
|
+
self.client = FalkorDB(host=host, port=port, username=username, password=password)
|
|
80
98
|
|
|
81
99
|
def _get_graph(self, graph_name: str | None) -> FalkorGraph:
|
|
82
|
-
# FalkorDB requires a non-None database name for multi-tenant graphs; the default is
|
|
100
|
+
# FalkorDB requires a non-None database name for multi-tenant graphs; the default is DEFAULT_DATABASE
|
|
83
101
|
if graph_name is None:
|
|
84
|
-
graph_name =
|
|
102
|
+
graph_name = DEFAULT_DATABASE
|
|
85
103
|
return self.client.select_graph(graph_name)
|
|
86
104
|
|
|
87
105
|
async def execute_query(self, cypher_query_, **kwargs: Any):
|
|
@@ -92,7 +110,7 @@ class FalkorDriver(GraphDriver):
|
|
|
92
110
|
params = convert_datetimes_to_strings(dict(kwargs))
|
|
93
111
|
|
|
94
112
|
try:
|
|
95
|
-
result = await graph.query(cypher_query_, params)
|
|
113
|
+
result = await graph.query(cypher_query_, params) # type: ignore[reportUnknownArgumentType]
|
|
96
114
|
except Exception as e:
|
|
97
115
|
if 'already indexed' in str(e):
|
|
98
116
|
# check if index already exists
|
|
@@ -102,17 +120,36 @@ class FalkorDriver(GraphDriver):
|
|
|
102
120
|
raise
|
|
103
121
|
|
|
104
122
|
# Convert the result header to a list of strings
|
|
105
|
-
header = [h[1]
|
|
106
|
-
|
|
123
|
+
header = [h[1] for h in result.header]
|
|
124
|
+
|
|
125
|
+
# Convert FalkorDB's result format (list of lists) to the format expected by Graphiti (list of dicts)
|
|
126
|
+
records = []
|
|
127
|
+
for row in result.result_set:
|
|
128
|
+
record = {}
|
|
129
|
+
for i, field_name in enumerate(header):
|
|
130
|
+
if i < len(row):
|
|
131
|
+
record[field_name] = row[i]
|
|
132
|
+
else:
|
|
133
|
+
# If there are more fields in header than values in row, set to None
|
|
134
|
+
record[field_name] = None
|
|
135
|
+
records.append(record)
|
|
136
|
+
|
|
137
|
+
return records, header, None
|
|
107
138
|
|
|
108
139
|
def session(self, database: str | None) -> GraphDriverSession:
|
|
109
140
|
return FalkorDriverSession(self._get_graph(database))
|
|
110
141
|
|
|
111
142
|
async def close(self) -> None:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
143
|
+
"""Close the driver connection."""
|
|
144
|
+
if hasattr(self.client, 'aclose'):
|
|
145
|
+
await self.client.aclose() # type: ignore[reportUnknownMemberType]
|
|
146
|
+
elif hasattr(self.client.connection, 'aclose'):
|
|
147
|
+
await self.client.connection.aclose()
|
|
148
|
+
elif hasattr(self.client.connection, 'close'):
|
|
149
|
+
await self.client.connection.close()
|
|
150
|
+
|
|
151
|
+
async def delete_all_indexes(self, database_: str = DEFAULT_DATABASE) -> None:
|
|
152
|
+
await self.execute_query(
|
|
116
153
|
'CALL db.indexes() YIELD name DROP INDEX name',
|
|
117
154
|
database_=database_,
|
|
118
155
|
)
|
|
@@ -18,7 +18,7 @@ import logging
|
|
|
18
18
|
from collections.abc import Coroutine
|
|
19
19
|
from typing import Any
|
|
20
20
|
|
|
21
|
-
from neo4j import AsyncGraphDatabase
|
|
21
|
+
from neo4j import AsyncGraphDatabase, EagerResult
|
|
22
22
|
from typing_extensions import LiteralString
|
|
23
23
|
|
|
24
24
|
from graphiti_core.driver.driver import GraphDriver, GraphDriverSession
|
|
@@ -42,7 +42,7 @@ class Neo4jDriver(GraphDriver):
|
|
|
42
42
|
auth=(user or '', password or ''),
|
|
43
43
|
)
|
|
44
44
|
|
|
45
|
-
async def execute_query(self, cypher_query_: LiteralString, **kwargs: Any) ->
|
|
45
|
+
async def execute_query(self, cypher_query_: LiteralString, **kwargs: Any) -> EagerResult:
|
|
46
46
|
params = kwargs.pop('params', None)
|
|
47
47
|
result = await self.client.execute_query(cypher_query_, parameters_=params, **kwargs)
|
|
48
48
|
|
|
@@ -54,7 +54,9 @@ class Neo4jDriver(GraphDriver):
|
|
|
54
54
|
async def close(self) -> None:
|
|
55
55
|
return await self.client.close()
|
|
56
56
|
|
|
57
|
-
def delete_all_indexes(
|
|
57
|
+
def delete_all_indexes(
|
|
58
|
+
self, database_: str = DEFAULT_DATABASE
|
|
59
|
+
) -> Coroutine[Any, Any, EagerResult]:
|
|
58
60
|
return self.client.execute_query(
|
|
59
61
|
'CALL db.indexes() YIELD name DROP INDEX name',
|
|
60
62
|
database_=database_,
|
graphiti_core/embedder/gemini.py
CHANGED
|
@@ -15,9 +15,21 @@ limitations under the License.
|
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
from collections.abc import Iterable
|
|
18
|
+
from typing import TYPE_CHECKING
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from google import genai
|
|
22
|
+
from google.genai import types
|
|
23
|
+
else:
|
|
24
|
+
try:
|
|
25
|
+
from google import genai
|
|
26
|
+
from google.genai import types
|
|
27
|
+
except ImportError:
|
|
28
|
+
raise ImportError(
|
|
29
|
+
'google-genai is required for GeminiEmbedder. '
|
|
30
|
+
'Install it with: pip install graphiti-core[google-genai]'
|
|
31
|
+
) from None
|
|
18
32
|
|
|
19
|
-
from google import genai # type: ignore
|
|
20
|
-
from google.genai import types # type: ignore
|
|
21
33
|
from pydantic import Field
|
|
22
34
|
|
|
23
35
|
from .client import EmbedderClient, EmbedderConfig
|
graphiti_core/embedder/voyage.py
CHANGED
|
@@ -15,8 +15,19 @@ limitations under the License.
|
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
from collections.abc import Iterable
|
|
18
|
+
from typing import TYPE_CHECKING
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
import voyageai
|
|
22
|
+
else:
|
|
23
|
+
try:
|
|
24
|
+
import voyageai
|
|
25
|
+
except ImportError:
|
|
26
|
+
raise ImportError(
|
|
27
|
+
'voyageai is required for VoyageAIEmbedderClient. '
|
|
28
|
+
'Install it with: pip install graphiti-core[voyageai]'
|
|
29
|
+
) from None
|
|
18
30
|
|
|
19
|
-
import voyageai # type: ignore
|
|
20
31
|
from pydantic import Field
|
|
21
32
|
|
|
22
33
|
from .client import EmbedderClient, EmbedderConfig
|
|
@@ -38,7 +49,7 @@ class VoyageAIEmbedder(EmbedderClient):
|
|
|
38
49
|
if config is None:
|
|
39
50
|
config = VoyageAIEmbedderConfig()
|
|
40
51
|
self.config = config
|
|
41
|
-
self.client = voyageai.AsyncClient(api_key=config.api_key)
|
|
52
|
+
self.client = voyageai.AsyncClient(api_key=config.api_key) # type: ignore[reportUnknownMemberType]
|
|
42
53
|
|
|
43
54
|
async def create(
|
|
44
55
|
self, input_data: str | list[str] | Iterable[int] | Iterable[Iterable[int]]
|
graphiti_core/graphiti.py
CHANGED
|
@@ -101,7 +101,7 @@ class AddEpisodeResults(BaseModel):
|
|
|
101
101
|
class Graphiti:
|
|
102
102
|
def __init__(
|
|
103
103
|
self,
|
|
104
|
-
uri: str,
|
|
104
|
+
uri: str | None = None,
|
|
105
105
|
user: str | None = None,
|
|
106
106
|
password: str | None = None,
|
|
107
107
|
llm_client: LLMClient | None = None,
|
|
@@ -162,7 +162,12 @@ class Graphiti:
|
|
|
162
162
|
Graphiti if you're using the default OpenAIClient.
|
|
163
163
|
"""
|
|
164
164
|
|
|
165
|
-
|
|
165
|
+
if graph_driver:
|
|
166
|
+
self.driver = graph_driver
|
|
167
|
+
else:
|
|
168
|
+
if uri is None:
|
|
169
|
+
raise ValueError('uri must be provided when graph_driver is None')
|
|
170
|
+
self.driver = Neo4jDriver(uri, user, password)
|
|
166
171
|
|
|
167
172
|
self.database = DEFAULT_DATABASE
|
|
168
173
|
self.store_raw_episode_content = store_raw_episode_content
|
graphiti_core/helpers.py
CHANGED
|
@@ -19,6 +19,7 @@ import os
|
|
|
19
19
|
import re
|
|
20
20
|
from collections.abc import Coroutine
|
|
21
21
|
from datetime import datetime
|
|
22
|
+
from typing import Any
|
|
22
23
|
|
|
23
24
|
import numpy as np
|
|
24
25
|
from dotenv import load_dotenv
|
|
@@ -31,7 +32,7 @@ from graphiti_core.errors import GroupIdValidationError
|
|
|
31
32
|
|
|
32
33
|
load_dotenv()
|
|
33
34
|
|
|
34
|
-
DEFAULT_DATABASE = os.getenv('DEFAULT_DATABASE', '
|
|
35
|
+
DEFAULT_DATABASE = os.getenv('DEFAULT_DATABASE', 'default_db')
|
|
35
36
|
USE_PARALLEL_RUNTIME = bool(os.getenv('USE_PARALLEL_RUNTIME', False))
|
|
36
37
|
SEMAPHORE_LIMIT = int(os.getenv('SEMAPHORE_LIMIT', 20))
|
|
37
38
|
MAX_REFLEXION_ITERATIONS = int(os.getenv('MAX_REFLEXION_ITERATIONS', 0))
|
|
@@ -99,7 +100,7 @@ def normalize_l2(embedding: list[float]) -> NDArray:
|
|
|
99
100
|
async def semaphore_gather(
|
|
100
101
|
*coroutines: Coroutine,
|
|
101
102
|
max_coroutines: int | None = None,
|
|
102
|
-
):
|
|
103
|
+
) -> list[Any]:
|
|
103
104
|
semaphore = asyncio.Semaphore(max_coroutines or SEMAPHORE_LIMIT)
|
|
104
105
|
|
|
105
106
|
async def _wrap_coroutine(coroutine):
|
|
@@ -19,11 +19,8 @@ import logging
|
|
|
19
19
|
import os
|
|
20
20
|
import typing
|
|
21
21
|
from json import JSONDecodeError
|
|
22
|
-
from typing import Literal
|
|
22
|
+
from typing import TYPE_CHECKING, Literal
|
|
23
23
|
|
|
24
|
-
import anthropic
|
|
25
|
-
from anthropic import AsyncAnthropic
|
|
26
|
-
from anthropic.types import MessageParam, ToolChoiceParam, ToolUnionParam
|
|
27
24
|
from pydantic import BaseModel, ValidationError
|
|
28
25
|
|
|
29
26
|
from ..prompts.models import Message
|
|
@@ -31,6 +28,22 @@ from .client import LLMClient
|
|
|
31
28
|
from .config import DEFAULT_MAX_TOKENS, LLMConfig, ModelSize
|
|
32
29
|
from .errors import RateLimitError, RefusalError
|
|
33
30
|
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
import anthropic
|
|
33
|
+
from anthropic import AsyncAnthropic
|
|
34
|
+
from anthropic.types import MessageParam, ToolChoiceParam, ToolUnionParam
|
|
35
|
+
else:
|
|
36
|
+
try:
|
|
37
|
+
import anthropic
|
|
38
|
+
from anthropic import AsyncAnthropic
|
|
39
|
+
from anthropic.types import MessageParam, ToolChoiceParam, ToolUnionParam
|
|
40
|
+
except ImportError:
|
|
41
|
+
raise ImportError(
|
|
42
|
+
'anthropic is required for AnthropicClient. '
|
|
43
|
+
'Install it with: pip install graphiti-core[anthropic]'
|
|
44
|
+
) from None
|
|
45
|
+
|
|
46
|
+
|
|
34
47
|
logger = logging.getLogger(__name__)
|
|
35
48
|
|
|
36
49
|
AnthropicModel = Literal[
|
|
@@ -17,19 +17,34 @@ limitations under the License.
|
|
|
17
17
|
import json
|
|
18
18
|
import logging
|
|
19
19
|
import typing
|
|
20
|
+
from typing import TYPE_CHECKING, ClassVar
|
|
20
21
|
|
|
21
|
-
from google import genai # type: ignore
|
|
22
|
-
from google.genai import types # type: ignore
|
|
23
22
|
from pydantic import BaseModel
|
|
24
23
|
|
|
25
24
|
from ..prompts.models import Message
|
|
26
|
-
from .client import LLMClient
|
|
25
|
+
from .client import MULTILINGUAL_EXTRACTION_RESPONSES, LLMClient
|
|
27
26
|
from .config import DEFAULT_MAX_TOKENS, LLMConfig, ModelSize
|
|
28
27
|
from .errors import RateLimitError
|
|
29
28
|
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from google import genai
|
|
31
|
+
from google.genai import types
|
|
32
|
+
else:
|
|
33
|
+
try:
|
|
34
|
+
from google import genai
|
|
35
|
+
from google.genai import types
|
|
36
|
+
except ImportError:
|
|
37
|
+
# If gemini client is not installed, raise an ImportError
|
|
38
|
+
raise ImportError(
|
|
39
|
+
'google-genai is required for GeminiClient. '
|
|
40
|
+
'Install it with: pip install graphiti-core[google-genai]'
|
|
41
|
+
) from None
|
|
42
|
+
|
|
43
|
+
|
|
30
44
|
logger = logging.getLogger(__name__)
|
|
31
45
|
|
|
32
|
-
DEFAULT_MODEL = 'gemini-2.
|
|
46
|
+
DEFAULT_MODEL = 'gemini-2.5-flash'
|
|
47
|
+
DEFAULT_SMALL_MODEL = 'models/gemini-2.5-flash-lite-preview-06-17'
|
|
33
48
|
|
|
34
49
|
|
|
35
50
|
class GeminiClient(LLMClient):
|
|
@@ -43,27 +58,34 @@ class GeminiClient(LLMClient):
|
|
|
43
58
|
model (str): The model name to use for generating responses.
|
|
44
59
|
temperature (float): The temperature to use for generating responses.
|
|
45
60
|
max_tokens (int): The maximum number of tokens to generate in a response.
|
|
46
|
-
|
|
61
|
+
thinking_config (types.ThinkingConfig | None): Optional thinking configuration for models that support it.
|
|
47
62
|
Methods:
|
|
48
|
-
__init__(config: LLMConfig | None = None, cache: bool = False):
|
|
49
|
-
Initializes the GeminiClient with the provided configuration and
|
|
63
|
+
__init__(config: LLMConfig | None = None, cache: bool = False, thinking_config: types.ThinkingConfig | None = None):
|
|
64
|
+
Initializes the GeminiClient with the provided configuration, cache setting, and optional thinking config.
|
|
50
65
|
|
|
51
66
|
_generate_response(messages: list[Message]) -> dict[str, typing.Any]:
|
|
52
67
|
Generates a response from the language model based on the provided messages.
|
|
53
68
|
"""
|
|
54
69
|
|
|
70
|
+
# Class-level constants
|
|
71
|
+
MAX_RETRIES: ClassVar[int] = 2
|
|
72
|
+
|
|
55
73
|
def __init__(
|
|
56
74
|
self,
|
|
57
75
|
config: LLMConfig | None = None,
|
|
58
76
|
cache: bool = False,
|
|
59
77
|
max_tokens: int = DEFAULT_MAX_TOKENS,
|
|
78
|
+
thinking_config: types.ThinkingConfig | None = None,
|
|
60
79
|
):
|
|
61
80
|
"""
|
|
62
|
-
Initialize the GeminiClient with the provided configuration and
|
|
81
|
+
Initialize the GeminiClient with the provided configuration, cache setting, and optional thinking config.
|
|
63
82
|
|
|
64
83
|
Args:
|
|
65
84
|
config (LLMConfig | None): The configuration for the LLM client, including API key, model, temperature, and max tokens.
|
|
66
85
|
cache (bool): Whether to use caching for responses. Defaults to False.
|
|
86
|
+
thinking_config (types.ThinkingConfig | None): Optional thinking configuration for models that support it.
|
|
87
|
+
Only use with models that support thinking (gemini-2.5+). Defaults to None.
|
|
88
|
+
|
|
67
89
|
"""
|
|
68
90
|
if config is None:
|
|
69
91
|
config = LLMConfig()
|
|
@@ -76,6 +98,50 @@ class GeminiClient(LLMClient):
|
|
|
76
98
|
api_key=config.api_key,
|
|
77
99
|
)
|
|
78
100
|
self.max_tokens = max_tokens
|
|
101
|
+
self.thinking_config = thinking_config
|
|
102
|
+
|
|
103
|
+
def _check_safety_blocks(self, response) -> None:
|
|
104
|
+
"""Check if response was blocked for safety reasons and raise appropriate exceptions."""
|
|
105
|
+
# Check if the response was blocked for safety reasons
|
|
106
|
+
if not (hasattr(response, 'candidates') and response.candidates):
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
candidate = response.candidates[0]
|
|
110
|
+
if not (hasattr(candidate, 'finish_reason') and candidate.finish_reason == 'SAFETY'):
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
# Content was blocked for safety reasons - collect safety details
|
|
114
|
+
safety_info = []
|
|
115
|
+
safety_ratings = getattr(candidate, 'safety_ratings', None)
|
|
116
|
+
|
|
117
|
+
if safety_ratings:
|
|
118
|
+
for rating in safety_ratings:
|
|
119
|
+
if getattr(rating, 'blocked', False):
|
|
120
|
+
category = getattr(rating, 'category', 'Unknown')
|
|
121
|
+
probability = getattr(rating, 'probability', 'Unknown')
|
|
122
|
+
safety_info.append(f'{category}: {probability}')
|
|
123
|
+
|
|
124
|
+
safety_details = (
|
|
125
|
+
', '.join(safety_info) if safety_info else 'Content blocked for safety reasons'
|
|
126
|
+
)
|
|
127
|
+
raise Exception(f'Response blocked by Gemini safety filters: {safety_details}')
|
|
128
|
+
|
|
129
|
+
def _check_prompt_blocks(self, response) -> None:
|
|
130
|
+
"""Check if prompt was blocked and raise appropriate exceptions."""
|
|
131
|
+
prompt_feedback = getattr(response, 'prompt_feedback', None)
|
|
132
|
+
if not prompt_feedback:
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
block_reason = getattr(prompt_feedback, 'block_reason', None)
|
|
136
|
+
if block_reason:
|
|
137
|
+
raise Exception(f'Prompt blocked by Gemini: {block_reason}')
|
|
138
|
+
|
|
139
|
+
def _get_model_for_size(self, model_size: ModelSize) -> str:
|
|
140
|
+
"""Get the appropriate model name based on the requested size."""
|
|
141
|
+
if model_size == ModelSize.small:
|
|
142
|
+
return self.small_model or DEFAULT_SMALL_MODEL
|
|
143
|
+
else:
|
|
144
|
+
return self.model or DEFAULT_MODEL
|
|
79
145
|
|
|
80
146
|
async def _generate_response(
|
|
81
147
|
self,
|
|
@@ -91,17 +157,17 @@ class GeminiClient(LLMClient):
|
|
|
91
157
|
messages (list[Message]): A list of messages to send to the language model.
|
|
92
158
|
response_model (type[BaseModel] | None): An optional Pydantic model to parse the response into.
|
|
93
159
|
max_tokens (int): The maximum number of tokens to generate in the response.
|
|
160
|
+
model_size (ModelSize): The size of the model to use (small or medium).
|
|
94
161
|
|
|
95
162
|
Returns:
|
|
96
163
|
dict[str, typing.Any]: The response from the language model.
|
|
97
164
|
|
|
98
165
|
Raises:
|
|
99
166
|
RateLimitError: If the API rate limit is exceeded.
|
|
100
|
-
|
|
101
|
-
Exception: If there is an error generating the response.
|
|
167
|
+
Exception: If there is an error generating the response or content is blocked.
|
|
102
168
|
"""
|
|
103
169
|
try:
|
|
104
|
-
gemini_messages:
|
|
170
|
+
gemini_messages: typing.Any = []
|
|
105
171
|
# If a response model is provided, add schema for structured output
|
|
106
172
|
system_prompt = ''
|
|
107
173
|
if response_model is not None:
|
|
@@ -127,6 +193,9 @@ class GeminiClient(LLMClient):
|
|
|
127
193
|
types.Content(role=m.role, parts=[types.Part.from_text(text=m.content)])
|
|
128
194
|
)
|
|
129
195
|
|
|
196
|
+
# Get the appropriate model for the requested size
|
|
197
|
+
model = self._get_model_for_size(model_size)
|
|
198
|
+
|
|
130
199
|
# Create generation config
|
|
131
200
|
generation_config = types.GenerateContentConfig(
|
|
132
201
|
temperature=self.temperature,
|
|
@@ -134,15 +203,20 @@ class GeminiClient(LLMClient):
|
|
|
134
203
|
response_mime_type='application/json' if response_model else None,
|
|
135
204
|
response_schema=response_model if response_model else None,
|
|
136
205
|
system_instruction=system_prompt,
|
|
206
|
+
thinking_config=self.thinking_config,
|
|
137
207
|
)
|
|
138
208
|
|
|
139
209
|
# Generate content using the simple string approach
|
|
140
210
|
response = await self.client.aio.models.generate_content(
|
|
141
|
-
model=
|
|
142
|
-
contents=gemini_messages,
|
|
211
|
+
model=model,
|
|
212
|
+
contents=gemini_messages,
|
|
143
213
|
config=generation_config,
|
|
144
214
|
)
|
|
145
215
|
|
|
216
|
+
# Check for safety and prompt blocks
|
|
217
|
+
self._check_safety_blocks(response)
|
|
218
|
+
self._check_prompt_blocks(response)
|
|
219
|
+
|
|
146
220
|
# If this was a structured output request, parse the response into the Pydantic model
|
|
147
221
|
if response_model is not None:
|
|
148
222
|
try:
|
|
@@ -160,9 +234,16 @@ class GeminiClient(LLMClient):
|
|
|
160
234
|
return {'content': response.text}
|
|
161
235
|
|
|
162
236
|
except Exception as e:
|
|
163
|
-
# Check if it's a rate limit error
|
|
164
|
-
|
|
237
|
+
# Check if it's a rate limit error based on Gemini API error codes
|
|
238
|
+
error_message = str(e).lower()
|
|
239
|
+
if (
|
|
240
|
+
'rate limit' in error_message
|
|
241
|
+
or 'quota' in error_message
|
|
242
|
+
or 'resource_exhausted' in error_message
|
|
243
|
+
or '429' in str(e)
|
|
244
|
+
):
|
|
165
245
|
raise RateLimitError from e
|
|
246
|
+
|
|
166
247
|
logger.error(f'Error in generating LLM response: {e}')
|
|
167
248
|
raise
|
|
168
249
|
|
|
@@ -174,13 +255,14 @@ class GeminiClient(LLMClient):
|
|
|
174
255
|
model_size: ModelSize = ModelSize.medium,
|
|
175
256
|
) -> dict[str, typing.Any]:
|
|
176
257
|
"""
|
|
177
|
-
Generate a response from the Gemini language model.
|
|
178
|
-
This method overrides the parent class method to provide a direct implementation.
|
|
258
|
+
Generate a response from the Gemini language model with retry logic and error handling.
|
|
259
|
+
This method overrides the parent class method to provide a direct implementation with advanced retry logic.
|
|
179
260
|
|
|
180
261
|
Args:
|
|
181
262
|
messages (list[Message]): A list of messages to send to the language model.
|
|
182
263
|
response_model (type[BaseModel] | None): An optional Pydantic model to parse the response into.
|
|
183
|
-
max_tokens (int): The maximum number of tokens to generate in the response.
|
|
264
|
+
max_tokens (int | None): The maximum number of tokens to generate in the response.
|
|
265
|
+
model_size (ModelSize): The size of the model to use (small or medium).
|
|
184
266
|
|
|
185
267
|
Returns:
|
|
186
268
|
dict[str, typing.Any]: The response from the language model.
|
|
@@ -188,10 +270,53 @@ class GeminiClient(LLMClient):
|
|
|
188
270
|
if max_tokens is None:
|
|
189
271
|
max_tokens = self.max_tokens
|
|
190
272
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
273
|
+
retry_count = 0
|
|
274
|
+
last_error = None
|
|
275
|
+
|
|
276
|
+
# Add multilingual extraction instructions
|
|
277
|
+
messages[0].content += MULTILINGUAL_EXTRACTION_RESPONSES
|
|
278
|
+
|
|
279
|
+
while retry_count <= self.MAX_RETRIES:
|
|
280
|
+
try:
|
|
281
|
+
response = await self._generate_response(
|
|
282
|
+
messages=messages,
|
|
283
|
+
response_model=response_model,
|
|
284
|
+
max_tokens=max_tokens,
|
|
285
|
+
model_size=model_size,
|
|
286
|
+
)
|
|
287
|
+
return response
|
|
288
|
+
except RateLimitError:
|
|
289
|
+
# Rate limit errors should not trigger retries (fail fast)
|
|
290
|
+
raise
|
|
291
|
+
except Exception as e:
|
|
292
|
+
last_error = e
|
|
293
|
+
|
|
294
|
+
# Check if this is a safety block - these typically shouldn't be retried
|
|
295
|
+
if 'safety' in str(e).lower() or 'blocked' in str(e).lower():
|
|
296
|
+
logger.warning(f'Content blocked by safety filters: {e}')
|
|
297
|
+
raise
|
|
298
|
+
|
|
299
|
+
# Don't retry if we've hit the max retries
|
|
300
|
+
if retry_count >= self.MAX_RETRIES:
|
|
301
|
+
logger.error(f'Max retries ({self.MAX_RETRIES}) exceeded. Last error: {e}')
|
|
302
|
+
raise
|
|
303
|
+
|
|
304
|
+
retry_count += 1
|
|
305
|
+
|
|
306
|
+
# Construct a detailed error message for the LLM
|
|
307
|
+
error_context = (
|
|
308
|
+
f'The previous response attempt was invalid. '
|
|
309
|
+
f'Error type: {e.__class__.__name__}. '
|
|
310
|
+
f'Error details: {str(e)}. '
|
|
311
|
+
f'Please try again with a valid response, ensuring the output matches '
|
|
312
|
+
f'the expected format and constraints.'
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
error_message = Message(role='user', content=error_context)
|
|
316
|
+
messages.append(error_message)
|
|
317
|
+
logger.warning(
|
|
318
|
+
f'Retrying after application error (attempt {retry_count}/{self.MAX_RETRIES}): {e}'
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
# If we somehow get here, raise the last error
|
|
322
|
+
raise last_error or Exception('Max retries exceeded with no specific error')
|
|
@@ -17,10 +17,21 @@ limitations under the License.
|
|
|
17
17
|
import json
|
|
18
18
|
import logging
|
|
19
19
|
import typing
|
|
20
|
+
from typing import TYPE_CHECKING
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
from groq
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
import groq
|
|
24
|
+
from groq import AsyncGroq
|
|
25
|
+
from groq.types.chat import ChatCompletionMessageParam
|
|
26
|
+
else:
|
|
27
|
+
try:
|
|
28
|
+
import groq
|
|
29
|
+
from groq import AsyncGroq
|
|
30
|
+
from groq.types.chat import ChatCompletionMessageParam
|
|
31
|
+
except ImportError:
|
|
32
|
+
raise ImportError(
|
|
33
|
+
'groq is required for GroqClient. Install it with: pip install graphiti-core[groq]'
|
|
34
|
+
) from None
|
|
24
35
|
from pydantic import BaseModel
|
|
25
36
|
|
|
26
37
|
from ..prompts.models import Message
|
graphiti_core/nodes.py
CHANGED
|
@@ -540,10 +540,18 @@ class CommunityNode(Node):
|
|
|
540
540
|
|
|
541
541
|
# Node helpers
|
|
542
542
|
def get_episodic_node_from_record(record: Any) -> EpisodicNode:
|
|
543
|
+
created_at = parse_db_date(record['created_at'])
|
|
544
|
+
valid_at = parse_db_date(record['valid_at'])
|
|
545
|
+
|
|
546
|
+
if created_at is None:
|
|
547
|
+
raise ValueError(f'created_at cannot be None for episode {record.get("uuid", "unknown")}')
|
|
548
|
+
if valid_at is None:
|
|
549
|
+
raise ValueError(f'valid_at cannot be None for episode {record.get("uuid", "unknown")}')
|
|
550
|
+
|
|
543
551
|
return EpisodicNode(
|
|
544
552
|
content=record['content'],
|
|
545
|
-
created_at=
|
|
546
|
-
valid_at=
|
|
553
|
+
created_at=created_at,
|
|
554
|
+
valid_at=valid_at,
|
|
547
555
|
uuid=record['uuid'],
|
|
548
556
|
group_id=record['group_id'],
|
|
549
557
|
source=EpisodeType.from_str(record['source']),
|
|
@@ -19,7 +19,6 @@ from enum import Enum
|
|
|
19
19
|
from typing import Any
|
|
20
20
|
|
|
21
21
|
from pydantic import BaseModel, Field
|
|
22
|
-
from typing_extensions import LiteralString
|
|
23
22
|
|
|
24
23
|
|
|
25
24
|
class ComparisonOperator(Enum):
|
|
@@ -53,8 +52,8 @@ class SearchFilters(BaseModel):
|
|
|
53
52
|
|
|
54
53
|
def node_search_filter_query_constructor(
|
|
55
54
|
filters: SearchFilters,
|
|
56
|
-
) -> tuple[
|
|
57
|
-
filter_query:
|
|
55
|
+
) -> tuple[str, dict[str, Any]]:
|
|
56
|
+
filter_query: str = ''
|
|
58
57
|
filter_params: dict[str, Any] = {}
|
|
59
58
|
|
|
60
59
|
if filters.node_labels is not None:
|
|
@@ -67,8 +66,8 @@ def node_search_filter_query_constructor(
|
|
|
67
66
|
|
|
68
67
|
def edge_search_filter_query_constructor(
|
|
69
68
|
filters: SearchFilters,
|
|
70
|
-
) -> tuple[
|
|
71
|
-
filter_query:
|
|
69
|
+
) -> tuple[str, dict[str, Any]]:
|
|
70
|
+
filter_query: str = ''
|
|
72
71
|
filter_params: dict[str, Any] = {}
|
|
73
72
|
|
|
74
73
|
if filters.edge_types is not None:
|
|
@@ -278,9 +278,6 @@ async def edge_similarity_search(
|
|
|
278
278
|
routing_='r',
|
|
279
279
|
)
|
|
280
280
|
|
|
281
|
-
if driver.provider == 'falkordb':
|
|
282
|
-
records = [dict(zip(header, row, strict=True)) for row in records]
|
|
283
|
-
|
|
284
281
|
edges = [get_entity_edge_from_record(record) for record in records]
|
|
285
282
|
|
|
286
283
|
return edges
|
|
@@ -377,8 +374,6 @@ async def node_fulltext_search(
|
|
|
377
374
|
database_=DEFAULT_DATABASE,
|
|
378
375
|
routing_='r',
|
|
379
376
|
)
|
|
380
|
-
if driver.provider == 'falkordb':
|
|
381
|
-
records = [dict(zip(header, row, strict=True)) for row in records]
|
|
382
377
|
|
|
383
378
|
nodes = [get_entity_node_from_record(record) for record in records]
|
|
384
379
|
|
|
@@ -433,8 +428,7 @@ async def node_similarity_search(
|
|
|
433
428
|
database_=DEFAULT_DATABASE,
|
|
434
429
|
routing_='r',
|
|
435
430
|
)
|
|
436
|
-
|
|
437
|
-
records = [dict(zip(header, row, strict=True)) for row in records]
|
|
431
|
+
|
|
438
432
|
nodes = [get_entity_node_from_record(record) for record in records]
|
|
439
433
|
|
|
440
434
|
return nodes
|
|
@@ -545,7 +539,8 @@ async def community_fulltext_search(
|
|
|
545
539
|
comm.group_id AS group_id,
|
|
546
540
|
comm.name AS name,
|
|
547
541
|
comm.created_at AS created_at,
|
|
548
|
-
comm.summary AS summary
|
|
542
|
+
comm.summary AS summary,
|
|
543
|
+
comm.name_embedding AS name_embedding
|
|
549
544
|
ORDER BY score DESC
|
|
550
545
|
LIMIT $limit
|
|
551
546
|
"""
|
|
@@ -595,7 +590,8 @@ async def community_similarity_search(
|
|
|
595
590
|
comm.group_id AS group_id,
|
|
596
591
|
comm.name AS name,
|
|
597
592
|
comm.created_at AS created_at,
|
|
598
|
-
comm.summary AS summary
|
|
593
|
+
comm.summary AS summary,
|
|
594
|
+
comm.name_embedding AS name_embedding
|
|
599
595
|
ORDER BY score DESC
|
|
600
596
|
LIMIT $limit
|
|
601
597
|
"""
|
|
@@ -40,7 +40,7 @@ async def get_community_clusters(
|
|
|
40
40
|
database_=DEFAULT_DATABASE,
|
|
41
41
|
)
|
|
42
42
|
|
|
43
|
-
group_ids = group_id_values[0]['group_ids']
|
|
43
|
+
group_ids = group_id_values[0]['group_ids'] if group_id_values else []
|
|
44
44
|
|
|
45
45
|
for group_id in group_ids:
|
|
46
46
|
projection: dict[str, list[Neighbor]] = {}
|
|
@@ -297,7 +297,7 @@ async def resolve_extracted_edges(
|
|
|
297
297
|
embedder = clients.embedder
|
|
298
298
|
await create_entity_edge_embeddings(embedder, extracted_edges)
|
|
299
299
|
|
|
300
|
-
search_results
|
|
300
|
+
search_results = await semaphore_gather(
|
|
301
301
|
get_relevant_edges(driver, extracted_edges, SearchFilters()),
|
|
302
302
|
get_edge_invalidation_candidates(driver, extracted_edges, SearchFilters(), 0.2),
|
|
303
303
|
)
|
|
@@ -21,7 +21,7 @@ from typing_extensions import LiteralString
|
|
|
21
21
|
|
|
22
22
|
from graphiti_core.driver.driver import GraphDriver
|
|
23
23
|
from graphiti_core.graph_queries import get_fulltext_indices, get_range_indices
|
|
24
|
-
from graphiti_core.helpers import DEFAULT_DATABASE, semaphore_gather
|
|
24
|
+
from graphiti_core.helpers import DEFAULT_DATABASE, parse_db_date, semaphore_gather
|
|
25
25
|
from graphiti_core.nodes import EpisodeType, EpisodicNode
|
|
26
26
|
|
|
27
27
|
EPISODE_WINDOW_LEN = 3
|
|
@@ -140,10 +140,9 @@ async def retrieve_episodes(
|
|
|
140
140
|
episodes = [
|
|
141
141
|
EpisodicNode(
|
|
142
142
|
content=record['content'],
|
|
143
|
-
created_at=
|
|
144
|
-
|
|
145
|
-
),
|
|
146
|
-
valid_at=(record['valid_at'].to_native()),
|
|
143
|
+
created_at=parse_db_date(record['created_at'])
|
|
144
|
+
or datetime.min.replace(tzinfo=timezone.utc),
|
|
145
|
+
valid_at=parse_db_date(record['valid_at']) or datetime.min.replace(tzinfo=timezone.utc),
|
|
147
146
|
uuid=record['uuid'],
|
|
148
147
|
group_id=record['group_id'],
|
|
149
148
|
source=EpisodeType.from_str(record['source']),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: graphiti-core
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.15.1
|
|
4
4
|
Summary: A temporal graph building library
|
|
5
5
|
Project-URL: Homepage, https://help.getzep.com/graphiti/graphiti/overview
|
|
6
6
|
Project-URL: Repository, https://github.com/getzep/graphiti
|
|
@@ -21,6 +21,7 @@ Requires-Dist: anthropic>=0.49.0; extra == 'anthropic'
|
|
|
21
21
|
Provides-Extra: dev
|
|
22
22
|
Requires-Dist: anthropic>=0.49.0; extra == 'dev'
|
|
23
23
|
Requires-Dist: diskcache-stubs>=5.6.3.6.20240818; extra == 'dev'
|
|
24
|
+
Requires-Dist: falkordb<2.0.0,>=1.1.2; extra == 'dev'
|
|
24
25
|
Requires-Dist: google-genai>=1.8.0; extra == 'dev'
|
|
25
26
|
Requires-Dist: groq>=0.2.0; extra == 'dev'
|
|
26
27
|
Requires-Dist: ipykernel>=6.29.5; extra == 'dev'
|
|
@@ -29,7 +30,7 @@ Requires-Dist: langchain-anthropic>=0.2.4; extra == 'dev'
|
|
|
29
30
|
Requires-Dist: langchain-openai>=0.2.6; extra == 'dev'
|
|
30
31
|
Requires-Dist: langgraph>=0.2.15; extra == 'dev'
|
|
31
32
|
Requires-Dist: langsmith>=0.1.108; extra == 'dev'
|
|
32
|
-
Requires-Dist:
|
|
33
|
+
Requires-Dist: pyright>=1.1.380; extra == 'dev'
|
|
33
34
|
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
|
|
34
35
|
Requires-Dist: pytest-xdist>=3.6.1; extra == 'dev'
|
|
35
36
|
Requires-Dist: pytest>=8.3.3; extra == 'dev'
|
|
@@ -37,12 +38,16 @@ Requires-Dist: ruff>=0.7.1; extra == 'dev'
|
|
|
37
38
|
Requires-Dist: sentence-transformers>=3.2.1; extra == 'dev'
|
|
38
39
|
Requires-Dist: transformers>=4.45.2; extra == 'dev'
|
|
39
40
|
Requires-Dist: voyageai>=0.2.3; extra == 'dev'
|
|
40
|
-
Provides-Extra:
|
|
41
|
-
Requires-Dist: falkordb<2.0.0,>=1.1.2; extra == '
|
|
41
|
+
Provides-Extra: falkordb
|
|
42
|
+
Requires-Dist: falkordb<2.0.0,>=1.1.2; extra == 'falkordb'
|
|
42
43
|
Provides-Extra: google-genai
|
|
43
44
|
Requires-Dist: google-genai>=1.8.0; extra == 'google-genai'
|
|
44
45
|
Provides-Extra: groq
|
|
45
46
|
Requires-Dist: groq>=0.2.0; extra == 'groq'
|
|
47
|
+
Provides-Extra: sentence-transformers
|
|
48
|
+
Requires-Dist: sentence-transformers>=3.2.1; extra == 'sentence-transformers'
|
|
49
|
+
Provides-Extra: voyageai
|
|
50
|
+
Requires-Dist: voyageai>=0.2.3; extra == 'voyageai'
|
|
46
51
|
Description-Content-Type: text/markdown
|
|
47
52
|
|
|
48
53
|
<p align="center">
|
|
@@ -153,7 +158,7 @@ Requirements:
|
|
|
153
158
|
|
|
154
159
|
- Python 3.10 or higher
|
|
155
160
|
- Neo4j 5.26 / FalkorDB 1.1.2 or higher (serves as the embeddings storage backend)
|
|
156
|
-
- OpenAI API key (for LLM inference and embedding)
|
|
161
|
+
- OpenAI API key (Graphiti defaults to OpenAI for LLM inference and embedding)
|
|
157
162
|
|
|
158
163
|
> [!IMPORTANT]
|
|
159
164
|
> Graphiti works best with LLM services that support Structured Output (such as OpenAI and Gemini).
|
|
@@ -167,6 +172,12 @@ Optional:
|
|
|
167
172
|
> [!TIP]
|
|
168
173
|
> The simplest way to install Neo4j is via [Neo4j Desktop](https://neo4j.com/download/). It provides a user-friendly
|
|
169
174
|
> interface to manage Neo4j instances and databases.
|
|
175
|
+
> Alternatively, you can use FalkorDB on-premises via Docker and instantly start with the quickstart example:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
docker run -p 6379:6379 -p 3000:3000 -it --rm falkordb/falkordb:latest
|
|
179
|
+
|
|
180
|
+
```
|
|
170
181
|
|
|
171
182
|
```bash
|
|
172
183
|
pip install graphiti-core
|
|
@@ -178,7 +189,18 @@ or
|
|
|
178
189
|
uv add graphiti-core
|
|
179
190
|
```
|
|
180
191
|
|
|
181
|
-
|
|
192
|
+
### Installing with FalkorDB Support
|
|
193
|
+
|
|
194
|
+
If you plan to use FalkorDB as your graph database backend, install with the FalkorDB extra:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
pip install graphiti-core[falkordb]
|
|
198
|
+
|
|
199
|
+
# or with uv
|
|
200
|
+
uv add graphiti-core[falkordb]
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### You can also install optional LLM providers as extras:
|
|
182
204
|
|
|
183
205
|
```bash
|
|
184
206
|
# Install with Anthropic support
|
|
@@ -192,18 +214,21 @@ pip install graphiti-core[google-genai]
|
|
|
192
214
|
|
|
193
215
|
# Install with multiple providers
|
|
194
216
|
pip install graphiti-core[anthropic,groq,google-genai]
|
|
217
|
+
|
|
218
|
+
# Install with FalkorDB and LLM providers
|
|
219
|
+
pip install graphiti-core[falkordb,anthropic,google-genai]
|
|
195
220
|
```
|
|
196
221
|
|
|
197
222
|
## Quick Start
|
|
198
223
|
|
|
199
224
|
> [!IMPORTANT]
|
|
200
|
-
> Graphiti
|
|
225
|
+
> Graphiti defaults to using OpenAI for LLM inference and embedding. Ensure that an `OPENAI_API_KEY` is set in your environment.
|
|
201
226
|
> Support for Anthropic and Groq LLM inferences is available, too. Other LLM providers may be supported via OpenAI
|
|
202
227
|
> compatible APIs.
|
|
203
228
|
|
|
204
229
|
For a complete working example, see the [Quickstart Example](./examples/quickstart/README.md) in the examples directory. The quickstart demonstrates:
|
|
205
230
|
|
|
206
|
-
1. Connecting to a Neo4j database
|
|
231
|
+
1. Connecting to a Neo4j or FalkorDB database
|
|
207
232
|
2. Initializing Graphiti indices and constraints
|
|
208
233
|
3. Adding episodes to the graph (both text and structured JSON)
|
|
209
234
|
4. Searching for relationships (edges) using hybrid search
|
|
@@ -247,7 +272,7 @@ as such this feature is off by default.
|
|
|
247
272
|
|
|
248
273
|
## Using Graphiti with Azure OpenAI
|
|
249
274
|
|
|
250
|
-
Graphiti supports Azure OpenAI for both LLM inference and embeddings.
|
|
275
|
+
Graphiti supports Azure OpenAI for both LLM inference and embeddings. Azure deployments often require different endpoints for LLM and embedding services, and separate deployments for default and small models.
|
|
251
276
|
|
|
252
277
|
```python
|
|
253
278
|
from openai import AsyncAzureOpenAI
|
|
@@ -256,19 +281,26 @@ from graphiti_core.llm_client import LLMConfig, OpenAIClient
|
|
|
256
281
|
from graphiti_core.embedder.openai import OpenAIEmbedder, OpenAIEmbedderConfig
|
|
257
282
|
from graphiti_core.cross_encoder.openai_reranker_client import OpenAIRerankerClient
|
|
258
283
|
|
|
259
|
-
# Azure OpenAI configuration
|
|
284
|
+
# Azure OpenAI configuration - use separate endpoints for different services
|
|
260
285
|
api_key = "<your-api-key>"
|
|
261
286
|
api_version = "<your-api-version>"
|
|
262
|
-
|
|
287
|
+
llm_endpoint = "<your-llm-endpoint>" # e.g., "https://your-llm-resource.openai.azure.com/"
|
|
288
|
+
embedding_endpoint = "<your-embedding-endpoint>" # e.g., "https://your-embedding-resource.openai.azure.com/"
|
|
263
289
|
|
|
264
|
-
# Create Azure OpenAI
|
|
265
|
-
|
|
290
|
+
# Create separate Azure OpenAI clients for different services
|
|
291
|
+
llm_client_azure = AsyncAzureOpenAI(
|
|
266
292
|
api_key=api_key,
|
|
267
293
|
api_version=api_version,
|
|
268
|
-
azure_endpoint=
|
|
294
|
+
azure_endpoint=llm_endpoint
|
|
269
295
|
)
|
|
270
296
|
|
|
271
|
-
|
|
297
|
+
embedding_client_azure = AsyncAzureOpenAI(
|
|
298
|
+
api_key=api_key,
|
|
299
|
+
api_version=api_version,
|
|
300
|
+
azure_endpoint=embedding_endpoint
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# Create LLM Config with your Azure deployment names
|
|
272
304
|
azure_llm_config = LLMConfig(
|
|
273
305
|
small_model="gpt-4.1-nano",
|
|
274
306
|
model="gpt-4.1-mini",
|
|
@@ -281,29 +313,30 @@ graphiti = Graphiti(
|
|
|
281
313
|
"password",
|
|
282
314
|
llm_client=OpenAIClient(
|
|
283
315
|
llm_config=azure_llm_config,
|
|
284
|
-
client=
|
|
316
|
+
client=llm_client_azure
|
|
285
317
|
),
|
|
286
318
|
embedder=OpenAIEmbedder(
|
|
287
319
|
config=OpenAIEmbedderConfig(
|
|
288
|
-
embedding_model="text-embedding-3-small" #
|
|
320
|
+
embedding_model="text-embedding-3-small-deployment" # Your Azure embedding deployment name
|
|
289
321
|
),
|
|
290
|
-
client=
|
|
322
|
+
client=embedding_client_azure
|
|
291
323
|
),
|
|
292
|
-
# Optional: Configure the OpenAI cross encoder with Azure OpenAI
|
|
293
324
|
cross_encoder=OpenAIRerankerClient(
|
|
294
|
-
llm_config=
|
|
295
|
-
|
|
325
|
+
llm_config=LLMConfig(
|
|
326
|
+
model=azure_llm_config.small_model # Use small model for reranking
|
|
327
|
+
),
|
|
328
|
+
client=llm_client_azure
|
|
296
329
|
)
|
|
297
330
|
)
|
|
298
331
|
|
|
299
332
|
# Now you can use Graphiti with Azure OpenAI
|
|
300
333
|
```
|
|
301
334
|
|
|
302
|
-
Make sure to replace the placeholder values with your actual Azure OpenAI credentials and
|
|
335
|
+
Make sure to replace the placeholder values with your actual Azure OpenAI credentials and deployment names that match your Azure OpenAI service configuration.
|
|
303
336
|
|
|
304
337
|
## Using Graphiti with Google Gemini
|
|
305
338
|
|
|
306
|
-
Graphiti supports Google's Gemini models for
|
|
339
|
+
Graphiti supports Google's Gemini models for LLM inference, embeddings, and cross-encoding/reranking. To use Gemini, you'll need to configure the LLM client, embedder, and the cross-encoder with your Google API key.
|
|
307
340
|
|
|
308
341
|
Install Graphiti:
|
|
309
342
|
|
|
@@ -319,6 +352,7 @@ pip install "graphiti-core[google-genai]"
|
|
|
319
352
|
from graphiti_core import Graphiti
|
|
320
353
|
from graphiti_core.llm_client.gemini_client import GeminiClient, LLMConfig
|
|
321
354
|
from graphiti_core.embedder.gemini import GeminiEmbedder, GeminiEmbedderConfig
|
|
355
|
+
from graphiti_core.cross_encoder.gemini_reranker_client import GeminiRerankerClient
|
|
322
356
|
|
|
323
357
|
# Google API key configuration
|
|
324
358
|
api_key = "<your-google-api-key>"
|
|
@@ -339,12 +373,20 @@ graphiti = Graphiti(
|
|
|
339
373
|
api_key=api_key,
|
|
340
374
|
embedding_model="embedding-001"
|
|
341
375
|
)
|
|
376
|
+
),
|
|
377
|
+
cross_encoder=GeminiRerankerClient(
|
|
378
|
+
config=LLMConfig(
|
|
379
|
+
api_key=api_key,
|
|
380
|
+
model="gemini-2.5-flash-lite-preview-06-17"
|
|
381
|
+
)
|
|
342
382
|
)
|
|
343
383
|
)
|
|
344
384
|
|
|
345
|
-
# Now you can use Graphiti with Google Gemini
|
|
385
|
+
# Now you can use Graphiti with Google Gemini for all components
|
|
346
386
|
```
|
|
347
387
|
|
|
388
|
+
The Gemini reranker uses the `gemini-2.5-flash-lite-preview-06-17` model by default, which is optimized for cost-effective and low-latency classification tasks. It uses the same boolean classification approach as the OpenAI reranker, leveraging Gemini's log probabilities feature to rank passage relevance.
|
|
389
|
+
|
|
348
390
|
## Using Graphiti with Ollama (Local LLM)
|
|
349
391
|
|
|
350
392
|
Graphiti supports Ollama for running local LLMs and embedding models via Ollama's OpenAI-compatible API. This is ideal for privacy-focused applications or when you want to avoid API costs.
|
|
@@ -2,33 +2,34 @@ graphiti_core/__init__.py,sha256=e5SWFkRiaUwfprYIeIgVIh7JDedNiloZvd3roU-0aDY,55
|
|
|
2
2
|
graphiti_core/edges.py,sha256=h67vyXYhZYqlwaOmaqjHiGns6nEjuBVSIAFBMveNVo8,16257
|
|
3
3
|
graphiti_core/errors.py,sha256=cH_v9TPgEPeQE6GFOHIg5TvejpUCBddGarMY2Whxbwc,2707
|
|
4
4
|
graphiti_core/graph_queries.py,sha256=KfWDp8xDnPa9bcHskw8NeMpeeHBtZWBCosVdu1Iwv34,7076
|
|
5
|
-
graphiti_core/graphiti.py,sha256=
|
|
5
|
+
graphiti_core/graphiti.py,sha256=TcZTJJ_4E3qt_bFNZNDy3ujFyjZLMmSYKmixN2kdqqg,33163
|
|
6
6
|
graphiti_core/graphiti_types.py,sha256=rL-9bvnLobunJfXU4hkD6mAj14pofKp_wq8QsFDZwDU,1035
|
|
7
|
-
graphiti_core/helpers.py,sha256=
|
|
8
|
-
graphiti_core/nodes.py,sha256=
|
|
7
|
+
graphiti_core/helpers.py,sha256=ixUOfWN_GJVRvdiK-RzgAYJD18nM1CLmLBDNmVrIboQ,4948
|
|
8
|
+
graphiti_core/nodes.py,sha256=mRK5QaTzYzznAx4OSHKFFOoiyAUKJmXbjWv3ovDJ6z8,18994
|
|
9
9
|
graphiti_core/py.typed,sha256=vlmmzQOt7bmeQl9L3XJP4W6Ry0iiELepnOrinKz5KQg,79
|
|
10
10
|
graphiti_core/cross_encoder/__init__.py,sha256=hry59vz21x-AtGZ0MJ7ugw0HTwJkXiddpp_Yqnwsen0,723
|
|
11
|
-
graphiti_core/cross_encoder/bge_reranker_client.py,sha256=
|
|
11
|
+
graphiti_core/cross_encoder/bge_reranker_client.py,sha256=y3TfFxZh0Yvj6HUShmfUm6MC7OPXwWUlv1Qe5HF3S3I,1797
|
|
12
12
|
graphiti_core/cross_encoder/client.py,sha256=KLsbfWKOEaAV3adFe3XZlAeb-gje9_sVKCVZTaJP3ac,1441
|
|
13
|
-
graphiti_core/cross_encoder/
|
|
14
|
-
graphiti_core/
|
|
13
|
+
graphiti_core/cross_encoder/gemini_reranker_client.py,sha256=FoOwTqGFQ833X7eAZU11FX7qW4_Fs9iSNyHIc7cslFU,6158
|
|
14
|
+
graphiti_core/cross_encoder/openai_reranker_client.py,sha256=hoaGyu9nCNMJyP8si0Bha5Q9CFszfiHQmLgE9IsX7sY,4653
|
|
15
|
+
graphiti_core/driver/__init__.py,sha256=kCWimqQU19airu5gKwCmZtZuXkDfaQfKSUhMDoL-rTA,626
|
|
15
16
|
graphiti_core/driver/driver.py,sha256=-FHAA2gM8FA0re-q6udmjQ6pNFdFGRQrMRuAiqX_1A4,1829
|
|
16
|
-
graphiti_core/driver/falkordb_driver.py,sha256=
|
|
17
|
-
graphiti_core/driver/neo4j_driver.py,sha256=
|
|
17
|
+
graphiti_core/driver/falkordb_driver.py,sha256=9VnZwAgv0YzpFK5g-nS7YiqOOp3zxaiCW1y2etSYt3w,6331
|
|
18
|
+
graphiti_core/driver/neo4j_driver.py,sha256=f8cSkcaCDyQLyI85JBprw0rdrarpd5Tq1mlX-Mz3kls,1962
|
|
18
19
|
graphiti_core/embedder/__init__.py,sha256=EL564ZuE-DZjcuKNUK_exMn_XHXm2LdO9fzdXePVKL4,179
|
|
19
20
|
graphiti_core/embedder/azure_openai.py,sha256=OyomPwC1fIsddI-3n6g00kQFdQznZorBhHwkQKCLUok,2384
|
|
20
21
|
graphiti_core/embedder/client.py,sha256=qEpSHceL_Gc4QQPJWIOnuNLemNuR_TYA4r28t2Vldbg,1115
|
|
21
|
-
graphiti_core/embedder/gemini.py,sha256=
|
|
22
|
+
graphiti_core/embedder/gemini.py,sha256=RbJnG-GqzzGamxsGrbjLQUu6ayQ-p-sl1y9wb0SsAik,3580
|
|
22
23
|
graphiti_core/embedder/openai.py,sha256=bIThUoLMeGlHG2-3VikzK6JZfOHKn4PKvUMx5sHxJy8,2192
|
|
23
|
-
graphiti_core/embedder/voyage.py,sha256=
|
|
24
|
+
graphiti_core/embedder/voyage.py,sha256=oJHAZiNqjdEJOKgoKfGWcxK2-Ewqn5UB3vrBwIwP2u4,2546
|
|
24
25
|
graphiti_core/llm_client/__init__.py,sha256=QgBWUiCeBp6YiA_xqyrDvJ9jIyy1hngH8g7FWahN3nw,776
|
|
25
|
-
graphiti_core/llm_client/anthropic_client.py,sha256=
|
|
26
|
+
graphiti_core/llm_client/anthropic_client.py,sha256=xTFcrgMDK77BwnChBhYj51Jaa2mRNI850oJv2pKZI0A,12892
|
|
26
27
|
graphiti_core/llm_client/azure_openai_client.py,sha256=ekERggAekbb7enes1RJqdRChf_mjaZTFXsnMbxO7azQ,2497
|
|
27
28
|
graphiti_core/llm_client/client.py,sha256=v_w5TBbDJYYADCXSs2r287g5Ami2Urma-GGEbHSI_Jg,5826
|
|
28
29
|
graphiti_core/llm_client/config.py,sha256=90IgSBxZE_3nWdaEONVLUznI8lytPA7ZyexQz-_c55U,2560
|
|
29
30
|
graphiti_core/llm_client/errors.py,sha256=pn6brRiLW60DAUIXJYKBT6MInrS4ueuH1hNLbn_JbQo,1243
|
|
30
|
-
graphiti_core/llm_client/gemini_client.py,sha256=
|
|
31
|
-
graphiti_core/llm_client/groq_client.py,sha256=
|
|
31
|
+
graphiti_core/llm_client/gemini_client.py,sha256=LLyos2irtidZL3qZ8gGLk23l9JWuHtxRdrcmFHtn0Uw,13235
|
|
32
|
+
graphiti_core/llm_client/groq_client.py,sha256=bYLE_cg1QEhugsJOXh4b1vPbxagKeMWqk48240GCzMs,2922
|
|
32
33
|
graphiti_core/llm_client/openai_base_client.py,sha256=gfMcKPyLrylz_ouRdoenDWXyitmgfFZ17Zthbkq3Qs4,8126
|
|
33
34
|
graphiti_core/llm_client/openai_client.py,sha256=ykBK94gxzE7iXux5rvOzVNA8q0Sqzq-8njPB75XcRe8,3240
|
|
34
35
|
graphiti_core/llm_client/openai_generic_client.py,sha256=WElMnPqdb1CxzYH4p2-m_9rVMr5M93-eXnc3yVxBgFg,7001
|
|
@@ -54,23 +55,23 @@ graphiti_core/search/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hS
|
|
|
54
55
|
graphiti_core/search/search.py,sha256=bJCFaNApu5396pXTa-xciu8ORDdRFJqfE3j2ieRVd7Y,15162
|
|
55
56
|
graphiti_core/search/search_config.py,sha256=VvKg6AB_RPhoe56DBBXHRBXHThAVJ_OLFCyq_yKof-A,3765
|
|
56
57
|
graphiti_core/search/search_config_recipes.py,sha256=4GquRphHhJlpXQhAZOySYnCzBWYoTwxlJj44eTOavZQ,7443
|
|
57
|
-
graphiti_core/search/search_filters.py,sha256=
|
|
58
|
+
graphiti_core/search/search_filters.py,sha256=H7Vgob2SvwsG56qiTDXDhI4R4MMY40TVpphY5KHPwYU,6382
|
|
58
59
|
graphiti_core/search/search_helpers.py,sha256=G5Ceaq5Pfgx0Weelqgeylp_pUHwiBnINaUYsDbURJbE,2636
|
|
59
|
-
graphiti_core/search/search_utils.py,sha256=
|
|
60
|
+
graphiti_core/search/search_utils.py,sha256=2g5xL11IUhG2tce2hVwN1bPYWFFxuB3jJh8G-aOdYzg,34724
|
|
60
61
|
graphiti_core/telemetry/__init__.py,sha256=5kALLDlU9bb2v19CdN7qVANsJWyfnL9E60J6FFgzm3o,226
|
|
61
62
|
graphiti_core/telemetry/telemetry.py,sha256=47LrzOVBCcZxsYPsnSxWFiztHoxYKKxPwyRX0hnbDGc,3230
|
|
62
63
|
graphiti_core/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
63
64
|
graphiti_core/utils/bulk_utils.py,sha256=YnyXzmOFgqbLdIAIu9Y6aJjUZHhXj8nBnlegkXBTKi8,16344
|
|
64
65
|
graphiti_core/utils/datetime_utils.py,sha256=Ti-2tnrDFRzBsbfblzsHybsM3jaDLP4-VT2t0VhpIzU,1357
|
|
65
66
|
graphiti_core/utils/maintenance/__init__.py,sha256=vW4H1KyapTl-OOz578uZABYcpND4wPx3Vt6aAPaXh78,301
|
|
66
|
-
graphiti_core/utils/maintenance/community_operations.py,sha256=
|
|
67
|
-
graphiti_core/utils/maintenance/edge_operations.py,sha256=
|
|
68
|
-
graphiti_core/utils/maintenance/graph_data_operations.py,sha256=
|
|
67
|
+
graphiti_core/utils/maintenance/community_operations.py,sha256=AimQzT7wr4M3ofsUetHa1cPEmhsngqJoNWm3Q-3Hwww,10115
|
|
68
|
+
graphiti_core/utils/maintenance/edge_operations.py,sha256=sj4AJ9zPm8ACiC1wSj99bFUUmg4OgFVFnPOSXKfb3T8,21578
|
|
69
|
+
graphiti_core/utils/maintenance/graph_data_operations.py,sha256=NH1FLwVKqnDdt2JKa38g_y2lG08ID5cAR-GPTQccxC4,5403
|
|
69
70
|
graphiti_core/utils/maintenance/node_operations.py,sha256=0WdH_VrkVXLV9YX3xPErXOFygOo2N9g3es9yIB2Yl8Q,15876
|
|
70
71
|
graphiti_core/utils/maintenance/temporal_operations.py,sha256=mJkw9xLB4W2BsLfC5POr0r-PHWL9SIfNj_l_xu0B5ug,3410
|
|
71
72
|
graphiti_core/utils/maintenance/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
72
73
|
graphiti_core/utils/ontology_utils/entity_types_utils.py,sha256=QJX5cG0GSSNF_Mm_yrldr69wjVAbN_MxLhOSznz85Hk,1279
|
|
73
|
-
graphiti_core-0.
|
|
74
|
-
graphiti_core-0.
|
|
75
|
-
graphiti_core-0.
|
|
76
|
-
graphiti_core-0.
|
|
74
|
+
graphiti_core-0.15.1.dist-info/METADATA,sha256=KGLbx0MsKX6M0lw_26eCbKdy_yYHm8XyaFMKB3QDf7Q,22414
|
|
75
|
+
graphiti_core-0.15.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
76
|
+
graphiti_core-0.15.1.dist-info/licenses/LICENSE,sha256=KCUwCyDXuVEgmDWkozHyniRyWjnWUWjkuDHfU6o3JlA,11325
|
|
77
|
+
graphiti_core-0.15.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|