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,500 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Inference Engine
|
|
3
|
+
|
|
4
|
+
Rule-based logical inference over knowledge graphs.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import uuid
|
|
8
|
+
import time
|
|
9
|
+
from typing import List, Optional, Dict, Any, Set, Tuple
|
|
10
|
+
from collections import defaultdict
|
|
11
|
+
from aiecs.infrastructure.graph_storage.base import GraphStore
|
|
12
|
+
from aiecs.domain.knowledge_graph.models.relation import Relation
|
|
13
|
+
from aiecs.domain.knowledge_graph.models.inference_rule import (
|
|
14
|
+
InferenceRule,
|
|
15
|
+
InferenceStep,
|
|
16
|
+
InferenceResult,
|
|
17
|
+
RuleType,
|
|
18
|
+
)
|
|
19
|
+
from aiecs.domain.knowledge_graph.schema.relation_type import RelationType
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class InferenceCache:
|
|
23
|
+
"""
|
|
24
|
+
Cache for inference results
|
|
25
|
+
|
|
26
|
+
Stores previously computed inference results to avoid recomputation.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, max_size: int = 1000, ttl_seconds: Optional[float] = None):
|
|
30
|
+
"""
|
|
31
|
+
Initialize inference cache
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
max_size: Maximum number of cached entries
|
|
35
|
+
ttl_seconds: Time-to-live in seconds (None = no expiration)
|
|
36
|
+
"""
|
|
37
|
+
self.max_size = max_size
|
|
38
|
+
self.ttl_seconds = ttl_seconds
|
|
39
|
+
self._cache: Dict[str, Tuple[InferenceResult, float]] = {}
|
|
40
|
+
self._access_times: Dict[str, float] = {}
|
|
41
|
+
|
|
42
|
+
def _make_key(
|
|
43
|
+
self,
|
|
44
|
+
relation_type: str,
|
|
45
|
+
source_id: Optional[str] = None,
|
|
46
|
+
target_id: Optional[str] = None,
|
|
47
|
+
) -> str:
|
|
48
|
+
"""Create cache key"""
|
|
49
|
+
if source_id and target_id:
|
|
50
|
+
return f"{relation_type}:{source_id}:{target_id}"
|
|
51
|
+
elif source_id:
|
|
52
|
+
return f"{relation_type}:{source_id}:*"
|
|
53
|
+
elif target_id:
|
|
54
|
+
return f"{relation_type}:*:{target_id}"
|
|
55
|
+
else:
|
|
56
|
+
return f"{relation_type}:*:*"
|
|
57
|
+
|
|
58
|
+
def get(
|
|
59
|
+
self,
|
|
60
|
+
relation_type: str,
|
|
61
|
+
source_id: Optional[str] = None,
|
|
62
|
+
target_id: Optional[str] = None,
|
|
63
|
+
) -> Optional[InferenceResult]:
|
|
64
|
+
"""
|
|
65
|
+
Get cached inference result
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
relation_type: Relation type
|
|
69
|
+
source_id: Source entity ID
|
|
70
|
+
target_id: Target entity ID
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Cached result or None
|
|
74
|
+
"""
|
|
75
|
+
key = self._make_key(relation_type, source_id, target_id)
|
|
76
|
+
|
|
77
|
+
if key not in self._cache:
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
result, cached_time = self._cache[key]
|
|
81
|
+
|
|
82
|
+
# Check TTL
|
|
83
|
+
if self.ttl_seconds and (time.time() - cached_time) > self.ttl_seconds:
|
|
84
|
+
del self._cache[key]
|
|
85
|
+
if key in self._access_times:
|
|
86
|
+
del self._access_times[key]
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
# Update access time
|
|
90
|
+
self._access_times[key] = time.time()
|
|
91
|
+
return result
|
|
92
|
+
|
|
93
|
+
def put(
|
|
94
|
+
self,
|
|
95
|
+
relation_type: str,
|
|
96
|
+
result: InferenceResult,
|
|
97
|
+
source_id: Optional[str] = None,
|
|
98
|
+
target_id: Optional[str] = None,
|
|
99
|
+
) -> None:
|
|
100
|
+
"""
|
|
101
|
+
Cache inference result
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
relation_type: Relation type
|
|
105
|
+
result: Inference result to cache
|
|
106
|
+
source_id: Source entity ID
|
|
107
|
+
target_id: Target entity ID
|
|
108
|
+
"""
|
|
109
|
+
key = self._make_key(relation_type, source_id, target_id)
|
|
110
|
+
|
|
111
|
+
# Evict if cache is full (LRU)
|
|
112
|
+
if len(self._cache) >= self.max_size and key not in self._cache:
|
|
113
|
+
# Remove least recently used
|
|
114
|
+
lru_key = min(self._access_times.items(), key=lambda x: x[1])[0]
|
|
115
|
+
del self._cache[lru_key]
|
|
116
|
+
del self._access_times[lru_key]
|
|
117
|
+
|
|
118
|
+
self._cache[key] = (result, time.time())
|
|
119
|
+
self._access_times[key] = time.time()
|
|
120
|
+
|
|
121
|
+
def clear(self) -> None:
|
|
122
|
+
"""Clear all cached results"""
|
|
123
|
+
self._cache.clear()
|
|
124
|
+
self._access_times.clear()
|
|
125
|
+
|
|
126
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
127
|
+
"""Get cache statistics"""
|
|
128
|
+
return {
|
|
129
|
+
"size": len(self._cache),
|
|
130
|
+
"max_size": self.max_size,
|
|
131
|
+
"ttl_seconds": self.ttl_seconds,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class InferenceEngine:
|
|
136
|
+
"""
|
|
137
|
+
Rule-Based Inference Engine
|
|
138
|
+
|
|
139
|
+
Applies logical inference rules to infer new relations from existing ones.
|
|
140
|
+
|
|
141
|
+
Features:
|
|
142
|
+
- Transitive inference (A->B, B->C => A->C)
|
|
143
|
+
- Symmetric inference (A->B => B->A)
|
|
144
|
+
- Custom inference rules
|
|
145
|
+
- Result caching
|
|
146
|
+
- Explainability (trace inference steps)
|
|
147
|
+
|
|
148
|
+
Example:
|
|
149
|
+
```python
|
|
150
|
+
engine = InferenceEngine(graph_store)
|
|
151
|
+
|
|
152
|
+
# Add rules
|
|
153
|
+
engine.add_rule(InferenceRule(
|
|
154
|
+
rule_id="transitive_works_for",
|
|
155
|
+
rule_type=RuleType.TRANSITIVE,
|
|
156
|
+
relation_type="WORKS_FOR"
|
|
157
|
+
))
|
|
158
|
+
|
|
159
|
+
# Infer relations
|
|
160
|
+
result = await engine.infer_relations(
|
|
161
|
+
relation_type="WORKS_FOR",
|
|
162
|
+
max_steps=3
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
print(f"Inferred {len(result.inferred_relations)} relations")
|
|
166
|
+
print(result.get_explanation_string())
|
|
167
|
+
```
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
def __init__(self, graph_store: GraphStore, cache: Optional[InferenceCache] = None):
|
|
171
|
+
"""
|
|
172
|
+
Initialize inference engine
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
graph_store: Graph storage backend
|
|
176
|
+
cache: Optional inference cache (creates one if not provided)
|
|
177
|
+
"""
|
|
178
|
+
self.graph_store = graph_store
|
|
179
|
+
self.cache = cache or InferenceCache()
|
|
180
|
+
self.rules: Dict[str, InferenceRule] = {}
|
|
181
|
+
self.relation_type_schemas: Dict[str, RelationType] = {}
|
|
182
|
+
|
|
183
|
+
def add_rule(self, rule: InferenceRule) -> None:
|
|
184
|
+
"""
|
|
185
|
+
Add an inference rule
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
rule: Inference rule to add
|
|
189
|
+
"""
|
|
190
|
+
self.rules[rule.rule_id] = rule
|
|
191
|
+
|
|
192
|
+
def remove_rule(self, rule_id: str) -> None:
|
|
193
|
+
"""
|
|
194
|
+
Remove an inference rule
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
rule_id: ID of rule to remove
|
|
198
|
+
"""
|
|
199
|
+
if rule_id in self.rules:
|
|
200
|
+
del self.rules[rule_id]
|
|
201
|
+
|
|
202
|
+
def get_rules(self, relation_type: Optional[str] = None) -> List[InferenceRule]:
|
|
203
|
+
"""
|
|
204
|
+
Get inference rules
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
relation_type: Filter by relation type (None = all)
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
List of inference rules
|
|
211
|
+
"""
|
|
212
|
+
rules = list(self.rules.values())
|
|
213
|
+
if relation_type:
|
|
214
|
+
rules = [r for r in rules if r.relation_type == relation_type]
|
|
215
|
+
return rules
|
|
216
|
+
|
|
217
|
+
async def infer_relations(
|
|
218
|
+
self,
|
|
219
|
+
relation_type: str,
|
|
220
|
+
max_steps: int = 10,
|
|
221
|
+
source_id: Optional[str] = None,
|
|
222
|
+
target_id: Optional[str] = None,
|
|
223
|
+
use_cache: bool = True,
|
|
224
|
+
) -> InferenceResult:
|
|
225
|
+
"""
|
|
226
|
+
Infer relations using enabled rules
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
relation_type: Relation type to infer
|
|
230
|
+
max_steps: Maximum number of inference steps
|
|
231
|
+
source_id: Optional source entity ID filter
|
|
232
|
+
target_id: Optional target entity ID filter
|
|
233
|
+
use_cache: Whether to use cache
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
Inference result with inferred relations and steps
|
|
237
|
+
"""
|
|
238
|
+
# Check cache
|
|
239
|
+
if use_cache:
|
|
240
|
+
cached = self.cache.get(relation_type, source_id, target_id)
|
|
241
|
+
if cached:
|
|
242
|
+
return cached
|
|
243
|
+
|
|
244
|
+
time.time()
|
|
245
|
+
inferred_relations: List[Relation] = []
|
|
246
|
+
inference_steps: List[InferenceStep] = []
|
|
247
|
+
# Track inferred relation IDs to avoid duplicates
|
|
248
|
+
visited: Set[str] = set()
|
|
249
|
+
|
|
250
|
+
# Get applicable rules
|
|
251
|
+
applicable_rules = [rule for rule in self.get_rules(relation_type) if rule.enabled]
|
|
252
|
+
|
|
253
|
+
if not applicable_rules:
|
|
254
|
+
result = InferenceResult(
|
|
255
|
+
inferred_relations=[],
|
|
256
|
+
inference_steps=[],
|
|
257
|
+
total_steps=0,
|
|
258
|
+
confidence=0.0,
|
|
259
|
+
explanation=f"No inference rules enabled for relation type: {relation_type}",
|
|
260
|
+
)
|
|
261
|
+
return result
|
|
262
|
+
|
|
263
|
+
# Get existing relations by traversing the graph
|
|
264
|
+
# We'll collect relations as we discover them through inference
|
|
265
|
+
# Start with relations we can find through get_neighbors
|
|
266
|
+
existing_relations: List[Relation] = []
|
|
267
|
+
|
|
268
|
+
# For inference, we'll discover relations as we traverse
|
|
269
|
+
# This is a limitation of the current GraphStore interface
|
|
270
|
+
# In practice, we'd query for all relations of a type directly
|
|
271
|
+
|
|
272
|
+
# Apply rules iteratively
|
|
273
|
+
current_relations = existing_relations.copy()
|
|
274
|
+
step_count = 0
|
|
275
|
+
|
|
276
|
+
while step_count < max_steps:
|
|
277
|
+
new_relations = []
|
|
278
|
+
|
|
279
|
+
for rule in applicable_rules:
|
|
280
|
+
if rule.rule_type == RuleType.TRANSITIVE:
|
|
281
|
+
inferred = await self._apply_transitive_rule(rule, current_relations, visited)
|
|
282
|
+
new_relations.extend(inferred)
|
|
283
|
+
elif rule.rule_type == RuleType.SYMMETRIC:
|
|
284
|
+
inferred = await self._apply_symmetric_rule(rule, current_relations, visited)
|
|
285
|
+
new_relations.extend(inferred)
|
|
286
|
+
|
|
287
|
+
if not new_relations:
|
|
288
|
+
break # No new relations inferred
|
|
289
|
+
|
|
290
|
+
# Add new relations
|
|
291
|
+
for rel, step in new_relations:
|
|
292
|
+
if rel.id not in visited:
|
|
293
|
+
inferred_relations.append(rel)
|
|
294
|
+
inference_steps.append(step)
|
|
295
|
+
visited.add(rel.id)
|
|
296
|
+
current_relations.append(rel)
|
|
297
|
+
|
|
298
|
+
step_count += 1
|
|
299
|
+
|
|
300
|
+
# Calculate overall confidence
|
|
301
|
+
if inference_steps:
|
|
302
|
+
confidence = sum(step.confidence for step in inference_steps) / len(inference_steps)
|
|
303
|
+
else:
|
|
304
|
+
confidence = 0.0
|
|
305
|
+
|
|
306
|
+
# Create result
|
|
307
|
+
result = InferenceResult(
|
|
308
|
+
inferred_relations=inferred_relations,
|
|
309
|
+
inference_steps=inference_steps,
|
|
310
|
+
total_steps=step_count,
|
|
311
|
+
confidence=confidence,
|
|
312
|
+
explanation=f"Inferred {len(inferred_relations)} relations using {len(applicable_rules)} rules in {step_count} steps",
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
# Cache result
|
|
316
|
+
if use_cache:
|
|
317
|
+
self.cache.put(relation_type, result, source_id, target_id)
|
|
318
|
+
|
|
319
|
+
return result
|
|
320
|
+
|
|
321
|
+
async def _get_relations(self, relation_type: str) -> List[Relation]:
|
|
322
|
+
"""
|
|
323
|
+
Get all relations of a given type
|
|
324
|
+
|
|
325
|
+
Note: This is a simplified implementation that uses traversal.
|
|
326
|
+
In production, GraphStore should have a get_relations_by_type method.
|
|
327
|
+
"""
|
|
328
|
+
relations: List[Relation] = []
|
|
329
|
+
# visited_entities: Set[str] = set() # Reserved for future use
|
|
330
|
+
|
|
331
|
+
# Get all entities (we'll need to traverse to find them)
|
|
332
|
+
# For now, we'll collect relations as we traverse
|
|
333
|
+
# This is inefficient but works for the current interface
|
|
334
|
+
|
|
335
|
+
# Try to get relations from paths
|
|
336
|
+
# We'll use a simple approach: traverse from a few entities
|
|
337
|
+
# In practice, this should be optimized in GraphStore
|
|
338
|
+
|
|
339
|
+
return relations
|
|
340
|
+
|
|
341
|
+
async def _apply_transitive_rule(self, rule: InferenceRule, relations: List[Relation], visited: Set[str]) -> List[Tuple[Relation, InferenceStep]]:
|
|
342
|
+
"""
|
|
343
|
+
Apply transitive rule: A->B, B->C => A->C
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
rule: Transitive rule to apply
|
|
347
|
+
relations: Existing relations
|
|
348
|
+
visited: Set of already inferred relation IDs
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
List of (inferred_relation, inference_step) tuples
|
|
352
|
+
"""
|
|
353
|
+
inferred = []
|
|
354
|
+
|
|
355
|
+
# Build index: source -> target
|
|
356
|
+
source_to_targets: Dict[str, List[Relation]] = defaultdict(list)
|
|
357
|
+
for rel in relations:
|
|
358
|
+
if rel.relation_type == rule.relation_type:
|
|
359
|
+
source_to_targets[rel.source_id].append(rel)
|
|
360
|
+
|
|
361
|
+
# Find transitive chains
|
|
362
|
+
for rel1 in relations:
|
|
363
|
+
if rel1.relation_type != rule.relation_type:
|
|
364
|
+
continue
|
|
365
|
+
|
|
366
|
+
# rel1: A -> B
|
|
367
|
+
# Find relations where B is source: B -> C
|
|
368
|
+
for rel2 in source_to_targets.get(rel1.target_id, []):
|
|
369
|
+
# Check if A -> C already exists
|
|
370
|
+
inferred_id = f"inf_{rel1.source_id}_{rel2.target_id}_{rule.relation_type}"
|
|
371
|
+
|
|
372
|
+
if inferred_id in visited:
|
|
373
|
+
continue
|
|
374
|
+
|
|
375
|
+
# Check if relation already exists
|
|
376
|
+
existing = await self.graph_store.get_relation(inferred_id)
|
|
377
|
+
if existing:
|
|
378
|
+
continue
|
|
379
|
+
|
|
380
|
+
# Create inferred relation
|
|
381
|
+
# Confidence decays with each step
|
|
382
|
+
confidence = min(rel1.weight, rel2.weight) * (1.0 - rule.confidence_decay)
|
|
383
|
+
|
|
384
|
+
inferred_rel = Relation(
|
|
385
|
+
id=inferred_id,
|
|
386
|
+
relation_type=rule.relation_type,
|
|
387
|
+
source_id=rel1.source_id,
|
|
388
|
+
target_id=rel2.target_id,
|
|
389
|
+
weight=confidence,
|
|
390
|
+
properties={
|
|
391
|
+
"inferred": True,
|
|
392
|
+
"source_relations": [rel1.id, rel2.id],
|
|
393
|
+
"rule_id": rule.rule_id,
|
|
394
|
+
},
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
# Create inference step
|
|
398
|
+
step = InferenceStep(
|
|
399
|
+
step_id=f"step_{uuid.uuid4().hex[:8]}",
|
|
400
|
+
inferred_relation=inferred_rel,
|
|
401
|
+
source_relations=[rel1, rel2],
|
|
402
|
+
rule=rule,
|
|
403
|
+
confidence=confidence,
|
|
404
|
+
explanation=f"Transitive: {rel1.source_id} -> {rel1.target_id} -> {rel2.target_id} => {rel1.source_id} -> {rel2.target_id}",
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
inferred.append((inferred_rel, step))
|
|
408
|
+
|
|
409
|
+
return inferred
|
|
410
|
+
|
|
411
|
+
async def _apply_symmetric_rule(self, rule: InferenceRule, relations: List[Relation], visited: Set[str]) -> List[Tuple[Relation, InferenceStep]]:
|
|
412
|
+
"""
|
|
413
|
+
Apply symmetric rule: A->B => B->A
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
rule: Symmetric rule to apply
|
|
417
|
+
relations: Existing relations
|
|
418
|
+
visited: Set of already inferred relation IDs
|
|
419
|
+
|
|
420
|
+
Returns:
|
|
421
|
+
List of (inferred_relation, inference_step) tuples
|
|
422
|
+
"""
|
|
423
|
+
inferred = []
|
|
424
|
+
|
|
425
|
+
# Build set of existing relations (source, target) pairs
|
|
426
|
+
existing_pairs = set()
|
|
427
|
+
for rel in relations:
|
|
428
|
+
if rel.relation_type == rule.relation_type:
|
|
429
|
+
existing_pairs.add((rel.source_id, rel.target_id))
|
|
430
|
+
|
|
431
|
+
# Find relations that need symmetric inference
|
|
432
|
+
for rel in relations:
|
|
433
|
+
if rel.relation_type != rule.relation_type:
|
|
434
|
+
continue
|
|
435
|
+
|
|
436
|
+
# Check if reverse already exists
|
|
437
|
+
reverse_pair = (rel.target_id, rel.source_id)
|
|
438
|
+
if reverse_pair in existing_pairs:
|
|
439
|
+
continue
|
|
440
|
+
|
|
441
|
+
# Check if already inferred
|
|
442
|
+
inferred_id = f"inf_{rel.target_id}_{rel.source_id}_{rule.relation_type}"
|
|
443
|
+
if inferred_id in visited:
|
|
444
|
+
continue
|
|
445
|
+
|
|
446
|
+
# Check if relation already exists
|
|
447
|
+
existing = await self.graph_store.get_relation(inferred_id)
|
|
448
|
+
if existing:
|
|
449
|
+
continue
|
|
450
|
+
|
|
451
|
+
# Create inferred relation
|
|
452
|
+
# Confidence slightly lower than original
|
|
453
|
+
confidence = rel.weight * (1.0 - rule.confidence_decay)
|
|
454
|
+
|
|
455
|
+
inferred_rel = Relation(
|
|
456
|
+
id=inferred_id,
|
|
457
|
+
relation_type=rule.relation_type,
|
|
458
|
+
source_id=rel.target_id,
|
|
459
|
+
target_id=rel.source_id,
|
|
460
|
+
weight=confidence,
|
|
461
|
+
properties={
|
|
462
|
+
"inferred": True,
|
|
463
|
+
"source_relations": [rel.id],
|
|
464
|
+
"rule_id": rule.rule_id,
|
|
465
|
+
},
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
# Create inference step
|
|
469
|
+
step = InferenceStep(
|
|
470
|
+
step_id=f"step_{uuid.uuid4().hex[:8]}",
|
|
471
|
+
inferred_relation=inferred_rel,
|
|
472
|
+
source_relations=[rel],
|
|
473
|
+
rule=rule,
|
|
474
|
+
confidence=confidence,
|
|
475
|
+
explanation=f"Symmetric: {rel.source_id} -> {rel.target_id} => {rel.target_id} -> {rel.source_id}",
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
inferred.append((inferred_rel, step))
|
|
479
|
+
|
|
480
|
+
return inferred
|
|
481
|
+
|
|
482
|
+
def get_inference_trace(self, result: InferenceResult) -> List[str]:
|
|
483
|
+
"""
|
|
484
|
+
Get human-readable trace of inference steps
|
|
485
|
+
|
|
486
|
+
Args:
|
|
487
|
+
result: Inference result
|
|
488
|
+
|
|
489
|
+
Returns:
|
|
490
|
+
List of trace strings
|
|
491
|
+
"""
|
|
492
|
+
trace = []
|
|
493
|
+
trace.append(f"Inference trace for {result.total_steps} steps:")
|
|
494
|
+
|
|
495
|
+
for i, step in enumerate(result.inference_steps, 1):
|
|
496
|
+
trace.append(f" Step {i}: {step.explanation}")
|
|
497
|
+
trace.append(f" Confidence: {step.confidence:.2f}")
|
|
498
|
+
trace.append(f" Rule: {step.rule.rule_id} ({step.rule.rule_type})")
|
|
499
|
+
|
|
500
|
+
return trace
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Logic Form Parser
|
|
3
|
+
|
|
4
|
+
Wrapper around LogicQueryParser for converting natural language queries
|
|
5
|
+
to structured logical forms.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Dict, Any, List, Optional
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from enum import Enum
|
|
11
|
+
|
|
12
|
+
from aiecs.application.knowledge_graph.reasoning.logic_parser import (
|
|
13
|
+
LogicQueryParser,
|
|
14
|
+
)
|
|
15
|
+
from aiecs.domain.knowledge_graph.schema.schema_manager import SchemaManager
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class QueryType(str, Enum):
|
|
19
|
+
"""Query type enumeration"""
|
|
20
|
+
|
|
21
|
+
SELECT = "SELECT"
|
|
22
|
+
ASK = "ASK"
|
|
23
|
+
COUNT = "COUNT"
|
|
24
|
+
FIND = "FIND"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class Variable:
|
|
29
|
+
"""Query variable"""
|
|
30
|
+
|
|
31
|
+
name: str
|
|
32
|
+
type: Optional[str] = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class Predicate:
|
|
37
|
+
"""Logical predicate"""
|
|
38
|
+
|
|
39
|
+
name: str
|
|
40
|
+
arguments: List[Any] = field(default_factory=list)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class Constraint:
|
|
45
|
+
"""Query constraint"""
|
|
46
|
+
|
|
47
|
+
constraint_type: str
|
|
48
|
+
variable: Variable
|
|
49
|
+
value: Any
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class LogicalQuery:
|
|
54
|
+
"""
|
|
55
|
+
Structured logical query representation
|
|
56
|
+
|
|
57
|
+
Represents a parsed natural language query as a logical form
|
|
58
|
+
with variables, predicates, and constraints.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
query_type: QueryType
|
|
62
|
+
variables: List[Variable] = field(default_factory=list)
|
|
63
|
+
predicates: List[Predicate] = field(default_factory=list)
|
|
64
|
+
constraints: List[Constraint] = field(default_factory=list)
|
|
65
|
+
raw_query: str = ""
|
|
66
|
+
|
|
67
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
68
|
+
"""Convert to dictionary representation"""
|
|
69
|
+
return {
|
|
70
|
+
"query_type": self.query_type.value,
|
|
71
|
+
"variables": [{"name": v.name, "type": v.type} for v in self.variables],
|
|
72
|
+
"predicates": [
|
|
73
|
+
{
|
|
74
|
+
"name": p.name,
|
|
75
|
+
"arguments": [arg.name if hasattr(arg, "name") else str(arg) for arg in p.arguments],
|
|
76
|
+
}
|
|
77
|
+
for p in self.predicates
|
|
78
|
+
],
|
|
79
|
+
"constraints": [
|
|
80
|
+
{
|
|
81
|
+
"type": c.constraint_type,
|
|
82
|
+
"variable": c.variable.name,
|
|
83
|
+
"value": c.value,
|
|
84
|
+
}
|
|
85
|
+
for c in self.constraints
|
|
86
|
+
],
|
|
87
|
+
"raw_query": self.raw_query,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class LogicFormParser:
|
|
92
|
+
"""
|
|
93
|
+
Logic Form Parser
|
|
94
|
+
|
|
95
|
+
Converts natural language queries into structured logical forms.
|
|
96
|
+
Uses LogicQueryParser internally for parsing.
|
|
97
|
+
|
|
98
|
+
Example:
|
|
99
|
+
```python
|
|
100
|
+
parser = LogicFormParser()
|
|
101
|
+
logical_query = parser.parse("Find all people who work for companies in San Francisco")
|
|
102
|
+
|
|
103
|
+
print(f"Query type: {logical_query.query_type}")
|
|
104
|
+
print(f"Variables: {[v.name for v in logical_query.variables]}")
|
|
105
|
+
print(f"Predicates: {[p.name for p in logical_query.predicates]}")
|
|
106
|
+
```
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
def __init__(self, schema_manager: Optional[SchemaManager] = None):
|
|
110
|
+
"""
|
|
111
|
+
Initialize logic form parser
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
schema_manager: Optional schema manager for type validation
|
|
115
|
+
"""
|
|
116
|
+
self.schema_manager = schema_manager
|
|
117
|
+
# Type annotation for optional attribute
|
|
118
|
+
self.query_parser: Optional[LogicQueryParser]
|
|
119
|
+
if schema_manager:
|
|
120
|
+
self.query_parser = LogicQueryParser(schema_manager) # type: ignore[assignment]
|
|
121
|
+
else:
|
|
122
|
+
# Create a minimal schema manager if none provided
|
|
123
|
+
self.query_parser = None
|
|
124
|
+
|
|
125
|
+
def parse(self, query: str) -> LogicalQuery:
|
|
126
|
+
"""
|
|
127
|
+
Parse natural language query to logical form
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
query: Natural language query string
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
LogicalQuery with structured representation
|
|
134
|
+
"""
|
|
135
|
+
# Simple heuristic-based parsing for now
|
|
136
|
+
# In a full implementation, this would use NLP or the LogicQueryParser
|
|
137
|
+
|
|
138
|
+
logical_query = LogicalQuery(query_type=QueryType.FIND, raw_query=query)
|
|
139
|
+
|
|
140
|
+
# Extract variables (simple pattern matching)
|
|
141
|
+
if "people" in query.lower() or "person" in query.lower():
|
|
142
|
+
logical_query.variables.append(Variable(name="?person", type="Person"))
|
|
143
|
+
if "companies" in query.lower() or "company" in query.lower():
|
|
144
|
+
logical_query.variables.append(Variable(name="?company", type="Company"))
|
|
145
|
+
|
|
146
|
+
# Extract predicates
|
|
147
|
+
if "work for" in query.lower() or "works for" in query.lower():
|
|
148
|
+
logical_query.predicates.append(Predicate(name="WORKS_FOR", arguments=["?person", "?company"]))
|
|
149
|
+
if "located in" in query.lower():
|
|
150
|
+
logical_query.predicates.append(Predicate(name="LOCATED_IN", arguments=["?company", "San Francisco"]))
|
|
151
|
+
|
|
152
|
+
# Extract constraints
|
|
153
|
+
if "in San Francisco" in query or "in san francisco" in query.lower():
|
|
154
|
+
if logical_query.variables:
|
|
155
|
+
logical_query.constraints.append(
|
|
156
|
+
Constraint(
|
|
157
|
+
constraint_type="property_equals",
|
|
158
|
+
variable=logical_query.variables[-1],
|
|
159
|
+
value="San Francisco",
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
return logical_query
|