graphiti-core 0.11.2__tar.gz → 0.11.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of graphiti-core might be problematic. Click here for more details.

Files changed (66) hide show
  1. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/PKG-INFO +1 -1
  2. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/graphiti.py +1 -1
  3. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/llm_client/anthropic_client.py +6 -2
  4. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/llm_client/client.py +9 -3
  5. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/llm_client/config.py +13 -2
  6. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/llm_client/gemini_client.py +7 -2
  7. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/llm_client/groq_client.py +2 -1
  8. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/llm_client/openai_client.py +13 -3
  9. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/llm_client/openai_generic_client.py +4 -2
  10. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/prompts/dedupe_nodes.py +22 -13
  11. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/prompts/extract_nodes.py +1 -1
  12. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/utils/maintenance/edge_operations.py +4 -1
  13. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/utils/maintenance/node_operations.py +14 -13
  14. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/utils/maintenance/temporal_operations.py +4 -1
  15. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/pyproject.toml +1 -1
  16. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/LICENSE +0 -0
  17. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/README.md +0 -0
  18. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/__init__.py +0 -0
  19. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/cross_encoder/__init__.py +0 -0
  20. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/cross_encoder/bge_reranker_client.py +0 -0
  21. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/cross_encoder/client.py +0 -0
  22. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/cross_encoder/openai_reranker_client.py +0 -0
  23. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/edges.py +0 -0
  24. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/embedder/__init__.py +0 -0
  25. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/embedder/client.py +0 -0
  26. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/embedder/gemini.py +0 -0
  27. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/embedder/openai.py +0 -0
  28. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/embedder/voyage.py +0 -0
  29. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/errors.py +0 -0
  30. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/graphiti_types.py +0 -0
  31. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/helpers.py +0 -0
  32. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/llm_client/__init__.py +0 -0
  33. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/llm_client/errors.py +0 -0
  34. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/llm_client/utils.py +0 -0
  35. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/models/__init__.py +0 -0
  36. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/models/edges/__init__.py +0 -0
  37. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/models/edges/edge_db_queries.py +0 -0
  38. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/models/nodes/__init__.py +0 -0
  39. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/models/nodes/node_db_queries.py +0 -0
  40. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/nodes.py +0 -0
  41. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/prompts/__init__.py +0 -0
  42. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/prompts/dedupe_edges.py +0 -0
  43. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/prompts/eval.py +0 -0
  44. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/prompts/extract_edge_dates.py +0 -0
  45. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/prompts/extract_edges.py +0 -0
  46. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/prompts/invalidate_edges.py +0 -0
  47. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/prompts/lib.py +0 -0
  48. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/prompts/models.py +0 -0
  49. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/prompts/prompt_helpers.py +0 -0
  50. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/prompts/summarize_nodes.py +0 -0
  51. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/py.typed +0 -0
  52. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/search/__init__.py +0 -0
  53. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/search/search.py +0 -0
  54. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/search/search_config.py +0 -0
  55. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/search/search_config_recipes.py +0 -0
  56. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/search/search_filters.py +0 -0
  57. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/search/search_helpers.py +0 -0
  58. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/search/search_utils.py +0 -0
  59. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/utils/__init__.py +0 -0
  60. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/utils/bulk_utils.py +0 -0
  61. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/utils/datetime_utils.py +0 -0
  62. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/utils/maintenance/__init__.py +0 -0
  63. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/utils/maintenance/community_operations.py +0 -0
  64. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/utils/maintenance/graph_data_operations.py +0 -0
  65. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/utils/maintenance/utils.py +0 -0
  66. {graphiti_core-0.11.2 → graphiti_core-0.11.4}/graphiti_core/utils/ontology_utils/entity_types_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: graphiti-core
3
- Version: 0.11.2
3
+ Version: 0.11.4
4
4
  Summary: A temporal graph building library
5
5
  License: Apache-2.0
6
6
  Author: Paul Paliychuk
@@ -396,7 +396,7 @@ class Graphiti:
396
396
  episode.content = ''
397
397
 
