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
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"""GraphRAG helpers: query expansion over knowledge_graph_nodes / knowledge_graph_edges."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import re
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
from knowledge_base.db.client import get_conn
|
|
10
|
+
from knowledge_base.db.kg_introspect import get_kg_layout
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class KGContext:
|
|
17
|
+
"""Structured graph context fused into prompts."""
|
|
18
|
+
|
|
19
|
+
text_block: str
|
|
20
|
+
node_ids: list[str]
|
|
21
|
+
node_count: int
|
|
22
|
+
edge_count: int
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def classify_query_style(query: str) -> str:
|
|
26
|
+
if re.search(r"app\.[a-z0-9_.]+", query, re.IGNORECASE):
|
|
27
|
+
return "exact"
|
|
28
|
+
if re.search(r"\b(step|workflow|scenario|procedure)\b", query, re.IGNORECASE):
|
|
29
|
+
return "workflow"
|
|
30
|
+
return "semantic"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _q(col: str) -> str:
|
|
34
|
+
return '"' + col.replace('"', '""') + '"'
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _search_terms(query: str) -> list[str]:
|
|
38
|
+
q = query.strip()
|
|
39
|
+
keys = re.findall(r"app\.[a-z0-9_.]+", q, flags=re.IGNORECASE)
|
|
40
|
+
tokens = [t for t in re.split(r"[^\w\-./]+", q) if len(t) >= 3]
|
|
41
|
+
out: list[str] = []
|
|
42
|
+
for x in keys + tokens[:12]:
|
|
43
|
+
lx = x.lower()
|
|
44
|
+
if lx not in {y.lower() for y in out}:
|
|
45
|
+
out.append(x)
|
|
46
|
+
if len(out) >= 16:
|
|
47
|
+
break
|
|
48
|
+
return out
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _find_matching_node_ids(
|
|
52
|
+
*,
|
|
53
|
+
lay,
|
|
54
|
+
terms: list[str],
|
|
55
|
+
seed_parent_ids: list[str] | None,
|
|
56
|
+
limit: int,
|
|
57
|
+
) -> list[str]:
|
|
58
|
+
if not lay.node_id or not lay.value_col:
|
|
59
|
+
return []
|
|
60
|
+
|
|
61
|
+
nid = _q(lay.node_id)
|
|
62
|
+
vcol = _q(lay.value_col)
|
|
63
|
+
tname = _q(lay.nodes_table)
|
|
64
|
+
|
|
65
|
+
clauses: list[str] = []
|
|
66
|
+
params: list[object] = []
|
|
67
|
+
|
|
68
|
+
pat_clauses: list[str] = []
|
|
69
|
+
for t in terms:
|
|
70
|
+
esc = "%" + t.replace("%", r"\%").replace("_", r"\_") + "%"
|
|
71
|
+
pat_clauses.append(f"{vcol} ILIKE %s")
|
|
72
|
+
params.append(esc)
|
|
73
|
+
|
|
74
|
+
if lay.normalized_col:
|
|
75
|
+
ncol = _q(lay.normalized_col)
|
|
76
|
+
for t in terms:
|
|
77
|
+
esc = "%" + t.lower() + "%"
|
|
78
|
+
pat_clauses.append(f"{ncol} ILIKE %s")
|
|
79
|
+
params.append(esc)
|
|
80
|
+
|
|
81
|
+
if pat_clauses:
|
|
82
|
+
clauses.append("(" + " OR ".join(pat_clauses) + ")")
|
|
83
|
+
|
|
84
|
+
if lay.parent_chunk_col and seed_parent_ids:
|
|
85
|
+
pcol = _q(lay.parent_chunk_col)
|
|
86
|
+
clauses.append(f"{pcol}::uuid = ANY(%s::uuid[])")
|
|
87
|
+
params.append(seed_parent_ids[:512])
|
|
88
|
+
|
|
89
|
+
if not clauses:
|
|
90
|
+
return []
|
|
91
|
+
|
|
92
|
+
where_sql = "(" + (" OR ".join(clauses)) + ")" if len(clauses) > 1 else clauses[0]
|
|
93
|
+
sql = f"SELECT {nid} FROM {tname} WHERE {where_sql} LIMIT %s"
|
|
94
|
+
params.append(limit)
|
|
95
|
+
|
|
96
|
+
with get_conn() as conn:
|
|
97
|
+
with conn.cursor() as cur:
|
|
98
|
+
cur.execute(sql, tuple(params))
|
|
99
|
+
return [str(r[0]) for r in cur.fetchall()]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _expand_neighbors(
|
|
103
|
+
layout,
|
|
104
|
+
frontier: list[str],
|
|
105
|
+
*,
|
|
106
|
+
exclude: set[str],
|
|
107
|
+
) -> tuple[list[str], int]:
|
|
108
|
+
if (
|
|
109
|
+
not frontier
|
|
110
|
+
or not getattr(layout, "source_col", None)
|
|
111
|
+
or not getattr(layout, "target_col", None)
|
|
112
|
+
):
|
|
113
|
+
return [], 0
|
|
114
|
+
|
|
115
|
+
src_q = _q(layout.source_col)
|
|
116
|
+
tgt_q = _q(layout.target_col)
|
|
117
|
+
tbl = _q(layout.edges_table)
|
|
118
|
+
|
|
119
|
+
sql = (
|
|
120
|
+
f"SELECT {src_q}::text, {tgt_q}::text FROM {tbl} WHERE "
|
|
121
|
+
f"{src_q}::uuid = ANY(%s::uuid[]) OR {tgt_q}::uuid = ANY(%s::uuid[])"
|
|
122
|
+
)
|
|
123
|
+
batch = frontier[:512]
|
|
124
|
+
with get_conn() as conn:
|
|
125
|
+
with conn.cursor() as cur:
|
|
126
|
+
cur.execute(sql, (batch, batch))
|
|
127
|
+
rows = cur.fetchall()
|
|
128
|
+
|
|
129
|
+
new_neighbors: list[str] = []
|
|
130
|
+
for a, b in rows:
|
|
131
|
+
for nid in (a, b):
|
|
132
|
+
if nid and nid not in exclude:
|
|
133
|
+
new_neighbors.append(str(nid))
|
|
134
|
+
exclude.add(str(nid))
|
|
135
|
+
return new_neighbors, len(rows)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def retrieve_kg_context(
|
|
139
|
+
query: str,
|
|
140
|
+
max_hops: int = 2,
|
|
141
|
+
max_nodes: int = 48,
|
|
142
|
+
seed_parent_chunk_ids: list[str] | None = None,
|
|
143
|
+
feature_area: str | None = None,
|
|
144
|
+
) -> KGContext:
|
|
145
|
+
"""
|
|
146
|
+
Expand around nodes matching query terms (+ optional anchor parent chunks).
|
|
147
|
+
Routes to Neo4j when KG_BACKEND=neo4j, else PostgreSQL.
|
|
148
|
+
"""
|
|
149
|
+
from knowledge_base.config import cfg
|
|
150
|
+
|
|
151
|
+
if cfg.KG_BACKEND == "neo4j":
|
|
152
|
+
from knowledge_base.graph_db.kg_traversal import retrieve_kg_context_neo4j
|
|
153
|
+
|
|
154
|
+
return retrieve_kg_context_neo4j(
|
|
155
|
+
query,
|
|
156
|
+
max_hops=max_hops,
|
|
157
|
+
max_nodes=max_nodes,
|
|
158
|
+
feature_area=feature_area,
|
|
159
|
+
seed_parent_chunk_ids=seed_parent_chunk_ids,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
lay = get_kg_layout()
|
|
163
|
+
if lay is None or not lay.node_id or not lay.value_col:
|
|
164
|
+
return KGContext(text_block="", node_ids=[], node_count=0, edge_count=0)
|
|
165
|
+
|
|
166
|
+
hops = max(0, min(int(max_hops), 3))
|
|
167
|
+
terms = _search_terms(query)
|
|
168
|
+
seeds_parent = seed_parent_chunk_ids or []
|
|
169
|
+
|
|
170
|
+
matched = _find_matching_node_ids(
|
|
171
|
+
lay=lay,
|
|
172
|
+
terms=terms,
|
|
173
|
+
seed_parent_ids=seeds_parent or None,
|
|
174
|
+
limit=max_nodes * 4,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
expanded_ids = list(dict.fromkeys(matched))
|
|
178
|
+
exclude = set(expanded_ids)
|
|
179
|
+
total_edges = 0
|
|
180
|
+
|
|
181
|
+
can_expand = (
|
|
182
|
+
hops > 0
|
|
183
|
+
and bool(lay.edges_table)
|
|
184
|
+
and lay.source_col
|
|
185
|
+
and lay.target_col
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
frontier = expanded_ids[:]
|
|
189
|
+
for _ in range(hops):
|
|
190
|
+
if not can_expand or not frontier:
|
|
191
|
+
break
|
|
192
|
+
new_nodes, ec = _expand_neighbors(lay, frontier, exclude=exclude)
|
|
193
|
+
total_edges += ec
|
|
194
|
+
frontier = []
|
|
195
|
+
for nid in new_nodes:
|
|
196
|
+
if nid not in expanded_ids and len(expanded_ids) < max_nodes * 4:
|
|
197
|
+
expanded_ids.append(nid)
|
|
198
|
+
frontier.append(nid)
|
|
199
|
+
if len(expanded_ids) >= max_nodes * 4:
|
|
200
|
+
break
|
|
201
|
+
|
|
202
|
+
capped = expanded_ids[: max_nodes * 2]
|
|
203
|
+
text_block = _format_nodes_block(lay, capped)
|
|
204
|
+
|
|
205
|
+
return KGContext(
|
|
206
|
+
text_block=text_block,
|
|
207
|
+
node_ids=capped,
|
|
208
|
+
node_count=len(expanded_ids),
|
|
209
|
+
edge_count=total_edges,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _format_nodes_block(layout, node_ids: list[str]) -> str:
|
|
214
|
+
"""Format KG nodes grouped by type so the LLM can map them to executable steps."""
|
|
215
|
+
if not node_ids or not layout.node_id or not layout.value_col:
|
|
216
|
+
return ""
|
|
217
|
+
|
|
218
|
+
nid = _q(layout.node_id)
|
|
219
|
+
vcol = _q(layout.value_col)
|
|
220
|
+
tname = _q(layout.nodes_table)
|
|
221
|
+
|
|
222
|
+
select_list = [vcol]
|
|
223
|
+
type_col = layout.node_type_col
|
|
224
|
+
if type_col:
|
|
225
|
+
select_list.append(_q(type_col))
|
|
226
|
+
|
|
227
|
+
sql = (
|
|
228
|
+
"SELECT DISTINCT " + ", ".join(select_list) + " FROM " + tname + " WHERE "
|
|
229
|
+
f"{nid}::uuid = ANY(%s::uuid[])"
|
|
230
|
+
)
|
|
231
|
+
with get_conn() as conn:
|
|
232
|
+
with conn.cursor() as cur:
|
|
233
|
+
cur.execute(sql, (node_ids[:200],))
|
|
234
|
+
rows = cur.fetchall()
|
|
235
|
+
|
|
236
|
+
if not rows:
|
|
237
|
+
return ""
|
|
238
|
+
|
|
239
|
+
grouped: dict[str, list[str]] = {}
|
|
240
|
+
for row in rows:
|
|
241
|
+
value = (str(row[0]).strip() if row[0] else "")
|
|
242
|
+
ntype = (str(row[1]).strip() if len(row) > 1 and row[1] else "entity")
|
|
243
|
+
if value:
|
|
244
|
+
grouped.setdefault(ntype, []).append(value)
|
|
245
|
+
|
|
246
|
+
_TYPE_HEADINGS = {
|
|
247
|
+
"registry_key": "Registry keys (use with `get`/`set` on device shell)",
|
|
248
|
+
"ui_path": "TC Setup / UI navigation paths (exact menu paths for configuration)",
|
|
249
|
+
"feature": "Features & sections (cross-document relationships)",
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
lines = ["—— Knowledge graph entities (use these in test steps for accuracy) ——"]
|
|
253
|
+
for ntype in ("ui_path", "registry_key", "feature"):
|
|
254
|
+
items = grouped.pop(ntype, [])
|
|
255
|
+
if not items:
|
|
256
|
+
continue
|
|
257
|
+
heading = _TYPE_HEADINGS.get(ntype, ntype)
|
|
258
|
+
lines.append(f"\n[{heading}]")
|
|
259
|
+
for val in items[:30]:
|
|
260
|
+
lines.append(f" • {val}")
|
|
261
|
+
|
|
262
|
+
for ntype, items in sorted(grouped.items()):
|
|
263
|
+
lines.append(f"\n[{ntype}]")
|
|
264
|
+
for val in items[:20]:
|
|
265
|
+
lines.append(f" • {val}")
|
|
266
|
+
|
|
267
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pluggable cross-encoder reranking for retrieval (after RRF, before budget assembly).
|
|
3
|
+
|
|
4
|
+
Backends:
|
|
5
|
+
local — sentence-transformers CrossEncoder (OSS, CPU-optimized by default)
|
|
6
|
+
cohere — Cohere Rerank API (optional hosted)
|
|
7
|
+
none — passthrough (scores inferred from rank order)
|
|
8
|
+
|
|
9
|
+
CPU defaults (192.168.204.65 server — no GPU):
|
|
10
|
+
Model: cross-encoder/ms-marco-MiniLM-L-6-v2 (~80MB, ~2-5s for 16 passages)
|
|
11
|
+
top_n: 16
|
|
12
|
+
max_length: 256 tokens per query-passage pair
|
|
13
|
+
batch_size: 8
|
|
14
|
+
|
|
15
|
+
Multilingual OSS alternative (slower on CPU, ~30-60s):
|
|
16
|
+
RERANKER_MODEL=BAAI/bge-reranker-base
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import logging
|
|
21
|
+
import os
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
from time import perf_counter
|
|
24
|
+
|
|
25
|
+
from knowledge_base.config import cfg
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
_CROSS_ENCODER = None
|
|
30
|
+
_CROSS_ENCODER_KEY: str | None = None
|
|
31
|
+
_DEPS_PREPARED = False
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _prepare_reranker_environment() -> None:
|
|
35
|
+
"""Fix dependency conflicts and tune PyTorch for CPU inference."""
|
|
36
|
+
global _DEPS_PREPARED
|
|
37
|
+
if _DEPS_PREPARED:
|
|
38
|
+
return
|
|
39
|
+
try:
|
|
40
|
+
import markupsafe
|
|
41
|
+
|
|
42
|
+
if not hasattr(markupsafe, "soft_unicode"):
|
|
43
|
+
markupsafe.soft_unicode = getattr(markupsafe, "soft_str", str)
|
|
44
|
+
except ImportError:
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
device = _resolve_device()
|
|
48
|
+
if device == "cpu":
|
|
49
|
+
threads = max(1, int(getattr(cfg, "RERANKER_TORCH_THREADS", 4)))
|
|
50
|
+
os.environ.setdefault("OMP_NUM_THREADS", str(threads))
|
|
51
|
+
os.environ.setdefault("MKL_NUM_THREADS", str(threads))
|
|
52
|
+
try:
|
|
53
|
+
import torch
|
|
54
|
+
|
|
55
|
+
torch.set_num_threads(threads)
|
|
56
|
+
torch.set_grad_enabled(False)
|
|
57
|
+
except ImportError:
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
_DEPS_PREPARED = True
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _resolve_device() -> str:
|
|
64
|
+
explicit = (getattr(cfg, "RERANKER_DEVICE", None) or "cpu").strip().lower()
|
|
65
|
+
if explicit not in ("cpu", "cuda", "mps"):
|
|
66
|
+
explicit = "cpu"
|
|
67
|
+
if explicit == "cuda":
|
|
68
|
+
try:
|
|
69
|
+
import torch
|
|
70
|
+
|
|
71
|
+
if not torch.cuda.is_available():
|
|
72
|
+
logger.info("RERANKER_DEVICE=cuda requested but no GPU — using cpu")
|
|
73
|
+
return "cpu"
|
|
74
|
+
except ImportError:
|
|
75
|
+
return "cpu"
|
|
76
|
+
return explicit
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _load_cross_encoder(model_name: str):
|
|
80
|
+
"""Load sentence-transformers CrossEncoder with CPU-optimized settings."""
|
|
81
|
+
global _CROSS_ENCODER, _CROSS_ENCODER_KEY
|
|
82
|
+
_prepare_reranker_environment()
|
|
83
|
+
|
|
84
|
+
device = _resolve_device()
|
|
85
|
+
max_length = max(64, int(getattr(cfg, "RERANKER_MAX_LENGTH", 256)))
|
|
86
|
+
cache_key = f"{model_name}|{device}|{max_length}"
|
|
87
|
+
|
|
88
|
+
if _CROSS_ENCODER is not None and _CROSS_ENCODER_KEY == cache_key:
|
|
89
|
+
return _CROSS_ENCODER
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
from sentence_transformers import CrossEncoder
|
|
93
|
+
except ImportError as e:
|
|
94
|
+
raise ImportError(
|
|
95
|
+
"sentence-transformers not available. Run: "
|
|
96
|
+
"bash knowledge_base/deploy/install_reranker.sh"
|
|
97
|
+
) from e
|
|
98
|
+
|
|
99
|
+
logger.info(
|
|
100
|
+
"Loading OSS CrossEncoder: model=%s device=%s max_length=%d",
|
|
101
|
+
model_name,
|
|
102
|
+
device,
|
|
103
|
+
max_length,
|
|
104
|
+
)
|
|
105
|
+
t0 = perf_counter()
|
|
106
|
+
_CROSS_ENCODER = CrossEncoder(model_name, max_length=max_length, device=device)
|
|
107
|
+
_CROSS_ENCODER_KEY = cache_key
|
|
108
|
+
logger.info("Reranker ready in %.1fs", perf_counter() - t0)
|
|
109
|
+
return _CROSS_ENCODER
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class RerankItem:
|
|
114
|
+
child_chunk_id: str
|
|
115
|
+
passage: str
|
|
116
|
+
parent_passage: str = ""
|
|
117
|
+
section_title: str = ""
|
|
118
|
+
source_url: str = ""
|
|
119
|
+
metadata: dict = field(default_factory=dict)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def build_rerank_passage(item: RerankItem) -> str:
|
|
123
|
+
"""Build a compact passage for cross-encoder input (CPU-friendly length)."""
|
|
124
|
+
passage_limit = int(getattr(cfg, "RERANKER_PASSAGE_CHARS", 2000))
|
|
125
|
+
parent_limit = int(getattr(cfg, "RERANKER_PARENT_CHARS", 1200))
|
|
126
|
+
|
|
127
|
+
parts: list[str] = []
|
|
128
|
+
if item.section_title:
|
|
129
|
+
parts.append(f"Section: {item.section_title[:200]}")
|
|
130
|
+
if item.parent_passage and item.parent_passage.strip() != item.passage.strip():
|
|
131
|
+
parts.append(f"Context:\n{item.parent_passage[:parent_limit]}")
|
|
132
|
+
parts.append(f"Passage:\n{item.passage[:passage_limit]}")
|
|
133
|
+
combined = "\n\n".join(parts)
|
|
134
|
+
return combined[: passage_limit + parent_limit + 250]
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def rerank_passages(query: str, items: list[RerankItem]) -> list[tuple[str, float]]:
|
|
138
|
+
"""
|
|
139
|
+
Return list of (child_chunk_id, score) sorted by descending relevance.
|
|
140
|
+
Scores normalized roughly to [0, 1] within the batch where applicable.
|
|
141
|
+
"""
|
|
142
|
+
if not items:
|
|
143
|
+
return []
|
|
144
|
+
backend = (cfg.RERANKER_BACKEND or "none").lower().strip()
|
|
145
|
+
|
|
146
|
+
if backend == "none":
|
|
147
|
+
return _score_by_rank_fallback(items)
|
|
148
|
+
|
|
149
|
+
if backend == "cohere":
|
|
150
|
+
return _rerank_cohere(query, items)
|
|
151
|
+
|
|
152
|
+
if backend == "local":
|
|
153
|
+
return _rerank_local(query, items)
|
|
154
|
+
|
|
155
|
+
logger.warning("Unknown RERANKER_BACKEND=%r; using rank fallback", backend)
|
|
156
|
+
return _score_by_rank_fallback(items)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def apply_rerank_threshold(
|
|
160
|
+
ranked: list[tuple[str, float]],
|
|
161
|
+
threshold: float | None = None,
|
|
162
|
+
) -> list[tuple[str, float]]:
|
|
163
|
+
"""Filter reranked results below RERANKER_SCORE_THRESHOLD."""
|
|
164
|
+
cutoff = threshold if threshold is not None else getattr(cfg, "RERANKER_SCORE_THRESHOLD", None)
|
|
165
|
+
if cutoff is None or cutoff <= 0:
|
|
166
|
+
return ranked
|
|
167
|
+
filtered = [(cid, sc) for cid, sc in ranked if sc >= cutoff]
|
|
168
|
+
if len(filtered) < len(ranked):
|
|
169
|
+
logger.debug(
|
|
170
|
+
"rerank threshold %.2f removed %d/%d items",
|
|
171
|
+
cutoff,
|
|
172
|
+
len(ranked) - len(filtered),
|
|
173
|
+
len(ranked),
|
|
174
|
+
)
|
|
175
|
+
return filtered or ranked[: max(1, len(ranked) // 3)]
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _score_by_rank_fallback(items: list[RerankItem]) -> list[tuple[str, float]]:
|
|
179
|
+
n = len(items)
|
|
180
|
+
if n == 0:
|
|
181
|
+
return []
|
|
182
|
+
return [(it.child_chunk_id, 1.0 - (i / max(n, 1))) for i, it in enumerate(items)]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _normalize_scores(pairs: list[tuple[str, float]]) -> list[tuple[str, float]]:
|
|
186
|
+
if not pairs:
|
|
187
|
+
return []
|
|
188
|
+
vals = [s for _, s in pairs]
|
|
189
|
+
lo, hi = min(vals), max(vals)
|
|
190
|
+
if hi <= lo:
|
|
191
|
+
return [(cid, 0.5) for cid, _ in pairs]
|
|
192
|
+
return [(cid, (s - lo) / (hi - lo)) for cid, s in pairs]
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _rerank_local(query: str, items: list[RerankItem]) -> list[tuple[str, float]]:
|
|
196
|
+
model_name = cfg.RERANKER_MODEL or "cross-encoder/ms-marco-MiniLM-L-6-v2"
|
|
197
|
+
try:
|
|
198
|
+
encoder = _load_cross_encoder(model_name)
|
|
199
|
+
except ImportError as e:
|
|
200
|
+
logger.error("%s", e)
|
|
201
|
+
return _score_by_rank_fallback(items)
|
|
202
|
+
except Exception as e:
|
|
203
|
+
logger.error("Failed to load reranker model %s: %s", model_name, e)
|
|
204
|
+
return _score_by_rank_fallback(items)
|
|
205
|
+
|
|
206
|
+
# Cap items — retriever should already limit, but enforce here for safety
|
|
207
|
+
top_n = max(1, int(getattr(cfg, "RERANKER_TOP_N", 16)))
|
|
208
|
+
items = items[:top_n]
|
|
209
|
+
|
|
210
|
+
pairs = [(query, build_rerank_passage(it)) for it in items]
|
|
211
|
+
batch_size = max(1, int(getattr(cfg, "RERANKER_BATCH_SIZE", 8)))
|
|
212
|
+
|
|
213
|
+
t0 = perf_counter()
|
|
214
|
+
raw = encoder.predict(pairs, show_progress_bar=False, batch_size=batch_size)
|
|
215
|
+
elapsed = perf_counter() - t0
|
|
216
|
+
logger.info(
|
|
217
|
+
"Reranked %d passages in %.2fs (model=%s, device=%s)",
|
|
218
|
+
len(items),
|
|
219
|
+
elapsed,
|
|
220
|
+
model_name,
|
|
221
|
+
_resolve_device(),
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
scored = [(items[i].child_chunk_id, float(raw[i])) for i in range(len(items))]
|
|
225
|
+
scored.sort(key=lambda x: x[1], reverse=True)
|
|
226
|
+
normalized = _normalize_scores(scored)
|
|
227
|
+
return apply_rerank_threshold(normalized)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _rerank_cohere(query: str, items: list[RerankItem]) -> list[tuple[str, float]]:
|
|
231
|
+
try:
|
|
232
|
+
import cohere
|
|
233
|
+
except ImportError as e:
|
|
234
|
+
logger.error("cohere package not installed: %s", e)
|
|
235
|
+
return _score_by_rank_fallback(items)
|
|
236
|
+
|
|
237
|
+
key = getattr(cfg, "COHERE_API_KEY", "") or ""
|
|
238
|
+
if not key:
|
|
239
|
+
logger.error("COHERE_API_KEY not set — cannot use cohere reranker")
|
|
240
|
+
return _score_by_rank_fallback(items)
|
|
241
|
+
|
|
242
|
+
client = cohere.Client(key)
|
|
243
|
+
docs = [build_rerank_passage(it) for it in items]
|
|
244
|
+
model = getattr(cfg, "COHERE_RERANK_MODEL", None) or "rerank-multilingual-v3.0"
|
|
245
|
+
top_n = min(len(docs), max(cfg.RERANKER_TOP_N, len(docs)))
|
|
246
|
+
rsp = client.rerank(model=model, query=query, documents=docs, top_n=top_n)
|
|
247
|
+
|
|
248
|
+
out: list[tuple[str, float]] = []
|
|
249
|
+
for r in rsp.results:
|
|
250
|
+
out.append((items[r.index].child_chunk_id, float(r.relevance_score)))
|
|
251
|
+
out.sort(key=lambda x: x[1], reverse=True)
|
|
252
|
+
normalized = _normalize_scores(out)
|
|
253
|
+
return apply_rerank_threshold(normalized)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def rerank_web_pages(
|
|
257
|
+
query: str,
|
|
258
|
+
pages: list[tuple[str, str, str]],
|
|
259
|
+
) -> list[tuple[str, float]]:
|
|
260
|
+
"""
|
|
261
|
+
Rerank web pages by pure query-passage relevancy (url, title, content).
|
|
262
|
+
Uses the same cross-encoder as local RAG but without score threshold filtering.
|
|
263
|
+
"""
|
|
264
|
+
if not pages:
|
|
265
|
+
return []
|
|
266
|
+
|
|
267
|
+
items = [
|
|
268
|
+
RerankItem(
|
|
269
|
+
child_chunk_id=url,
|
|
270
|
+
passage=content,
|
|
271
|
+
section_title=title,
|
|
272
|
+
source_url=url,
|
|
273
|
+
)
|
|
274
|
+
for url, title, content in pages
|
|
275
|
+
]
|
|
276
|
+
backend = (cfg.RERANKER_BACKEND or "none").lower().strip()
|
|
277
|
+
if backend == "local":
|
|
278
|
+
model_name = cfg.RERANKER_MODEL or "cross-encoder/ms-marco-MiniLM-L-6-v2"
|
|
279
|
+
try:
|
|
280
|
+
encoder = _load_cross_encoder(model_name)
|
|
281
|
+
except Exception as e:
|
|
282
|
+
logger.warning("web reranker unavailable: %s", e)
|
|
283
|
+
return [(url, 0.5 - i * 0.01) for i, (url, _, _) in enumerate(pages)]
|
|
284
|
+
|
|
285
|
+
pairs = [(query, build_rerank_passage(it)) for it in items]
|
|
286
|
+
batch_size = max(1, int(getattr(cfg, "RERANKER_BATCH_SIZE", 8)))
|
|
287
|
+
raw = encoder.predict(pairs, show_progress_bar=False, batch_size=batch_size)
|
|
288
|
+
scored = [(pages[i][0], float(raw[i])) for i in range(len(pages))]
|
|
289
|
+
scored.sort(key=lambda x: x[1], reverse=True)
|
|
290
|
+
return _normalize_scores(scored)
|
|
291
|
+
|
|
292
|
+
if backend == "cohere":
|
|
293
|
+
ranked = _rerank_cohere(query, items)
|
|
294
|
+
return ranked
|
|
295
|
+
|
|
296
|
+
return [(url, 0.5 - i * 0.01) for i, (url, _, _) in enumerate(pages)]
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def warm_start_reranker() -> None:
|
|
300
|
+
"""Pre-load cross-encoder model at startup."""
|
|
301
|
+
if cfg.RERANKER_BACKEND == "local":
|
|
302
|
+
_load_cross_encoder()
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def is_reranker_loaded() -> bool:
|
|
306
|
+
if cfg.RERANKER_BACKEND != "local":
|
|
307
|
+
return True
|
|
308
|
+
return _CROSS_ENCODER is not None
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Retrieval observability: scores, ranks, inclusion reasons for tuning GraphRAG."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
from dataclasses import dataclass, field, asdict
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class ChunkScoreTrace:
|
|
15
|
+
child_chunk_id: str
|
|
16
|
+
parent_chunk_id: str
|
|
17
|
+
source_file: str
|
|
18
|
+
section_title: str
|
|
19
|
+
vector_rank: int | None
|
|
20
|
+
fts_rank: int | None
|
|
21
|
+
rrf_score: float
|
|
22
|
+
rerank_score: float | None
|
|
23
|
+
included: bool
|
|
24
|
+
exclusion_reason: str # "" when included
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class RetrievalDebugReport:
|
|
29
|
+
query: str
|
|
30
|
+
query_classification: str
|
|
31
|
+
total_vector_candidates: int
|
|
32
|
+
total_fts_candidates: int
|
|
33
|
+
rrf_merged_count: int
|
|
34
|
+
reranker_applied: bool
|
|
35
|
+
post_rerank_count: int
|
|
36
|
+
kg_nodes_found: int
|
|
37
|
+
kg_edges_traversed: int
|
|
38
|
+
final_chunk_count: int
|
|
39
|
+
total_tokens_used: int
|
|
40
|
+
token_budget: int
|
|
41
|
+
traces: list[ChunkScoreTrace] = field(default_factory=list)
|
|
42
|
+
timing_ms: dict[str, float] = field(default_factory=dict)
|
|
43
|
+
|
|
44
|
+
def to_json(self) -> str:
|
|
45
|
+
return json.dumps(asdict(self), indent=2)
|
|
46
|
+
|
|
47
|
+
def log_summary(self) -> None:
|
|
48
|
+
logger.info(
|
|
49
|
+
"retrieval_debug: class=%s vec=%d fts=%d rrf_merged=%d rerank=%s "
|
|
50
|
+
"final=%d kg_nodes=%d kg_edges=%d tokens~%d/%d",
|
|
51
|
+
self.query_classification,
|
|
52
|
+
self.total_vector_candidates,
|
|
53
|
+
self.total_fts_candidates,
|
|
54
|
+
self.rrf_merged_count,
|
|
55
|
+
self.reranker_applied,
|
|
56
|
+
self.final_chunk_count,
|
|
57
|
+
self.kg_nodes_found,
|
|
58
|
+
self.kg_edges_traversed,
|
|
59
|
+
self.total_tokens_used,
|
|
60
|
+
self.token_budget,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def merge_traces_into_report(
|
|
65
|
+
base: RetrievalDebugReport,
|
|
66
|
+
traces: list[ChunkScoreTrace],
|
|
67
|
+
) -> RetrievalDebugReport:
|
|
68
|
+
base.traces = traces
|
|
69
|
+
return base
|