aiecs 1.5.1__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.
- aiecs/__init__.py +72 -0
- aiecs/__main__.py +41 -0
- aiecs/aiecs_client.py +469 -0
- aiecs/application/__init__.py +10 -0
- aiecs/application/executors/__init__.py +10 -0
- aiecs/application/executors/operation_executor.py +363 -0
- aiecs/application/knowledge_graph/__init__.py +7 -0
- aiecs/application/knowledge_graph/builder/__init__.py +37 -0
- aiecs/application/knowledge_graph/builder/document_builder.py +375 -0
- aiecs/application/knowledge_graph/builder/graph_builder.py +356 -0
- aiecs/application/knowledge_graph/builder/schema_mapping.py +531 -0
- aiecs/application/knowledge_graph/builder/structured_pipeline.py +443 -0
- aiecs/application/knowledge_graph/builder/text_chunker.py +319 -0
- aiecs/application/knowledge_graph/extractors/__init__.py +27 -0
- aiecs/application/knowledge_graph/extractors/base.py +100 -0
- aiecs/application/knowledge_graph/extractors/llm_entity_extractor.py +327 -0
- aiecs/application/knowledge_graph/extractors/llm_relation_extractor.py +349 -0
- aiecs/application/knowledge_graph/extractors/ner_entity_extractor.py +244 -0
- aiecs/application/knowledge_graph/fusion/__init__.py +23 -0
- aiecs/application/knowledge_graph/fusion/entity_deduplicator.py +387 -0
- aiecs/application/knowledge_graph/fusion/entity_linker.py +343 -0
- aiecs/application/knowledge_graph/fusion/knowledge_fusion.py +580 -0
- aiecs/application/knowledge_graph/fusion/relation_deduplicator.py +189 -0
- aiecs/application/knowledge_graph/pattern_matching/__init__.py +21 -0
- aiecs/application/knowledge_graph/pattern_matching/pattern_matcher.py +344 -0
- aiecs/application/knowledge_graph/pattern_matching/query_executor.py +378 -0
- aiecs/application/knowledge_graph/profiling/__init__.py +12 -0
- aiecs/application/knowledge_graph/profiling/query_plan_visualizer.py +199 -0
- aiecs/application/knowledge_graph/profiling/query_profiler.py +223 -0
- aiecs/application/knowledge_graph/reasoning/__init__.py +27 -0
- aiecs/application/knowledge_graph/reasoning/evidence_synthesis.py +347 -0
- aiecs/application/knowledge_graph/reasoning/inference_engine.py +504 -0
- aiecs/application/knowledge_graph/reasoning/logic_form_parser.py +167 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/__init__.py +79 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_builder.py +513 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_nodes.py +630 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_validator.py +654 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/error_handler.py +477 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/parser.py +390 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/query_context.py +217 -0
- aiecs/application/knowledge_graph/reasoning/logic_query_integration.py +169 -0
- aiecs/application/knowledge_graph/reasoning/query_planner.py +872 -0
- aiecs/application/knowledge_graph/reasoning/reasoning_engine.py +554 -0
- aiecs/application/knowledge_graph/retrieval/__init__.py +19 -0
- aiecs/application/knowledge_graph/retrieval/retrieval_strategies.py +596 -0
- aiecs/application/knowledge_graph/search/__init__.py +59 -0
- aiecs/application/knowledge_graph/search/hybrid_search.py +423 -0
- aiecs/application/knowledge_graph/search/reranker.py +295 -0
- aiecs/application/knowledge_graph/search/reranker_strategies.py +553 -0
- aiecs/application/knowledge_graph/search/text_similarity.py +398 -0
- aiecs/application/knowledge_graph/traversal/__init__.py +15 -0
- aiecs/application/knowledge_graph/traversal/enhanced_traversal.py +329 -0
- aiecs/application/knowledge_graph/traversal/path_scorer.py +269 -0
- aiecs/application/knowledge_graph/validators/__init__.py +13 -0
- aiecs/application/knowledge_graph/validators/relation_validator.py +189 -0
- aiecs/application/knowledge_graph/visualization/__init__.py +11 -0
- aiecs/application/knowledge_graph/visualization/graph_visualizer.py +321 -0
- aiecs/common/__init__.py +9 -0
- aiecs/common/knowledge_graph/__init__.py +17 -0
- aiecs/common/knowledge_graph/runnable.py +484 -0
- aiecs/config/__init__.py +16 -0
- aiecs/config/config.py +498 -0
- aiecs/config/graph_config.py +137 -0
- aiecs/config/registry.py +23 -0
- aiecs/core/__init__.py +46 -0
- aiecs/core/interface/__init__.py +34 -0
- aiecs/core/interface/execution_interface.py +152 -0
- aiecs/core/interface/storage_interface.py +171 -0
- aiecs/domain/__init__.py +289 -0
- aiecs/domain/agent/__init__.py +189 -0
- aiecs/domain/agent/base_agent.py +697 -0
- aiecs/domain/agent/exceptions.py +103 -0
- aiecs/domain/agent/graph_aware_mixin.py +559 -0
- aiecs/domain/agent/hybrid_agent.py +490 -0
- aiecs/domain/agent/integration/__init__.py +26 -0
- aiecs/domain/agent/integration/context_compressor.py +222 -0
- aiecs/domain/agent/integration/context_engine_adapter.py +252 -0
- aiecs/domain/agent/integration/retry_policy.py +219 -0
- aiecs/domain/agent/integration/role_config.py +213 -0
- aiecs/domain/agent/knowledge_aware_agent.py +646 -0
- aiecs/domain/agent/lifecycle.py +296 -0
- aiecs/domain/agent/llm_agent.py +300 -0
- aiecs/domain/agent/memory/__init__.py +12 -0
- aiecs/domain/agent/memory/conversation.py +197 -0
- aiecs/domain/agent/migration/__init__.py +14 -0
- aiecs/domain/agent/migration/conversion.py +160 -0
- aiecs/domain/agent/migration/legacy_wrapper.py +90 -0
- aiecs/domain/agent/models.py +317 -0
- aiecs/domain/agent/observability.py +407 -0
- aiecs/domain/agent/persistence.py +289 -0
- aiecs/domain/agent/prompts/__init__.py +29 -0
- aiecs/domain/agent/prompts/builder.py +161 -0
- aiecs/domain/agent/prompts/formatters.py +189 -0
- aiecs/domain/agent/prompts/template.py +255 -0
- aiecs/domain/agent/registry.py +260 -0
- aiecs/domain/agent/tool_agent.py +257 -0
- aiecs/domain/agent/tools/__init__.py +12 -0
- aiecs/domain/agent/tools/schema_generator.py +221 -0
- aiecs/domain/community/__init__.py +155 -0
- aiecs/domain/community/agent_adapter.py +477 -0
- aiecs/domain/community/analytics.py +481 -0
- aiecs/domain/community/collaborative_workflow.py +642 -0
- aiecs/domain/community/communication_hub.py +645 -0
- aiecs/domain/community/community_builder.py +320 -0
- aiecs/domain/community/community_integration.py +800 -0
- aiecs/domain/community/community_manager.py +813 -0
- aiecs/domain/community/decision_engine.py +879 -0
- aiecs/domain/community/exceptions.py +225 -0
- aiecs/domain/community/models/__init__.py +33 -0
- aiecs/domain/community/models/community_models.py +268 -0
- aiecs/domain/community/resource_manager.py +457 -0
- aiecs/domain/community/shared_context_manager.py +603 -0
- aiecs/domain/context/__init__.py +58 -0
- aiecs/domain/context/context_engine.py +989 -0
- aiecs/domain/context/conversation_models.py +354 -0
- aiecs/domain/context/graph_memory.py +467 -0
- aiecs/domain/execution/__init__.py +12 -0
- aiecs/domain/execution/model.py +57 -0
- aiecs/domain/knowledge_graph/__init__.py +19 -0
- aiecs/domain/knowledge_graph/models/__init__.py +52 -0
- aiecs/domain/knowledge_graph/models/entity.py +130 -0
- aiecs/domain/knowledge_graph/models/evidence.py +194 -0
- aiecs/domain/knowledge_graph/models/inference_rule.py +186 -0
- aiecs/domain/knowledge_graph/models/path.py +179 -0
- aiecs/domain/knowledge_graph/models/path_pattern.py +173 -0
- aiecs/domain/knowledge_graph/models/query.py +272 -0
- aiecs/domain/knowledge_graph/models/query_plan.py +187 -0
- aiecs/domain/knowledge_graph/models/relation.py +136 -0
- aiecs/domain/knowledge_graph/schema/__init__.py +23 -0
- aiecs/domain/knowledge_graph/schema/entity_type.py +135 -0
- aiecs/domain/knowledge_graph/schema/graph_schema.py +271 -0
- aiecs/domain/knowledge_graph/schema/property_schema.py +155 -0
- aiecs/domain/knowledge_graph/schema/relation_type.py +171 -0
- aiecs/domain/knowledge_graph/schema/schema_manager.py +496 -0
- aiecs/domain/knowledge_graph/schema/type_enums.py +205 -0
- aiecs/domain/task/__init__.py +13 -0
- aiecs/domain/task/dsl_processor.py +613 -0
- aiecs/domain/task/model.py +62 -0
- aiecs/domain/task/task_context.py +268 -0
- aiecs/infrastructure/__init__.py +24 -0
- aiecs/infrastructure/graph_storage/__init__.py +11 -0
- aiecs/infrastructure/graph_storage/base.py +601 -0
- aiecs/infrastructure/graph_storage/batch_operations.py +449 -0
- aiecs/infrastructure/graph_storage/cache.py +429 -0
- aiecs/infrastructure/graph_storage/distributed.py +226 -0
- aiecs/infrastructure/graph_storage/error_handling.py +390 -0
- aiecs/infrastructure/graph_storage/graceful_degradation.py +306 -0
- aiecs/infrastructure/graph_storage/health_checks.py +378 -0
- aiecs/infrastructure/graph_storage/in_memory.py +514 -0
- aiecs/infrastructure/graph_storage/index_optimization.py +483 -0
- aiecs/infrastructure/graph_storage/lazy_loading.py +410 -0
- aiecs/infrastructure/graph_storage/metrics.py +357 -0
- aiecs/infrastructure/graph_storage/migration.py +413 -0
- aiecs/infrastructure/graph_storage/pagination.py +471 -0
- aiecs/infrastructure/graph_storage/performance_monitoring.py +466 -0
- aiecs/infrastructure/graph_storage/postgres.py +871 -0
- aiecs/infrastructure/graph_storage/query_optimizer.py +635 -0
- aiecs/infrastructure/graph_storage/schema_cache.py +290 -0
- aiecs/infrastructure/graph_storage/sqlite.py +623 -0
- aiecs/infrastructure/graph_storage/streaming.py +495 -0
- aiecs/infrastructure/messaging/__init__.py +13 -0
- aiecs/infrastructure/messaging/celery_task_manager.py +383 -0
- aiecs/infrastructure/messaging/websocket_manager.py +298 -0
- aiecs/infrastructure/monitoring/__init__.py +34 -0
- aiecs/infrastructure/monitoring/executor_metrics.py +174 -0
- aiecs/infrastructure/monitoring/global_metrics_manager.py +213 -0
- aiecs/infrastructure/monitoring/structured_logger.py +48 -0
- aiecs/infrastructure/monitoring/tracing_manager.py +410 -0
- aiecs/infrastructure/persistence/__init__.py +24 -0
- aiecs/infrastructure/persistence/context_engine_client.py +187 -0
- aiecs/infrastructure/persistence/database_manager.py +333 -0
- aiecs/infrastructure/persistence/file_storage.py +754 -0
- aiecs/infrastructure/persistence/redis_client.py +220 -0
- aiecs/llm/__init__.py +86 -0
- aiecs/llm/callbacks/__init__.py +11 -0
- aiecs/llm/callbacks/custom_callbacks.py +264 -0
- aiecs/llm/client_factory.py +420 -0
- aiecs/llm/clients/__init__.py +33 -0
- aiecs/llm/clients/base_client.py +193 -0
- aiecs/llm/clients/googleai_client.py +181 -0
- aiecs/llm/clients/openai_client.py +131 -0
- aiecs/llm/clients/vertex_client.py +437 -0
- aiecs/llm/clients/xai_client.py +184 -0
- aiecs/llm/config/__init__.py +51 -0
- aiecs/llm/config/config_loader.py +275 -0
- aiecs/llm/config/config_validator.py +236 -0
- aiecs/llm/config/model_config.py +151 -0
- aiecs/llm/utils/__init__.py +10 -0
- aiecs/llm/utils/validate_config.py +91 -0
- aiecs/main.py +363 -0
- aiecs/scripts/__init__.py +3 -0
- aiecs/scripts/aid/VERSION_MANAGEMENT.md +97 -0
- aiecs/scripts/aid/__init__.py +19 -0
- aiecs/scripts/aid/version_manager.py +215 -0
- aiecs/scripts/dependance_check/DEPENDENCY_SYSTEM_SUMMARY.md +242 -0
- aiecs/scripts/dependance_check/README_DEPENDENCY_CHECKER.md +310 -0
- aiecs/scripts/dependance_check/__init__.py +17 -0
- aiecs/scripts/dependance_check/dependency_checker.py +938 -0
- aiecs/scripts/dependance_check/dependency_fixer.py +391 -0
- aiecs/scripts/dependance_check/download_nlp_data.py +396 -0
- aiecs/scripts/dependance_check/quick_dependency_check.py +270 -0
- aiecs/scripts/dependance_check/setup_nlp_data.sh +217 -0
- aiecs/scripts/dependance_patch/__init__.py +7 -0
- aiecs/scripts/dependance_patch/fix_weasel/README_WEASEL_PATCH.md +126 -0
- aiecs/scripts/dependance_patch/fix_weasel/__init__.py +11 -0
- aiecs/scripts/dependance_patch/fix_weasel/fix_weasel_validator.py +128 -0
- aiecs/scripts/dependance_patch/fix_weasel/fix_weasel_validator.sh +82 -0
- aiecs/scripts/dependance_patch/fix_weasel/patch_weasel_library.sh +188 -0
- aiecs/scripts/dependance_patch/fix_weasel/run_weasel_patch.sh +41 -0
- aiecs/scripts/tools_develop/README.md +449 -0
- aiecs/scripts/tools_develop/TOOL_AUTO_DISCOVERY.md +234 -0
- aiecs/scripts/tools_develop/__init__.py +21 -0
- aiecs/scripts/tools_develop/check_type_annotations.py +259 -0
- aiecs/scripts/tools_develop/validate_tool_schemas.py +422 -0
- aiecs/scripts/tools_develop/verify_tools.py +356 -0
- aiecs/tasks/__init__.py +1 -0
- aiecs/tasks/worker.py +172 -0
- aiecs/tools/__init__.py +299 -0
- aiecs/tools/apisource/__init__.py +99 -0
- aiecs/tools/apisource/intelligence/__init__.py +19 -0
- aiecs/tools/apisource/intelligence/data_fusion.py +381 -0
- aiecs/tools/apisource/intelligence/query_analyzer.py +413 -0
- aiecs/tools/apisource/intelligence/search_enhancer.py +388 -0
- aiecs/tools/apisource/monitoring/__init__.py +9 -0
- aiecs/tools/apisource/monitoring/metrics.py +303 -0
- aiecs/tools/apisource/providers/__init__.py +115 -0
- aiecs/tools/apisource/providers/base.py +664 -0
- aiecs/tools/apisource/providers/census.py +401 -0
- aiecs/tools/apisource/providers/fred.py +564 -0
- aiecs/tools/apisource/providers/newsapi.py +412 -0
- aiecs/tools/apisource/providers/worldbank.py +357 -0
- aiecs/tools/apisource/reliability/__init__.py +12 -0
- aiecs/tools/apisource/reliability/error_handler.py +375 -0
- aiecs/tools/apisource/reliability/fallback_strategy.py +391 -0
- aiecs/tools/apisource/tool.py +850 -0
- aiecs/tools/apisource/utils/__init__.py +9 -0
- aiecs/tools/apisource/utils/validators.py +338 -0
- aiecs/tools/base_tool.py +201 -0
- aiecs/tools/docs/__init__.py +121 -0
- aiecs/tools/docs/ai_document_orchestrator.py +599 -0
- aiecs/tools/docs/ai_document_writer_orchestrator.py +2403 -0
- aiecs/tools/docs/content_insertion_tool.py +1333 -0
- aiecs/tools/docs/document_creator_tool.py +1317 -0
- aiecs/tools/docs/document_layout_tool.py +1166 -0
- aiecs/tools/docs/document_parser_tool.py +994 -0
- aiecs/tools/docs/document_writer_tool.py +1818 -0
- aiecs/tools/knowledge_graph/__init__.py +17 -0
- aiecs/tools/knowledge_graph/graph_reasoning_tool.py +734 -0
- aiecs/tools/knowledge_graph/graph_search_tool.py +923 -0
- aiecs/tools/knowledge_graph/kg_builder_tool.py +476 -0
- aiecs/tools/langchain_adapter.py +542 -0
- aiecs/tools/schema_generator.py +275 -0
- aiecs/tools/search_tool/__init__.py +100 -0
- aiecs/tools/search_tool/analyzers.py +589 -0
- aiecs/tools/search_tool/cache.py +260 -0
- aiecs/tools/search_tool/constants.py +128 -0
- aiecs/tools/search_tool/context.py +216 -0
- aiecs/tools/search_tool/core.py +749 -0
- aiecs/tools/search_tool/deduplicator.py +123 -0
- aiecs/tools/search_tool/error_handler.py +271 -0
- aiecs/tools/search_tool/metrics.py +371 -0
- aiecs/tools/search_tool/rate_limiter.py +178 -0
- aiecs/tools/search_tool/schemas.py +277 -0
- aiecs/tools/statistics/__init__.py +80 -0
- aiecs/tools/statistics/ai_data_analysis_orchestrator.py +643 -0
- aiecs/tools/statistics/ai_insight_generator_tool.py +505 -0
- aiecs/tools/statistics/ai_report_orchestrator_tool.py +694 -0
- aiecs/tools/statistics/data_loader_tool.py +564 -0
- aiecs/tools/statistics/data_profiler_tool.py +658 -0
- aiecs/tools/statistics/data_transformer_tool.py +573 -0
- aiecs/tools/statistics/data_visualizer_tool.py +495 -0
- aiecs/tools/statistics/model_trainer_tool.py +487 -0
- aiecs/tools/statistics/statistical_analyzer_tool.py +459 -0
- aiecs/tools/task_tools/__init__.py +86 -0
- aiecs/tools/task_tools/chart_tool.py +732 -0
- aiecs/tools/task_tools/classfire_tool.py +922 -0
- aiecs/tools/task_tools/image_tool.py +447 -0
- aiecs/tools/task_tools/office_tool.py +684 -0
- aiecs/tools/task_tools/pandas_tool.py +635 -0
- aiecs/tools/task_tools/report_tool.py +635 -0
- aiecs/tools/task_tools/research_tool.py +392 -0
- aiecs/tools/task_tools/scraper_tool.py +715 -0
- aiecs/tools/task_tools/stats_tool.py +688 -0
- aiecs/tools/temp_file_manager.py +130 -0
- aiecs/tools/tool_executor/__init__.py +37 -0
- aiecs/tools/tool_executor/tool_executor.py +881 -0
- aiecs/utils/LLM_output_structor.py +445 -0
- aiecs/utils/__init__.py +34 -0
- aiecs/utils/base_callback.py +47 -0
- aiecs/utils/cache_provider.py +695 -0
- aiecs/utils/execution_utils.py +184 -0
- aiecs/utils/logging.py +1 -0
- aiecs/utils/prompt_loader.py +14 -0
- aiecs/utils/token_usage_repository.py +323 -0
- aiecs/ws/__init__.py +0 -0
- aiecs/ws/socket_server.py +52 -0
- aiecs-1.5.1.dist-info/METADATA +608 -0
- aiecs-1.5.1.dist-info/RECORD +302 -0
- aiecs-1.5.1.dist-info/WHEEL +5 -0
- aiecs-1.5.1.dist-info/entry_points.txt +10 -0
- aiecs-1.5.1.dist-info/licenses/LICENSE +225 -0
- aiecs-1.5.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Graph Store Base Interface
|
|
3
|
+
|
|
4
|
+
Two-tier abstract interface for graph storage backends:
|
|
5
|
+
- Tier 1 (Basic): Must implement - core CRUD operations
|
|
6
|
+
- Tier 2 (Advanced): Has defaults, can optimize - complex queries
|
|
7
|
+
|
|
8
|
+
This design allows minimal adapters (Tier 1 only) to work immediately,
|
|
9
|
+
while backends can optimize Tier 2 methods for better performance.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from abc import ABC, abstractmethod
|
|
13
|
+
from typing import List, Optional, Set
|
|
14
|
+
from collections import deque
|
|
15
|
+
|
|
16
|
+
from aiecs.domain.knowledge_graph.models.entity import Entity
|
|
17
|
+
from aiecs.domain.knowledge_graph.models.relation import Relation
|
|
18
|
+
from aiecs.domain.knowledge_graph.models.path import Path
|
|
19
|
+
from aiecs.domain.knowledge_graph.models.query import (
|
|
20
|
+
GraphQuery,
|
|
21
|
+
GraphResult,
|
|
22
|
+
QueryType,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class GraphStore(ABC):
|
|
27
|
+
"""
|
|
28
|
+
Abstract Graph Storage Interface
|
|
29
|
+
|
|
30
|
+
Two-tier design:
|
|
31
|
+
|
|
32
|
+
**Tier 1 - Basic Interface (MUST IMPLEMENT)**:
|
|
33
|
+
- add_entity() - Add entity to graph
|
|
34
|
+
- get_entity() - Get entity by ID
|
|
35
|
+
- add_relation() - Add relation to graph
|
|
36
|
+
- get_relation() - Get relation by ID
|
|
37
|
+
- get_neighbors() - Get neighboring entities
|
|
38
|
+
- initialize() - Initialize storage
|
|
39
|
+
- close() - Close storage connection
|
|
40
|
+
|
|
41
|
+
**Tier 2 - Advanced Interface (HAS DEFAULTS, CAN OPTIMIZE)**:
|
|
42
|
+
- traverse() - Multi-hop graph traversal
|
|
43
|
+
- find_paths() - Find paths between entities
|
|
44
|
+
- subgraph_query() - Extract subgraph
|
|
45
|
+
- vector_search() - Semantic vector search
|
|
46
|
+
- execute_query() - Execute GraphQuery
|
|
47
|
+
|
|
48
|
+
Implementations only need to provide Tier 1 methods. Tier 2 methods
|
|
49
|
+
have default implementations using Tier 1, but can be overridden for
|
|
50
|
+
performance optimization (e.g., using SQL recursive CTEs, Cypher queries).
|
|
51
|
+
|
|
52
|
+
Example:
|
|
53
|
+
```python
|
|
54
|
+
# Minimal implementation (Tier 1 only)
|
|
55
|
+
class CustomGraphStore(GraphStore):
|
|
56
|
+
async def add_entity(self, entity: Entity) -> None:
|
|
57
|
+
# Your implementation
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
# ... implement other Tier 1 methods
|
|
61
|
+
# Tier 2 methods work automatically!
|
|
62
|
+
|
|
63
|
+
# Optimized implementation (override Tier 2)
|
|
64
|
+
class OptimizedGraphStore(CustomGraphStore):
|
|
65
|
+
async def traverse(self, ...):
|
|
66
|
+
# Use database-specific optimization
|
|
67
|
+
pass
|
|
68
|
+
```
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
# =========================================================================
|
|
72
|
+
# TIER 1: BASIC INTERFACE - MUST IMPLEMENT
|
|
73
|
+
# =========================================================================
|
|
74
|
+
|
|
75
|
+
@abstractmethod
|
|
76
|
+
async def initialize(self) -> None:
|
|
77
|
+
"""
|
|
78
|
+
Initialize the graph storage backend
|
|
79
|
+
|
|
80
|
+
Called once before using the store. Use this to:
|
|
81
|
+
- Create database connections
|
|
82
|
+
- Initialize data structures
|
|
83
|
+
- Create tables/indexes
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
@abstractmethod
|
|
87
|
+
async def close(self) -> None:
|
|
88
|
+
"""
|
|
89
|
+
Close the graph storage backend and cleanup resources
|
|
90
|
+
|
|
91
|
+
Called when shutting down. Use this to:
|
|
92
|
+
- Close database connections
|
|
93
|
+
- Flush pending writes
|
|
94
|
+
- Cleanup resources
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
@abstractmethod
|
|
98
|
+
async def add_entity(self, entity: Entity) -> None:
|
|
99
|
+
"""
|
|
100
|
+
Add an entity to the graph
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
entity: Entity to add
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
ValueError: If entity with same ID already exists
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
@abstractmethod
|
|
110
|
+
async def get_entity(self, entity_id: str) -> Optional[Entity]:
|
|
111
|
+
"""
|
|
112
|
+
Get an entity by ID
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
entity_id: Entity ID to retrieve
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Entity if found, None otherwise
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
@abstractmethod
|
|
122
|
+
async def add_relation(self, relation: Relation) -> None:
|
|
123
|
+
"""
|
|
124
|
+
Add a relation to the graph
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
relation: Relation to add
|
|
128
|
+
|
|
129
|
+
Raises:
|
|
130
|
+
ValueError: If relation with same ID already exists
|
|
131
|
+
ValueError: If source or target entity doesn't exist
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
@abstractmethod
|
|
135
|
+
async def get_relation(self, relation_id: str) -> Optional[Relation]:
|
|
136
|
+
"""
|
|
137
|
+
Get a relation by ID
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
relation_id: Relation ID to retrieve
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Relation if found, None otherwise
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
@abstractmethod
|
|
147
|
+
async def get_neighbors(
|
|
148
|
+
self,
|
|
149
|
+
entity_id: str,
|
|
150
|
+
relation_type: Optional[str] = None,
|
|
151
|
+
direction: str = "outgoing",
|
|
152
|
+
) -> List[Entity]:
|
|
153
|
+
"""
|
|
154
|
+
Get neighboring entities connected by relations
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
entity_id: ID of entity to get neighbors for
|
|
158
|
+
relation_type: Optional filter by relation type
|
|
159
|
+
direction: "outgoing", "incoming", or "both"
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
List of neighboring entities
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
# =========================================================================
|
|
166
|
+
# TIER 2: ADVANCED INTERFACE - HAS DEFAULTS (Template Method Pattern)
|
|
167
|
+
# =========================================================================
|
|
168
|
+
|
|
169
|
+
async def traverse(
|
|
170
|
+
self,
|
|
171
|
+
start_entity_id: str,
|
|
172
|
+
relation_type: Optional[str] = None,
|
|
173
|
+
max_depth: int = 3,
|
|
174
|
+
max_results: int = 100,
|
|
175
|
+
) -> List[Path]:
|
|
176
|
+
"""
|
|
177
|
+
Traverse the graph starting from an entity (BFS traversal)
|
|
178
|
+
|
|
179
|
+
**DEFAULT IMPLEMENTATION**: Uses get_neighbors() in BFS pattern.
|
|
180
|
+
Override for better performance (e.g., recursive CTEs in SQL).
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
start_entity_id: Starting entity ID
|
|
184
|
+
relation_type: Optional filter by relation type
|
|
185
|
+
max_depth: Maximum traversal depth
|
|
186
|
+
max_results: Maximum number of paths to return
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
List of paths found during traversal
|
|
190
|
+
"""
|
|
191
|
+
return await self._default_traverse_bfs(
|
|
192
|
+
start_entity_id, relation_type, max_depth, max_results
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
async def find_paths(
|
|
196
|
+
self,
|
|
197
|
+
source_entity_id: str,
|
|
198
|
+
target_entity_id: str,
|
|
199
|
+
max_depth: int = 5,
|
|
200
|
+
max_paths: int = 10,
|
|
201
|
+
) -> List[Path]:
|
|
202
|
+
"""
|
|
203
|
+
Find paths between two entities
|
|
204
|
+
|
|
205
|
+
**DEFAULT IMPLEMENTATION**: Uses traverse() with early stopping.
|
|
206
|
+
Override for better performance (e.g., bidirectional search).
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
source_entity_id: Source entity ID
|
|
210
|
+
target_entity_id: Target entity ID
|
|
211
|
+
max_depth: Maximum path length
|
|
212
|
+
max_paths: Maximum number of paths to return
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
List of paths between source and target
|
|
216
|
+
"""
|
|
217
|
+
return await self._default_find_paths(
|
|
218
|
+
source_entity_id, target_entity_id, max_depth, max_paths
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
async def subgraph_query(
|
|
222
|
+
self, entity_ids: List[str], include_relations: bool = True
|
|
223
|
+
) -> tuple[List[Entity], List[Relation]]:
|
|
224
|
+
"""
|
|
225
|
+
Extract a subgraph containing specified entities
|
|
226
|
+
|
|
227
|
+
**DEFAULT IMPLEMENTATION**: Uses get_entity() and get_neighbors().
|
|
228
|
+
Override for better performance (e.g., single JOIN query).
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
entity_ids: List of entity IDs to include
|
|
232
|
+
include_relations: Whether to include relations between entities
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
Tuple of (entities, relations)
|
|
236
|
+
"""
|
|
237
|
+
return await self._default_subgraph_query(entity_ids, include_relations)
|
|
238
|
+
|
|
239
|
+
async def vector_search(
|
|
240
|
+
self,
|
|
241
|
+
query_embedding: List[float],
|
|
242
|
+
entity_type: Optional[str] = None,
|
|
243
|
+
max_results: int = 10,
|
|
244
|
+
score_threshold: float = 0.0,
|
|
245
|
+
) -> List[tuple[Entity, float]]:
|
|
246
|
+
"""
|
|
247
|
+
Semantic vector search over entities
|
|
248
|
+
|
|
249
|
+
**DEFAULT IMPLEMENTATION**: Brute-force cosine similarity.
|
|
250
|
+
Override for better performance (e.g., pgvector, FAISS, ANN indexes).
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
query_embedding: Query vector embedding
|
|
254
|
+
entity_type: Optional filter by entity type
|
|
255
|
+
max_results: Maximum number of results
|
|
256
|
+
score_threshold: Minimum similarity score (0.0-1.0)
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
List of (entity, score) tuples, sorted by score descending
|
|
260
|
+
"""
|
|
261
|
+
return await self._default_vector_search(
|
|
262
|
+
query_embedding, entity_type, max_results, score_threshold
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
async def text_search(
|
|
266
|
+
self,
|
|
267
|
+
query_text: str,
|
|
268
|
+
entity_type: Optional[str] = None,
|
|
269
|
+
max_results: int = 10,
|
|
270
|
+
score_threshold: float = 0.0,
|
|
271
|
+
method: str = "bm25",
|
|
272
|
+
) -> List[tuple[Entity, float]]:
|
|
273
|
+
"""
|
|
274
|
+
Text-based search over entities using text similarity
|
|
275
|
+
|
|
276
|
+
**DEFAULT IMPLEMENTATION**: Uses text similarity utilities (BM25, Jaccard, etc.).
|
|
277
|
+
Override for better performance (e.g., full-text search indexes).
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
query_text: Query text string
|
|
281
|
+
entity_type: Optional filter by entity type
|
|
282
|
+
max_results: Maximum number of results
|
|
283
|
+
score_threshold: Minimum similarity score (0.0-1.0)
|
|
284
|
+
method: Similarity method ("bm25", "jaccard", "cosine", "levenshtein")
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
List of (entity, score) tuples, sorted by score descending
|
|
288
|
+
"""
|
|
289
|
+
return await self._default_text_search(
|
|
290
|
+
query_text, entity_type, max_results, score_threshold, method
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
async def execute_query(self, query: GraphQuery) -> GraphResult:
|
|
294
|
+
"""
|
|
295
|
+
Execute a graph query
|
|
296
|
+
|
|
297
|
+
**DEFAULT IMPLEMENTATION**: Routes to appropriate methods based on query type.
|
|
298
|
+
Override for custom query execution logic.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
query: Graph query to execute
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
Query results
|
|
305
|
+
"""
|
|
306
|
+
return await self._default_execute_query(query)
|
|
307
|
+
|
|
308
|
+
# =========================================================================
|
|
309
|
+
# DEFAULT IMPLEMENTATIONS (Template Methods)
|
|
310
|
+
# =========================================================================
|
|
311
|
+
|
|
312
|
+
async def _default_traverse_bfs(
|
|
313
|
+
self,
|
|
314
|
+
start_entity_id: str,
|
|
315
|
+
relation_type: Optional[str],
|
|
316
|
+
max_depth: int,
|
|
317
|
+
max_results: int,
|
|
318
|
+
) -> List[Path]:
|
|
319
|
+
"""
|
|
320
|
+
Default BFS traversal implementation using get_neighbors()
|
|
321
|
+
|
|
322
|
+
This provides a working traversal that any Tier 1 implementation gets for free.
|
|
323
|
+
Backends can override traverse() with optimized versions.
|
|
324
|
+
"""
|
|
325
|
+
start_entity = await self.get_entity(start_entity_id)
|
|
326
|
+
if start_entity is None:
|
|
327
|
+
return []
|
|
328
|
+
|
|
329
|
+
paths: List[Path] = []
|
|
330
|
+
visited: Set[str] = set()
|
|
331
|
+
queue: deque = deque([(start_entity, [])]) # (entity, edges_path)
|
|
332
|
+
|
|
333
|
+
while queue and len(paths) < max_results:
|
|
334
|
+
current_entity, edges_path = queue.popleft()
|
|
335
|
+
current_depth = len(edges_path)
|
|
336
|
+
|
|
337
|
+
if current_entity.id in visited:
|
|
338
|
+
continue
|
|
339
|
+
visited.add(current_entity.id)
|
|
340
|
+
|
|
341
|
+
# Create path for this node
|
|
342
|
+
if current_depth > 0: # Don't add single-node paths
|
|
343
|
+
nodes_path = [start_entity]
|
|
344
|
+
for edge in edges_path:
|
|
345
|
+
target_entity = await self.get_entity(edge.target_id)
|
|
346
|
+
if target_entity:
|
|
347
|
+
nodes_path.append(target_entity)
|
|
348
|
+
|
|
349
|
+
if len(nodes_path) == len(edges_path) + 1:
|
|
350
|
+
paths.append(Path(nodes=nodes_path, edges=edges_path))
|
|
351
|
+
|
|
352
|
+
# Explore neighbors if not at max depth
|
|
353
|
+
if current_depth < max_depth:
|
|
354
|
+
neighbors = await self.get_neighbors(
|
|
355
|
+
current_entity.id,
|
|
356
|
+
relation_type=relation_type,
|
|
357
|
+
direction="outgoing",
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
for neighbor in neighbors:
|
|
361
|
+
if neighbor.id not in visited:
|
|
362
|
+
# Find the relation connecting them
|
|
363
|
+
# (In a real implementation, get_neighbors should return relations too)
|
|
364
|
+
# For now, create a placeholder relation
|
|
365
|
+
edge = Relation(
|
|
366
|
+
id=f"rel_{current_entity.id}_{neighbor.id}",
|
|
367
|
+
relation_type=relation_type or "CONNECTED_TO",
|
|
368
|
+
source_id=current_entity.id,
|
|
369
|
+
target_id=neighbor.id,
|
|
370
|
+
)
|
|
371
|
+
queue.append((neighbor, edges_path + [edge]))
|
|
372
|
+
|
|
373
|
+
return paths
|
|
374
|
+
|
|
375
|
+
async def _default_find_paths(
|
|
376
|
+
self,
|
|
377
|
+
source_entity_id: str,
|
|
378
|
+
target_entity_id: str,
|
|
379
|
+
max_depth: int,
|
|
380
|
+
max_paths: int,
|
|
381
|
+
) -> List[Path]:
|
|
382
|
+
"""
|
|
383
|
+
Default path finding using BFS with target check
|
|
384
|
+
"""
|
|
385
|
+
all_paths = await self.traverse(
|
|
386
|
+
source_entity_id,
|
|
387
|
+
max_depth=max_depth,
|
|
388
|
+
max_results=max_paths * 10, # Get more, filter later
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
# Filter paths that end at target
|
|
392
|
+
target_paths = [path for path in all_paths if path.end_entity.id == target_entity_id]
|
|
393
|
+
|
|
394
|
+
return target_paths[:max_paths]
|
|
395
|
+
|
|
396
|
+
async def _default_subgraph_query(
|
|
397
|
+
self, entity_ids: List[str], include_relations: bool
|
|
398
|
+
) -> tuple[List[Entity], List[Relation]]:
|
|
399
|
+
"""
|
|
400
|
+
Default subgraph extraction
|
|
401
|
+
"""
|
|
402
|
+
entities = []
|
|
403
|
+
relations = []
|
|
404
|
+
|
|
405
|
+
# Fetch all entities
|
|
406
|
+
for entity_id in entity_ids:
|
|
407
|
+
entity = await self.get_entity(entity_id)
|
|
408
|
+
if entity:
|
|
409
|
+
entities.append(entity)
|
|
410
|
+
|
|
411
|
+
# Fetch relations between entities (if requested)
|
|
412
|
+
if include_relations:
|
|
413
|
+
entity_id_set = set(entity_ids)
|
|
414
|
+
for entity_id in entity_ids:
|
|
415
|
+
neighbors = await self.get_neighbors(entity_id, direction="outgoing")
|
|
416
|
+
for neighbor in neighbors:
|
|
417
|
+
if neighbor.id in entity_id_set:
|
|
418
|
+
# Fetch the relation (simplified - needs proper
|
|
419
|
+
# implementation)
|
|
420
|
+
rel = Relation(
|
|
421
|
+
id=f"rel_{entity_id}_{neighbor.id}",
|
|
422
|
+
relation_type="CONNECTED_TO",
|
|
423
|
+
source_id=entity_id,
|
|
424
|
+
target_id=neighbor.id,
|
|
425
|
+
)
|
|
426
|
+
relations.append(rel)
|
|
427
|
+
|
|
428
|
+
return entities, relations
|
|
429
|
+
|
|
430
|
+
async def _default_vector_search(
|
|
431
|
+
self,
|
|
432
|
+
query_embedding: List[float],
|
|
433
|
+
entity_type: Optional[str],
|
|
434
|
+
max_results: int,
|
|
435
|
+
score_threshold: float,
|
|
436
|
+
) -> List[tuple[Entity, float]]:
|
|
437
|
+
"""
|
|
438
|
+
Default brute-force vector search using cosine similarity
|
|
439
|
+
|
|
440
|
+
This is slow but works. Backends should override with ANN indexes.
|
|
441
|
+
"""
|
|
442
|
+
# Note: This requires iterating all entities - needs proper implementation
|
|
443
|
+
# In a real store, you'd have a method to get all entities or use an index
|
|
444
|
+
# For now, return empty results
|
|
445
|
+
# TODO: Implement when we have entity enumeration capability
|
|
446
|
+
return []
|
|
447
|
+
|
|
448
|
+
async def _default_text_search(
|
|
449
|
+
self,
|
|
450
|
+
query_text: str,
|
|
451
|
+
entity_type: Optional[str],
|
|
452
|
+
max_results: int,
|
|
453
|
+
score_threshold: float,
|
|
454
|
+
method: str,
|
|
455
|
+
) -> List[tuple[Entity, float]]:
|
|
456
|
+
"""
|
|
457
|
+
Default text search using text similarity utilities
|
|
458
|
+
|
|
459
|
+
This implementation requires get_all_entities() or similar method.
|
|
460
|
+
Backends should override for better performance (e.g., full-text indexes).
|
|
461
|
+
"""
|
|
462
|
+
# Try to get all entities - check if store has get_all_entities method
|
|
463
|
+
if hasattr(self, "get_all_entities"):
|
|
464
|
+
entities = await self.get_all_entities(entity_type=entity_type)
|
|
465
|
+
else:
|
|
466
|
+
# Fallback: return empty if no way to enumerate entities
|
|
467
|
+
return []
|
|
468
|
+
|
|
469
|
+
if not query_text:
|
|
470
|
+
return []
|
|
471
|
+
|
|
472
|
+
from aiecs.application.knowledge_graph.search.text_similarity import (
|
|
473
|
+
BM25Scorer,
|
|
474
|
+
jaccard_similarity_text,
|
|
475
|
+
cosine_similarity_text,
|
|
476
|
+
normalized_levenshtein_similarity,
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
scored_entities = []
|
|
480
|
+
|
|
481
|
+
# Extract text from entities (combine properties into searchable text)
|
|
482
|
+
entity_texts = []
|
|
483
|
+
for entity in entities:
|
|
484
|
+
# Combine all string properties into searchable text
|
|
485
|
+
text_parts = []
|
|
486
|
+
for key, value in entity.properties.items():
|
|
487
|
+
if isinstance(value, str):
|
|
488
|
+
text_parts.append(value)
|
|
489
|
+
elif isinstance(value, (list, tuple)):
|
|
490
|
+
text_parts.extend(str(v) for v in value if isinstance(v, str))
|
|
491
|
+
entity_text = " ".join(text_parts)
|
|
492
|
+
entity_texts.append((entity, entity_text))
|
|
493
|
+
|
|
494
|
+
if method == "bm25":
|
|
495
|
+
# Use BM25 scorer
|
|
496
|
+
corpus = [text for _, text in entity_texts]
|
|
497
|
+
scorer = BM25Scorer(corpus)
|
|
498
|
+
scores = scorer.score(query_text)
|
|
499
|
+
|
|
500
|
+
for (entity, _), score in zip(entity_texts, scores):
|
|
501
|
+
if score >= score_threshold:
|
|
502
|
+
scored_entities.append((entity, float(score)))
|
|
503
|
+
|
|
504
|
+
elif method == "jaccard":
|
|
505
|
+
for entity, text in entity_texts:
|
|
506
|
+
score = jaccard_similarity_text(query_text, text)
|
|
507
|
+
if score >= score_threshold:
|
|
508
|
+
scored_entities.append((entity, score))
|
|
509
|
+
|
|
510
|
+
elif method == "cosine":
|
|
511
|
+
for entity, text in entity_texts:
|
|
512
|
+
score = cosine_similarity_text(query_text, text)
|
|
513
|
+
if score >= score_threshold:
|
|
514
|
+
scored_entities.append((entity, score))
|
|
515
|
+
|
|
516
|
+
elif method == "levenshtein":
|
|
517
|
+
for entity, text in entity_texts:
|
|
518
|
+
score = normalized_levenshtein_similarity(query_text, text)
|
|
519
|
+
if score >= score_threshold:
|
|
520
|
+
scored_entities.append((entity, score))
|
|
521
|
+
|
|
522
|
+
else:
|
|
523
|
+
raise ValueError(
|
|
524
|
+
f"Unknown text search method: {method}. Use 'bm25', 'jaccard', 'cosine', or 'levenshtein'"
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
# Sort by score descending and return top results
|
|
528
|
+
scored_entities.sort(key=lambda x: x[1], reverse=True)
|
|
529
|
+
return scored_entities[:max_results]
|
|
530
|
+
|
|
531
|
+
async def _default_execute_query(self, query: GraphQuery) -> GraphResult:
|
|
532
|
+
"""
|
|
533
|
+
Default query execution router
|
|
534
|
+
"""
|
|
535
|
+
import time
|
|
536
|
+
|
|
537
|
+
start_time = time.time()
|
|
538
|
+
|
|
539
|
+
if query.query_type == QueryType.ENTITY_LOOKUP:
|
|
540
|
+
entity = await self.get_entity(query.entity_id) if query.entity_id else None
|
|
541
|
+
entities = [entity] if entity else []
|
|
542
|
+
|
|
543
|
+
elif query.query_type == QueryType.VECTOR_SEARCH:
|
|
544
|
+
if query.embedding:
|
|
545
|
+
results = await self.vector_search(
|
|
546
|
+
query.embedding,
|
|
547
|
+
query.entity_type,
|
|
548
|
+
query.max_results,
|
|
549
|
+
query.score_threshold,
|
|
550
|
+
)
|
|
551
|
+
entities = [entity for entity, score in results]
|
|
552
|
+
else:
|
|
553
|
+
entities = []
|
|
554
|
+
|
|
555
|
+
elif query.query_type == QueryType.TRAVERSAL:
|
|
556
|
+
if query.entity_id:
|
|
557
|
+
paths = await self.traverse(
|
|
558
|
+
query.entity_id,
|
|
559
|
+
query.relation_type,
|
|
560
|
+
query.max_depth,
|
|
561
|
+
query.max_results,
|
|
562
|
+
)
|
|
563
|
+
# Extract unique entities from paths
|
|
564
|
+
entity_ids_seen = set()
|
|
565
|
+
entities = []
|
|
566
|
+
for path in paths:
|
|
567
|
+
for entity in path.nodes:
|
|
568
|
+
if entity.id not in entity_ids_seen:
|
|
569
|
+
entities.append(entity)
|
|
570
|
+
entity_ids_seen.add(entity.id)
|
|
571
|
+
else:
|
|
572
|
+
entities = []
|
|
573
|
+
paths = []
|
|
574
|
+
|
|
575
|
+
elif query.query_type == QueryType.PATH_FINDING:
|
|
576
|
+
if query.source_entity_id and query.target_entity_id:
|
|
577
|
+
paths = await self.find_paths(
|
|
578
|
+
query.source_entity_id,
|
|
579
|
+
query.target_entity_id,
|
|
580
|
+
query.max_depth,
|
|
581
|
+
query.max_results,
|
|
582
|
+
)
|
|
583
|
+
entities = []
|
|
584
|
+
else:
|
|
585
|
+
paths = []
|
|
586
|
+
entities = []
|
|
587
|
+
|
|
588
|
+
else:
|
|
589
|
+
entities = []
|
|
590
|
+
paths = []
|
|
591
|
+
|
|
592
|
+
execution_time_ms = (time.time() - start_time) * 1000
|
|
593
|
+
|
|
594
|
+
return GraphResult(
|
|
595
|
+
query=query,
|
|
596
|
+
entities=entities[: query.max_results],
|
|
597
|
+
paths=paths[: query.max_results] if "paths" in locals() else [],
|
|
598
|
+
scores=[],
|
|
599
|
+
total_count=len(entities),
|
|
600
|
+
execution_time_ms=execution_time_ms,
|
|
601
|
+
)
|