398
398
  await add_nodes_and_edges_bulk(
399
- self.driver, [episode], episodic_edges, nodes, entity_edges
399
+ self.driver, [episode], episodic_edges, hydrated_nodes, entity_edges
400
400
  )
401
401
 
402
402
  # Update any communities
@@ -28,7 +28,7 @@ from pydantic import BaseModel, ValidationError
28
28
 
29
29
  from ..prompts.models import Message
30
30
  from .client import LLMClient
31
- from .config import DEFAULT_MAX_TOKENS, LLMConfig
31
+ from .config import DEFAULT_MAX_TOKENS, LLMConfig, ModelSize
32
32
  from .errors import RateLimitError, RefusalError
33
33
 
34
34
  logger = logging.getLogger(__name__)
@@ -173,6 +173,7 @@ class AnthropicClient(LLMClient):
173
173
  messages: list[Message],
174
174
  response_model: type[BaseModel] | None = None,
175
175
  max_tokens: int | None = None,
176
+ model_size: ModelSize = ModelSize.medium,
176
177
  ) -> dict[str, typing.Any]:
177
178
  """
178
179
  Generate a response from the Anthropic LLM using tool-based approach for all requests.
@@ -263,6 +264,7 @@ class AnthropicClient(LLMClient):
263
264
  messages: list[Message],
264
265
  response_model: type[BaseModel] | None = None,
265
266
  max_tokens: int | None = None,
267
+ model_size: ModelSize = ModelSize.medium,
266
268
  ) -> dict[str, typing.Any]:
267
269
  """
268
270
  Generate a response from the LLM.
@@ -289,7 +291,9 @@ class AnthropicClient(LLMClient):
289
291
 
290
292
  while retry_count <= max_retries:
291
293
  try:
292
- response = await self._generate_response(messages, response_model, max_tokens)
294
+ response = await self._generate_response(
295
+ messages, response_model, max_tokens, model_size
296
+ )
293
297
 
294
298
  # If we have a response_model, attempt to validate the response
295
299
  if response_model is not None:
@@ -26,7 +26,7 @@ from pydantic import BaseModel
26
26
  from tenacity import retry, retry_if_exception, stop_after_attempt, wait_random_exponential
27
27
 
28
28
  from ..prompts.models import Message
29
- from .config import DEFAULT_MAX_TOKENS, LLMConfig
29
+ from .config import DEFAULT_MAX_TOKENS, LLMConfig, ModelSize
30
30
  from .errors import RateLimitError
31
31
 
32
32
  DEFAULT_TEMPERATURE = 0
@@ -55,6 +55,7 @@ class LLMClient(ABC):
55
55
 
56
56
  self.config = config
57
57
  self.model = config.model
58
+ self.small_model = config.small_model
58
59
  self.temperature = config.temperature
59
60
  self.max_tokens = config.max_tokens
60
61
  self.cache_enabled = cache
@@ -102,9 +103,10 @@ class LLMClient(ABC):
102
103
  messages: list[Message],
103
104
  response_model: type[BaseModel] | None = None,
104
105
  max_tokens: int = DEFAULT_MAX_TOKENS,
106
+ model_size: ModelSize = ModelSize.medium,
105
107
  ) -> dict[str, typing.Any]:
106
108
  try:
107
- return await self._generate_response(messages, response_model, max_tokens)
109
+ return await self._generate_response(messages, response_model, max_tokens, model_size)
108
110
  except (httpx.HTTPStatusError, RateLimitError) as e:
109
111
  raise e
110
112
 
@@ -114,6 +116,7 @@ class LLMClient(ABC):
114
116
  messages: list[Message],
115
117
  response_model: type[BaseModel] | None = None,
116
118
  max_tokens: int = DEFAULT_MAX_TOKENS,
119
+ model_size: ModelSize = ModelSize.medium,
117
120
  ) -> dict[str, typing.Any]:
118
121
  pass
119
122
 
@@ -128,6 +131,7 @@ class LLMClient(ABC):
128
131
  messages: list[Message],
129
132
  response_model: type[BaseModel] | None = None,
130
133
  max_tokens: int | None = None,
134
+ model_size: ModelSize = ModelSize.medium,
131
135
  ) -> dict[str, typing.Any]:
