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.
- cognee/__init__.py +1 -0
- cognee/api/health.py +2 -12
- cognee/api/v1/add/add.py +46 -6
- cognee/api/v1/add/routers/get_add_router.py +5 -1
- cognee/api/v1/cognify/cognify.py +29 -9
- cognee/api/v1/datasets/datasets.py +11 -0
- cognee/api/v1/responses/default_tools.py +0 -1
- cognee/api/v1/responses/dispatch_function.py +1 -1
- cognee/api/v1/responses/routers/default_tools.py +0 -1
- cognee/api/v1/search/search.py +11 -9
- cognee/api/v1/settings/routers/get_settings_router.py +7 -1
- cognee/api/v1/ui/ui.py +47 -16
- cognee/api/v1/update/routers/get_update_router.py +1 -1
- cognee/api/v1/update/update.py +3 -3
- cognee/cli/_cognee.py +61 -10
- cognee/cli/commands/add_command.py +3 -3
- cognee/cli/commands/cognify_command.py +3 -3
- cognee/cli/commands/config_command.py +9 -7
- cognee/cli/commands/delete_command.py +3 -3
- cognee/cli/commands/search_command.py +3 -7
- cognee/cli/config.py +0 -1
- cognee/context_global_variables.py +5 -0
- cognee/exceptions/exceptions.py +1 -1
- cognee/infrastructure/databases/cache/__init__.py +2 -0
- cognee/infrastructure/databases/cache/cache_db_interface.py +79 -0
- cognee/infrastructure/databases/cache/config.py +44 -0
- cognee/infrastructure/databases/cache/get_cache_engine.py +67 -0
- cognee/infrastructure/databases/cache/redis/RedisAdapter.py +243 -0
- cognee/infrastructure/databases/exceptions/__init__.py +1 -0
- cognee/infrastructure/databases/exceptions/exceptions.py +18 -2
- cognee/infrastructure/databases/graph/get_graph_engine.py +1 -1
- cognee/infrastructure/databases/graph/graph_db_interface.py +5 -0
- cognee/infrastructure/databases/graph/kuzu/adapter.py +67 -44
- cognee/infrastructure/databases/graph/neo4j_driver/adapter.py +13 -3
- cognee/infrastructure/databases/graph/neo4j_driver/deadlock_retry.py +1 -1
- cognee/infrastructure/databases/graph/neptune_driver/neptune_utils.py +1 -1
- cognee/infrastructure/databases/relational/sqlalchemy/SqlAlchemyAdapter.py +1 -1
- cognee/infrastructure/databases/vector/embeddings/FastembedEmbeddingEngine.py +21 -3
- cognee/infrastructure/databases/vector/embeddings/LiteLLMEmbeddingEngine.py +17 -10
- cognee/infrastructure/databases/vector/embeddings/OllamaEmbeddingEngine.py +17 -4
- cognee/infrastructure/databases/vector/embeddings/config.py +2 -3
- cognee/infrastructure/databases/vector/exceptions/exceptions.py +1 -1
- cognee/infrastructure/databases/vector/lancedb/LanceDBAdapter.py +0 -1
- cognee/infrastructure/files/exceptions.py +1 -1
- cognee/infrastructure/files/storage/LocalFileStorage.py +9 -9
- cognee/infrastructure/files/storage/S3FileStorage.py +11 -11
- cognee/infrastructure/files/utils/guess_file_type.py +6 -0
- cognee/infrastructure/llm/prompts/search_type_selector_prompt.txt +0 -5
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/anthropic/adapter.py +19 -9
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/gemini/adapter.py +17 -5
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/generic_llm_api/adapter.py +17 -5
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/get_llm_client.py +32 -0
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/mistral/__init__.py +0 -0
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/mistral/adapter.py +109 -0
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/ollama/adapter.py +33 -8
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/openai/adapter.py +40 -18
- cognee/infrastructure/loaders/LoaderEngine.py +27 -7
- cognee/infrastructure/loaders/external/__init__.py +7 -0
- cognee/infrastructure/loaders/external/advanced_pdf_loader.py +2 -8
- cognee/infrastructure/loaders/external/beautiful_soup_loader.py +310 -0
- cognee/infrastructure/loaders/supported_loaders.py +7 -0
- cognee/modules/data/exceptions/exceptions.py +1 -1
- cognee/modules/data/methods/__init__.py +3 -0
- cognee/modules/data/methods/get_dataset_data.py +4 -1
- cognee/modules/data/methods/has_dataset_data.py +21 -0
- cognee/modules/engine/models/TableRow.py +0 -1
- cognee/modules/ingestion/save_data_to_file.py +9 -2
- cognee/modules/pipelines/exceptions/exceptions.py +1 -1
- cognee/modules/pipelines/operations/pipeline.py +12 -1
- cognee/modules/pipelines/operations/run_tasks.py +25 -197
- cognee/modules/pipelines/operations/run_tasks_data_item.py +260 -0
- cognee/modules/pipelines/operations/run_tasks_distributed.py +121 -38
- cognee/modules/retrieval/EntityCompletionRetriever.py +48 -8
- cognee/modules/retrieval/base_graph_retriever.py +3 -1
- cognee/modules/retrieval/base_retriever.py +3 -1
- cognee/modules/retrieval/chunks_retriever.py +5 -1
- cognee/modules/retrieval/code_retriever.py +20 -2
- cognee/modules/retrieval/completion_retriever.py +50 -9
- cognee/modules/retrieval/cypher_search_retriever.py +11 -1
- cognee/modules/retrieval/graph_completion_context_extension_retriever.py +47 -8
- cognee/modules/retrieval/graph_completion_cot_retriever.py +32 -1
- cognee/modules/retrieval/graph_completion_retriever.py +54 -10
- cognee/modules/retrieval/lexical_retriever.py +20 -2
- cognee/modules/retrieval/natural_language_retriever.py +10 -1
- cognee/modules/retrieval/summaries_retriever.py +5 -1
- cognee/modules/retrieval/temporal_retriever.py +62 -10
- cognee/modules/retrieval/user_qa_feedback.py +3 -2
- cognee/modules/retrieval/utils/completion.py +5 -0
- cognee/modules/retrieval/utils/description_to_codepart_search.py +1 -1
- cognee/modules/retrieval/utils/session_cache.py +156 -0
- cognee/modules/search/methods/get_search_type_tools.py +0 -5
- cognee/modules/search/methods/no_access_control_search.py +12 -1
- cognee/modules/search/methods/search.py +34 -2
- cognee/modules/search/types/SearchType.py +0 -1
- cognee/modules/settings/get_settings.py +23 -0
- cognee/modules/users/methods/get_authenticated_user.py +3 -1
- cognee/modules/users/methods/get_default_user.py +1 -6
- cognee/modules/users/roles/methods/create_role.py +2 -2
- cognee/modules/users/tenants/methods/create_tenant.py +2 -2
- cognee/shared/exceptions/exceptions.py +1 -1
- cognee/tasks/codingagents/coding_rule_associations.py +1 -2
- cognee/tasks/documents/exceptions/exceptions.py +1 -1
- cognee/tasks/graph/extract_graph_from_data.py +2 -0
- cognee/tasks/ingestion/data_item_to_text_file.py +3 -3
- cognee/tasks/ingestion/ingest_data.py +11 -5
- cognee/tasks/ingestion/save_data_item_to_storage.py +12 -1
- cognee/tasks/storage/add_data_points.py +3 -10
- cognee/tasks/storage/index_data_points.py +19 -14
- cognee/tasks/storage/index_graph_edges.py +25 -11
- cognee/tasks/web_scraper/__init__.py +34 -0
- cognee/tasks/web_scraper/config.py +26 -0
- cognee/tasks/web_scraper/default_url_crawler.py +446 -0
- cognee/tasks/web_scraper/models.py +46 -0
- cognee/tasks/web_scraper/types.py +4 -0
- cognee/tasks/web_scraper/utils.py +142 -0
- cognee/tasks/web_scraper/web_scraper_task.py +396 -0
- cognee/tests/cli_tests/cli_unit_tests/test_cli_utils.py +0 -1
- cognee/tests/integration/web_url_crawler/test_default_url_crawler.py +13 -0
- cognee/tests/integration/web_url_crawler/test_tavily_crawler.py +19 -0
- cognee/tests/integration/web_url_crawler/test_url_adding_e2e.py +344 -0
- cognee/tests/subprocesses/reader.py +25 -0
- cognee/tests/subprocesses/simple_cognify_1.py +31 -0
- cognee/tests/subprocesses/simple_cognify_2.py +31 -0
- cognee/tests/subprocesses/writer.py +32 -0
- cognee/tests/tasks/descriptive_metrics/metrics_test_utils.py +0 -2
- cognee/tests/tasks/descriptive_metrics/neo4j_metrics_test.py +8 -3
- cognee/tests/tasks/entity_extraction/entity_extraction_test.py +89 -0
- cognee/tests/tasks/web_scraping/web_scraping_test.py +172 -0
- cognee/tests/test_add_docling_document.py +56 -0
- cognee/tests/test_chromadb.py +7 -11
- cognee/tests/test_concurrent_subprocess_access.py +76 -0
- cognee/tests/test_conversation_history.py +240 -0
- cognee/tests/test_kuzu.py +27 -15
- cognee/tests/test_lancedb.py +7 -11
- cognee/tests/test_library.py +32 -2
- cognee/tests/test_neo4j.py +24 -16
- cognee/tests/test_neptune_analytics_vector.py +7 -11
- cognee/tests/test_permissions.py +9 -13
- cognee/tests/test_pgvector.py +4 -4
- cognee/tests/test_remote_kuzu.py +8 -11
- cognee/tests/test_s3_file_storage.py +1 -1
- cognee/tests/test_search_db.py +6 -8
- cognee/tests/unit/infrastructure/databases/cache/test_cache_config.py +89 -0
- cognee/tests/unit/modules/retrieval/conversation_history_test.py +154 -0
- {cognee-0.3.5.dist-info → cognee-0.3.7.dist-info}/METADATA +22 -7
- {cognee-0.3.5.dist-info → cognee-0.3.7.dist-info}/RECORD +155 -128
- {cognee-0.3.5.dist-info → cognee-0.3.7.dist-info}/entry_points.txt +1 -0
- distributed/Dockerfile +0 -3
- distributed/entrypoint.py +21 -9
- distributed/signal.py +5 -0
- distributed/workers/data_point_saving_worker.py +64 -34
- distributed/workers/graph_saving_worker.py +71 -47
- cognee/infrastructure/databases/graph/memgraph/memgraph_adapter.py +0 -1116
- cognee/modules/retrieval/insights_retriever.py +0 -133
- cognee/tests/test_memgraph.py +0 -109
- cognee/tests/unit/modules/retrieval/insights_retriever_test.py +0 -251
- distributed/poetry.lock +0 -12238
- distributed/pyproject.toml +0 -185
- {cognee-0.3.5.dist-info → cognee-0.3.7.dist-info}/WHEEL +0 -0
- {cognee-0.3.5.dist-info → cognee-0.3.7.dist-info}/licenses/LICENSE +0 -0
- {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"
|
|
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(
|
|
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
|
@@ -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):
|
cognee/exceptions/exceptions.py
CHANGED
|
@@ -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.
|
|
59
|
+
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
60
60
|
log=True,
|
|
61
61
|
log_level="ERROR",
|
|
62
62
|
):
|
|
@@ -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())
|
|
@@ -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.
|
|
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.
|
|
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', '
|
|
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
|
"""
|