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,392 @@
1
+ """
2
+ Text Similarity Utilities
3
+
4
+ Provides various text similarity and matching functions for knowledge graph operations.
5
+ Includes BM25, Jaccard, cosine similarity, Levenshtein distance, and fuzzy matching.
6
+ """
7
+
8
+ import re
9
+ import math
10
+ from typing import List, Optional, Tuple, Callable, Any
11
+ from collections import Counter
12
+ from difflib import SequenceMatcher
13
+
14
+
15
+ class BM25Scorer:
16
+ """
17
+ BM25 (Best Matching 25) scorer for text similarity
18
+
19
+ BM25 is a ranking function used to estimate the relevance of documents
20
+ to a given search query. It's an improvement over TF-IDF.
21
+
22
+ Example::
23
+
24
+ scorer = BM25Scorer(corpus=[
25
+ "The quick brown fox jumps over the lazy dog",
26
+ "A quick brown dog jumps over a lazy fox",
27
+ "The lazy dog sleeps all day"
28
+ ])
29
+
30
+ scores = scorer.score("quick brown fox")
31
+ # Returns scores for each document in corpus
32
+ """
33
+
34
+ def __init__(
35
+ self,
36
+ corpus: List[str],
37
+ k1: float = 1.5,
38
+ b: float = 0.75,
39
+ tokenizer: Optional[Callable[[str], List[str]]] = None,
40
+ ):
41
+ """
42
+ Initialize BM25 scorer
43
+
44
+ Args:
45
+ corpus: List of documents to score against
46
+ k1: Term frequency saturation parameter (default: 1.5)
47
+ b: Length normalization parameter (default: 0.75)
48
+ tokenizer: Optional tokenizer function (default: simple word split)
49
+ """
50
+ self.k1 = k1
51
+ self.b = b
52
+ self.tokenizer = tokenizer or self._default_tokenizer
53
+
54
+ # Tokenize corpus
55
+ self.documents = [self.tokenizer(doc) for doc in corpus]
56
+ self.doc_count = len(self.documents)
57
+
58
+ # Calculate document lengths
59
+ self.doc_lengths = [len(doc) for doc in self.documents]
60
+ self.avg_doc_length = sum(self.doc_lengths) / self.doc_count if self.doc_count > 0 else 0
61
+
62
+ # Build term frequency dictionary
63
+ self.term_freqs = []
64
+ self.doc_freqs: Counter[str] = Counter()
65
+
66
+ for doc in self.documents:
67
+ tf = Counter(doc)
68
+ self.term_freqs.append(tf)
69
+ for term in set(doc):
70
+ self.doc_freqs[term] += 1
71
+
72
+ # Calculate IDF (Inverse Document Frequency)
73
+ self.idf = {}
74
+ for term, df in self.doc_freqs.items():
75
+ self.idf[term] = math.log((self.doc_count - df + 0.5) / (df + 0.5) + 1.0)
76
+
77
+ def _default_tokenizer(self, text: str) -> List[str]:
78
+ """Default tokenizer: lowercase and split on whitespace"""
79
+ return re.findall(r"\w+", text.lower())
80
+
81
+ def score(self, query: str) -> List[float]:
82
+ """
83
+ Score documents against query
84
+
85
+ Args:
86
+ query: Query string
87
+
88
+ Returns:
89
+ List of BM25 scores for each document
90
+ """
91
+ query_terms = self.tokenizer(query)
92
+ scores = []
93
+
94
+ for i, doc in enumerate(self.documents):
95
+ score = 0.0
96
+ doc_length = self.doc_lengths[i]
97
+ term_freq = self.term_freqs[i]
98
+
99
+ for term in query_terms:
100
+ if term in term_freq:
101
+ tf = term_freq[term]
102
+ idf = self.idf.get(term, 0.0)
103
+
104
+ # BM25 formula
105
+ numerator = idf * tf * (self.k1 + 1)
106
+ denominator = tf + self.k1 * (1 - self.b + self.b * (doc_length / self.avg_doc_length))
107
+ score += numerator / denominator
108
+
109
+ scores.append(score)
110
+
111
+ return scores
112
+
113
+ def get_top_n(self, query: str, n: int = 10) -> List[Tuple[int, float]]:
114
+ """
115
+ Get top N documents by BM25 score
116
+
117
+ Args:
118
+ query: Query string
119
+ n: Number of top results to return
120
+
121
+ Returns:
122
+ List of (document_index, score) tuples, sorted by score descending
123
+ """
124
+ scores = self.score(query)
125
+ indexed_scores = [(i, score) for i, score in enumerate(scores)]
126
+ indexed_scores.sort(key=lambda x: x[1], reverse=True)
127
+ return indexed_scores[:n]
128
+
129
+
130
+ def jaccard_similarity(set1: set, set2: set) -> float:
131
+ """
132
+ Calculate Jaccard similarity between two sets
133
+
134
+ Jaccard similarity = (size of intersection) / (size of union)
135
+
136
+ Args:
137
+ set1: First set
138
+ set2: Second set
139
+
140
+ Returns:
141
+ Jaccard similarity score (0.0 to 1.0)
142
+ """
143
+ if not set1 and not set2:
144
+ return 1.0
145
+
146
+ intersection = len(set1 & set2)
147
+ union = len(set1 | set2)
148
+
149
+ if union == 0:
150
+ return 0.0
151
+
152
+ return intersection / union
153
+
154
+
155
+ def jaccard_similarity_text(text1: str, text2: str, tokenizer: Optional[Callable[[str], Any]] = None) -> float:
156
+ """
157
+ Calculate Jaccard similarity between two text strings
158
+
159
+ Args:
160
+ text1: First text string
161
+ text2: Second text string
162
+ tokenizer: Optional tokenizer function (default: word split)
163
+
164
+ Returns:
165
+ Jaccard similarity score (0.0 to 1.0)
166
+ """
167
+ if tokenizer is None:
168
+
169
+ def tokenizer(t):
170
+ return set(re.findall(r"\w+", t.lower()))
171
+
172
+ else:
173
+ # Wrap tokenizer to ensure it returns a set
174
+ original_tokenizer = tokenizer
175
+
176
+ def tokenizer(t):
177
+ return set(original_tokenizer(t))
178
+
179
+ set1 = tokenizer(text1)
180
+ set2 = tokenizer(text2)
181
+
182
+ return jaccard_similarity(set1, set2)
183
+
184
+
185
+ def cosine_similarity_text(text1: str, text2: str, tokenizer: Optional[Callable[[str], List[str]]] = None) -> float:
186
+ """
187
+ Calculate cosine similarity between two text strings
188
+
189
+ Cosine similarity measures the cosine of the angle between two vectors
190
+ in a multi-dimensional space. For text, vectors are TF-IDF representations.
191
+
192
+ Args:
193
+ text1: First text string
194
+ text2: Second text string
195
+ tokenizer: Optional tokenizer function (default: word split)
196
+
197
+ Returns:
198
+ Cosine similarity score (0.0 to 1.0)
199
+ """
200
+ if tokenizer is None:
201
+
202
+ def tokenizer(t):
203
+ return re.findall(r"\w+", t.lower())
204
+
205
+ tokens1 = tokenizer(text1)
206
+ tokens2 = tokenizer(text2)
207
+
208
+ # Build vocabulary
209
+ vocab = set(tokens1) | set(tokens2)
210
+
211
+ if not vocab:
212
+ return 1.0 if not text1 and not text2 else 0.0
213
+
214
+ # Create term frequency vectors
215
+ tf1 = Counter(tokens1)
216
+ tf2 = Counter(tokens2)
217
+
218
+ # Calculate dot product and magnitudes
219
+ dot_product = sum(tf1.get(term, 0) * tf2.get(term, 0) for term in vocab)
220
+ magnitude1 = math.sqrt(sum(tf1.get(term, 0) ** 2 for term in vocab))
221
+ magnitude2 = math.sqrt(sum(tf2.get(term, 0) ** 2 for term in vocab))
222
+
223
+ if magnitude1 == 0 or magnitude2 == 0:
224
+ return 0.0
225
+
226
+ similarity = dot_product / (magnitude1 * magnitude2)
227
+ # Handle floating point precision issues
228
+ return min(1.0, max(0.0, similarity))
229
+
230
+
231
+ def levenshtein_distance(s1: str, s2: str) -> int:
232
+ """
233
+ Calculate Levenshtein distance (edit distance) between two strings
234
+
235
+ Levenshtein distance is the minimum number of single-character edits
236
+ (insertions, deletions, or substitutions) required to change one string
237
+ into another.
238
+
239
+ Args:
240
+ s1: First string
241
+ s2: Second string
242
+
243
+ Returns:
244
+ Levenshtein distance (0 = identical, higher = more different)
245
+ """
246
+ if len(s1) < len(s2):
247
+ return levenshtein_distance(s2, s1)
248
+
249
+ if len(s2) == 0:
250
+ return len(s1)
251
+
252
+ # Use dynamic programming
253
+ previous_row = list(range(len(s2) + 1))
254
+
255
+ for i, c1 in enumerate(s1):
256
+ current_row = [i + 1]
257
+ for j, c2 in enumerate(s2):
258
+ insertions = previous_row[j + 1] + 1
259
+ deletions = current_row[j] + 1
260
+ substitutions = previous_row[j] + (c1 != c2)
261
+ current_row.append(min(insertions, deletions, substitutions))
262
+ previous_row = current_row
263
+
264
+ return previous_row[-1]
265
+
266
+
267
+ def normalized_levenshtein_similarity(s1: str, s2: str) -> float:
268
+ """
269
+ Calculate normalized Levenshtein similarity (0.0 to 1.0)
270
+
271
+ Args:
272
+ s1: First string
273
+ s2: Second string
274
+
275
+ Returns:
276
+ Normalized similarity score (1.0 = identical, 0.0 = completely different)
277
+ """
278
+ max_len = max(len(s1), len(s2))
279
+ if max_len == 0:
280
+ return 1.0
281
+
282
+ distance = levenshtein_distance(s1, s2)
283
+ return 1.0 - (distance / max_len)
284
+
285
+
286
+ def fuzzy_match(
287
+ query: str,
288
+ candidates: List[str],
289
+ threshold: float = 0.6,
290
+ method: str = "jaccard",
291
+ ) -> List[Tuple[str, float]]:
292
+ """
293
+ Find fuzzy matches for a query string in a list of candidates
294
+
295
+ Args:
296
+ query: Query string to match
297
+ candidates: List of candidate strings
298
+ threshold: Minimum similarity threshold (0.0 to 1.0)
299
+ method: Similarity method ("jaccard", "cosine", "levenshtein", "ratio")
300
+
301
+ Returns:
302
+ List of (candidate, similarity_score) tuples above threshold,
303
+ sorted by score descending
304
+ """
305
+ results = []
306
+
307
+ for candidate in candidates:
308
+ if method == "jaccard":
309
+ score = jaccard_similarity_text(query, candidate)
310
+ elif method == "cosine":
311
+ score = cosine_similarity_text(query, candidate)
312
+ elif method == "levenshtein":
313
+ score = normalized_levenshtein_similarity(query, candidate)
314
+ elif method == "ratio":
315
+ # Use SequenceMatcher ratio (built-in fuzzy matching)
316
+ score = SequenceMatcher(None, query.lower(), candidate.lower()).ratio()
317
+ else:
318
+ raise ValueError(f"Unknown method: {method}. Use 'jaccard', 'cosine', 'levenshtein', or 'ratio'")
319
+
320
+ if score >= threshold:
321
+ results.append((candidate, score))
322
+
323
+ # Sort by score descending
324
+ results.sort(key=lambda x: x[1], reverse=True)
325
+ return results
326
+
327
+
328
+ class TextSimilarity:
329
+ """
330
+ Convenience class for text similarity operations
331
+
332
+ Provides a unified interface for various text similarity methods.
333
+
334
+ Example::
335
+
336
+ similarity = TextSimilarity()
337
+
338
+ # Jaccard similarity
339
+ score = similarity.jaccard("hello world", "world hello")
340
+
341
+ # Cosine similarity
342
+ score = similarity.cosine("machine learning", "deep learning")
343
+
344
+ # Levenshtein distance
345
+ distance = similarity.levenshtein("kitten", "sitting")
346
+
347
+ # Fuzzy matching
348
+ matches = similarity.fuzzy_match(
349
+ "python",
350
+ ["python3", "pyton", "java", "pythn"],
351
+ threshold=0.7
352
+ )
353
+ """
354
+
355
+ def __init__(self, tokenizer: Optional[Callable[[str], List[str]]] = None):
356
+ """
357
+ Initialize TextSimilarity
358
+
359
+ Args:
360
+ tokenizer: Optional tokenizer function for text processing
361
+ """
362
+ self.tokenizer = tokenizer
363
+
364
+ def jaccard(self, text1: str, text2: str) -> float:
365
+ """Calculate Jaccard similarity between two texts"""
366
+ return jaccard_similarity_text(text1, text2, self.tokenizer)
367
+
368
+ def cosine(self, text1: str, text2: str) -> float:
369
+ """Calculate cosine similarity between two texts"""
370
+ return cosine_similarity_text(text1, text2, self.tokenizer)
371
+
372
+ def levenshtein(self, text1: str, text2: str) -> int:
373
+ """Calculate Levenshtein distance between two texts"""
374
+ return levenshtein_distance(text1, text2)
375
+
376
+ def levenshtein_similarity(self, text1: str, text2: str) -> float:
377
+ """Calculate normalized Levenshtein similarity"""
378
+ return normalized_levenshtein_similarity(text1, text2)
379
+
380
+ def fuzzy_match(
381
+ self,
382
+ query: str,
383
+ candidates: List[str],
384
+ threshold: float = 0.6,
385
+ method: str = "jaccard",
386
+ ) -> List[Tuple[str, float]]:
387
+ """Find fuzzy matches for a query"""
388
+ return fuzzy_match(query, candidates, threshold, method)
389
+
390
+ def bm25(self, corpus: List[str], k1: float = 1.5, b: float = 0.75) -> BM25Scorer:
391
+ """Create a BM25 scorer for a corpus"""
392
+ return BM25Scorer(corpus, k1=k1, b=b, tokenizer=self.tokenizer)
@@ -0,0 +1,15 @@
1
+ """
2
+ Knowledge Graph Traversal Application Layer
3
+
4
+ Advanced traversal algorithms and path ranking utilities.
5
+ """
6
+
7
+ from aiecs.application.knowledge_graph.traversal.path_scorer import PathScorer
8
+ from aiecs.application.knowledge_graph.traversal.enhanced_traversal import (
9
+ EnhancedTraversal,
10
+ )
11
+
12
+ __all__ = [
13
+ "PathScorer",
14
+ "EnhancedTraversal",
15
+ ]
@@ -0,0 +1,305 @@
1
+ """
2
+ Enhanced Graph Traversal
3
+
4
+ Provides advanced traversal capabilities with PathPattern support,
5
+ cycle detection, and sophisticated path filtering.
6
+ """
7
+
8
+ from typing import List, Optional
9
+ from collections import deque
10
+ from aiecs.domain.knowledge_graph.models.relation import Relation
11
+ from aiecs.domain.knowledge_graph.models.path import Path
12
+ from aiecs.domain.knowledge_graph.models.path_pattern import (
13
+ PathPattern,
14
+ TraversalDirection,
15
+ )
16
+ from aiecs.infrastructure.graph_storage.base import GraphStore
17
+
18
+
19
+ class EnhancedTraversal:
20
+ """
21
+ Enhanced Graph Traversal Service
22
+
23
+ Provides advanced traversal capabilities beyond basic BFS:
24
+ - PathPattern-based traversal
25
+ - Cycle detection and handling
26
+ - Depth-limited traversal with constraints
27
+ - Path filtering by pattern
28
+
29
+ Example:
30
+ ```python
31
+ traversal = EnhancedTraversal(graph_store)
32
+
33
+ # Define pattern
34
+ pattern = PathPattern(
35
+ relation_types=["WORKS_FOR", "LOCATED_IN"],
36
+ max_depth=2,
37
+ allow_cycles=False
38
+ )
39
+
40
+ # Traverse with pattern
41
+ paths = await traversal.traverse_with_pattern(
42
+ start_entity_id="person_1",
43
+ pattern=pattern,
44
+ max_results=10
45
+ )
46
+ ```
47
+ """
48
+
49
+ def __init__(self, graph_store: GraphStore):
50
+ """
51
+ Initialize enhanced traversal service
52
+
53
+ Args:
54
+ graph_store: Graph storage backend to use
55
+ """
56
+ self.graph_store = graph_store
57
+
58
+ async def traverse_with_pattern(
59
+ self,
60
+ start_entity_id: str,
61
+ pattern: PathPattern,
62
+ max_results: int = 100,
63
+ ) -> List[Path]:
64
+ """
65
+ Traverse graph following a path pattern
66
+
67
+ Args:
68
+ start_entity_id: Starting entity ID
69
+ pattern: Path pattern to follow
70
+ max_results: Maximum number of paths to return
71
+
72
+ Returns:
73
+ List of paths matching the pattern
74
+ """
75
+ start_entity = await self.graph_store.get_entity(start_entity_id)
76
+ if start_entity is None:
77
+ return []
78
+
79
+ # Check if start entity is allowed
80
+ if not pattern.is_entity_allowed(start_entity.id, start_entity.entity_type):
81
+ return []
82
+
83
+ paths: List[Path] = []
84
+ # visited_in_path: Set[str] = set() if not pattern.allow_cycles else
85
+ # None # Reserved for future use
86
+
87
+ # BFS with pattern matching
88
+ queue: deque = deque()
89
+ queue.append(
90
+ {
91
+ "entity": start_entity,
92
+ "path_entities": [start_entity],
93
+ "path_edges": [],
94
+ "depth": 0,
95
+ "visited": ({start_entity.id} if not pattern.allow_cycles else set()),
96
+ }
97
+ )
98
+
99
+ while queue and len(paths) < max_results:
100
+ current = queue.popleft()
101
+ current_entity = current["entity"]
102
+ current_depth = current["depth"]
103
+ path_entities = current["path_entities"]
104
+ path_edges = current["path_edges"]
105
+ visited_nodes = current["visited"]
106
+
107
+ # Add path if it meets length requirements
108
+ if pattern.is_valid_path_length(len(path_edges)):
109
+ path = Path(nodes=path_entities, edges=path_edges)
110
+ paths.append(path)
111
+
112
+ # Continue traversal if not at max depth
113
+ if not pattern.should_continue_traversal(current_depth):
114
+ continue
115
+
116
+ # Get neighbors based on pattern direction
117
+ # pattern.direction is already a string due to use_enum_values=True
118
+ direction_str = pattern.direction if isinstance(pattern.direction, str) else pattern.direction.value
119
+ neighbors = await self.graph_store.get_neighbors(
120
+ current_entity.id,
121
+ relation_type=None, # We'll filter by pattern
122
+ direction=direction_str,
123
+ )
124
+
125
+ for neighbor in neighbors:
126
+ # Check if entity is allowed
127
+ if not pattern.is_entity_allowed(neighbor.id, neighbor.entity_type):
128
+ continue
129
+
130
+ # Check for cycles
131
+ if not pattern.allow_cycles and neighbor.id in visited_nodes:
132
+ continue
133
+
134
+ # Get the relation between current and neighbor
135
+ # We need to find the actual relation
136
+ relation = await self._find_relation(current_entity.id, neighbor.id, pattern.direction)
137
+
138
+ if relation is None:
139
+ continue
140
+
141
+ # Check if relation is allowed at this depth
142
+ if not pattern.is_relation_allowed(relation.relation_type, current_depth):
143
+ continue
144
+
145
+ # For incoming direction, we need to reverse the relation for path construction
146
+ # because paths expect edges[i].source_id == nodes[i].id
147
+ direction_str = pattern.direction if isinstance(pattern.direction, str) else pattern.direction.value
148
+ if direction_str == "incoming":
149
+ # Reverse the relation: if we have e1->e2 and we're going from e2 to e1,
150
+ # the path needs e2->e1 (source=current, target=neighbor)
151
+ path_relation = Relation(
152
+ id=f"{relation.id}_reversed",
153
+ relation_type=relation.relation_type,
154
+ source_id=current_entity.id,
155
+ target_id=neighbor.id,
156
+ weight=relation.weight,
157
+ )
158
+ else:
159
+ path_relation = relation
160
+
161
+ # Create new path state
162
+ new_path_entities = path_entities + [neighbor]
163
+ new_path_edges = path_edges + [path_relation]
164
+ new_visited = visited_nodes | {neighbor.id} if not pattern.allow_cycles else visited_nodes
165
+
166
+ queue.append(
167
+ {
168
+ "entity": neighbor,
169
+ "path_entities": new_path_entities,
170
+ "path_edges": new_path_edges,
171
+ "depth": current_depth + 1,
172
+ "visited": new_visited,
173
+ }
174
+ )
175
+
176
+ return paths
177
+
178
+ async def _find_relation(self, source_id: str, target_id: str, direction: TraversalDirection) -> Optional[Relation]:
179
+ """
180
+ Find the relation between two entities
181
+
182
+ Args:
183
+ source_id: Source entity ID
184
+ target_id: Target entity ID
185
+ direction: Traversal direction (can be enum or string)
186
+
187
+ Returns:
188
+ Relation if found, None otherwise
189
+ """
190
+ # Try to find the actual relation in the graph store
191
+ # This works with both InMemoryGraphStore and SQLiteGraphStore
192
+
193
+ # Handle both enum and string directions
194
+ direction_str = direction if isinstance(direction, str) else direction.value
195
+ direction_enum = TraversalDirection(direction_str) if isinstance(direction, str) else direction
196
+
197
+ if direction_enum == TraversalDirection.OUTGOING or direction_enum == TraversalDirection.BOTH:
198
+ # Look for outgoing relations from source
199
+ neighbors = await self.graph_store.get_neighbors(source_id, relation_type=None, direction="outgoing")
200
+ for neighbor in neighbors:
201
+ if neighbor.id == target_id:
202
+ # Found the neighbor, now get the relation
203
+ # This is a workaround - ideally get_neighbors would return relations too
204
+ # For now, check if the store exposes relations
205
+ from aiecs.infrastructure.graph_storage.in_memory import (
206
+ InMemoryGraphStore,
207
+ )
208
+
209
+ if isinstance(self.graph_store, InMemoryGraphStore):
210
+ for rel in self.graph_store.relations.values():
211
+ if rel.source_id == source_id and rel.target_id == target_id:
212
+ return rel
213
+ else:
214
+ # For SQLite or other stores, try to get the relation
215
+ # This is a placeholder - real implementation would
216
+ # query the DB
217
+ return Relation(
218
+ id=f"rel_{source_id}_{target_id}",
219
+ relation_type="CONNECTED_TO",
220
+ source_id=source_id,
221
+ target_id=target_id,
222
+ )
223
+
224
+ if direction_enum == TraversalDirection.INCOMING or direction_enum == TraversalDirection.BOTH:
225
+ # Look for incoming relations to source (i.e., outgoing from
226
+ # target)
227
+ neighbors = await self.graph_store.get_neighbors(target_id, relation_type=None, direction="outgoing")
228
+ for neighbor in neighbors:
229
+ if neighbor.id == source_id:
230
+ from aiecs.infrastructure.graph_storage.in_memory import (
231
+ InMemoryGraphStore,
232
+ )
233
+
234
+ if isinstance(self.graph_store, InMemoryGraphStore):
235
+ for rel in self.graph_store.relations.values():
236
+ if rel.source_id == target_id and rel.target_id == source_id:
237
+ return rel
238
+ else:
239
+ return Relation(
240
+ id=f"rel_{target_id}_{source_id}",
241
+ relation_type="CONNECTED_TO",
242
+ source_id=target_id,
243
+ target_id=source_id,
244
+ )
245
+
246
+ return None
247
+
248
+ def detect_cycles(self, path: Path) -> bool:
249
+ """
250
+ Detect if a path contains cycles (repeated nodes)
251
+
252
+ Args:
253
+ path: Path to check
254
+
255
+ Returns:
256
+ True if path contains cycles
257
+ """
258
+ entity_ids = path.get_entity_ids()
259
+ return len(entity_ids) != len(set(entity_ids))
260
+
261
+ def filter_paths_without_cycles(self, paths: List[Path]) -> List[Path]:
262
+ """
263
+ Filter out paths that contain cycles
264
+
265
+ Args:
266
+ paths: List of paths to filter
267
+
268
+ Returns:
269
+ List of paths without cycles
270
+ """
271
+ return [path for path in paths if not self.detect_cycles(path)]
272
+
273
+ async def find_all_paths_between(
274
+ self,
275
+ source_id: str,
276
+ target_id: str,
277
+ pattern: Optional[PathPattern] = None,
278
+ max_paths: int = 10,
279
+ ) -> List[Path]:
280
+ """
281
+ Find all paths between two entities matching a pattern
282
+
283
+ Args:
284
+ source_id: Source entity ID
285
+ target_id: Target entity ID
286
+ pattern: Optional path pattern to follow
287
+ max_paths: Maximum number of paths to return
288
+
289
+ Returns:
290
+ List of paths from source to target
291
+ """
292
+ if pattern is None:
293
+ pattern = PathPattern(max_depth=5, allow_cycles=False)
294
+
295
+ # Traverse from source
296
+ all_paths = await self.traverse_with_pattern(
297
+ start_entity_id=source_id,
298
+ pattern=pattern,
299
+ max_results=max_paths * 10, # Get more paths for filtering
300
+ )
301
+
302
+ # Filter paths that end at target
303
+ target_paths = [path for path in all_paths if path.end_entity.id == target_id]
304
+
305
+ return target_paths[:max_paths]