by-qa 0.4.4.dev0__tar.gz → 0.4.4.dev2__tar.gz

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.
Files changed (213) hide show
  1. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/PKG-INFO +1 -1
  2. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/pyproject.toml +1 -1
  3. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/api/knowledge_entity_schemas.py +17 -1
  4. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/api/routes.py +7 -3
  5. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/api/schemas.py +4 -0
  6. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/dsl/compiler.py +16 -7
  7. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/dsl/validator.py +29 -6
  8. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/infrastructure/runtime.py +11 -0
  9. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/repositories/knowledge_entity_repository.py +38 -1
  10. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/repositories/knowledge_fs_entry_repository.py +79 -64
  11. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/knowledge_base_service.py +13 -0
  12. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/knowledge_entity_enrichment.py +18 -3
  13. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/knowledge_entity_processing_service.py +121 -23
  14. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/knowledge_entity_synonym_resolution.py +97 -0
  15. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/knowledge_entity_task_worker.py +277 -19
  16. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/knowledge_item_ingestion_service.py +25 -11
  17. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/markdown_reference_rewriter.py +4 -0
  18. by_qa-0.4.4.dev2/src/by_qa/knowledge_base/sql/042_knowledge_fs_entry_upload_uniqueness.sql +12 -0
  19. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_build/services/document_chunking_service.py +5 -2
  20. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_common/markdown_reference.py +25 -4
  21. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/agents/multi_hop_react.py +21 -2
  22. by_qa-0.4.4.dev2/src/by_qa/qa/agents/recursion_fallback.py +206 -0
  23. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/agents/single_hop_react.py +21 -2
  24. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/engines/instant/engine.py +1 -0
  25. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/tools/dsl_guide.py +12 -9
  26. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/tools/knowledge_tools.py +37 -3
  27. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/uv.lock +2 -2
  28. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/.env.example +0 -0
  29. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/.github/workflows/ci.yml +0 -0
  30. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/.github/workflows/release.yml +0 -0
  31. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/.gitignore +0 -0
  32. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/.pre-commit-config.yaml +0 -0
  33. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/AGENTS.md +0 -0
  34. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/CLAUDE.md +0 -0
  35. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/CONTEXT.md +0 -0
  36. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/Dockerfile +0 -0
  37. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/LICENSE +0 -0
  38. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/Makefile +0 -0
  39. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/README.md +0 -0
  40. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/README_zh.md +0 -0
  41. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/docker/minio/init/init-minio.sh +0 -0
  42. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/docker/opengauss/custom/Dockerfile +0 -0
  43. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/docker/opengauss/init/init-opengauss.sh +0 -0
  44. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/docker-compose.kb-stack.yml +0 -0
  45. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/eval/__init__.py +0 -0
  46. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/eval/cli.py +0 -0
  47. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/eval/judge.py +0 -0
  48. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/eval/models.py +0 -0
  49. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/eval/runner.py +0 -0
  50. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/pylintrc +0 -0
  51. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/pytest.ini +0 -0
  52. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/__init__.py +0 -0
  53. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/config.py +0 -0
  54. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/core/__init__.py +0 -0
  55. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/core/exceptions.py +0 -0
  56. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/core/framework_client.py +0 -0
  57. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/core/logger.py +0 -0
  58. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/core/model_config.py +0 -0
  59. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/__init__.py +0 -0
  60. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/api/__init__.py +0 -0
  61. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/api/metadata_schemas.py +0 -0
  62. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/build_status.py +0 -0
  63. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/dsl/__init__.py +0 -0
  64. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/dsl/errors.py +0 -0
  65. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/events.py +0 -0
  66. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/infrastructure/__init__.py +0 -0
  67. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/infrastructure/database.py +0 -0
  68. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/infrastructure/object_storage.py +0 -0
  69. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/infrastructure/storage.py +0 -0
  70. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/infrastructure/storage_s3.py +0 -0
  71. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/metadata_types.py +0 -0
  72. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/repositories/__init__.py +0 -0
  73. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/repositories/file_metadata_value_repository.py +0 -0
  74. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/repositories/knowledge_base_repository.py +0 -0
  75. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/repositories/knowledge_build_acceptance_repository.py +0 -0
  76. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/repositories/knowledge_build_batch_repository.py +0 -0
  77. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/repositories/knowledge_build_task_repository.py +0 -0
  78. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/repositories/knowledge_entity_asset_repository.py +0 -0
  79. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/repositories/knowledge_fetch_cache_repository.py +0 -0
  80. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/repositories/knowledge_file_reference_repository.py +0 -0
  81. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/repositories/knowledge_file_update_timeline_repository.py +0 -0
  82. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/repositories/knowledge_item_chunk_repository.py +0 -0
  83. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/repositories/knowledge_item_search_repository.py +0 -0
  84. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/repositories/knowledge_semantic_processing_batch_repository.py +0 -0
  85. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/repositories/knowledge_semantic_processing_task_repository.py +0 -0
  86. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/repositories/metadata_search_repository.py +0 -0
  87. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/repositories/processing_task_query_repository.py +0 -0
  88. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/repositories/retrieval_projection_repository.py +0 -0
  89. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/__init__.py +0 -0
  90. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/bootstrap_service.py +0 -0
  91. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/cache_file_lock.py +0 -0
  92. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/document_update_service.py +0 -0
  93. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/embedding_query_service.py +0 -0
  94. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/entry_metadata.py +0 -0
  95. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/errors.py +0 -0
  96. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/file_build_background_runner.py +0 -0
  97. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/file_build_execution_service.py +0 -0
  98. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/file_build_models.py +0 -0
  99. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/file_build_mutation_service.py +0 -0
  100. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/file_build_processing_service.py +0 -0
  101. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/file_build_terminal_event_service.py +0 -0
  102. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/file_metadata_query_service.py +0 -0
  103. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/file_metadata_update_service.py +0 -0
  104. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/knowledge_document_metadata.py +0 -0
  105. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/knowledge_entity_background_runner.py +0 -0
  106. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/knowledge_entity_discovery.py +0 -0
  107. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/knowledge_entity_intelligence.py +0 -0
  108. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/knowledge_fetch_cache_cleanup_service.py +0 -0
  109. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/knowledge_item_search_service.py +0 -0
  110. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/markdown_front_matter.py +0 -0
  111. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/markdown_reference_resolver.py +0 -0
  112. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/markdown_update_summary_service.py +0 -0
  113. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/metadata_search_service.py +0 -0
  114. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/semantic_task_mutation_service.py +0 -0
  115. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/services/zip_batch_import_service.py +0 -0
  116. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/000_extensions.sql +0 -0
  117. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/001_knowledge_base.sql +0 -0
  118. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/002_knowledge_fs_entry.sql +0 -0
  119. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/003_knowledge_chunk.sql +0 -0
  120. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/004_retrieval_projection.sql +0 -0
  121. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/005_knowledge_fetch_cache_index.sql +0 -0
  122. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/006_knowledge_build_task.sql +0 -0
  123. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/009_knowledge_fs_entry_indexes.sql +0 -0
  124. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/010_knowledge_chunk_indexes.sql +0 -0
  125. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/011_retrieval_projection_indexes.sql +0 -0
  126. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/012_knowledge_fetch_cache_index_indexes.sql +0 -0
  127. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/013_knowledge_build_task_indexes.sql +0 -0
  128. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/014_embedding_table.sql.tpl +0 -0
  129. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/015_metadata_property_def.sql +0 -0
  130. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/016_metadata_property_def_indexes.sql +0 -0
  131. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/017_file_metadata_value.sql +0 -0
  132. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/018_file_metadata_value_indexes.sql +0 -0
  133. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/019_knowledge_fs_entry_virtual_path.sql +0 -0
  134. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/020_knowledge_fs_entry_virtual_path_indexes.sql +0 -0
  135. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/021_knowledge_fs_entry_virtual_path_backfill.sql +0 -0
  136. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/022_knowledge_fs_entry_top_level_sibling_unique.sql +0 -0
  137. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/023_metadata_property_def_name_unique.sql +0 -0
  138. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/024_knowledge_base_name_unique.sql +0 -0
  139. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/025_metadata_value_self_contained_migration.sql +0 -0
  140. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/026_knowledge_file_reference.sql +0 -0
  141. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/027_knowledge_file_update_timeline.sql +0 -0
  142. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/028_knowledge_fs_entry_checksum_index.sql +0 -0
  143. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/029_knowledge_semantic_processing_task.sql +0 -0
  144. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/030_knowledge_file_reference_semantic_extension.sql +0 -0
  145. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/031_knowledge_file_document_kind_backfill.sql +0 -0
  146. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/032_knowledge_schema_migration.sql +0 -0
  147. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/033_knowledge_entity.sql +0 -0
  148. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/034_knowledge_entity_embedding_table.sql.tpl +0 -0
  149. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/035_knowledge_entity_discovery_schema.sql +0 -0
  150. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/036_knowledge_entity_embedding_full_only.sql.tpl +0 -0
  151. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/037_knowledge_semantic_processing_directory_scope.sql +0 -0
  152. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/038_knowledge_build_batch.sql +0 -0
  153. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/039_knowledge_build_task_background_extension.sql +0 -0
  154. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/040_knowledge_build_task_legacy_backfill.sql.tpl +0 -0
  155. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_base/sql/041_knowledge_build_task_active_constraints.sql +0 -0
  156. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_build/__init__.py +0 -0
  157. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_build/runtime.py +0 -0
  158. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_build/services/__init__.py +0 -0
  159. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_build/services/heading_patterns.py +0 -0
  160. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_common/__init__.py +0 -0
  161. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_common/exceptions.py +0 -0
  162. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_common/kb_path_utils.py +0 -0
  163. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_common/schemas.py +0 -0
  164. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/knowledge_common/text_segmentation.py +0 -0
  165. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/main.py +0 -0
  166. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/__init__.py +0 -0
  167. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/agents/__init__.py +0 -0
  168. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/agents/answer_synthesizer.py +0 -0
  169. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/agents/multi_hop_summarizer.py +0 -0
  170. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/agents/query_decomposer.py +0 -0
  171. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/agents/standalone_question_rewriter.py +0 -0
  172. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/agents/subanswer_aggregator.py +0 -0
  173. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/common/__init__.py +0 -0
  174. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/common/base_engine.py +0 -0
  175. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/common/config.py +0 -0
  176. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/common/context.py +0 -0
  177. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/common/context_manager.py +0 -0
  178. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/common/event_filter.py +0 -0
  179. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/common/exceptions.py +0 -0
  180. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/common/fallback_messages.py +0 -0
  181. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/common/messages.py +0 -0
  182. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/common/middleware/__init__.py +0 -0
  183. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/common/middleware/tool_call_guard.py +0 -0
  184. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/common/models.py +0 -0
  185. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/common/operation_registry.py +0 -0
  186. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/common/prompt_fragments.py +0 -0
  187. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/common/reducers.py +0 -0
  188. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/common/state.py +0 -0
  189. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/engines/__init__.py +0 -0
  190. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/engines/fast/__init__.py +0 -0
  191. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/engines/fast/engine.py +0 -0
  192. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/engines/fast/graph.py +0 -0
  193. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/engines/fast/nodes/__init__.py +0 -0
  194. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/engines/fast/nodes/retrieve.py +0 -0
  195. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/engines/fast/state.py +0 -0
  196. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/engines/fast/types.py +0 -0
  197. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/engines/instant/__init__.py +0 -0
  198. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/engines/instant/graph.py +0 -0
  199. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/engines/instant/nodes/__init__.py +0 -0
  200. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/engines/instant/nodes/final_answer.py +0 -0
  201. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/engines/instant/nodes/router.py +0 -0
  202. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/engines/instant/nodes/state_cleanup.py +0 -0
  203. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/engines/instant/state.py +0 -0
  204. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/engines/instant/types.py +0 -0
  205. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/services/__init__.py +0 -0
  206. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/services/checkpointer_factory.py +0 -0
  207. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/services/llm_service.py +0 -0
  208. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/services/opengauss_checkpointer.py +0 -0
  209. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/services/session_history.py +0 -0
  210. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/tools/__init__.py +0 -0
  211. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/tools/operations/__init__.py +0 -0
  212. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/tools/operations/base.py +0 -0
  213. {by_qa-0.4.4.dev0 → by_qa-0.4.4.dev2}/src/by_qa/qa/tools/operations/knowledge_search.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: by-qa
