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.

Files changed (340) hide show
  1. aiecs/__init__.py +13 -16
  2. aiecs/__main__.py +7 -7
  3. aiecs/aiecs_client.py +269 -75
  4. aiecs/application/executors/operation_executor.py +79 -54
  5. aiecs/application/knowledge_graph/__init__.py +7 -0
  6. aiecs/application/knowledge_graph/builder/__init__.py +37 -0
  7. aiecs/application/knowledge_graph/builder/data_quality.py +302 -0
  8. aiecs/application/knowledge_graph/builder/data_reshaping.py +293 -0
  9. aiecs/application/knowledge_graph/builder/document_builder.py +369 -0
  10. aiecs/application/knowledge_graph/builder/graph_builder.py +490 -0
  11. aiecs/application/knowledge_graph/builder/import_optimizer.py +396 -0
  12. aiecs/application/knowledge_graph/builder/schema_inference.py +462 -0
  13. aiecs/application/knowledge_graph/builder/schema_mapping.py +563 -0
  14. aiecs/application/knowledge_graph/builder/structured_pipeline.py +1384 -0
  15. aiecs/application/knowledge_graph/builder/text_chunker.py +317 -0
  16. aiecs/application/knowledge_graph/extractors/__init__.py +27 -0
  17. aiecs/application/knowledge_graph/extractors/base.py +98 -0
  18. aiecs/application/knowledge_graph/extractors/llm_entity_extractor.py +422 -0
  19. aiecs/application/knowledge_graph/extractors/llm_relation_extractor.py +347 -0
  20. aiecs/application/knowledge_graph/extractors/ner_entity_extractor.py +241 -0
  21. aiecs/application/knowledge_graph/fusion/__init__.py +78 -0
  22. aiecs/application/knowledge_graph/fusion/ab_testing.py +395 -0
  23. aiecs/application/knowledge_graph/fusion/abbreviation_expander.py +327 -0
  24. aiecs/application/knowledge_graph/fusion/alias_index.py +597 -0
  25. aiecs/application/knowledge_graph/fusion/alias_matcher.py +384 -0
  26. aiecs/application/knowledge_graph/fusion/cache_coordinator.py +343 -0
  27. aiecs/application/knowledge_graph/fusion/entity_deduplicator.py +433 -0
  28. aiecs/application/knowledge_graph/fusion/entity_linker.py +511 -0
  29. aiecs/application/knowledge_graph/fusion/evaluation_dataset.py +240 -0
  30. aiecs/application/knowledge_graph/fusion/knowledge_fusion.py +632 -0
  31. aiecs/application/knowledge_graph/fusion/matching_config.py +489 -0
  32. aiecs/application/knowledge_graph/fusion/name_normalizer.py +352 -0
  33. aiecs/application/knowledge_graph/fusion/relation_deduplicator.py +183 -0
  34. aiecs/application/knowledge_graph/fusion/semantic_name_matcher.py +464 -0
  35. aiecs/application/knowledge_graph/fusion/similarity_pipeline.py +534 -0
  36. aiecs/application/knowledge_graph/pattern_matching/__init__.py +21 -0
  37. aiecs/application/knowledge_graph/pattern_matching/pattern_matcher.py +342 -0
  38. aiecs/application/knowledge_graph/pattern_matching/query_executor.py +366 -0
  39. aiecs/application/knowledge_graph/profiling/__init__.py +12 -0
  40. aiecs/application/knowledge_graph/profiling/query_plan_visualizer.py +195 -0
  41. aiecs/application/knowledge_graph/profiling/query_profiler.py +223 -0
  42. aiecs/application/knowledge_graph/reasoning/__init__.py +27 -0
  43. aiecs/application/knowledge_graph/reasoning/evidence_synthesis.py +341 -0
  44. aiecs/application/knowledge_graph/reasoning/inference_engine.py +500 -0
  45. aiecs/application/knowledge_graph/reasoning/logic_form_parser.py +163 -0
  46. aiecs/application/knowledge_graph/reasoning/logic_parser/__init__.py +79 -0
  47. aiecs/application/knowledge_graph/reasoning/logic_parser/ast_builder.py +513 -0
  48. aiecs/application/knowledge_graph/reasoning/logic_parser/ast_nodes.py +913 -0
  49. aiecs/application/knowledge_graph/reasoning/logic_parser/ast_validator.py +866 -0
  50. aiecs/application/knowledge_graph/reasoning/logic_parser/error_handler.py +475 -0
  51. aiecs/application/knowledge_graph/reasoning/logic_parser/parser.py +396 -0
  52. aiecs/application/knowledge_graph/reasoning/logic_parser/query_context.py +208 -0
  53. aiecs/application/knowledge_graph/reasoning/logic_query_integration.py +170 -0
  54. aiecs/application/knowledge_graph/reasoning/query_planner.py +855 -0
  55. aiecs/application/knowledge_graph/reasoning/reasoning_engine.py +518 -0
  56. aiecs/application/knowledge_graph/retrieval/__init__.py +27 -0
  57. aiecs/application/knowledge_graph/retrieval/query_intent_classifier.py +211 -0
  58. aiecs/application/knowledge_graph/retrieval/retrieval_strategies.py +592 -0
  59. aiecs/application/knowledge_graph/retrieval/strategy_types.py +23 -0
  60. aiecs/application/knowledge_graph/search/__init__.py +59 -0
  61. aiecs/application/knowledge_graph/search/hybrid_search.py +457 -0
  62. aiecs/application/knowledge_graph/search/reranker.py +293 -0
  63. aiecs/application/knowledge_graph/search/reranker_strategies.py +535 -0
  64. aiecs/application/knowledge_graph/search/text_similarity.py +392 -0
  65. aiecs/application/knowledge_graph/traversal/__init__.py +15 -0
  66. aiecs/application/knowledge_graph/traversal/enhanced_traversal.py +305 -0
  67. aiecs/application/knowledge_graph/traversal/path_scorer.py +271 -0
  68. aiecs/application/knowledge_graph/validators/__init__.py +13 -0
  69. aiecs/application/knowledge_graph/validators/relation_validator.py +239 -0
  70. aiecs/application/knowledge_graph/visualization/__init__.py +11 -0
  71. aiecs/application/knowledge_graph/visualization/graph_visualizer.py +313 -0
  72. aiecs/common/__init__.py +9 -0
  73. aiecs/common/knowledge_graph/__init__.py +17 -0
  74. aiecs/common/knowledge_graph/runnable.py +471 -0
  75. aiecs/config/__init__.py +20 -5
  76. aiecs/config/config.py +762 -31
  77. aiecs/config/graph_config.py +131 -0
  78. aiecs/config/tool_config.py +399 -0
  79. aiecs/core/__init__.py +29 -13
  80. aiecs/core/interface/__init__.py +2 -2
  81. aiecs/core/interface/execution_interface.py +22 -22
  82. aiecs/core/interface/storage_interface.py +37 -88
  83. aiecs/core/registry/__init__.py +31 -0
  84. aiecs/core/registry/service_registry.py +92 -0
  85. aiecs/domain/__init__.py +270 -1
  86. aiecs/domain/agent/__init__.py +191 -0
  87. aiecs/domain/agent/base_agent.py +3870 -0
  88. aiecs/domain/agent/exceptions.py +99 -0
  89. aiecs/domain/agent/graph_aware_mixin.py +569 -0
  90. aiecs/domain/agent/hybrid_agent.py +1435 -0
  91. aiecs/domain/agent/integration/__init__.py +29 -0
  92. aiecs/domain/agent/integration/context_compressor.py +216 -0
  93. aiecs/domain/agent/integration/context_engine_adapter.py +587 -0
  94. aiecs/domain/agent/integration/protocols.py +281 -0
  95. aiecs/domain/agent/integration/retry_policy.py +218 -0
  96. aiecs/domain/agent/integration/role_config.py +213 -0
  97. aiecs/domain/agent/knowledge_aware_agent.py +1892 -0
  98. aiecs/domain/agent/lifecycle.py +291 -0
  99. aiecs/domain/agent/llm_agent.py +692 -0
  100. aiecs/domain/agent/memory/__init__.py +12 -0
  101. aiecs/domain/agent/memory/conversation.py +1124 -0
  102. aiecs/domain/agent/migration/__init__.py +14 -0
  103. aiecs/domain/agent/migration/conversion.py +163 -0
  104. aiecs/domain/agent/migration/legacy_wrapper.py +86 -0
  105. aiecs/domain/agent/models.py +884 -0
  106. aiecs/domain/agent/observability.py +479 -0
  107. aiecs/domain/agent/persistence.py +449 -0
  108. aiecs/domain/agent/prompts/__init__.py +29 -0
  109. aiecs/domain/agent/prompts/builder.py +159 -0
  110. aiecs/domain/agent/prompts/formatters.py +187 -0
  111. aiecs/domain/agent/prompts/template.py +255 -0
  112. aiecs/domain/agent/registry.py +253 -0
  113. aiecs/domain/agent/tool_agent.py +444 -0
  114. aiecs/domain/agent/tools/__init__.py +15 -0
  115. aiecs/domain/agent/tools/schema_generator.py +364 -0
  116. aiecs/domain/community/__init__.py +155 -0
  117. aiecs/domain/community/agent_adapter.py +469 -0
  118. aiecs/domain/community/analytics.py +432 -0
  119. aiecs/domain/community/collaborative_workflow.py +648 -0
  120. aiecs/domain/community/communication_hub.py +634 -0
  121. aiecs/domain/community/community_builder.py +320 -0
  122. aiecs/domain/community/community_integration.py +796 -0
  123. aiecs/domain/community/community_manager.py +803 -0
  124. aiecs/domain/community/decision_engine.py +849 -0
  125. aiecs/domain/community/exceptions.py +231 -0
  126. aiecs/domain/community/models/__init__.py +33 -0
  127. aiecs/domain/community/models/community_models.py +234 -0
  128. aiecs/domain/community/resource_manager.py +461 -0
  129. aiecs/domain/community/shared_context_manager.py +589 -0
  130. aiecs/domain/context/__init__.py +40 -10
  131. aiecs/domain/context/context_engine.py +1910 -0
  132. aiecs/domain/context/conversation_models.py +87 -53
  133. aiecs/domain/context/graph_memory.py +582 -0
  134. aiecs/domain/execution/model.py +12 -4
  135. aiecs/domain/knowledge_graph/__init__.py +19 -0
  136. aiecs/domain/knowledge_graph/models/__init__.py +52 -0
  137. aiecs/domain/knowledge_graph/models/entity.py +148 -0
  138. aiecs/domain/knowledge_graph/models/evidence.py +178 -0
  139. aiecs/domain/knowledge_graph/models/inference_rule.py +184 -0
  140. aiecs/domain/knowledge_graph/models/path.py +171 -0
  141. aiecs/domain/knowledge_graph/models/path_pattern.py +171 -0
  142. aiecs/domain/knowledge_graph/models/query.py +261 -0
  143. aiecs/domain/knowledge_graph/models/query_plan.py +181 -0
  144. aiecs/domain/knowledge_graph/models/relation.py +202 -0
  145. aiecs/domain/knowledge_graph/schema/__init__.py +23 -0
  146. aiecs/domain/knowledge_graph/schema/entity_type.py +131 -0
  147. aiecs/domain/knowledge_graph/schema/graph_schema.py +253 -0
  148. aiecs/domain/knowledge_graph/schema/property_schema.py +143 -0
  149. aiecs/domain/knowledge_graph/schema/relation_type.py +163 -0
  150. aiecs/domain/knowledge_graph/schema/schema_manager.py +691 -0
  151. aiecs/domain/knowledge_graph/schema/type_enums.py +209 -0
  152. aiecs/domain/task/dsl_processor.py +172 -56
  153. aiecs/domain/task/model.py +20 -8
  154. aiecs/domain/task/task_context.py +27 -24
  155. aiecs/infrastructure/__init__.py +0 -2
  156. aiecs/infrastructure/graph_storage/__init__.py +11 -0
  157. aiecs/infrastructure/graph_storage/base.py +837 -0
  158. aiecs/infrastructure/graph_storage/batch_operations.py +458 -0
  159. aiecs/infrastructure/graph_storage/cache.py +424 -0
  160. aiecs/infrastructure/graph_storage/distributed.py +223 -0
  161. aiecs/infrastructure/graph_storage/error_handling.py +380 -0
  162. aiecs/infrastructure/graph_storage/graceful_degradation.py +294 -0
  163. aiecs/infrastructure/graph_storage/health_checks.py +378 -0
  164. aiecs/infrastructure/graph_storage/in_memory.py +1197 -0
  165. aiecs/infrastructure/graph_storage/index_optimization.py +446 -0
  166. aiecs/infrastructure/graph_storage/lazy_loading.py +431 -0
  167. aiecs/infrastructure/graph_storage/metrics.py +344 -0
  168. aiecs/infrastructure/graph_storage/migration.py +400 -0
  169. aiecs/infrastructure/graph_storage/pagination.py +483 -0
  170. aiecs/infrastructure/graph_storage/performance_monitoring.py +456 -0
  171. aiecs/infrastructure/graph_storage/postgres.py +1563 -0
  172. aiecs/infrastructure/graph_storage/property_storage.py +353 -0
  173. aiecs/infrastructure/graph_storage/protocols.py +76 -0
  174. aiecs/infrastructure/graph_storage/query_optimizer.py +642 -0
  175. aiecs/infrastructure/graph_storage/schema_cache.py +290 -0
  176. aiecs/infrastructure/graph_storage/sqlite.py +1373 -0
  177. aiecs/infrastructure/graph_storage/streaming.py +487 -0
  178. aiecs/infrastructure/graph_storage/tenant.py +412 -0
  179. aiecs/infrastructure/messaging/celery_task_manager.py +92 -54
  180. aiecs/infrastructure/messaging/websocket_manager.py +51 -35
  181. aiecs/infrastructure/monitoring/__init__.py +22 -0
  182. aiecs/infrastructure/monitoring/executor_metrics.py +45 -11
  183. aiecs/infrastructure/monitoring/global_metrics_manager.py +212 -0
  184. aiecs/infrastructure/monitoring/structured_logger.py +3 -7
  185. aiecs/infrastructure/monitoring/tracing_manager.py +63 -35
  186. aiecs/infrastructure/persistence/__init__.py +14 -1
  187. aiecs/infrastructure/persistence/context_engine_client.py +184 -0
  188. aiecs/infrastructure/persistence/database_manager.py +67 -43
  189. aiecs/infrastructure/persistence/file_storage.py +180 -103
  190. aiecs/infrastructure/persistence/redis_client.py +74 -21
  191. aiecs/llm/__init__.py +73 -25
  192. aiecs/llm/callbacks/__init__.py +11 -0
  193. aiecs/llm/{custom_callbacks.py → callbacks/custom_callbacks.py} +26 -19
  194. aiecs/llm/client_factory.py +224 -36
  195. aiecs/llm/client_resolver.py +155 -0
  196. aiecs/llm/clients/__init__.py +38 -0
  197. aiecs/llm/clients/base_client.py +324 -0
  198. aiecs/llm/clients/google_function_calling_mixin.py +457 -0
  199. aiecs/llm/clients/googleai_client.py +241 -0
  200. aiecs/llm/clients/openai_client.py +158 -0
  201. aiecs/llm/clients/openai_compatible_mixin.py +367 -0
  202. aiecs/llm/clients/vertex_client.py +897 -0
  203. aiecs/llm/clients/xai_client.py +201 -0
  204. aiecs/llm/config/__init__.py +51 -0
  205. aiecs/llm/config/config_loader.py +272 -0
  206. aiecs/llm/config/config_validator.py +206 -0
  207. aiecs/llm/config/model_config.py +143 -0
  208. aiecs/llm/protocols.py +149 -0
  209. aiecs/llm/utils/__init__.py +10 -0
  210. aiecs/llm/utils/validate_config.py +89 -0
  211. aiecs/main.py +140 -121
  212. aiecs/scripts/aid/VERSION_MANAGEMENT.md +138 -0
  213. aiecs/scripts/aid/__init__.py +19 -0
  214. aiecs/scripts/aid/module_checker.py +499 -0
  215. aiecs/scripts/aid/version_manager.py +235 -0
  216. aiecs/scripts/{DEPENDENCY_SYSTEM_SUMMARY.md → dependance_check/DEPENDENCY_SYSTEM_SUMMARY.md} +1 -0
  217. aiecs/scripts/{README_DEPENDENCY_CHECKER.md → dependance_check/README_DEPENDENCY_CHECKER.md} +1 -0
  218. aiecs/scripts/dependance_check/__init__.py +15 -0
  219. aiecs/scripts/dependance_check/dependency_checker.py +1835 -0
  220. aiecs/scripts/{dependency_fixer.py → dependance_check/dependency_fixer.py} +192 -90
  221. aiecs/scripts/{download_nlp_data.py → dependance_check/download_nlp_data.py} +203 -71
  222. aiecs/scripts/dependance_patch/__init__.py +7 -0
  223. aiecs/scripts/dependance_patch/fix_weasel/__init__.py +11 -0
  224. aiecs/scripts/{fix_weasel_validator.py → dependance_patch/fix_weasel/fix_weasel_validator.py} +21 -14
  225. aiecs/scripts/{patch_weasel_library.sh → dependance_patch/fix_weasel/patch_weasel_library.sh} +1 -1
  226. aiecs/scripts/knowledge_graph/__init__.py +3 -0
  227. aiecs/scripts/knowledge_graph/run_threshold_experiments.py +212 -0
  228. aiecs/scripts/migrations/multi_tenancy/README.md +142 -0
  229. aiecs/scripts/tools_develop/README.md +671 -0
  230. aiecs/scripts/tools_develop/README_CONFIG_CHECKER.md +273 -0
  231. aiecs/scripts/tools_develop/TOOLS_CONFIG_GUIDE.md +1287 -0
  232. aiecs/scripts/tools_develop/TOOL_AUTO_DISCOVERY.md +234 -0
  233. aiecs/scripts/tools_develop/__init__.py +21 -0
  234. aiecs/scripts/tools_develop/check_all_tools_config.py +548 -0
  235. aiecs/scripts/tools_develop/check_type_annotations.py +257 -0
  236. aiecs/scripts/tools_develop/pre-commit-schema-coverage.sh +66 -0
  237. aiecs/scripts/tools_develop/schema_coverage.py +511 -0
  238. aiecs/scripts/tools_develop/validate_tool_schemas.py +475 -0
  239. aiecs/scripts/tools_develop/verify_executor_config_fix.py +98 -0
  240. aiecs/scripts/tools_develop/verify_tools.py +352 -0
  241. aiecs/tasks/__init__.py +0 -1
  242. aiecs/tasks/worker.py +115 -47
  243. aiecs/tools/__init__.py +194 -72
  244. aiecs/tools/apisource/__init__.py +99 -0
  245. aiecs/tools/apisource/intelligence/__init__.py +19 -0
  246. aiecs/tools/apisource/intelligence/data_fusion.py +632 -0
  247. aiecs/tools/apisource/intelligence/query_analyzer.py +417 -0
  248. aiecs/tools/apisource/intelligence/search_enhancer.py +385 -0
  249. aiecs/tools/apisource/monitoring/__init__.py +9 -0
  250. aiecs/tools/apisource/monitoring/metrics.py +330 -0
  251. aiecs/tools/apisource/providers/__init__.py +112 -0
  252. aiecs/tools/apisource/providers/base.py +671 -0
  253. aiecs/tools/apisource/providers/census.py +397 -0
  254. aiecs/tools/apisource/providers/fred.py +535 -0
  255. aiecs/tools/apisource/providers/newsapi.py +409 -0
  256. aiecs/tools/apisource/providers/worldbank.py +352 -0
  257. aiecs/tools/apisource/reliability/__init__.py +12 -0
  258. aiecs/tools/apisource/reliability/error_handler.py +363 -0
  259. aiecs/tools/apisource/reliability/fallback_strategy.py +376 -0
  260. aiecs/tools/apisource/tool.py +832 -0
  261. aiecs/tools/apisource/utils/__init__.py +9 -0
  262. aiecs/tools/apisource/utils/validators.py +334 -0
  263. aiecs/tools/base_tool.py +415 -21
  264. aiecs/tools/docs/__init__.py +121 -0
  265. aiecs/tools/docs/ai_document_orchestrator.py +607 -0
  266. aiecs/tools/docs/ai_document_writer_orchestrator.py +2350 -0
  267. aiecs/tools/docs/content_insertion_tool.py +1320 -0
  268. aiecs/tools/docs/document_creator_tool.py +1323 -0
  269. aiecs/tools/docs/document_layout_tool.py +1160 -0
  270. aiecs/tools/docs/document_parser_tool.py +1011 -0
  271. aiecs/tools/docs/document_writer_tool.py +1829 -0
  272. aiecs/tools/knowledge_graph/__init__.py +17 -0
  273. aiecs/tools/knowledge_graph/graph_reasoning_tool.py +807 -0
  274. aiecs/tools/knowledge_graph/graph_search_tool.py +944 -0
  275. aiecs/tools/knowledge_graph/kg_builder_tool.py +524 -0
  276. aiecs/tools/langchain_adapter.py +300 -138
  277. aiecs/tools/schema_generator.py +455 -0
  278. aiecs/tools/search_tool/__init__.py +100 -0
  279. aiecs/tools/search_tool/analyzers.py +581 -0
  280. aiecs/tools/search_tool/cache.py +264 -0
  281. aiecs/tools/search_tool/constants.py +128 -0
  282. aiecs/tools/search_tool/context.py +224 -0
  283. aiecs/tools/search_tool/core.py +778 -0
  284. aiecs/tools/search_tool/deduplicator.py +119 -0
  285. aiecs/tools/search_tool/error_handler.py +242 -0
  286. aiecs/tools/search_tool/metrics.py +343 -0
  287. aiecs/tools/search_tool/rate_limiter.py +172 -0
  288. aiecs/tools/search_tool/schemas.py +275 -0
  289. aiecs/tools/statistics/__init__.py +80 -0
  290. aiecs/tools/statistics/ai_data_analysis_orchestrator.py +646 -0
  291. aiecs/tools/statistics/ai_insight_generator_tool.py +508 -0
  292. aiecs/tools/statistics/ai_report_orchestrator_tool.py +684 -0
  293. aiecs/tools/statistics/data_loader_tool.py +555 -0
  294. aiecs/tools/statistics/data_profiler_tool.py +638 -0
  295. aiecs/tools/statistics/data_transformer_tool.py +580 -0
  296. aiecs/tools/statistics/data_visualizer_tool.py +498 -0
  297. aiecs/tools/statistics/model_trainer_tool.py +507 -0
  298. aiecs/tools/statistics/statistical_analyzer_tool.py +472 -0
  299. aiecs/tools/task_tools/__init__.py +49 -36
  300. aiecs/tools/task_tools/chart_tool.py +200 -184
  301. aiecs/tools/task_tools/classfire_tool.py +268 -267
  302. aiecs/tools/task_tools/image_tool.py +175 -131
  303. aiecs/tools/task_tools/office_tool.py +226 -146
  304. aiecs/tools/task_tools/pandas_tool.py +477 -121
  305. aiecs/tools/task_tools/report_tool.py +390 -142
  306. aiecs/tools/task_tools/research_tool.py +149 -79
  307. aiecs/tools/task_tools/scraper_tool.py +339 -145
  308. aiecs/tools/task_tools/stats_tool.py +448 -209
  309. aiecs/tools/temp_file_manager.py +26 -24
  310. aiecs/tools/tool_executor/__init__.py +18 -16
  311. aiecs/tools/tool_executor/tool_executor.py +364 -52
  312. aiecs/utils/LLM_output_structor.py +74 -48
  313. aiecs/utils/__init__.py +14 -3
  314. aiecs/utils/base_callback.py +0 -3
  315. aiecs/utils/cache_provider.py +696 -0
  316. aiecs/utils/execution_utils.py +50 -31
  317. aiecs/utils/prompt_loader.py +1 -0
  318. aiecs/utils/token_usage_repository.py +37 -11
  319. aiecs/ws/socket_server.py +14 -4
  320. {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/METADATA +52 -15
  321. aiecs-1.7.6.dist-info/RECORD +337 -0
  322. aiecs-1.7.6.dist-info/entry_points.txt +13 -0
  323. aiecs/config/registry.py +0 -19
  324. aiecs/domain/context/content_engine.py +0 -982
  325. aiecs/llm/base_client.py +0 -99
  326. aiecs/llm/openai_client.py +0 -125
  327. aiecs/llm/vertex_client.py +0 -186
  328. aiecs/llm/xai_client.py +0 -184
  329. aiecs/scripts/dependency_checker.py +0 -857
  330. aiecs/scripts/quick_dependency_check.py +0 -269
  331. aiecs/tools/task_tools/search_api.py +0 -7
  332. aiecs-1.0.1.dist-info/RECORD +0 -90
  333. aiecs-1.0.1.dist-info/entry_points.txt +0 -7
  334. /aiecs/scripts/{setup_nlp_data.sh → dependance_check/setup_nlp_data.sh} +0 -0
  335. /aiecs/scripts/{README_WEASEL_PATCH.md → dependance_patch/fix_weasel/README_WEASEL_PATCH.md} +0 -0
  336. /aiecs/scripts/{fix_weasel_validator.sh → dependance_patch/fix_weasel/fix_weasel_validator.sh} +0 -0
  337. /aiecs/scripts/{run_weasel_patch.sh → dependance_patch/fix_weasel/run_weasel_patch.sh} +0 -0
  338. {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/WHEEL +0 -0
  339. {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/licenses/LICENSE +0 -0
  340. {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,913 @@
1
+ """
2
+ AST Node Definitions for Logic Query Parser
3
+
4
+ This module defines the Abstract Syntax Tree (AST) node hierarchy for the Logic Query DSL.
5
+ Each node is self-contained and responsible for its own validation and conversion to QueryPlan.
6
+
7
+ Design Principles:
8
+ 1. Self-Contained: Each node owns its complete structure (e.g., FindNode has filters)
9
+ 2. Polymorphic Conversion: Each node implements its own to_query_plan() method
10
+ 3. Immutable: Nodes are frozen dataclasses (cannot be modified after creation)
11
+ 4. Type-Safe: Full type hints for all fields and methods
12
+
13
+ Phase: 2.4 - Logic Query Parser
14
+ Version: 1.0
15
+ """
16
+
17
+ import logging
18
+ from abc import ABC, abstractmethod
19
+ from dataclasses import dataclass, field
20
+ from typing import Any, Dict, List, Optional, TYPE_CHECKING
21
+ import uuid
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+ if TYPE_CHECKING:
26
+ from .query_context import QueryContext
27
+
28
+ # Import QueryPlan models
29
+ try:
30
+ from aiecs.domain.knowledge_graph.models.query_plan import (
31
+ QueryPlan,
32
+ QueryStep,
33
+ QueryOperation,
34
+ )
35
+ from aiecs.domain.knowledge_graph.models.query import GraphQuery, QueryType
36
+
37
+ QUERY_PLAN_AVAILABLE = True
38
+ except ImportError:
39
+ QUERY_PLAN_AVAILABLE = False
40
+ # Use TYPE_CHECKING to avoid redefinition errors
41
+ if TYPE_CHECKING:
42
+ from typing import Any
43
+ QueryPlan: Any # type: ignore[assignment,no-redef]
44
+ QueryStep: Any # type: ignore[assignment,no-redef]
45
+ QueryOperation: Any # type: ignore[assignment,no-redef]
46
+ GraphQuery: Any # type: ignore[assignment,no-redef]
47
+ QueryType: Any # type: ignore[assignment,no-redef]
48
+ else:
49
+ from typing import Any
50
+ QueryPlan = None # type: ignore[assignment]
51
+ QueryStep = None # type: ignore[assignment]
52
+ QueryOperation = None # type: ignore[assignment]
53
+ GraphQuery = None # type: ignore[assignment]
54
+ QueryType = None # type: ignore[assignment]
55
+
56
+ # Placeholder for ValidationError (will be defined in error_handler.py)
57
+
58
+
59
+ @dataclass
60
+ class ValidationError:
61
+ """Validation error with location information"""
62
+
63
+ line: int
64
+ column: int
65
+ message: str
66
+ suggestion: Optional[str] = None
67
+
68
+
69
+ @dataclass(frozen=True)
70
+ class ASTNode(ABC):
71
+ """
72
+ Base class for all AST nodes
73
+
74
+ All AST nodes must:
75
+ 1. Store line/column metadata for error reporting
76
+ 2. Implement validate() for semantic validation
77
+ 3. Implement conversion to query plan (via to_query_plan or to_filter_dict)
78
+
79
+ Attributes:
80
+ line: Line number in source query (1-based)
81
+ column: Column number in source query (1-based)
82
+ """
83
+
84
+ line: int
85
+ column: int
86
+
87
+ @abstractmethod
88
+ def validate(self, schema: Any, entity_type: Optional[str] = None) -> List[ValidationError]:
89
+ """
90
+ Validate this node against the schema
91
+
92
+ Args:
93
+ schema: SchemaManager instance for validation
94
+ entity_type: Optional entity type context for property validation
95
+
96
+ Returns:
97
+ List of validation errors (empty if valid)
98
+ """
99
+
100
+
101
+ @dataclass(frozen=True)
102
+ class QueryNode(ASTNode):
103
+ """
104
+ Top-level query node: Find + optional Traversals + optional WHERE
105
+
106
+ Represents a complete query with:
107
+ - Required: FindNode for entity selection
108
+ - Optional: List of TraversalNodes for graph navigation
109
+ - Optional: WHERE clause (embedded in FindNode.filters)
110
+
111
+ Example:
112
+ Find(Person) FOLLOWS AuthoredBy WHERE year > 2020
113
+
114
+ QueryNode(
115
+ find=FindNode(entity_type="Person", ...),
116
+ traversals=[TraversalNode(relation_type="AuthoredBy", ...)],
117
+ ...
118
+ )
119
+ """
120
+
121
+ find: "FindNode"
122
+ traversals: List["TraversalNode"] = field(default_factory=list)
123
+
124
+ def validate(self, schema: Any) -> List[ValidationError]:
125
+ """Validate all parts of the query"""
126
+ errors = []
127
+ errors.extend(self.find.validate(schema))
128
+ for traversal in self.traversals:
129
+ errors.extend(traversal.validate(schema))
130
+ return errors
131
+
132
+ def to_query_plan(self, context: "QueryContext", original_query: str = "") -> Any:
133
+ """
134
+ Convert QueryNode to QueryPlan
135
+
136
+ Creates a QueryPlan with multiple steps for complex queries with traversals.
137
+
138
+ Args:
139
+ context: Query context for variable resolution
140
+ original_query: Original query string for documentation
141
+
142
+ Returns:
143
+ QueryPlan with one or more QuerySteps
144
+ """
145
+ if not QUERY_PLAN_AVAILABLE:
146
+ raise ImportError("QueryPlan models not available")
147
+
148
+ # Generate plan ID
149
+ plan_id = f"plan_{uuid.uuid4().hex[:8]}"
150
+
151
+ # Convert to query steps
152
+ steps = self.to_query_steps(context)
153
+
154
+ # Create explanation
155
+ explanation = self._generate_explanation()
156
+
157
+ # Create QueryPlan
158
+ plan = QueryPlan(
159
+ plan_id=plan_id,
160
+ original_query=original_query or str(self),
161
+ steps=steps,
162
+ explanation=explanation,
163
+ optimized=False,
164
+ )
165
+
166
+ # Calculate total cost
167
+ plan.total_estimated_cost = plan.calculate_total_cost()
168
+
169
+ return plan
170
+
171
+ def to_query_steps(self, context: "QueryContext") -> List[Any]:
172
+ """
173
+ Convert QueryNode to list of QuerySteps
174
+
175
+ For simple queries (Find only), creates a single step.
176
+ For complex queries (Find + Traversals), creates multiple steps.
177
+
178
+ Args:
179
+ context: Query context for variable resolution
180
+
181
+ Returns:
182
+ List of QueryStep objects
183
+ """
184
+ if not QUERY_PLAN_AVAILABLE:
185
+ raise ImportError("QueryPlan models not available")
186
+
187
+ steps = []
188
+
189
+ # Step 1: Find entities (always present)
190
+ find_step = self.find.to_query_step(context, step_id="step_1")
191
+ steps.append(find_step)
192
+
193
+ # Steps 2+: Traversals (if any)
194
+ for i, traversal in enumerate(self.traversals, start=2):
195
+ step_id = f"step_{i}"
196
+ depends_on = [f"step_{i-1}"] # Each step depends on previous
197
+ traversal_step = traversal.to_query_step(context, step_id=step_id, depends_on=depends_on)
198
+ steps.append(traversal_step)
199
+
200
+ return steps
201
+
202
+ def _generate_explanation(self) -> str:
203
+ """Generate human-readable explanation of the query"""
204
+ parts = [f"Find {self.find.entity_type} entities"]
205
+
206
+ if self.find.entity_name:
207
+ parts.append(f"named '{self.find.entity_name}'")
208
+
209
+ if self.find.filters:
210
+ parts.append(f"with {len(self.find.filters)} filter(s)")
211
+
212
+ if self.traversals:
213
+ parts.append(f"then traverse {len(self.traversals)} relation(s)")
214
+
215
+ return " ".join(parts)
216
+
217
+ def __repr__(self) -> str:
218
+ """String representation for debugging"""
219
+ traversals_str = f", traversals={len(self.traversals)}" if self.traversals else ""
220
+ return f"QueryNode(find={self.find}{traversals_str})"
221
+
222
+
223
+ @dataclass(frozen=True)
224
+ class FindNode(ASTNode):
225
+ """
226
+ Entity selection node: Find(EntityType) or Find(EntityType[`Name`])
227
+
228
+ Represents entity selection with optional filters.
229
+ This node is self-contained and owns its filters.
230
+
231
+ Attributes:
232
+ entity_type: Type of entity to find (e.g., "Person", "Paper")
233
+ entity_name: Optional specific entity name (e.g., "Alice")
234
+ filters: List of filter nodes (from WHERE clause)
235
+
236
+ Example:
237
+ Find(Person[`Alice`]) WHERE age > 30
238
+
239
+ FindNode(
240
+ entity_type="Person",
241
+ entity_name="Alice",
242
+ filters=[PropertyFilterNode(property="age", operator=">", value=30)]
243
+ )
244
+ """
245
+
246
+ entity_type: str
247
+ entity_name: Optional[str] = None
248
+ filters: List["FilterNode"] = field(default_factory=list)
249
+
250
+ def validate(self, schema: Any, entity_type: Optional[str] = None) -> List[ValidationError]:
251
+ """
252
+ Validate entity type and all filters
253
+
254
+ Args:
255
+ schema: Schema object for validation
256
+ entity_type: Optional entity type (uses self.entity_type if not provided)
257
+ """
258
+ errors = []
259
+
260
+ # Use self.entity_type if entity_type parameter not provided
261
+ effective_entity_type = entity_type if entity_type is not None else self.entity_type
262
+
263
+ # Validate entity type exists (if schema has this method)
264
+ if hasattr(schema, "has_entity_type"):
265
+ if not schema.has_entity_type(effective_entity_type):
266
+ errors.append(
267
+ ValidationError(
268
+ self.line,
269
+ self.column,
270
+ f"Entity type '{effective_entity_type}' not found",
271
+ suggestion=f"Available types: {', '.join(schema.get_entity_types())}",
272
+ )
273
+ )
274
+
275
+ # Validate all filters (pass entity_type for property validation)
276
+ for filter_node in self.filters:
277
+ errors.extend(filter_node.validate(schema, entity_type=effective_entity_type))
278
+
279
+ return errors
280
+
281
+ def to_query_step(
282
+ self,
283
+ context: "QueryContext",
284
+ step_id: str = "step_1",
285
+ depends_on: Optional[List[str]] = None,
286
+ ) -> Any:
287
+ """
288
+ Convert FindNode to QueryStep
289
+
290
+ Creates a QueryStep for entity lookup/filter operation.
291
+
292
+ Args:
293
+ context: Query context for variable resolution
294
+ step_id: Unique identifier for this step
295
+ depends_on: List of step IDs this step depends on
296
+
297
+ Returns:
298
+ QueryStep for entity lookup/filter
299
+ """
300
+ if not QUERY_PLAN_AVAILABLE:
301
+ raise ImportError("QueryPlan models not available")
302
+
303
+ # Build property filters
304
+ properties = {}
305
+ if self.filters:
306
+ # Combine all filters into a single filter dict
307
+ for filter_node in self.filters:
308
+ filter_dict = filter_node.to_filter_dict(context)
309
+ properties.update(filter_dict)
310
+
311
+ # Determine query type and operation
312
+ # If entity_name is provided, it's an entity lookup
313
+ # Otherwise, it's a filter operation
314
+ if self.entity_name:
315
+ query_type = QueryType.ENTITY_LOOKUP
316
+ operation = QueryOperation.ENTITY_LOOKUP
317
+ else:
318
+ # For filter operations, we use ENTITY_LOOKUP query type with
319
+ # filters
320
+ query_type = QueryType.ENTITY_LOOKUP
321
+ operation = QueryOperation.FILTER
322
+
323
+ # Create GraphQuery
324
+ query = GraphQuery(
325
+ query_type=query_type,
326
+ entity_type=self.entity_type,
327
+ entity_id=self.entity_name, # If specific entity name is provided
328
+ properties=properties,
329
+ max_results=100, # Default limit
330
+ )
331
+
332
+ # Create description
333
+ description = f"Find {self.entity_type} entities"
334
+ if self.entity_name:
335
+ description += f" named '{self.entity_name}'"
336
+ if self.filters:
337
+ description += f" with {len(self.filters)} filter(s)"
338
+
339
+ # Create QueryStep
340
+ step = QueryStep(
341
+ step_id=step_id,
342
+ operation=operation,
343
+ query=query,
344
+ depends_on=depends_on or [],
345
+ description=description,
346
+ estimated_cost=0.3, # Low cost for simple entity lookup
347
+ )
348
+
349
+ return step
350
+
351
+ def __repr__(self) -> str:
352
+ """String representation for debugging"""
353
+ name_str = f"[`{self.entity_name}`]" if self.entity_name else ""
354
+ filters_str = f", filters={len(self.filters)}" if self.filters else ""
355
+ return f"FindNode({self.entity_type}{name_str}{filters_str})"
356
+
357
+
358
+ @dataclass(frozen=True)
359
+ class TraversalNode(ASTNode):
360
+ """
361
+ Graph traversal node: FOLLOWS RelationType [direction]
362
+
363
+ Represents navigation along graph relationships.
364
+
365
+ Attributes:
366
+ relation_type: Type of relation to follow (e.g., "AuthoredBy")
367
+ direction: Direction of traversal ("outgoing", "incoming", or None for default)
368
+
369
+ Example:
370
+ FOLLOWS AuthoredBy INCOMING
371
+
372
+ TraversalNode(
373
+ relation_type="AuthoredBy",
374
+ direction="incoming"
375
+ )
376
+ """
377
+
378
+ relation_type: str
379
+ direction: Optional[str] = "outgoing" # "incoming" | "outgoing" | None
380
+
381
+ def validate(self, schema: Any, entity_type: Optional[str] = None) -> List[ValidationError]:
382
+ """
383
+ Validate relation type exists
384
+
385
+ Args:
386
+ schema: Schema object for validation
387
+ entity_type: Optional entity type (not used for traversal validation)
388
+ """
389
+ errors = []
390
+
391
+ # Validate relation type exists (if schema has this method)
392
+ if hasattr(schema, "has_relation_type"):
393
+ if not schema.has_relation_type(self.relation_type):
394
+ errors.append(
395
+ ValidationError(
396
+ self.line,
397
+ self.column,
398
+ f"Relation type '{self.relation_type}' not found",
399
+ )
400
+ )
401
+
402
+ # Validate direction
403
+ if self.direction and self.direction not in ["incoming", "outgoing"]:
404
+ errors.append(
405
+ ValidationError(
406
+ self.line,
407
+ self.column,
408
+ f"Invalid direction '{self.direction}'. Must be 'incoming' or 'outgoing'",
409
+ )
410
+ )
411
+
412
+ return errors
413
+
414
+ def to_query_step(self, context: "QueryContext", step_id: str, depends_on: List[str]) -> Any:
415
+ """
416
+ Convert TraversalNode to QueryStep
417
+
418
+ Creates a QueryStep for graph traversal operation.
419
+
420
+ Args:
421
+ context: Query context for variable resolution
422
+ step_id: Unique identifier for this step
423
+ depends_on: List of step IDs this step depends on
424
+
425
+ Returns:
426
+ QueryStep for graph traversal
427
+ """
428
+ if not QUERY_PLAN_AVAILABLE:
429
+ raise ImportError("QueryPlan models not available")
430
+
431
+ # Create GraphQuery for traversal
432
+ query = GraphQuery(
433
+ query_type=QueryType.TRAVERSAL,
434
+ relation_type=self.relation_type,
435
+ max_depth=1, # Single hop traversal
436
+ max_results=100, # Default limit
437
+ )
438
+
439
+ # Create description
440
+ direction_str = self.direction.upper() if self.direction else "OUTGOING"
441
+ description = f"Traverse {self.relation_type} relation ({direction_str})"
442
+
443
+ # Create QueryStep
444
+ step = QueryStep(
445
+ step_id=step_id,
446
+ operation=QueryOperation.TRAVERSAL,
447
+ query=query,
448
+ depends_on=depends_on,
449
+ description=description,
450
+ estimated_cost=0.5, # Medium cost for traversal
451
+ metadata={"direction": self.direction or "outgoing"},
452
+ )
453
+
454
+ return step
455
+
456
+ def __repr__(self) -> str:
457
+ """String representation for debugging"""
458
+ dir_str = f" {self.direction.upper()}" if self.direction else ""
459
+ return f"TraversalNode({self.relation_type}{dir_str})"
460
+
461
+
462
+ @dataclass(frozen=True)
463
+ class FilterNode(ASTNode):
464
+ """
465
+ Base class for filter nodes (WHERE conditions)
466
+
467
+ Filter nodes represent conditions in WHERE clauses.
468
+ They convert to filter dictionaries (MongoDB-style) for query execution.
469
+
470
+ Subclasses:
471
+ - PropertyFilterNode: property operator value (e.g., age > 30)
472
+ - BooleanFilterNode: AND/OR/NOT combinations
473
+ """
474
+
475
+ @abstractmethod
476
+ def to_filter_dict(self, context: "QueryContext") -> Dict[str, Any]:
477
+ """
478
+ Convert filter to MongoDB-style filter dictionary
479
+
480
+ Args:
481
+ context: Query context for variable resolution
482
+
483
+ Returns:
484
+ Filter dictionary (e.g., {"age": {"$gt": 30}})
485
+ """
486
+
487
+
488
+ @dataclass(frozen=True)
489
+ class PropertyFilterNode(FilterNode):
490
+ """
491
+ Property filter node: property operator value
492
+
493
+ Represents a comparison between a property and a value.
494
+
495
+ Attributes:
496
+ property_path: Property name or nested path (e.g., "age" or "address.city")
497
+ operator: Comparison operator (==, !=, >, <, >=, <=, IN, CONTAINS)
498
+ value: Value to compare against
499
+
500
+ Example:
501
+ age > 30
502
+
503
+ PropertyFilterNode(
504
+ property_path="age",
505
+ operator=">",
506
+ value=30
507
+ )
508
+ """
509
+
510
+ property_path: str # Can be nested: "address.city"
511
+ operator: str # "==", "!=", ">", "<", ">=", "<=", "IN", "CONTAINS"
512
+ value: Any
513
+
514
+ def to_filter_dict(self, context: "QueryContext") -> Dict[str, Any]:
515
+ """Convert to MongoDB-style filter dict"""
516
+ operator_map = {
517
+ "==": "$eq",
518
+ "!=": "$ne",
519
+ ">": "$gt",
520
+ "<": "$lt",
521
+ ">=": "$gte",
522
+ "<=": "$lte",
523
+ "IN": "$in",
524
+ "CONTAINS": "$regex",
525
+ }
526
+
527
+ mongo_op = operator_map.get(self.operator, "$eq")
528
+
529
+ # For CONTAINS, convert to regex pattern
530
+ if self.operator == "CONTAINS":
531
+ return {self.property_path: {mongo_op: self.value}}
532
+
533
+ return {self.property_path: {mongo_op: self.value}}
534
+
535
+ def validate(self, schema: Any, entity_type: Optional[str] = None) -> List[ValidationError]:
536
+ """
537
+ Validate property exists and type matches
538
+
539
+ Args:
540
+ schema: Schema object for validation
541
+ entity_type: Optional entity type context for property validation
542
+ """
543
+ errors = []
544
+
545
+ # Validate operator is valid
546
+ valid_operators = ["==", "!=", ">", "<", ">=", "<=", "IN", "CONTAINS"]
547
+ if self.operator not in valid_operators:
548
+ errors.append(
549
+ ValidationError(
550
+ self.line,
551
+ self.column,
552
+ f"Invalid operator '{self.operator}'. Must be one of: {', '.join(valid_operators)}",
553
+ )
554
+ )
555
+
556
+ # Validate IN operator has list value
557
+ if self.operator == "IN" and not isinstance(self.value, list):
558
+ errors.append(
559
+ ValidationError(
560
+ self.line,
561
+ self.column,
562
+ f"IN operator requires a list value, got {type(self.value).__name__}",
563
+ )
564
+ )
565
+
566
+ # Validate CONTAINS operator has string value
567
+ if self.operator == "CONTAINS" and not isinstance(self.value, str):
568
+ errors.append(
569
+ ValidationError(
570
+ self.line,
571
+ self.column,
572
+ f"CONTAINS operator requires a string value, got {type(self.value).__name__}",
573
+ )
574
+ )
575
+
576
+ # Validate property exists in schema (if entity_type provided)
577
+ if entity_type:
578
+ errors.extend(self._validate_property_in_schema(schema, entity_type))
579
+ else:
580
+ logger.debug(
581
+ f"PropertyFilterNode.validate: "
582
+ f"FALLBACK - No entity_type provided, skipping property validation for '{self.property_path}'"
583
+ )
584
+
585
+ return errors
586
+
587
+ def _validate_property_in_schema(self, schema: Any, entity_type: str) -> List[ValidationError]:
588
+ """
589
+ Validate that property exists in entity type schema
590
+
591
+ Args:
592
+ schema: Schema object for validation
593
+ entity_type: Entity type to validate against
594
+
595
+ Returns:
596
+ List of validation errors
597
+ """
598
+ errors = []
599
+
600
+ # Check if schema has required methods
601
+ if not hasattr(schema, "get_entity_type"):
602
+ logger.debug(
603
+ f"PropertyFilterNode._validate_property_in_schema: "
604
+ f"FALLBACK - schema missing 'get_entity_type' method, skipping property validation"
605
+ )
606
+ return errors
607
+
608
+ # Get entity type schema
609
+ entity_schema = schema.get_entity_type(entity_type)
610
+ if entity_schema is None:
611
+ # Entity type doesn't exist - error already reported by FindNode
612
+ logger.debug(
613
+ f"PropertyFilterNode._validate_property_in_schema: "
614
+ f"FALLBACK - entity type '{entity_type}' not found in schema, skipping property validation"
615
+ )
616
+ return errors
617
+
618
+ # Validate nested property path recursively
619
+ errors.extend(
620
+ self._validate_nested_property_path(
621
+ entity_schema=entity_schema,
622
+ property_path=self.property_path,
623
+ entity_type=entity_type,
624
+ current_path="",
625
+ )
626
+ )
627
+
628
+ return errors
629
+
630
+ def _validate_nested_property_path(
631
+ self,
632
+ entity_schema: Any,
633
+ property_path: str,
634
+ entity_type: str,
635
+ current_path: str = "",
636
+ ) -> List[ValidationError]:
637
+ """
638
+ Recursively validate a nested property path
639
+
640
+ Args:
641
+ entity_schema: Current entity schema (may be nested)
642
+ property_path: Remaining property path to validate
643
+ entity_type: Root entity type name
644
+ current_path: Accumulated path so far (for error messages)
645
+
646
+ Returns:
647
+ List of validation errors
648
+ """
649
+ errors = []
650
+
651
+ # Split property path into parts
652
+ property_parts = property_path.split(".")
653
+ current_property = property_parts[0]
654
+ remaining_path = ".".join(property_parts[1:]) if len(property_parts) > 1 else None
655
+
656
+ # Build full path for error messages
657
+ full_path = f"{current_path}.{current_property}" if current_path else current_property
658
+
659
+ # Check if entity schema has get_property method
660
+ if not hasattr(entity_schema, "get_property"):
661
+ logger.debug(
662
+ f"PropertyFilterNode._validate_nested_property_path: "
663
+ f"FALLBACK - entity_schema missing 'get_property' method for '{entity_type}', "
664
+ f"skipping nested property validation at '{full_path}'"
665
+ )
666
+ return errors
667
+
668
+ # Get property schema
669
+ property_schema = entity_schema.get_property(current_property)
670
+ if property_schema is None:
671
+ # Property doesn't exist - get available properties for suggestion
672
+ available_props = []
673
+ if hasattr(entity_schema, "properties"):
674
+ available_props = list(entity_schema.properties.keys())
675
+ logger.debug(
676
+ f"PropertyFilterNode._validate_nested_property_path: "
677
+ f"Using 'properties' attribute to get available properties for '{entity_type}'"
678
+ )
679
+ elif hasattr(entity_schema, "get_property_names"):
680
+ available_props = entity_schema.get_property_names()
681
+ logger.debug(
682
+ f"PropertyFilterNode._validate_nested_property_path: "
683
+ f"FALLBACK - Using 'get_property_names()' method instead of 'properties' attribute "
684
+ f"for '{entity_type}'"
685
+ )
686
+
687
+ suggestion = None
688
+ if available_props:
689
+ context = f"{entity_type}.{current_path}" if current_path else entity_type
690
+ suggestion = f"Available properties for {context}: {', '.join(available_props[:5])}"
691
+ if len(available_props) > 5:
692
+ suggestion += f" (and {len(available_props) - 5} more)"
693
+
694
+ errors.append(
695
+ ValidationError(
696
+ self.line,
697
+ self.column,
698
+ f"Property '{full_path}' not found in {entity_type if not current_path else current_path}",
699
+ suggestion=suggestion,
700
+ )
701
+ )
702
+ return errors # Can't continue validation if property doesn't exist
703
+
704
+ # Check if there's more nesting to validate
705
+ if remaining_path:
706
+ # Check if current property is DICT type (supports nesting)
707
+ if not hasattr(property_schema, "property_type"):
708
+ # Can't determine if nesting is supported
709
+ errors.append(
710
+ ValidationError(
711
+ self.line,
712
+ self.column,
713
+ f"Cannot validate nested path '{full_path}.{remaining_path}': "
714
+ f"property '{current_property}' type unknown",
715
+ suggestion="Ensure property schema defines property_type",
716
+ )
717
+ )
718
+ return errors
719
+
720
+ property_type = property_schema.property_type
721
+
722
+ # Check if property type supports nesting
723
+ if hasattr(property_type, "value"):
724
+ type_value = property_type.value
725
+ elif hasattr(property_type, "name"):
726
+ type_value = property_type.name
727
+ else:
728
+ type_value = str(property_type)
729
+
730
+ # Import PropertyType to check if it's DICT
731
+ from aiecs.domain.knowledge_graph.schema.property_schema import PropertyType
732
+
733
+ if type_value == PropertyType.DICT.value or type_value == "dict":
734
+ # Property is DICT type - check for nested schema
735
+ nested_schema = self._get_nested_schema(property_schema)
736
+ if nested_schema is None:
737
+ # No nested schema defined - can't validate deeper nesting
738
+ errors.append(
739
+ ValidationError(
740
+ self.line,
741
+ self.column,
742
+ f"Cannot validate nested path '{full_path}.{remaining_path}': "
743
+ f"property '{current_property}' is DICT type but nested schema not defined",
744
+ suggestion=f"Define nested schema for '{current_property}' or use flat property path",
745
+ )
746
+ )
747
+ return errors
748
+
749
+ # Recursively validate remaining path
750
+ errors.extend(
751
+ self._validate_nested_property_path(
752
+ entity_schema=nested_schema,
753
+ property_path=remaining_path,
754
+ entity_type=entity_type,
755
+ current_path=full_path,
756
+ )
757
+ )
758
+ else:
759
+ # Property is not DICT type - can't nest further
760
+ errors.append(
761
+ ValidationError(
762
+ self.line,
763
+ self.column,
764
+ f"Cannot access nested path '{full_path}.{remaining_path}': "
765
+ f"property '{current_property}' is {type_value} type, not DICT",
766
+ suggestion=f"Use '{full_path}' directly or change property type to DICT",
767
+ )
768
+ )
769
+
770
+ return errors
771
+
772
+ def _get_nested_schema(self, property_schema: Any) -> Optional[Any]:
773
+ """
774
+ Get nested schema for a DICT property
775
+
776
+ Checks for nested schema in multiple ways:
777
+ 1. property_schema.nested_schema attribute
778
+ 2. property_schema.schema attribute (if not a callable method)
779
+ 3. property_schema.properties attribute (treat as EntityType-like)
780
+
781
+ Args:
782
+ property_schema: Property schema to get nested schema from
783
+
784
+ Returns:
785
+ Nested schema object or None if not found
786
+ """
787
+ # Check for explicit nested_schema attribute
788
+ if hasattr(property_schema, "nested_schema"):
789
+ nested_schema = getattr(property_schema, "nested_schema", None)
790
+ if nested_schema is not None:
791
+ return nested_schema
792
+
793
+ # Check for schema attribute (but not if it's a callable method)
794
+ if hasattr(property_schema, "schema"):
795
+ schema_attr = getattr(property_schema, "schema", None)
796
+ # Only use if it's not callable (Pydantic models have schema() method)
797
+ if schema_attr is not None and not callable(schema_attr):
798
+ return schema_attr
799
+
800
+ # Check if property_schema has properties attribute (treat as EntityType-like)
801
+ if hasattr(property_schema, "properties"):
802
+ properties = getattr(property_schema, "properties", None)
803
+ # Only use if it's a dict-like structure (not a Pydantic method)
804
+ if properties and isinstance(properties, dict) and len(properties) > 0:
805
+ # Create a mock entity schema-like object
806
+ class NestedSchema:
807
+ def __init__(self, properties):
808
+ self.properties = properties
809
+
810
+ def get_property(self, property_name: str):
811
+ if isinstance(self.properties, dict):
812
+ return self.properties.get(property_name)
813
+ return None
814
+
815
+ def get_property_names(self):
816
+ if isinstance(self.properties, dict):
817
+ return list(self.properties.keys())
818
+ return []
819
+
820
+ return NestedSchema(properties)
821
+
822
+ return None
823
+
824
+ def __repr__(self) -> str:
825
+ """String representation for debugging"""
826
+ value_repr = f'"{self.value}"' if isinstance(self.value, str) else str(self.value)
827
+ return f"PropertyFilterNode({self.property_path} {self.operator} {value_repr})"
828
+
829
+
830
+ @dataclass(frozen=True)
831
+ class BooleanFilterNode(FilterNode):
832
+ """
833
+ Boolean filter node: AND/OR/NOT combinations
834
+
835
+ Represents boolean combinations of filters.
836
+
837
+ Attributes:
838
+ operator: Boolean operator ("AND", "OR", "NOT")
839
+ operands: List of filter nodes to combine
840
+
841
+ Example:
842
+ age > 30 AND status == "active"
843
+
844
+ BooleanFilterNode(
845
+ operator="AND",
846
+ operands=[
847
+ PropertyFilterNode(property_path="age", operator=">", value=30),
848
+ PropertyFilterNode(property_path="status", operator="==", value="active")
849
+ ]
850
+ )
851
+ """
852
+
853
+ operator: str # "AND", "OR", "NOT"
854
+ operands: List[FilterNode] = field(default_factory=list)
855
+
856
+ def to_filter_dict(self, context: "QueryContext") -> Dict[str, Any]:
857
+ """Convert to MongoDB-style boolean filter"""
858
+ op_map = {"AND": "$and", "OR": "$or", "NOT": "$not"}
859
+
860
+ mongo_op = op_map.get(self.operator, "$and")
861
+ operand_dicts = [op.to_filter_dict(context) for op in self.operands]
862
+
863
+ # NOT operator has special handling (single operand)
864
+ if self.operator == "NOT":
865
+ if len(operand_dicts) == 1:
866
+ return {mongo_op: operand_dicts[0]}
867
+ else:
868
+ # Multiple operands: NOT (a AND b AND c) = NOT {$and: [a, b,
869
+ # c]}
870
+ return {mongo_op: {"$and": operand_dicts}}
871
+
872
+ return {mongo_op: operand_dicts}
873
+
874
+ def validate(self, schema: Any, entity_type: Optional[str] = None) -> List[ValidationError]:
875
+ """
876
+ Validate all operands
877
+
878
+ Args:
879
+ schema: Schema object for validation
880
+ entity_type: Optional entity type context for property validation
881
+ """
882
+ errors = []
883
+
884
+ # Validate operator is valid
885
+ valid_operators = ["AND", "OR", "NOT"]
886
+ if self.operator not in valid_operators:
887
+ errors.append(
888
+ ValidationError(
889
+ self.line,
890
+ self.column,
891
+ f"Invalid boolean operator '{self.operator}'. Must be one of: {', '.join(valid_operators)}",
892
+ )
893
+ )
894
+
895
+ # Validate operand count
896
+ if not self.operands:
897
+ errors.append(
898
+ ValidationError(
899
+ self.line,
900
+ self.column,
901
+ f"Boolean operator '{self.operator}' requires at least one operand",
902
+ )
903
+ )
904
+
905
+ # Validate all operands (pass entity_type for property validation)
906
+ for operand in self.operands:
907
+ errors.extend(operand.validate(schema, entity_type=entity_type))
908
+
909
+ return errors
910
+
911
+ def __repr__(self) -> str:
912
+ """String representation for debugging"""
913
+ return f"BooleanFilterNode({self.operator}, operands={len(self.operands)})"