cognee 0.3.5__py3-none-any.whl → 0.3.7__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 (161) hide show
  1. cognee/__init__.py +1 -0
  2. cognee/api/health.py +2 -12
  3. cognee/api/v1/add/add.py +46 -6
  4. cognee/api/v1/add/routers/get_add_router.py +5 -1
  5. cognee/api/v1/cognify/cognify.py +29 -9
  6. cognee/api/v1/datasets/datasets.py +11 -0
  7. cognee/api/v1/responses/default_tools.py +0 -1
  8. cognee/api/v1/responses/dispatch_function.py +1 -1
  9. cognee/api/v1/responses/routers/default_tools.py +0 -1
  10. cognee/api/v1/search/search.py +11 -9
  11. cognee/api/v1/settings/routers/get_settings_router.py +7 -1
  12. cognee/api/v1/ui/ui.py +47 -16
  13. cognee/api/v1/update/routers/get_update_router.py +1 -1
  14. cognee/api/v1/update/update.py +3 -3
  15. cognee/cli/_cognee.py +61 -10
  16. cognee/cli/commands/add_command.py +3 -3
  17. cognee/cli/commands/cognify_command.py +3 -3
  18. cognee/cli/commands/config_command.py +9 -7
  19. cognee/cli/commands/delete_command.py +3 -3
  20. cognee/cli/commands/search_command.py +3 -7
  21. cognee/cli/config.py +0 -1
  22. cognee/context_global_variables.py +5 -0
  23. cognee/exceptions/exceptions.py +1 -1
  24. cognee/infrastructure/databases/cache/__init__.py +2 -0
  25. cognee/infrastructure/databases/cache/cache_db_interface.py +79 -0
  26. cognee/infrastructure/databases/cache/config.py +44 -0
  27. cognee/infrastructure/databases/cache/get_cache_engine.py +67 -0
  28. cognee/infrastructure/databases/cache/redis/RedisAdapter.py +243 -0
  29. cognee/infrastructure/databases/exceptions/__init__.py +1 -0
  30. cognee/infrastructure/databases/exceptions/exceptions.py +18 -2
  31. cognee/infrastructure/databases/graph/get_graph_engine.py +1 -1
  32. cognee/infrastructure/databases/graph/graph_db_interface.py +5 -0
  33. cognee/infrastructure/databases/graph/kuzu/adapter.py +67 -44
  34. cognee/infrastructure/databases/graph/neo4j_driver/adapter.py +13 -3
  35. cognee/infrastructure/databases/graph/neo4j_driver/deadlock_retry.py +1 -1
  36. cognee/infrastructure/databases/graph/neptune_driver/neptune_utils.py +1 -1
  37. cognee/infrastructure/databases/relational/sqlalchemy/SqlAlchemyAdapter.py +1 -1
  38. cognee/infrastructure/databases/vector/embeddings/FastembedEmbeddingEngine.py +21 -3
  39. cognee/infrastructure/databases/vector/embeddings/LiteLLMEmbeddingEngine.py +17 -10
  40. cognee/infrastructure/databases/vector/embeddings/OllamaEmbeddingEngine.py +17 -4
  41. cognee/infrastructure/databases/vector/embeddings/config.py +2 -3
  42. cognee/infrastructure/databases/vector/exceptions/exceptions.py +1 -1
  43. cognee/infrastructure/databases/vector/lancedb/LanceDBAdapter.py +0 -1
  44. cognee/infrastructure/files/exceptions.py +1 -1
  45. cognee/infrastructure/files/storage/LocalFileStorage.py +9 -9
  46. cognee/infrastructure/files/storage/S3FileStorage.py +11 -11
  47. cognee/infrastructure/files/utils/guess_file_type.py +6 -0
  48. cognee/infrastructure/llm/prompts/search_type_selector_prompt.txt +0 -5
  49. cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/anthropic/adapter.py +19 -9
  50. cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/gemini/adapter.py +17 -5
  51. cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/generic_llm_api/adapter.py +17 -5
  52. cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/get_llm_client.py +32 -0
  53. cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/mistral/__init__.py +0 -0
  54. cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/mistral/adapter.py +109 -0
  55. cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/ollama/adapter.py +33 -8
  56. cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/openai/adapter.py +40 -18
  57. cognee/infrastructure/loaders/LoaderEngine.py +27 -7
  58. cognee/infrastructure/loaders/external/__init__.py +7 -0
  59. cognee/infrastructure/loaders/external/advanced_pdf_loader.py +2 -8
  60. cognee/infrastructure/loaders/external/beautiful_soup_loader.py +310 -0
  61. cognee/infrastructure/loaders/supported_loaders.py +7 -0
  62. cognee/modules/data/exceptions/exceptions.py +1 -1
  63. cognee/modules/data/methods/__init__.py +3 -0
  64. cognee/modules/data/methods/get_dataset_data.py +4 -1
  65. cognee/modules/data/methods/has_dataset_data.py +21 -0
  66. cognee/modules/engine/models/TableRow.py +0 -1
  67. cognee/modules/ingestion/save_data_to_file.py +9 -2
  68. cognee/modules/pipelines/exceptions/exceptions.py +1 -1
  69. cognee/modules/pipelines/operations/pipeline.py +12 -1
  70. cognee/modules/pipelines/operations/run_tasks.py +25 -197
  71. cognee/modules/pipelines/operations/run_tasks_data_item.py +260 -0
  72. cognee/modules/pipelines/operations/run_tasks_distributed.py +121 -38
  73. cognee/modules/retrieval/EntityCompletionRetriever.py +48 -8
  74. cognee/modules/retrieval/base_graph_retriever.py +3 -1
  75. cognee/modules/retrieval/base_retriever.py +3 -1
  76. cognee/modules/retrieval/chunks_retriever.py +5 -1
  77. cognee/modules/retrieval/code_retriever.py +20 -2
  78. cognee/modules/retrieval/completion_retriever.py +50 -9
  79. cognee/modules/retrieval/cypher_search_retriever.py +11 -1
  80. cognee/modules/retrieval/graph_completion_context_extension_retriever.py +47 -8
  81. cognee/modules/retrieval/graph_completion_cot_retriever.py +32 -1
  82. cognee/modules/retrieval/graph_completion_retriever.py +54 -10
  83. cognee/modules/retrieval/lexical_retriever.py +20 -2
  84. cognee/modules/retrieval/natural_language_retriever.py +10 -1
  85. cognee/modules/retrieval/summaries_retriever.py +5 -1
  86. cognee/modules/retrieval/temporal_retriever.py +62 -10
  87. cognee/modules/retrieval/user_qa_feedback.py +3 -2
  88. cognee/modules/retrieval/utils/completion.py +5 -0
  89. cognee/modules/retrieval/utils/description_to_codepart_search.py +1 -1
  90. cognee/modules/retrieval/utils/session_cache.py +156 -0
  91. cognee/modules/search/methods/get_search_type_tools.py +0 -5
  92. cognee/modules/search/methods/no_access_control_search.py +12 -1
  93. cognee/modules/search/methods/search.py +34 -2
  94. cognee/modules/search/types/SearchType.py +0 -1
  95. cognee/modules/settings/get_settings.py +23 -0
  96. cognee/modules/users/methods/get_authenticated_user.py +3 -1
  97. cognee/modules/users/methods/get_default_user.py +1 -6
  98. cognee/modules/users/roles/methods/create_role.py +2 -2
  99. cognee/modules/users/tenants/methods/create_tenant.py +2 -2
  100. cognee/shared/exceptions/exceptions.py +1 -1
  101. cognee/tasks/codingagents/coding_rule_associations.py +1 -2
  102. cognee/tasks/documents/exceptions/exceptions.py +1 -1
  103. cognee/tasks/graph/extract_graph_from_data.py +2 -0
  104. cognee/tasks/ingestion/data_item_to_text_file.py +3 -3
  105. cognee/tasks/ingestion/ingest_data.py +11 -5
  106. cognee/tasks/ingestion/save_data_item_to_storage.py +12 -1
  107. cognee/tasks/storage/add_data_points.py +3 -10
  108. cognee/tasks/storage/index_data_points.py +19 -14
  109. cognee/tasks/storage/index_graph_edges.py +25 -11
  110. cognee/tasks/web_scraper/__init__.py +34 -0
  111. cognee/tasks/web_scraper/config.py +26 -0
  112. cognee/tasks/web_scraper/default_url_crawler.py +446 -0
  113. cognee/tasks/web_scraper/models.py +46 -0
  114. cognee/tasks/web_scraper/types.py +4 -0
  115. cognee/tasks/web_scraper/utils.py +142 -0
  116. cognee/tasks/web_scraper/web_scraper_task.py +396 -0
  117. cognee/tests/cli_tests/cli_unit_tests/test_cli_utils.py +0 -1
  118. cognee/tests/integration/web_url_crawler/test_default_url_crawler.py +13 -0
  119. cognee/tests/integration/web_url_crawler/test_tavily_crawler.py +19 -0
  120. cognee/tests/integration/web_url_crawler/test_url_adding_e2e.py +344 -0
  121. cognee/tests/subprocesses/reader.py +25 -0
  122. cognee/tests/subprocesses/simple_cognify_1.py +31 -0
  123. cognee/tests/subprocesses/simple_cognify_2.py +31 -0
  124. cognee/tests/subprocesses/writer.py +32 -0
  125. cognee/tests/tasks/descriptive_metrics/metrics_test_utils.py +0 -2
  126. cognee/tests/tasks/descriptive_metrics/neo4j_metrics_test.py +8 -3
  127. cognee/tests/tasks/entity_extraction/entity_extraction_test.py +89 -0
  128. cognee/tests/tasks/web_scraping/web_scraping_test.py +172 -0
  129. cognee/tests/test_add_docling_document.py +56 -0
  130. cognee/tests/test_chromadb.py +7 -11
  131. cognee/tests/test_concurrent_subprocess_access.py +76 -0
  132. cognee/tests/test_conversation_history.py +240 -0
  133. cognee/tests/test_kuzu.py +27 -15
  134. cognee/tests/test_lancedb.py +7 -11
  135. cognee/tests/test_library.py +32 -2
  136. cognee/tests/test_neo4j.py +24 -16
  137. cognee/tests/test_neptune_analytics_vector.py +7 -11
  138. cognee/tests/test_permissions.py +9 -13
  139. cognee/tests/test_pgvector.py +4 -4
  140. cognee/tests/test_remote_kuzu.py +8 -11
  141. cognee/tests/test_s3_file_storage.py +1 -1
  142. cognee/tests/test_search_db.py +6 -8
  143. cognee/tests/unit/infrastructure/databases/cache/test_cache_config.py +89 -0
  144. cognee/tests/unit/modules/retrieval/conversation_history_test.py +154 -0
  145. {cognee-0.3.5.dist-info → cognee-0.3.7.dist-info}/METADATA +22 -7
  146. {cognee-0.3.5.dist-info → cognee-0.3.7.dist-info}/RECORD +155 -128
  147. {cognee-0.3.5.dist-info → cognee-0.3.7.dist-info}/entry_points.txt +1 -0
  148. distributed/Dockerfile +0 -3
  149. distributed/entrypoint.py +21 -9
  150. distributed/signal.py +5 -0
  151. distributed/workers/data_point_saving_worker.py +64 -34
  152. distributed/workers/graph_saving_worker.py +71 -47
  153. cognee/infrastructure/databases/graph/memgraph/memgraph_adapter.py +0 -1116
  154. cognee/modules/retrieval/insights_retriever.py +0 -133
  155. cognee/tests/test_memgraph.py +0 -109
  156. cognee/tests/unit/modules/retrieval/insights_retriever_test.py +0 -251
  157. distributed/poetry.lock +0 -12238
  158. distributed/pyproject.toml +0 -185
  159. {cognee-0.3.5.dist-info → cognee-0.3.7.dist-info}/WHEEL +0 -0
  160. {cognee-0.3.5.dist-info → cognee-0.3.7.dist-info}/licenses/LICENSE +0 -0
  161. {cognee-0.3.5.dist-info → cognee-0.3.7.dist-info}/licenses/NOTICE.md +0 -0
