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.
Files changed (68) hide show
  1. graphiti_core/cross_encoder/bge_reranker_client.py +12 -2
  2. graphiti_core/cross_encoder/gemini_reranker_client.py +161 -0
  3. graphiti_core/cross_encoder/openai_reranker_client.py +7 -5
  4. graphiti_core/decorators.py +110 -0
  5. graphiti_core/driver/__init__.py +19 -0
  6. graphiti_core/driver/driver.py +124 -0
  7. graphiti_core/driver/falkordb_driver.py +362 -0
  8. graphiti_core/driver/graph_operations/graph_operations.py +191 -0
  9. graphiti_core/driver/kuzu_driver.py +182 -0
  10. graphiti_core/driver/neo4j_driver.py +117 -0
  11. graphiti_core/driver/neptune_driver.py +305 -0
  12. graphiti_core/driver/search_interface/search_interface.py +89 -0
  13. graphiti_core/edges.py +287 -172
  14. graphiti_core/embedder/azure_openai.py +71 -0
  15. graphiti_core/embedder/client.py +2 -1
  16. graphiti_core/embedder/gemini.py +116 -22
  17. graphiti_core/embedder/voyage.py +13 -2
  18. graphiti_core/errors.py +8 -0
  19. graphiti_core/graph_queries.py +162 -0
  20. graphiti_core/graphiti.py +705 -193
  21. graphiti_core/graphiti_types.py +4 -2
  22. graphiti_core/helpers.py +87 -10
  23. graphiti_core/llm_client/__init__.py +16 -0
  24. graphiti_core/llm_client/anthropic_client.py +159 -56
  25. graphiti_core/llm_client/azure_openai_client.py +115 -0
  26. graphiti_core/llm_client/client.py +98 -21
  27. graphiti_core/llm_client/config.py +1 -1
  28. graphiti_core/llm_client/gemini_client.py +290 -41
  29. graphiti_core/llm_client/groq_client.py +14 -3
  30. graphiti_core/llm_client/openai_base_client.py +261 -0
  31. graphiti_core/llm_client/openai_client.py +56 -132
  32. graphiti_core/llm_client/openai_generic_client.py +91 -56
  33. graphiti_core/models/edges/edge_db_queries.py +259 -35
  34. graphiti_core/models/nodes/node_db_queries.py +311 -32
  35. graphiti_core/nodes.py +420 -205
  36. graphiti_core/prompts/dedupe_edges.py +46 -32
  37. graphiti_core/prompts/dedupe_nodes.py +67 -42
  38. graphiti_core/prompts/eval.py +4 -4
  39. graphiti_core/prompts/extract_edges.py +27 -16
  40. graphiti_core/prompts/extract_nodes.py +74 -31
  41. graphiti_core/prompts/prompt_helpers.py +39 -0
  42. graphiti_core/prompts/snippets.py +29 -0
  43. graphiti_core/prompts/summarize_nodes.py +23 -25
  44. graphiti_core/search/search.py +158 -82
  45. graphiti_core/search/search_config.py +39 -4
  46. graphiti_core/search/search_filters.py +126 -35
  47. graphiti_core/search/search_helpers.py +5 -6
  48. graphiti_core/search/search_utils.py +1405 -485
  49. graphiti_core/telemetry/__init__.py +9 -0
  50. graphiti_core/telemetry/telemetry.py +117 -0
  51. graphiti_core/tracer.py +193 -0
  52. graphiti_core/utils/bulk_utils.py +364 -285
  53. graphiti_core/utils/datetime_utils.py +13 -0
  54. graphiti_core/utils/maintenance/community_operations.py +67 -49
  55. graphiti_core/utils/maintenance/dedup_helpers.py +262 -0
  56. graphiti_core/utils/maintenance/edge_operations.py +339 -197
  57. graphiti_core/utils/maintenance/graph_data_operations.py +50 -114
  58. graphiti_core/utils/maintenance/node_operations.py +319 -238
  59. graphiti_core/utils/maintenance/temporal_operations.py +11 -3
  60. graphiti_core/utils/ontology_utils/entity_types_utils.py +1 -1
  61. graphiti_core/utils/text_utils.py +53 -0
  62. graphiti_core-0.24.3.dist-info/METADATA +726 -0
  63. graphiti_core-0.24.3.dist-info/RECORD +86 -0
  64. {graphiti_core-0.12.0rc1.dist-info → graphiti_core-0.24.3.dist-info}/WHEEL +1 -1
  65. graphiti_core-0.12.0rc1.dist-info/METADATA +0 -350
  66. graphiti_core-0.12.0rc1.dist-info/RECORD +0 -66
  67. /graphiti_core/{utils/maintenance/utils.py → migrations/__init__.py} +0 -0
  68. {graphiti_core-0.12.0rc1.dist-info → graphiti_core-0.24.3.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,9 @@
1
+ """
2
+ Telemetry module for Graphiti.
3
+
4
+ This module provides anonymous usage analytics to help improve Graphiti.
5
+ """
6
+
7
+ from .telemetry import capture_event, is_telemetry_enabled
8
+
9
+ __all__ = ['capture_event', 'is_telemetry_enabled']
@@ -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
@@ -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)