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,329 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Enhanced Graph Traversal
|
|
3
|
+
|
|
4
|
+
Provides advanced traversal capabilities with PathPattern support,
|
|
5
|
+
cycle detection, and sophisticated path filtering.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import List, Optional
|
|
9
|
+
from collections import deque
|
|
10
|
+
from aiecs.domain.knowledge_graph.models.relation import Relation
|
|
11
|
+
from aiecs.domain.knowledge_graph.models.path import Path
|
|
12
|
+
from aiecs.domain.knowledge_graph.models.path_pattern import (
|
|
13
|
+
PathPattern,
|
|
14
|
+
TraversalDirection,
|
|
15
|
+
)
|
|
16
|
+
from aiecs.infrastructure.graph_storage.base import GraphStore
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class EnhancedTraversal:
|
|
20
|
+
"""
|
|
21
|
+
Enhanced Graph Traversal Service
|
|
22
|
+
|
|
23
|
+
Provides advanced traversal capabilities beyond basic BFS:
|
|
24
|
+
- PathPattern-based traversal
|
|
25
|
+
- Cycle detection and handling
|
|
26
|
+
- Depth-limited traversal with constraints
|
|
27
|
+
- Path filtering by pattern
|
|
28
|
+
|
|
29
|
+
Example:
|
|
30
|
+
```python
|
|
31
|
+
traversal = EnhancedTraversal(graph_store)
|
|
32
|
+
|
|
33
|
+
# Define pattern
|
|
34
|
+
pattern = PathPattern(
|
|
35
|
+
relation_types=["WORKS_FOR", "LOCATED_IN"],
|
|
36
|
+
max_depth=2,
|
|
37
|
+
allow_cycles=False
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Traverse with pattern
|
|
41
|
+
paths = await traversal.traverse_with_pattern(
|
|
42
|
+
start_entity_id="person_1",
|
|
43
|
+
pattern=pattern,
|
|
44
|
+
max_results=10
|
|
45
|
+
)
|
|
46
|
+
```
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, graph_store: GraphStore):
|
|
50
|
+
"""
|
|
51
|
+
Initialize enhanced traversal service
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
graph_store: Graph storage backend to use
|
|
55
|
+
"""
|
|
56
|
+
self.graph_store = graph_store
|
|
57
|
+
|
|
58
|
+
async def traverse_with_pattern(
|
|
59
|
+
self,
|
|
60
|
+
start_entity_id: str,
|
|
61
|
+
pattern: PathPattern,
|
|
62
|
+
max_results: int = 100,
|
|
63
|
+
) -> List[Path]:
|
|
64
|
+
"""
|
|
65
|
+
Traverse graph following a path pattern
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
start_entity_id: Starting entity ID
|
|
69
|
+
pattern: Path pattern to follow
|
|
70
|
+
max_results: Maximum number of paths to return
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
List of paths matching the pattern
|
|
74
|
+
"""
|
|
75
|
+
start_entity = await self.graph_store.get_entity(start_entity_id)
|
|
76
|
+
if start_entity is None:
|
|
77
|
+
return []
|
|
78
|
+
|
|
79
|
+
# Check if start entity is allowed
|
|
80
|
+
if not pattern.is_entity_allowed(start_entity.id, start_entity.entity_type):
|
|
81
|
+
return []
|
|
82
|
+
|
|
83
|
+
paths: List[Path] = []
|
|
84
|
+
# visited_in_path: Set[str] = set() if not pattern.allow_cycles else
|
|
85
|
+
# None # Reserved for future use
|
|
86
|
+
|
|
87
|
+
# BFS with pattern matching
|
|
88
|
+
queue: deque = deque()
|
|
89
|
+
queue.append(
|
|
90
|
+
{
|
|
91
|
+
"entity": start_entity,
|
|
92
|
+
"path_entities": [start_entity],
|
|
93
|
+
"path_edges": [],
|
|
94
|
+
"depth": 0,
|
|
95
|
+
"visited": ({start_entity.id} if not pattern.allow_cycles else set()),
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
while queue and len(paths) < max_results:
|
|
100
|
+
current = queue.popleft()
|
|
101
|
+
current_entity = current["entity"]
|
|
102
|
+
current_depth = current["depth"]
|
|
103
|
+
path_entities = current["path_entities"]
|
|
104
|
+
path_edges = current["path_edges"]
|
|
105
|
+
visited_nodes = current["visited"]
|
|
106
|
+
|
|
107
|
+
# Add path if it meets length requirements
|
|
108
|
+
if pattern.is_valid_path_length(len(path_edges)):
|
|
109
|
+
path = Path(nodes=path_entities, edges=path_edges)
|
|
110
|
+
paths.append(path)
|
|
111
|
+
|
|
112
|
+
# Continue traversal if not at max depth
|
|
113
|
+
if not pattern.should_continue_traversal(current_depth):
|
|
114
|
+
continue
|
|
115
|
+
|
|
116
|
+
# Get neighbors based on pattern direction
|
|
117
|
+
# pattern.direction is already a string due to use_enum_values=True
|
|
118
|
+
direction_str = (
|
|
119
|
+
pattern.direction if isinstance(pattern.direction, str) else pattern.direction.value
|
|
120
|
+
)
|
|
121
|
+
neighbors = await self.graph_store.get_neighbors(
|
|
122
|
+
current_entity.id,
|
|
123
|
+
relation_type=None, # We'll filter by pattern
|
|
124
|
+
direction=direction_str,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
for neighbor in neighbors:
|
|
128
|
+
# Check if entity is allowed
|
|
129
|
+
if not pattern.is_entity_allowed(neighbor.id, neighbor.entity_type):
|
|
130
|
+
continue
|
|
131
|
+
|
|
132
|
+
# Check for cycles
|
|
133
|
+
if not pattern.allow_cycles and neighbor.id in visited_nodes:
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
# Get the relation between current and neighbor
|
|
137
|
+
# We need to find the actual relation
|
|
138
|
+
relation = await self._find_relation(
|
|
139
|
+
current_entity.id, neighbor.id, pattern.direction
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
if relation is None:
|
|
143
|
+
continue
|
|
144
|
+
|
|
145
|
+
# Check if relation is allowed at this depth
|
|
146
|
+
if not pattern.is_relation_allowed(relation.relation_type, current_depth):
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
# For incoming direction, we need to reverse the relation for path construction
|
|
150
|
+
# because paths expect edges[i].source_id == nodes[i].id
|
|
151
|
+
direction_str = (
|
|
152
|
+
pattern.direction
|
|
153
|
+
if isinstance(pattern.direction, str)
|
|
154
|
+
else pattern.direction.value
|
|
155
|
+
)
|
|
156
|
+
if direction_str == "incoming":
|
|
157
|
+
# Reverse the relation: if we have e1->e2 and we're going from e2 to e1,
|
|
158
|
+
# the path needs e2->e1 (source=current, target=neighbor)
|
|
159
|
+
path_relation = Relation(
|
|
160
|
+
id=f"{relation.id}_reversed",
|
|
161
|
+
relation_type=relation.relation_type,
|
|
162
|
+
source_id=current_entity.id,
|
|
163
|
+
target_id=neighbor.id,
|
|
164
|
+
weight=relation.weight,
|
|
165
|
+
)
|
|
166
|
+
else:
|
|
167
|
+
path_relation = relation
|
|
168
|
+
|
|
169
|
+
# Create new path state
|
|
170
|
+
new_path_entities = path_entities + [neighbor]
|
|
171
|
+
new_path_edges = path_edges + [path_relation]
|
|
172
|
+
new_visited = (
|
|
173
|
+
visited_nodes | {neighbor.id} if not pattern.allow_cycles else visited_nodes
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
queue.append(
|
|
177
|
+
{
|
|
178
|
+
"entity": neighbor,
|
|
179
|
+
"path_entities": new_path_entities,
|
|
180
|
+
"path_edges": new_path_edges,
|
|
181
|
+
"depth": current_depth + 1,
|
|
182
|
+
"visited": new_visited,
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
return paths
|
|
187
|
+
|
|
188
|
+
async def _find_relation(
|
|
189
|
+
self, source_id: str, target_id: str, direction: TraversalDirection
|
|
190
|
+
) -> Optional[Relation]:
|
|
191
|
+
"""
|
|
192
|
+
Find the relation between two entities
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
source_id: Source entity ID
|
|
196
|
+
target_id: Target entity ID
|
|
197
|
+
direction: Traversal direction (can be enum or string)
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
Relation if found, None otherwise
|
|
201
|
+
"""
|
|
202
|
+
# Try to find the actual relation in the graph store
|
|
203
|
+
# This works with both InMemoryGraphStore and SQLiteGraphStore
|
|
204
|
+
|
|
205
|
+
# Handle both enum and string directions
|
|
206
|
+
direction_str = direction if isinstance(direction, str) else direction.value
|
|
207
|
+
direction_enum = (
|
|
208
|
+
TraversalDirection(direction_str) if isinstance(direction, str) else direction
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
if (
|
|
212
|
+
direction_enum == TraversalDirection.OUTGOING
|
|
213
|
+
or direction_enum == TraversalDirection.BOTH
|
|
214
|
+
):
|
|
215
|
+
# Look for outgoing relations from source
|
|
216
|
+
neighbors = await self.graph_store.get_neighbors(
|
|
217
|
+
source_id, relation_type=None, direction="outgoing"
|
|
218
|
+
)
|
|
219
|
+
for neighbor in neighbors:
|
|
220
|
+
if neighbor.id == target_id:
|
|
221
|
+
# Found the neighbor, now get the relation
|
|
222
|
+
# This is a workaround - ideally get_neighbors would return relations too
|
|
223
|
+
# For now, check if the store exposes relations
|
|
224
|
+
from aiecs.infrastructure.graph_storage.in_memory import (
|
|
225
|
+
InMemoryGraphStore,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
if isinstance(self.graph_store, InMemoryGraphStore):
|
|
229
|
+
for rel in self.graph_store.relations.values():
|
|
230
|
+
if rel.source_id == source_id and rel.target_id == target_id:
|
|
231
|
+
return rel
|
|
232
|
+
else:
|
|
233
|
+
# For SQLite or other stores, try to get the relation
|
|
234
|
+
# This is a placeholder - real implementation would
|
|
235
|
+
# query the DB
|
|
236
|
+
return Relation(
|
|
237
|
+
id=f"rel_{source_id}_{target_id}",
|
|
238
|
+
relation_type="CONNECTED_TO",
|
|
239
|
+
source_id=source_id,
|
|
240
|
+
target_id=target_id,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
if (
|
|
244
|
+
direction_enum == TraversalDirection.INCOMING
|
|
245
|
+
or direction_enum == TraversalDirection.BOTH
|
|
246
|
+
):
|
|
247
|
+
# Look for incoming relations to source (i.e., outgoing from
|
|
248
|
+
# target)
|
|
249
|
+
neighbors = await self.graph_store.get_neighbors(
|
|
250
|
+
target_id, relation_type=None, direction="outgoing"
|
|
251
|
+
)
|
|
252
|
+
for neighbor in neighbors:
|
|
253
|
+
if neighbor.id == source_id:
|
|
254
|
+
from aiecs.infrastructure.graph_storage.in_memory import (
|
|
255
|
+
InMemoryGraphStore,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
if isinstance(self.graph_store, InMemoryGraphStore):
|
|
259
|
+
for rel in self.graph_store.relations.values():
|
|
260
|
+
if rel.source_id == target_id and rel.target_id == source_id:
|
|
261
|
+
return rel
|
|
262
|
+
else:
|
|
263
|
+
return Relation(
|
|
264
|
+
id=f"rel_{target_id}_{source_id}",
|
|
265
|
+
relation_type="CONNECTED_TO",
|
|
266
|
+
source_id=target_id,
|
|
267
|
+
target_id=source_id,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
return None
|
|
271
|
+
|
|
272
|
+
def detect_cycles(self, path: Path) -> bool:
|
|
273
|
+
"""
|
|
274
|
+
Detect if a path contains cycles (repeated nodes)
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
path: Path to check
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
True if path contains cycles
|
|
281
|
+
"""
|
|
282
|
+
entity_ids = path.get_entity_ids()
|
|
283
|
+
return len(entity_ids) != len(set(entity_ids))
|
|
284
|
+
|
|
285
|
+
def filter_paths_without_cycles(self, paths: List[Path]) -> List[Path]:
|
|
286
|
+
"""
|
|
287
|
+
Filter out paths that contain cycles
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
paths: List of paths to filter
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
List of paths without cycles
|
|
294
|
+
"""
|
|
295
|
+
return [path for path in paths if not self.detect_cycles(path)]
|
|
296
|
+
|
|
297
|
+
async def find_all_paths_between(
|
|
298
|
+
self,
|
|
299
|
+
source_id: str,
|
|
300
|
+
target_id: str,
|
|
301
|
+
pattern: Optional[PathPattern] = None,
|
|
302
|
+
max_paths: int = 10,
|
|
303
|
+
) -> List[Path]:
|
|
304
|
+
"""
|
|
305
|
+
Find all paths between two entities matching a pattern
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
source_id: Source entity ID
|
|
309
|
+
target_id: Target entity ID
|
|
310
|
+
pattern: Optional path pattern to follow
|
|
311
|
+
max_paths: Maximum number of paths to return
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
List of paths from source to target
|
|
315
|
+
"""
|
|
316
|
+
if pattern is None:
|
|
317
|
+
pattern = PathPattern(max_depth=5, allow_cycles=False)
|
|
318
|
+
|
|
319
|
+
# Traverse from source
|
|
320
|
+
all_paths = await self.traverse_with_pattern(
|
|
321
|
+
start_entity_id=source_id,
|
|
322
|
+
pattern=pattern,
|
|
323
|
+
max_results=max_paths * 10, # Get more paths for filtering
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
# Filter paths that end at target
|
|
327
|
+
target_paths = [path for path in all_paths if path.end_entity.id == target_id]
|
|
328
|
+
|
|
329
|
+
return target_paths[:max_paths]
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Path Scoring and Ranking
|
|
3
|
+
|
|
4
|
+
Provides utilities for scoring and ranking graph paths based on various criteria.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import List, Callable, Optional
|
|
8
|
+
from aiecs.domain.knowledge_graph.models.path import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PathScorer:
|
|
12
|
+
"""
|
|
13
|
+
Path Scoring Utility
|
|
14
|
+
|
|
15
|
+
Scores and ranks paths based on configurable criteria:
|
|
16
|
+
- Path length (shorter/longer)
|
|
17
|
+
- Relation types (preferred types)
|
|
18
|
+
- Entity types (preferred types)
|
|
19
|
+
- Relation weights (accumulated)
|
|
20
|
+
- Custom scoring functions
|
|
21
|
+
|
|
22
|
+
Example:
|
|
23
|
+
```python
|
|
24
|
+
scorer = PathScorer()
|
|
25
|
+
|
|
26
|
+
# Score by path length (shorter is better)
|
|
27
|
+
scored_paths = scorer.score_by_length(paths, prefer_shorter=True)
|
|
28
|
+
|
|
29
|
+
# Score by relation weights
|
|
30
|
+
scored_paths = scorer.score_by_weights(paths)
|
|
31
|
+
|
|
32
|
+
# Custom scoring
|
|
33
|
+
scored_paths = scorer.score_custom(paths, lambda p: compute_score(p))
|
|
34
|
+
|
|
35
|
+
# Rank and return top paths
|
|
36
|
+
top_paths = scorer.rank_paths(scored_paths, top_k=10)
|
|
37
|
+
```
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def score_by_length(
|
|
41
|
+
self,
|
|
42
|
+
paths: List[Path],
|
|
43
|
+
prefer_shorter: bool = True,
|
|
44
|
+
normalize: bool = True,
|
|
45
|
+
) -> List[Path]:
|
|
46
|
+
"""
|
|
47
|
+
Score paths by their length
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
paths: List of paths to score
|
|
51
|
+
prefer_shorter: If True, shorter paths get higher scores
|
|
52
|
+
normalize: If True, normalize scores to 0-1 range
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
List of paths with scores assigned
|
|
56
|
+
"""
|
|
57
|
+
if not paths:
|
|
58
|
+
return []
|
|
59
|
+
|
|
60
|
+
# Get length range
|
|
61
|
+
lengths = [p.length for p in paths]
|
|
62
|
+
min_len = min(lengths)
|
|
63
|
+
max_len = max(lengths)
|
|
64
|
+
|
|
65
|
+
scored_paths = []
|
|
66
|
+
for path in paths:
|
|
67
|
+
if max_len == min_len:
|
|
68
|
+
# All paths same length
|
|
69
|
+
score = 1.0
|
|
70
|
+
elif prefer_shorter:
|
|
71
|
+
# Shorter paths get higher scores
|
|
72
|
+
if normalize:
|
|
73
|
+
score = 1.0 - ((path.length - min_len) / (max_len - min_len))
|
|
74
|
+
else:
|
|
75
|
+
score = 1.0 / (path.length + 1)
|
|
76
|
+
else:
|
|
77
|
+
# Longer paths get higher scores
|
|
78
|
+
if normalize:
|
|
79
|
+
score = (path.length - min_len) / (max_len - min_len)
|
|
80
|
+
else:
|
|
81
|
+
score = path.length / (max_len + 1)
|
|
82
|
+
|
|
83
|
+
# Create new path with score
|
|
84
|
+
scored_path = Path(nodes=path.nodes, edges=path.edges, score=score)
|
|
85
|
+
scored_paths.append(scored_path)
|
|
86
|
+
|
|
87
|
+
return scored_paths
|
|
88
|
+
|
|
89
|
+
def score_by_weights(self, paths: List[Path], aggregation: str = "mean") -> List[Path]:
|
|
90
|
+
"""
|
|
91
|
+
Score paths by relation weights
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
paths: List of paths to score
|
|
95
|
+
aggregation: How to aggregate weights ("mean", "sum", "min", "max")
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
List of paths with scores based on relation weights
|
|
99
|
+
"""
|
|
100
|
+
scored_paths = []
|
|
101
|
+
|
|
102
|
+
for path in paths:
|
|
103
|
+
if not path.edges:
|
|
104
|
+
score = 1.0
|
|
105
|
+
else:
|
|
106
|
+
weights = [edge.weight for edge in path.edges]
|
|
107
|
+
|
|
108
|
+
if aggregation == "mean":
|
|
109
|
+
score = sum(weights) / len(weights)
|
|
110
|
+
elif aggregation == "sum":
|
|
111
|
+
score = sum(weights) / len(weights) # Normalized by length
|
|
112
|
+
elif aggregation == "min":
|
|
113
|
+
score = min(weights)
|
|
114
|
+
elif aggregation == "max":
|
|
115
|
+
score = max(weights)
|
|
116
|
+
else:
|
|
117
|
+
score = sum(weights) / len(weights) # Default to mean
|
|
118
|
+
|
|
119
|
+
scored_path = Path(nodes=path.nodes, edges=path.edges, score=score)
|
|
120
|
+
scored_paths.append(scored_path)
|
|
121
|
+
|
|
122
|
+
return scored_paths
|
|
123
|
+
|
|
124
|
+
def score_by_relation_types(
|
|
125
|
+
self,
|
|
126
|
+
paths: List[Path],
|
|
127
|
+
preferred_types: List[str],
|
|
128
|
+
penalty: float = 0.5,
|
|
129
|
+
) -> List[Path]:
|
|
130
|
+
"""
|
|
131
|
+
Score paths by preferred relation types
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
paths: List of paths to score
|
|
135
|
+
preferred_types: List of preferred relation types
|
|
136
|
+
penalty: Score multiplier for non-preferred relations
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
List of paths scored by relation type preferences
|
|
140
|
+
"""
|
|
141
|
+
scored_paths = []
|
|
142
|
+
|
|
143
|
+
for path in paths:
|
|
144
|
+
if not path.edges:
|
|
145
|
+
score = 1.0
|
|
146
|
+
else:
|
|
147
|
+
# Calculate score based on preferred types
|
|
148
|
+
type_scores = []
|
|
149
|
+
for edge in path.edges:
|
|
150
|
+
if edge.relation_type in preferred_types:
|
|
151
|
+
type_scores.append(1.0)
|
|
152
|
+
else:
|
|
153
|
+
type_scores.append(penalty)
|
|
154
|
+
|
|
155
|
+
score = sum(type_scores) / len(type_scores)
|
|
156
|
+
|
|
157
|
+
scored_path = Path(nodes=path.nodes, edges=path.edges, score=score)
|
|
158
|
+
scored_paths.append(scored_path)
|
|
159
|
+
|
|
160
|
+
return scored_paths
|
|
161
|
+
|
|
162
|
+
def score_custom(self, paths: List[Path], scoring_fn: Callable[[Path], float]) -> List[Path]:
|
|
163
|
+
"""
|
|
164
|
+
Score paths using a custom scoring function
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
paths: List of paths to score
|
|
168
|
+
scoring_fn: Function that takes a Path and returns a score (0.0-1.0)
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
List of paths with custom scores
|
|
172
|
+
"""
|
|
173
|
+
scored_paths = []
|
|
174
|
+
|
|
175
|
+
for path in paths:
|
|
176
|
+
score = scoring_fn(path)
|
|
177
|
+
# Clamp score to valid range
|
|
178
|
+
score = max(0.0, min(1.0, score))
|
|
179
|
+
|
|
180
|
+
scored_path = Path(nodes=path.nodes, edges=path.edges, score=score)
|
|
181
|
+
scored_paths.append(scored_path)
|
|
182
|
+
|
|
183
|
+
return scored_paths
|
|
184
|
+
|
|
185
|
+
def combine_scores(
|
|
186
|
+
self,
|
|
187
|
+
paths_lists: List[List[Path]],
|
|
188
|
+
weights: Optional[List[float]] = None,
|
|
189
|
+
) -> List[Path]:
|
|
190
|
+
"""
|
|
191
|
+
Combine scores from multiple scoring methods
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
paths_lists: List of path lists (each with different scores)
|
|
195
|
+
weights: Optional weights for each scoring method
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
List of paths with combined scores
|
|
199
|
+
"""
|
|
200
|
+
if not paths_lists:
|
|
201
|
+
return []
|
|
202
|
+
|
|
203
|
+
if weights is None:
|
|
204
|
+
weights = [1.0] * len(paths_lists)
|
|
205
|
+
|
|
206
|
+
# Normalize weights
|
|
207
|
+
total_weight = sum(weights)
|
|
208
|
+
weights = [w / total_weight for w in weights]
|
|
209
|
+
|
|
210
|
+
# Build path ID to combined score mapping
|
|
211
|
+
path_scores = {}
|
|
212
|
+
|
|
213
|
+
for paths, weight in zip(paths_lists, weights):
|
|
214
|
+
for path in paths:
|
|
215
|
+
# Use path node IDs as key
|
|
216
|
+
path_key = tuple(p.id for p in path.nodes)
|
|
217
|
+
|
|
218
|
+
if path_key not in path_scores:
|
|
219
|
+
path_scores[path_key] = {"path": path, "score": 0.0}
|
|
220
|
+
|
|
221
|
+
# Add weighted score
|
|
222
|
+
if path.score is not None:
|
|
223
|
+
path_scores[path_key]["score"] += path.score * weight
|
|
224
|
+
|
|
225
|
+
# Create scored paths
|
|
226
|
+
combined_paths = []
|
|
227
|
+
for data in path_scores.values():
|
|
228
|
+
scored_path = Path(
|
|
229
|
+
nodes=data["path"].nodes,
|
|
230
|
+
edges=data["path"].edges,
|
|
231
|
+
score=data["score"],
|
|
232
|
+
)
|
|
233
|
+
combined_paths.append(scored_path)
|
|
234
|
+
|
|
235
|
+
return combined_paths
|
|
236
|
+
|
|
237
|
+
def rank_paths(
|
|
238
|
+
self,
|
|
239
|
+
paths: List[Path],
|
|
240
|
+
top_k: Optional[int] = None,
|
|
241
|
+
min_score: Optional[float] = None,
|
|
242
|
+
) -> List[Path]:
|
|
243
|
+
"""
|
|
244
|
+
Rank paths by score and return top results
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
paths: List of paths to rank
|
|
248
|
+
top_k: Number of top paths to return (None = all)
|
|
249
|
+
min_score: Minimum score threshold (None = no threshold)
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
Sorted list of paths (highest score first)
|
|
253
|
+
"""
|
|
254
|
+
# Filter by minimum score if specified
|
|
255
|
+
if min_score is not None:
|
|
256
|
+
paths = [p for p in paths if p.score is not None and p.score >= min_score]
|
|
257
|
+
|
|
258
|
+
# Sort by score (descending)
|
|
259
|
+
sorted_paths = sorted(
|
|
260
|
+
paths,
|
|
261
|
+
key=lambda p: p.score if p.score is not None else 0.0,
|
|
262
|
+
reverse=True,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
# Return top k if specified
|
|
266
|
+
if top_k is not None:
|
|
267
|
+
return sorted_paths[:top_k]
|
|
268
|
+
|
|
269
|
+
return sorted_paths
|