@@ -70,11 +70,11 @@ After adding data, use `cognee cognify` to process it into knowledge graphs.
70
70
  await cognee.add(data=data_to_add, dataset_name=args.dataset_name)
71
71
  fmt.success(f"Successfully added data to dataset '{args.dataset_name}'")
72
72
  except Exception as e:
73
- raise CliCommandInnerException(f"Failed to add data: {str(e)}")
73
+ raise CliCommandInnerException(f"Failed to add data: {str(e)}") from e
74
74
 
75
75
  asyncio.run(run_add())
76
76
 
77
77
  except Exception as e:
78
78
  if isinstance(e, CliCommandInnerException):
79
- raise CliCommandException(str(e), error_code=1)
80
- raise CliCommandException(f"Error adding data: {str(e)}", error_code=1)
79
+ raise CliCommandException(str(e), error_code=1) from e
80
+ raise CliCommandException(f"Failed to add data: {str(e)}", error_code=1) from e
@@ -107,7 +107,7 @@ After successful cognify processing, use `cognee search` to query the knowledge
107
107
  )
108
108
  return result
109
109
  except Exception as e:
110
- raise CliCommandInnerException(f"Failed to cognify: {str(e)}")
110
+ raise CliCommandInnerException(f"Failed to cognify: {str(e)}") from e
111
111
 
