igel-qe-core 1.0.0
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.
- package/dist/cli/index.js +38 -0
- package/dist/mcp/server.js +104 -0
- package/knowledge_base/__init__.py +3 -0
- package/knowledge_base/__pycache__/__init__.cpython-314.pyc +0 -0
- package/knowledge_base/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/__pycache__/confidence.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/config.cpython-314.pyc +0 -0
- package/knowledge_base/__pycache__/config.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/config.cpython-38.pyc +0 -0
- package/knowledge_base/__pycache__/context_fusion.cpython-314.pyc +0 -0
- package/knowledge_base/__pycache__/context_fusion.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/feedback_analytics.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/feedback_applier.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/feedback_cli.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/feedback_curator.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/feedback_handler.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/generate.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/generate_qcapps134.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/input_classifier.cpython-314.pyc +0 -0
- package/knowledge_base/__pycache__/input_classifier.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/kg_extractor.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/knowledge_graph.cpython-314.pyc +0 -0
- package/knowledge_base/__pycache__/knowledge_graph.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/manual_tc_parser.cpython-314.pyc +0 -0
- package/knowledge_base/__pycache__/manual_tc_parser.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/precedence.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/prompts.cpython-314.pyc +0 -0
- package/knowledge_base/__pycache__/prompts.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/reranker.cpython-314.pyc +0 -0
- package/knowledge_base/__pycache__/reranker.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/retrieval_debug.cpython-314.pyc +0 -0
- package/knowledge_base/__pycache__/retrieval_debug.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/retriever.cpython-314.pyc +0 -0
- package/knowledge_base/__pycache__/retriever.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/server.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/tc_transformer.cpython-314.pyc +0 -0
- package/knowledge_base/__pycache__/tc_transformer.cpython-37.pyc +0 -0
- package/knowledge_base/__pycache__/test_generator.cpython-314.pyc +0 -0
- package/knowledge_base/__pycache__/test_generator.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__init__.py +6 -0
- package/knowledge_base/agents/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/agents/__pycache__/fusion_agent.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/fusion_agent.cpython-38.pyc +0 -0
- package/knowledge_base/agents/__pycache__/graph.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/graph.cpython-38.pyc +0 -0
- package/knowledge_base/agents/__pycache__/healing_agent.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/intent_agent.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/intent_agent.cpython-38.pyc +0 -0
- package/knowledge_base/agents/__pycache__/kg_agent.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/kg_agent.cpython-38.pyc +0 -0
- package/knowledge_base/agents/__pycache__/memory_agent.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/memory_agent.cpython-38.pyc +0 -0
- package/knowledge_base/agents/__pycache__/rca_agent.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/reflection_agent.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/reflection_agent.cpython-38.pyc +0 -0
- package/knowledge_base/agents/__pycache__/regression_agent.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/retrieval_agent.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/retrieval_agent.cpython-38.pyc +0 -0
- package/knowledge_base/agents/__pycache__/script_agent.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/script_agent.cpython-38.pyc +0 -0
- package/knowledge_base/agents/__pycache__/state.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/state.cpython-38.pyc +0 -0
- package/knowledge_base/agents/__pycache__/temporal_agent.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/temporal_agent.cpython-38.pyc +0 -0
- package/knowledge_base/agents/__pycache__/validation_agent.cpython-37.pyc +0 -0
- package/knowledge_base/agents/__pycache__/validation_agent.cpython-38.pyc +0 -0
- package/knowledge_base/agents/automation_design_agent.py +123 -0
- package/knowledge_base/agents/code_intelligence_agent.py +147 -0
- package/knowledge_base/agents/confidence_agent.py +94 -0
- package/knowledge_base/agents/execution_validation_agent.py +129 -0
- package/knowledge_base/agents/feedback_agent.py +56 -0
- package/knowledge_base/agents/fusion_agent.py +113 -0
- package/knowledge_base/agents/graph.py +152 -0
- package/knowledge_base/agents/healing_agent.py +62 -0
- package/knowledge_base/agents/intent_agent.py +76 -0
- package/knowledge_base/agents/kg_agent.py +91 -0
- package/knowledge_base/agents/memory_agent.py +62 -0
- package/knowledge_base/agents/rca_agent.py +53 -0
- package/knowledge_base/agents/reflection_agent.py +62 -0
- package/knowledge_base/agents/regression_agent.py +81 -0
- package/knowledge_base/agents/requirement_agent.py +71 -0
- package/knowledge_base/agents/retrieval_agent.py +66 -0
- package/knowledge_base/agents/scenario_agent.py +81 -0
- package/knowledge_base/agents/script_agent.py +121 -0
- package/knowledge_base/agents/script_generator_agent.py +77 -0
- package/knowledge_base/agents/state.py +53 -0
- package/knowledge_base/agents/temporal_agent.py +48 -0
- package/knowledge_base/agents/testcase_agent.py +111 -0
- package/knowledge_base/agents/validation_agent.py +49 -0
- package/knowledge_base/agents/workflow_engine.py +87 -0
- package/knowledge_base/alembic/README.md +104 -0
- package/knowledge_base/alembic/__pycache__/env.cpython-37.pyc +0 -0
- package/knowledge_base/alembic/__pycache__/env.cpython-38.pyc +0 -0
- package/knowledge_base/alembic/env.py +76 -0
- package/knowledge_base/alembic/script.py.mako +5 -0
- package/knowledge_base/alembic/versions/.gitkeep +26 -0
- package/knowledge_base/alembic/versions/001_phase0_feedback.py +122 -0
- package/knowledge_base/alembic/versions/002_feedback_promotions.py +60 -0
- package/knowledge_base/alembic/versions/003_enterprise_hardening.py +74 -0
- package/knowledge_base/alembic/versions/__pycache__/001_phase0_feedback.cpython-37.pyc +0 -0
- package/knowledge_base/alembic/versions/__pycache__/001_phase0_feedback.cpython-38.pyc +0 -0
- package/knowledge_base/alembic/versions/__pycache__/002_feedback_promotions.cpython-37.pyc +0 -0
- package/knowledge_base/alembic/versions/__pycache__/002_feedback_promotions.cpython-38.pyc +0 -0
- package/knowledge_base/api/__init__.py +1 -0
- package/knowledge_base/api/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/api/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/api/__pycache__/middleware.cpython-38.pyc +0 -0
- package/knowledge_base/api/__pycache__/server.cpython-37.pyc +0 -0
- package/knowledge_base/api/__pycache__/server.cpython-38.pyc +0 -0
- package/knowledge_base/api/middleware.py +78 -0
- package/knowledge_base/api/server.py +778 -0
- package/knowledge_base/chunking/__init__.py +0 -0
- package/knowledge_base/chunking/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/chunking/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/chunking/__pycache__/chunker.cpython-37.pyc +0 -0
- package/knowledge_base/chunking/__pycache__/chunker.cpython-38.pyc +0 -0
- package/knowledge_base/chunking/__pycache__/validators.cpython-37.pyc +0 -0
- package/knowledge_base/chunking/__pycache__/validators.cpython-38.pyc +0 -0
- package/knowledge_base/chunking/chunker.py +402 -0
- package/knowledge_base/chunking/validators.py +144 -0
- package/knowledge_base/cli/__init__.py +1 -0
- package/knowledge_base/cli/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/cli/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/cli/__pycache__/feedback_cli.cpython-37.pyc +0 -0
- package/knowledge_base/cli/__pycache__/feedback_cli.cpython-38.pyc +0 -0
- package/knowledge_base/cli/__pycache__/generate.cpython-37.pyc +0 -0
- package/knowledge_base/cli/__pycache__/generate.cpython-38.pyc +0 -0
- package/knowledge_base/cli/__pycache__/generate_qcapps134.cpython-37.pyc +0 -0
- package/knowledge_base/cli/feedback_cli.py +121 -0
- package/knowledge_base/cli/generate.py +308 -0
- package/knowledge_base/cli/generate_qcapps134.py +391 -0
- package/knowledge_base/config/.env +100 -0
- package/knowledge_base/config/.env.example +75 -0
- package/knowledge_base/config/__init__.py +5 -0
- package/knowledge_base/config/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/config/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/config/__pycache__/settings.cpython-37.pyc +0 -0
- package/knowledge_base/config/__pycache__/settings.cpython-38.pyc +0 -0
- package/knowledge_base/config/settings.py +280 -0
- package/knowledge_base/connectors/__init__.py +5 -0
- package/knowledge_base/connectors/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/base.cpython-37.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/base.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/bitbucket_connector.cpython-37.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/bitbucket_connector.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/community_connector.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/confluence_connector.cpython-37.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/confluence_connector.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/jira_connector.cpython-37.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/jira_connector.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/kb_igel_connector.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/playwright_fetcher.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/query_bridge.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/search_backends.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/serviceNow_connector.cpython-37.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/serviceNow_connector.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/sync_scheduler.cpython-37.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/sync_scheduler.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/teamcity_connector.cpython-37.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/teamcity_connector.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/testRail_connector.cpython-37.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/testRail_connector.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/__pycache__/web_fetcher.cpython-38.pyc +0 -0
- package/knowledge_base/connectors/base.py +44 -0
- package/knowledge_base/connectors/bitbucket_connector.py +25 -0
- package/knowledge_base/connectors/community_connector.py +112 -0
- package/knowledge_base/connectors/confluence_connector.py +165 -0
- package/knowledge_base/connectors/jira_connector.py +174 -0
- package/knowledge_base/connectors/kb_igel_connector.py +145 -0
- package/knowledge_base/connectors/playwright_fetcher.py +50 -0
- package/knowledge_base/connectors/query_bridge.py +170 -0
- package/knowledge_base/connectors/search_backends.py +368 -0
- package/knowledge_base/connectors/serviceNow_connector.py +25 -0
- package/knowledge_base/connectors/sync_scheduler.py +111 -0
- package/knowledge_base/connectors/teamcity_connector.py +25 -0
- package/knowledge_base/connectors/testRail_connector.py +25 -0
- package/knowledge_base/connectors/web_fetcher.py +280 -0
- package/knowledge_base/core/__init__.py +8 -0
- package/knowledge_base/core/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/core/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/core/__pycache__/cache.cpython-38.pyc +0 -0
- package/knowledge_base/core/__pycache__/circuit_breaker.cpython-38.pyc +0 -0
- package/knowledge_base/core/__pycache__/confidence.cpython-37.pyc +0 -0
- package/knowledge_base/core/__pycache__/confidence.cpython-38.pyc +0 -0
- package/knowledge_base/core/__pycache__/logging_setup.cpython-38.pyc +0 -0
- package/knowledge_base/core/__pycache__/metrics.cpython-38.pyc +0 -0
- package/knowledge_base/core/__pycache__/precedence.cpython-37.pyc +0 -0
- package/knowledge_base/core/__pycache__/precedence.cpython-38.pyc +0 -0
- package/knowledge_base/core/__pycache__/request_context.cpython-38.pyc +0 -0
- package/knowledge_base/core/cache.py +98 -0
- package/knowledge_base/core/circuit_breaker.py +33 -0
- package/knowledge_base/core/confidence.py +254 -0
- package/knowledge_base/core/logging_setup.py +39 -0
- package/knowledge_base/core/metrics.py +63 -0
- package/knowledge_base/core/precedence.py +354 -0
- package/knowledge_base/core/request_context.py +54 -0
- package/knowledge_base/db/__init__.py +0 -0
- package/knowledge_base/db/__pycache__/__init__.cpython-314.pyc +0 -0
- package/knowledge_base/db/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/db/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/db/__pycache__/client.cpython-314.pyc +0 -0
- package/knowledge_base/db/__pycache__/client.cpython-37.pyc +0 -0
- package/knowledge_base/db/__pycache__/client.cpython-38.pyc +0 -0
- package/knowledge_base/db/__pycache__/connector_sync.cpython-38.pyc +0 -0
- package/knowledge_base/db/__pycache__/feedback_schema.cpython-37.pyc +0 -0
- package/knowledge_base/db/__pycache__/feedback_schema.cpython-38.pyc +0 -0
- package/knowledge_base/db/__pycache__/kg_introspect.cpython-314.pyc +0 -0
- package/knowledge_base/db/__pycache__/kg_introspect.cpython-37.pyc +0 -0
- package/knowledge_base/db/__pycache__/kg_introspect.cpython-38.pyc +0 -0
- package/knowledge_base/db/__pycache__/schema.cpython-37.pyc +0 -0
- package/knowledge_base/db/__pycache__/schema.cpython-38.pyc +0 -0
- package/knowledge_base/db/client.py +100 -0
- package/knowledge_base/db/connector_sync.py +43 -0
- package/knowledge_base/db/feedback_schema.py +548 -0
- package/knowledge_base/db/igel_schema.py +150 -0
- package/knowledge_base/db/kg_introspect.py +116 -0
- package/knowledge_base/db/migrations.py +22 -0
- package/knowledge_base/db/schema.py +649 -0
- package/knowledge_base/deploy/Dockerfile +23 -0
- package/knowledge_base/deploy/feedback_automation.sh +18 -0
- package/knowledge_base/deploy/firecrawl-docker.sh +50 -0
- package/knowledge_base/deploy/igel-testgen.service +26 -0
- package/knowledge_base/deploy/install_reranker.sh +92 -0
- package/knowledge_base/deploy/requirements.txt +68 -0
- package/knowledge_base/deploy/run_server.sh +35 -0
- package/knowledge_base/docs/TEST_GENERATION_GUIDE.md +174 -0
- package/knowledge_base/embeddings/__init__.py +0 -0
- package/knowledge_base/embeddings/__pycache__/__init__.cpython-314.pyc +0 -0
- package/knowledge_base/embeddings/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/embeddings/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/embeddings/__pycache__/azure_embedder.cpython-314.pyc +0 -0
- package/knowledge_base/embeddings/__pycache__/azure_embedder.cpython-37.pyc +0 -0
- package/knowledge_base/embeddings/__pycache__/azure_embedder.cpython-38.pyc +0 -0
- package/knowledge_base/embeddings/azure_embedder.py +99 -0
- package/knowledge_base/eval/__init__.py +33 -0
- package/knowledge_base/eval/__main__.py +172 -0
- package/knowledge_base/eval/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/eval/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/eval/__pycache__/__main__.cpython-37.pyc +0 -0
- package/knowledge_base/eval/__pycache__/baseline.cpython-37.pyc +0 -0
- package/knowledge_base/eval/__pycache__/baseline.cpython-38.pyc +0 -0
- package/knowledge_base/eval/__pycache__/harness.cpython-37.pyc +0 -0
- package/knowledge_base/eval/__pycache__/harness.cpython-38.pyc +0 -0
- package/knowledge_base/eval/__pycache__/scorers.cpython-37.pyc +0 -0
- package/knowledge_base/eval/__pycache__/scorers.cpython-38.pyc +0 -0
- package/knowledge_base/eval/baseline.py +213 -0
- package/knowledge_base/eval/harness.py +400 -0
- package/knowledge_base/eval/queries_50.json +302 -0
- package/knowledge_base/eval/scorers.py +494 -0
- package/knowledge_base/exemplars/README.md +7 -0
- package/knowledge_base/exemplars/__pycache__/pytest_reference_snippet.cpython-37.pyc +0 -0
- package/knowledge_base/exemplars/gold_manual_concise_snippet.md +8 -0
- package/knowledge_base/exemplars/gold_manual_detailed_snippet.md +7 -0
- package/knowledge_base/exemplars/pytest_reference_snippet.py +37 -0
- package/knowledge_base/feedback/__init__.py +10 -0
- package/knowledge_base/feedback/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/feedback/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/feedback/__pycache__/analytics.cpython-37.pyc +0 -0
- package/knowledge_base/feedback/__pycache__/analytics.cpython-38.pyc +0 -0
- package/knowledge_base/feedback/__pycache__/applier.cpython-37.pyc +0 -0
- package/knowledge_base/feedback/__pycache__/applier.cpython-38.pyc +0 -0
- package/knowledge_base/feedback/__pycache__/curator.cpython-37.pyc +0 -0
- package/knowledge_base/feedback/__pycache__/curator.cpython-38.pyc +0 -0
- package/knowledge_base/feedback/__pycache__/handler.cpython-37.pyc +0 -0
- package/knowledge_base/feedback/__pycache__/handler.cpython-38.pyc +0 -0
- package/knowledge_base/feedback/analytics.py +72 -0
- package/knowledge_base/feedback/applier.py +93 -0
- package/knowledge_base/feedback/curator.py +49 -0
- package/knowledge_base/feedback/handler.py +151 -0
- package/knowledge_base/generation/__init__.py +5 -0
- package/knowledge_base/generation/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/generation/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/generation/__pycache__/input_classifier.cpython-37.pyc +0 -0
- package/knowledge_base/generation/__pycache__/input_classifier.cpython-38.pyc +0 -0
- package/knowledge_base/generation/__pycache__/manual_tc_parser.cpython-37.pyc +0 -0
- package/knowledge_base/generation/__pycache__/manual_tc_parser.cpython-38.pyc +0 -0
- package/knowledge_base/generation/__pycache__/prompts.cpython-37.pyc +0 -0
- package/knowledge_base/generation/__pycache__/prompts.cpython-38.pyc +0 -0
- package/knowledge_base/generation/__pycache__/tc_transformer.cpython-37.pyc +0 -0
- package/knowledge_base/generation/__pycache__/test_generator.cpython-37.pyc +0 -0
- package/knowledge_base/generation/__pycache__/test_generator.cpython-38.pyc +0 -0
- package/knowledge_base/generation/input_classifier.py +148 -0
- package/knowledge_base/generation/manual_tc_parser.py +180 -0
- package/knowledge_base/generation/prompts.py +755 -0
- package/knowledge_base/generation/tc_transformer.py +368 -0
- package/knowledge_base/generation/test_generator.py +553 -0
- package/knowledge_base/graph_db/__init__.py +5 -0
- package/knowledge_base/graph_db/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/graph_db/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/graph_db/__pycache__/graphiti_client.cpython-37.pyc +0 -0
- package/knowledge_base/graph_db/__pycache__/kg_traversal.cpython-37.pyc +0 -0
- package/knowledge_base/graph_db/__pycache__/migrate_kg.cpython-37.pyc +0 -0
- package/knowledge_base/graph_db/__pycache__/neo4j_client.cpython-37.pyc +0 -0
- package/knowledge_base/graph_db/__pycache__/neo4j_client.cpython-38.pyc +0 -0
- package/knowledge_base/graph_db/__pycache__/schema.cpython-37.pyc +0 -0
- package/knowledge_base/graph_db/__pycache__/temporal_ingest.cpython-37.pyc +0 -0
- package/knowledge_base/graph_db/graphiti_client.py +127 -0
- package/knowledge_base/graph_db/kg_traversal.py +192 -0
- package/knowledge_base/graph_db/migrate_kg.py +93 -0
- package/knowledge_base/graph_db/neo4j_client.py +116 -0
- package/knowledge_base/graph_db/schema.py +22 -0
- package/knowledge_base/graph_db/temporal_ingest.py +59 -0
- package/knowledge_base/ingestion/__init__.py +1 -0
- package/knowledge_base/ingestion/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/ingestion/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/ingestion/__pycache__/ingest.cpython-37.pyc +0 -0
- package/knowledge_base/ingestion/__pycache__/ingest.cpython-38.pyc +0 -0
- package/knowledge_base/ingestion/__pycache__/ingest_images.cpython-37.pyc +0 -0
- package/knowledge_base/ingestion/__pycache__/ingest_images.cpython-38.pyc +0 -0
- package/knowledge_base/ingestion/__pycache__/kg_extractor.cpython-37.pyc +0 -0
- package/knowledge_base/ingestion/__pycache__/kg_extractor.cpython-38.pyc +0 -0
- package/knowledge_base/ingestion/__pycache__/web_ingest.cpython-38.pyc +0 -0
- package/knowledge_base/ingestion/ingest.py +346 -0
- package/knowledge_base/ingestion/ingest_images.py +330 -0
- package/knowledge_base/ingestion/kg_extractor.py +358 -0
- package/knowledge_base/ingestion/web_ingest.py +83 -0
- package/knowledge_base/intelligence/__init__.py +1 -0
- package/knowledge_base/intelligence/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/intelligence/__pycache__/execution_ingest.cpython-37.pyc +0 -0
- package/knowledge_base/intelligence/__pycache__/predictive_engine.cpython-37.pyc +0 -0
- package/knowledge_base/intelligence/__pycache__/risk_scorer.cpython-37.pyc +0 -0
- package/knowledge_base/intelligence/execution_ingest.py +34 -0
- package/knowledge_base/intelligence/predictive_engine.py +7 -0
- package/knowledge_base/intelligence/risk_scorer.py +8 -0
- package/knowledge_base/memory/__init__.py +6 -0
- package/knowledge_base/memory/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/memory/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/memory/__pycache__/cognee_client.cpython-37.pyc +0 -0
- package/knowledge_base/memory/__pycache__/cognee_client.cpython-38.pyc +0 -0
- package/knowledge_base/memory/__pycache__/ingest_memory.cpython-37.pyc +0 -0
- package/knowledge_base/memory/__pycache__/memory_types.cpython-37.pyc +0 -0
- package/knowledge_base/memory/__pycache__/memory_types.cpython-38.pyc +0 -0
- package/knowledge_base/memory/cognee_client.py +164 -0
- package/knowledge_base/memory/ingest_memory.py +60 -0
- package/knowledge_base/memory/memory_types.py +23 -0
- package/knowledge_base/models/test_requirement.py +84 -0
- package/knowledge_base/parsers/__init__.py +0 -0
- package/knowledge_base/parsers/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/parsers/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/parsers/__pycache__/csv_parser.cpython-37.pyc +0 -0
- package/knowledge_base/parsers/__pycache__/csv_parser.cpython-38.pyc +0 -0
- package/knowledge_base/parsers/__pycache__/doc_parser.cpython-37.pyc +0 -0
- package/knowledge_base/parsers/__pycache__/doc_parser.cpython-38.pyc +0 -0
- package/knowledge_base/parsers/__pycache__/html_parser.cpython-38.pyc +0 -0
- package/knowledge_base/parsers/__pycache__/image_extractor.cpython-37.pyc +0 -0
- package/knowledge_base/parsers/__pycache__/image_extractor.cpython-38.pyc +0 -0
- package/knowledge_base/parsers/__pycache__/markdown_parser.cpython-37.pyc +0 -0
- package/knowledge_base/parsers/__pycache__/markdown_parser.cpython-38.pyc +0 -0
- package/knowledge_base/parsers/__pycache__/pdf_parser.cpython-37.pyc +0 -0
- package/knowledge_base/parsers/__pycache__/pdf_parser.cpython-38.pyc +0 -0
- package/knowledge_base/parsers/csv_parser.py +126 -0
- package/knowledge_base/parsers/doc_parser.py +176 -0
- package/knowledge_base/parsers/html_parser.py +151 -0
- package/knowledge_base/parsers/image_extractor.py +370 -0
- package/knowledge_base/parsers/markdown_parser.py +116 -0
- package/knowledge_base/parsers/pdf_parser.py +178 -0
- package/knowledge_base/retrieval/__init__.py +1 -0
- package/knowledge_base/retrieval/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/context_fusion.cpython-37.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/context_fusion.cpython-38.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/knowledge_graph.cpython-37.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/knowledge_graph.cpython-38.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/reranker.cpython-37.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/reranker.cpython-38.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/retrieval_debug.cpython-37.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/retrieval_debug.cpython-38.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/retriever.cpython-37.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/retriever.cpython-38.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/unified_query.cpython-38.pyc +0 -0
- package/knowledge_base/retrieval/__pycache__/web_retriever.cpython-38.pyc +0 -0
- package/knowledge_base/retrieval/context_fusion.py +264 -0
- package/knowledge_base/retrieval/knowledge_graph.py +267 -0
- package/knowledge_base/retrieval/reranker.py +308 -0
- package/knowledge_base/retrieval/retrieval_debug.py +69 -0
- package/knowledge_base/retrieval/retriever.py +885 -0
- package/knowledge_base/retrieval/unified_query.py +189 -0
- package/knowledge_base/retrieval/web_retriever.py +337 -0
- package/knowledge_base/validators/__init__.py +1 -0
- package/knowledge_base/validators/__pycache__/__init__.cpython-314.pyc +0 -0
- package/knowledge_base/validators/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/validators/__pycache__/step_fidelity.cpython-314.pyc +0 -0
- package/knowledge_base/validators/__pycache__/step_fidelity.cpython-37.pyc +0 -0
- package/knowledge_base/validators/step_fidelity.py +88 -0
- package/knowledge_base/vision/__init__.py +0 -0
- package/knowledge_base/vision/__pycache__/__init__.cpython-37.pyc +0 -0
- package/knowledge_base/vision/__pycache__/__init__.cpython-38.pyc +0 -0
- package/knowledge_base/vision/__pycache__/image_describer.cpython-37.pyc +0 -0
- package/knowledge_base/vision/__pycache__/image_describer.cpython-38.pyc +0 -0
- package/knowledge_base/vision/image_describer.py +202 -0
- package/package.json +31 -0
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Production-ready Hierarchical Chunker for IGEL Knowledge Base.
|
|
3
|
+
|
|
4
|
+
Strategy:
|
|
5
|
+
1. Each document section → one or more Parent Chunks (800–1200 tokens)
|
|
6
|
+
2. Each Parent Chunk → 2–5 Child Chunks (150–300 tokens) for vector indexing
|
|
7
|
+
3. Step sequences are NEVER split across chunk boundaries
|
|
8
|
+
4. Tables are kept whole (header always included)
|
|
9
|
+
5. 80-token overlap between sibling children to prevent context boundary loss
|
|
10
|
+
6. Each chunk validated before being returned
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
import re
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from typing import Generator
|
|
16
|
+
|
|
17
|
+
import tiktoken
|
|
18
|
+
|
|
19
|
+
from knowledge_base.parsers.markdown_parser import Section
|
|
20
|
+
from knowledge_base.chunking.validators import (
|
|
21
|
+
ValidationResult,
|
|
22
|
+
validate_chunk,
|
|
23
|
+
validate_coverage,
|
|
24
|
+
classify_chunk_type,
|
|
25
|
+
contains_steps,
|
|
26
|
+
detect_product,
|
|
27
|
+
CHUNK_TOKEN_LIMITS,
|
|
28
|
+
)
|
|
29
|
+
from knowledge_base.config import cfg
|
|
30
|
+
|
|
31
|
+
_ENCODER = tiktoken.get_encoding("cl100k_base")
|
|
32
|
+
|
|
33
|
+
PARENT_MAX_TOKENS = getattr(cfg, "CHUNK_PARENT_MAX_TOKENS", 1024)
|
|
34
|
+
PARENT_MIN_TOKENS = getattr(cfg, "CHUNK_PARENT_MIN_TOKENS", 80)
|
|
35
|
+
CHILD_MAX_TOKENS = getattr(cfg, "CHUNK_CHILD_MAX_TOKENS", 256)
|
|
36
|
+
CHILD_MIN_TOKENS = getattr(cfg, "CHUNK_CHILD_MIN_TOKENS", 50)
|
|
37
|
+
OVERLAP_TOKENS = getattr(cfg, "CHUNK_OVERLAP_TOKENS", 64)
|
|
38
|
+
CONTEXT_HEADERS = getattr(cfg, "CHUNK_CONTEXT_HEADERS", True)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class ParentChunk:
|
|
43
|
+
content: str
|
|
44
|
+
section_title: str
|
|
45
|
+
chunk_type: str
|
|
46
|
+
page_number: int
|
|
47
|
+
token_count: int
|
|
48
|
+
metadata: dict = field(default_factory=dict)
|
|
49
|
+
children: list["ChildChunk"] = field(default_factory=list)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class ChildChunk:
|
|
54
|
+
content: str
|
|
55
|
+
chunk_type: str
|
|
56
|
+
token_count: int
|
|
57
|
+
contains_steps: bool
|
|
58
|
+
has_table: bool
|
|
59
|
+
metadata: dict = field(default_factory=dict)
|
|
60
|
+
validation: ValidationResult = field(default_factory=lambda: ValidationResult(passed=True))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def count_tokens(text: str) -> int:
|
|
64
|
+
return len(_ENCODER.encode(text))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def decode_tokens(tokens: list[int]) -> str:
|
|
68
|
+
return _ENCODER.decode(tokens)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def chunk_sections(
|
|
72
|
+
sections: list[Section],
|
|
73
|
+
file_name: str,
|
|
74
|
+
product: str | None = None,
|
|
75
|
+
) -> tuple[list[ParentChunk], list[str]]:
|
|
76
|
+
"""
|
|
77
|
+
Entry point: convert document sections into parent+child chunk hierarchy.
|
|
78
|
+
Returns (parent_chunks, validation_warnings).
|
|
79
|
+
"""
|
|
80
|
+
all_parents: list[ParentChunk] = []
|
|
81
|
+
all_warnings: list[str] = []
|
|
82
|
+
|
|
83
|
+
for section in sections:
|
|
84
|
+
if not section.content.strip():
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
chunk_type = classify_chunk_type(section.content, section.title)
|
|
88
|
+
detected_product = product or detect_product(section.content, file_name)
|
|
89
|
+
|
|
90
|
+
base_metadata = {
|
|
91
|
+
"source": file_name,
|
|
92
|
+
"product": detected_product,
|
|
93
|
+
"section": section.title,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
parents = _build_parent_chunks(section, chunk_type, base_metadata)
|
|
97
|
+
|
|
98
|
+
for parent in parents:
|
|
99
|
+
parent.children = _build_child_chunks(parent, base_metadata)
|
|
100
|
+
all_parents.append(parent)
|
|
101
|
+
|
|
102
|
+
# Collect validation warnings from children
|
|
103
|
+
for child in parent.children:
|
|
104
|
+
if not child.validation.passed:
|
|
105
|
+
all_warnings.append(
|
|
106
|
+
f"[{file_name}] Section '{section.title[:40]}': {child.validation.errors}"
|
|
107
|
+
)
|
|
108
|
+
for w in child.validation.warnings:
|
|
109
|
+
all_warnings.append(f"[{file_name}] Section '{section.title[:40]}': WARNING {w}")
|
|
110
|
+
|
|
111
|
+
# Coverage check across all parent chunks
|
|
112
|
+
original_tokens = sum(count_tokens(s.content) for s in sections if s.content.strip())
|
|
113
|
+
parent_tokens = [count_tokens(p.content) for p in all_parents]
|
|
114
|
+
try:
|
|
115
|
+
validate_coverage(original_tokens, parent_tokens)
|
|
116
|
+
except ValueError as e:
|
|
117
|
+
all_warnings.append(f"[{file_name}] COVERAGE WARNING: {e}")
|
|
118
|
+
|
|
119
|
+
return all_parents, all_warnings
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# ── Parent Chunk Building ─────────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
def _build_parent_chunks(section: Section, chunk_type: str, base_metadata: dict) -> list[ParentChunk]:
|
|
125
|
+
total_tokens = count_tokens(section.content)
|
|
126
|
+
|
|
127
|
+
# Small section → single parent
|
|
128
|
+
if total_tokens <= PARENT_MAX_TOKENS:
|
|
129
|
+
return [_make_parent(section.content, section.title, chunk_type, section.page_hint, base_metadata)]
|
|
130
|
+
|
|
131
|
+
# Large section → split carefully
|
|
132
|
+
return _split_large_section(section, chunk_type, base_metadata)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _split_large_section(section: Section, chunk_type: str, base_metadata: dict) -> list[ParentChunk]:
|
|
136
|
+
parents: list[ParentChunk] = []
|
|
137
|
+
text = section.content
|
|
138
|
+
|
|
139
|
+
# Tables → keep whole regardless of size
|
|
140
|
+
if chunk_type == "table" or ("|" in text and text.count("|") > 6):
|
|
141
|
+
return [_make_parent(text, section.title, "table", section.page_hint, base_metadata)]
|
|
142
|
+
|
|
143
|
+
# Find step blocks — extract them as atomic units
|
|
144
|
+
step_block, before, after = _extract_step_block(text)
|
|
145
|
+
|
|
146
|
+
if step_block:
|
|
147
|
+
# Before-steps content
|
|
148
|
+
if before.strip():
|
|
149
|
+
for chunk in _split_by_paragraph(before, PARENT_MAX_TOKENS):
|
|
150
|
+
parents.append(_make_parent(chunk, section.title, chunk_type, section.page_hint, base_metadata))
|
|
151
|
+
|
|
152
|
+
# Step block — if too large, split only between step groups
|
|
153
|
+
step_tokens = count_tokens(step_block)
|
|
154
|
+
if step_tokens <= PARENT_MAX_TOKENS:
|
|
155
|
+
parents.append(_make_parent(step_block, section.title, "procedure", section.page_hint, base_metadata))
|
|
156
|
+
else:
|
|
157
|
+
for part in _split_between_step_groups(step_block, PARENT_MAX_TOKENS):
|
|
158
|
+
parents.append(_make_parent(part, section.title, "procedure", section.page_hint, base_metadata))
|
|
159
|
+
|
|
160
|
+
# After-steps content
|
|
161
|
+
if after.strip():
|
|
162
|
+
for chunk in _split_by_paragraph(after, PARENT_MAX_TOKENS):
|
|
163
|
+
parents.append(_make_parent(chunk, section.title, chunk_type, section.page_hint, base_metadata))
|
|
164
|
+
else:
|
|
165
|
+
# No steps → split at paragraph boundaries
|
|
166
|
+
for chunk in _split_by_paragraph(text, PARENT_MAX_TOKENS):
|
|
167
|
+
parents.append(_make_parent(chunk, section.title, chunk_type, section.page_hint, base_metadata))
|
|
168
|
+
|
|
169
|
+
return parents
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _make_parent(content: str, title: str, chunk_type: str, page: int, metadata: dict) -> ParentChunk:
|
|
173
|
+
return ParentChunk(
|
|
174
|
+
content=content.strip(),
|
|
175
|
+
section_title=title,
|
|
176
|
+
chunk_type=chunk_type,
|
|
177
|
+
page_number=page,
|
|
178
|
+
token_count=count_tokens(content),
|
|
179
|
+
metadata=dict(metadata),
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# ── Child Chunk Building ──────────────────────────────────────────────────────
|
|
184
|
+
|
|
185
|
+
def _build_child_chunks(parent: ParentChunk, base_metadata: dict) -> list[ChildChunk]:
|
|
186
|
+
total_tokens = count_tokens(parent.content)
|
|
187
|
+
|
|
188
|
+
# Small parent → single child = parent itself
|
|
189
|
+
if total_tokens <= CHILD_MAX_TOKENS:
|
|
190
|
+
child = _make_child(parent.content, parent.chunk_type, base_metadata, parent.section_title)
|
|
191
|
+
return [child]
|
|
192
|
+
|
|
193
|
+
# Split parent into children
|
|
194
|
+
raw_children = _split_by_sentence(parent.content, CHILD_MAX_TOKENS)
|
|
195
|
+
|
|
196
|
+
# Add overlap between siblings
|
|
197
|
+
children_with_overlap = _apply_overlap(raw_children)
|
|
198
|
+
|
|
199
|
+
return [
|
|
200
|
+
_make_child(c, parent.chunk_type, base_metadata, parent.section_title)
|
|
201
|
+
for c in children_with_overlap
|
|
202
|
+
]
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _build_context_header(metadata: dict, section_title: str = "") -> str:
|
|
206
|
+
"""Prepend product/section/source context for better embedding retrieval."""
|
|
207
|
+
if not CONTEXT_HEADERS:
|
|
208
|
+
return ""
|
|
209
|
+
product = metadata.get("product") or "IGEL"
|
|
210
|
+
section = section_title or metadata.get("section") or metadata.get("section_title") or ""
|
|
211
|
+
source = metadata.get("source") or metadata.get("file_path") or metadata.get("canonical_url") or ""
|
|
212
|
+
parts = [f"Product: {product}"]
|
|
213
|
+
if section:
|
|
214
|
+
parts.append(f"Section: {section}")
|
|
215
|
+
if source:
|
|
216
|
+
parts.append(f"Source: {source}")
|
|
217
|
+
return "\n".join(parts) + "\n\n"
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _make_child(content: str, chunk_type: str, metadata: dict, section_title: str = "") -> ChildChunk:
|
|
221
|
+
header = _build_context_header(metadata, section_title)
|
|
222
|
+
embed_body = f"{header}{content.strip()}" if header else content.strip()
|
|
223
|
+
stripped = content.strip()
|
|
224
|
+
token_count = count_tokens(embed_body)
|
|
225
|
+
has_steps = contains_steps(stripped)
|
|
226
|
+
has_table = "|" in stripped and stripped.count("|") > 4
|
|
227
|
+
actual_type = "procedure" if has_steps else ("table" if has_table else chunk_type)
|
|
228
|
+
|
|
229
|
+
validation = validate_chunk(stripped, token_count, actual_type, metadata)
|
|
230
|
+
|
|
231
|
+
return ChildChunk(
|
|
232
|
+
content=embed_body,
|
|
233
|
+
chunk_type=actual_type,
|
|
234
|
+
token_count=token_count,
|
|
235
|
+
contains_steps=has_steps,
|
|
236
|
+
has_table=has_table,
|
|
237
|
+
metadata=dict(metadata),
|
|
238
|
+
validation=validation,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# ── Splitting Helpers ─────────────────────────────────────────────────────────
|
|
243
|
+
|
|
244
|
+
def _split_by_paragraph(text: str, max_tokens: int) -> list[str]:
|
|
245
|
+
"""Split text at double-newline boundaries without exceeding max_tokens."""
|
|
246
|
+
paragraphs = re.split(r"\n{2,}", text)
|
|
247
|
+
chunks: list[str] = []
|
|
248
|
+
current = ""
|
|
249
|
+
|
|
250
|
+
for para in paragraphs:
|
|
251
|
+
candidate = f"{current}\n\n{para}".strip() if current else para
|
|
252
|
+
if count_tokens(candidate) <= max_tokens:
|
|
253
|
+
current = candidate
|
|
254
|
+
else:
|
|
255
|
+
if current:
|
|
256
|
+
chunks.append(current.strip())
|
|
257
|
+
# Para itself too large — split by single newline
|
|
258
|
+
if count_tokens(para) > max_tokens:
|
|
259
|
+
chunks.extend(_split_by_line(para, max_tokens))
|
|
260
|
+
current = ""
|
|
261
|
+
else:
|
|
262
|
+
current = para
|
|
263
|
+
|
|
264
|
+
if current.strip():
|
|
265
|
+
chunks.append(current.strip())
|
|
266
|
+
|
|
267
|
+
return chunks or [text[:3000]] # hard fallback
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _split_by_line(text: str, max_tokens: int) -> list[str]:
|
|
271
|
+
lines = text.split("\n")
|
|
272
|
+
chunks: list[str] = []
|
|
273
|
+
current = ""
|
|
274
|
+
|
|
275
|
+
for line in lines:
|
|
276
|
+
candidate = f"{current}\n{line}".strip() if current else line
|
|
277
|
+
if count_tokens(candidate) <= max_tokens:
|
|
278
|
+
current = candidate
|
|
279
|
+
else:
|
|
280
|
+
if current:
|
|
281
|
+
chunks.append(current.strip())
|
|
282
|
+
current = line
|
|
283
|
+
|
|
284
|
+
if current.strip():
|
|
285
|
+
chunks.append(current.strip())
|
|
286
|
+
|
|
287
|
+
return chunks
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _split_by_sentence(text: str, max_tokens: int) -> list[str]:
|
|
291
|
+
"""Split into child-sized units at sentence boundaries."""
|
|
292
|
+
# Split on sentence-ending punctuation followed by space + capital
|
|
293
|
+
sentences = re.split(r"(?<=[.!?;])\s+(?=[A-Z\d])", text)
|
|
294
|
+
chunks: list[str] = []
|
|
295
|
+
current = ""
|
|
296
|
+
|
|
297
|
+
for sent in sentences:
|
|
298
|
+
candidate = f"{current} {sent}".strip() if current else sent
|
|
299
|
+
if count_tokens(candidate) <= max_tokens:
|
|
300
|
+
current = candidate
|
|
301
|
+
else:
|
|
302
|
+
if current:
|
|
303
|
+
chunks.append(current.strip())
|
|
304
|
+
# Single sentence too long → split by token hard limit
|
|
305
|
+
if count_tokens(sent) > max_tokens:
|
|
306
|
+
chunks.extend(_split_by_token_limit(sent, max_tokens))
|
|
307
|
+
current = ""
|
|
308
|
+
else:
|
|
309
|
+
current = sent
|
|
310
|
+
|
|
311
|
+
if current.strip():
|
|
312
|
+
chunks.append(current.strip())
|
|
313
|
+
|
|
314
|
+
return chunks or [text]
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _split_by_token_limit(text: str, max_tokens: int) -> list[str]:
|
|
318
|
+
"""Hard split by token count — last resort, used only for very long single sentences."""
|
|
319
|
+
tokens = _ENCODER.encode(text)
|
|
320
|
+
chunks = []
|
|
321
|
+
for i in range(0, len(tokens), max_tokens):
|
|
322
|
+
chunk_tokens = tokens[i:i + max_tokens]
|
|
323
|
+
chunks.append(_ENCODER.decode(chunk_tokens))
|
|
324
|
+
return chunks
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _apply_overlap(chunks: list[str]) -> list[str]:
|
|
328
|
+
"""Add trailing tokens from previous chunk to start of next chunk."""
|
|
329
|
+
if len(chunks) <= 1:
|
|
330
|
+
return chunks
|
|
331
|
+
|
|
332
|
+
overlapped = [chunks[0]]
|
|
333
|
+
for i in range(1, len(chunks)):
|
|
334
|
+
prev_tokens = _ENCODER.encode(chunks[i - 1])
|
|
335
|
+
if len(prev_tokens) >= OVERLAP_TOKENS:
|
|
336
|
+
overlap_text = _ENCODER.decode(prev_tokens[-OVERLAP_TOKENS:])
|
|
337
|
+
overlapped.append(f"[...]\n{overlap_text}\n\n{chunks[i]}")
|
|
338
|
+
else:
|
|
339
|
+
overlapped.append(chunks[i])
|
|
340
|
+
|
|
341
|
+
return overlapped
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _extract_step_block(text: str) -> tuple[str, str, str]:
|
|
345
|
+
"""
|
|
346
|
+
Locate the step sequence in text.
|
|
347
|
+
Returns (step_block, text_before, text_after).
|
|
348
|
+
If no steps found, returns ("", text, "").
|
|
349
|
+
"""
|
|
350
|
+
# Find lines that are part of a step pattern
|
|
351
|
+
lines = text.split("\n")
|
|
352
|
+
step_indices = []
|
|
353
|
+
|
|
354
|
+
step_pattern = re.compile(
|
|
355
|
+
r"(^\s*\d+\.\s|^\s*Step\s*\d+|^\s*[a-z]\)\s|^\s*[•\-\*]\s)",
|
|
356
|
+
re.MULTILINE | re.IGNORECASE,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
for i, line in enumerate(lines):
|
|
360
|
+
if step_pattern.match(line):
|
|
361
|
+
step_indices.append(i)
|
|
362
|
+
|
|
363
|
+
if len(step_indices) < 2:
|
|
364
|
+
return "", text, ""
|
|
365
|
+
|
|
366
|
+
start = step_indices[0]
|
|
367
|
+
end = step_indices[-1]
|
|
368
|
+
|
|
369
|
+
# Extend end to include lines belonging to the last step
|
|
370
|
+
while end + 1 < len(lines) and lines[end + 1].strip() and not _is_new_section(lines[end + 1]):
|
|
371
|
+
end += 1
|
|
372
|
+
|
|
373
|
+
before = "\n".join(lines[:start])
|
|
374
|
+
step_block = "\n".join(lines[start:end + 1])
|
|
375
|
+
after = "\n".join(lines[end + 1:])
|
|
376
|
+
|
|
377
|
+
return step_block, before, after
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _is_new_section(line: str) -> bool:
|
|
381
|
+
return bool(re.match(r"^#{1,4}\s|^[A-Z][A-Z\s]{5,}$", line))
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def _split_between_step_groups(text: str, max_tokens: int) -> list[str]:
|
|
385
|
+
"""Split a large steps block only at blank lines between step groups."""
|
|
386
|
+
groups = re.split(r"\n{2,}(?=\s*(?:\d+\.|Step\s*\d+))", text)
|
|
387
|
+
chunks: list[str] = []
|
|
388
|
+
current = ""
|
|
389
|
+
|
|
390
|
+
for group in groups:
|
|
391
|
+
candidate = f"{current}\n\n{group}".strip() if current else group
|
|
392
|
+
if count_tokens(candidate) <= max_tokens:
|
|
393
|
+
current = candidate
|
|
394
|
+
else:
|
|
395
|
+
if current:
|
|
396
|
+
chunks.append(current.strip())
|
|
397
|
+
current = group
|
|
398
|
+
|
|
399
|
+
if current.strip():
|
|
400
|
+
chunks.append(current.strip())
|
|
401
|
+
|
|
402
|
+
return chunks or [text]
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Chunk validation — runs before any chunk is written to the database.
|
|
3
|
+
Hard failures block insertion. Warnings are logged but allowed through.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
import re
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
STEP_PATTERNS = [
|
|
11
|
+
r"^\s*\d+\.\s",
|
|
12
|
+
r"^\s*Step\s*\d+",
|
|
13
|
+
r"^\s*[a-z]\)\s",
|
|
14
|
+
r"^\s*[•\-\*]\s",
|
|
15
|
+
r"^\s*\[\s*\]\s",
|
|
16
|
+
r"(^|\s)(First|Then|Next|Finally|After that|Once|Now),?\s",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
SENTENCE_ENDINGS = {".", ":", ";", "?", "!", ")", "]", '"', "|", ">"}
|
|
20
|
+
|
|
21
|
+
IGEL_PRODUCTS = ["UMS", "IGEL OS", "COSMOS", "Cloud Gateway", "ICG", "IMI", "IGEL Cloud"]
|
|
22
|
+
|
|
23
|
+
CHUNK_TOKEN_LIMITS = {
|
|
24
|
+
"procedure": (50, 750),
|
|
25
|
+
"concept": (50, 950),
|
|
26
|
+
"table": (30, 550),
|
|
27
|
+
"troubleshoot": (50, 650),
|
|
28
|
+
"configuration": (50, 750),
|
|
29
|
+
"api": (30, 650),
|
|
30
|
+
"default": (50, 950),
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class ValidationResult:
|
|
36
|
+
passed: bool
|
|
37
|
+
warnings: list[str] = field(default_factory=list)
|
|
38
|
+
errors: list[str] = field(default_factory=list)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def validate_chunk(content: str, token_count: int, chunk_type: str, metadata: dict) -> ValidationResult:
|
|
42
|
+
errors: list[str] = []
|
|
43
|
+
warnings: list[str] = []
|
|
44
|
+
|
|
45
|
+
stripped = content.strip()
|
|
46
|
+
if not stripped:
|
|
47
|
+
errors.append("EMPTY_CHUNK")
|
|
48
|
+
return ValidationResult(passed=False, errors=errors)
|
|
49
|
+
|
|
50
|
+
# 1. Sentence completeness (last char must be a meaningful ending)
|
|
51
|
+
last_char = stripped[-1]
|
|
52
|
+
if last_char not in SENTENCE_ENDINGS:
|
|
53
|
+
errors.append(f"INCOMPLETE_END: chunk ends with '{last_char}' — likely cut mid-sentence")
|
|
54
|
+
|
|
55
|
+
# 2. Token size bounds
|
|
56
|
+
min_t, max_t = CHUNK_TOKEN_LIMITS.get(chunk_type, CHUNK_TOKEN_LIMITS["default"])
|
|
57
|
+
if token_count < min_t:
|
|
58
|
+
errors.append(f"TOO_SMALL: {token_count} tokens (min {min_t} for type '{chunk_type}')")
|
|
59
|
+
if token_count > max_t:
|
|
60
|
+
errors.append(f"TOO_LARGE: {token_count} tokens (max {max_t} for type '{chunk_type}')")
|
|
61
|
+
|
|
62
|
+
# 3. Step integrity — if steps detected, must have at least 2
|
|
63
|
+
step_matches = _find_steps(stripped)
|
|
64
|
+
if 0 < len(step_matches) < 2:
|
|
65
|
+
errors.append(
|
|
66
|
+
f"ORPHANED_STEP: only 1 step found ('{step_matches[0][:30]}') — "
|
|
67
|
+
"likely cut mid-procedure"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# 4. Table header presence — if table rows exist, header divider must too
|
|
71
|
+
if "|" in stripped and stripped.count("|") > 4:
|
|
72
|
+
if "---" not in stripped and "===" not in stripped:
|
|
73
|
+
warnings.append("TABLE_NO_HEADER: table rows present without markdown header divider")
|
|
74
|
+
|
|
75
|
+
# 5. Mixed product warning
|
|
76
|
+
products_found = [p for p in IGEL_PRODUCTS if p.lower() in stripped.lower()]
|
|
77
|
+
if len(products_found) > 1:
|
|
78
|
+
warnings.append(f"MIXED_PRODUCTS: chunk references {products_found} — consider splitting")
|
|
79
|
+
|
|
80
|
+
# 6. Cross-reference flag
|
|
81
|
+
if re.search(r"(see section|refer to section|as described in section|page \d+)", stripped, re.IGNORECASE):
|
|
82
|
+
warnings.append("CROSS_REF: chunk contains a reference to another section")
|
|
83
|
+
|
|
84
|
+
return ValidationResult(
|
|
85
|
+
passed=len(errors) == 0,
|
|
86
|
+
warnings=warnings,
|
|
87
|
+
errors=errors,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def validate_coverage(original_token_count: int, chunk_token_counts: list[int]) -> float:
|
|
92
|
+
"""
|
|
93
|
+
Ensure chunking didn't lose content.
|
|
94
|
+
Returns coverage ratio. Raises if below 90%.
|
|
95
|
+
"""
|
|
96
|
+
if original_token_count == 0:
|
|
97
|
+
return 1.0
|
|
98
|
+
chunked_total = sum(chunk_token_counts)
|
|
99
|
+
ratio = chunked_total / original_token_count
|
|
100
|
+
if ratio < 0.90:
|
|
101
|
+
raise ValueError(
|
|
102
|
+
f"Coverage too low: {ratio:.1%} — {(1 - ratio) * 100:.1f}% of content lost during chunking"
|
|
103
|
+
)
|
|
104
|
+
return ratio
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def contains_steps(text: str) -> bool:
|
|
108
|
+
for pattern in STEP_PATTERNS:
|
|
109
|
+
if re.search(pattern, text, re.MULTILINE | re.IGNORECASE):
|
|
110
|
+
return True
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _find_steps(text: str) -> list[str]:
|
|
115
|
+
matches = []
|
|
116
|
+
for pattern in STEP_PATTERNS[:5]: # numbered/bullet patterns only
|
|
117
|
+
found = re.findall(pattern, text, re.MULTILINE | re.IGNORECASE)
|
|
118
|
+
matches.extend(found)
|
|
119
|
+
return matches
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def classify_chunk_type(text: str, section_title: str = "") -> str:
|
|
123
|
+
title_lower = section_title.lower()
|
|
124
|
+
text_lower = text.lower()
|
|
125
|
+
|
|
126
|
+
if contains_steps(text):
|
|
127
|
+
return "procedure"
|
|
128
|
+
if any(w in title_lower for w in ["troubleshoot", "error", "fail", "issue", "problem", "debug"]):
|
|
129
|
+
return "troubleshoot"
|
|
130
|
+
if any(w in title_lower for w in ["configure", "setup", "install", "enable", "disable", "settings"]):
|
|
131
|
+
return "configuration"
|
|
132
|
+
if any(w in text_lower for w in ["api", "endpoint", "request", "response", "curl", "http", "json"]):
|
|
133
|
+
return "api"
|
|
134
|
+
if "|" in text and text.count("|") > 4:
|
|
135
|
+
return "table"
|
|
136
|
+
return "concept"
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def detect_product(text: str, file_name: str = "") -> str:
|
|
140
|
+
combined = (text + " " + file_name).lower()
|
|
141
|
+
for product in IGEL_PRODUCTS:
|
|
142
|
+
if product.lower() in combined:
|
|
143
|
+
return product
|
|
144
|
+
return "IGEL" # default — everything is IGEL-related
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Command-line entry points."""
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|