3
- Version: 0.4.4.dev0
3
+ Version: 0.4.4.dev2
4
4
  Summary: Open-source knowledge-base ingestion and retrieval service.
5
5
  Author: AI Assistant
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "by-qa"
7
- version = "0.4.4.dev0"
7
+ version = "0.4.4.dev2"
8
8
  description = "Open-source knowledge-base ingestion and retrieval service."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -129,6 +129,11 @@ class EntityDiscoveryRequest(_ApiModel):
129
129
  default=None,
130
130
  validation_alias=AliasChoices("directoryPath", "directory_path"),
131
131
  )
132
+ target_directory_path: str | None = Field(
133
+ default=None,
134
+ validation_alias=AliasChoices("targetDirectoryPath", "target_directory_path"),
135
+ serialization_alias="targetDirectoryPath",
136
+ )
132
137
  max_entities: int = Field(
133
138
  default=12,
134
139
  ge=1,
@@ -142,6 +147,7 @@ class EntityDiscoveryRequest(_ApiModel):
142
147
  validation_alias=AliasChoices("maxTopics", "max_topics"),
143
148
  )
144
149
  force: bool = False
150
+ tags: list[str] | None = None
145
151
  extra_params: dict[str, Any] | None = Field(
146
152
  default=None,
147
153
  validation_alias=AliasChoices("extraParams", "extra_params"),
@@ -153,10 +159,13 @@ class EntityDiscoveryRequest(_ApiModel):
153
159
  _normalize_directory_path = field_validator("directory_path")(
154
160
  _validate_directory_path
155
161
  )
162
+ _normalize_target_directory_path = field_validator("target_directory_path")(
163
+ _validate_directory_path
164
+ )
156
165
 
157
166
 
158
167
  class EntityEnrichRequest(_ApiModel):
159
- """Request for single-file or whole-knowledge-base entity enrichment."""
168
+ """Request for file, directory, or whole-knowledge-base entity enrichment."""
160
169
 
161
170
  kb_code: str = Field(
162
171
  min_length=1,
@@ -166,6 +175,10 @@ class EntityEnrichRequest(_ApiModel):
166
175
  default=None,
167
176
  validation_alias=AliasChoices("filePath", "file_path"),
168
177
  )
178
+ directory_path: str | None = Field(
179
+ default=None,
180
+ validation_alias=AliasChoices("directoryPath", "directory_path"),
181
+ )
169
182
  top_k: int = Field(
170
183
  default=20,
171
184
  ge=1,
@@ -181,6 +194,9 @@ class EntityEnrichRequest(_ApiModel):
181
194
  description="Deprecated compatibility field; accepted but ignored.",
182
195
  )
183
196
  _normalize_file_path = field_validator("file_path")(_validate_file_path)
197
+ _normalize_directory_path = field_validator("directory_path")(
198
+ _validate_directory_path
199
+ )
184
200
 
185
201
 
186
202
  class DeleteKnowledgeEntityRequest(_ApiModel):
@@ -694,6 +694,7 @@ def register_routes(
694
694
  file_description: str | None = Body(None, alias="fileDescription"),
695
695
  file_content: UploadFile | None = File(None, alias="fileContent"),
696
696
  process_front_matter: bool = Form(True, alias="processFrontMatter"),
697
+ extract_zip: bool = Form(True, alias="extractZip"),
697
698
  skip_if_duplicate: bool = Form(False, alias="skipIfDuplicate"),
698
699
  metadata: str | None = Form(None, alias="metadata"),
699
700
  ):
@@ -709,6 +710,7 @@ def register_routes(
709
710
  file_content.content_type if file_content is not None else None
710
711
  ),
711
712
  "processFrontMatter": process_front_matter,
713
+ "extractZip": extract_zip,
712
714
  "skipIfDuplicate": skip_if_duplicate,
713
715
  "metadata": _parse_optional_metadata_form(metadata),
714
716
  }
