graphiti-core 0.22.0rc3__py3-none-any.whl → 0.22.0rc5__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.

@@ -67,13 +67,13 @@ def edge(context: dict[str, Any]) -> list[Message]:
67
67
  Given the following context, determine whether the New Edge represents any of the edges in the list of Existing Edges.
68
68
 
69
69
  <EXISTING EDGES>
70
- {to_prompt_json(context['related_edges'], indent=2)}
70
+ {to_prompt_json(context['related_edges'])}
71
71
  </EXISTING EDGES>
72
72
 
73
73
  <NEW EDGE>
74
- {to_prompt_json(context['extracted_edges'], indent=2)}
74
+ {to_prompt_json(context['extracted_edges'])}
75
75
  </NEW EDGE>
76
-
76
+
77
77
  Task:
78
78
  If the New Edges represents the same factual information as any edge in Existing Edges, return the id of the duplicate fact
79
79
  as part of the list of duplicate_facts.
@@ -98,7 +98,7 @@ def edge_list(context: dict[str, Any]) -> list[Message]:
98
98
  Given the following context, find all of the duplicates in a list of facts:
99
99
 
100
100
  Facts:
101
- {to_prompt_json(context['edges'], indent=2)}
101
+ {to_prompt_json(context['edges'])}
102
102
 
103
103
  Task:
104
104
  If any facts in Facts is a duplicate of another fact, return a new fact with one of their uuid's.
@@ -64,20 +64,20 @@ def node(context: dict[str, Any]) -> list[Message]:
64
64
  role='user',
65
65
  content=f"""
66
66
  <PREVIOUS MESSAGES>
67
- {to_prompt_json([ep for ep in context['previous_episodes']], indent=2)}
67
+ {to_prompt_json([ep for ep in context['previous_episodes']])}
68
68
  </PREVIOUS MESSAGES>
69
69
  <CURRENT MESSAGE>
70
70
  {context['episode_content']}
71
71
  </CURRENT MESSAGE>
72
72
  <NEW ENTITY>
73
- {to_prompt_json(context['extracted_node'], indent=2)}
73
+ {to_prompt_json(context['extracted_node'])}
74
74
  </NEW ENTITY>
75
75
  <ENTITY TYPE DESCRIPTION>
76
- {to_prompt_json(context['entity_type_description'], indent=2)}
76
+ {to_prompt_json(context['entity_type_description'])}
77
77
  </ENTITY TYPE DESCRIPTION>
78
78
 
79
79
  <EXISTING ENTITIES>
80
- {to_prompt_json(context['existing_nodes'], indent=2)}
80
+ {to_prompt_json(context['existing_nodes'])}
81
81
  </EXISTING ENTITIES>
82
82
 
83
83
  Given the above EXISTING ENTITIES and their attributes, MESSAGE, and PREVIOUS MESSAGES; Determine if the NEW ENTITY extracted from the conversation
@@ -125,13 +125,13 @@ def nodes(context: dict[str, Any]) -> list[Message]:
125
125
  role='user',
126
126
  content=f"""
127
127
  <PREVIOUS MESSAGES>
128
- {to_prompt_json([ep for ep in context['previous_episodes']], indent=2)}
128
+ {to_prompt_json([ep for ep in context['previous_episodes']])}
129
129
  </PREVIOUS MESSAGES>
130
130
  <CURRENT MESSAGE>
131
131
  {context['episode_content']}
132
132
  </CURRENT MESSAGE>
133
-
134
-
133
+
134
+
135
135
  Each of the following ENTITIES were extracted from the CURRENT MESSAGE.
136
136
  Each entity in ENTITIES is represented as a JSON object with the following structure:
137
137
  {{
@@ -142,11 +142,11 @@ def nodes(context: dict[str, Any]) -> list[Message]:
142
142
  }}
143
143
 
144
144
  <ENTITIES>
145
- {to_prompt_json(context['extracted_nodes'], indent=2)}
145
+ {to_prompt_json(context['extracted_nodes'])}
146
146
  </ENTITIES>
147
147
 
148
148
  <EXISTING ENTITIES>
149
- {to_prompt_json(context['existing_nodes'], indent=2)}
149
+ {to_prompt_json(context['existing_nodes'])}
150
150
  </EXISTING ENTITIES>
151
151
 
152
152
  Each entry in EXISTING ENTITIES is an object with the following structure:
@@ -197,7 +197,7 @@ def node_list(context: dict[str, Any]) -> list[Message]:
197
197
  Given the following context, deduplicate a list of nodes:
