cognee 0.2.4__py3-none-any.whl → 0.3.0__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 +2 -0
- cognee/api/client.py +28 -3
- cognee/api/health.py +10 -13
- cognee/api/v1/add/add.py +3 -1
- cognee/api/v1/add/routers/get_add_router.py +12 -37
- cognee/api/v1/cloud/routers/__init__.py +1 -0
- cognee/api/v1/cloud/routers/get_checks_router.py +23 -0
- cognee/api/v1/cognify/code_graph_pipeline.py +9 -4
- cognee/api/v1/cognify/cognify.py +50 -3
- cognee/api/v1/cognify/routers/get_cognify_router.py +1 -1
- cognee/api/v1/datasets/routers/get_datasets_router.py +15 -4
- cognee/api/v1/memify/__init__.py +0 -0
- cognee/api/v1/memify/routers/__init__.py +1 -0
- cognee/api/v1/memify/routers/get_memify_router.py +100 -0
- cognee/api/v1/notebooks/routers/__init__.py +1 -0
- cognee/api/v1/notebooks/routers/get_notebooks_router.py +96 -0
- cognee/api/v1/search/routers/get_search_router.py +20 -1
- cognee/api/v1/search/search.py +11 -4
- cognee/api/v1/sync/__init__.py +17 -0
- cognee/api/v1/sync/routers/__init__.py +3 -0
- cognee/api/v1/sync/routers/get_sync_router.py +241 -0
- cognee/api/v1/sync/sync.py +877 -0
- cognee/api/v1/ui/__init__.py +1 -0
- cognee/api/v1/ui/ui.py +529 -0
- cognee/api/v1/users/routers/get_auth_router.py +13 -1
- cognee/base_config.py +10 -1
- cognee/cli/_cognee.py +93 -0
- cognee/infrastructure/databases/graph/config.py +10 -4
- cognee/infrastructure/databases/graph/kuzu/adapter.py +135 -0
- cognee/infrastructure/databases/graph/neo4j_driver/adapter.py +89 -0
- cognee/infrastructure/databases/relational/__init__.py +2 -0
- cognee/infrastructure/databases/relational/get_async_session.py +15 -0
- cognee/infrastructure/databases/relational/sqlalchemy/SqlAlchemyAdapter.py +6 -1
- cognee/infrastructure/databases/relational/with_async_session.py +25 -0
- cognee/infrastructure/databases/vector/chromadb/ChromaDBAdapter.py +1 -1
- cognee/infrastructure/databases/vector/config.py +13 -6
- cognee/infrastructure/databases/vector/embeddings/FastembedEmbeddingEngine.py +1 -1
- cognee/infrastructure/databases/vector/embeddings/embedding_rate_limiter.py +2 -6
- cognee/infrastructure/databases/vector/embeddings/get_embedding_engine.py +4 -1
- cognee/infrastructure/files/storage/LocalFileStorage.py +9 -0
- cognee/infrastructure/files/storage/S3FileStorage.py +5 -0
- cognee/infrastructure/files/storage/StorageManager.py +7 -1
- cognee/infrastructure/files/storage/storage.py +16 -0
- cognee/infrastructure/llm/LLMGateway.py +18 -0
- cognee/infrastructure/llm/config.py +4 -2
- cognee/infrastructure/llm/prompts/extract_query_time.txt +15 -0
- cognee/infrastructure/llm/prompts/generate_event_entity_prompt.txt +25 -0
- cognee/infrastructure/llm/prompts/generate_event_graph_prompt.txt +30 -0
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/extraction/__init__.py +2 -0
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/extraction/extract_event_entities.py +44 -0
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/extraction/knowledge_graph/__init__.py +1 -0
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/extraction/knowledge_graph/extract_event_graph.py +46 -0
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/openai/adapter.py +25 -1
- cognee/infrastructure/utils/run_sync.py +8 -1
- cognee/modules/chunking/models/DocumentChunk.py +4 -3
- cognee/modules/cloud/exceptions/CloudApiKeyMissingError.py +15 -0
- cognee/modules/cloud/exceptions/CloudConnectionError.py +15 -0
- cognee/modules/cloud/exceptions/__init__.py +2 -0
- cognee/modules/cloud/operations/__init__.py +1 -0
- cognee/modules/cloud/operations/check_api_key.py +25 -0
- cognee/modules/data/deletion/prune_system.py +1 -1
- cognee/modules/data/methods/check_dataset_name.py +1 -1
- cognee/modules/data/methods/get_dataset_data.py +1 -1
- cognee/modules/data/methods/load_or_create_datasets.py +1 -1
- cognee/modules/engine/models/Event.py +16 -0
- cognee/modules/engine/models/Interval.py +8 -0
- cognee/modules/engine/models/Timestamp.py +13 -0
- cognee/modules/engine/models/__init__.py +3 -0
- cognee/modules/engine/utils/__init__.py +2 -0
- cognee/modules/engine/utils/generate_event_datapoint.py +46 -0
- cognee/modules/engine/utils/generate_timestamp_datapoint.py +51 -0
- cognee/modules/graph/cognee_graph/CogneeGraph.py +2 -2
- cognee/modules/graph/utils/__init__.py +1 -0
- cognee/modules/graph/utils/resolve_edges_to_text.py +71 -0
- cognee/modules/memify/__init__.py +1 -0
- cognee/modules/memify/memify.py +118 -0
- cognee/modules/notebooks/methods/__init__.py +5 -0
- cognee/modules/notebooks/methods/create_notebook.py +26 -0
- cognee/modules/notebooks/methods/delete_notebook.py +13 -0
- cognee/modules/notebooks/methods/get_notebook.py +21 -0
- cognee/modules/notebooks/methods/get_notebooks.py +18 -0
- cognee/modules/notebooks/methods/update_notebook.py +17 -0
- cognee/modules/notebooks/models/Notebook.py +53 -0
- cognee/modules/notebooks/models/__init__.py +1 -0
- cognee/modules/notebooks/operations/__init__.py +1 -0
- cognee/modules/notebooks/operations/run_in_local_sandbox.py +55 -0
- cognee/modules/pipelines/layers/reset_dataset_pipeline_run_status.py +19 -3
- cognee/modules/pipelines/operations/pipeline.py +1 -0
- cognee/modules/pipelines/operations/run_tasks.py +17 -41
- cognee/modules/retrieval/base_graph_retriever.py +18 -0
- cognee/modules/retrieval/base_retriever.py +1 -1
- cognee/modules/retrieval/code_retriever.py +8 -0
- cognee/modules/retrieval/coding_rules_retriever.py +31 -0
- cognee/modules/retrieval/completion_retriever.py +9 -3
- cognee/modules/retrieval/context_providers/TripletSearchContextProvider.py +1 -0
- cognee/modules/retrieval/graph_completion_context_extension_retriever.py +23 -14
- cognee/modules/retrieval/graph_completion_cot_retriever.py +21 -11
- cognee/modules/retrieval/graph_completion_retriever.py +32 -65
- cognee/modules/retrieval/graph_summary_completion_retriever.py +3 -1
- cognee/modules/retrieval/insights_retriever.py +14 -3
- cognee/modules/retrieval/summaries_retriever.py +1 -1
- cognee/modules/retrieval/temporal_retriever.py +152 -0
- cognee/modules/retrieval/utils/brute_force_triplet_search.py +7 -32
- cognee/modules/retrieval/utils/completion.py +10 -3
- cognee/modules/search/methods/get_search_type_tools.py +168 -0
- cognee/modules/search/methods/no_access_control_search.py +47 -0
- cognee/modules/search/methods/search.py +219 -139
- cognee/modules/search/types/SearchResult.py +21 -0
- cognee/modules/search/types/SearchType.py +2 -0
- cognee/modules/search/types/__init__.py +1 -0
- cognee/modules/search/utils/__init__.py +2 -0
- cognee/modules/search/utils/prepare_search_result.py +41 -0
- cognee/modules/search/utils/transform_context_to_graph.py +38 -0
- cognee/modules/sync/__init__.py +1 -0
- cognee/modules/sync/methods/__init__.py +23 -0
- cognee/modules/sync/methods/create_sync_operation.py +53 -0
- cognee/modules/sync/methods/get_sync_operation.py +107 -0
- cognee/modules/sync/methods/update_sync_operation.py +248 -0
- cognee/modules/sync/models/SyncOperation.py +142 -0
- cognee/modules/sync/models/__init__.py +3 -0
- cognee/modules/users/__init__.py +0 -1
- cognee/modules/users/methods/__init__.py +4 -1
- cognee/modules/users/methods/create_user.py +26 -1
- cognee/modules/users/methods/get_authenticated_user.py +36 -42
- cognee/modules/users/methods/get_default_user.py +3 -1
- cognee/modules/users/permissions/methods/get_specific_user_permission_datasets.py +2 -1
- cognee/root_dir.py +19 -0
- cognee/shared/logging_utils.py +1 -1
- cognee/tasks/codingagents/__init__.py +0 -0
- cognee/tasks/codingagents/coding_rule_associations.py +127 -0
- cognee/tasks/ingestion/save_data_item_to_storage.py +23 -0
- cognee/tasks/memify/__init__.py +2 -0
- cognee/tasks/memify/extract_subgraph.py +7 -0
- cognee/tasks/memify/extract_subgraph_chunks.py +11 -0
- cognee/tasks/repo_processor/get_repo_file_dependencies.py +52 -27
- cognee/tasks/temporal_graph/__init__.py +1 -0
- cognee/tasks/temporal_graph/add_entities_to_event.py +85 -0
- cognee/tasks/temporal_graph/enrich_events.py +34 -0
- cognee/tasks/temporal_graph/extract_events_and_entities.py +32 -0
- cognee/tasks/temporal_graph/extract_knowledge_graph_from_events.py +41 -0
- cognee/tasks/temporal_graph/models.py +49 -0
- cognee/tests/test_kuzu.py +4 -4
- cognee/tests/test_neo4j.py +4 -4
- cognee/tests/test_permissions.py +3 -3
- cognee/tests/test_relational_db_migration.py +7 -5
- cognee/tests/test_search_db.py +18 -24
- cognee/tests/test_temporal_graph.py +167 -0
- cognee/tests/unit/api/__init__.py +1 -0
- cognee/tests/unit/api/test_conditional_authentication_endpoints.py +246 -0
- cognee/tests/unit/modules/retrieval/chunks_retriever_test.py +18 -2
- cognee/tests/unit/modules/retrieval/graph_completion_retriever_context_extension_test.py +13 -16
- cognee/tests/unit/modules/retrieval/graph_completion_retriever_cot_test.py +11 -16
- cognee/tests/unit/modules/retrieval/graph_completion_retriever_test.py +5 -4
- cognee/tests/unit/modules/retrieval/insights_retriever_test.py +4 -2
- cognee/tests/unit/modules/retrieval/rag_completion_retriever_test.py +18 -2
- cognee/tests/unit/modules/retrieval/temporal_retriever_test.py +225 -0
- cognee/tests/unit/modules/users/__init__.py +1 -0
- cognee/tests/unit/modules/users/test_conditional_authentication.py +277 -0
- cognee/tests/unit/processing/utils/utils_test.py +20 -1
- {cognee-0.2.4.dist-info → cognee-0.3.0.dist-info}/METADATA +8 -6
- {cognee-0.2.4.dist-info → cognee-0.3.0.dist-info}/RECORD +165 -90
- cognee/tests/unit/modules/search/search_methods_test.py +0 -225
- {cognee-0.2.4.dist-info → cognee-0.3.0.dist-info}/WHEEL +0 -0
- {cognee-0.2.4.dist-info → cognee-0.3.0.dist-info}/entry_points.txt +0 -0
- {cognee-0.2.4.dist-info → cognee-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {cognee-0.2.4.dist-info → cognee-0.3.0.dist-info}/licenses/NOTICE.md +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
|
+
from typing import List
|
|
2
3
|
import pytest
|
|
3
4
|
import pathlib
|
|
4
|
-
|
|
5
5
|
import cognee
|
|
6
6
|
from cognee.low_level import setup
|
|
7
7
|
from cognee.tasks.storage import add_data_points
|
|
@@ -10,6 +10,20 @@ from cognee.modules.chunking.models import DocumentChunk
|
|
|
10
10
|
from cognee.modules.data.processing.document_types import TextDocument
|
|
11
11
|
from cognee.modules.retrieval.exceptions.exceptions import NoDataError
|
|
12
12
|
from cognee.modules.retrieval.completion_retriever import CompletionRetriever
|
|
13
|
+
from cognee.infrastructure.engine import DataPoint
|
|
14
|
+
from cognee.modules.data.processing.document_types import Document
|
|
15
|
+
from cognee.modules.engine.models import Entity
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DocumentChunkWithEntities(DataPoint):
|
|
19
|
+
text: str
|
|
20
|
+
chunk_size: int
|
|
21
|
+
chunk_index: int
|
|
22
|
+
cut_type: str
|
|
23
|
+
is_part_of: Document
|
|
24
|
+
contains: List[Entity] = None
|
|
25
|
+
|
|
26
|
+
metadata: dict = {"index_fields": ["text"]}
|
|
13
27
|
|
|
14
28
|
|
|
15
29
|
class TestRAGCompletionRetriever:
|
|
@@ -182,7 +196,9 @@ class TestRAGCompletionRetriever:
|
|
|
182
196
|
await retriever.get_context("Christina Mayer")
|
|
183
197
|
|
|
184
198
|
vector_engine = get_vector_engine()
|
|
185
|
-
await vector_engine.create_collection(
|
|
199
|
+
await vector_engine.create_collection(
|
|
200
|
+
"DocumentChunk_text", payload_schema=DocumentChunkWithEntities
|
|
201
|
+
)
|
|
186
202
|
|
|
187
203
|
context = await retriever.get_context("Christina Mayer")
|
|
188
204
|
assert context == "", "Returned context should be empty on an empty graph"
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from types import SimpleNamespace
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from cognee.modules.retrieval.temporal_retriever import TemporalRetriever
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# Test TemporalRetriever initialization defaults and overrides
|
|
9
|
+
def test_init_defaults_and_overrides():
|
|
10
|
+
tr = TemporalRetriever()
|
|
11
|
+
assert tr.top_k == 5
|
|
12
|
+
assert tr.user_prompt_path == "graph_context_for_question.txt"
|
|
13
|
+
assert tr.system_prompt_path == "answer_simple_question.txt"
|
|
14
|
+
assert tr.time_extraction_prompt_path == "extract_query_time.txt"
|
|
15
|
+
|
|
16
|
+
tr2 = TemporalRetriever(
|
|
17
|
+
top_k=3,
|
|
18
|
+
user_prompt_path="u.txt",
|
|
19
|
+
system_prompt_path="s.txt",
|
|
20
|
+
time_extraction_prompt_path="t.txt",
|
|
21
|
+
)
|
|
22
|
+
assert tr2.top_k == 3
|
|
23
|
+
assert tr2.user_prompt_path == "u.txt"
|
|
24
|
+
assert tr2.system_prompt_path == "s.txt"
|
|
25
|
+
assert tr2.time_extraction_prompt_path == "t.txt"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Test descriptions_to_string with basic and empty results
|
|
29
|
+
def test_descriptions_to_string_basic_and_empty():
|
|
30
|
+
tr = TemporalRetriever()
|
|
31
|
+
|
|
32
|
+
results = [
|
|
33
|
+
{"description": " First "},
|
|
34
|
+
{"nope": "no description"},
|
|
35
|
+
{"description": "Second"},
|
|
36
|
+
{"description": ""},
|
|
37
|
+
{"description": " Third line "},
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
s = tr.descriptions_to_string(results)
|
|
41
|
+
assert s == "First\n#####################\nSecond\n#####################\nThird line"
|
|
42
|
+
|
|
43
|
+
assert tr.descriptions_to_string([]) == ""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Test filter_top_k_events sorts and limits correctly
|
|
47
|
+
@pytest.mark.asyncio
|
|
48
|
+
async def test_filter_top_k_events_sorts_and_limits():
|
|
49
|
+
tr = TemporalRetriever(top_k=2)
|
|
50
|
+
|
|
51
|
+
relevant_events = [
|
|
52
|
+
{
|
|
53
|
+
"events": [
|
|
54
|
+
{"id": "e1", "description": "E1"},
|
|
55
|
+
{"id": "e2", "description": "E2"},
|
|
56
|
+
{"id": "e3", "description": "E3 - not in vector results"},
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
scored_results = [
|
|
62
|
+
SimpleNamespace(payload={"id": "e2"}, score=0.10),
|
|
63
|
+
SimpleNamespace(payload={"id": "e1"}, score=0.20),
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
top = await tr.filter_top_k_events(relevant_events, scored_results)
|
|
67
|
+
|
|
68
|
+
assert [e["id"] for e in top] == ["e2", "e1"]
|
|
69
|
+
assert all("score" in e for e in top)
|
|
70
|
+
assert top[0]["score"] == 0.10
|
|
71
|
+
assert top[1]["score"] == 0.20
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# Test filter_top_k_events handles unknown ids as infinite scores
|
|
75
|
+
@pytest.mark.asyncio
|
|
76
|
+
async def test_filter_top_k_events_includes_unknown_as_infinite_but_not_in_top_k():
|
|
77
|
+
tr = TemporalRetriever(top_k=2)
|
|
78
|
+
|
|
79
|
+
relevant_events = [
|
|
80
|
+
{
|
|
81
|
+
"events": [
|
|
82
|
+
{"id": "known1", "description": "Known 1"},
|
|
83
|
+
{"id": "unknown", "description": "Unknown"},
|
|
84
|
+
{"id": "known2", "description": "Known 2"},
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
scored_results = [
|
|
90
|
+
SimpleNamespace(payload={"id": "known2"}, score=0.05),
|
|
91
|
+
SimpleNamespace(payload={"id": "known1"}, score=0.50),
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
top = await tr.filter_top_k_events(relevant_events, scored_results)
|
|
95
|
+
assert [e["id"] for e in top] == ["known2", "known1"]
|
|
96
|
+
assert all(e["score"] != float("inf") for e in top)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# Test descriptions_to_string with unicode and newlines
|
|
100
|
+
def test_descriptions_to_string_unicode_and_newlines():
|
|
101
|
+
tr = TemporalRetriever()
|
|
102
|
+
results = [
|
|
103
|
+
{"description": "Line A\nwith newline"},
|
|
104
|
+
{"description": "This is a description"},
|
|
105
|
+
]
|
|
106
|
+
s = tr.descriptions_to_string(results)
|
|
107
|
+
assert "Line A\nwith newline" in s
|
|
108
|
+
assert "This is a description" in s
|
|
109
|
+
assert s.count("#####################") == 1
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# Test filter_top_k_events when top_k is larger than available events
|
|
113
|
+
@pytest.mark.asyncio
|
|
114
|
+
async def test_filter_top_k_events_limits_when_top_k_exceeds_events():
|
|
115
|
+
tr = TemporalRetriever(top_k=10)
|
|
116
|
+
relevant_events = [{"events": [{"id": "a"}, {"id": "b"}]}]
|
|
117
|
+
scored_results = [
|
|
118
|
+
SimpleNamespace(payload={"id": "a"}, score=0.1),
|
|
119
|
+
SimpleNamespace(payload={"id": "b"}, score=0.2),
|
|
120
|
+
]
|
|
121
|
+
out = await tr.filter_top_k_events(relevant_events, scored_results)
|
|
122
|
+
assert [e["id"] for e in out] == ["a", "b"]
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# Test filter_top_k_events when scored_results is empty
|
|
126
|
+
@pytest.mark.asyncio
|
|
127
|
+
async def test_filter_top_k_events_handles_empty_scored_results():
|
|
128
|
+
tr = TemporalRetriever(top_k=2)
|
|
129
|
+
relevant_events = [{"events": [{"id": "x"}, {"id": "y"}]}]
|
|
130
|
+
scored_results = []
|
|
131
|
+
out = await tr.filter_top_k_events(relevant_events, scored_results)
|
|
132
|
+
assert [e["id"] for e in out] == ["x", "y"]
|
|
133
|
+
assert all(e["score"] == float("inf") for e in out)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# Test filter_top_k_events error handling for missing structure
|
|
137
|
+
@pytest.mark.asyncio
|
|
138
|
+
async def test_filter_top_k_events_error_handling():
|
|
139
|
+
tr = TemporalRetriever(top_k=2)
|
|
140
|
+
with pytest.raises((KeyError, TypeError)):
|
|
141
|
+
await tr.filter_top_k_events([{}], [])
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class _FakeRetriever(TemporalRetriever):
|
|
145
|
+
def __init__(self, *args, **kwargs):
|
|
146
|
+
super().__init__(*args, **kwargs)
|
|
147
|
+
self._calls = []
|
|
148
|
+
|
|
149
|
+
async def extract_time_from_query(self, query: str):
|
|
150
|
+
if "both" in query:
|
|
151
|
+
return "2024-01-01", "2024-12-31"
|
|
152
|
+
if "from_only" in query:
|
|
153
|
+
return "2024-01-01", None
|
|
154
|
+
if "to_only" in query:
|
|
155
|
+
return None, "2024-12-31"
|
|
156
|
+
return None, None
|
|
157
|
+
|
|
158
|
+
async def get_triplets(self, query: str):
|
|
159
|
+
self._calls.append(("get_triplets", query))
|
|
160
|
+
return [{"s": "a", "p": "b", "o": "c"}]
|
|
161
|
+
|
|
162
|
+
async def resolve_edges_to_text(self, triplets):
|
|
163
|
+
self._calls.append(("resolve_edges_to_text", len(triplets)))
|
|
164
|
+
return "edges->text"
|
|
165
|
+
|
|
166
|
+
async def _fake_graph_collect_ids(self, **kwargs):
|
|
167
|
+
return ["e1", "e2"]
|
|
168
|
+
|
|
169
|
+
async def _fake_graph_collect_events(self, ids):
|
|
170
|
+
return [
|
|
171
|
+
{
|
|
172
|
+
"events": [
|
|
173
|
+
{"id": "e1", "description": "E1"},
|
|
174
|
+
{"id": "e2", "description": "E2"},
|
|
175
|
+
{"id": "e3", "description": "E3"},
|
|
176
|
+
]
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
async def _fake_vector_embed(self, texts):
|
|
181
|
+
assert isinstance(texts, list) and texts
|
|
182
|
+
return [[0.0, 1.0, 2.0]]
|
|
183
|
+
|
|
184
|
+
async def _fake_vector_search(self, **kwargs):
|
|
185
|
+
return [
|
|
186
|
+
SimpleNamespace(payload={"id": "e2"}, score=0.05),
|
|
187
|
+
SimpleNamespace(payload={"id": "e1"}, score=0.10),
|
|
188
|
+
]
|
|
189
|
+
|
|
190
|
+
async def get_context(self, query: str):
|
|
191
|
+
time_from, time_to = await self.extract_time_from_query(query)
|
|
192
|
+
|
|
193
|
+
if not (time_from or time_to):
|
|
194
|
+
triplets = await self.get_triplets(query)
|
|
195
|
+
return await self.resolve_edges_to_text(triplets)
|
|
196
|
+
|
|
197
|
+
ids = await self._fake_graph_collect_ids(time_from=time_from, time_to=time_to)
|
|
198
|
+
relevant_events = await self._fake_graph_collect_events(ids)
|
|
199
|
+
|
|
200
|
+
_ = await self._fake_vector_embed([query])
|
|
201
|
+
vector_search_results = await self._fake_vector_search(
|
|
202
|
+
collection_name="Event_name", query_vector=[0.0], limit=0
|
|
203
|
+
)
|
|
204
|
+
top_k_events = await self.filter_top_k_events(relevant_events, vector_search_results)
|
|
205
|
+
return self.descriptions_to_string(top_k_events)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
# Test get_context fallback to triplets when no time is extracted
|
|
209
|
+
@pytest.mark.asyncio
|
|
210
|
+
async def test_fake_get_context_falls_back_to_triplets_when_no_time():
|
|
211
|
+
tr = _FakeRetriever(top_k=2)
|
|
212
|
+
ctx = await tr.get_context("no_time")
|
|
213
|
+
assert ctx == "edges->text"
|
|
214
|
+
assert tr._calls[0][0] == "get_triplets"
|
|
215
|
+
assert tr._calls[1][0] == "resolve_edges_to_text"
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
# Test get_context when time is extracted and vector ranking is applied
|
|
219
|
+
@pytest.mark.asyncio
|
|
220
|
+
async def test_fake_get_context_with_time_filters_and_vector_ranking():
|
|
221
|
+
tr = _FakeRetriever(top_k=2)
|
|
222
|
+
ctx = await tr.get_context("both time")
|
|
223
|
+
assert ctx.startswith("E2")
|
|
224
|
+
assert "#####################" in ctx
|
|
225
|
+
assert "E1" in ctx and "E3" not in ctx
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Test package for user module tests
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import pytest
|
|
4
|
+
from unittest.mock import AsyncMock, patch
|
|
5
|
+
from uuid import uuid4
|
|
6
|
+
from types import SimpleNamespace
|
|
7
|
+
import importlib
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
from cognee.modules.users.models import User
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
gau_mod = importlib.import_module("cognee.modules.users.methods.get_authenticated_user")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestConditionalAuthentication:
|
|
17
|
+
"""Test cases for conditional authentication functionality."""
|
|
18
|
+
|
|
19
|
+
@pytest.mark.asyncio
|
|
20
|
+
@patch.object(gau_mod, "get_default_user", new_callable=AsyncMock)
|
|
21
|
+
async def test_require_authentication_false_no_token_returns_default_user(
|
|
22
|
+
self, mock_get_default
|
|
23
|
+
):
|
|
24
|
+
"""Test that when REQUIRE_AUTHENTICATION=false and no token, returns default user."""
|
|
25
|
+
# Mock the default user
|
|
26
|
+
mock_default_user = SimpleNamespace(id=uuid4(), email="default@example.com", is_active=True)
|
|
27
|
+
mock_get_default.return_value = mock_default_user
|
|
28
|
+
|
|
29
|
+
# Use gau_mod.get_authenticated_user instead
|
|
30
|
+
|
|
31
|
+
# Test with None user (no authentication)
|
|
32
|
+
result = await gau_mod.get_authenticated_user(user=None)
|
|
33
|
+
|
|
34
|
+
assert result == mock_default_user
|
|
35
|
+
mock_get_default.assert_called_once()
|
|
36
|
+
|
|
37
|
+
@pytest.mark.asyncio
|
|
38
|
+
@patch.object(gau_mod, "get_default_user", new_callable=AsyncMock)
|
|
39
|
+
async def test_require_authentication_false_with_valid_user_returns_user(
|
|
40
|
+
self, mock_get_default
|
|
41
|
+
):
|
|
42
|
+
"""Test that when REQUIRE_AUTHENTICATION=false and valid user, returns that user."""
|
|
43
|
+
mock_authenticated_user = User(
|
|
44
|
+
id=uuid4(),
|
|
45
|
+
email="user@example.com",
|
|
46
|
+
hashed_password="hashed",
|
|
47
|
+
is_active=True,
|
|
48
|
+
is_verified=True,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Use gau_mod.get_authenticated_user instead
|
|
52
|
+
|
|
53
|
+
# Test with authenticated user
|
|
54
|
+
result = await gau_mod.get_authenticated_user(user=mock_authenticated_user)
|
|
55
|
+
|
|
56
|
+
assert result == mock_authenticated_user
|
|
57
|
+
mock_get_default.assert_not_called()
|
|
58
|
+
|
|
59
|
+
@pytest.mark.asyncio
|
|
60
|
+
@patch.object(gau_mod, "get_default_user", new_callable=AsyncMock)
|
|
61
|
+
async def test_require_authentication_true_with_user_returns_user(self, mock_get_default):
|
|
62
|
+
"""Test that when REQUIRE_AUTHENTICATION=true and user present, returns user."""
|
|
63
|
+
mock_authenticated_user = User(
|
|
64
|
+
id=uuid4(),
|
|
65
|
+
email="user@example.com",
|
|
66
|
+
hashed_password="hashed",
|
|
67
|
+
is_active=True,
|
|
68
|
+
is_verified=True,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Use gau_mod.get_authenticated_user instead
|
|
72
|
+
|
|
73
|
+
result = await gau_mod.get_authenticated_user(user=mock_authenticated_user)
|
|
74
|
+
|
|
75
|
+
assert result == mock_authenticated_user
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class TestConditionalAuthenticationIntegration:
|
|
79
|
+
"""Integration tests that test the full authentication flow."""
|
|
80
|
+
|
|
81
|
+
@pytest.mark.asyncio
|
|
82
|
+
async def test_fastapi_users_dependency_creation(self):
|
|
83
|
+
"""Test that FastAPI Users dependency can be created correctly."""
|
|
84
|
+
from cognee.modules.users.get_fastapi_users import get_fastapi_users
|
|
85
|
+
|
|
86
|
+
fastapi_users = get_fastapi_users()
|
|
87
|
+
|
|
88
|
+
# Test that we can create optional dependency
|
|
89
|
+
optional_dependency = fastapi_users.current_user(optional=True, active=True)
|
|
90
|
+
assert callable(optional_dependency)
|
|
91
|
+
|
|
92
|
+
# Test that we can create required dependency
|
|
93
|
+
required_dependency = fastapi_users.current_user(active=True) # optional=False by default
|
|
94
|
+
assert callable(required_dependency)
|
|
95
|
+
|
|
96
|
+
@pytest.mark.asyncio
|
|
97
|
+
async def test_conditional_authentication_function_exists(self):
|
|
98
|
+
"""Test that the conditional authentication function can be imported and used."""
|
|
99
|
+
from cognee.modules.users.methods.get_authenticated_user import (
|
|
100
|
+
get_authenticated_user,
|
|
101
|
+
REQUIRE_AUTHENTICATION,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Should be callable
|
|
105
|
+
assert callable(get_authenticated_user)
|
|
106
|
+
|
|
107
|
+
# REQUIRE_AUTHENTICATION should be a boolean
|
|
108
|
+
assert isinstance(REQUIRE_AUTHENTICATION, bool)
|
|
109
|
+
|
|
110
|
+
# Currently should be False (optional authentication)
|
|
111
|
+
assert not REQUIRE_AUTHENTICATION
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class TestConditionalAuthenticationEnvironmentVariables:
|
|
115
|
+
"""Test environment variable handling."""
|
|
116
|
+
|
|
117
|
+
def test_require_authentication_default_false(self):
|
|
118
|
+
"""Test that REQUIRE_AUTHENTICATION defaults to false when imported with no env vars."""
|
|
119
|
+
with patch.dict(os.environ, {}, clear=True):
|
|
120
|
+
# Remove module from cache to force fresh import
|
|
121
|
+
module_name = "cognee.modules.users.methods.get_authenticated_user"
|
|
122
|
+
if module_name in sys.modules:
|
|
123
|
+
del sys.modules[module_name]
|
|
124
|
+
|
|
125
|
+
# Import after patching environment - module will see empty environment
|
|
126
|
+
from cognee.modules.users.methods.get_authenticated_user import (
|
|
127
|
+
REQUIRE_AUTHENTICATION,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
importlib.invalidate_caches()
|
|
131
|
+
assert not REQUIRE_AUTHENTICATION
|
|
132
|
+
|
|
133
|
+
def test_require_authentication_true(self):
|
|
134
|
+
"""Test that REQUIRE_AUTHENTICATION=true is parsed correctly when imported."""
|
|
135
|
+
with patch.dict(os.environ, {"REQUIRE_AUTHENTICATION": "true"}):
|
|
136
|
+
# Remove module from cache to force fresh import
|
|
137
|
+
module_name = "cognee.modules.users.methods.get_authenticated_user"
|
|
138
|
+
if module_name in sys.modules:
|
|
139
|
+
del sys.modules[module_name]
|
|
140
|
+
|
|
141
|
+
# Import after patching environment - module will see REQUIRE_AUTHENTICATION=true
|
|
142
|
+
from cognee.modules.users.methods.get_authenticated_user import (
|
|
143
|
+
REQUIRE_AUTHENTICATION,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
assert REQUIRE_AUTHENTICATION
|
|
147
|
+
|
|
148
|
+
def test_require_authentication_false_explicit(self):
|
|
149
|
+
"""Test that REQUIRE_AUTHENTICATION=false is parsed correctly when imported."""
|
|
150
|
+
with patch.dict(os.environ, {"REQUIRE_AUTHENTICATION": "false"}):
|
|
151
|
+
# Remove module from cache to force fresh import
|
|
152
|
+
module_name = "cognee.modules.users.methods.get_authenticated_user"
|
|
153
|
+
if module_name in sys.modules:
|
|
154
|
+
del sys.modules[module_name]
|
|
155
|
+
|
|
156
|
+
# Import after patching environment - module will see REQUIRE_AUTHENTICATION=false
|
|
157
|
+
from cognee.modules.users.methods.get_authenticated_user import (
|
|
158
|
+
REQUIRE_AUTHENTICATION,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
assert not REQUIRE_AUTHENTICATION
|
|
162
|
+
|
|
163
|
+
def test_require_authentication_case_insensitive(self):
|
|
164
|
+
"""Test that environment variable parsing is case insensitive when imported."""
|
|
165
|
+
test_cases = ["TRUE", "True", "tRuE", "FALSE", "False", "fAlSe"]
|
|
166
|
+
|
|
167
|
+
for case in test_cases:
|
|
168
|
+
with patch.dict(os.environ, {"REQUIRE_AUTHENTICATION": case}):
|
|
169
|
+
# Remove module from cache to force fresh import
|
|
170
|
+
module_name = "cognee.modules.users.methods.get_authenticated_user"
|
|
171
|
+
if module_name in sys.modules:
|
|
172
|
+
del sys.modules[module_name]
|
|
173
|
+
|
|
174
|
+
# Import after patching environment
|
|
175
|
+
from cognee.modules.users.methods.get_authenticated_user import (
|
|
176
|
+
REQUIRE_AUTHENTICATION,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
expected = case.lower() == "true"
|
|
180
|
+
assert REQUIRE_AUTHENTICATION == expected, f"Failed for case: {case}"
|
|
181
|
+
|
|
182
|
+
def test_current_require_authentication_value(self):
|
|
183
|
+
"""Test that the current REQUIRE_AUTHENTICATION module value is as expected."""
|
|
184
|
+
from cognee.modules.users.methods.get_authenticated_user import (
|
|
185
|
+
REQUIRE_AUTHENTICATION,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# The module-level variable should currently be False (set at import time)
|
|
189
|
+
assert isinstance(REQUIRE_AUTHENTICATION, bool)
|
|
190
|
+
assert not REQUIRE_AUTHENTICATION
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class TestConditionalAuthenticationEdgeCases:
|
|
194
|
+
"""Test edge cases and error scenarios."""
|
|
195
|
+
|
|
196
|
+
@pytest.mark.asyncio
|
|
197
|
+
@patch.object(gau_mod, "get_default_user", new_callable=AsyncMock)
|
|
198
|
+
async def test_get_default_user_raises_exception(self, mock_get_default):
|
|
199
|
+
"""Test behavior when get_default_user raises an exception."""
|
|
200
|
+
mock_get_default.side_effect = Exception("Database error")
|
|
201
|
+
|
|
202
|
+
# This should propagate the exception
|
|
203
|
+
with pytest.raises(Exception, match="Database error"):
|
|
204
|
+
await gau_mod.get_authenticated_user(user=None)
|
|
205
|
+
|
|
206
|
+
@pytest.mark.asyncio
|
|
207
|
+
@patch.object(gau_mod, "get_default_user", new_callable=AsyncMock)
|
|
208
|
+
async def test_user_type_consistency(self, mock_get_default):
|
|
209
|
+
"""Test that the function always returns the same type."""
|
|
210
|
+
mock_user = User(
|
|
211
|
+
id=uuid4(),
|
|
212
|
+
email="test@example.com",
|
|
213
|
+
hashed_password="hashed",
|
|
214
|
+
is_active=True,
|
|
215
|
+
is_verified=True,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
mock_default_user = SimpleNamespace(id=uuid4(), email="default@example.com", is_active=True)
|
|
219
|
+
mock_get_default.return_value = mock_default_user
|
|
220
|
+
|
|
221
|
+
# Test with user
|
|
222
|
+
result1 = await gau_mod.get_authenticated_user(user=mock_user)
|
|
223
|
+
assert result1 == mock_user
|
|
224
|
+
|
|
225
|
+
# Test with None
|
|
226
|
+
result2 = await gau_mod.get_authenticated_user(user=None)
|
|
227
|
+
assert result2 == mock_default_user
|
|
228
|
+
|
|
229
|
+
# Both should have user-like interface
|
|
230
|
+
assert hasattr(result1, "id")
|
|
231
|
+
assert hasattr(result1, "email")
|
|
232
|
+
assert result1.id == mock_user.id
|
|
233
|
+
assert result1.email == mock_user.email
|
|
234
|
+
assert hasattr(result2, "id")
|
|
235
|
+
assert hasattr(result2, "email")
|
|
236
|
+
assert result2.id == mock_default_user.id
|
|
237
|
+
assert result2.email == mock_default_user.email
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@pytest.mark.asyncio
|
|
241
|
+
class TestAuthenticationScenarios:
|
|
242
|
+
"""Test specific authentication scenarios that could occur in FastAPI Users."""
|
|
243
|
+
|
|
244
|
+
@patch.object(gau_mod, "get_default_user", new_callable=AsyncMock)
|
|
245
|
+
async def test_fallback_to_default_user_scenarios(self, mock_get_default):
|
|
246
|
+
"""
|
|
247
|
+
Test fallback to default user for all scenarios where FastAPI Users returns None:
|
|
248
|
+
- No JWT/Cookie present
|
|
249
|
+
- Invalid JWT/Cookie
|
|
250
|
+
- Valid JWT but user doesn't exist in database
|
|
251
|
+
- Valid JWT but user is inactive (active=True requirement)
|
|
252
|
+
|
|
253
|
+
All these scenarios result in FastAPI Users returning None when optional=True,
|
|
254
|
+
which should trigger fallback to default user.
|
|
255
|
+
"""
|
|
256
|
+
mock_default_user = SimpleNamespace(id=uuid4(), email="default@example.com")
|
|
257
|
+
mock_get_default.return_value = mock_default_user
|
|
258
|
+
|
|
259
|
+
# All the above scenarios result in user=None being passed to our function
|
|
260
|
+
result = await gau_mod.get_authenticated_user(user=None)
|
|
261
|
+
assert result == mock_default_user
|
|
262
|
+
mock_get_default.assert_called_once()
|
|
263
|
+
|
|
264
|
+
async def test_scenario_valid_active_user(self):
|
|
265
|
+
"""Scenario: Valid JWT and user exists and is active → returns the user."""
|
|
266
|
+
mock_user = User(
|
|
267
|
+
id=uuid4(),
|
|
268
|
+
email="active@example.com",
|
|
269
|
+
hashed_password="hashed",
|
|
270
|
+
is_active=True,
|
|
271
|
+
is_verified=True,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# Use gau_mod.get_authenticated_user instead
|
|
275
|
+
|
|
276
|
+
result = await gau_mod.get_authenticated_user(user=mock_user)
|
|
277
|
+
assert result == mock_user
|
|
@@ -4,8 +4,9 @@ import pytest
|
|
|
4
4
|
from unittest.mock import patch, mock_open
|
|
5
5
|
from io import BytesIO
|
|
6
6
|
from uuid import uuid4
|
|
7
|
+
from pathlib import Path
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
from cognee.root_dir import ensure_absolute_path
|
|
9
10
|
from cognee.infrastructure.files.utils.get_file_content_hash import get_file_content_hash
|
|
10
11
|
from cognee.shared.utils import get_anonymous_id
|
|
11
12
|
|
|
@@ -52,3 +53,21 @@ async def test_get_file_content_hash_stream():
|
|
|
52
53
|
expected_hash = hashlib.md5(b"test_data").hexdigest()
|
|
53
54
|
result = await get_file_content_hash(stream)
|
|
54
55
|
assert result == expected_hash
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@pytest.mark.asyncio
|
|
59
|
+
async def test_root_dir_absolute_paths():
|
|
60
|
+
"""Test absolute path handling in root_dir.py"""
|
|
61
|
+
# Test with absolute path
|
|
62
|
+
abs_path = "C:/absolute/path" if os.name == "nt" else "/absolute/path"
|
|
63
|
+
result = ensure_absolute_path(abs_path)
|
|
64
|
+
assert result == str(Path(abs_path).resolve())
|
|
65
|
+
|
|
66
|
+
# Test with relative path (should fail)
|
|
67
|
+
rel_path = "relative/path"
|
|
68
|
+
with pytest.raises(ValueError, match="must be absolute"):
|
|
69
|
+
ensure_absolute_path(rel_path)
|
|
70
|
+
|
|
71
|
+
# Test with None path
|
|
72
|
+
with pytest.raises(ValueError, match="cannot be None"):
|
|
73
|
+
ensure_absolute_path(None)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cognee
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Cognee - is a library for enriching LLM context with a semantic layer for better understanding and reasoning.
|
|
5
5
|
Project-URL: Homepage, https://www.cognee.ai
|
|
6
6
|
Project-URL: Repository, https://github.com/topoteretes/cognee
|
|
@@ -37,7 +37,7 @@ Requires-Dist: networkx<4,>=3.4.2
|
|
|
37
37
|
Requires-Dist: nltk<4.0.0,>=3.9.1
|
|
38
38
|
Requires-Dist: numpy<=4.0.0,>=1.26.4
|
|
39
39
|
Requires-Dist: onnxruntime<2.0.0,>=1.0.0
|
|
40
|
-
Requires-Dist: openai<
|
|
40
|
+
Requires-Dist: openai<2.0.0,>=1.80.1
|
|
41
41
|
Requires-Dist: pandas<3.0.0,>=2.2.2
|
|
42
42
|
Requires-Dist: pre-commit<5,>=4.0.1
|
|
43
43
|
Requires-Dist: pydantic-settings<3,>=2.2.1
|
|
@@ -183,7 +183,7 @@ Description-Content-Type: text/markdown
|
|
|
183
183
|
|
|
184
184
|
|
|
185
185
|
|
|
186
|
-
**🚀 We launched Cogwit beta (Fully-hosted AI Memory): Sign up [here](https://platform.cognee.ai/)! 🚀**
|
|
186
|
+
**🚀 We launched Cogwit beta (Fully-hosted AI Memory): Sign up [here](https://platform.cognee.ai/)! 🚀**
|
|
187
187
|
|
|
188
188
|
Build dynamic memory for Agents and replace RAG using scalable, modular ECL (Extract, Cognify, Load) pipelines.
|
|
189
189
|
|
|
@@ -219,7 +219,9 @@ More on [use-cases](https://docs.cognee.ai/use-cases) and [evals](https://github
|
|
|
219
219
|
|
|
220
220
|
## Get Started
|
|
221
221
|
|
|
222
|
-
Get started quickly with a Google Colab <a href="https://colab.research.google.com/drive/1jHbWVypDgCLwjE71GSXhRL3YxYhCZzG1?usp=sharing">notebook</a> , <a href="https://deepnote.com/workspace/cognee-382213d0-0444-4c89-8265-13770e333c02/project/cognee-demo-78ffacb9-5832-4611-bb1a-560386068b30/notebook/Notebook-1-75b24cda566d4c24ab348f7150792601?utm_source=share-modal&utm_medium=product-shared-content&utm_campaign=notebook&utm_content=78ffacb9-5832-4611-bb1a-560386068b30">Deepnote notebook</a> or <a href="https://github.com/topoteretes/cognee-starter">starter repo</a>
|
|
222
|
+
Get started quickly with a Google Colab <a href="https://colab.research.google.com/drive/1jHbWVypDgCLwjE71GSXhRL3YxYhCZzG1?usp=sharing">notebook</a> , <a href="https://deepnote.com/workspace/cognee-382213d0-0444-4c89-8265-13770e333c02/project/cognee-demo-78ffacb9-5832-4611-bb1a-560386068b30/notebook/Notebook-1-75b24cda566d4c24ab348f7150792601?utm_source=share-modal&utm_medium=product-shared-content&utm_campaign=notebook&utm_content=78ffacb9-5832-4611-bb1a-560386068b30">Deepnote notebook</a> or <a href="https://github.com/topoteretes/cognee/tree/main/cognee-starter-kit">starter repo</a>
|
|
223
|
+
|
|
224
|
+
|
|
223
225
|
|
|
224
226
|
|
|
225
227
|
## Contributing
|
|
@@ -312,9 +314,9 @@ Example output:
|
|
|
312
314
|
|
|
313
315
|
You can also cognify your files and query using cognee UI.
|
|
314
316
|
|
|
315
|
-
<img src="assets/cognee-ui
|
|
317
|
+
<img src="assets/cognee-new-ui.webp" width="100%" alt="Cognee UI 2"></a>
|
|
316
318
|
|
|
317
|
-
Try cognee UI
|
|
319
|
+
Try cognee UI by runnning ``` cognee -ui ``` command on your terminal.
|
|
318
320
|
|
|
319
321
|
## Understand our architecture
|
|
320
322
|
|