132
136
  if max_tokens is None:
133
137
  max_tokens = self.max_tokens
@@ -154,7 +158,9 @@ class LLMClient(ABC):
154
158
  for message in messages:
155
159
  message.content = self._clean_input(message.content)
156
160
 
157
- response = await self._generate_response_with_retry(messages, response_model, max_tokens)
161
+ response = await self._generate_response_with_retry(
162
+ messages, response_model, max_tokens, model_size
163
+ )
158
164
 
159
165
  if self.cache_enabled and self.cache_dir is not None:
160
166
  cache_key = self._get_cache_key(messages)
@@ -14,10 +14,17 @@ See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  """
16
16
 
17
+ from enum import Enum
18
+
17
19
  DEFAULT_MAX_TOKENS = 8192
18
20
  DEFAULT_TEMPERATURE = 0
19
21
 
20
22
 
23
+ class ModelSize(Enum):
24
+ small = 'small'
25
+ medium = 'medium'
26
+
27
+
21
28
  class LLMConfig:
22
29
  """
23
30
  Configuration class for the Language Learning Model (LLM).
@@ -34,6 +41,7 @@ class LLMConfig:
34
41
  base_url: str | None = None,
35
42
  temperature: float = DEFAULT_TEMPERATURE,
36
43
  max_tokens: int = DEFAULT_MAX_TOKENS,
44
+ small_model: str | None = None,
37
45
  ):
38
46
  """
39
47
  Initialize the LLMConfig with the provided parameters.
@@ -43,15 +51,18 @@ class LLMConfig:
43
51
  This is required for making authorized requests.
44
52
 
45
53
  model (str, optional): The specific LLM model to use for generating responses.
46
- Defaults to "gpt-4.1-mini", which appears to be a custom model name.
47
- Common values might include "gpt-3.5-turbo" or "gpt-4".
54
+ Defaults to "gpt-4.1-mini".
48
55
 
49
56
  base_url (str, optional): The base URL of the LLM API service.
50
57
  Defaults to "https://api.openai.com", which is OpenAI's standard API endpoint.
51
58
  This can be changed if using a different provider or a custom endpoint.
59
+
60
+ small_model (str, optional): The specific LLM model to use for generating responses of simpler prompts.
61
+ Defaults to "gpt-4.1-nano".
52
62
  """
53
63
  self.base_url = base_url
54
64
  self.api_key = api_key
55
65
  self.model = model
66
+ self.small_model = small_model
56
67
  self.temperature = temperature
57
68
  self.max_tokens = max_tokens
@@ -24,7 +24,7 @@ from pydantic import BaseModel
24
24
 
25
25
  from ..prompts.models import Message
26
26
  from .client import LLMClient
27
- from .config import DEFAULT_MAX_TOKENS, LLMConfig
27
+ from .config import DEFAULT_MAX_TOKENS, LLMConfig, ModelSize
28
28
  from .errors import RateLimitError
29
29
 
30
30
  logger = logging.getLogger(__name__)
@@ -82,6 +82,7 @@ class GeminiClient(LLMClient):
82
82
  messages: list[Message],
83
83
  response_model: type[BaseModel] | None = None,
84
84
  max_tokens: int = DEFAULT_MAX_TOKENS,
85
+ model_size: ModelSize = ModelSize.medium,
85
86
  ) -> dict[str, typing.Any]:
86
87
  """
87
88
  Generate a response from the Gemini language model.
@@ -167,6 +168,7 @@ class GeminiClient(LLMClient):
167
168
  messages: list[Message],
168
169
  response_model: type[BaseModel] | None = None,
169
170
  max_tokens: int | None = None,
171
+ model_size: ModelSize = ModelSize.medium,
170
172
  ) -> dict[str, typing.Any]:
171
173
  """
172
174
  Generate a response from the Gemini language model.
@@ -185,5 +187,8 @@ class GeminiClient(LLMClient):
185
187
 
186
188
  # Call the internal _generate_response method
187
189
  return await self._generate_response(
188
- messages=messages, response_model=response_model, max_tokens=max_tokens
190
+ messages=messages,
191
+ response_model=response_model,
192
+ max_tokens=max_tokens,
193
+ model_size=model_size,
189
194
  )