112
112
  result = asyncio.run(run_cognify())
113
113
 
@@ -124,5 +124,5 @@ After successful cognify processing, use `cognee search` to query the knowledge
124
124
 
125
125
  except Exception as e:
126
126
  if isinstance(e, CliCommandInnerException):
127
- raise CliCommandException(str(e), error_code=1)
128
- raise CliCommandException(f"Error during cognification: {str(e)}", error_code=1)
127
+ raise CliCommandException(str(e), error_code=1) from e
128
+ raise CliCommandException(f"Error during cognification: {str(e)}", error_code=1) from e
@@ -79,8 +79,10 @@ Configuration changes will affect how cognee processes and stores data.
79
79
 
80
80
  except Exception as e:
81
81
  if isinstance(e, CliCommandInnerException):
82
- raise CliCommandException(str(e), error_code=1)
83
- raise CliCommandException(f"Error managing configuration: {str(e)}", error_code=1)
82
+ raise CliCommandException(str(e), error_code=1) from e
83
+ raise CliCommandException(
84
+ f"Error managing configuration: {str(e)}", error_code=1
85
+ ) from e
84
86
 
85
87
  def _handle_get(self, args: argparse.Namespace) -> None:
86
88
  try:
@@ -122,7 +124,7 @@ Configuration changes will affect how cognee processes and stores data.
122
124
  fmt.note("Configuration viewing not fully implemented yet")
