by-qa 0.4.3.dev1__tar.gz → 0.4.4.dev1__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.
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/PKG-INFO +1 -1
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/pyproject.toml +1 -1
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/api/knowledge_entity_schemas.py +17 -1
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/api/routes.py +2 -1
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/dsl/compiler.py +16 -7
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/dsl/validator.py +29 -6
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/infrastructure/runtime.py +11 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/repositories/knowledge_entity_repository.py +38 -1
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/repositories/knowledge_fs_entry_repository.py +79 -64
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/knowledge_base_service.py +13 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/knowledge_entity_enrichment.py +18 -3
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/knowledge_entity_processing_service.py +121 -23
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/knowledge_entity_synonym_resolution.py +97 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/knowledge_entity_task_worker.py +277 -19
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/knowledge_item_ingestion_service.py +25 -11
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/markdown_reference_rewriter.py +4 -0
- by_qa-0.4.4.dev1/src/by_qa/knowledge_base/sql/042_knowledge_fs_entry_upload_uniqueness.sql +12 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_build/services/document_chunking_service.py +5 -2
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_common/markdown_reference.py +25 -4
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/agents/multi_hop_react.py +21 -2
- by_qa-0.4.4.dev1/src/by_qa/qa/agents/recursion_fallback.py +206 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/agents/single_hop_react.py +21 -2
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/engines/instant/engine.py +1 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/tools/dsl_guide.py +12 -9
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/tools/knowledge_tools.py +37 -3
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/uv.lock +1 -1
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/.env.example +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/.github/workflows/ci.yml +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/.github/workflows/release.yml +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/.gitignore +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/.pre-commit-config.yaml +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/AGENTS.md +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/CLAUDE.md +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/CONTEXT.md +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/Dockerfile +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/LICENSE +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/Makefile +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/README.md +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/README_zh.md +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/docker/minio/init/init-minio.sh +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/docker/opengauss/custom/Dockerfile +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/docker/opengauss/init/init-opengauss.sh +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/docker-compose.kb-stack.yml +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/eval/__init__.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/eval/cli.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/eval/judge.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/eval/models.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/eval/runner.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/pylintrc +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/pytest.ini +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/__init__.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/config.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/core/__init__.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/core/exceptions.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/core/framework_client.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/core/logger.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/core/model_config.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/__init__.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/api/__init__.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/api/metadata_schemas.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/api/schemas.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/build_status.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/dsl/__init__.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/dsl/errors.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/events.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/infrastructure/__init__.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/infrastructure/database.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/infrastructure/object_storage.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/infrastructure/storage.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/infrastructure/storage_s3.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/metadata_types.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/repositories/__init__.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/repositories/file_metadata_value_repository.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/repositories/knowledge_base_repository.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/repositories/knowledge_build_acceptance_repository.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/repositories/knowledge_build_batch_repository.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/repositories/knowledge_build_task_repository.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/repositories/knowledge_entity_asset_repository.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/repositories/knowledge_fetch_cache_repository.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/repositories/knowledge_file_reference_repository.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/repositories/knowledge_file_update_timeline_repository.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/repositories/knowledge_item_chunk_repository.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/repositories/knowledge_item_search_repository.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/repositories/knowledge_semantic_processing_batch_repository.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/repositories/knowledge_semantic_processing_task_repository.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/repositories/metadata_search_repository.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/repositories/processing_task_query_repository.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/repositories/retrieval_projection_repository.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/__init__.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/bootstrap_service.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/cache_file_lock.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/document_update_service.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/embedding_query_service.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/entry_metadata.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/errors.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/file_build_background_runner.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/file_build_execution_service.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/file_build_models.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/file_build_mutation_service.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/file_build_processing_service.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/file_build_terminal_event_service.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/file_metadata_query_service.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/file_metadata_update_service.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/knowledge_document_metadata.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/knowledge_entity_background_runner.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/knowledge_entity_discovery.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/knowledge_entity_intelligence.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/knowledge_fetch_cache_cleanup_service.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/knowledge_item_search_service.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/markdown_front_matter.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/markdown_reference_resolver.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/markdown_update_summary_service.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/metadata_search_service.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/semantic_task_mutation_service.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/zip_batch_import_service.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/000_extensions.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/001_knowledge_base.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/002_knowledge_fs_entry.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/003_knowledge_chunk.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/004_retrieval_projection.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/005_knowledge_fetch_cache_index.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/006_knowledge_build_task.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/009_knowledge_fs_entry_indexes.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/010_knowledge_chunk_indexes.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/011_retrieval_projection_indexes.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/012_knowledge_fetch_cache_index_indexes.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/013_knowledge_build_task_indexes.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/014_embedding_table.sql.tpl +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/015_metadata_property_def.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/016_metadata_property_def_indexes.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/017_file_metadata_value.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/018_file_metadata_value_indexes.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/019_knowledge_fs_entry_virtual_path.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/020_knowledge_fs_entry_virtual_path_indexes.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/021_knowledge_fs_entry_virtual_path_backfill.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/022_knowledge_fs_entry_top_level_sibling_unique.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/023_metadata_property_def_name_unique.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/024_knowledge_base_name_unique.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/025_metadata_value_self_contained_migration.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/026_knowledge_file_reference.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/027_knowledge_file_update_timeline.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/028_knowledge_fs_entry_checksum_index.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/029_knowledge_semantic_processing_task.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/030_knowledge_file_reference_semantic_extension.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/031_knowledge_file_document_kind_backfill.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/032_knowledge_schema_migration.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/033_knowledge_entity.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/034_knowledge_entity_embedding_table.sql.tpl +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/035_knowledge_entity_discovery_schema.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/036_knowledge_entity_embedding_full_only.sql.tpl +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/037_knowledge_semantic_processing_directory_scope.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/038_knowledge_build_batch.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/039_knowledge_build_task_background_extension.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/040_knowledge_build_task_legacy_backfill.sql.tpl +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/sql/041_knowledge_build_task_active_constraints.sql +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_build/__init__.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_build/runtime.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_build/services/__init__.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_build/services/heading_patterns.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_common/__init__.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_common/exceptions.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_common/kb_path_utils.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_common/schemas.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_common/text_segmentation.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/main.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/__init__.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/agents/__init__.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/agents/answer_synthesizer.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/agents/multi_hop_summarizer.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/agents/query_decomposer.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/agents/standalone_question_rewriter.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/agents/subanswer_aggregator.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/common/__init__.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/common/base_engine.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/common/config.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/common/context.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/common/context_manager.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/common/event_filter.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/common/exceptions.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/common/fallback_messages.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/common/messages.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/common/middleware/__init__.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/common/middleware/tool_call_guard.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/common/models.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/common/operation_registry.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/common/prompt_fragments.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/common/reducers.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/common/state.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/engines/__init__.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/engines/fast/__init__.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/engines/fast/engine.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/engines/fast/graph.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/engines/fast/nodes/__init__.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/engines/fast/nodes/retrieve.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/engines/fast/state.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/engines/fast/types.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/engines/instant/__init__.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/engines/instant/graph.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/engines/instant/nodes/__init__.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/engines/instant/nodes/final_answer.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/engines/instant/nodes/router.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/engines/instant/nodes/state_cleanup.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/engines/instant/state.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/engines/instant/types.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/services/__init__.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/services/checkpointer_factory.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/services/llm_service.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/services/opengauss_checkpointer.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/services/session_history.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/tools/__init__.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/tools/operations/__init__.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/tools/operations/base.py +0 -0
- {by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/qa/tools/operations/knowledge_search.py +0 -0
{by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/api/knowledge_entity_schemas.py
RENAMED
|
@@ -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
|
|
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):
|
|
@@ -1854,9 +1854,10 @@ def register_routes(
|
|
|
1854
1854
|
status_code=422,
|
|
1855
1855
|
)
|
|
1856
1856
|
logger.info(
|
|
1857
|
-
"entity_enrich request received: kb_code=%s, file_path=%s, force=%s",
|
|
1857
|
+
"entity_enrich request received: kb_code=%s, file_path=%s, directory_path=%s, force=%s",
|
|
1858
1858
|
request.kb_code,
|
|
1859
1859
|
request.file_path,
|
|
1860
|
+
request.directory_path,
|
|
1860
1861
|
request.force,
|
|
1861
1862
|
)
|
|
1862
1863
|
try:
|
|
@@ -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
|
|
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
|
|
199
|
-
#
|
|
200
|
-
# fields; no system-field branch here.
|
|
201
|
-
body = node[
|
|
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
|
-
|
|
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
|
|
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
|
|
272
|
+
if operator in {"contains", "containsAll", "containsAny"}:
|
|
271
273
|
if value_type != "stringList":
|
|
272
274
|
errors.append(
|
|
273
275
|
DslValidationDetail(
|
|
274
|
-
path=f"{path}.
|
|
276
|
+
path=f"{path}.{operator}.fieldName",
|
|
275
277
|
code="INVALID_FIELD_VALUE_TYPE",
|
|
276
278
|
message=(
|
|
277
|
-
f"'
|
|
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
|
|
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
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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,
|
{by_qa-0.4.3.dev1 → by_qa-0.4.4.dev1}/src/by_qa/knowledge_base/services/knowledge_base_service.py
RENAMED
|
@@ -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,
|
|
@@ -1173,8 +1173,13 @@ def _canonical_link_target(target: str) -> str:
|
|
|
1173
1173
|
return quote(decoded, safe="/:@-._~!$&'*+,;=%")
|
|
1174
1174
|
|
|
1175
1175
|
|
|
1176
|
-
def _is_knowledge_entity_target(
|
|
1177
|
-
|
|
1176
|
+
def _is_knowledge_entity_target(
|
|
1177
|
+
target: str, *, known_entity_targets: set[str] | None = None
|
|
1178
|
+
) -> bool:
|
|
1179
|
+
canonical = _canonical_link_target(target)
|
|
1180
|
+
if known_entity_targets and canonical in known_entity_targets:
|
|
1181
|
+
return True
|
|
1182
|
+
return unquote(canonical).startswith("/KnowledgeEntity/")
|
|
1178
1183
|
|
|
1179
1184
|
|
|
1180
1185
|
def normalize_generated_references(
|
|
@@ -1234,11 +1239,21 @@ def normalize_generated_references(
|
|
|
1234
1239
|
for item in evidence
|
|
1235
1240
|
if item.document_kind != "knowledgeEntity"
|
|
1236
1241
|
}
|
|
1242
|
+
known_entity_targets = {
|
|
1243
|
+
_canonical_link_target(
|
|
1244
|
+
_MARKDOWN_LINK_RE.search(
|
|
1245
|
+
format_source_reference(item.document_path, item.document_kind)
|
|
1246
|
+
).group(2)
|
|
1247
|
+
)
|
|
1248
|
+
for item in evidence
|
|
1249
|
+
if item.document_kind == "knowledgeEntity"
|
|
1250
|
+
}
|
|
1237
1251
|
original_source_targets.update(
|
|
1238
1252
|
target
|
|
1239
1253
|
for match in _MARKDOWN_LINK_RE.finditer(existing_markdown or "")
|
|
1240
1254
|
if not _is_knowledge_entity_target(
|
|
1241
|
-
target := _canonical_link_target(match.group(2))
|
|
1255
|
+
target := _canonical_link_target(match.group(2)),
|
|
1256
|
+
known_entity_targets=known_entity_targets,
|
|
1242
1257
|
)
|
|
1243
1258
|
)
|
|
1244
1259
|
normalized, moved_count = _move_trailing_original_links_to_references(
|