198
198
 
199
199
  Nodes:
200
- {to_prompt_json(context['nodes'], indent=2)}
200
+ {to_prompt_json(context['nodes'])}
201
201
 
202
202
  Task:
203
203
  1. Group nodes together such that all duplicate nodes are in the same list of uuids
@@ -80,7 +80,7 @@ def edge(context: dict[str, Any]) -> list[Message]:
80
80
  </FACT TYPES>
81
81
 
82
82
  <PREVIOUS_MESSAGES>
83
- {to_prompt_json([ep for ep in context['previous_episodes']], indent=2)}
83
+ {to_prompt_json([ep for ep in context['previous_episodes']])}
84
84
  </PREVIOUS_MESSAGES>
85
85
 
86
86
  <CURRENT_MESSAGE>
@@ -88,7 +88,7 @@ def edge(context: dict[str, Any]) -> list[Message]:
88
88
  </CURRENT_MESSAGE>
89
89
 
90
90
  <ENTITIES>
91
- {to_prompt_json(context['nodes'], indent=2)}
91
+ {to_prompt_json(context['nodes'])}
92
92
  </ENTITIES>
93
93
 
94
94
  <REFERENCE_TIME>
@@ -141,7 +141,7 @@ def reflexion(context: dict[str, Any]) -> list[Message]:
141
141
 
142
142
  user_prompt = f"""
143
143
  <PREVIOUS MESSAGES>
144
- {to_prompt_json([ep for ep in context['previous_episodes']], indent=2)}
144
+ {to_prompt_json([ep for ep in context['previous_episodes']])}
145
145
  </PREVIOUS MESSAGES>
146
146
  <CURRENT MESSAGE>
147
147
  {context['episode_content']}
@@ -175,7 +175,7 @@ def extract_attributes(context: dict[str, Any]) -> list[Message]:
175
175
  content=f"""
176
176
 
177
177
  <MESSAGE>
178
- {to_prompt_json(context['episode_content'], indent=2)}
178
+ {to_prompt_json(context['episode_content'])}
179
179
  </MESSAGE>
180
180
  <REFERENCE TIME>
181
181
  {context['reference_time']}
@@ -93,7 +93,7 @@ def extract_message(context: dict[str, Any]) -> list[Message]:
93
93
  </ENTITY TYPES>
94
94
 
95
95
  <PREVIOUS MESSAGES>
96
- {to_prompt_json([ep for ep in context['previous_episodes']], indent=2)}
96
+ {to_prompt_json([ep for ep in context['previous_episodes']])}
97
97
  </PREVIOUS MESSAGES>
98
98
 
99
99
  <CURRENT MESSAGE>
@@ -201,7 +201,7 @@ def reflexion(context: dict[str, Any]) -> list[Message]:
201
201
 
202
202
  user_prompt = f"""
203
203
  <PREVIOUS MESSAGES>
204
- {to_prompt_json([ep for ep in context['previous_episodes']], indent=2)}
204
+ {to_prompt_json([ep for ep in context['previous_episodes']])}
205
205
  </PREVIOUS MESSAGES>
206
206
  <CURRENT MESSAGE>
207
207
  {context['episode_content']}
@@ -225,22 +225,22 @@ def classify_nodes(context: dict[str, Any]) -> list[Message]:
225
225
 
226
226
  user_prompt = f"""
227
227
  <PREVIOUS MESSAGES>
228
- {to_prompt_json([ep for ep in context['previous_episodes']], indent=2)}
228
+ {to_prompt_json([ep for ep in context['previous_episodes']])}
229
229
  </PREVIOUS MESSAGES>
230
230
  <CURRENT MESSAGE>
231
231
  {context['episode_content']}
232
232
  </CURRENT MESSAGE>
233
-
233
+
234
234
  <EXTRACTED ENTITIES>
235
235
  {context['extracted_entities']}
236
236
  </EXTRACTED ENTITIES>
237
-
237
+
238
238
  <ENTITY TYPES>
239
239
  {context['entity_types']}
240
240
  </ENTITY TYPES>
241
-
241
+
242
242
  Given the above conversation, extracted entities, and provided entity types and their descriptions, classify the extracted entities.
243
-
243
+
244
244
  Guidelines:
245
245
  1. Each entity must have exactly one type
246
246
  2. Only use the provided ENTITY TYPES as types, do not use additional types to classify entities.
@@ -269,10 +269,10 @@ def extract_attributes(context: dict[str, Any]) -> list[Message]:
269
269
  2. Only use the provided MESSAGES and ENTITY to set attribute values.