@@ -25,7 +25,7 @@ from pydantic import BaseModel
25
25
 
26
26
  from ..prompts.models import Message
27
27
  from .client import LLMClient
28
- from .config import LLMConfig
28
+ from .config import LLMConfig, ModelSize
29
29
  from .errors import RateLimitError
30
30
 
31
31
  logger = logging.getLogger(__name__)
@@ -49,6 +49,7 @@ class GroqClient(LLMClient):
49
49
  messages: list[Message],
50
50
  response_model: type[BaseModel] | None = None,
51
51
  max_tokens: int = DEFAULT_MAX_TOKENS,
52
+ model_size: ModelSize = ModelSize.medium,
52
53
  ) -> dict[str, typing.Any]:
53
54
  msgs: list[ChatCompletionMessageParam] = []
54
55
  for m in messages:
@@ -25,12 +25,13 @@ from pydantic import BaseModel
25
25
 
26
26
  from ..prompts.models import Message
27
27
  from .client import MULTILINGUAL_EXTRACTION_RESPONSES, LLMClient
28
- from .config import DEFAULT_MAX_TOKENS, LLMConfig
28
+ from .config import DEFAULT_MAX_TOKENS, LLMConfig, ModelSize
29
29
  from .errors import RateLimitError, RefusalError
30
30
 
31
31
  logger = logging.getLogger(__name__)
32
32
 
33
33
  DEFAULT_MODEL = 'gpt-4.1-mini'
34
+ DEFAULT_SMALL_MODEL = 'gpt-4.1-nano'
34
35
 
35
36
 
36
37
  class OpenAIClient(LLMClient):
@@ -94,6 +95,7 @@ class OpenAIClient(LLMClient):
94
95
  messages: list[Message],
95
96
  response_model: type[BaseModel] | None = None,
96
97
  max_tokens: int = DEFAULT_MAX_TOKENS,
98
+ model_size: ModelSize = ModelSize.medium,
97
99
  ) -> dict[str, typing.Any]:
98
100
  openai_messages: list[ChatCompletionMessageParam] = []
99
101
  for m in messages:
@@ -103,8 +105,13 @@ class OpenAIClient(LLMClient):
103
105
  elif m.role == 'system':
104
106
  openai_messages.append({'role': 'system', 'content': m.content})
105
107
  try:
108
+ if model_size == ModelSize.small:
109
+ model = self.small_model or DEFAULT_SMALL_MODEL
110
+ else:
111
+ model = self.model or DEFAULT_MODEL
112
+
106
113
  response = await self.client.beta.chat.completions.parse(
107
- model=self.model or DEFAULT_MODEL,
114
+ model=model,
108
115
  messages=openai_messages,
109
116
  temperature=self.temperature,
110
117
  max_tokens=max_tokens or self.max_tokens,
@@ -132,6 +139,7 @@ class OpenAIClient(LLMClient):
132
139
  messages: list[Message],
133
140
  response_model: type[BaseModel] | None = None,
134
141
  max_tokens: int | None = None,
142
+ model_size: ModelSize = ModelSize.medium,
135
143
  ) -> dict[str, typing.Any]:
136
144
  if max_tokens is None:
137
145
  max_tokens = self.max_tokens
@@ -144,7 +152,9 @@ class OpenAIClient(LLMClient):
144
152
 
145
153
  while retry_count <= self.MAX_RETRIES:
146
154
  try:
147
- response = await self._generate_response(messages, response_model, max_tokens)
155
+ response = await self._generate_response(
156
+ messages, response_model, max_tokens, model_size
157
+ )
148
158
  return response
149
159
  except (RateLimitError, RefusalError):
150
160
  # These errors should not trigger retries
@@ -26,7 +26,7 @@ from pydantic import BaseModel
26
26
 
27
27
  from ..prompts.models import Message
28
28
  from .client import MULTILINGUAL_EXTRACTION_RESPONSES, LLMClient