123
125
 
124
126
  except Exception as e:
125
- raise CliCommandInnerException(f"Failed to get configuration: {str(e)}")
127
+ raise CliCommandInnerException(f"Failed to get configuration: {str(e)}") from e
126
128
 
127
129
  def _handle_set(self, args: argparse.Namespace) -> None:
128
130
  try:
@@ -141,7 +143,7 @@ Configuration changes will affect how cognee processes and stores data.
141
143
  fmt.error(f"Failed to set configuration key '{args.key}'")
142
144
 
143
145
  except Exception as e:
144
- raise CliCommandInnerException(f"Failed to set configuration: {str(e)}")
146
+ raise CliCommandInnerException(f"Failed to set configuration: {str(e)}") from e
145
147
 
146
148
  def _handle_unset(self, args: argparse.Namespace) -> None:
147
149
  try:
@@ -189,7 +191,7 @@ Configuration changes will affect how cognee processes and stores data.
189
191
  fmt.note("Use 'cognee config list' to see all available configuration options")
190
192
 
191
193
  except Exception as e:
192
- raise CliCommandInnerException(f"Failed to unset configuration: {str(e)}")
194
+ raise CliCommandInnerException(f"Failed to unset configuration: {str(e)}") from e
193
195
 
194
196
  def _handle_list(self, args: argparse.Namespace) -> None:
195
197
  try:
@@ -209,7 +211,7 @@ Configuration changes will affect how cognee processes and stores data.
209
211
  fmt.echo(" cognee config reset - Reset all to defaults")
210
212
 
211
213
  except Exception as e:
212
- raise CliCommandInnerException(f"Failed to list configuration: {str(e)}")
214
+ raise CliCommandInnerException(f"Failed to list configuration: {str(e)}") from e
213
215
 
214
216
  def _handle_reset(self, args: argparse.Namespace) -> None:
215
217
  try:
@@ -222,4 +224,4 @@ Configuration changes will affect how cognee processes and stores data.
222
224
  fmt.echo("This would reset all settings to their default values")
223
225
 
224
226
  except Exception as e:
225
- raise CliCommandInnerException(f"Failed to reset configuration: {str(e)}")
227
+ raise CliCommandInnerException(f"Failed to reset configuration: {str(e)}") from e
@@ -100,7 +100,7 @@ Be careful with deletion operations as they are irreversible.
100
100
  else:
101
101
  await cognee.delete(dataset_name=args.dataset_name, user_id=args.user_id)
102
102
  except Exception as e:
103
- raise CliCommandInnerException(f"Failed to delete: {str(e)}")
103
+ raise CliCommandInnerException(f"Failed to delete: {str(e)}") from e
104
104
 
105
105
  asyncio.run(run_delete())
106
106
  # This success message may be inaccurate due to the underlying bug, but we leave it for now.
@@ -108,5 +108,5 @@ Be careful with deletion operations as they are irreversible.
108
108
 
109
109
  except Exception as e:
110
110
  if isinstance(e, CliCommandInnerException):
111
- raise CliCommandException(str(e), error_code=1)
112
- raise CliCommandException(f"Error deleting data: {str(e)}", error_code=1)
111
+ raise CliCommandException(str(e), error_code=1) from e
112
+ raise CliCommandException(f"Error deleting data: {str(e)}", error_code=1) from e
@@ -31,10 +31,6 @@ Search Types & Use Cases:
31
31
  Traditional RAG using document chunks without graph structure.
32
32
  Best for: Direct document retrieval, specific fact-finding.
33
33
 
34
- **INSIGHTS**:
35
- Structured entity relationships and semantic connections.
36
- Best for: Understanding concept relationships, knowledge mapping.
37
-
38
34
  **CHUNKS**:
39
35
  Raw text segments that match the query semantically.
40
36
  Best for: Finding specific passages, citations, exact content.
@@ -108,7 +104,7 @@ Search Types & Use Cases:
108
104
  )
109
105
  return results
110
106
  except Exception as e:
111
- raise CliCommandInnerException(f"Failed to search: {str(e)}")
107
+ raise CliCommandInnerException(f"Failed to search: {str(e)}") from e
112
108
 
113
109
  results = asyncio.run(run_search())
114
110
 
@@ -145,5 +141,5 @@ Search Types & Use Cases:
145
141
 
146
142
  except Exception as e:
147
143
  if isinstance(e, CliCommandInnerException):
