cognee 0.2.3.dev0__py3-none-any.whl → 0.2.4__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 (179) hide show
  1. cognee/__main__.py +4 -0
  2. cognee/api/v1/add/add.py +18 -11
  3. cognee/api/v1/cognify/code_graph_pipeline.py +7 -1
  4. cognee/api/v1/cognify/cognify.py +22 -115
  5. cognee/api/v1/cognify/routers/get_cognify_router.py +11 -3
  6. cognee/api/v1/config/config.py +5 -13
  7. cognee/api/v1/datasets/routers/get_datasets_router.py +2 -2
  8. cognee/api/v1/delete/delete.py +1 -1
  9. cognee/api/v1/exceptions/__init__.py +13 -0
  10. cognee/api/v1/{delete → exceptions}/exceptions.py +15 -12
  11. cognee/api/v1/responses/default_tools.py +4 -0
  12. cognee/api/v1/responses/dispatch_function.py +6 -1
  13. cognee/api/v1/responses/models.py +1 -1
  14. cognee/api/v1/search/search.py +6 -7
  15. cognee/cli/__init__.py +10 -0
  16. cognee/cli/_cognee.py +180 -0
  17. cognee/cli/commands/__init__.py +1 -0
  18. cognee/cli/commands/add_command.py +80 -0
  19. cognee/cli/commands/cognify_command.py +128 -0
  20. cognee/cli/commands/config_command.py +225 -0
  21. cognee/cli/commands/delete_command.py +80 -0
  22. cognee/cli/commands/search_command.py +149 -0
  23. cognee/cli/config.py +33 -0
  24. cognee/cli/debug.py +21 -0
  25. cognee/cli/echo.py +45 -0
  26. cognee/cli/exceptions.py +23 -0
  27. cognee/cli/minimal_cli.py +97 -0
  28. cognee/cli/reference.py +26 -0
  29. cognee/cli/suppress_logging.py +12 -0
  30. cognee/eval_framework/corpus_builder/corpus_builder_executor.py +2 -2
  31. cognee/eval_framework/eval_config.py +1 -1
  32. cognee/exceptions/__init__.py +5 -5
  33. cognee/exceptions/exceptions.py +37 -17
  34. cognee/infrastructure/data/exceptions/__init__.py +7 -0
  35. cognee/infrastructure/data/exceptions/exceptions.py +22 -0
  36. cognee/infrastructure/data/utils/extract_keywords.py +3 -3
  37. cognee/infrastructure/databases/exceptions/__init__.py +3 -0
  38. cognee/infrastructure/databases/exceptions/exceptions.py +57 -9
  39. cognee/infrastructure/databases/graph/get_graph_engine.py +4 -9
  40. cognee/infrastructure/databases/graph/kuzu/adapter.py +64 -2
  41. cognee/infrastructure/databases/graph/neo4j_driver/adapter.py +49 -0
  42. cognee/infrastructure/databases/graph/neptune_driver/exceptions.py +15 -10
  43. cognee/infrastructure/databases/hybrid/falkordb/FalkorDBAdapter.py +2 -2
  44. cognee/infrastructure/databases/hybrid/neptune_analytics/NeptuneAnalyticsAdapter.py +4 -5
  45. cognee/infrastructure/databases/vector/chromadb/ChromaDBAdapter.py +2 -2
  46. cognee/infrastructure/databases/vector/embeddings/FastembedEmbeddingEngine.py +5 -3
  47. cognee/infrastructure/databases/vector/embeddings/LiteLLMEmbeddingEngine.py +17 -8
  48. cognee/infrastructure/databases/vector/embeddings/OllamaEmbeddingEngine.py +5 -5
  49. cognee/infrastructure/databases/vector/embeddings/config.py +2 -2
  50. cognee/infrastructure/databases/vector/embeddings/get_embedding_engine.py +6 -6
  51. cognee/infrastructure/databases/vector/exceptions/exceptions.py +3 -3
  52. cognee/infrastructure/databases/vector/lancedb/LanceDBAdapter.py +2 -2
  53. cognee/infrastructure/databases/vector/pgvector/PGVectorAdapter.py +4 -3
  54. cognee/infrastructure/files/utils/get_data_file_path.py +14 -9
  55. cognee/infrastructure/files/utils/get_file_metadata.py +2 -1
  56. cognee/infrastructure/llm/LLMGateway.py +14 -5
  57. cognee/infrastructure/llm/config.py +5 -5
  58. cognee/infrastructure/llm/exceptions.py +30 -2
  59. cognee/infrastructure/llm/structured_output_framework/baml/baml_src/extraction/knowledge_graph/extract_content_graph.py +16 -5
  60. cognee/infrastructure/llm/structured_output_framework/litellm_instructor/extraction/knowledge_graph/extract_content_graph.py +19 -15
  61. cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/anthropic/adapter.py +5 -5
  62. cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/gemini/adapter.py +6 -6
  63. cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/generic_llm_api/adapter.py +2 -2
  64. cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/get_llm_client.py +24 -15
  65. cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/ollama/adapter.py +6 -4
  66. cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/openai/adapter.py +9 -7
  67. cognee/infrastructure/llm/tokenizer/Gemini/adapter.py +2 -2
  68. cognee/infrastructure/llm/tokenizer/HuggingFace/adapter.py +3 -3
  69. cognee/infrastructure/llm/tokenizer/Mistral/adapter.py +3 -3
  70. cognee/infrastructure/llm/tokenizer/TikToken/adapter.py +6 -6
  71. cognee/infrastructure/llm/utils.py +7 -7
  72. cognee/modules/data/exceptions/exceptions.py +18 -5
  73. cognee/modules/data/methods/__init__.py +2 -0
  74. cognee/modules/data/methods/create_authorized_dataset.py +19 -0
  75. cognee/modules/data/methods/delete_data.py +2 -4
  76. cognee/modules/data/methods/get_authorized_dataset.py +11 -5
  77. cognee/modules/data/methods/get_authorized_dataset_by_name.py +16 -0
  78. cognee/modules/data/methods/load_or_create_datasets.py +2 -20
  79. cognee/modules/data/processing/document_types/exceptions/exceptions.py +2 -2
  80. cognee/modules/graph/cognee_graph/CogneeGraph.py +6 -4
  81. cognee/modules/graph/cognee_graph/CogneeGraphElements.py +5 -10
  82. cognee/modules/graph/exceptions/__init__.py +2 -0
  83. cognee/modules/graph/exceptions/exceptions.py +25 -3
  84. cognee/modules/graph/methods/get_formatted_graph_data.py +3 -2
  85. cognee/modules/ingestion/exceptions/exceptions.py +2 -2
  86. cognee/modules/ontology/exceptions/exceptions.py +4 -4
  87. cognee/modules/pipelines/__init__.py +1 -1
  88. cognee/modules/pipelines/exceptions/exceptions.py +2 -2
  89. cognee/modules/pipelines/exceptions/tasks.py +18 -0
  90. cognee/modules/pipelines/layers/__init__.py +1 -0
  91. cognee/modules/pipelines/layers/check_pipeline_run_qualification.py +59 -0
  92. cognee/modules/pipelines/layers/pipeline_execution_mode.py +127 -0
  93. cognee/modules/pipelines/layers/reset_dataset_pipeline_run_status.py +12 -0
  94. cognee/modules/pipelines/layers/resolve_authorized_user_dataset.py +34 -0
  95. cognee/modules/pipelines/layers/resolve_authorized_user_datasets.py +55 -0
  96. cognee/modules/pipelines/layers/setup_and_check_environment.py +41 -0
  97. cognee/modules/pipelines/layers/validate_pipeline_tasks.py +20 -0
  98. cognee/modules/pipelines/methods/__init__.py +2 -0
  99. cognee/modules/pipelines/methods/get_pipeline_runs_by_dataset.py +34 -0
  100. cognee/modules/pipelines/methods/reset_pipeline_run_status.py +16 -0
  101. cognee/modules/pipelines/operations/__init__.py +0 -1
  102. cognee/modules/pipelines/operations/log_pipeline_run_initiated.py +1 -1
  103. cognee/modules/pipelines/operations/pipeline.py +23 -138
  104. cognee/modules/retrieval/base_feedback.py +11 -0
  105. cognee/modules/retrieval/cypher_search_retriever.py +1 -9
  106. cognee/modules/retrieval/exceptions/exceptions.py +12 -6
  107. cognee/modules/retrieval/graph_completion_context_extension_retriever.py +9 -2
  108. cognee/modules/retrieval/graph_completion_cot_retriever.py +13 -6
  109. cognee/modules/retrieval/graph_completion_retriever.py +89 -5
  110. cognee/modules/retrieval/graph_summary_completion_retriever.py +2 -0
  111. cognee/modules/retrieval/natural_language_retriever.py +0 -4
  112. cognee/modules/retrieval/user_qa_feedback.py +83 -0
  113. cognee/modules/retrieval/utils/extract_uuid_from_node.py +18 -0
  114. cognee/modules/retrieval/utils/models.py +40 -0
  115. cognee/modules/search/exceptions/__init__.py +7 -0
  116. cognee/modules/search/exceptions/exceptions.py +15 -0
  117. cognee/modules/search/methods/search.py +47 -7
  118. cognee/modules/search/types/SearchType.py +1 -0
  119. cognee/modules/settings/get_settings.py +2 -2
  120. cognee/modules/users/exceptions/exceptions.py +6 -6
  121. cognee/shared/CodeGraphEntities.py +1 -0
  122. cognee/shared/exceptions/exceptions.py +2 -2
  123. cognee/shared/logging_utils.py +142 -31
  124. cognee/shared/utils.py +0 -1
  125. cognee/tasks/completion/exceptions/exceptions.py +3 -3
  126. cognee/tasks/documents/classify_documents.py +4 -0
  127. cognee/tasks/documents/exceptions/__init__.py +11 -0
  128. cognee/tasks/documents/exceptions/exceptions.py +36 -0
  129. cognee/tasks/documents/extract_chunks_from_documents.py +8 -2
  130. cognee/tasks/graph/exceptions/__init__.py +12 -0
  131. cognee/tasks/graph/exceptions/exceptions.py +41 -0
  132. cognee/tasks/graph/extract_graph_from_data.py +34 -2
  133. cognee/tasks/ingestion/exceptions/__init__.py +8 -0
  134. cognee/tasks/ingestion/exceptions/exceptions.py +12 -0
  135. cognee/tasks/ingestion/resolve_data_directories.py +5 -0
  136. cognee/tasks/repo_processor/get_local_dependencies.py +2 -0
  137. cognee/tasks/repo_processor/get_repo_file_dependencies.py +120 -48
  138. cognee/tasks/storage/add_data_points.py +41 -3
  139. cognee/tasks/storage/exceptions/__init__.py +9 -0
  140. cognee/tasks/storage/exceptions/exceptions.py +13 -0
  141. cognee/tasks/storage/index_data_points.py +1 -1
  142. cognee/tasks/summarization/exceptions/__init__.py +9 -0
  143. cognee/tasks/summarization/exceptions/exceptions.py +14 -0
  144. cognee/tasks/summarization/summarize_text.py +8 -1
  145. cognee/tests/integration/cli/__init__.py +3 -0
  146. cognee/tests/integration/cli/test_cli_integration.py +331 -0
  147. cognee/tests/integration/documents/PdfDocument_test.py +2 -2
  148. cognee/tests/integration/documents/TextDocument_test.py +2 -4
  149. cognee/tests/integration/documents/UnstructuredDocument_test.py +5 -8
  150. cognee/tests/test_delete_by_id.py +1 -1
  151. cognee/tests/{test_deletion.py → test_delete_hard.py} +0 -37
  152. cognee/tests/test_delete_soft.py +85 -0
  153. cognee/tests/test_kuzu.py +2 -2
  154. cognee/tests/test_neo4j.py +2 -2
  155. cognee/tests/test_search_db.py +126 -7
  156. cognee/tests/unit/cli/__init__.py +3 -0
  157. cognee/tests/unit/cli/test_cli_commands.py +483 -0
  158. cognee/tests/unit/cli/test_cli_edge_cases.py +625 -0
  159. cognee/tests/unit/cli/test_cli_main.py +173 -0
  160. cognee/tests/unit/cli/test_cli_runner.py +62 -0
  161. cognee/tests/unit/cli/test_cli_utils.py +127 -0
  162. cognee/tests/unit/modules/graph/cognee_graph_elements_test.py +5 -5
  163. cognee/tests/unit/modules/retrieval/graph_completion_retriever_context_extension_test.py +3 -3
  164. cognee/tests/unit/modules/retrieval/graph_completion_retriever_cot_test.py +3 -3
  165. cognee/tests/unit/modules/retrieval/graph_completion_retriever_test.py +3 -3
  166. cognee/tests/unit/modules/search/search_methods_test.py +4 -2
  167. {cognee-0.2.3.dev0.dist-info → cognee-0.2.4.dist-info}/METADATA +7 -5
  168. {cognee-0.2.3.dev0.dist-info → cognee-0.2.4.dist-info}/RECORD +172 -121
  169. cognee-0.2.4.dist-info/entry_points.txt +2 -0
  170. cognee/infrastructure/databases/exceptions/EmbeddingException.py +0 -20
  171. cognee/infrastructure/databases/graph/networkx/__init__.py +0 -0
  172. cognee/infrastructure/databases/graph/networkx/adapter.py +0 -1017
  173. cognee/infrastructure/pipeline/models/Operation.py +0 -60
  174. cognee/infrastructure/pipeline/models/__init__.py +0 -0
  175. cognee/notebooks/github_analysis_step_by_step.ipynb +0 -37
  176. cognee/tests/tasks/descriptive_metrics/networkx_metrics_test.py +0 -7
  177. {cognee-0.2.3.dev0.dist-info → cognee-0.2.4.dist-info}/WHEEL +0 -0
  178. {cognee-0.2.3.dev0.dist-info → cognee-0.2.4.dist-info}/licenses/LICENSE +0 -0
  179. {cognee-0.2.3.dev0.dist-info → cognee-0.2.4.dist-info}/licenses/NOTICE.md +0 -0