@@ -724,16 +726,17 @@ def register_routes(
724
726
  status_code=422,
725
727
  )
726
728
  logger.info(
727
- "upload_file request received: kb_code=%s, file_path=%s, has_description=%s, process_front_matter=%s",
729
+ "upload_file request received: kb_code=%s, file_path=%s, has_description=%s, process_front_matter=%s, extract_zip=%s",
728
730
  request.kb_code,
729
731
  request.file_path,
730
732
  request.file_description is not None,
731
733
  request.process_front_matter,
734
+ request.extract_zip,
732
735
  )
733
736
  filename = (file_content.filename or "") if file_content is not None else ""
734
737
  try:
735
738
  service = await get_knowledge_item_ingestion_service()
736
- if filename.lower().endswith(".zip"):
739
+ if request.extract_zip and filename.lower().endswith(".zip"):
737
740
  if not zipfile.is_zipfile(io.BytesIO(payload or b"")):
738
741
  return _documented_error_response(
739
742
  result_msg="invalid zip file",
@@ -1854,9 +1857,10 @@ def register_routes(
1854
1857
  status_code=422,
1855
1858
  )
1856
1859
  logger.info(
1857
- "entity_enrich request received: kb_code=%s, file_path=%s, force=%s",
1860
+ "entity_enrich request received: kb_code=%s, file_path=%s, directory_path=%s, force=%s",
1858
1861
  request.kb_code,
1859
1862
  request.file_path,
1863
+ request.directory_path,
1860
1864
  request.force,
1861
1865
  )
1862
1866
  try:
@@ -329,6 +329,10 @@ class KnowledgeItemUploadRequest(BaseModel):
329
329
  default=True,
330
330
  validation_alias=AliasChoices("processFrontMatter", "process_front_matter"),
331
331
  )
