graphiti-core 0.12.0rc1__py3-none-any.whl → 0.24.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- graphiti_core/cross_encoder/bge_reranker_client.py +12 -2
- graphiti_core/cross_encoder/gemini_reranker_client.py +161 -0
- graphiti_core/cross_encoder/openai_reranker_client.py +7 -5
- graphiti_core/decorators.py +110 -0
- graphiti_core/driver/__init__.py +19 -0
- graphiti_core/driver/driver.py +124 -0
- graphiti_core/driver/falkordb_driver.py +362 -0
- graphiti_core/driver/graph_operations/graph_operations.py +191 -0
- graphiti_core/driver/kuzu_driver.py +182 -0
- graphiti_core/driver/neo4j_driver.py +117 -0
- graphiti_core/driver/neptune_driver.py +305 -0
- graphiti_core/driver/search_interface/search_interface.py +89 -0
- graphiti_core/edges.py +287 -172
- graphiti_core/embedder/azure_openai.py +71 -0
- graphiti_core/embedder/client.py +2 -1
- graphiti_core/embedder/gemini.py +116 -22
- graphiti_core/embedder/voyage.py +13 -2
- graphiti_core/errors.py +8 -0
- graphiti_core/graph_queries.py +162 -0
- graphiti_core/graphiti.py +705 -193
- graphiti_core/graphiti_types.py +4 -2
- graphiti_core/helpers.py +87 -10
- graphiti_core/llm_client/__init__.py +16 -0
- graphiti_core/llm_client/anthropic_client.py +159 -56
- graphiti_core/llm_client/azure_openai_client.py +115 -0
- graphiti_core/llm_client/client.py +98 -21
- graphiti_core/llm_client/config.py +1 -1
- graphiti_core/llm_client/gemini_client.py +290 -41
- graphiti_core/llm_client/groq_client.py +14 -3
- graphiti_core/llm_client/openai_base_client.py +261 -0
- graphiti_core/llm_client/openai_client.py +56 -132
- graphiti_core/llm_client/openai_generic_client.py +91 -56
- graphiti_core/models/edges/edge_db_queries.py +259 -35
- graphiti_core/models/nodes/node_db_queries.py +311 -32
- graphiti_core/nodes.py +420 -205
- graphiti_core/prompts/dedupe_edges.py +46 -32
- graphiti_core/prompts/dedupe_nodes.py +67 -42
- graphiti_core/prompts/eval.py +4 -4
- graphiti_core/prompts/extract_edges.py +27 -16
- graphiti_core/prompts/extract_nodes.py +74 -31
- graphiti_core/prompts/prompt_helpers.py +39 -0
- graphiti_core/prompts/snippets.py +29 -0
- graphiti_core/prompts/summarize_nodes.py +23 -25
- graphiti_core/search/search.py +158 -82
- graphiti_core/search/search_config.py +39 -4
- graphiti_core/search/search_filters.py +126 -35
- graphiti_core/search/search_helpers.py +5 -6
- graphiti_core/search/search_utils.py +1405 -485
- graphiti_core/telemetry/__init__.py +9 -0
- graphiti_core/telemetry/telemetry.py +117 -0
- graphiti_core/tracer.py +193 -0
- graphiti_core/utils/bulk_utils.py +364 -285
- graphiti_core/utils/datetime_utils.py +13 -0
- graphiti_core/utils/maintenance/community_operations.py +67 -49
- graphiti_core/utils/maintenance/dedup_helpers.py +262 -0
- graphiti_core/utils/maintenance/edge_operations.py +339 -197
- graphiti_core/utils/maintenance/graph_data_operations.py +50 -114
- graphiti_core/utils/maintenance/node_operations.py +319 -238
- graphiti_core/utils/maintenance/temporal_operations.py +11 -3
- graphiti_core/utils/ontology_utils/entity_types_utils.py +1 -1
- graphiti_core/utils/text_utils.py +53 -0
- graphiti_core-0.24.3.dist-info/METADATA +726 -0
- graphiti_core-0.24.3.dist-info/RECORD +86 -0
- {graphiti_core-0.12.0rc1.dist-info → graphiti_core-0.24.3.dist-info}/WHEEL +1 -1
- graphiti_core-0.12.0rc1.dist-info/METADATA +0 -350
- graphiti_core-0.12.0rc1.dist-info/RECORD +0 -66
- /graphiti_core/{utils/maintenance/utils.py → migrations/__init__.py} +0 -0
- {graphiti_core-0.12.0rc1.dist-info → graphiti_core-0.24.3.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Telemetry client for Graphiti.
|
|
3
|
+
|
|
4
|
+
Collects anonymous usage statistics to help improve the product.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import contextlib
|
|
8
|
+
import os
|
|
9
|
+
import platform
|
|
10
|
+
import sys
|
|
11
|
+
import uuid
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
# PostHog configuration
|
|
16
|
+
# Note: This is a public API key intended for client-side use and safe to commit
|
|
17
|
+
# PostHog public keys are designed to be exposed in client applications
|
|
18
|
+
POSTHOG_API_KEY = 'phc_UG6EcfDbuXz92neb3rMlQFDY0csxgMqRcIPWESqnSmo'
|
|
19
|
+
POSTHOG_HOST = 'https://us.i.posthog.com'
|
|
20
|
+
|
|
21
|
+
# Environment variable to control telemetry
|
|
22
|
+
TELEMETRY_ENV_VAR = 'GRAPHITI_TELEMETRY_ENABLED'
|
|
23
|
+
|
|
24
|
+
# Cache directory for anonymous ID
|
|
25
|
+
CACHE_DIR = Path.home() / '.cache' / 'graphiti'
|
|
26
|
+
ANON_ID_FILE = CACHE_DIR / 'telemetry_anon_id'
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def is_telemetry_enabled() -> bool:
|
|
30
|
+
"""Check if telemetry is enabled."""
|
|
31
|
+
# Disable during pytest runs
|
|
32
|
+
if 'pytest' in sys.modules:
|
|
33
|
+
return False
|
|
34
|
+
|
|
35
|
+
# Check environment variable (default: enabled)
|
|
36
|
+
env_value = os.environ.get(TELEMETRY_ENV_VAR, 'true').lower()
|
|
37
|
+
return env_value in ('true', '1', 'yes', 'on')
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_anonymous_id() -> str:
|
|
41
|
+
"""Get or create anonymous user ID."""
|
|
42
|
+
try:
|
|
43
|
+
# Create cache directory if it doesn't exist
|
|
44
|
+
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
|
|
46
|
+
# Try to read existing ID
|
|
47
|
+
if ANON_ID_FILE.exists():
|
|
48
|
+
try:
|
|
49
|
+
return ANON_ID_FILE.read_text().strip()
|
|
50
|
+
except Exception:
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
# Generate new ID
|
|
54
|
+
anon_id = str(uuid.uuid4())
|
|
55
|
+
|
|
56
|
+
# Save to file
|
|
57
|
+
with contextlib.suppress(Exception):
|
|
58
|
+
ANON_ID_FILE.write_text(anon_id)
|
|
59
|
+
|
|
60
|
+
return anon_id
|
|
61
|
+
except Exception:
|
|
62
|
+
return 'UNKNOWN'
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def get_graphiti_version() -> str:
|
|
66
|
+
"""Get Graphiti version."""
|
|
67
|
+
try:
|
|
68
|
+
# Try to get version from package metadata
|
|
69
|
+
import importlib.metadata
|
|
70
|
+
|
|
71
|
+
return importlib.metadata.version('graphiti-core')
|
|
72
|
+
except Exception:
|
|
73
|
+
return 'unknown'
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def initialize_posthog():
|
|
77
|
+
"""Initialize PostHog client."""
|
|
78
|
+
try:
|
|
79
|
+
import posthog
|
|
80
|
+
|
|
81
|
+
posthog.api_key = POSTHOG_API_KEY
|
|
82
|
+
posthog.host = POSTHOG_HOST
|
|
83
|
+
return posthog
|
|
84
|
+
except ImportError:
|
|
85
|
+
# PostHog not installed, silently disable telemetry
|
|
86
|
+
return None
|
|
87
|
+
except Exception:
|
|
88
|
+
# Any other error, silently disable telemetry
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def capture_event(event_name: str, properties: dict[str, Any] | None = None) -> None:
|
|
93
|
+
"""Capture a telemetry event."""
|
|
94
|
+
if not is_telemetry_enabled():
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
posthog_client = initialize_posthog()
|
|
99
|
+
if posthog_client is None:
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
# Get anonymous ID
|
|
103
|
+
user_id = get_anonymous_id()
|
|
104
|
+
|
|
105
|
+
# Prepare event properties
|
|
106
|
+
event_properties = {
|
|
107
|
+
'$process_person_profile': False,
|
|
108
|
+
'graphiti_version': get_graphiti_version(),
|
|
109
|
+
'architecture': platform.machine(),
|
|
110
|
+
**(properties or {}),
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# Capture the event
|
|
114
|
+
posthog_client.capture(distinct_id=user_id, event=event_name, properties=event_properties)
|
|
115
|
+
except Exception:
|
|
116
|
+
# Silently handle all telemetry errors to avoid disrupting the main application
|
|
117
|
+
pass
|
graphiti_core/tracer.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright 2024, Zep Software, Inc.
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from abc import ABC, abstractmethod
|
|
18
|
+
from collections.abc import Generator
|
|
19
|
+
from contextlib import AbstractContextManager, contextmanager, suppress
|
|
20
|
+
from typing import TYPE_CHECKING, Any
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from opentelemetry.trace import Span, StatusCode
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
from opentelemetry.trace import Span, StatusCode
|
|
27
|
+
|
|
28
|
+
OTEL_AVAILABLE = True
|
|
29
|
+
except ImportError:
|
|
30
|
+
OTEL_AVAILABLE = False
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TracerSpan(ABC):
|
|
34
|
+
"""Abstract base class for tracer spans."""
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def add_attributes(self, attributes: dict[str, Any]) -> None:
|
|
38
|
+
"""Add attributes to the span."""
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def set_status(self, status: str, description: str | None = None) -> None:
|
|
43
|
+
"""Set the status of the span."""
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def record_exception(self, exception: Exception) -> None:
|
|
48
|
+
"""Record an exception in the span."""
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Tracer(ABC):
|
|
53
|
+
"""Abstract base class for tracers."""
|
|
54
|
+
|
|
55
|
+
@abstractmethod
|
|
56
|
+
def start_span(self, name: str) -> AbstractContextManager[TracerSpan]:
|
|
57
|
+
"""Start a new span with the given name."""
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class NoOpSpan(TracerSpan):
|
|
62
|
+
"""No-op span implementation that does nothing."""
|
|
63
|
+
|
|
64
|
+
def add_attributes(self, attributes: dict[str, Any]) -> None:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
def set_status(self, status: str, description: str | None = None) -> None:
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
def record_exception(self, exception: Exception) -> None:
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class NoOpTracer(Tracer):
|
|
75
|
+
"""No-op tracer implementation that does nothing."""
|
|
76
|
+
|
|
77
|
+
@contextmanager
|
|
78
|
+
def start_span(self, name: str) -> Generator[NoOpSpan, None, None]:
|
|
79
|
+
"""Return a no-op span."""
|
|
80
|
+
yield NoOpSpan()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class OpenTelemetrySpan(TracerSpan):
|
|
84
|
+
"""Wrapper for OpenTelemetry span."""
|
|
85
|
+
|
|
86
|
+
def __init__(self, span: 'Span'):
|
|
87
|
+
self._span = span
|
|
88
|
+
|
|
89
|
+
def add_attributes(self, attributes: dict[str, Any]) -> None:
|
|
90
|
+
"""Add attributes to the OpenTelemetry span."""
|
|
91
|
+
try:
|
|
92
|
+
# Filter out None values and convert all values to appropriate types
|
|
93
|
+
filtered_attrs = {}
|
|
94
|
+
for key, value in attributes.items():
|
|
95
|
+
if value is not None:
|
|
96
|
+
# Convert to string if not a primitive type
|
|
97
|
+
if isinstance(value, str | int | float | bool):
|
|
98
|
+
filtered_attrs[key] = value
|
|
99
|
+
else:
|
|
100
|
+
filtered_attrs[key] = str(value)
|
|
101
|
+
|
|
102
|
+
if filtered_attrs:
|
|
103
|
+
self._span.set_attributes(filtered_attrs)
|
|
104
|
+
except Exception:
|
|
105
|
+
# Silently ignore tracing errors
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
def set_status(self, status: str, description: str | None = None) -> None:
|
|
109
|
+
"""Set the status of the OpenTelemetry span."""
|
|
110
|
+
try:
|
|
111
|
+
if OTEL_AVAILABLE:
|
|
112
|
+
if status == 'error':
|
|
113
|
+
self._span.set_status(StatusCode.ERROR, description)
|
|
114
|
+
elif status == 'ok':
|
|
115
|
+
self._span.set_status(StatusCode.OK, description)
|
|
116
|
+
except Exception:
|
|
117
|
+
# Silently ignore tracing errors
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
def record_exception(self, exception: Exception) -> None:
|
|
121
|
+
"""Record an exception in the OpenTelemetry span."""
|
|
122
|
+
with suppress(Exception):
|
|
123
|
+
self._span.record_exception(exception)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class OpenTelemetryTracer(Tracer):
|
|
127
|
+
"""Wrapper for OpenTelemetry tracer with configurable span name prefix."""
|
|
128
|
+
|
|
129
|
+
def __init__(self, tracer: Any, span_prefix: str = 'graphiti'):
|
|
130
|
+
"""
|
|
131
|
+
Initialize the OpenTelemetry tracer wrapper.
|
|
132
|
+
|
|
133
|
+
Parameters
|
|
134
|
+
----------
|
|
135
|
+
tracer : opentelemetry.trace.Tracer
|
|
136
|
+
The OpenTelemetry tracer instance.
|
|
137
|
+
span_prefix : str, optional
|
|
138
|
+
Prefix to prepend to all span names. Defaults to 'graphiti'.
|
|
139
|
+
"""
|
|
140
|
+
if not OTEL_AVAILABLE:
|
|
141
|
+
raise ImportError(
|
|
142
|
+
'OpenTelemetry is not installed. Install it with: pip install opentelemetry-api'
|
|
143
|
+
)
|
|
144
|
+
self._tracer = tracer
|
|
145
|
+
self._span_prefix = span_prefix.rstrip('.')
|
|
146
|
+
|
|
147
|
+
@contextmanager
|
|
148
|
+
def start_span(self, name: str) -> Generator[OpenTelemetrySpan | NoOpSpan, None, None]:
|
|
149
|
+
"""Start a new OpenTelemetry span with the configured prefix."""
|
|
150
|
+
try:
|
|
151
|
+
full_name = f'{self._span_prefix}.{name}'
|
|
152
|
+
with self._tracer.start_as_current_span(full_name) as span:
|
|
153
|
+
yield OpenTelemetrySpan(span)
|
|
154
|
+
except Exception:
|
|
155
|
+
# If tracing fails, yield a no-op span to prevent breaking the operation
|
|
156
|
+
yield NoOpSpan()
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def create_tracer(otel_tracer: Any | None = None, span_prefix: str = 'graphiti') -> Tracer:
|
|
160
|
+
"""
|
|
161
|
+
Create a tracer instance.
|
|
162
|
+
|
|
163
|
+
Parameters
|
|
164
|
+
----------
|
|
165
|
+
otel_tracer : opentelemetry.trace.Tracer | None, optional
|
|
166
|
+
An OpenTelemetry tracer instance. If None, a no-op tracer is returned.
|
|
167
|
+
span_prefix : str, optional
|
|
168
|
+
Prefix to prepend to all span names. Defaults to 'graphiti'.
|
|
169
|
+
|
|
170
|
+
Returns
|
|
171
|
+
-------
|
|
172
|
+
Tracer
|
|
173
|
+
A tracer instance (either OpenTelemetryTracer or NoOpTracer).
|
|
174
|
+
|
|
175
|
+
Examples
|
|
176
|
+
--------
|
|
177
|
+
Using with OpenTelemetry:
|
|
178
|
+
|
|
179
|
+
>>> from opentelemetry import trace
|
|
180
|
+
>>> otel_tracer = trace.get_tracer(__name__)
|
|
181
|
+
>>> tracer = create_tracer(otel_tracer, span_prefix='myapp.graphiti')
|
|
182
|
+
|
|
183
|
+
Using no-op tracer:
|
|
184
|
+
|
|
185
|
+
>>> tracer = create_tracer() # Returns NoOpTracer
|
|
186
|
+
"""
|
|
187
|
+
if otel_tracer is None:
|
|
188
|
+
return NoOpTracer()
|
|
189
|
+
|
|
190
|
+
if not OTEL_AVAILABLE:
|
|
191
|
+
return NoOpTracer()
|
|
192
|
+
|
|
193
|
+
return OpenTelemetryTracer(otel_tracer, span_prefix)
|