270
270
 
271
271
  <MESSAGES>
272
- {to_prompt_json(context['previous_episodes'], indent=2)}
273
- {to_prompt_json(context['episode_content'], indent=2)}
272
+ {to_prompt_json(context['previous_episodes'])}
273
+ {to_prompt_json(context['episode_content'])}
274
274
  </MESSAGES>
275
-
275
+
276
276
  <ENTITY>
277
277
  {context['node']}
278
278
  </ENTITY>
@@ -292,12 +292,12 @@ def extract_summary(context: dict[str, Any]) -> list[Message]:
292
292
  content=f"""
293
293
  Given the MESSAGES and the ENTITY, update the summary that combines relevant information about the entity
294
294
  from the messages and relevant information from the existing summary.
295
-
295
+
296
296
  {summary_instructions}
297
297
 
298
298
  <MESSAGES>
299
- {to_prompt_json(context['previous_episodes'], indent=2)}
300
- {to_prompt_json(context['episode_content'], indent=2)}
299
+ {to_prompt_json(context['previous_episodes'])}
300
+ {to_prompt_json(context['episode_content'])}
301
301
  </MESSAGES>
302
302
 
303
303
  <ENTITY>
@@ -20,14 +20,14 @@ from typing import Any
20
20
  DO_NOT_ESCAPE_UNICODE = '\nDo not escape unicode characters.\n'
21
21
 
22
22
 
23
- def to_prompt_json(data: Any, ensure_ascii: bool = False, indent: int = 2) -> str:
23
+ def to_prompt_json(data: Any, ensure_ascii: bool = False, indent: int | None = None) -> str:
24
24
  """
25
25
  Serialize data to JSON for use in prompts.
26
26
 
27
27
  Args:
28
28
  data: The data to serialize
29
29
  ensure_ascii: If True, escape non-ASCII characters. If False (default), preserve them.
30
- indent: Number of spaces for indentation
30
+ indent: Number of spaces for indentation. Defaults to None (minified).
31
31
 
32
32
  Returns:
33
33
  JSON string representation of the data
@@ -56,11 +56,11 @@ def summarize_pair(context: dict[str, Any]) -> list[Message]:
56
56
  role='user',
57
57
  content=f"""
58
58
  Synthesize the information from the following two summaries into a single succinct summary.
59
-
59
+
60
60
  IMPORTANT: Keep the summary concise and to the point. SUMMARIES MUST BE LESS THAN 250 CHARACTERS.
61
61
 
62
62
  Summaries:
63
- {to_prompt_json(context['node_summaries'], indent=2)}
63
+ {to_prompt_json(context['node_summaries'])}
64
64
  """,
65
65
  ),
66
66
  ]
@@ -77,28 +77,28 @@ def summarize_context(context: dict[str, Any]) -> list[Message]:
77
77
  content=f"""
78
78
  Given the MESSAGES and the ENTITY name, create a summary for the ENTITY. Your summary must only use
79
79
  information from the provided MESSAGES. Your summary should also only contain information relevant to the
80
- provided ENTITY.
81
-
80
+ provided ENTITY.
81
+
82
82
  In addition, extract any values for the provided entity properties based on their descriptions.
83
83
  If the value of the entity property cannot be found in the current context, set the value of the property to the Python value None.
84
-
84
+
85
85
  {summary_instructions}
86
86
 
87
87
  <MESSAGES>
88
- {to_prompt_json(context['previous_episodes'], indent=2)}
89
- {to_prompt_json(context['episode_content'], indent=2)}
88
+ {to_prompt_json(context['previous_episodes'])}
89
+ {to_prompt_json(context['episode_content'])}
90
90
  </MESSAGES>
91
-
91
+
92
92
  <ENTITY>
93
93
  {context['node_name']}
94
94
  </ENTITY>
95
-
95
+
96
96
  <ENTITY CONTEXT>
97
97
  {context['node_summary']}
98
98
  </ENTITY CONTEXT>
99
-
99
+
100
100
  <ATTRIBUTES>
101
- {to_prompt_json(context['attributes'], indent=2)}
101
+ {to_prompt_json(context['attributes'])}
102
102
  </ATTRIBUTES>
103
103
  """,
104
104
  ),
@@ -118,7 +118,7 @@ def summary_description(context: dict[str, Any]) -> list[Message]:
118
118
  Summaries must be under 250 characters.
119
119
 
120
120
  Summary:
121
- {to_prompt_json(context['summary'], indent=2)}
121
+ {to_prompt_json(context['summary'])}
122
122
  """,
123
123
  ),
124
124
  ]
@@ -56,16 +56,16 @@ def search_results_to_context_string(search_results: SearchResults) -> str:
56
56
  These are the most relevant facts and their valid and invalid dates. Facts are considered valid
57
57
  between their valid_at and invalid_at dates. Facts with an invalid_at date of "Present" are considered valid.
58
58
  <FACTS>
59
- {to_prompt_json(fact_json, indent=12)}
59
+ {to_prompt_json(fact_json)}
60
60
  </FACTS>
61
61
  <ENTITIES>
62
- {to_prompt_json(entity_json, indent=12)}
62
+ {to_prompt_json(entity_json)}
63
63
  </ENTITIES>
64
64
  <EPISODES>
65
- {to_prompt_json(episode_json, indent=12)}
65
+ {to_prompt_json(episode_json)}
66
66
  </EPISODES>
67
67
  <COMMUNITIES>
68
- {to_prompt_json(community_json, indent=12)}
68
+ {to_prompt_json(community_json)}
69
69
  </COMMUNITIES>
70
70
  """
71
71
 
@@ -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)
@@ -138,7 +138,9 @@ async def summarize_pair(llm_client: LLMClient, summary_pair: tuple[str, str]) -
138
138
  }
139
139
 
140
140
  llm_response = await llm_client.generate_response(
141
- prompt_library.summarize_nodes.summarize_pair(context), response_model=Summary
141
+ prompt_library.summarize_nodes.summarize_pair(context),
142
+ response_model=Summary,
143
+ prompt_name='summarize_nodes.summarize_pair',
142
144
  )
143
145
 
144
146
  pair_summary = llm_response.get('summary', '')
@@ -154,6 +156,7 @@ async def generate_summary_description(llm_client: LLMClient, summary: str) -> s
154
156
  llm_response = await llm_client.generate_response(
155
157
  prompt_library.summarize_nodes.summary_description(context),
156
158
  response_model=SummaryDescription,
159
+ prompt_name='summarize_nodes.summary_description',
157
160
  )
158
161
 
159
162
  description = llm_response.get('description', '')
@@ -140,6 +140,7 @@ async def extract_edges(
140
140
  response_model=ExtractedEdges,
141
141
  max_tokens=extract_edges_max_tokens,
142
142
  group_id=group_id,
143
+ prompt_name='extract_edges.edge',
143
144
  )
144
145
  edges_data = ExtractedEdges(**llm_response).edges
145
146
 
@@ -152,6 +153,7 @@ async def extract_edges(
152
153
  response_model=MissingFacts,
153
154
  max_tokens=extract_edges_max_tokens,
154
155
  group_id=group_id,
156
+ prompt_name='extract_edges.reflexion',
155
157
  )
156
158
 
157
159
  missing_facts = reflexion_response.get('missing_facts', [])
@@ -526,6 +528,7 @@ async def resolve_extracted_edge(
526
528
  prompt_library.dedupe_edges.resolve_edge(context),
527
529
  response_model=EdgeDuplicate,
528
530
  model_size=ModelSize.small,
531
+ prompt_name='dedupe_edges.resolve_edge',
529
532
  )
530
533
  response_object = EdgeDuplicate(**llm_response)
531
534
  duplicate_facts = response_object.duplicate_facts
@@ -589,6 +592,7 @@ async def resolve_extracted_edge(
589
592
  prompt_library.extract_edges.extract_attributes(edge_attributes_context),
590
593
  response_model=edge_model, # type: ignore
591
594
  model_size=ModelSize.small,
595
+ prompt_name='extract_edges.extract_attributes',
592
596
  )
593
597
 
594
598
  resolved_edge.attributes = edge_attributes_response
@@ -78,6 +78,7 @@ async def extract_nodes_reflexion(
78
78
  prompt_library.extract_nodes.reflexion(context),
79
79
  MissedEntities,
80
80
  group_id=group_id,
81
+ prompt_name='extract_nodes.reflexion',
81
82
  )
82
83
  missed_entities = llm_response.get('missed_entities', [])
83
84
 
@@ -134,18 +135,21 @@ async def extract_nodes(
134
135
  prompt_library.extract_nodes.extract_message(context),
135
136
  response_model=ExtractedEntities,
136
137
  group_id=episode.group_id,
138
+ prompt_name='extract_nodes.extract_message',
137
139
  )