@@ -29,6 +29,7 @@ class GraphCompletionContextExtensionRetriever(GraphCompletionRetriever):
29
29
  top_k: Optional[int] = 5,
30
30
  node_type: Optional[Type] = None,
31
31
  node_name: Optional[List[str]] = None,
32
+ save_interaction: bool = False,
32
33
  ):
33
34
  super().__init__(
34
35
  user_prompt_path=user_prompt_path,
@@ -36,6 +37,7 @@ class GraphCompletionContextExtensionRetriever(GraphCompletionRetriever):
36
37
  top_k=top_k,
37
38
  node_type=node_type,
38
39
  node_name=node_name,
40
+ save_interaction=save_interaction,
39
41
  )
40
42
 
41
43
  async def get_completion(
@@ -105,11 +107,16 @@ class GraphCompletionContextExtensionRetriever(GraphCompletionRetriever):
105
107
 
106
108
  round_idx += 1
107
109
 
108
- answer = await generate_completion(
110
+ completion = await generate_completion(
109
111
  query=query,
110
112
  context=context,
111
113
  user_prompt_path=self.user_prompt_path,
112
114
  system_prompt_path=self.system_prompt_path,
113
115
  )
114
116
 
115
- return [answer]
117
+ if self.save_interaction and context and triplets and completion:
118
+ await self.save_qa(
119
+ question=query, answer=completion, context=context, triplets=triplets
120
+ )
121
+
122
+ return [completion]
@@ -35,6 +35,7 @@ class GraphCompletionCotRetriever(GraphCompletionRetriever):
35
35
  top_k: Optional[int] = 5,
36
36
  node_type: Optional[Type] = None,
37
37
  node_name: Optional[List[str]] = None,
38
+ save_interaction: bool = False,
38
39
  ):
39
40
  super().__init__(
40
41
  user_prompt_path=user_prompt_path,
@@ -42,6 +43,7 @@ class GraphCompletionCotRetriever(GraphCompletionRetriever):
42
43
  top_k=top_k,
43
44
  node_type=node_type,
44
45
  node_name=node_name,
46
+ save_interaction=save_interaction,
45
47
  )
46
48
  self.validation_system_prompt_path = validation_system_prompt_path
47
49
  self.validation_user_prompt_path = validation_user_prompt_path
@@ -75,7 +77,7 @@ class GraphCompletionCotRetriever(GraphCompletionRetriever):
75
77
  """
76
78
  followup_question = ""
77
79
  triplets = []
78
- answer = [""]
80
+ completion = [""]
79
81
 
80
82
  for round_idx in range(max_iter + 1):
81
83
  if round_idx == 0:
@@ -85,15 +87,15 @@ class GraphCompletionCotRetriever(GraphCompletionRetriever):
85
87
  triplets += await self.get_triplets(followup_question)
86
88
  context = await self.resolve_edges_to_text(list(set(triplets)))
87
89
 
88
- answer = await generate_completion(
90
+ completion = await generate_completion(
89
91
  query=query,
90
92
  context=context,
91
93
  user_prompt_path=self.user_prompt_path,
92
94
  system_prompt_path=self.system_prompt_path,
93
95
  )
94
- logger.info(f"Chain-of-thought: round {round_idx} - answer: {answer}")
96
+ logger.info(f"Chain-of-thought: round {round_idx} - answer: {completion}")
95
97
  if round_idx < max_iter:
96
- valid_args = {"query": query, "answer": answer, "context": context}
98
+ valid_args = {"query": query, "answer": completion, "context": context}
97
99
  valid_user_prompt = LLMGateway.render_prompt(
98
100
  filename=self.validation_user_prompt_path, context=valid_args
99
101
  )
@@ -106,7 +108,7 @@ class GraphCompletionCotRetriever(GraphCompletionRetriever):
106
108
  system_prompt=valid_system_prompt,
107
109
  response_model=str,
108
110
  )
109
- followup_args = {"query": query, "answer": answer, "reasoning": reasoning}
111
+ followup_args = {"query": query, "answer": completion, "reasoning": reasoning}
110
112
  followup_prompt = LLMGateway.render_prompt(
111
113
  filename=self.followup_user_prompt_path, context=followup_args
112
114
  )
@@ -121,4 +123,9 @@ class GraphCompletionCotRetriever(GraphCompletionRetriever):
121
123
  f"Chain-of-thought: round {round_idx} - follow-up question: {followup_question}"
122
124
  )
123
125
 
124
- return [answer]
126
+ if self.save_interaction and context and triplets and completion:
127
+ await self.save_qa(
128
+ question=query, answer=completion, context=context, triplets=triplets
129
+ )
130
+
131
+ return [completion]
@@ -1,14 +1,20 @@
1
- from typing import Any, Optional, Type, List
1
+ from typing import Any, Optional, Type, List, Coroutine
2
2
  from collections import Counter
3
+ from uuid import NAMESPACE_OID, uuid5
3
4
  import string
4
5
 
5
6
  from cognee.infrastructure.engine import DataPoint
7
+ from cognee.tasks.storage import add_data_points
6
8
  from cognee.modules.graph.utils.convert_node_to_data_point import get_all_subclasses
7
9
  from cognee.modules.retrieval.base_retriever import BaseRetriever
8
10
  from cognee.modules.retrieval.utils.brute_force_triplet_search import brute_force_triplet_search
9
11
  from cognee.modules.retrieval.utils.completion import generate_completion
10
12
  from cognee.modules.retrieval.utils.stop_words import DEFAULT_STOP_WORDS
11
13
  from cognee.shared.logging_utils import get_logger
14
+ from cognee.modules.retrieval.utils.extract_uuid_from_node import extract_uuid_from_node
15
+ from cognee.modules.retrieval.utils.models import CogneeUserInteraction
16
+ from cognee.modules.engine.models.node_set import NodeSet
17
+ from cognee.infrastructure.databases.graph import get_graph_engine
12
18
 
13
19
  logger = get_logger("GraphCompletionRetriever")
14
20
 
@@ -33,8 +39,10 @@ class GraphCompletionRetriever(BaseRetriever):
33
39
  top_k: Optional[int] = 5,
34
40
  node_type: Optional[Type] = None,
35
41
  node_name: Optional[List[str]] = None,
42
+ save_interaction: bool = False,
36
43
  ):
37
44
  """Initialize retriever with prompt paths and search parameters."""
45
+ self.save_interaction = save_interaction
38
46
  self.user_prompt_path = user_prompt_path
39
47
  self.system_prompt_path = system_prompt_path
40
48
  self.top_k = top_k if top_k is not None else 5
@@ -118,7 +126,7 @@ class GraphCompletionRetriever(BaseRetriever):
118
126
 
119
127
  return found_triplets
120
128
 
121
- async def get_context(self, query: str) -> str:
129
+ async def get_context(self, query: str) -> str | tuple[str, list]:
122
130
  """
123
131
  Retrieves and resolves graph triplets into context based on a query.
124
132
 
@@ -137,9 +145,11 @@ class GraphCompletionRetriever(BaseRetriever):
137
145
 
138
146
  if len(triplets) == 0:
139
147
  logger.warning("Empty context was provided to the completion")
140
- return ""
148
+ return "", triplets
141
149
 
142
- return await self.resolve_edges_to_text(triplets)
150
+ context = await self.resolve_edges_to_text(triplets)
151
+
152
+ return context, triplets
143
153
 
144
154
  async def get_completion(self, query: str, context: Optional[Any] = None) -> Any:
145
155
  """
@@ -157,8 +167,10 @@ class GraphCompletionRetriever(BaseRetriever):
157
167
 
158
168
  - Any: A generated completion based on the query and context provided.
159
169
  """
170
+ triplets = None
171
+
160
172
  if context is None:
161
- context = await self.get_context(query)
173
+ context, triplets = await self.get_context(query)
162
174
 
163
175
  completion = await generate_completion(
164
176
  query=query,
@@ -166,6 +178,12 @@ class GraphCompletionRetriever(BaseRetriever):
166
178
  user_prompt_path=self.user_prompt_path,
167
179
  system_prompt_path=self.system_prompt_path,
168
180
  )
181
+
182
+ if self.save_interaction and context and triplets and completion:
183
+ await self.save_qa(
184
+ question=query, answer=completion, context=context, triplets=triplets
185
+ )
186
+
169
187
  return [completion]
170
188
 
171
189
  def _top_n_words(self, text, stop_words=None, top_n=3, separator=", "):
@@ -187,3 +205,69 @@ class GraphCompletionRetriever(BaseRetriever):
187
205
  first_n_words = text.split()[:first_n_words]
188
206
  top_n_words = self._top_n_words(text, top_n=top_n_words)
189
207
  return f"{' '.join(first_n_words)}... [{top_n_words}]"
208
+
209
+ async def save_qa(self, question: str, answer: str, context: str, triplets: List) -> None:
210
+ """
211
+ Saves a question and answer pair for later analysis or storage.
212
+ Parameters:
213
+ -----------
214
+ - question (str): The question text.
215
+ - answer (str): The answer text.
216
+ - context (str): The context text.
217
+ - triplets (List): A list of triples retrieved from the graph.
218
+ """
219
+ nodeset_name = "Interactions"
220
+ interactions_node_set = NodeSet(
221
+ id=uuid5(NAMESPACE_OID, name=nodeset_name), name=nodeset_name
222
+ )
223
+ source_id = uuid5(NAMESPACE_OID, name=(question + answer + context))
224
+
225
+ cognee_user_interaction = CogneeUserInteraction(
226
+ id=source_id,
227
+ question=question,
228
+ answer=answer,
229
+ context=context,
230
+ belongs_to_set=interactions_node_set,
231
+ )
232
+
233
+ await add_data_points(data_points=[cognee_user_interaction], update_edge_collection=False)
234
+
235
+ relationships = []
236
+ relationship_name = "used_graph_element_to_answer"
237
+ for triplet in triplets:
238
+ target_id_1 = extract_uuid_from_node(triplet.node1)
239
+ target_id_2 = extract_uuid_from_node(triplet.node2)
240
+ if target_id_1 and target_id_2:
241
+ relationships.append(
242
+ (
243
+ source_id,
244
+ target_id_1,
245
+ relationship_name,
246
+ {
247
+ "relationship_name": relationship_name,
248
+ "source_node_id": source_id,
249
+ "target_node_id": target_id_1,
250
+ "ontology_valid": False,
251
+ "feedback_weight": 0,
252
+ },
253
+ )
254
+ )
255
+
256
+ relationships.append(
257
+ (
258
+ source_id,
259
+ target_id_2,
260
+ relationship_name,
261
+ {
262
+ "relationship_name": relationship_name,
263
+ "source_node_id": source_id,
264
+ "target_node_id": target_id_2,
265
+ "ontology_valid": False,
266
+ "feedback_weight": 0,
267
+ },
268
+ )
269
+ )
270
+
271
+ if len(relationships) > 0:
272
+ graph_engine = await get_graph_engine()
273
+ await graph_engine.add_edges(relationships)
@@ -24,6 +24,7 @@ class GraphSummaryCompletionRetriever(GraphCompletionRetriever):
24
24
  top_k: Optional[int] = 5,
25
25
  node_type: Optional[Type] = None,
26
26
  node_name: Optional[List[str]] = None,
27
+ save_interaction: bool = False,
27
28
  ):