29
- from .config import DEFAULT_MAX_TOKENS, LLMConfig
29
+ from .config import DEFAULT_MAX_TOKENS, LLMConfig, ModelSize
30
30
  from .errors import RateLimitError, RefusalError
31
31
 
32
32
  logger = logging.getLogger(__name__)
@@ -89,6 +89,7 @@ class OpenAIGenericClient(LLMClient):
89
89
  messages: list[Message],
90
90
  response_model: type[BaseModel] | None = None,
91
91
  max_tokens: int = DEFAULT_MAX_TOKENS,
92
+ model_size: ModelSize = ModelSize.medium,
92
93
  ) -> dict[str, typing.Any]:
93
94
  openai_messages: list[ChatCompletionMessageParam] = []
94
95
  for m in messages:
@@ -118,6 +119,7 @@ class OpenAIGenericClient(LLMClient):
118
119
  messages: list[Message],
119
120
  response_model: type[BaseModel] | None = None,
120
121
  max_tokens: int | None = None,
122
+ model_size: ModelSize = ModelSize.medium,
121
123
  ) -> dict[str, typing.Any]:
122
124
  if max_tokens is None:
123
125
  max_tokens = self.max_tokens
@@ -139,7 +141,7 @@ class OpenAIGenericClient(LLMClient):
139
141
  while retry_count <= self.MAX_RETRIES:
140
142
  try:
141
143
  response = await self._generate_response(
142
- messages, response_model, max_tokens=max_tokens
144
+ messages, response_model, max_tokens=max_tokens, model_size=model_size
143
145
  )
144
146
  return response
145
147
  except (RateLimitError, RefusalError):
@@ -27,6 +27,7 @@ class NodeDuplicate(BaseModel):
27
27
  ...,
28
28
  description='id of the duplicate node. If no duplicate nodes are found, default to -1.',
29
29
  )
30
+ name: str = Field(..., description='Name of the entity.')
30
31
 
31
32
 
32
33
  class Prompt(Protocol):
