cognee 0.5.1.dev0__py3-none-any.whl → 0.5.2__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/alembic/README +1 -0
- cognee/alembic/env.py +107 -0
- cognee/alembic/script.py.mako +26 -0
- cognee/alembic/versions/1a58b986e6e1_enable_delete_for_old_tutorial_notebooks.py +52 -0
- cognee/alembic/versions/1d0bb7fede17_add_pipeline_run_status.py +33 -0
- cognee/alembic/versions/1daae0df1866_incremental_loading.py +48 -0
- cognee/alembic/versions/211ab850ef3d_add_sync_operations_table.py +118 -0
- cognee/alembic/versions/45957f0a9849_add_notebook_table.py +46 -0
- cognee/alembic/versions/46a6ce2bd2b2_expand_dataset_database_with_json_.py +333 -0
- cognee/alembic/versions/482cd6517ce4_add_default_user.py +30 -0
- cognee/alembic/versions/76625596c5c3_expand_dataset_database_for_multi_user.py +98 -0
- cognee/alembic/versions/8057ae7329c2_initial_migration.py +25 -0
- cognee/alembic/versions/9e7a3cb85175_loader_separation.py +104 -0
- cognee/alembic/versions/a1b2c3d4e5f6_add_label_column_to_data.py +38 -0
- cognee/alembic/versions/ab7e313804ae_permission_system_rework.py +236 -0
- cognee/alembic/versions/b9274c27a25a_kuzu_11_migration.py +75 -0
- cognee/alembic/versions/c946955da633_multi_tenant_support.py +137 -0
- cognee/alembic/versions/e1ec1dcb50b6_add_last_accessed_to_data.py +51 -0
- cognee/alembic/versions/e4ebee1091e7_expand_data_model_info.py +140 -0
- cognee/alembic.ini +117 -0
- cognee/api/v1/add/routers/get_add_router.py +2 -0
- cognee/api/v1/cognify/cognify.py +11 -6
- cognee/api/v1/cognify/routers/get_cognify_router.py +8 -0
- cognee/api/v1/config/config.py +60 -0
- cognee/api/v1/datasets/routers/get_datasets_router.py +45 -3
- cognee/api/v1/memify/routers/get_memify_router.py +2 -0
- cognee/api/v1/search/routers/get_search_router.py +21 -6
- cognee/api/v1/search/search.py +25 -5
- cognee/api/v1/sync/routers/get_sync_router.py +3 -3
- cognee/cli/commands/add_command.py +1 -1
- cognee/cli/commands/cognify_command.py +6 -0
- cognee/cli/commands/config_command.py +1 -1
- cognee/context_global_variables.py +5 -1
- cognee/eval_framework/answer_generation/answer_generation_executor.py +7 -8
- cognee/infrastructure/databases/cache/cache_db_interface.py +38 -1
- cognee/infrastructure/databases/cache/config.py +6 -0
- cognee/infrastructure/databases/cache/fscache/FsCacheAdapter.py +21 -0
- cognee/infrastructure/databases/cache/get_cache_engine.py +9 -3
- cognee/infrastructure/databases/cache/redis/RedisAdapter.py +60 -1
- cognee/infrastructure/databases/dataset_database_handler/supported_dataset_database_handlers.py +7 -0
- cognee/infrastructure/databases/graph/get_graph_engine.py +29 -1
- cognee/infrastructure/databases/graph/neo4j_driver/Neo4jAuraDevDatasetDatabaseHandler.py +62 -27
- cognee/infrastructure/databases/hybrid/neptune_analytics/NeptuneAnalyticsAdapter.py +17 -4
- cognee/infrastructure/databases/relational/sqlalchemy/SqlAlchemyAdapter.py +2 -1
- cognee/infrastructure/databases/vector/chromadb/ChromaDBAdapter.py +2 -0
- cognee/infrastructure/databases/vector/config.py +6 -0
- cognee/infrastructure/databases/vector/create_vector_engine.py +69 -22
- cognee/infrastructure/databases/vector/embeddings/LiteLLMEmbeddingEngine.py +64 -9
- cognee/infrastructure/databases/vector/embeddings/OllamaEmbeddingEngine.py +13 -2
- cognee/infrastructure/databases/vector/lancedb/LanceDBAdapter.py +16 -3
- cognee/infrastructure/databases/vector/models/ScoredResult.py +3 -3
- cognee/infrastructure/databases/vector/pgvector/PGVectorAdapter.py +16 -3
- cognee/infrastructure/databases/vector/pgvector/PGVectorDatasetDatabaseHandler.py +86 -0
- cognee/infrastructure/databases/vector/pgvector/create_db_and_tables.py +81 -2
- cognee/infrastructure/databases/vector/vector_db_interface.py +8 -0
- cognee/infrastructure/files/utils/get_data_file_path.py +33 -27
- cognee/infrastructure/llm/prompts/extract_query_time.txt +1 -1
- cognee/infrastructure/llm/prompts/generate_event_entity_prompt.txt +1 -1
- cognee/infrastructure/llm/prompts/generate_event_graph_prompt.txt +1 -1
- cognee/infrastructure/llm/prompts/generate_graph_prompt.txt +2 -2
- cognee/infrastructure/llm/prompts/generate_graph_prompt_guided.txt +1 -1
- cognee/infrastructure/llm/prompts/generate_graph_prompt_oneshot.txt +2 -2
- cognee/infrastructure/llm/prompts/generate_graph_prompt_simple.txt +1 -1
- cognee/infrastructure/llm/prompts/generate_graph_prompt_strict.txt +1 -1
- cognee/infrastructure/llm/prompts/search_type_selector_prompt.txt +6 -6
- cognee/infrastructure/llm/prompts/test.txt +1 -1
- cognee/infrastructure/llm/prompts/translate_content.txt +19 -0
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/get_llm_client.py +24 -0
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/llama_cpp/adapter.py +191 -0
- cognee/modules/chunking/models/DocumentChunk.py +0 -1
- cognee/modules/cognify/config.py +2 -0
- cognee/modules/data/models/Data.py +1 -0
- cognee/modules/engine/models/Entity.py +0 -1
- cognee/modules/engine/operations/setup.py +6 -0
- cognee/modules/graph/cognee_graph/CogneeGraph.py +150 -37
- cognee/modules/graph/cognee_graph/CogneeGraphElements.py +48 -2
- cognee/modules/graph/utils/__init__.py +1 -0
- cognee/modules/graph/utils/get_entity_nodes_from_triplets.py +12 -0
- cognee/modules/notebooks/methods/__init__.py +1 -0
- cognee/modules/notebooks/methods/create_notebook.py +0 -34
- cognee/modules/notebooks/methods/create_tutorial_notebooks.py +191 -0
- cognee/modules/notebooks/methods/get_notebooks.py +12 -8
- cognee/modules/notebooks/tutorials/cognee-basics/cell-1.md +3 -0
- cognee/modules/notebooks/tutorials/cognee-basics/cell-2.md +10 -0
- cognee/modules/notebooks/tutorials/cognee-basics/cell-3.md +7 -0
- cognee/modules/notebooks/tutorials/cognee-basics/cell-4.py +28 -0
- cognee/modules/notebooks/tutorials/cognee-basics/cell-5.py +3 -0
- cognee/modules/notebooks/tutorials/cognee-basics/cell-6.py +9 -0
- cognee/modules/notebooks/tutorials/cognee-basics/cell-7.py +17 -0
- cognee/modules/notebooks/tutorials/cognee-basics/config.json +4 -0
- cognee/modules/notebooks/tutorials/python-development-with-cognee/cell-1.md +3 -0
- cognee/modules/notebooks/tutorials/python-development-with-cognee/cell-10.md +3 -0
- cognee/modules/notebooks/tutorials/python-development-with-cognee/cell-11.md +3 -0
- cognee/modules/notebooks/tutorials/python-development-with-cognee/cell-12.py +3 -0
- cognee/modules/notebooks/tutorials/python-development-with-cognee/cell-13.md +7 -0
- cognee/modules/notebooks/tutorials/python-development-with-cognee/cell-14.py +6 -0
- cognee/modules/notebooks/tutorials/python-development-with-cognee/cell-15.md +3 -0
- cognee/modules/notebooks/tutorials/python-development-with-cognee/cell-16.py +7 -0
- cognee/modules/notebooks/tutorials/python-development-with-cognee/cell-2.md +9 -0
- cognee/modules/notebooks/tutorials/python-development-with-cognee/cell-3.md +7 -0
- cognee/modules/notebooks/tutorials/python-development-with-cognee/cell-4.md +9 -0
- cognee/modules/notebooks/tutorials/python-development-with-cognee/cell-5.md +5 -0
- cognee/modules/notebooks/tutorials/python-development-with-cognee/cell-6.py +13 -0
- cognee/modules/notebooks/tutorials/python-development-with-cognee/cell-7.md +3 -0
- cognee/modules/notebooks/tutorials/python-development-with-cognee/cell-8.md +3 -0
- cognee/modules/notebooks/tutorials/python-development-with-cognee/cell-9.py +31 -0
- cognee/modules/notebooks/tutorials/python-development-with-cognee/config.json +4 -0
- cognee/modules/notebooks/tutorials/python-development-with-cognee/data/copilot_conversations.json +107 -0
- cognee/modules/notebooks/tutorials/python-development-with-cognee/data/guido_contributions.json +976 -0
- cognee/modules/notebooks/tutorials/python-development-with-cognee/data/my_developer_rules.md +79 -0
- cognee/modules/notebooks/tutorials/python-development-with-cognee/data/pep_style_guide.md +74 -0
- cognee/modules/notebooks/tutorials/python-development-with-cognee/data/zen_principles.md +74 -0
- cognee/modules/retrieval/EntityCompletionRetriever.py +51 -38
- cognee/modules/retrieval/__init__.py +0 -1
- cognee/modules/retrieval/base_retriever.py +66 -10
- cognee/modules/retrieval/chunks_retriever.py +57 -49
- cognee/modules/retrieval/coding_rules_retriever.py +12 -5
- cognee/modules/retrieval/completion_retriever.py +29 -28
- cognee/modules/retrieval/cypher_search_retriever.py +25 -20
- cognee/modules/retrieval/graph_completion_context_extension_retriever.py +42 -46
- cognee/modules/retrieval/graph_completion_cot_retriever.py +68 -51
- cognee/modules/retrieval/graph_completion_retriever.py +78 -63
- cognee/modules/retrieval/graph_summary_completion_retriever.py +2 -0
- cognee/modules/retrieval/lexical_retriever.py +34 -12
- cognee/modules/retrieval/natural_language_retriever.py +18 -15
- cognee/modules/retrieval/summaries_retriever.py +51 -34
- cognee/modules/retrieval/temporal_retriever.py +59 -49
- cognee/modules/retrieval/triplet_retriever.py +31 -32
- cognee/modules/retrieval/utils/access_tracking.py +88 -0
- cognee/modules/retrieval/utils/brute_force_triplet_search.py +99 -85
- cognee/modules/retrieval/utils/node_edge_vector_search.py +174 -0
- cognee/modules/search/methods/__init__.py +1 -0
- cognee/modules/search/methods/get_retriever_output.py +53 -0
- cognee/modules/search/methods/get_search_type_retriever_instance.py +252 -0
- cognee/modules/search/methods/search.py +90 -215
- cognee/modules/search/models/SearchResultPayload.py +67 -0
- cognee/modules/search/types/SearchResult.py +1 -8
- cognee/modules/search/types/SearchType.py +1 -2
- cognee/modules/search/types/__init__.py +1 -1
- cognee/modules/search/utils/__init__.py +1 -2
- cognee/modules/search/utils/transform_insights_to_graph.py +2 -2
- cognee/modules/search/utils/{transform_context_to_graph.py → transform_triplets_to_graph.py} +2 -2
- cognee/modules/users/authentication/default/default_transport.py +11 -1
- cognee/modules/users/authentication/get_api_auth_backend.py +2 -1
- cognee/modules/users/authentication/get_client_auth_backend.py +2 -1
- cognee/modules/users/methods/create_user.py +0 -9
- cognee/modules/users/permissions/methods/has_user_management_permission.py +29 -0
- cognee/modules/visualization/cognee_network_visualization.py +1 -1
- cognee/run_migrations.py +48 -0
- cognee/shared/exceptions/__init__.py +1 -3
- cognee/shared/exceptions/exceptions.py +11 -1
- cognee/shared/usage_logger.py +332 -0
- cognee/shared/utils.py +12 -5
- cognee/tasks/chunks/__init__.py +9 -0
- cognee/tasks/cleanup/cleanup_unused_data.py +172 -0
- cognee/tasks/graph/__init__.py +7 -0
- cognee/tasks/memify/__init__.py +8 -0
- cognee/tasks/memify/extract_usage_frequency.py +613 -0
- cognee/tasks/summarization/models.py +0 -2
- cognee/tasks/temporal_graph/__init__.py +0 -1
- cognee/tasks/translation/__init__.py +96 -0
- cognee/tasks/translation/config.py +110 -0
- cognee/tasks/translation/detect_language.py +190 -0
- cognee/tasks/translation/exceptions.py +62 -0
- cognee/tasks/translation/models.py +72 -0
- cognee/tasks/translation/providers/__init__.py +44 -0
- cognee/tasks/translation/providers/azure_provider.py +192 -0
- cognee/tasks/translation/providers/base.py +85 -0
- cognee/tasks/translation/providers/google_provider.py +158 -0
- cognee/tasks/translation/providers/llm_provider.py +143 -0
- cognee/tasks/translation/translate_content.py +282 -0
- cognee/tasks/web_scraper/default_url_crawler.py +6 -2
- cognee/tests/cli_tests/cli_unit_tests/test_cli_commands.py +1 -0
- cognee/tests/cli_tests/cli_unit_tests/test_cli_edge_cases.py +3 -0
- cognee/tests/integration/retrieval/test_brute_force_triplet_search_with_cognify.py +62 -0
- cognee/tests/integration/retrieval/test_chunks_retriever.py +115 -16
- cognee/tests/integration/retrieval/test_graph_completion_retriever.py +13 -5
- cognee/tests/integration/retrieval/test_graph_completion_retriever_context_extension.py +22 -20
- cognee/tests/integration/retrieval/test_graph_completion_retriever_cot.py +23 -24
- cognee/tests/integration/retrieval/test_rag_completion_retriever.py +70 -5
- cognee/tests/integration/retrieval/test_structured_output.py +62 -18
- cognee/tests/integration/retrieval/test_summaries_retriever.py +20 -9
- cognee/tests/integration/retrieval/test_temporal_retriever.py +38 -8
- cognee/tests/integration/retrieval/test_triplet_retriever.py +13 -4
- cognee/tests/integration/shared/test_usage_logger_integration.py +255 -0
- cognee/tests/tasks/translation/README.md +147 -0
- cognee/tests/tasks/translation/__init__.py +1 -0
- cognee/tests/tasks/translation/config_test.py +93 -0
- cognee/tests/tasks/translation/detect_language_test.py +118 -0
- cognee/tests/tasks/translation/providers_test.py +151 -0
- cognee/tests/tasks/translation/translate_content_test.py +213 -0
- cognee/tests/test_chromadb.py +1 -1
- cognee/tests/test_cleanup_unused_data.py +165 -0
- cognee/tests/test_delete_by_id.py +6 -6
- cognee/tests/test_extract_usage_frequency.py +308 -0
- cognee/tests/test_kuzu.py +17 -7
- cognee/tests/test_lancedb.py +3 -1
- cognee/tests/test_library.py +1 -1
- cognee/tests/test_neo4j.py +17 -7
- cognee/tests/test_neptune_analytics_vector.py +3 -1
- cognee/tests/test_permissions.py +172 -187
- cognee/tests/test_pgvector.py +3 -1
- cognee/tests/test_relational_db_migration.py +15 -1
- cognee/tests/test_remote_kuzu.py +3 -1
- cognee/tests/test_s3_file_storage.py +1 -1
- cognee/tests/test_search_db.py +97 -110
- cognee/tests/test_usage_logger_e2e.py +268 -0
- cognee/tests/unit/api/test_get_raw_data_endpoint.py +206 -0
- cognee/tests/unit/eval_framework/answer_generation_test.py +4 -3
- cognee/tests/unit/infrastructure/databases/cache/test_cache_config.py +2 -0
- cognee/tests/unit/modules/graph/cognee_graph_elements_test.py +42 -2
- cognee/tests/unit/modules/graph/cognee_graph_test.py +329 -31
- cognee/tests/unit/modules/retrieval/chunks_retriever_test.py +31 -59
- cognee/tests/unit/modules/retrieval/graph_completion_retriever_context_extension_test.py +70 -33
- cognee/tests/unit/modules/retrieval/graph_completion_retriever_cot_test.py +72 -52
- cognee/tests/unit/modules/retrieval/graph_completion_retriever_test.py +27 -33
- cognee/tests/unit/modules/retrieval/rag_completion_retriever_test.py +28 -15
- cognee/tests/unit/modules/retrieval/summaries_retriever_test.py +37 -42
- cognee/tests/unit/modules/retrieval/temporal_retriever_test.py +48 -64
- cognee/tests/unit/modules/retrieval/test_brute_force_triplet_search.py +263 -24
- cognee/tests/unit/modules/retrieval/test_node_edge_vector_search.py +273 -0
- cognee/tests/unit/modules/retrieval/triplet_retriever_test.py +30 -16
- cognee/tests/unit/modules/search/test_get_search_type_retriever_instance.py +125 -0
- cognee/tests/unit/modules/search/test_search.py +176 -0
- cognee/tests/unit/modules/search/test_search_prepare_search_result_contract.py +190 -0
- cognee/tests/unit/modules/users/test_tutorial_notebook_creation.py +511 -297
- cognee/tests/unit/shared/test_usage_logger.py +241 -0
- cognee/tests/unit/users/permissions/test_has_user_management_permission.py +46 -0
- {cognee-0.5.1.dev0.dist-info → cognee-0.5.2.dist-info}/METADATA +22 -17
- {cognee-0.5.1.dev0.dist-info → cognee-0.5.2.dist-info}/RECORD +235 -147
- cognee/api/.env.example +0 -5
- cognee/modules/retrieval/base_graph_retriever.py +0 -24
- cognee/modules/search/methods/get_search_type_tools.py +0 -223
- cognee/modules/search/methods/no_access_control_search.py +0 -62
- cognee/modules/search/utils/prepare_search_result.py +0 -63
- cognee/tests/test_feedback_enrichment.py +0 -174
- {cognee-0.5.1.dev0.dist-info → cognee-0.5.2.dist-info}/WHEEL +0 -0
- {cognee-0.5.1.dev0.dist-info → cognee-0.5.2.dist-info}/entry_points.txt +0 -0
- {cognee-0.5.1.dev0.dist-info → cognee-0.5.2.dist-info}/licenses/LICENSE +0 -0
- {cognee-0.5.1.dev0.dist-info → cognee-0.5.2.dist-info}/licenses/NOTICE.md +0 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from uuid import UUID
|
|
2
|
+
from typing import Optional, Any, List, Union
|
|
3
|
+
from pydantic import BaseModel, ConfigDict, field_serializer
|
|
4
|
+
from pydantic.alias_generators import to_camel
|
|
5
|
+
from cognee.modules.search.types.SearchType import SearchType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SearchResultPayload(BaseModel):
|
|
9
|
+
"""Result payload from retriever classes."""
|
|
10
|
+
|
|
11
|
+
model_config = ConfigDict(
|
|
12
|
+
arbitrary_types_allowed=True,
|
|
13
|
+
alias_generator=to_camel,
|
|
14
|
+
populate_by_name=True,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
result_object: Any = None
|
|
18
|
+
context: Optional[Union[str, List[str]]] = None
|
|
19
|
+
completion: Optional[Union[str, List[str], List[dict]]] = None
|
|
20
|
+
|
|
21
|
+
# TODO: Add return_type info
|
|
22
|
+
search_type: SearchType
|
|
23
|
+
only_context: bool = False
|
|
24
|
+
|
|
25
|
+
dataset_name: Optional[str] = None
|
|
26
|
+
dataset_id: Optional[UUID] = None
|
|
27
|
+
dataset_tenant_id: Optional[UUID] = None
|
|
28
|
+
|
|
29
|
+
@field_serializer("result_object")
|
|
30
|
+
def serialize_complex_types(self, v: Any):
|
|
31
|
+
"""
|
|
32
|
+
Custom serializer to handle complex types in result_object.
|
|
33
|
+
Transforms non-JSON-compatible types to their string representation.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
# Helper to check if a value is a "simple" JSON-compatible type
|
|
37
|
+
def is_simple(item):
|
|
38
|
+
return isinstance(item, (int, float, dict, str, bool, type(None)))
|
|
39
|
+
|
|
40
|
+
if isinstance(v, list) and all(isinstance(item, dict) for item in v):
|
|
41
|
+
# Handle List of Dictionaries
|
|
42
|
+
return [
|
|
43
|
+
{key: (val if is_simple(val) else str(val)) for key, val in item.items()}
|
|
44
|
+
for item in v
|
|
45
|
+
]
|
|
46
|
+
elif isinstance(v, list):
|
|
47
|
+
# Handle Lists
|
|
48
|
+
return [item if is_simple(item) else str(item) for item in v]
|
|
49
|
+
elif isinstance(v, dict):
|
|
50
|
+
# Handle Dictionaries
|
|
51
|
+
return {key: (val if is_simple(val) else str(val)) for key, val in v.items()}
|
|
52
|
+
else:
|
|
53
|
+
# Fallback for the object itself
|
|
54
|
+
return v if is_simple(v) else str(v)
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def result(self) -> Any:
|
|
58
|
+
"""Function used to determine search_result for users request.
|
|
59
|
+
Return context if only_context is True, else return completion if it exists, else return result_object."""
|
|
60
|
+
if self.only_context:
|
|
61
|
+
return self.context
|
|
62
|
+
elif self.completion:
|
|
63
|
+
return self.completion
|
|
64
|
+
elif self.context:
|
|
65
|
+
return self.context
|
|
66
|
+
else:
|
|
67
|
+
return self.result_object
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from uuid import UUID
|
|
2
2
|
from pydantic import BaseModel
|
|
3
|
-
from typing import Any,
|
|
3
|
+
from typing import Any, Optional
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class SearchResultDataset(BaseModel):
|
|
@@ -8,13 +8,6 @@ class SearchResultDataset(BaseModel):
|
|
|
8
8
|
name: str
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class CombinedSearchResult(BaseModel):
|
|
12
|
-
result: Optional[Any]
|
|
13
|
-
context: Dict[str, Any]
|
|
14
|
-
graphs: Optional[Dict[str, Any]] = {}
|
|
15
|
-
datasets: Optional[List[SearchResultDataset]] = None
|
|
16
|
-
|
|
17
|
-
|
|
18
11
|
class SearchResult(BaseModel):
|
|
19
12
|
search_result: Any
|
|
20
13
|
dataset_id: Optional[UUID]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
class SearchType(Enum):
|
|
4
|
+
class SearchType(str, Enum):
|
|
5
5
|
SUMMARIES = "SUMMARIES"
|
|
6
6
|
CHUNKS = "CHUNKS"
|
|
7
7
|
RAG_COMPLETION = "RAG_COMPLETION"
|
|
@@ -13,7 +13,6 @@ class SearchType(Enum):
|
|
|
13
13
|
GRAPH_COMPLETION_COT = "GRAPH_COMPLETION_COT"
|
|
14
14
|
GRAPH_COMPLETION_CONTEXT_EXTENSION = "GRAPH_COMPLETION_CONTEXT_EXTENSION"
|
|
15
15
|
FEELING_LUCKY = "FEELING_LUCKY"
|
|
16
|
-
FEEDBACK = "FEEDBACK"
|
|
17
16
|
TEMPORAL = "TEMPORAL"
|
|
18
17
|
CODING_RULES = "CODING_RULES"
|
|
19
18
|
CHUNKS_LEXICAL = "CHUNKS_LEXICAL"
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
from .SearchType import SearchType
|
|
2
|
-
from .SearchResult import SearchResult, SearchResultDataset
|
|
2
|
+
from .SearchResult import SearchResult, SearchResultDataset
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
from .
|
|
2
|
-
from .transform_context_to_graph import transform_context_to_graph
|
|
1
|
+
from .transform_triplets_to_graph import transform_triplets_to_graph
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
from typing import Dict, List, Tuple
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
def transform_insights_to_graph(
|
|
4
|
+
def transform_insights_to_graph(input_triplets: List[Tuple[Dict, Dict, Dict]]):
|
|
5
5
|
nodes = {}
|
|
6
6
|
edges = {}
|
|
7
7
|
|
|
8
|
-
for triplet in
|
|
8
|
+
for triplet in input_triplets:
|
|
9
9
|
nodes[triplet[0]["id"]] = {
|
|
10
10
|
"id": triplet[0]["id"],
|
|
11
11
|
"label": triplet[0]["name"] if "name" in triplet[0] else triplet[0]["id"],
|
cognee/modules/search/utils/{transform_context_to_graph.py → transform_triplets_to_graph.py}
RENAMED
|
@@ -3,11 +3,11 @@ from typing import List
|
|
|
3
3
|
from cognee.modules.graph.cognee_graph.CogneeGraphElements import Edge
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
def
|
|
6
|
+
def transform_triplets_to_graph(input_triplets: List[Edge]):
|
|
7
7
|
nodes = {}
|
|
8
8
|
edges = {}
|
|
9
9
|
|
|
10
|
-
for triplet in
|
|
10
|
+
for triplet in input_triplets:
|
|
11
11
|
nodes[triplet.node1.id] = {
|
|
12
12
|
"id": triplet.node1.id,
|
|
13
13
|
"label": triplet.node1.attributes["name"]
|
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from fastapi_users.authentication import CookieTransport
|
|
3
3
|
|
|
4
|
+
# Get cookie domain from environment variable
|
|
5
|
+
# If not set or empty, use None to allow cookie to work on any domain
|
|
6
|
+
cookie_domain = os.getenv("AUTH_TOKEN_COOKIE_DOMAIN")
|
|
7
|
+
if cookie_domain == "":
|
|
8
|
+
cookie_domain = None
|
|
9
|
+
|
|
10
|
+
# Note: Cookie expiration is automatically set by FastAPI Users based on JWT Strategy's lifetime_seconds
|
|
11
|
+
# The JWT Strategy lifetime_seconds is configured in get_client_auth_backend.py
|
|
12
|
+
# and reads from JWT_LIFETIME_SECONDS environment variable
|
|
13
|
+
|
|
4
14
|
default_transport = CookieTransport(
|
|
5
15
|
cookie_name=os.getenv("AUTH_TOKEN_COOKIE_NAME", "auth_token"),
|
|
6
16
|
cookie_secure=False,
|
|
7
17
|
cookie_httponly=True,
|
|
8
18
|
cookie_samesite="Lax",
|
|
9
|
-
cookie_domain=
|
|
19
|
+
cookie_domain=cookie_domain, # None allows cookie to work on any domain
|
|
10
20
|
)
|
|
11
21
|
|
|
12
22
|
default_transport.name = "cookie"
|
|
@@ -16,8 +16,9 @@ def get_api_auth_backend():
|
|
|
16
16
|
|
|
17
17
|
def get_jwt_strategy() -> JWTStrategy[models.UP, models.ID]:
|
|
18
18
|
secret = os.getenv("FASTAPI_USERS_JWT_SECRET", "super_secret")
|
|
19
|
+
lifetime_seconds = int(os.getenv("JWT_LIFETIME_SECONDS", "3600"))
|
|
19
20
|
|
|
20
|
-
return APIJWTStrategy(secret, lifetime_seconds=
|
|
21
|
+
return APIJWTStrategy(secret, lifetime_seconds=lifetime_seconds)
|
|
21
22
|
|
|
22
23
|
auth_backend = AuthenticationBackend(
|
|
23
24
|
name=transport.name,
|
|
@@ -18,8 +18,9 @@ def get_client_auth_backend():
|
|
|
18
18
|
from .default.default_jwt_strategy import DefaultJWTStrategy
|
|
19
19
|
|
|
20
20
|
secret = os.getenv("FASTAPI_USERS_JWT_SECRET", "super_secret")
|
|
21
|
+
lifetime_seconds = int(os.getenv("JWT_LIFETIME_SECONDS", "3600"))
|
|
21
22
|
|
|
22
|
-
return DefaultJWTStrategy(secret, lifetime_seconds=
|
|
23
|
+
return DefaultJWTStrategy(secret, lifetime_seconds=lifetime_seconds)
|
|
23
24
|
|
|
24
25
|
auth_backend = AuthenticationBackend(
|
|
25
26
|
name=transport.name,
|
|
@@ -1,18 +1,9 @@
|
|
|
1
|
-
from uuid import UUID, uuid4
|
|
2
1
|
from fastapi_users.exceptions import UserAlreadyExists
|
|
3
|
-
from sqlalchemy.ext.asyncio import AsyncSession
|
|
4
2
|
|
|
5
3
|
from cognee.infrastructure.databases.relational import get_relational_engine
|
|
6
|
-
from cognee.modules.notebooks.models.Notebook import Notebook
|
|
7
|
-
from cognee.modules.notebooks.methods.create_notebook import _create_tutorial_notebook
|
|
8
|
-
from cognee.modules.users.exceptions import TenantNotFoundError
|
|
9
4
|
from cognee.modules.users.get_user_manager import get_user_manager_context
|
|
10
5
|
from cognee.modules.users.get_user_db import get_user_db_context
|
|
11
6
|
from cognee.modules.users.models.User import UserCreate
|
|
12
|
-
from cognee.modules.users.models.Tenant import Tenant
|
|
13
|
-
|
|
14
|
-
from sqlalchemy import select
|
|
15
|
-
from typing import Optional
|
|
16
7
|
|
|
17
8
|
|
|
18
9
|
async def create_user(
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from uuid import UUID
|
|
2
|
+
|
|
3
|
+
from cognee.modules.users.permissions.methods.get_tenant import get_tenant
|
|
4
|
+
from cognee.modules.users.exceptions import PermissionDeniedError
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
async def has_user_management_permission(requester_id: UUID, tenant_id: UUID) -> bool:
|
|
8
|
+
"""
|
|
9
|
+
Check if requester is allowed to manage users for a tenant.
|
|
10
|
+
Args:
|
|
11
|
+
requester_id: Id of the user making the request
|
|
12
|
+
tenant_id: Id of the tenant
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
True if requester has permission
|
|
16
|
+
|
|
17
|
+
Raises:
|
|
18
|
+
PermissionDeniedError: If requester is not authorized.
|
|
19
|
+
TenantNotFoundError: If the tenant does not exist.
|
|
20
|
+
"""
|
|
21
|
+
tenant = await get_tenant(tenant_id)
|
|
22
|
+
|
|
23
|
+
# TODO: extend to support admin roles
|
|
24
|
+
if tenant.owner_id != requester_id:
|
|
25
|
+
raise PermissionDeniedError(
|
|
26
|
+
message="User is not authorized to manage users for this tenant"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
return True
|
cognee/run_migrations.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import importlib.resources as pkg_resources
|
|
6
|
+
|
|
7
|
+
# Assuming your package is named 'cognee' and the migrations are under 'cognee/alembic'
|
|
8
|
+
# This is a placeholder for the path logic.
|
|
9
|
+
MIGRATIONS_PACKAGE = "cognee"
|
|
10
|
+
MIGRATIONS_DIR_NAME = "alembic"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def run_migrations():
|
|
14
|
+
"""
|
|
15
|
+
Finds the Alembic configuration within the installed package and
|
|
16
|
+
programmatically executes 'alembic upgrade head'.
|
|
17
|
+
"""
|
|
18
|
+
# 1. Locate the base path of the installed package.
|
|
19
|
+
# This reliably finds the root directory of the installed 'cognee' package.
|
|
20
|
+
# We look for the parent of the 'migrations' directory.
|
|
21
|
+
package_root = str(pkg_resources.files(MIGRATIONS_PACKAGE))
|
|
22
|
+
|
|
23
|
+
# 2. Define the paths for config and scripts
|
|
24
|
+
alembic_ini_path = os.path.join(package_root, "alembic.ini")
|
|
25
|
+
script_location_path = os.path.join(package_root, MIGRATIONS_DIR_NAME)
|
|
26
|
+
|
|
27
|
+
if not os.path.exists(alembic_ini_path):
|
|
28
|
+
raise FileNotFoundError(
|
|
29
|
+
f"Error: alembic.ini not found at expected locations for package '{MIGRATIONS_PACKAGE}'."
|
|
30
|
+
)
|
|
31
|
+
if not os.path.exists(script_location_path):
|
|
32
|
+
raise FileNotFoundError(
|
|
33
|
+
f"Error: Migrations directory not found at expected locations for package '{MIGRATIONS_PACKAGE}'."
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
migration_result = subprocess.run(
|
|
37
|
+
["python", "-m", "alembic", "upgrade", "head"],
|
|
38
|
+
capture_output=True,
|
|
39
|
+
text=True,
|
|
40
|
+
cwd=Path(package_root),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if migration_result.returncode != 0:
|
|
44
|
+
migration_output = migration_result.stderr + migration_result.stdout
|
|
45
|
+
print(f"Migration failed with unexpected error: {migration_output}")
|
|
46
|
+
sys.exit(1)
|
|
47
|
+
|
|
48
|
+
print("Migration completed successfully.")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from cognee.exceptions import CogneeValidationError
|
|
1
|
+
from cognee.exceptions import CogneeConfigurationError, CogneeValidationError
|
|
2
2
|
from fastapi import status
|
|
3
3
|
|
|
4
4
|
|
|
@@ -10,3 +10,13 @@ class IngestionError(CogneeValidationError):
|
|
|
10
10
|
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
11
11
|
):
|
|
12
12
|
super().__init__(message, name, status_code)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class UsageLoggerError(CogneeConfigurationError):
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
message: str = "Usage logging configuration is invalid.",
|
|
19
|
+
name: str = "UsageLoggerError",
|
|
20
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
21
|
+
):
|
|
22
|
+
super().__init__(message, name, status_code)
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import inspect
|
|
3
|
+
import os
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from functools import singledispatch, wraps
|
|
6
|
+
from typing import Any, Callable, Optional
|
|
7
|
+
from uuid import UUID
|
|
8
|
+
|
|
9
|
+
from cognee.infrastructure.databases.cache.config import get_cache_config
|
|
10
|
+
from cognee.infrastructure.databases.cache.get_cache_engine import get_cache_engine
|
|
11
|
+
from cognee.shared.exceptions import UsageLoggerError
|
|
12
|
+
from cognee.shared.logging_utils import get_logger
|
|
13
|
+
from cognee import __version__ as cognee_version
|
|
14
|
+
|
|
15
|
+
logger = get_logger("usage_logger")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@singledispatch
|
|
19
|
+
def _sanitize_value(value: Any) -> Any:
|
|
20
|
+
"""Default handler for JSON serialization - converts to string."""
|
|
21
|
+
try:
|
|
22
|
+
str_repr = str(value)
|
|
23
|
+
if str_repr.startswith("<") and str_repr.endswith(">"):
|
|
24
|
+
return f"<cannot be serialized: {type(value).__name__}>"
|
|
25
|
+
return str_repr
|
|
26
|
+
except Exception:
|
|
27
|
+
return f"<cannot be serialized: {type(value).__name__}>"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@_sanitize_value.register(type(None))
|
|
31
|
+
def _(value: None) -> None:
|
|
32
|
+
"""Handle None values - returns None as-is."""
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@_sanitize_value.register(str)
|
|
37
|
+
@_sanitize_value.register(int)
|
|
38
|
+
@_sanitize_value.register(float)
|
|
39
|
+
@_sanitize_value.register(bool)
|
|
40
|
+
def _(value: str | int | float | bool) -> str | int | float | bool:
|
|
41
|
+
"""Handle primitive types - returns value as-is since they're JSON-serializable."""
|
|
42
|
+
return value
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@_sanitize_value.register(UUID)
|
|
46
|
+
def _(value: UUID) -> str:
|
|
47
|
+
"""Convert UUID to string representation."""
|
|
48
|
+
return str(value)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@_sanitize_value.register(datetime)
|
|
52
|
+
def _(value: datetime) -> str:
|
|
53
|
+
"""Convert datetime to ISO format string."""
|
|
54
|
+
return value.isoformat()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@_sanitize_value.register(list)
|
|
58
|
+
@_sanitize_value.register(tuple)
|
|
59
|
+
def _(value: list | tuple) -> list:
|
|
60
|
+
"""Recursively sanitize list or tuple elements."""
|
|
61
|
+
return [_sanitize_value(v) for v in value]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@_sanitize_value.register(dict)
|
|
65
|
+
def _(value: dict) -> dict:
|
|
66
|
+
"""Recursively sanitize dictionary keys and values."""
|
|
67
|
+
sanitized = {}
|
|
68
|
+
for k, v in value.items():
|
|
69
|
+
key_str = k if isinstance(k, str) else _sanitize_dict_key(k)
|
|
70
|
+
sanitized[key_str] = _sanitize_value(v)
|
|
71
|
+
return sanitized
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _sanitize_dict_key(key: Any) -> str:
|
|
75
|
+
"""Convert a non-string dict key to a string."""
|
|
76
|
+
sanitized_key = _sanitize_value(key)
|
|
77
|
+
if isinstance(sanitized_key, str):
|
|
78
|
+
if sanitized_key.startswith("<cannot be serialized"):
|
|
79
|
+
return f"<key:{type(key).__name__}>"
|
|
80
|
+
return sanitized_key
|
|
81
|
+
return str(sanitized_key)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _get_param_names(func: Callable) -> list[str]:
|
|
85
|
+
"""Get parameter names from function signature."""
|
|
86
|
+
try:
|
|
87
|
+
return list(inspect.signature(func).parameters.keys())
|
|
88
|
+
except Exception:
|
|
89
|
+
return []
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _get_param_defaults(func: Callable) -> dict[str, Any]:
|
|
93
|
+
"""Get parameter defaults from function signature."""
|
|
94
|
+
try:
|
|
95
|
+
sig = inspect.signature(func)
|
|
96
|
+
defaults = {}
|
|
97
|
+
for param_name, param in sig.parameters.items():
|
|
98
|
+
if param.default != inspect.Parameter.empty:
|
|
99
|
+
defaults[param_name] = param.default
|
|
100
|
+
return defaults
|
|
101
|
+
except Exception:
|
|
102
|
+
return {}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _extract_user_id(args: tuple, kwargs: dict, param_names: list[str]) -> Optional[str]:
|
|
106
|
+
"""Extract user_id from function arguments if available."""
|
|
107
|
+
try:
|
|
108
|
+
if "user" in kwargs and kwargs["user"] is not None:
|
|
109
|
+
user = kwargs["user"]
|
|
110
|
+
if hasattr(user, "id"):
|
|
111
|
+
return str(user.id)
|
|
112
|
+
|
|
113
|
+
for i, param_name in enumerate(param_names):
|
|
114
|
+
if i < len(args) and param_name == "user":
|
|
115
|
+
user = args[i]
|
|
116
|
+
if user is not None and hasattr(user, "id"):
|
|
117
|
+
return str(user.id)
|
|
118
|
+
return None
|
|
119
|
+
except Exception:
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _extract_parameters(args: tuple, kwargs: dict, param_names: list[str], func: Callable) -> dict:
|
|
124
|
+
"""Extract function parameters - captures all parameters including defaults, sanitizes for JSON."""
|
|
125
|
+
params = {}
|
|
126
|
+
|
|
127
|
+
for key, value in kwargs.items():
|
|
128
|
+
if key != "user":
|
|
129
|
+
params[key] = _sanitize_value(value)
|
|
130
|
+
|
|
131
|
+
if param_names:
|
|
132
|
+
for i, param_name in enumerate(param_names):
|
|
133
|
+
if i < len(args) and param_name != "user" and param_name not in kwargs:
|
|
134
|
+
params[param_name] = _sanitize_value(args[i])
|
|
135
|
+
else:
|
|
136
|
+
for i, arg_value in enumerate(args):
|
|
137
|
+
params[f"arg_{i}"] = _sanitize_value(arg_value)
|
|
138
|
+
|
|
139
|
+
if param_names:
|
|
140
|
+
defaults = _get_param_defaults(func)
|
|
141
|
+
for param_name in param_names:
|
|
142
|
+
if param_name != "user" and param_name not in params and param_name in defaults:
|
|
143
|
+
params[param_name] = _sanitize_value(defaults[param_name])
|
|
144
|
+
|
|
145
|
+
return params
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
async def _log_usage_async(
|
|
149
|
+
function_name: str,
|
|
150
|
+
log_type: str,
|
|
151
|
+
user_id: Optional[str],
|
|
152
|
+
parameters: dict,
|
|
153
|
+
result: Any,
|
|
154
|
+
success: bool,
|
|
155
|
+
error: Optional[str],
|
|
156
|
+
duration_ms: float,
|
|
157
|
+
start_time: datetime,
|
|
158
|
+
end_time: datetime,
|
|
159
|
+
):
|
|
160
|
+
"""Asynchronously log function usage to Redis.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
function_name: Name of the function being logged.
|
|
164
|
+
log_type: Type of log entry (e.g., "api_endpoint", "mcp_tool", "function").
|
|
165
|
+
user_id: User identifier, or None to use "unknown".
|
|
166
|
+
parameters: Dictionary of function parameters (sanitized).
|
|
167
|
+
result: Function return value (will be sanitized).
|
|
168
|
+
success: Whether the function executed successfully.
|
|
169
|
+
error: Error message if function failed, None otherwise.
|
|
170
|
+
duration_ms: Execution duration in milliseconds.
|
|
171
|
+
start_time: Function start timestamp.
|
|
172
|
+
end_time: Function end timestamp.
|
|
173
|
+
|
|
174
|
+
Note:
|
|
175
|
+
This function silently handles errors to avoid disrupting the original
|
|
176
|
+
function execution. Logs are written to Redis with TTL from config.
|
|
177
|
+
"""
|
|
178
|
+
try:
|
|
179
|
+
logger.debug(f"Starting to log usage for {function_name} at {start_time.isoformat()}")
|
|
180
|
+
config = get_cache_config()
|
|
181
|
+
if not config.usage_logging:
|
|
182
|
+
logger.debug("Usage logging disabled, skipping log")
|
|
183
|
+
return
|
|
184
|
+
|
|
185
|
+
logger.debug(f"Getting cache engine for {function_name}")
|
|
186
|
+
cache_engine = get_cache_engine()
|
|
187
|
+
if cache_engine is None:
|
|
188
|
+
logger.warning(
|
|
189
|
+
f"Cache engine not available for usage logging (function: {function_name})"
|
|
190
|
+
)
|
|
191
|
+
return
|
|
192
|
+
|
|
193
|
+
logger.debug(f"Cache engine obtained for {function_name}")
|
|
194
|
+
|
|
195
|
+
if user_id is None:
|
|
196
|
+
user_id = "unknown"
|
|
197
|
+
logger.debug(f"No user_id provided, using 'unknown' for {function_name}")
|
|
198
|
+
|
|
199
|
+
log_entry = {
|
|
200
|
+
"timestamp": start_time.isoformat(),
|
|
201
|
+
"type": log_type,
|
|
202
|
+
"function_name": function_name,
|
|
203
|
+
"user_id": user_id,
|
|
204
|
+
"parameters": parameters,
|
|
205
|
+
"result": _sanitize_value(result),
|
|
206
|
+
"success": success,
|
|
207
|
+
"error": error,
|
|
208
|
+
"duration_ms": round(duration_ms, 2),
|
|
209
|
+
"start_time": start_time.isoformat(),
|
|
210
|
+
"end_time": end_time.isoformat(),
|
|
211
|
+
"metadata": {
|
|
212
|
+
"cognee_version": cognee_version,
|
|
213
|
+
"environment": os.getenv("ENV", "prod"),
|
|
214
|
+
},
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
logger.debug(f"Calling log_usage for {function_name}, user_id={user_id}")
|
|
218
|
+
await cache_engine.log_usage(
|
|
219
|
+
user_id=user_id,
|
|
220
|
+
log_entry=log_entry,
|
|
221
|
+
ttl=config.usage_logging_ttl,
|
|
222
|
+
)
|
|
223
|
+
logger.info(f"Successfully logged usage for {function_name} (user_id={user_id})")
|
|
224
|
+
except Exception as e:
|
|
225
|
+
logger.error(f"Failed to log usage for {function_name}: {str(e)}", exc_info=True)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def log_usage(function_name: Optional[str] = None, log_type: str = "function"):
|
|
229
|
+
"""
|
|
230
|
+
Decorator to log function usage to Redis.
|
|
231
|
+
|
|
232
|
+
This decorator is completely transparent - it doesn't change function behavior.
|
|
233
|
+
It logs function name, parameters, result, timing, and user (if available).
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
function_name: Optional name for the function (defaults to func.__name__)
|
|
237
|
+
log_type: Type of log entry (e.g., "api_endpoint", "mcp_tool")
|
|
238
|
+
|
|
239
|
+
Usage:
|
|
240
|
+
@log_usage(function_name="MCP my_mcp_tool", log_type="mcp_tool")
|
|
241
|
+
async def my_mcp_tool(...):
|
|
242
|
+
# mcp code
|
|
243
|
+
|
|
244
|
+
@log_usage(function_name="POST API /v1/add", log_type="api_endpoint")
|
|
245
|
+
async def add(...):
|
|
246
|
+
# endpoint code
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
def decorator(func: Callable) -> Callable:
|
|
250
|
+
"""Inner decorator that wraps the function with usage logging.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
func: The async function to wrap with usage logging.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
Callable: The wrapped function with usage logging enabled.
|
|
257
|
+
|
|
258
|
+
Raises:
|
|
259
|
+
UsageLoggerError: If the function is not async.
|
|
260
|
+
"""
|
|
261
|
+
if not inspect.iscoroutinefunction(func):
|
|
262
|
+
raise UsageLoggerError(
|
|
263
|
+
f"@log_usage requires an async function. Got {func.__name__} which is not async."
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
@wraps(func)
|
|
267
|
+
async def async_wrapper(*args, **kwargs):
|
|
268
|
+
"""Wrapper function that executes the original function and logs usage.
|
|
269
|
+
|
|
270
|
+
This wrapper:
|
|
271
|
+
- Extracts user ID and parameters from function arguments
|
|
272
|
+
- Executes the original function
|
|
273
|
+
- Captures result, success status, and any errors
|
|
274
|
+
- Logs usage information asynchronously without blocking
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
*args: Positional arguments passed to the original function.
|
|
278
|
+
**kwargs: Keyword arguments passed to the original function.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
Any: The return value of the original function.
|
|
282
|
+
|
|
283
|
+
Raises:
|
|
284
|
+
Any exception raised by the original function (re-raised after logging).
|
|
285
|
+
"""
|
|
286
|
+
config = get_cache_config()
|
|
287
|
+
if not config.usage_logging:
|
|
288
|
+
return await func(*args, **kwargs)
|
|
289
|
+
|
|
290
|
+
start_time = datetime.now(timezone.utc)
|
|
291
|
+
|
|
292
|
+
param_names = _get_param_names(func)
|
|
293
|
+
user_id = _extract_user_id(args, kwargs, param_names)
|
|
294
|
+
parameters = _extract_parameters(args, kwargs, param_names, func)
|
|
295
|
+
|
|
296
|
+
result = None
|
|
297
|
+
success = True
|
|
298
|
+
error = None
|
|
299
|
+
|
|
300
|
+
try:
|
|
301
|
+
result = await func(*args, **kwargs)
|
|
302
|
+
return result
|
|
303
|
+
except Exception as e:
|
|
304
|
+
success = False
|
|
305
|
+
error = str(e)
|
|
306
|
+
raise
|
|
307
|
+
finally:
|
|
308
|
+
end_time = datetime.now(timezone.utc)
|
|
309
|
+
duration_ms = (end_time - start_time).total_seconds() * 1000
|
|
310
|
+
|
|
311
|
+
try:
|
|
312
|
+
await _log_usage_async(
|
|
313
|
+
function_name=function_name or func.__name__,
|
|
314
|
+
log_type=log_type,
|
|
315
|
+
user_id=user_id,
|
|
316
|
+
parameters=parameters,
|
|
317
|
+
result=result,
|
|
318
|
+
success=success,
|
|
319
|
+
error=error,
|
|
320
|
+
duration_ms=duration_ms,
|
|
321
|
+
start_time=start_time,
|
|
322
|
+
end_time=end_time,
|
|
323
|
+
)
|
|
324
|
+
except Exception as e:
|
|
325
|
+
logger.error(
|
|
326
|
+
f"Failed to log usage for {function_name or func.__name__}: {str(e)}",
|
|
327
|
+
exc_info=True,
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
return async_wrapper
|
|
331
|
+
|
|
332
|
+
return decorator
|