28
29
  """Initialize retriever with default prompt paths and search parameters."""
29
30
  super().__init__(
@@ -32,6 +33,7 @@ class GraphSummaryCompletionRetriever(GraphCompletionRetriever):
32
33
  top_k=top_k,
33
34
  node_type=node_type,
34
35
  node_name=node_name,
36
+ save_interaction=save_interaction,
35
37
  )
36
38
  self.summarize_prompt_path = summarize_prompt_path
37
39
 
@@ -1,7 +1,6 @@
1
1
  from typing import Any, Optional
2
2
  from cognee.shared.logging_utils import get_logger
3
3
  from cognee.infrastructure.databases.graph import get_graph_engine
4
- from cognee.infrastructure.databases.graph.networkx.adapter import NetworkXAdapter
5
4
  from cognee.infrastructure.llm.LLMGateway import LLMGateway
6
5
  from cognee.modules.retrieval.base_retriever import BaseRetriever
7
6
  from cognee.modules.retrieval.exceptions import SearchTypeNotSupported
@@ -123,9 +122,6 @@ class NaturalLanguageRetriever(BaseRetriever):
123
122
  """
124
123
  graph_engine = await get_graph_engine()
125
124
 
126
- if isinstance(graph_engine, (NetworkXAdapter)):
127
- raise SearchTypeNotSupported("Natural language search type not supported.")
128
-
129
125
  return await self._execute_cypher_query(query, graph_engine)
130
126
 
131
127
  async def get_completion(self, query: str, context: Optional[Any] = None) -> Any:
@@ -0,0 +1,83 @@
1
+ from typing import Any, Optional, List
2
+
3
+ from uuid import NAMESPACE_OID, uuid5, UUID
4
+ from cognee.infrastructure.databases.graph import get_graph_engine
5
+ from cognee.infrastructure.llm import LLMGateway
6
+ from cognee.modules.engine.models import NodeSet
7
+ from cognee.shared.logging_utils import get_logger
8
+ from cognee.modules.retrieval.base_feedback import BaseFeedback
9
+ from cognee.modules.retrieval.utils.models import CogneeUserFeedback
10
+ from cognee.modules.retrieval.utils.models import UserFeedbackEvaluation
11
+ from cognee.tasks.storage import add_data_points
12
+
13
+ logger = get_logger("CompletionRetriever")
14
+
15
+
16
+ class UserQAFeedback(BaseFeedback):
17
+ """
18
+ Interface for handling user feedback queries.
19
+ Public methods:
20
+ - get_context(query: str) -> str
21
+ - get_completion(query: str, context: Optional[Any] = None) -> Any
22
+ """
23
+
24
+ def __init__(self, last_k: Optional[int] = 1) -> None:
25
+ """Initialize retriever with optional custom prompt paths."""
26
+ self.last_k = last_k
27
+
28
+ async def add_feedback(self, feedback_text: str) -> List[str]:
29
+ feedback_sentiment = await LLMGateway.acreate_structured_output(
30
+ text_input=feedback_text,
31
+ system_prompt="You are a sentiment analysis assistant. For each piece of user feedback you receive, return exactly one of: Positive, Negative, or Neutral classification and a corresponding score from -5 (worst negative) to 5 (best positive)",
32
+ response_model=UserFeedbackEvaluation,
33
+ )
34
+
35
+ graph_engine = await get_graph_engine()
36
+ last_interaction_ids = await graph_engine.get_last_user_interaction_ids(limit=self.last_k)
37
+
38
+ nodeset_name = "UserQAFeedbacks"
39
+ feedbacks_node_set = NodeSet(id=uuid5(NAMESPACE_OID, name=nodeset_name), name=nodeset_name)
40
+ feedback_id = uuid5(NAMESPACE_OID, name=feedback_text)
41
+
42
+ cognee_user_feedback = CogneeUserFeedback(
43
+ id=feedback_id,
44
+ feedback=feedback_text,
45
+ sentiment=feedback_sentiment.evaluation.value,
46
+ score=feedback_sentiment.score,
47
+ belongs_to_set=feedbacks_node_set,
48
+ )
49
+
50
+ await add_data_points(data_points=[cognee_user_feedback], update_edge_collection=False)
51
+
52
+ relationships = []
53
+ relationship_name = "gives_feedback_to"
54
+ to_node_ids = []
55
+
56
+ for interaction_id in last_interaction_ids:
57
+ target_id_1 = feedback_id
58
+ target_id_2 = UUID(interaction_id)
59
+
60
+ if target_id_1 and target_id_2:
61
+ relationships.append(
62
+ (
63
+ target_id_1,
64
+ target_id_2,
65
+ relationship_name,
66
+ {
67
+ "relationship_name": relationship_name,
68
+ "source_node_id": target_id_1,
69
+ "target_node_id": target_id_2,
70
+ "ontology_valid": False,
71
+ },
72
+ )
73
+ )
74
+ to_node_ids.append(str(target_id_2))
75
+
76
+ if len(relationships) > 0:
77
+ graph_engine = await get_graph_engine()
78
+ await graph_engine.add_edges(relationships)
79
+ await graph_engine.apply_feedback_weight(
80
+ node_ids=to_node_ids, weight=feedback_sentiment.score
81
+ )
82
+
83
+ return [feedback_text]
@@ -0,0 +1,18 @@
1
+ from typing import Any, Optional
2
+ from uuid import UUID
3
+
4
+
5
+ def extract_uuid_from_node(node: Any) -> Optional[UUID]:
6
+ """
7
+ Try to pull a UUID string out of node.id or node.properties['id'],
8
+ then return a UUID instance (or None if neither exists).
9
+ """
10
+ id_str = None
11
+ if not id_str:
12
+ id_str = getattr(node, "id", None)
13
+
14
+ if hasattr(node, "attributes") and not id_str:
15
+ id_str = node.attributes.get("id", None)
16
+
17
+ id = UUID(id_str) if isinstance(id_str, str) else None
18
+ return id
@@ -0,0 +1,40 @@
1
+ from typing import Optional
2
+ from cognee.infrastructure.engine.models.DataPoint import DataPoint
3
+ from cognee.modules.engine.models.node_set import NodeSet
4
+ from enum import Enum
5
+ from pydantic import BaseModel, Field, confloat
6
+
7
+
8
+ class CogneeUserInteraction(DataPoint):
9
+ """User - Cognee interaction"""
10
+
11
+ question: str
12
+ answer: str
13
+ context: str
14
+ belongs_to_set: Optional[NodeSet] = None
15
+
16
+
17
+ class CogneeUserFeedback(DataPoint):
18
+ """User - Cognee Feedback"""
19
+
20
+ feedback: str
21
+ sentiment: str
22
+ score: float
23
+ belongs_to_set: Optional[NodeSet] = None
24
+
25
+
26
+ class UserFeedbackSentiment(str, Enum):
27
+ """User - User feedback sentiment"""
28
+
29
+ positive = "positive"
30
+ negative = "negative"
31
+ neutral = "neutral"
32
+
33
+
34
+ class UserFeedbackEvaluation(BaseModel):
35
+ """User - User feedback evaluation"""
36
+
37
+ score: confloat(ge=-5, le=5) = Field(
38
+ ..., description="Sentiment score from -5 (negative) to +5 (positive)"
39
+ )
40
+ evaluation: UserFeedbackSentiment
@@ -0,0 +1,7 @@
1
+ """
2
+ Custom exceptions for the Cognee API.
3
+
4
+ This module defines a set of exceptions for handling various data errors
5
+ """
6
+
7
+ from .exceptions import UnsupportedSearchTypeError
@@ -0,0 +1,15 @@
1
+ from cognee.exceptions import (
2
+ CogneeValidationError,
3
+ )
4
+ from fastapi import status
5
+
6
+
7
+ class UnsupportedSearchTypeError(CogneeValidationError):
8
+ def __init__(
9
+ self,
10
+ search_type: str,
11
+ name: str = "UnsupportedSearchTypeError",
12
+ status_code: int = status.HTTP_400_BAD_REQUEST,
13
+ ):
14
+ message = f"Unsupported search type: {search_type}"
15
+ super().__init__(message, name, status_code)
@@ -4,8 +4,9 @@ import asyncio
4
4
  from uuid import UUID
5
5
  from typing import Callable, List, Optional, Type, Union
6
6
 
7
+ from cognee.modules.retrieval.user_qa_feedback import UserQAFeedback
8
+ from cognee.modules.search.exceptions import UnsupportedSearchTypeError
7
9
  from cognee.context_global_variables import set_database_global_context_variables
8
- from cognee.exceptions import InvalidValueError
9
10
  from cognee.modules.retrieval.chunks_retriever import ChunksRetriever
10
11
  from cognee.modules.retrieval.insights_retriever import InsightsRetriever
11
12
  from cognee.modules.retrieval.summaries_retriever import SummariesRetriever
@@ -39,6 +40,8 @@ async def search(
39
40
  top_k: int = 10,
40
41
  node_type: Optional[Type] = None,
41
42
  node_name: Optional[List[str]] = None,
43
+ save_interaction: Optional[bool] = False,
44
+ last_k: Optional[int] = None,
42
45
  ):
43
46
  """