@@ -43,7 +44,7 @@ def node(context: dict[str, Any]) -> list[Message]:
43
44
  return [
44
45
  Message(
45
46
  role='system',
46
- content='You are a helpful assistant that de-duplicates nodes from node lists.',
47
+ content='You are a helpful assistant that de-duplicates entities from entity lists.',
47
48
  ),
48
49
  Message(
49
50
  role='user',
@@ -54,25 +55,33 @@ def node(context: dict[str, Any]) -> list[Message]:
54
55
  <CURRENT MESSAGE>
55
56
  {context['episode_content']}
56
57
  </CURRENT MESSAGE>
58
+ <NEW ENTITY>
59
+ {json.dumps(context['extracted_node'], indent=2)}
60
+ </NEW ENTITY>
61
+ <ENTITY TYPE DESCRIPTION>
62
+ {json.dumps(context['entity_type_description'], indent=2)}
63
+ </ENTITY TYPE DESCRIPTION>
57
64
 
58
- <EXISTING NODES>
65
+ <EXISTING ENTITIES>
59
66
  {json.dumps(context['existing_nodes'], indent=2)}
60
- </EXISTING NODES>
67
+ </EXISTING ENTITIES>
68
+
69
+ Given the above EXISTING ENTITIES and their attributes, MESSAGE, and PREVIOUS MESSAGES; Determine if the NEW ENTITY extracted from the conversation
70
+ is a duplicate entity of one of the EXISTING ENTITIES.
61
71
 
62
- Given the above EXISTING NODES and their attributes, MESSAGE, and PREVIOUS MESSAGES; Determine if the NEW NODE extracted from the conversation
63
- is a duplicate entity of one of the EXISTING NODES.
72
+ The ENTITY TYPE DESCRIPTION gives more insight into what the entity type means for the NEW ENTITY.
64
73
 
65
- <NEW NODE>
66
- {json.dumps(context['extracted_node'], indent=2)}
67
- </NEW NODE>
68
74
  Task:
69
- If the NEW NODE is a duplicate of any node in EXISTING NODES, set duplicate_node_id to the
70
- id of the EXISTING NODE that is the duplicate. If the NEW NODE is not a duplicate of any of the EXISTING NODES,
71
- duplicate_node_id should be set to -1.
75
+ If the NEW ENTITY represents a duplicate entity of any entity in EXISTING ENTITIES, set duplicate_entity_id to the
76
+ id of the EXISTING ENTITY that is the duplicate. If the NEW ENTITY is not a duplicate of any of the EXISTING ENTITIES,
77
+ duplicate_entity_id should be set to -1.
78
+
79
+ Also return the most complete name for the entity.
72
80
 
73
81
  Guidelines:
74
- 1. Use the name, summary, and attributes of nodes to determine if the entities are duplicates,
75
- duplicate nodes may have different names
82
+ 1. Entities with the same name should be considered duplicates
83
+ 2. Duplicate entities may refer to the same real-world entity even if names differ. Use context clues from the MESSAGES
84
+ to determine if the NEW ENTITY represents a duplicate entity of one of the EXISTING ENTITIES.
76
85
  """,
77
86
  ),
78
87
  ]
@@ -256,7 +256,7 @@ def extract_attributes(context: dict[str, Any]) -> list[Message]:
256
256
  1. Do not hallucinate entity property values if they cannot be found in the current context.
257
257
  2. Only use the provided MESSAGES and ENTITY to set attribute values.
258
258
  3. The summary attribute represents a summary of the ENTITY, and should be updated with new information about the Entity from the MESSAGES.
259
- Summaries must be no longer than 200 words.
259
+ Summaries must be no longer than 500 words.
260
260
 
261
261
  <ENTITY>
262
262
  {context['node']}
@@ -27,6 +27,7 @@ from graphiti_core.edges import (
27
27
  from graphiti_core.graphiti_types import GraphitiClients
28
28
  from graphiti_core.helpers import MAX_REFLEXION_ITERATIONS, semaphore_gather
29
29
  from graphiti_core.llm_client import LLMClient
30
+ from graphiti_core.llm_client.config import ModelSize
30
31
  from graphiti_core.nodes import CommunityNode, EntityNode, EpisodicNode
31
32
  from graphiti_core.prompts import prompt_library
32
33
  from graphiti_core.prompts.dedupe_edges import EdgeDuplicate, UniqueFacts
@@ -377,7 +378,9 @@ async def dedupe_extracted_edge(
377
378
  }
378
379
 
379
380
  llm_response = await llm_client.generate_response(
380
- prompt_library.dedupe_edges.edge(context), response_model=EdgeDuplicate
381
+ prompt_library.dedupe_edges.edge(context),
382
+ response_model=EdgeDuplicate,
383
+ model_size=ModelSize.small,
381
384
  )
382
385
 
383
386
  duplicate_fact_id: int = llm_response.get('duplicate_fact_id', -1)
@@ -25,6 +25,7 @@ from pydantic import BaseModel, Field
25
25
  from graphiti_core.graphiti_types import GraphitiClients
26
26
  from graphiti_core.helpers import MAX_REFLEXION_ITERATIONS, semaphore_gather
27
27
  from graphiti_core.llm_client import LLMClient
28
+ from graphiti_core.llm_client.config import ModelSize
28
29
  from graphiti_core.nodes import EntityNode, EpisodeType, EpisodicNode, create_entity_node_embeddings
29
30
  from graphiti_core.prompts import prompt_library
30
31
  from graphiti_core.prompts.dedupe_nodes import NodeDuplicate
@@ -140,11 +141,12 @@ async def extract_nodes(
140
141
  for entity in missing_entities:
141
142
  custom_prompt += f'\n{entity},'
142
143
 
144
+ filtered_extracted_entities = [entity for entity in extracted_entities if entity.name.strip()]
143
145
  end = time()
144
- logger.debug(f'Extracted new nodes: {extracted_entities} in {(end - start) * 1000} ms')
146
+ logger.debug(f'Extracted new nodes: {filtered_extracted_entities} in {(end - start) * 1000} ms')
145
147
  # Convert the extracted data into EntityNode objects
146
148
  extracted_nodes = []
147
- for extracted_entity in extracted_entities:
149
+ for extracted_entity in filtered_extracted_entities:
148
150
  entity_type_name = entity_types_context[extracted_entity.entity_type_id].get(
149
151
  'entity_type_name'
150
152
  )
@@ -280,7 +282,6 @@ async def resolve_extracted_node(
280
282
  'id': i,
281
283
  'name': node.name,
282
284
  'entity_types': node.labels,
283
- 'summary': node.summary,
284
285
  },
285
286
  **node.attributes,
286
287
  }
@@ -290,14 +291,14 @@ async def resolve_extracted_node(
290
291
  extracted_node_context = {
291
292
  'name': extracted_node.name,
292
293
  'entity_type': entity_type.__name__ if entity_type is not None else 'Entity', # type: ignore
293
- 'entity_type_description': entity_type.__doc__
294
- if entity_type is not None
295
- else 'Default Entity Type',
296
294
  }
297
295
 
298
296
  context = {
299
297
  'existing_nodes': existing_nodes_context,
300
298
  'extracted_node': extracted_node_context,
299
+ 'entity_type_description': entity_type.__doc__
300
+ if entity_type is not None
301
+ else 'Default Entity Type',
301
302
  'episode_content': episode.content if episode is not None else '',
302
303
  'previous_episodes': [ep.content for ep in previous_episodes]
303
304
  if previous_episodes is not None
@@ -305,7 +306,9 @@ async def resolve_extracted_node(
305
306
  }
306
307
 
307
308
  llm_response = await llm_client.generate_response(
308
- prompt_library.dedupe_nodes.node(context), response_model=NodeDuplicate
309
+ prompt_library.dedupe_nodes.node(context),
310
+ response_model=NodeDuplicate,
311
+ model_size=ModelSize.small,
309
312
  )
310
313
 
311
314
  duplicate_id: int = llm_response.get('duplicate_node_id', -1)
@@ -314,6 +317,8 @@ async def resolve_extracted_node(
314
317
  existing_nodes[duplicate_id] if 0 <= duplicate_id < len(existing_nodes) else extracted_node
315
318
  )
316
319
 
320
+ node.name = llm_response.get('name', '')
321
+
317
322
  end = time()
318
323
  logger.debug(
319
324
  f'Resolved node: {extracted_node.name} is {node.name}, in {(end - start) * 1000} ms'
@@ -370,13 +375,9 @@ async def extract_attributes_from_node(
370
375
  'summary': (
371
376
  str,
372
377
  Field(
373
- description='Summary containing the important information about the entity. Under 200 words',
378
+ description='Summary containing the important information about the entity. Under 500 words',
374
379
  ),
375
- ),
376
- 'name': (
377
- str,
378
- Field(description='Name of the ENTITY'),
379
- ),
380
+ )
380
381
  }
381
382
 
382
383
  if entity_type is not None:
@@ -20,6 +20,7 @@ from time import time
20
20
 
21
21
  from graphiti_core.edges import EntityEdge
22
22
  from graphiti_core.llm_client import LLMClient
23
+ from graphiti_core.llm_client.config import ModelSize
23
24
  from graphiti_core.nodes import EpisodicNode
24
25
  from graphiti_core.prompts import prompt_library
25
26
  from graphiti_core.prompts.extract_edge_dates import EdgeDates
@@ -81,7 +82,9 @@ async def get_edge_contradictions(
81
82
  context = {'new_edge': new_edge_context, 'existing_edges': existing_edge_context}
82
83
 
83
84
  llm_response = await llm_client.generate_response(
84
- prompt_library.invalidate_edges.v2(context), response_model=InvalidatedEdges
85
+ prompt_library.invalidate_edges.v2(context),
86
+ response_model=InvalidatedEdges,
87
+ model_size=ModelSize.small,
85
88
  )
86
89
 
87
90
  contradicted_facts: list[int] = llm_response.get('contradicted_facts', [])
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "graphiti-core"
3
3
  description = "A temporal graph building library"
4
- version = "0.11.2"
4
+ version = "0.11.4"
5
5
  authors = [
6
6
  { "name" = "Paul Paliychuk", "email" = "paul@getzep.com" },
7
7
  { "name" = "Preston Rasmussen", "email" = "preston@getzep.com" },
File without changes
File without changes