332
+ extract_zip: bool = Field(
333
+ default=True,
334
+ validation_alias=AliasChoices("extractZip", "extract_zip"),
335
+ )
332
336
  metadata: dict[str, Any] | None = None
333
337
  skip_if_duplicate: bool = Field(
334
338
  default=False,
@@ -74,7 +74,7 @@ def _custom_field_filter(
74
74
  def _query_value_type(operator: str, value: Any) -> str:
75
75
  if operator in ("prefix", "wildcard"):
76
76
  return "string"
77
- if operator == "contains":
77
+ if operator in {"contains", "containsAll", "containsAny"}:
78
78
  return "stringList"
79
79
  if operator in ORDER_OPS and isinstance(value, str):
80
80
  if _parse_datetime_literal(value) is not None:
@@ -195,10 +195,10 @@ def _compile_node(
195
195
  f"AND mv.{col} LIKE %({val_key})s ESCAPE '{LIKE_ESCAPE_CHAR}')"
196
196
  )
197
197
 
198
- if operator == "contains":
199
- # `contains` is validator-restricted to user-defined stringList
200
- # fields; no system-field branch here.
201
- body = node["contains"]
198
+ if operator in {"contains", "containsAll", "containsAny"}:
199
+ # These operators are validator-restricted to user-defined stringList
200
+ # fields; there is no system-field branch here.
201
+ body = node[operator]
202
202
  field_name = body["fieldName"]
203
203
  value = body["value"]
204
204
  prop = property_map[field_name]
@@ -206,11 +206,20 @@ def _compile_node(
206
206
  filters = _custom_field_filter(
207
207
  ctx, field_name=field_name, value_type=value_type
208
208
  )
209
- val_key = ctx.next_param(json.dumps([value]))
209
+ values = [value] if operator == "contains" else value
210
+ if operator == "containsAny":
211
+ predicates = []
212
+ for item in values:
213
+ val_key = ctx.next_param(json.dumps([item]))
214
+ predicates.append(f"mv.value_string_list @> %({val_key})s::jsonb")
215
+ value_predicate = "(" + " OR ".join(predicates) + ")"
216
+ else:
217
+ val_key = ctx.next_param(json.dumps(values))
218
+ value_predicate = f"mv.value_string_list @> %({val_key})s::jsonb"
210
219
  return (
211
220
  f"EXISTS (SELECT 1 FROM knowledge_file_metadata_value mv "
212
221
  f"WHERE {filters} "
213
- f"AND mv.value_string_list @> %({val_key})s::jsonb)"
222
+ f"AND {value_predicate})"
214
223
  )
215
224
 
216
225
  if operator == "in":
@@ -14,6 +14,8 @@ LEAF_OPERATORS = {
14
14
  "ne",
15
15
  "in",
16
16
  "contains",
17
+ "containsAll",
18
+ "containsAny",
17
19
  "exists",
18
20
  "gt",
19
21
  "gte",
@@ -267,20 +269,20 @@ def _validate_leaf(
267
269
  )
268
270
  return
269
271
 
270
- if operator == "contains":
272
+ if operator in {"contains", "containsAll", "containsAny"}:
271
273
  if value_type != "stringList":
272
274
  errors.append(
273
275
  DslValidationDetail(
274
- path=f"{path}.contains.fieldName",
276
+ path=f"{path}.{operator}.fieldName",
275
277
  code="INVALID_FIELD_VALUE_TYPE",
276
278
  message=(
277
- f"'contains' is only valid for stringList fields; "
279
+ f"'{operator}' is only valid for stringList fields; "
278
280
  f"'{field_name}' is {value_type}"
279
281
  ),
280
282
  )
281
283
  )
282
284
  return
283
- if not isinstance(value, str):
285
+ if operator == "contains" and not isinstance(value, str):
284
286
  errors.append(
285
287
  DslValidationDetail(
286
288
  path=f"{path}.contains.value",
@@ -288,6 +290,26 @@ def _validate_leaf(
288
290
  message="'contains' value must be a string",
289
291
  )
290
292
  )
293
+ elif operator != "contains":
294
+ if not isinstance(value, list) or not value:
295
+ errors.append(
296
+ DslValidationDetail(
297
+ path=f"{path}.{operator}.value",
298
+ code="INVALID_FIELD_VALUE_TYPE",
299
+ message=f"'{operator}' value must be a non-empty array",
300
+ )
301
+ )
302
+ return
303
+ for i, item in enumerate(value):
304
+ if not isinstance(item, str):
305
+ errors.append(
306
+ DslValidationDetail(
307
+ path=f"{path}.{operator}.value[{i}]",
308
+ code="INVALID_FIELD_VALUE_TYPE",
309
+ message=f"'{operator}' value at index {i} must be a string",
310
+ )
311
+ )
312
+ return
291
313
  return
292
314
 
293
315
  if operator == "in":
@@ -297,7 +319,8 @@ def _validate_leaf(
297
319
  path=f"{path}.in.fieldName",
298
320
  code="INVALID_FIELD_VALUE_TYPE",
299
321
  message=(
300
- "'in' is not supported for stringList fields; use 'contains'"
322
+ "'in' is not supported for stringList fields; use "
323
+ "'containsAll' or 'containsAny'"
301
324
  ),
302
325
  )
303
326
  )
@@ -381,7 +404,7 @@ def _value_matches_type(value: Any, value_type: str) -> bool:
381
404
  def _infer_query_value_type(operator: str, value: Any) -> str:
382
405
  if operator in ("prefix", "wildcard"):
383
406
  return "string"
384
- if operator == "contains":
407
+ if operator in {"contains", "containsAll", "containsAny"}:
385
408
  return "stringList"
386
409
  if operator == "in" and isinstance(value, list) and value:
387
410
  return _infer_query_value_type("eq", value[0])
@@ -642,6 +642,15 @@ async def build_knowledge_entity_processing_service(
642
642
  settings, "entity_synonym_adjudication_enabled", True
643
643
  ),
644
644
  )
645
+ move_service = KnowledgeBaseService(
646
+ connection_factory=connection_factory,
647
+ knowledge_base_repository=knowledge_base_repository,
648
+ knowledge_fs_entry_repository=KnowledgeFsEntryRepository(),
649
+ retrieval_projection_repository=RetrievalProjectionRepository(),
650
+ knowledge_fetch_cache_repository=KnowledgeFetchCacheRepository(),
651
+ storage_provider=storage_provider,
652
+ file_metadata_value_repository=FileMetadataValueRepository(),
653
+ )
645
654
  worker = KnowledgeEntityTaskWorker(
646
655
  connection_factory=connection_factory,
647
656
  knowledge_entity_repository=knowledge_entity_repository,
@@ -654,6 +663,8 @@ async def build_knowledge_entity_processing_service(
654
663
  knowledge_entity_discovery=KnowledgeEntityDiscovery(discovery_llm),
655
664
  knowledge_entity_enricher=KnowledgeEntityEnricher(enrichment_llm),
656
665
  knowledge_entity_asset_service=asset_service,
666
+ semantic_task_repository=semantic_processing_task_repository,
667
+ knowledge_item_move_service=move_service,
657
668
  )
658
669
  service = KnowledgeEntityProcessingOrchestrator(
659
670
  connection_factory=connection_factory,
@@ -24,8 +24,9 @@ class KnowledgeEntityRepository:
24
24
  "subjectFileId",
25
25
  "entityType",
26
26
  "entityEnriched",
27
+ "tags",
27
28
  )
28
- _LIST_METADATA_FIELDS = frozenset({"processingCapabilities", "aliases"})
29
+ _LIST_METADATA_FIELDS = frozenset({"processingCapabilities", "aliases", "tags"})
29
30
  _OUTPUT_FIELD_BY_PROPERTY = {
30
31
  "documentKind": "document_kind",
31
32
  "processingCapabilities": "processing_capabilities",
@@ -34,6 +35,7 @@ class KnowledgeEntityRepository:
34
35
  "subjectFileId": "subject_file_id",
35
36
  "entityType": "entity_type",
36
37
  "entityEnriched": "entity_enriched",
38
+ "tags": "tags",
37
39
  }
38
40
 
39
41
  async def get_directory_by_path(
@@ -102,8 +104,13 @@ class KnowledgeEntityRepository:
102
104
  knowledge_base_id: int,
103
105
  path_prefix: str | None = None,
104
106
  exclude_knowledge_entities: bool = False,
107
+ include_knowledge_entities_only: bool = False,
105
108
  ) -> list[dict[str, Any]]:
106
109
  """List live files in stable path order with optional scope filters."""
110
+ if exclude_knowledge_entities and include_knowledge_entities_only:
111
+ raise ValueError(
112
+ "entity include and exclude filters are mutually exclusive"
113
+ )
107
114
  normalized_prefix = (
108
115
  self._normalize_path(path_prefix) if path_prefix is not None else None
109
116
  )
@@ -143,6 +150,34 @@ class KnowledgeEntityRepository:
143
150
  )
144
151
  )
145
152
  )
153
+ AND (
154
+ NOT %(include_knowledge_entities_only)s
155
+ OR EXISTS (
156
+ SELECT 1
157
+ FROM knowledge_file_metadata_value entity_document_kind
158
+ WHERE entity_document_kind.fs_entry_id = fe.kid
159
+ AND entity_document_kind.is_deleted = FALSE
160
+ AND entity_document_kind.property_name = 'documentKind'
161
+ AND entity_document_kind.value_type = 'string'
162
+ AND entity_document_kind.value_string = 'knowledgeEntity'
163
+ )
164
+ OR (
165
+ (
166
+ fe.virtual_path = '/KnowledgeEntity'
167
+ OR LEFT(
168
+ fe.virtual_path,
169
+ LENGTH('/KnowledgeEntity/')
170
+ ) = '/KnowledgeEntity/'
171
+ )
172
+ AND NOT EXISTS (
173
+ SELECT 1
174
+ FROM knowledge_file_metadata_value configured_document_kind
175
+ WHERE configured_document_kind.fs_entry_id = fe.kid
176
+ AND configured_document_kind.is_deleted = FALSE
177
+ AND configured_document_kind.property_name = 'documentKind'
178
+ )
179
+ )
180
+ )
146
181
  ORDER BY fe.virtual_path ASC, fe.kid ASC,
147
182
  mv.property_name ASC, mv.kid ASC
148
183
  """,
@@ -150,6 +185,7 @@ class KnowledgeEntityRepository:
150
185
  "knowledge_base_id": knowledge_base_id,
151
186
  "path_prefix": normalized_prefix,
152
187
  "exclude_knowledge_entities": exclude_knowledge_entities,
188
+ "include_knowledge_entities_only": include_knowledge_entities_only,
153
189
  "property_names": list(self._METADATA_FIELDS),
154
190
  },
155
191
  )
@@ -335,6 +371,7 @@ class KnowledgeEntityRepository:
335
371
  "subject_file_id": None,
336
372
  "entity_type": None,
337
373
  "entity_enriched": None,
374
+ "tags": [],
338
375
  }
339
376
  records[file_id] = record
340
377
  cls._apply_metadata(record, row)
@@ -30,7 +30,6 @@ class KnowledgeFsEntryRepository:
30
30
  current_path_ltree: str | None = None
31
31
  current_virtual_path: str = "/"
32
32
  path_segments = normalized_path.split("/")
33
- await self._lock_entry_tree_for_write(cursor)
34
33
 
35
34
  for index, segment in enumerate(path_segments[:-1], start=1):
36
35
  existing = await self._get_child_entry(
@@ -38,6 +37,7 @@ class KnowledgeFsEntryRepository:
38
37
  knowledge_base_id=knowledge_base_id,
39
38
  parent_entry_id=current_parent_id,
40
39
  name=segment,
40
+ for_share=True,
41
41
  )
42
42
  if existing is not None:
43
43
  if existing.get("entry_type") != "DIRECTORY":
@@ -101,6 +101,7 @@ class KnowledgeFsEntryRepository:
101
101
  full_path: str,
102
102
  file_description: str | None = None,
103
103
  created_parent_entries: list[dict[str, Any]] | None = None,
104
+ create_missing_parents: bool = True,
104
105
  ) -> dict[str, Any] | None:
105
106
  """Create one file entry under an existing parent directory."""
106
107
  normalized_path = full_path.strip("/")
@@ -111,7 +112,6 @@ class KnowledgeFsEntryRepository:
111
112
  current_path_ltree: str | None = None
112
113
  current_virtual_path: str = "/"
113
114
  path_segments = normalized_path.split("/")
114
- await self._lock_entry_tree_for_write(cursor)
115
115
 
116
116
  for index, segment in enumerate(path_segments[:-1], start=1):
117
117
  existing = await self._get_child_entry(
@@ -119,6 +119,7 @@ class KnowledgeFsEntryRepository:
119
119
  knowledge_base_id=knowledge_base_id,
120
120
  parent_entry_id=current_parent_id,
121
121
  name=segment,
122
+ for_share=True,
122
123
  )
123
124
  if existing is not None:
124
125
  if existing.get("entry_type") != "DIRECTORY":
@@ -131,6 +132,9 @@ class KnowledgeFsEntryRepository:
131
132
  current_virtual_path = self._row_value(existing, "virtual_path")
132
133
  continue
133
134
 
135
+ if not create_missing_parents:
136
+ raise ValueError(f"parent directory not found: {normalized_path}")
137
+
134
138
  parent_directory, created = await self._insert_directory_entry(
135
139
  cursor,
136
140
  knowledge_base_id=knowledge_base_id,
@@ -165,62 +169,69 @@ class KnowledgeFsEntryRepository:
165
169
  if current_virtual_path != "/"
166
170
  else f"/{path_segments[-1]}"
167
171
  )
168
- await cursor.execute(
169
- """
170
- INSERT INTO knowledge_fs_entry (
171
- knowledge_base_id,
172
- parent_entry_id,
173
- entry_type,
174
- is_root,
175
- name,
176
- path_ltree,
177
- depth,
178
- description,
179
- virtual_path,
180
- file_bucket_name,
181
- file_object_key,
182
- markdown_bucket_name,
183
- markdown_object_key,
184
- file_size,
185
- mime_type,
186
- checksum,
187
- line_count,
188
- created_at,
189
- updated_at
190
- )
191
- VALUES (
192
- %(knowledge_base_id)s,
193
- %(parent_entry_id)s,
194
- 'FILE',
195
- FALSE,
196
- %(name)s,
197
- %(path_ltree)s::ltree,
198
- %(depth)s,
199
- %(description)s,
200
- %(virtual_path)s,
201
- NULL,
202
- NULL,
203
- NULL,
204
- NULL,
205
- NULL,
206
- NULL,
207
- NULL,
208
- NULL,
209
- NOW(),
210
- NOW()
172
+ try:
173
+ await cursor.execute(
174
+ """
175
+ INSERT INTO knowledge_fs_entry (
176
+ knowledge_base_id,
177
+ parent_entry_id,
178
+ entry_type,
179
+ is_root,
180
+ name,
181
+ path_ltree,
182
+ depth,
183
+ description,
184
+ virtual_path,
185
+ file_bucket_name,
186
+ file_object_key,
187
+ markdown_bucket_name,
188
+ markdown_object_key,
189
+ file_size,
190
+ mime_type,
191
+ checksum,
192
+ line_count,
193
+ created_at,
194
+ updated_at
195
+ )
196
+ VALUES (
197
+ %(knowledge_base_id)s,
198
+ %(parent_entry_id)s,
199
+ 'FILE',
200
+ FALSE,
201
+ %(name)s,
202
+ %(path_ltree)s::ltree,
203
+ %(depth)s,
204
+ %(description)s,
205
+ %(virtual_path)s,
206
+ NULL,
207
+ NULL,
208
+ NULL,
209
+ NULL,
210
+ NULL,
211
+ NULL,
212
+ NULL,
213
+ NULL,
214
+ NOW(),
215
+ NOW()
216
+ )
217
+ RETURNING kid, knowledge_base_id, parent_entry_id, path_ltree, name, entry_type, is_root, depth, virtual_path
218
+ """,
219
+ {
220
+ "knowledge_base_id": knowledge_base_id,
221
+ "parent_entry_id": current_parent_id,
222
+ "name": path_segments[-1],
223
+ "path_ltree": path_ltree,
224
+ "depth": len(path_segments),
225
+ "description": file_description,
226
+ "virtual_path": virtual_path,
227
+ },
211
228
  )
212
- RETURNING kid, knowledge_base_id, parent_entry_id, path_ltree, name, entry_type, is_root, depth, virtual_path
213
- """,
214
- {
215
- "knowledge_base_id": knowledge_base_id,
216
- "parent_entry_id": current_parent_id,
217
- "name": path_segments[-1],
218
- "path_ltree": path_ltree,
219
- "depth": len(path_segments),
220
- "description": file_description,
221
- "virtual_path": virtual_path,
222
- },
223
- )
229
+ except Exception as exc:
230
+ if self._is_unique_violation(exc):
231
+ raise ValueError(
232
+ f"file path already exists: /{normalized_path}"
233
+ ) from exc
234
+ raise
224
235
  return await cursor.fetchone()
225
236
 
226
237
  async def update_file_entry_storage(
@@ -439,11 +450,6 @@ class KnowledgeFsEntryRepository:
439
450
  or getattr(exc, "pgcode", None) == "23505"
440
451
  )
441
452
 
442
- async def _lock_entry_tree_for_write(self, cursor: Any) -> None:
443
- await cursor.execute(
444
- "LOCK TABLE knowledge_fs_entry IN SHARE ROW EXCLUSIVE MODE"
445
- )
446
-
447
453
  async def lock_checksum_scope(
448
454
  self,
449
455
  cursor: Any,
@@ -797,6 +803,7 @@ class KnowledgeFsEntryRepository:
797
803
  knowledge_base_id: int,
798
804
  parent_entry_id: int | None,
799
805
  name: str,
806
+ for_share: bool = False,
800
807
  ) -> dict[str, Any] | None:
801
808
  if parent_entry_id is None:
802
809
  await cursor.execute(
@@ -824,7 +831,8 @@ class KnowledgeFsEntryRepository:
824
831
  AND parent_entry_id IS NULL
825
832
  AND name = %(name)s
826
833
  AND is_deleted = FALSE
827
- """,
834
+ """
835
+ + (" FOR SHARE" if for_share else ""),
828
836
  {
829
837
  "knowledge_base_id": knowledge_base_id,
830
838
  "name": name,
@@ -856,7 +864,8 @@ class KnowledgeFsEntryRepository:
856
864
  AND parent_entry_id = %(parent_entry_id)s
857
865
  AND name = %(name)s
858
866
  AND is_deleted = FALSE
859
- """,
867
+ """
868
+ + (" FOR SHARE" if for_share else ""),
860
869
  {
861
870
  "knowledge_base_id": knowledge_base_id,
862
871
  "parent_entry_id": parent_entry_id,
@@ -944,6 +953,12 @@ class KnowledgeFsEntryRepository:
944
953
  """Fetch one filesystem entry by id."""
945
954
  return await self._get_entry_by_id(cursor, entry_id=entry_id)
946
955
 
956
+ async def get_entry_by_id_for_update(
957
+ self, cursor: Any, *, entry_id: int
958
+ ) -> dict[str, Any] | None:
959
+ """Fetch and lock one live filesystem entry by id."""
960
+ return await self._get_entry_by_id(cursor, entry_id=entry_id, for_update=True)
961
+
947
962
  async def get_child_entry(
948
963
  self,
949
964
  cursor: Any,
@@ -347,6 +347,15 @@ class KnowledgeBaseService:
347
347
  finally:
348
348
  await connection.close()
349
349
 
350
+ @staticmethod
351
+ async def _lock_entry_for_tree_mutation(cursor: Any, entry_id: int) -> None:
352
+ # Wait for uploads holding shared ancestor locks before taking the
353
+ # subtree snapshot, so newly committed files are included in the change.
354
+ await cursor.execute(
355
+ "SELECT kid FROM knowledge_fs_entry WHERE kid = %(entry_id)s FOR UPDATE",
356
+ {"entry_id": entry_id},
357
+ )
358
+
350
359
  async def delete_directory(self, request: DeleteDirectoryRequest) -> None:
351
360
  """Logically delete one directory subtree and its retrieval projection rows."""
352
361
  logger.info(
@@ -381,6 +390,7 @@ class KnowledgeBaseService:
381
390
  f"directory not found: {request.directory_path}"
382
391
  )
383
392
  root_fs_entry_id = int(directory_row["kid"])
393
+ await self._lock_entry_for_tree_mutation(cursor, root_fs_entry_id)
384
394
  fs_entry_ids = (
385
395
  await self.knowledge_fs_entry_repository.list_subtree_entry_ids(
386
396
  cursor,
@@ -523,6 +533,7 @@ class KnowledgeBaseService:
523
533
  f"directory not found: {request.directory_path}"
524
534
  )
525
535
  fs_entry_id = int(fs_entry_row["kid"])
536
+ await self._lock_entry_for_tree_mutation(cursor, fs_entry_id)
526
537
  moved: list = []
527
538
  locator_updates: list = []
528
539
 
@@ -753,6 +764,8 @@ class KnowledgeBaseService:
753
764
  if source is None:
754
765
  raise KnowledgeBaseValidationError(f"source path not found: {source_path}")
755
766
 
767
+ await self._lock_entry_for_tree_mutation(cursor, int(source["kid"]))
768
+
756
769
  target_parent_path, target_name, target_path = await self._resolve_move_target(
757
770
  request=request,
758
771
  source=source,