44
47
 
@@ -58,7 +61,14 @@ async def search(
58
61
  # Use search function filtered by permissions if access control is enabled
59
62
  if os.getenv("ENABLE_BACKEND_ACCESS_CONTROL", "false").lower() == "true":
60
63
  return await authorized_search(
61
- query_text, query_type, user, dataset_ids, system_prompt_path, top_k
64
+ query_text=query_text,
65
+ query_type=query_type,
66
+ user=user,
67
+ dataset_ids=dataset_ids,
68
+ system_prompt_path=system_prompt_path,
69
+ top_k=top_k,
70
+ save_interaction=save_interaction,
71
+ last_k=last_k,
62
72
  )
63
73
 
64
74
  query = await log_query(query_text, query_type.value, user.id)
@@ -71,6 +81,8 @@ async def search(
71
81
  top_k=top_k,
72
82
  node_type=node_type,
73
83
  node_name=node_name,
84
+ save_interaction=save_interaction,
85
+ last_k=last_k,
74
86
  )
75
87
 
76
88
  await log_result(
@@ -92,6 +104,8 @@ async def specific_search(
92
104
  top_k: int = 10,
93
105
  node_type: Optional[Type] = None,
94
106
  node_name: Optional[List[str]] = None,
107
+ save_interaction: Optional[bool] = False,
108
+ last_k: Optional[int] = None,
95
109
  ) -> list:
96
110
  search_tasks: dict[SearchType, Callable] = {
97
111
  SearchType.SUMMARIES: SummariesRetriever(top_k=top_k).get_completion,
@@ -105,28 +119,33 @@ async def specific_search(
105
119
  top_k=top_k,
106
120
  node_type=node_type,
107
121
  node_name=node_name,
122
+ save_interaction=save_interaction,
108
123
  ).get_completion,
109
124
  SearchType.GRAPH_COMPLETION_COT: GraphCompletionCotRetriever(
110
125
  system_prompt_path=system_prompt_path,
111
126
  top_k=top_k,
112
127
  node_type=node_type,
113
128
  node_name=node_name,
129
+ save_interaction=save_interaction,
114
130
  ).get_completion,
115
131
  SearchType.GRAPH_COMPLETION_CONTEXT_EXTENSION: GraphCompletionContextExtensionRetriever(
116
132
  system_prompt_path=system_prompt_path,
117
133
  top_k=top_k,
118
134
  node_type=node_type,
119
135
  node_name=node_name,
136
+ save_interaction=save_interaction,
120
137
  ).get_completion,
121
138
  SearchType.GRAPH_SUMMARY_COMPLETION: GraphSummaryCompletionRetriever(
122
139
  system_prompt_path=system_prompt_path,
123
140
  top_k=top_k,
124
141
  node_type=node_type,
125
142
  node_name=node_name,
143
+ save_interaction=save_interaction,
126
144
  ).get_completion,
127
145
  SearchType.CODE: CodeRetriever(top_k=top_k).get_completion,
128
146
  SearchType.CYPHER: CypherSearchRetriever().get_completion,
129
147
  SearchType.NATURAL_LANGUAGE: NaturalLanguageRetriever().get_completion,
148
+ SearchType.FEEDBACK: UserQAFeedback(last_k=last_k).add_feedback,
130
149
  }
131
150
 
132
151
  # If the query type is FEELING_LUCKY, select the search type intelligently
@@ -136,7 +155,7 @@ async def specific_search(
136
155
  search_task = search_tasks.get(query_type)
137
156
 
138
157
  if search_task is None:
139
- raise InvalidValueError(message=f"Unsupported search type: {query_type}")
158
+ raise UnsupportedSearchTypeError(str(query_type))
140
159
 
141
160
  send_telemetry("cognee.search EXECUTION STARTED", user.id)
142
161
 
@@ -154,6 +173,8 @@ async def authorized_search(
154
173
  dataset_ids: Optional[list[UUID]] = None,
155
174
  system_prompt_path: str = "answer_simple_question.txt",
156
175
  top_k: int = 10,
176
+ save_interaction: bool = False,
177
+ last_k: Optional[int] = None,
157
178
  ) -> list:
158
179
  """
159
180
  Verifies access for provided datasets or uses all datasets user has read access for and performs search per dataset.
@@ -167,7 +188,14 @@ async def authorized_search(
167
188
 
168
189
  # Searches all provided datasets and handles setting up of appropriate database context based on permissions
169
190
  search_results = await specific_search_by_context(
170
- search_datasets, query_text, query_type, user, system_prompt_path, top_k
191
+ search_datasets,
192
+ query_text,
193
+ query_type,
194
+ user,
195
+ system_prompt_path,
196
+ top_k,
197
+ save_interaction,
198
+ last_k=last_k,
171
199
  )
172
200
 
173
201
  await log_result(query.id, json.dumps(search_results, cls=JSONEncoder), user.id)
@@ -182,17 +210,27 @@ async def specific_search_by_context(
182
210
  user: User,
183
211
  system_prompt_path: str,
184
212
  top_k: int,
213
+ save_interaction: bool = False,
214
+ last_k: Optional[int] = None,
185
215
  ):
186
216
  """
187
217
  Searches all provided datasets and handles setting up of appropriate database context based on permissions.
188
218
  Not to be used outside of active access control mode.
189
219
  """
190
220
 
191
- async def _search_by_context(dataset, user, query_type, query_text, system_prompt_path, top_k):
221
+ async def _search_by_context(
222
+ dataset, user, query_type, query_text, system_prompt_path, top_k, last_k
223
+ ):
192
224
  # Set database configuration in async context for each dataset user has access for
193
225
  await set_database_global_context_variables(dataset.id, dataset.owner_id)
194
226
  search_results = await specific_search(
195
- query_type, query_text, user, system_prompt_path=system_prompt_path, top_k=top_k
227
+ query_type,
228
+ query_text,
229
+ user,
230
+ system_prompt_path=system_prompt_path,
231
+ top_k=top_k,
232
+ save_interaction=save_interaction,
233
+ last_k=last_k,
196
234
  )
197
235
  return {
198
236
  "search_result": search_results,
@@ -204,7 +242,9 @@ async def specific_search_by_context(
204
242
  tasks = []
205
243
  for dataset in search_datasets:
206
244
  tasks.append(
207
- _search_by_context(dataset, user, query_type, query_text, system_prompt_path, top_k)
245
+ _search_by_context(
246
+ dataset, user, query_type, query_text, system_prompt_path, top_k, last_k
247
+ )
208
248
  )
209
249
 
210
250
  return await asyncio.gather(*tasks)
@@ -14,3 +14,4 @@ class SearchType(Enum):
14
14
  GRAPH_COMPLETION_COT = "GRAPH_COMPLETION_COT"
15
15
  GRAPH_COMPLETION_CONTEXT_EXTENSION = "GRAPH_COMPLETION_CONTEXT_EXTENSION"
16
16
  FEELING_LUCKY = "FEELING_LUCKY"
17
+ FEEDBACK = "FEEDBACK"
@@ -88,8 +88,8 @@ def get_settings() -> SettingsDict:
88
88
  "models": {
89
89
  "openai": [
90
90
  {
91
- "value": "gpt-4o-mini",
92
- "label": "gpt-4o-mini",
91
+ "value": "gpt-5-mini",
92
+ "label": "gpt-5-mini",
93
93
  },
94
94
  {
95
95
  "value": "gpt-4o",
@@ -1,8 +1,8 @@
1
- from cognee.exceptions import CogneeApiError
1
+ from cognee.exceptions import CogneeValidationError
2
2
  from fastapi import status
3
3
 
4
4
 
5
- class RoleNotFoundError(CogneeApiError):
5
+ class RoleNotFoundError(CogneeValidationError):
6
6
  """User group not found"""
7
7
 
8
8
  def __init__(
@@ -14,7 +14,7 @@ class RoleNotFoundError(CogneeApiError):
14
14
  super().__init__(message, name, status_code)
15
15
 
16
16
 
17
- class TenantNotFoundError(CogneeApiError):
17
+ class TenantNotFoundError(CogneeValidationError):
18
18
  """User group not found"""
19
19
 
20
20
  def __init__(
@@ -26,7 +26,7 @@ class TenantNotFoundError(CogneeApiError):
26
26
  super().__init__(message, name, status_code)
27
27
 
28
28
 
29
- class UserNotFoundError(CogneeApiError):
29
+ class UserNotFoundError(CogneeValidationError):
30
30
  """User not found"""
31
31
 
32
32
  def __init__(
@@ -38,7 +38,7 @@ class UserNotFoundError(CogneeApiError):
38
38
  super().__init__(message, name, status_code)
39
39
 
40
40
 
41
- class PermissionDeniedError(CogneeApiError):
41
+ class PermissionDeniedError(CogneeValidationError):
42
42
  def __init__(
43
43
  self,
44
44
  message: str = "User does not have permission on documents.",
@@ -48,7 +48,7 @@ class PermissionDeniedError(CogneeApiError):
48
48
  super().__init__(message, name, status_code)
49
49
 
50
50
 
51
- class PermissionNotFoundError(CogneeApiError):
51
+ class PermissionNotFoundError(CogneeValidationError):
52
52
  def __init__(
53
53
  self,
54
54
  message: str = "Permission type does not exist.",