148
- raise CliCommandException(str(e), error_code=1)
149
- raise CliCommandException(f"Error searching: {str(e)}", error_code=1)
144
+ raise CliCommandException(str(e), error_code=1) from e
145
+ raise CliCommandException(f"Error searching: {str(e)}", error_code=1) from e
cognee/cli/config.py CHANGED
@@ -19,7 +19,6 @@ COMMAND_DESCRIPTIONS = {
19
19
  SEARCH_TYPE_CHOICES = [
20
20
  "GRAPH_COMPLETION",
21
21
  "RAG_COMPLETION",
22
- "INSIGHTS",
23
22
  "CHUNKS",
24
23
  "SUMMARIES",
25
24
  "CODE",
@@ -12,6 +12,11 @@ from cognee.modules.users.methods import get_user
12
12
  # for different async tasks, threads and processes
13
13
  vector_db_config = ContextVar("vector_db_config", default=None)
14
14
  graph_db_config = ContextVar("graph_db_config", default=None)
15
+ session_user = ContextVar("session_user", default=None)
16
+
17
+
18
+ async def set_session_user_context_variable(user):
19
+ session_user.set(user)
15
20
 
16
21
 
17
22
  async def set_database_global_context_variables(dataset: Union[str, UUID], user_id: UUID):
@@ -56,7 +56,7 @@ class CogneeValidationError(CogneeApiError):
56
56
  self,
57
57
  message: str = "A validation error occurred.",
58
58
  name: str = "CogneeValidationError",
59
- status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
59
+ status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
60
60
  log=True,
61
61
  log_level="ERROR",
62
62
  ):
@@ -0,0 +1,2 @@
1
+ from .get_cache_engine import get_cache_engine
2
+ from .config import get_cache_config
@@ -0,0 +1,79 @@
1
+ from abc import ABC, abstractmethod
2
+ from contextlib import contextmanager
3
+
4
+
5
+ class CacheDBInterface(ABC):
6
+ """
7
+ Abstract base class for distributed cache coordination systems (e.g., Redis, Memcached).
8
+ Provides a common interface for lock acquisition, release, and context-managed locking.
9
+ """
10
+
11
+ def __init__(self, host: str, port: int, lock_key: str):
12
+ self.host = host
13
+ self.port = port
14
+ self.lock_key = lock_key
15
+ self.lock = None
16
+
17
+ @abstractmethod
18
+ def acquire_lock(self):
19
+ """
20
+ Acquire a lock on the given key.
21
+ Must be implemented by subclasses.
22
+ """
23
+ pass
24
+
25
+ @abstractmethod
26
+ def release_lock(self):
27
+ """
28
+ Release the lock if it is held.
29
+ Must be implemented by subclasses.
30
+ """
31
+ pass
32
+
33
+ @contextmanager
34
+ def hold_lock(self):
35
+ """
36
+ Context manager for safely acquiring and releasing the lock.
37
+ """
38
+ self.acquire()
39
+ try:
40
+ yield
41
+ finally:
42
+ self.release()
43
+
44
+ @abstractmethod
45
+ async def add_qa(
46
+ self,
47
+ user_id: str,
48
+ session_id: str,
49
+ question: str,
50
+ context: str,
51
+ answer: str,
52
+ ttl: int | None = 86400,
53
+ ):
54
+ """
55
+ Add a Q/A/context triplet to a cache session.
56
+ """
57
+
58
+ pass
59
+
60
+ @abstractmethod
61
+ async def get_latest_qa(self, user_id: str, session_id: str, last_n: int = 5):
62
+ """
63
+ Retrieve the most recent Q/A/context triplets for a session.
64
+ """
65
+ pass
66
+
67
+ @abstractmethod
68
+ async def get_all_qas(self, user_id: str, session_id: str):
69
+ """
70
+ Retrieve all Q/A/context triplets for the given session.
71
+ """
72
+ pass
73
+
74
+ @abstractmethod
75
+ async def close(self):
76
+ """
77
+ Gracefully close any async connections.
78
+ """
79
+ pass
@@ -0,0 +1,44 @@
1
+ from pydantic_settings import BaseSettings, SettingsConfigDict
2
+ from functools import lru_cache
3
+ from typing import Optional
4
+
5
+
6
+ class CacheConfig(BaseSettings):
7
+ """
8
+ Configuration for distributed cache systems (e.g., Redis), used for locking or coordination.
9
+
10
+ Attributes:
11
+ - shared_kuzu_lock: Shared kuzu lock logic on/off.
12
+ - cache_host: Hostname of the cache service.
13
+ - cache_port: Port number for the cache service.
14
+ - agentic_lock_expire: Automatic lock expiration time (in seconds).
15
+ - agentic_lock_timeout: Maximum time (in seconds) to wait for the lock release.
16
+ """
17
+
18
+ caching: bool = False
19
+ shared_kuzu_lock: bool = False
20
+ cache_host: str = "localhost"
21
+ cache_port: int = 6379
22
+ cache_username: Optional[str] = None
23
+ cache_password: Optional[str] = None
24
+ agentic_lock_expire: int = 240
25
+ agentic_lock_timeout: int = 300
26
+
27
+ model_config = SettingsConfigDict(env_file=".env", extra="allow")
28
+
29
+ def to_dict(self) -> dict:
30
+ return {
31
+ "caching": self.caching,
32
+ "shared_kuzu_lock": self.shared_kuzu_lock,
33
+ "cache_host": self.cache_host,
34
+ "cache_port": self.cache_port,
35
+ "cache_username": self.cache_username,
36
+ "cache_password": self.cache_password,
37
+ "agentic_lock_expire": self.agentic_lock_expire,
38
+ "agentic_lock_timeout": self.agentic_lock_timeout,
39
+ }
40
+
41
+
42
+ @lru_cache
43
+ def get_cache_config():
44
+ return CacheConfig()
@@ -0,0 +1,67 @@
1
+ """Factory to get the appropriate cache coordination engine (e.g., Redis)."""
2
+
3
+ from functools import lru_cache
4
+ from typing import Optional
5
+ from cognee.infrastructure.databases.cache.config import get_cache_config
6
+ from cognee.infrastructure.databases.cache.cache_db_interface import CacheDBInterface
7
+
8
+ config = get_cache_config()
9
+
10
+
11
+ @lru_cache
12
+ def create_cache_engine(
13
+ cache_host: str,
14
+ cache_port: int,
15
+ cache_username: str,
16
+ cache_password: str,
17
+ lock_key: str,
18
+ agentic_lock_expire: int = 240,
19
+ agentic_lock_timeout: int = 300,
20
+ ):
21
+ """
22
+ Factory function to instantiate a cache coordination backend (currently Redis).
23
+
24
+ Parameters:
25
+ -----------
26
+ - cache_host: Hostname or IP of the cache server.
27
+ - cache_port: Port number to connect to.
28
+ - cache_username: Username to authenticate with.
29
+ - cache_password: Password to authenticate with.
30
+ - lock_key: Identifier used for the locking resource.
31
+ - agentic_lock_expire: Duration to hold the lock after acquisition.
32
+ - agentic_lock_timeout: Max time to wait for the lock before failing.
33
+
34
+ Returns:
35
+ --------
36
+ - CacheDBInterface: An instance of the appropriate cache adapter. :TODO: Now we support only Redis. later if we add more here we can split the logic
37
+ """
38
+ if config.caching:
39
+ from cognee.infrastructure.databases.cache.redis.RedisAdapter import RedisAdapter
40
+
41
+ return RedisAdapter(
42
+ host=cache_host,
43
+ port=cache_port,
44
+ username=cache_username,
45
+ password=cache_password,
46
+ lock_name=lock_key,
47
+ timeout=agentic_lock_expire,
48
+ blocking_timeout=agentic_lock_timeout,
49
+ )
50
+ else:
51
+ return None
52
+
53
+
54
+ def get_cache_engine(lock_key: Optional[str] = None) -> CacheDBInterface:
55
+ """
56
+ Returns a cache adapter instance using current context configuration.
57
+ """
58
+
59
+ return create_cache_engine(
60
+ cache_host=config.cache_host,
61
+ cache_port=config.cache_port,
62
+ cache_username=config.cache_username,
63
+ cache_password=config.cache_password,
64
+ lock_key=lock_key,
65
+ agentic_lock_expire=config.agentic_lock_expire,
66
+ agentic_lock_timeout=config.agentic_lock_timeout,
67
+ )
@@ -0,0 +1,243 @@
1
+ import asyncio
2
+ import redis
3
+ import redis.asyncio as aioredis
4
+ from contextlib import contextmanager
5
+ from cognee.infrastructure.databases.cache.cache_db_interface import CacheDBInterface
6
+ from cognee.infrastructure.databases.exceptions import CacheConnectionError
7
+ from cognee.shared.logging_utils import get_logger
8
+ from datetime import datetime
9
+ import json
10
+
11
+ logger = get_logger("RedisAdapter")
12
+
13
+
14
+ class RedisAdapter(CacheDBInterface):
15
+ def __init__(
16
+ self,
17
+ host,
18
+ port,
19
+ lock_name="default_lock",
20
+ username=None,
21
+ password=None,
22
+ timeout=240,
23
+ blocking_timeout=300,
24
+ connection_timeout=30,
25
+ ):
26
+ super().__init__(host, port, lock_name)
27
+
28
+ self.host = host
29
+ self.port = port
30
+ self.connection_timeout = connection_timeout
31
+
32
+ try:
33
+ self.sync_redis = redis.Redis(
34
+ host=host,
35
+ port=port,
36
+ username=username,
37
+ password=password,
38
+ socket_connect_timeout=connection_timeout,
39
+ socket_timeout=connection_timeout,
40
+ )
41
+ self.async_redis = aioredis.Redis(
42
+ host=host,
43
+ port=port,
44
+ username=username,
45
+ password=password,
46
+ decode_responses=True,
47
+ socket_connect_timeout=connection_timeout,
48
+ )
49
+ self.timeout = timeout
50
+ self.blocking_timeout = blocking_timeout
51
+
52
+ # Validate connection on initialization
53
+ self._validate_connection()
54
+ logger.info(f"Successfully connected to Redis at {host}:{port}")
55
+
56
+ except (redis.ConnectionError, redis.TimeoutError) as e:
57
+ error_msg = f"Failed to connect to Redis at {host}:{port}: {str(e)}"
58
+ logger.error(error_msg)
59
+ raise CacheConnectionError(error_msg) from e
60
+ except Exception as e:
61
+ error_msg = f"Unexpected error initializing Redis adapter: {str(e)}"
62
+ logger.error(error_msg)
63
+ raise CacheConnectionError(error_msg) from e
64
+
65
+ def _validate_connection(self):
66
+ """Validate Redis connection is available."""
67
+ try:
68
+ self.sync_redis.ping()
69
+ except (redis.ConnectionError, redis.TimeoutError) as e:
70
+ raise CacheConnectionError(
71
+ f"Cannot connect to Redis at {self.host}:{self.port}: {str(e)}"
72
+ ) from e
73
+
74
+ def acquire_lock(self):
75
+ """
76
+ Acquire the Redis lock manually. Raises if acquisition fails. (Sync because of Kuzu)
77
+ """
78
+ self.lock = self.sync_redis.lock(
79
+ name=self.lock_key,
80
+ timeout=self.timeout,
81
+ blocking_timeout=self.blocking_timeout,
82
+ )
83
+
84
+ acquired = self.lock.acquire()
85
+ if not acquired:
86
+ raise RuntimeError(f"Could not acquire Redis lock: {self.lock_key}")
87
+
88
+ return self.lock
89
+
90
+ def release_lock(self):
91
+ """
92
+ Release the Redis lock manually, if held. (Sync because of Kuzu)
93
+ """
94
+ if self.lock:
95
+ try:
96
+ self.lock.release()
97
+ self.lock = None
98
+ except redis.exceptions.LockError:
99
+ pass
100
+
101
+ @contextmanager
102
+ def hold_lock(self):
103
+ """
104
+ Context manager for acquiring and releasing the Redis lock automatically. (Sync because of Kuzu)
105
+ """
106
+ self.acquire()
107
+ try:
108
+ yield
109
+ finally:
110
+ self.release()
111
+
112
+ async def add_qa(
113
+ self,
114
+ user_id: str,
115
+ session_id: str,
116
+ question: str,
117
+ context: str,
118
+ answer: str,
119
+ ttl: int | None = 86400,
120
+ ):
121
+ """
122
+ Add a Q/A/context triplet to a Redis list for this session.
123
+ Creates the session if it doesn't exist.
124
+
125
+ Args:
126
+ user_id (str): The user ID.
127
+ session_id: Unique identifier for the session.
128
+ question: User question text.
129
+ context: Context used to answer.
130
+ answer: Assistant answer text.
131
+ ttl: Optional time-to-live (seconds). If provided, the session expires after this time.
132
+
133
+ Raises:
134
+ CacheConnectionError: If Redis connection fails or times out.
135
+ """
136
+ try:
137
+ session_key = f"agent_sessions:{user_id}:{session_id}"
138
+
139
+ qa_entry = {
140
+ "time": datetime.utcnow().isoformat(),
141
+ "question": question,
142
+ "context": context,
143
+ "answer": answer,
144
+ }
145
+
146
+ await self.async_redis.rpush(session_key, json.dumps(qa_entry))
147
+
148
+ if ttl is not None:
149
+ await self.async_redis.expire(session_key, ttl)
150
+
151
+ except (redis.ConnectionError, redis.TimeoutError) as e:
152
+ error_msg = f"Redis connection error while adding Q&A: {str(e)}"
153
+ logger.error(error_msg)
154
+ raise CacheConnectionError(error_msg) from e
155
+ except Exception as e:
156
+ error_msg = f"Unexpected error while adding Q&A to Redis: {str(e)}"
157
+ logger.error(error_msg)
158
+ raise CacheConnectionError(error_msg) from e
159
+
160
+ async def get_latest_qa(self, user_id: str, session_id: str, last_n: int = 5):
161
+ """
162
+ Retrieve the most recent Q/A/context triplet(s) for the given session.
163
+ """
164
+ session_key = f"agent_sessions:{user_id}:{session_id}"
165
+ if last_n == 1:
166
+ data = await self.async_redis.lindex(session_key, -1)
167
+ return [json.loads(data)] if data else None
168
+ else:
169
+ data = await self.async_redis.lrange(session_key, -last_n, -1)
170
+ return [json.loads(d) for d in data] if data else []
171
+
172
+ async def get_all_qas(self, user_id: str, session_id: str):
173
+ """
174
+ Retrieve all Q/A/context triplets for the given session.
175
+ """
176
+ session_key = f"agent_sessions:{user_id}:{session_id}"
177
+ entries = await self.async_redis.lrange(session_key, 0, -1)
178
+ return [json.loads(e) for e in entries]
179
+
180
+ async def close(self):
181
+ """
182
+ Gracefully close the async Redis connection.
183
+ """
184
+ await self.async_redis.aclose()
185
+
186
+
187
+ async def main():
188
+ HOST = "localhost"
189
+ PORT = 6379
190
+
191
+ adapter = RedisAdapter(host=HOST, port=PORT)
192
+ session_id = "demo_session"
193
+ user_id = "demo_user_id"
194
+
195
+ print("\nAdding sample Q/A pairs...")
196
+ await adapter.add_qa(
197
+ user_id,
198
+ session_id,
199
+ "What is Redis?",
200
+ "Basic DB context",
201
+ "Redis is an in-memory data store.",
202
+ )
203
+ await adapter.add_qa(
204
+ user_id,
205
+ session_id,
206
+ "Who created Redis?",
207
+ "Historical context",
208
+ "Salvatore Sanfilippo (antirez).",
209
+ )
210
+
211
+ print("\nLatest QA:")
212
+ latest = await adapter.get_latest_qa(user_id, session_id)
213
+ print(json.dumps(latest, indent=2))
214
+
215
+ print("\nLast 2 QAs:")
216
+ last_two = await adapter.get_latest_qa(user_id, session_id, last_n=2)
217
+ print(json.dumps(last_two, indent=2))
218
+
219
+ session_id = "session_expire_demo"
220
+
221
+ await adapter.add_qa(
222
+ user_id,
223
+ session_id,
224
+ "What is Redis?",
225
+ "Database context",
226
+ "Redis is an in-memory data store.",
227
+ )
228
+
229
+ await adapter.add_qa(
230
+ user_id,
231
+ session_id,
232
+ "Who created Redis?",
233
+ "History context",
234
+ "Salvatore Sanfilippo (antirez).",
235
+ )
236
+
237
+ print(await adapter.get_all_qas(user_id, session_id))
238
+
239
+ await adapter.close()
240
+
241
+
242
+ if __name__ == "__main__":
243
+ asyncio.run(main())
@@ -11,4 +11,5 @@ from .exceptions import (
11
11
  EmbeddingException,
12
12
  MissingQueryParameterError,
13
13
  MutuallyExclusiveQueryParametersError,
14
+ CacheConnectionError,
14
15
  )
@@ -15,7 +15,7 @@ class DatabaseNotCreatedError(CogneeSystemError):
15
15
  self,
16
16
  message: str = "The database has not been created yet. Please call `await setup()` first.",
17
17
  name: str = "DatabaseNotCreatedError",
18
- status_code: int = status.HTTP_422_UNPROCESSABLE_ENTITY,
18
+ status_code: int = status.HTTP_422_UNPROCESSABLE_CONTENT,
19
19
  ):