138
140
  elif episode.source == EpisodeType.text:
139
141
  llm_response = await llm_client.generate_response(
140
142
  prompt_library.extract_nodes.extract_text(context),
141
143
  response_model=ExtractedEntities,
142
144
  group_id=episode.group_id,
145
+ prompt_name='extract_nodes.extract_text',
143
146
  )
144
147
  elif episode.source == EpisodeType.json:
145
148
  llm_response = await llm_client.generate_response(
146
149
  prompt_library.extract_nodes.extract_json(context),
147
150
  response_model=ExtractedEntities,
148
151
  group_id=episode.group_id,
152
+ prompt_name='extract_nodes.extract_json',
149
153
  )
150
154
 
151
155
  response_object = ExtractedEntities(**llm_response)
@@ -318,6 +322,7 @@ async def _resolve_with_llm(
318
322
  llm_response = await llm_client.generate_response(
319
323
  prompt_library.dedupe_nodes.nodes(context),
320
324
  response_model=NodeResolutions,
325
+ prompt_name='dedupe_nodes.nodes',
321
326
  )
322
327
 
323
328
  node_resolutions: list[NodeDuplicate] = NodeResolutions(**llm_response).entity_resolutions
@@ -527,6 +532,7 @@ async def _extract_entity_attributes(
527
532
  response_model=entity_type,
528
533
  model_size=ModelSize.small,
529
534
  group_id=node.group_id,
535
+ prompt_name='extract_nodes.extract_attributes',
530
536
  )
531
537
 
532
538
  # validate response
@@ -561,6 +567,7 @@ async def _extract_entity_summary(
561
567
  response_model=EntitySummary,
562
568
  model_size=ModelSize.small,
563
569
  group_id=node.group_id,
570
+ prompt_name='extract_nodes.extract_summary',
564
571
  )
565
572
 
566
573
  node.summary = truncate_at_sentence(summary_response.get('summary', ''), MAX_SUMMARY_CHARS)
@@ -43,7 +43,9 @@ async def extract_edge_dates(
43
43
  'reference_timestamp': current_episode.valid_at.isoformat(),
44
44
  }
45
45
  llm_response = await llm_client.generate_response(
46
- prompt_library.extract_edge_dates.v1(context), response_model=EdgeDates
46
+ prompt_library.extract_edge_dates.v1(context),
47
+ response_model=EdgeDates,
48
+ prompt_name='extract_edge_dates.v1',
47
49
  )
48
50
 
49
51
  valid_at = llm_response.get('valid_at')
@@ -90,6 +92,7 @@ async def get_edge_contradictions(
90
92
  prompt_library.invalidate_edges.v2(context),
91
93
  response_model=InvalidatedEdges,
92
94
  model_size=ModelSize.small,
95
+ prompt_name='invalidate_edges.v2',
93
96
  )
94
97
 
95
98
  contradicted_facts: list[int] = llm_response.get('contradicted_facts', [])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphiti-core
3
- Version: 0.22.0rc3
3
+ Version: 0.22.0rc5
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
@@ -34,6 +34,7 @@ Requires-Dist: langchain-openai>=0.2.6; extra == 'dev'
34
34
  Requires-Dist: langgraph>=0.2.15; extra == 'dev'
35
35
  Requires-Dist: langsmith>=0.1.108; extra == 'dev'
36
36
  Requires-Dist: opensearch-py>=3.0.0; extra == 'dev'
37
+ Requires-Dist: opentelemetry-sdk>=1.20.0; extra == 'dev'
37
38
  Requires-Dist: pyright>=1.1.404; extra == 'dev'
38
39
  Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
39
40
  Requires-Dist: pytest-xdist>=3.6.1; extra == 'dev'
@@ -59,6 +60,9 @@ Requires-Dist: langchain-aws>=0.2.29; extra == 'neptune'
59
60
  Requires-Dist: opensearch-py>=3.0.0; extra == 'neptune'
60
61
  Provides-Extra: sentence-transformers
61
62
  Requires-Dist: sentence-transformers>=3.2.1; extra == 'sentence-transformers'
63
+ Provides-Extra: tracing
64
+ Requires-Dist: opentelemetry-api>=1.20.0; extra == 'tracing'
65
+ Requires-Dist: opentelemetry-sdk>=1.20.0; extra == 'tracing'
62
66
  Provides-Extra: voyageai
63
67
  Requires-Dist: voyageai>=0.2.3; extra == 'voyageai'
64
68
  Description-Content-Type: text/markdown