aiecs 1.0.1__py3-none-any.whl → 1.7.6__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.
Potentially problematic release.
This version of aiecs might be problematic. Click here for more details.
- aiecs/__init__.py +13 -16
- aiecs/__main__.py +7 -7
- aiecs/aiecs_client.py +269 -75
- aiecs/application/executors/operation_executor.py +79 -54
- aiecs/application/knowledge_graph/__init__.py +7 -0
- aiecs/application/knowledge_graph/builder/__init__.py +37 -0
- aiecs/application/knowledge_graph/builder/data_quality.py +302 -0
- aiecs/application/knowledge_graph/builder/data_reshaping.py +293 -0
- aiecs/application/knowledge_graph/builder/document_builder.py +369 -0
- aiecs/application/knowledge_graph/builder/graph_builder.py +490 -0
- aiecs/application/knowledge_graph/builder/import_optimizer.py +396 -0
- aiecs/application/knowledge_graph/builder/schema_inference.py +462 -0
- aiecs/application/knowledge_graph/builder/schema_mapping.py +563 -0
- aiecs/application/knowledge_graph/builder/structured_pipeline.py +1384 -0
- aiecs/application/knowledge_graph/builder/text_chunker.py +317 -0
- aiecs/application/knowledge_graph/extractors/__init__.py +27 -0
- aiecs/application/knowledge_graph/extractors/base.py +98 -0
- aiecs/application/knowledge_graph/extractors/llm_entity_extractor.py +422 -0
- aiecs/application/knowledge_graph/extractors/llm_relation_extractor.py +347 -0
- aiecs/application/knowledge_graph/extractors/ner_entity_extractor.py +241 -0
- aiecs/application/knowledge_graph/fusion/__init__.py +78 -0
- aiecs/application/knowledge_graph/fusion/ab_testing.py +395 -0
- aiecs/application/knowledge_graph/fusion/abbreviation_expander.py +327 -0
- aiecs/application/knowledge_graph/fusion/alias_index.py +597 -0
- aiecs/application/knowledge_graph/fusion/alias_matcher.py +384 -0
- aiecs/application/knowledge_graph/fusion/cache_coordinator.py +343 -0
- aiecs/application/knowledge_graph/fusion/entity_deduplicator.py +433 -0
- aiecs/application/knowledge_graph/fusion/entity_linker.py +511 -0
- aiecs/application/knowledge_graph/fusion/evaluation_dataset.py +240 -0
- aiecs/application/knowledge_graph/fusion/knowledge_fusion.py +632 -0
- aiecs/application/knowledge_graph/fusion/matching_config.py +489 -0
- aiecs/application/knowledge_graph/fusion/name_normalizer.py +352 -0
- aiecs/application/knowledge_graph/fusion/relation_deduplicator.py +183 -0
- aiecs/application/knowledge_graph/fusion/semantic_name_matcher.py +464 -0
- aiecs/application/knowledge_graph/fusion/similarity_pipeline.py +534 -0
- aiecs/application/knowledge_graph/pattern_matching/__init__.py +21 -0
- aiecs/application/knowledge_graph/pattern_matching/pattern_matcher.py +342 -0
- aiecs/application/knowledge_graph/pattern_matching/query_executor.py +366 -0
- aiecs/application/knowledge_graph/profiling/__init__.py +12 -0
- aiecs/application/knowledge_graph/profiling/query_plan_visualizer.py +195 -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 +341 -0
- aiecs/application/knowledge_graph/reasoning/inference_engine.py +500 -0
- aiecs/application/knowledge_graph/reasoning/logic_form_parser.py +163 -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 +913 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_validator.py +866 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/error_handler.py +475 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/parser.py +396 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/query_context.py +208 -0
- aiecs/application/knowledge_graph/reasoning/logic_query_integration.py +170 -0
- aiecs/application/knowledge_graph/reasoning/query_planner.py +855 -0
- aiecs/application/knowledge_graph/reasoning/reasoning_engine.py +518 -0
- aiecs/application/knowledge_graph/retrieval/__init__.py +27 -0
- aiecs/application/knowledge_graph/retrieval/query_intent_classifier.py +211 -0
- aiecs/application/knowledge_graph/retrieval/retrieval_strategies.py +592 -0
- aiecs/application/knowledge_graph/retrieval/strategy_types.py +23 -0
- aiecs/application/knowledge_graph/search/__init__.py +59 -0
- aiecs/application/knowledge_graph/search/hybrid_search.py +457 -0
- aiecs/application/knowledge_graph/search/reranker.py +293 -0
- aiecs/application/knowledge_graph/search/reranker_strategies.py +535 -0
- aiecs/application/knowledge_graph/search/text_similarity.py +392 -0
- aiecs/application/knowledge_graph/traversal/__init__.py +15 -0
- aiecs/application/knowledge_graph/traversal/enhanced_traversal.py +305 -0
- aiecs/application/knowledge_graph/traversal/path_scorer.py +271 -0
- aiecs/application/knowledge_graph/validators/__init__.py +13 -0
- aiecs/application/knowledge_graph/validators/relation_validator.py +239 -0
- aiecs/application/knowledge_graph/visualization/__init__.py +11 -0
- aiecs/application/knowledge_graph/visualization/graph_visualizer.py +313 -0
- aiecs/common/__init__.py +9 -0
- aiecs/common/knowledge_graph/__init__.py +17 -0
- aiecs/common/knowledge_graph/runnable.py +471 -0
- aiecs/config/__init__.py +20 -5
- aiecs/config/config.py +762 -31
- aiecs/config/graph_config.py +131 -0
- aiecs/config/tool_config.py +399 -0
- aiecs/core/__init__.py +29 -13
- aiecs/core/interface/__init__.py +2 -2
- aiecs/core/interface/execution_interface.py +22 -22
- aiecs/core/interface/storage_interface.py +37 -88
- aiecs/core/registry/__init__.py +31 -0
- aiecs/core/registry/service_registry.py +92 -0
- aiecs/domain/__init__.py +270 -1
- aiecs/domain/agent/__init__.py +191 -0
- aiecs/domain/agent/base_agent.py +3870 -0
- aiecs/domain/agent/exceptions.py +99 -0
- aiecs/domain/agent/graph_aware_mixin.py +569 -0
- aiecs/domain/agent/hybrid_agent.py +1435 -0
- aiecs/domain/agent/integration/__init__.py +29 -0
- aiecs/domain/agent/integration/context_compressor.py +216 -0
- aiecs/domain/agent/integration/context_engine_adapter.py +587 -0
- aiecs/domain/agent/integration/protocols.py +281 -0
- aiecs/domain/agent/integration/retry_policy.py +218 -0
- aiecs/domain/agent/integration/role_config.py +213 -0
- aiecs/domain/agent/knowledge_aware_agent.py +1892 -0
- aiecs/domain/agent/lifecycle.py +291 -0
- aiecs/domain/agent/llm_agent.py +692 -0
- aiecs/domain/agent/memory/__init__.py +12 -0
- aiecs/domain/agent/memory/conversation.py +1124 -0
- aiecs/domain/agent/migration/__init__.py +14 -0
- aiecs/domain/agent/migration/conversion.py +163 -0
- aiecs/domain/agent/migration/legacy_wrapper.py +86 -0
- aiecs/domain/agent/models.py +884 -0
- aiecs/domain/agent/observability.py +479 -0
- aiecs/domain/agent/persistence.py +449 -0
- aiecs/domain/agent/prompts/__init__.py +29 -0
- aiecs/domain/agent/prompts/builder.py +159 -0
- aiecs/domain/agent/prompts/formatters.py +187 -0
- aiecs/domain/agent/prompts/template.py +255 -0
- aiecs/domain/agent/registry.py +253 -0
- aiecs/domain/agent/tool_agent.py +444 -0
- aiecs/domain/agent/tools/__init__.py +15 -0
- aiecs/domain/agent/tools/schema_generator.py +364 -0
- aiecs/domain/community/__init__.py +155 -0
- aiecs/domain/community/agent_adapter.py +469 -0
- aiecs/domain/community/analytics.py +432 -0
- aiecs/domain/community/collaborative_workflow.py +648 -0
- aiecs/domain/community/communication_hub.py +634 -0
- aiecs/domain/community/community_builder.py +320 -0
- aiecs/domain/community/community_integration.py +796 -0
- aiecs/domain/community/community_manager.py +803 -0
- aiecs/domain/community/decision_engine.py +849 -0
- aiecs/domain/community/exceptions.py +231 -0
- aiecs/domain/community/models/__init__.py +33 -0
- aiecs/domain/community/models/community_models.py +234 -0
- aiecs/domain/community/resource_manager.py +461 -0
- aiecs/domain/community/shared_context_manager.py +589 -0
- aiecs/domain/context/__init__.py +40 -10
- aiecs/domain/context/context_engine.py +1910 -0
- aiecs/domain/context/conversation_models.py +87 -53
- aiecs/domain/context/graph_memory.py +582 -0
- aiecs/domain/execution/model.py +12 -4
- aiecs/domain/knowledge_graph/__init__.py +19 -0
- aiecs/domain/knowledge_graph/models/__init__.py +52 -0
- aiecs/domain/knowledge_graph/models/entity.py +148 -0
- aiecs/domain/knowledge_graph/models/evidence.py +178 -0
- aiecs/domain/knowledge_graph/models/inference_rule.py +184 -0
- aiecs/domain/knowledge_graph/models/path.py +171 -0
- aiecs/domain/knowledge_graph/models/path_pattern.py +171 -0
- aiecs/domain/knowledge_graph/models/query.py +261 -0
- aiecs/domain/knowledge_graph/models/query_plan.py +181 -0
- aiecs/domain/knowledge_graph/models/relation.py +202 -0
- aiecs/domain/knowledge_graph/schema/__init__.py +23 -0
- aiecs/domain/knowledge_graph/schema/entity_type.py +131 -0
- aiecs/domain/knowledge_graph/schema/graph_schema.py +253 -0
- aiecs/domain/knowledge_graph/schema/property_schema.py +143 -0
- aiecs/domain/knowledge_graph/schema/relation_type.py +163 -0
- aiecs/domain/knowledge_graph/schema/schema_manager.py +691 -0
- aiecs/domain/knowledge_graph/schema/type_enums.py +209 -0
- aiecs/domain/task/dsl_processor.py +172 -56
- aiecs/domain/task/model.py +20 -8
- aiecs/domain/task/task_context.py +27 -24
- aiecs/infrastructure/__init__.py +0 -2
- aiecs/infrastructure/graph_storage/__init__.py +11 -0
- aiecs/infrastructure/graph_storage/base.py +837 -0
- aiecs/infrastructure/graph_storage/batch_operations.py +458 -0
- aiecs/infrastructure/graph_storage/cache.py +424 -0
- aiecs/infrastructure/graph_storage/distributed.py +223 -0
- aiecs/infrastructure/graph_storage/error_handling.py +380 -0
- aiecs/infrastructure/graph_storage/graceful_degradation.py +294 -0
- aiecs/infrastructure/graph_storage/health_checks.py +378 -0
- aiecs/infrastructure/graph_storage/in_memory.py +1197 -0
- aiecs/infrastructure/graph_storage/index_optimization.py +446 -0
- aiecs/infrastructure/graph_storage/lazy_loading.py +431 -0
- aiecs/infrastructure/graph_storage/metrics.py +344 -0
- aiecs/infrastructure/graph_storage/migration.py +400 -0
- aiecs/infrastructure/graph_storage/pagination.py +483 -0
- aiecs/infrastructure/graph_storage/performance_monitoring.py +456 -0
- aiecs/infrastructure/graph_storage/postgres.py +1563 -0
- aiecs/infrastructure/graph_storage/property_storage.py +353 -0
- aiecs/infrastructure/graph_storage/protocols.py +76 -0
- aiecs/infrastructure/graph_storage/query_optimizer.py +642 -0
- aiecs/infrastructure/graph_storage/schema_cache.py +290 -0
- aiecs/infrastructure/graph_storage/sqlite.py +1373 -0
- aiecs/infrastructure/graph_storage/streaming.py +487 -0
- aiecs/infrastructure/graph_storage/tenant.py +412 -0
- aiecs/infrastructure/messaging/celery_task_manager.py +92 -54
- aiecs/infrastructure/messaging/websocket_manager.py +51 -35
- aiecs/infrastructure/monitoring/__init__.py +22 -0
- aiecs/infrastructure/monitoring/executor_metrics.py +45 -11
- aiecs/infrastructure/monitoring/global_metrics_manager.py +212 -0
- aiecs/infrastructure/monitoring/structured_logger.py +3 -7
- aiecs/infrastructure/monitoring/tracing_manager.py +63 -35
- aiecs/infrastructure/persistence/__init__.py +14 -1
- aiecs/infrastructure/persistence/context_engine_client.py +184 -0
- aiecs/infrastructure/persistence/database_manager.py +67 -43
- aiecs/infrastructure/persistence/file_storage.py +180 -103
- aiecs/infrastructure/persistence/redis_client.py +74 -21
- aiecs/llm/__init__.py +73 -25
- aiecs/llm/callbacks/__init__.py +11 -0
- aiecs/llm/{custom_callbacks.py → callbacks/custom_callbacks.py} +26 -19
- aiecs/llm/client_factory.py +224 -36
- aiecs/llm/client_resolver.py +155 -0
- aiecs/llm/clients/__init__.py +38 -0
- aiecs/llm/clients/base_client.py +324 -0
- aiecs/llm/clients/google_function_calling_mixin.py +457 -0
- aiecs/llm/clients/googleai_client.py +241 -0
- aiecs/llm/clients/openai_client.py +158 -0
- aiecs/llm/clients/openai_compatible_mixin.py +367 -0
- aiecs/llm/clients/vertex_client.py +897 -0
- aiecs/llm/clients/xai_client.py +201 -0
- aiecs/llm/config/__init__.py +51 -0
- aiecs/llm/config/config_loader.py +272 -0
- aiecs/llm/config/config_validator.py +206 -0
- aiecs/llm/config/model_config.py +143 -0
- aiecs/llm/protocols.py +149 -0
- aiecs/llm/utils/__init__.py +10 -0
- aiecs/llm/utils/validate_config.py +89 -0
- aiecs/main.py +140 -121
- aiecs/scripts/aid/VERSION_MANAGEMENT.md +138 -0
- aiecs/scripts/aid/__init__.py +19 -0
- aiecs/scripts/aid/module_checker.py +499 -0
- aiecs/scripts/aid/version_manager.py +235 -0
- aiecs/scripts/{DEPENDENCY_SYSTEM_SUMMARY.md → dependance_check/DEPENDENCY_SYSTEM_SUMMARY.md} +1 -0
- aiecs/scripts/{README_DEPENDENCY_CHECKER.md → dependance_check/README_DEPENDENCY_CHECKER.md} +1 -0
- aiecs/scripts/dependance_check/__init__.py +15 -0
- aiecs/scripts/dependance_check/dependency_checker.py +1835 -0
- aiecs/scripts/{dependency_fixer.py → dependance_check/dependency_fixer.py} +192 -90
- aiecs/scripts/{download_nlp_data.py → dependance_check/download_nlp_data.py} +203 -71
- aiecs/scripts/dependance_patch/__init__.py +7 -0
- aiecs/scripts/dependance_patch/fix_weasel/__init__.py +11 -0
- aiecs/scripts/{fix_weasel_validator.py → dependance_patch/fix_weasel/fix_weasel_validator.py} +21 -14
- aiecs/scripts/{patch_weasel_library.sh → dependance_patch/fix_weasel/patch_weasel_library.sh} +1 -1
- aiecs/scripts/knowledge_graph/__init__.py +3 -0
- aiecs/scripts/knowledge_graph/run_threshold_experiments.py +212 -0
- aiecs/scripts/migrations/multi_tenancy/README.md +142 -0
- aiecs/scripts/tools_develop/README.md +671 -0
- aiecs/scripts/tools_develop/README_CONFIG_CHECKER.md +273 -0
- aiecs/scripts/tools_develop/TOOLS_CONFIG_GUIDE.md +1287 -0
- aiecs/scripts/tools_develop/TOOL_AUTO_DISCOVERY.md +234 -0
- aiecs/scripts/tools_develop/__init__.py +21 -0
- aiecs/scripts/tools_develop/check_all_tools_config.py +548 -0
- aiecs/scripts/tools_develop/check_type_annotations.py +257 -0
- aiecs/scripts/tools_develop/pre-commit-schema-coverage.sh +66 -0
- aiecs/scripts/tools_develop/schema_coverage.py +511 -0
- aiecs/scripts/tools_develop/validate_tool_schemas.py +475 -0
- aiecs/scripts/tools_develop/verify_executor_config_fix.py +98 -0
- aiecs/scripts/tools_develop/verify_tools.py +352 -0
- aiecs/tasks/__init__.py +0 -1
- aiecs/tasks/worker.py +115 -47
- aiecs/tools/__init__.py +194 -72
- aiecs/tools/apisource/__init__.py +99 -0
- aiecs/tools/apisource/intelligence/__init__.py +19 -0
- aiecs/tools/apisource/intelligence/data_fusion.py +632 -0
- aiecs/tools/apisource/intelligence/query_analyzer.py +417 -0
- aiecs/tools/apisource/intelligence/search_enhancer.py +385 -0
- aiecs/tools/apisource/monitoring/__init__.py +9 -0
- aiecs/tools/apisource/monitoring/metrics.py +330 -0
- aiecs/tools/apisource/providers/__init__.py +112 -0
- aiecs/tools/apisource/providers/base.py +671 -0
- aiecs/tools/apisource/providers/census.py +397 -0
- aiecs/tools/apisource/providers/fred.py +535 -0
- aiecs/tools/apisource/providers/newsapi.py +409 -0
- aiecs/tools/apisource/providers/worldbank.py +352 -0
- aiecs/tools/apisource/reliability/__init__.py +12 -0
- aiecs/tools/apisource/reliability/error_handler.py +363 -0
- aiecs/tools/apisource/reliability/fallback_strategy.py +376 -0
- aiecs/tools/apisource/tool.py +832 -0
- aiecs/tools/apisource/utils/__init__.py +9 -0
- aiecs/tools/apisource/utils/validators.py +334 -0
- aiecs/tools/base_tool.py +415 -21
- aiecs/tools/docs/__init__.py +121 -0
- aiecs/tools/docs/ai_document_orchestrator.py +607 -0
- aiecs/tools/docs/ai_document_writer_orchestrator.py +2350 -0
- aiecs/tools/docs/content_insertion_tool.py +1320 -0
- aiecs/tools/docs/document_creator_tool.py +1323 -0
- aiecs/tools/docs/document_layout_tool.py +1160 -0
- aiecs/tools/docs/document_parser_tool.py +1011 -0
- aiecs/tools/docs/document_writer_tool.py +1829 -0
- aiecs/tools/knowledge_graph/__init__.py +17 -0
- aiecs/tools/knowledge_graph/graph_reasoning_tool.py +807 -0
- aiecs/tools/knowledge_graph/graph_search_tool.py +944 -0
- aiecs/tools/knowledge_graph/kg_builder_tool.py +524 -0
- aiecs/tools/langchain_adapter.py +300 -138
- aiecs/tools/schema_generator.py +455 -0
- aiecs/tools/search_tool/__init__.py +100 -0
- aiecs/tools/search_tool/analyzers.py +581 -0
- aiecs/tools/search_tool/cache.py +264 -0
- aiecs/tools/search_tool/constants.py +128 -0
- aiecs/tools/search_tool/context.py +224 -0
- aiecs/tools/search_tool/core.py +778 -0
- aiecs/tools/search_tool/deduplicator.py +119 -0
- aiecs/tools/search_tool/error_handler.py +242 -0
- aiecs/tools/search_tool/metrics.py +343 -0
- aiecs/tools/search_tool/rate_limiter.py +172 -0
- aiecs/tools/search_tool/schemas.py +275 -0
- aiecs/tools/statistics/__init__.py +80 -0
- aiecs/tools/statistics/ai_data_analysis_orchestrator.py +646 -0
- aiecs/tools/statistics/ai_insight_generator_tool.py +508 -0
- aiecs/tools/statistics/ai_report_orchestrator_tool.py +684 -0
- aiecs/tools/statistics/data_loader_tool.py +555 -0
- aiecs/tools/statistics/data_profiler_tool.py +638 -0
- aiecs/tools/statistics/data_transformer_tool.py +580 -0
- aiecs/tools/statistics/data_visualizer_tool.py +498 -0
- aiecs/tools/statistics/model_trainer_tool.py +507 -0
- aiecs/tools/statistics/statistical_analyzer_tool.py +472 -0
- aiecs/tools/task_tools/__init__.py +49 -36
- aiecs/tools/task_tools/chart_tool.py +200 -184
- aiecs/tools/task_tools/classfire_tool.py +268 -267
- aiecs/tools/task_tools/image_tool.py +175 -131
- aiecs/tools/task_tools/office_tool.py +226 -146
- aiecs/tools/task_tools/pandas_tool.py +477 -121
- aiecs/tools/task_tools/report_tool.py +390 -142
- aiecs/tools/task_tools/research_tool.py +149 -79
- aiecs/tools/task_tools/scraper_tool.py +339 -145
- aiecs/tools/task_tools/stats_tool.py +448 -209
- aiecs/tools/temp_file_manager.py +26 -24
- aiecs/tools/tool_executor/__init__.py +18 -16
- aiecs/tools/tool_executor/tool_executor.py +364 -52
- aiecs/utils/LLM_output_structor.py +74 -48
- aiecs/utils/__init__.py +14 -3
- aiecs/utils/base_callback.py +0 -3
- aiecs/utils/cache_provider.py +696 -0
- aiecs/utils/execution_utils.py +50 -31
- aiecs/utils/prompt_loader.py +1 -0
- aiecs/utils/token_usage_repository.py +37 -11
- aiecs/ws/socket_server.py +14 -4
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/METADATA +52 -15
- aiecs-1.7.6.dist-info/RECORD +337 -0
- aiecs-1.7.6.dist-info/entry_points.txt +13 -0
- aiecs/config/registry.py +0 -19
- aiecs/domain/context/content_engine.py +0 -982
- aiecs/llm/base_client.py +0 -99
- aiecs/llm/openai_client.py +0 -125
- aiecs/llm/vertex_client.py +0 -186
- aiecs/llm/xai_client.py +0 -184
- aiecs/scripts/dependency_checker.py +0 -857
- aiecs/scripts/quick_dependency_check.py +0 -269
- aiecs/tools/task_tools/search_api.py +0 -7
- aiecs-1.0.1.dist-info/RECORD +0 -90
- aiecs-1.0.1.dist-info/entry_points.txt +0 -7
- /aiecs/scripts/{setup_nlp_data.sh → dependance_check/setup_nlp_data.sh} +0 -0
- /aiecs/scripts/{README_WEASEL_PATCH.md → dependance_patch/fix_weasel/README_WEASEL_PATCH.md} +0 -0
- /aiecs/scripts/{fix_weasel_validator.sh → dependance_patch/fix_weasel/fix_weasel_validator.sh} +0 -0
- /aiecs/scripts/{run_weasel_patch.sh → dependance_patch/fix_weasel/run_weasel_patch.sh} +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/WHEEL +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/licenses/LICENSE +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Reasoning Engine
|
|
3
|
+
|
|
4
|
+
Multi-hop reasoning over knowledge graphs with evidence collection and answer generation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import uuid
|
|
8
|
+
import time
|
|
9
|
+
from typing import List, Optional, Dict, Any, Tuple
|
|
10
|
+
from aiecs.infrastructure.graph_storage.base import GraphStore
|
|
11
|
+
from aiecs.domain.knowledge_graph.models.path import Path
|
|
12
|
+
from aiecs.domain.knowledge_graph.models.evidence import (
|
|
13
|
+
Evidence,
|
|
14
|
+
EvidenceType,
|
|
15
|
+
ReasoningResult,
|
|
16
|
+
)
|
|
17
|
+
from aiecs.domain.knowledge_graph.models.query_plan import QueryPlan, QueryStep
|
|
18
|
+
from aiecs.domain.knowledge_graph.models.query import QueryType
|
|
19
|
+
from aiecs.application.knowledge_graph.traversal.enhanced_traversal import (
|
|
20
|
+
EnhancedTraversal,
|
|
21
|
+
)
|
|
22
|
+
from aiecs.application.knowledge_graph.traversal.path_scorer import PathScorer
|
|
23
|
+
from aiecs.application.knowledge_graph.reasoning.query_planner import (
|
|
24
|
+
QueryPlanner,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ReasoningEngine:
|
|
29
|
+
"""
|
|
30
|
+
Multi-Hop Reasoning Engine
|
|
31
|
+
|
|
32
|
+
Executes query plans, collects evidence, and generates answers
|
|
33
|
+
for complex multi-hop queries over knowledge graphs.
|
|
34
|
+
|
|
35
|
+
Features:
|
|
36
|
+
- Execute query plans from QueryPlanner
|
|
37
|
+
- Multi-hop path finding
|
|
38
|
+
- Evidence collection and scoring
|
|
39
|
+
- Path ranking by relevance
|
|
40
|
+
- Answer generation from evidence
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
```python
|
|
44
|
+
engine = ReasoningEngine(graph_store)
|
|
45
|
+
|
|
46
|
+
# Reason over a query
|
|
47
|
+
result = await engine.reason(
|
|
48
|
+
query="What companies does Alice know people at?",
|
|
49
|
+
context={"start_entity_id": "person_alice"}
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
print(f"Answer: {result.answer}")
|
|
53
|
+
print(f"Confidence: {result.confidence}")
|
|
54
|
+
print(f"Evidence: {result.evidence_count} pieces")
|
|
55
|
+
```
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
graph_store: GraphStore,
|
|
61
|
+
query_planner: Optional[QueryPlanner] = None,
|
|
62
|
+
):
|
|
63
|
+
"""
|
|
64
|
+
Initialize reasoning engine
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
graph_store: Graph storage backend
|
|
68
|
+
query_planner: Query planner (creates one if not provided)
|
|
69
|
+
"""
|
|
70
|
+
self.graph_store = graph_store
|
|
71
|
+
self.query_planner = query_planner or QueryPlanner(graph_store)
|
|
72
|
+
self.traversal = EnhancedTraversal(graph_store)
|
|
73
|
+
self.path_scorer = PathScorer()
|
|
74
|
+
|
|
75
|
+
async def reason(
|
|
76
|
+
self,
|
|
77
|
+
query: str,
|
|
78
|
+
context: Optional[Dict[str, Any]] = None,
|
|
79
|
+
max_hops: int = 3,
|
|
80
|
+
max_evidence: int = 20,
|
|
81
|
+
) -> ReasoningResult:
|
|
82
|
+
"""
|
|
83
|
+
Perform multi-hop reasoning on a query
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
query: Natural language query
|
|
87
|
+
context: Query context (entity IDs, embeddings, etc.)
|
|
88
|
+
max_hops: Maximum number of hops for traversal
|
|
89
|
+
max_evidence: Maximum number of evidence pieces to collect
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Reasoning result with evidence and answer
|
|
93
|
+
"""
|
|
94
|
+
start_time = time.time()
|
|
95
|
+
context = context or {}
|
|
96
|
+
trace = []
|
|
97
|
+
|
|
98
|
+
# Step 1: Plan the query
|
|
99
|
+
trace.append(f"Planning query: {query}")
|
|
100
|
+
plan = self.query_planner.plan_query(query, context)
|
|
101
|
+
trace.append(f"Created plan with {len(plan.steps)} steps")
|
|
102
|
+
|
|
103
|
+
# Step 2: Execute plan and collect evidence
|
|
104
|
+
trace.append("Executing query plan...")
|
|
105
|
+
evidence = await self._execute_plan_with_evidence(plan, trace)
|
|
106
|
+
|
|
107
|
+
# Fallback: If no evidence found but start_entity_id is provided, try
|
|
108
|
+
# direct traversal
|
|
109
|
+
if not evidence and context.get("start_entity_id"):
|
|
110
|
+
import logging
|
|
111
|
+
|
|
112
|
+
logger = logging.getLogger(__name__)
|
|
113
|
+
|
|
114
|
+
trace.append(f"WARNING: Query plan returned no evidence. Plan had {len(plan.steps)} steps.")
|
|
115
|
+
trace.append(f"Plan steps: {[s.step_id + ':' + s.operation.value for s in plan.steps]}")
|
|
116
|
+
logger.warning(f"Query plan returned no evidence. " f"Plan ID: {plan.plan_id}, Steps: {len(plan.steps)}, " f"Query: {query}, Context: {context}")
|
|
117
|
+
|
|
118
|
+
trace.append(f"FALLBACK: Trying direct traversal from {context['start_entity_id']}")
|
|
119
|
+
start_id = context["start_entity_id"]
|
|
120
|
+
target_id = context.get("target_entity_id")
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
paths = await self.find_multi_hop_paths(
|
|
124
|
+
start_entity_id=start_id,
|
|
125
|
+
target_entity_id=target_id,
|
|
126
|
+
max_hops=max_hops,
|
|
127
|
+
relation_types=context.get("relation_types"),
|
|
128
|
+
max_paths=max_evidence,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
if paths:
|
|
132
|
+
path_evidence = await self.collect_evidence_from_paths(paths, source="direct_traversal_fallback")
|
|
133
|
+
evidence.extend(path_evidence)
|
|
134
|
+
trace.append(f"FALLBACK SUCCESS: Found {len(path_evidence)} evidence pieces from direct traversal")
|
|
135
|
+
logger.info(f"Fallback traversal succeeded: {len(path_evidence)} evidence pieces from {start_id}")
|
|
136
|
+
else:
|
|
137
|
+
trace.append(f"FALLBACK FAILED: No paths found from {start_id}")
|
|
138
|
+
logger.warning(f"Fallback traversal found no paths from {start_id}")
|
|
139
|
+
except Exception as e:
|
|
140
|
+
trace.append(f"FALLBACK ERROR: {str(e)}")
|
|
141
|
+
logger.error(f"Fallback traversal failed: {str(e)}", exc_info=True)
|
|
142
|
+
|
|
143
|
+
# Step 3: Rank and filter evidence
|
|
144
|
+
trace.append(f"Collected {len(evidence)} pieces of evidence")
|
|
145
|
+
evidence = self._rank_and_filter_evidence(evidence, max_evidence)
|
|
146
|
+
trace.append(f"Filtered to top {len(evidence)} pieces")
|
|
147
|
+
|
|
148
|
+
# Step 4: Generate answer
|
|
149
|
+
trace.append("Generating answer from evidence...")
|
|
150
|
+
answer, confidence = self._generate_answer(query, evidence)
|
|
151
|
+
|
|
152
|
+
execution_time = (time.time() - start_time) * 1000
|
|
153
|
+
|
|
154
|
+
return ReasoningResult(
|
|
155
|
+
query=query,
|
|
156
|
+
evidence=evidence,
|
|
157
|
+
answer=answer,
|
|
158
|
+
confidence=confidence,
|
|
159
|
+
reasoning_trace=trace,
|
|
160
|
+
execution_time_ms=execution_time,
|
|
161
|
+
metadata={"plan_id": plan.plan_id, "num_steps": len(plan.steps)},
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
async def find_multi_hop_paths(
|
|
165
|
+
self,
|
|
166
|
+
start_entity_id: str,
|
|
167
|
+
target_entity_id: Optional[str] = None,
|
|
168
|
+
max_hops: int = 3,
|
|
169
|
+
relation_types: Optional[List[str]] = None,
|
|
170
|
+
max_paths: int = 10,
|
|
171
|
+
) -> List[Path]:
|
|
172
|
+
"""
|
|
173
|
+
Find multi-hop paths between entities
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
start_entity_id: Starting entity ID
|
|
177
|
+
target_entity_id: Target entity ID (None for all reachable)
|
|
178
|
+
max_hops: Maximum number of hops
|
|
179
|
+
relation_types: Allowed relation types (None for all)
|
|
180
|
+
max_paths: Maximum number of paths to return
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
List of paths found
|
|
184
|
+
"""
|
|
185
|
+
# Use graph store's traverse method
|
|
186
|
+
paths = await self.graph_store.traverse(
|
|
187
|
+
start_entity_id=start_entity_id,
|
|
188
|
+
relation_type=None, # Will filter later if needed
|
|
189
|
+
max_depth=max_hops,
|
|
190
|
+
max_results=max_paths * 2, # Get more, then filter
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Filter by target if specified
|
|
194
|
+
if target_entity_id:
|
|
195
|
+
paths = [path for path in paths if path.nodes[-1].id == target_entity_id]
|
|
196
|
+
|
|
197
|
+
# Filter by relation types if specified
|
|
198
|
+
if relation_types:
|
|
199
|
+
paths = [path for path in paths if all(rel.relation_type in relation_types for rel in path.edges)]
|
|
200
|
+
|
|
201
|
+
return paths[:max_paths]
|
|
202
|
+
|
|
203
|
+
async def collect_evidence_from_paths(self, paths: List[Path], source: str = "path_finding") -> List[Evidence]:
|
|
204
|
+
"""
|
|
205
|
+
Collect evidence from paths
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
paths: List of paths to extract evidence from
|
|
209
|
+
source: Source identifier for the evidence
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
List of evidence pieces
|
|
213
|
+
"""
|
|
214
|
+
evidence_list = []
|
|
215
|
+
|
|
216
|
+
for i, path in enumerate(paths):
|
|
217
|
+
# Calculate confidence based on path properties
|
|
218
|
+
confidence = self._calculate_path_confidence(path)
|
|
219
|
+
|
|
220
|
+
# Calculate relevance (for now, use path length as proxy)
|
|
221
|
+
relevance = 1.0 / max(1, len(path.nodes) - 1)
|
|
222
|
+
|
|
223
|
+
# Create explanation
|
|
224
|
+
explanation = self._create_path_explanation(path)
|
|
225
|
+
|
|
226
|
+
evidence = Evidence(
|
|
227
|
+
evidence_id=f"ev_{uuid.uuid4().hex[:8]}",
|
|
228
|
+
evidence_type=EvidenceType.PATH,
|
|
229
|
+
entities=path.nodes,
|
|
230
|
+
relations=path.edges,
|
|
231
|
+
paths=[path],
|
|
232
|
+
confidence=confidence,
|
|
233
|
+
relevance_score=relevance,
|
|
234
|
+
explanation=explanation,
|
|
235
|
+
source=source,
|
|
236
|
+
metadata={"path_index": i, "path_length": len(path.nodes)},
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
evidence_list.append(evidence)
|
|
240
|
+
|
|
241
|
+
return evidence_list
|
|
242
|
+
|
|
243
|
+
def rank_evidence(self, evidence: List[Evidence], ranking_method: str = "combined_score") -> List[Evidence]:
|
|
244
|
+
"""
|
|
245
|
+
Rank evidence by relevance
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
evidence: List of evidence to rank
|
|
249
|
+
ranking_method: Method to use for ranking
|
|
250
|
+
- "combined_score": confidence * relevance
|
|
251
|
+
- "confidence": confidence only
|
|
252
|
+
- "relevance": relevance only
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
Ranked evidence list
|
|
256
|
+
"""
|
|
257
|
+
if ranking_method == "combined_score":
|
|
258
|
+
return sorted(evidence, key=lambda e: e.combined_score, reverse=True)
|
|
259
|
+
elif ranking_method == "confidence":
|
|
260
|
+
return sorted(evidence, key=lambda e: e.confidence, reverse=True)
|
|
261
|
+
elif ranking_method == "relevance":
|
|
262
|
+
return sorted(evidence, key=lambda e: e.relevance_score, reverse=True)
|
|
263
|
+
else:
|
|
264
|
+
return evidence
|
|
265
|
+
|
|
266
|
+
def _calculate_path_confidence(self, path: Path) -> float:
|
|
267
|
+
"""Calculate confidence score for a path"""
|
|
268
|
+
if not path.edges:
|
|
269
|
+
return 1.0
|
|
270
|
+
|
|
271
|
+
# Use average weight of relations as confidence proxy
|
|
272
|
+
weights = [rel.weight for rel in path.edges if rel.weight is not None]
|
|
273
|
+
if not weights:
|
|
274
|
+
return 0.5
|
|
275
|
+
|
|
276
|
+
return sum(weights) / len(weights)
|
|
277
|
+
|
|
278
|
+
def _create_path_explanation(self, path: Path) -> str:
|
|
279
|
+
"""Create human-readable explanation of a path"""
|
|
280
|
+
if len(path.nodes) == 1:
|
|
281
|
+
entity = path.nodes[0]
|
|
282
|
+
return f"Entity: {entity.properties.get('name', entity.id)} ({entity.entity_type})"
|
|
283
|
+
|
|
284
|
+
parts = []
|
|
285
|
+
for i, entity in enumerate(path.nodes):
|
|
286
|
+
entity_name = entity.properties.get("name", entity.id)
|
|
287
|
+
entity_type = entity.entity_type
|
|
288
|
+
parts.append(f"{entity_name} ({entity_type})")
|
|
289
|
+
|
|
290
|
+
if i < len(path.edges):
|
|
291
|
+
relation = path.edges[i]
|
|
292
|
+
parts.append(f" --[{relation.relation_type}]--> ")
|
|
293
|
+
|
|
294
|
+
return "".join(parts)
|
|
295
|
+
|
|
296
|
+
async def _execute_plan_with_evidence(self, plan: QueryPlan, trace: List[str]) -> List[Evidence]:
|
|
297
|
+
"""Execute query plan and collect evidence"""
|
|
298
|
+
import logging
|
|
299
|
+
|
|
300
|
+
logger = logging.getLogger(__name__)
|
|
301
|
+
|
|
302
|
+
all_evidence = []
|
|
303
|
+
completed_steps = set()
|
|
304
|
+
step_results: Dict[str, Any] = {}
|
|
305
|
+
|
|
306
|
+
# Get execution order
|
|
307
|
+
execution_order = plan.get_execution_order()
|
|
308
|
+
trace.append(f"Plan has {len(plan.steps)} steps, execution order: {execution_order}")
|
|
309
|
+
|
|
310
|
+
for level, step_ids in enumerate(execution_order):
|
|
311
|
+
trace.append(f"Executing level {level}: {step_ids}")
|
|
312
|
+
|
|
313
|
+
# Execute steps in this level (could be parallelized)
|
|
314
|
+
for step_id in step_ids:
|
|
315
|
+
try:
|
|
316
|
+
step = next(s for s in plan.steps if s.step_id == step_id)
|
|
317
|
+
trace.append(f" Executing {step_id}: {step.operation.value} - {step.description}")
|
|
318
|
+
|
|
319
|
+
# Execute step
|
|
320
|
+
step_evidence = await self._execute_step(step, step_results)
|
|
321
|
+
all_evidence.extend(step_evidence)
|
|
322
|
+
|
|
323
|
+
# Store results for dependent steps
|
|
324
|
+
step_results[step_id] = step_evidence
|
|
325
|
+
completed_steps.add(step_id)
|
|
326
|
+
|
|
327
|
+
trace.append(f" {step_id}: Collected {len(step_evidence)} evidence")
|
|
328
|
+
logger.debug(f"Step {step_id} completed: {len(step_evidence)} evidence pieces")
|
|
329
|
+
|
|
330
|
+
if len(step_evidence) == 0:
|
|
331
|
+
trace.append(f" WARNING: {step_id} returned no evidence")
|
|
332
|
+
logger.warning(
|
|
333
|
+
f"Step {step_id} ({step.operation.value}) returned no evidence. "
|
|
334
|
+
f"Query: {step.query.query_type}, "
|
|
335
|
+
f"Entity ID: {getattr(step.query, 'entity_id', None)}, "
|
|
336
|
+
f"Source: {getattr(step.query, 'source_entity_id', None)}"
|
|
337
|
+
)
|
|
338
|
+
except Exception as e:
|
|
339
|
+
error_msg = f"Error executing step {step_id}: {str(e)}"
|
|
340
|
+
trace.append(f" ERROR: {error_msg}")
|
|
341
|
+
logger.error(error_msg, exc_info=True)
|
|
342
|
+
# Continue with other steps even if one fails
|
|
343
|
+
|
|
344
|
+
return all_evidence
|
|
345
|
+
|
|
346
|
+
async def _execute_step(self, step: QueryStep, previous_results: Dict[str, Any]) -> List[Evidence]:
|
|
347
|
+
"""Execute a single query step"""
|
|
348
|
+
query = step.query
|
|
349
|
+
evidence = []
|
|
350
|
+
|
|
351
|
+
# Entity lookup
|
|
352
|
+
if query.query_type == QueryType.ENTITY_LOOKUP:
|
|
353
|
+
if query.entity_id:
|
|
354
|
+
entity = await self.graph_store.get_entity(query.entity_id)
|
|
355
|
+
if entity:
|
|
356
|
+
evidence.append(
|
|
357
|
+
Evidence(
|
|
358
|
+
evidence_id=f"ev_{uuid.uuid4().hex[:8]}",
|
|
359
|
+
evidence_type=EvidenceType.ENTITY,
|
|
360
|
+
entities=[entity],
|
|
361
|
+
confidence=1.0,
|
|
362
|
+
relevance_score=1.0,
|
|
363
|
+
explanation=f"Found entity: {entity.id}",
|
|
364
|
+
source=step.step_id,
|
|
365
|
+
)
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
# Vector search
|
|
369
|
+
elif query.query_type == QueryType.VECTOR_SEARCH:
|
|
370
|
+
if query.embedding:
|
|
371
|
+
results = await self.graph_store.vector_search(
|
|
372
|
+
query_embedding=query.embedding,
|
|
373
|
+
entity_type=query.entity_type,
|
|
374
|
+
max_results=query.max_results,
|
|
375
|
+
score_threshold=query.score_threshold,
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
for entity, score in results:
|
|
379
|
+
evidence.append(
|
|
380
|
+
Evidence(
|
|
381
|
+
evidence_id=f"ev_{uuid.uuid4().hex[:8]}",
|
|
382
|
+
evidence_type=EvidenceType.ENTITY,
|
|
383
|
+
entities=[entity],
|
|
384
|
+
confidence=score,
|
|
385
|
+
relevance_score=score,
|
|
386
|
+
explanation=f"Similar entity: {entity.id} (score: {score:.2f})",
|
|
387
|
+
source=step.step_id,
|
|
388
|
+
)
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
# Traversal
|
|
392
|
+
elif query.query_type == QueryType.TRAVERSAL:
|
|
393
|
+
import logging
|
|
394
|
+
|
|
395
|
+
logger = logging.getLogger(__name__)
|
|
396
|
+
|
|
397
|
+
# Get starting entities from previous steps or query
|
|
398
|
+
start_ids = []
|
|
399
|
+
if query.entity_id:
|
|
400
|
+
start_ids = [query.entity_id]
|
|
401
|
+
logger.debug(f"TRAVERSAL: Using entity_id from query: {query.entity_id}")
|
|
402
|
+
elif step.depends_on:
|
|
403
|
+
# Get entities from dependent steps
|
|
404
|
+
for dep_id in step.depends_on:
|
|
405
|
+
if dep_id in previous_results:
|
|
406
|
+
dep_evidence = previous_results[dep_id]
|
|
407
|
+
extracted_ids = [e.id for ev in dep_evidence for e in ev.entities]
|
|
408
|
+
start_ids.extend(extracted_ids)
|
|
409
|
+
logger.debug(f"TRAVERSAL: Extracted {len(extracted_ids)} entity IDs from step {dep_id}")
|
|
410
|
+
else:
|
|
411
|
+
logger.warning(f"TRAVERSAL: Dependent step {dep_id} not found in previous_results")
|
|
412
|
+
else:
|
|
413
|
+
logger.warning("TRAVERSAL: No entity_id and no dependencies. Cannot traverse.")
|
|
414
|
+
|
|
415
|
+
if not start_ids:
|
|
416
|
+
logger.warning(
|
|
417
|
+
f"TRAVERSAL step {step.step_id} has no starting entities. "
|
|
418
|
+
f"Query entity_id: {getattr(query, 'entity_id', None)}, "
|
|
419
|
+
f"Dependencies: {step.depends_on}, "
|
|
420
|
+
f"Previous results keys: {list(previous_results.keys())}"
|
|
421
|
+
)
|
|
422
|
+
else:
|
|
423
|
+
# Traverse from each starting entity
|
|
424
|
+
# Limit starting points
|
|
425
|
+
for start_id in start_ids[: query.max_results]:
|
|
426
|
+
try:
|
|
427
|
+
paths = await self.graph_store.traverse(
|
|
428
|
+
start_entity_id=start_id,
|
|
429
|
+
relation_type=query.relation_type,
|
|
430
|
+
max_depth=query.max_depth,
|
|
431
|
+
max_results=query.max_results,
|
|
432
|
+
)
|
|
433
|
+
logger.debug(f"TRAVERSAL: Found {len(paths)} paths from {start_id}")
|
|
434
|
+
|
|
435
|
+
# Convert paths to evidence
|
|
436
|
+
path_evidence = await self.collect_evidence_from_paths(paths, source=step.step_id)
|
|
437
|
+
evidence.extend(path_evidence)
|
|
438
|
+
logger.debug(f"TRAVERSAL: Collected {len(path_evidence)} evidence from {start_id}")
|
|
439
|
+
except Exception as e:
|
|
440
|
+
logger.error(
|
|
441
|
+
f"TRAVERSAL: Error traversing from {start_id}: {str(e)}",
|
|
442
|
+
exc_info=True,
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
# Path finding
|
|
446
|
+
elif query.query_type == QueryType.PATH_FINDING:
|
|
447
|
+
if query.source_entity_id and query.target_entity_id:
|
|
448
|
+
paths = await self.find_multi_hop_paths(
|
|
449
|
+
start_entity_id=query.source_entity_id,
|
|
450
|
+
target_entity_id=query.target_entity_id,
|
|
451
|
+
max_hops=query.max_depth,
|
|
452
|
+
max_paths=query.max_results,
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
path_evidence = await self.collect_evidence_from_paths(paths, source=step.step_id)
|
|
456
|
+
evidence.extend(path_evidence)
|
|
457
|
+
|
|
458
|
+
return evidence
|
|
459
|
+
|
|
460
|
+
def _rank_and_filter_evidence(self, evidence: List[Evidence], max_evidence: int) -> List[Evidence]:
|
|
461
|
+
"""Rank and filter evidence to top N"""
|
|
462
|
+
# Rank by combined score
|
|
463
|
+
ranked = self.rank_evidence(evidence, ranking_method="combined_score")
|
|
464
|
+
|
|
465
|
+
# Filter to top N
|
|
466
|
+
return ranked[:max_evidence]
|
|
467
|
+
|
|
468
|
+
def _generate_answer(self, query: str, evidence: List[Evidence]) -> Tuple[str, float]:
|
|
469
|
+
"""
|
|
470
|
+
Generate answer from evidence
|
|
471
|
+
|
|
472
|
+
Args:
|
|
473
|
+
query: Original query
|
|
474
|
+
evidence: Collected evidence
|
|
475
|
+
|
|
476
|
+
Returns:
|
|
477
|
+
(answer, confidence) tuple
|
|
478
|
+
"""
|
|
479
|
+
if not evidence:
|
|
480
|
+
return "No evidence found to answer the query.", 0.0
|
|
481
|
+
|
|
482
|
+
# Calculate overall confidence
|
|
483
|
+
if evidence:
|
|
484
|
+
confidence = sum(e.combined_score for e in evidence) / len(evidence)
|
|
485
|
+
else:
|
|
486
|
+
confidence = 0.0
|
|
487
|
+
|
|
488
|
+
# Generate answer based on evidence type
|
|
489
|
+
top_evidence = evidence[:5] # Top 5 pieces
|
|
490
|
+
|
|
491
|
+
# Collect unique entities from evidence
|
|
492
|
+
entity_ids = set()
|
|
493
|
+
entity_names = []
|
|
494
|
+
|
|
495
|
+
for ev in top_evidence:
|
|
496
|
+
for entity in ev.entities:
|
|
497
|
+
if entity.id not in entity_ids:
|
|
498
|
+
entity_ids.add(entity.id)
|
|
499
|
+
name = entity.properties.get("name", entity.id)
|
|
500
|
+
entity_type = entity.entity_type
|
|
501
|
+
entity_names.append(f"{name} ({entity_type})")
|
|
502
|
+
|
|
503
|
+
# Build answer
|
|
504
|
+
if len(entity_names) == 0:
|
|
505
|
+
answer = "No relevant entities found."
|
|
506
|
+
elif len(entity_names) == 1:
|
|
507
|
+
answer = f"Found: {entity_names[0]}"
|
|
508
|
+
elif len(entity_names) <= 3:
|
|
509
|
+
answer = f"Found: {', '.join(entity_names)}"
|
|
510
|
+
else:
|
|
511
|
+
answer = f"Found {len(entity_names)} entities: {', '.join(entity_names[:3])}, and {len(entity_names) - 3} more"
|
|
512
|
+
|
|
513
|
+
# Add path information if available
|
|
514
|
+
path_count = sum(1 for ev in top_evidence if ev.evidence_type == EvidenceType.PATH)
|
|
515
|
+
if path_count > 0:
|
|
516
|
+
answer += f" (through {path_count} connection{'s' if path_count != 1 else ''})"
|
|
517
|
+
|
|
518
|
+
return answer, confidence
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Knowledge Graph Retrieval Application Layer
|
|
3
|
+
|
|
4
|
+
Advanced retrieval strategies for knowledge graph queries.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from aiecs.application.knowledge_graph.retrieval.retrieval_strategies import (
|
|
8
|
+
PersonalizedPageRank,
|
|
9
|
+
MultiHopRetrieval,
|
|
10
|
+
FilteredRetrieval,
|
|
11
|
+
RetrievalCache,
|
|
12
|
+
)
|
|
13
|
+
from aiecs.application.knowledge_graph.retrieval.strategy_types import (
|
|
14
|
+
RetrievalStrategy,
|
|
15
|
+
)
|
|
16
|
+
from aiecs.application.knowledge_graph.retrieval.query_intent_classifier import (
|
|
17
|
+
QueryIntentClassifier,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"PersonalizedPageRank",
|
|
22
|
+
"MultiHopRetrieval",
|
|
23
|
+
"FilteredRetrieval",
|
|
24
|
+
"RetrievalCache",
|
|
25
|
+
"RetrievalStrategy",
|
|
26
|
+
"QueryIntentClassifier",
|
|
27
|
+
]
|