20
20
  super().__init__(message, name, status_code)
21
21
 
@@ -99,7 +99,7 @@ class EmbeddingException(CogneeConfigurationError):
99
99
  self,
100
100
  message: str = "Embedding Exception.",
101
101
  name: str = "EmbeddingException",
102
- status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
102
+ status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
103
103
  ):
104
104
  super().__init__(message, name, status_code)
105
105
 
@@ -132,3 +132,19 @@ class MutuallyExclusiveQueryParametersError(CogneeValidationError):
132
132
  ):
133
133
  message = "The search function accepts either text or embedding as input, but not both."
134
134
  super().__init__(message, name, status_code)
135
+
136
+
137
+ class CacheConnectionError(CogneeConfigurationError):
138
+ """
139
+ Raised when connection to the cache database (e.g., Redis) fails.
140
+
141
+ This error indicates that the cache service is unavailable or misconfigured.
142
+ """
143
+
144
+ def __init__(
145
+ self,
146
+ message: str = "Failed to connect to cache database. Please check your cache configuration.",
147
+ name: str = "CacheConnectionError",
148
+ status_code: int = status.HTTP_503_SERVICE_UNAVAILABLE,
149
+ ):
150
+ super().__init__(message, name, status_code)
@@ -162,5 +162,5 @@ def create_graph_engine(
162
162
 
163
163
  raise EnvironmentError(
164
164
  f"Unsupported graph database provider: {graph_database_provider}. "
165
- f"Supported providers are: {', '.join(list(supported_databases.keys()) + ['neo4j', 'kuzu', 'kuzu-remote', 'memgraph', 'neptune', 'neptune_analytics'])}"
165
+ f"Supported providers are: {', '.join(list(supported_databases.keys()) + ['neo4j', 'kuzu', 'kuzu-remote', 'neptune', 'neptune_analytics'])}"
166
166
  )
@@ -159,6 +159,11 @@ class GraphDBInterface(ABC):
159
159
  - get_connections
160
160
  """
161
161
 
162
+ @abstractmethod
163
+ async def is_empty(self) -> bool:
164
+ logger.warning("is_empty() is not implemented")
165
+ return True
166
+
162
167
  @abstractmethod
163
168
  async def query(self, query: str, params: dict) -> List[Any]:
164
169
  """