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,189 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unified query orchestrator — RAG + EKG + Web + MCP external sources.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import random
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from time import perf_counter
|
|
11
|
+
|
|
12
|
+
from knowledge_base.config import cfg
|
|
13
|
+
from knowledge_base.connectors.query_bridge import ExternalContext, query_mcp_sources
|
|
14
|
+
from knowledge_base.retrieval.retriever import RetrievalBundle, retrieve_v3
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _empty_bundle(query: str, *, reason: str) -> RetrievalBundle:
|
|
20
|
+
"""Build a safe empty fallback bundle for timeout/error paths."""
|
|
21
|
+
from knowledge_base.retrieval.knowledge_graph import KGContext
|
|
22
|
+
from knowledge_base.retrieval.retrieval_debug import RetrievalDebugReport
|
|
23
|
+
|
|
24
|
+
return RetrievalBundle(
|
|
25
|
+
results=[],
|
|
26
|
+
kg_context=KGContext("", [], 0, 0),
|
|
27
|
+
image_block="",
|
|
28
|
+
image_hits=[],
|
|
29
|
+
debug=RetrievalDebugReport(
|
|
30
|
+
query=query,
|
|
31
|
+
query_classification=reason,
|
|
32
|
+
total_vector_candidates=0,
|
|
33
|
+
total_fts_candidates=0,
|
|
34
|
+
rrf_merged_count=0,
|
|
35
|
+
reranker_applied=False,
|
|
36
|
+
post_rerank_count=0,
|
|
37
|
+
kg_nodes_found=0,
|
|
38
|
+
kg_edges_traversed=0,
|
|
39
|
+
final_chunk_count=0,
|
|
40
|
+
total_tokens_used=0,
|
|
41
|
+
token_budget=int(getattr(cfg, "CONTEXT_TOKEN_BUDGET", 8000)),
|
|
42
|
+
traces=[],
|
|
43
|
+
timing_ms={},
|
|
44
|
+
),
|
|
45
|
+
web_hits=[],
|
|
46
|
+
web_block="",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class UnifiedQueryResult:
|
|
52
|
+
rag: RetrievalBundle
|
|
53
|
+
web_block: str = ""
|
|
54
|
+
web_hits: list = field(default_factory=list)
|
|
55
|
+
mcp: ExternalContext = field(default_factory=ExternalContext)
|
|
56
|
+
sources_summary: dict = field(default_factory=dict)
|
|
57
|
+
timing_ms: dict = field(default_factory=dict)
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def kg_context(self):
|
|
61
|
+
return self.rag.kg_context
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def results(self):
|
|
65
|
+
return self.rag.results
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def debug(self):
|
|
69
|
+
return self.rag.debug
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def precedence_conflicts(self):
|
|
73
|
+
return self.rag.precedence_conflicts
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def precedence_attribution(self):
|
|
77
|
+
return self.rag.precedence_attribution
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def combined_context_block(self) -> str:
|
|
81
|
+
parts = []
|
|
82
|
+
if self.rag.kg_context.text_block:
|
|
83
|
+
parts.append(self.rag.kg_context.text_block.strip())
|
|
84
|
+
if self.rag.image_block:
|
|
85
|
+
parts.append(self.rag.image_block.strip())
|
|
86
|
+
if self.web_block:
|
|
87
|
+
parts.append(self.web_block.strip())
|
|
88
|
+
if self.mcp.block:
|
|
89
|
+
parts.append(self.mcp.block.strip())
|
|
90
|
+
return "\n\n".join(p for p in parts if p)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _maybe_persist_trace(query: str, result: "UnifiedQueryResult") -> None:
|
|
94
|
+
rate = getattr(cfg, "RETRIEVAL_TRACE_SAMPLE_RATE", 0.0)
|
|
95
|
+
if rate <= 0 or random.random() > rate:
|
|
96
|
+
return
|
|
97
|
+
try:
|
|
98
|
+
from knowledge_base.core.request_context import get_request_id
|
|
99
|
+
from knowledge_base.db.client import get_conn
|
|
100
|
+
import json
|
|
101
|
+
with get_conn() as conn:
|
|
102
|
+
with conn.cursor() as cur:
|
|
103
|
+
cur.execute(
|
|
104
|
+
"""
|
|
105
|
+
INSERT INTO retrieval_traces (request_id, query, sources_summary, debug_json, timing_ms)
|
|
106
|
+
VALUES (%s, %s, %s::jsonb, %s::jsonb, %s::jsonb)
|
|
107
|
+
""",
|
|
108
|
+
(
|
|
109
|
+
get_request_id(),
|
|
110
|
+
query[:2000],
|
|
111
|
+
json.dumps(result.sources_summary),
|
|
112
|
+
result.debug.to_json() if result.debug else "{}",
|
|
113
|
+
json.dumps(result.timing_ms),
|
|
114
|
+
),
|
|
115
|
+
)
|
|
116
|
+
except Exception as exc:
|
|
117
|
+
logger.debug("Trace persistence skipped: %s", exc)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def unified_query(
|
|
121
|
+
query: str,
|
|
122
|
+
*,
|
|
123
|
+
top_k: int = 8,
|
|
124
|
+
product: str | None = None,
|
|
125
|
+
chunk_type: str | None = None,
|
|
126
|
+
) -> UnifiedQueryResult:
|
|
127
|
+
"""Orchestrate RAG + EKG + Web + MCP for every user query."""
|
|
128
|
+
timings: dict[str, float] = {}
|
|
129
|
+
timeout = max(1.0, float(getattr(cfg, "QUERY_TIMEOUT_SECONDS", 60)))
|
|
130
|
+
deadline = perf_counter() + timeout
|
|
131
|
+
|
|
132
|
+
def _rag():
|
|
133
|
+
t0 = perf_counter()
|
|
134
|
+
bundle = retrieve_v3(query, top_k=top_k, product=product, chunk_type=chunk_type)
|
|
135
|
+
timings["rag_kg_web_ms"] = (perf_counter() - t0) * 1000
|
|
136
|
+
return bundle
|
|
137
|
+
|
|
138
|
+
def _mcp(*, timeout_seconds: float | None = None):
|
|
139
|
+
t0 = perf_counter()
|
|
140
|
+
ctx = query_mcp_sources(
|
|
141
|
+
query,
|
|
142
|
+
max_hits=getattr(cfg, "WEB_MAX_PAGES", 3),
|
|
143
|
+
timeout_seconds=timeout_seconds,
|
|
144
|
+
)
|
|
145
|
+
timings["mcp_ms"] = (perf_counter() - t0) * 1000
|
|
146
|
+
return ctx
|
|
147
|
+
|
|
148
|
+
bundle = _empty_bundle(query, reason="init")
|
|
149
|
+
mcp = ExternalContext()
|
|
150
|
+
try:
|
|
151
|
+
bundle = _rag()
|
|
152
|
+
except Exception as exc:
|
|
153
|
+
logger.exception("RAG retrieval failed: %s", exc)
|
|
154
|
+
bundle = _empty_bundle(query, reason="error")
|
|
155
|
+
|
|
156
|
+
if getattr(cfg, "QUERY_EXTERNAL_ENABLED", True):
|
|
157
|
+
remaining = deadline - perf_counter()
|
|
158
|
+
if remaining <= 0:
|
|
159
|
+
logger.warning("MCP query skipped due exhausted unified timeout budget")
|
|
160
|
+
else:
|
|
161
|
+
try:
|
|
162
|
+
mcp = _mcp(timeout_seconds=max(0.05, remaining))
|
|
163
|
+
except Exception as exc:
|
|
164
|
+
logger.warning("MCP query failed: %s", exc)
|
|
165
|
+
|
|
166
|
+
web_block = getattr(bundle, "web_block", "") or ""
|
|
167
|
+
web_hits = list(getattr(bundle, "web_hits", []) or [])
|
|
168
|
+
|
|
169
|
+
sources_summary = {
|
|
170
|
+
"rag_chunks": len(bundle.results),
|
|
171
|
+
"kg_nodes": bundle.kg_context.node_count,
|
|
172
|
+
"kg_edges": bundle.kg_context.edge_count,
|
|
173
|
+
"web_hits": len(web_hits),
|
|
174
|
+
"mcp_hits": len(mcp.hits),
|
|
175
|
+
"mcp_sources": mcp.sources_queried,
|
|
176
|
+
"reranker": bundle.debug.reranker_applied,
|
|
177
|
+
"kg_backend": getattr(cfg, "KG_BACKEND", "postgres"),
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
result = UnifiedQueryResult(
|
|
181
|
+
rag=bundle,
|
|
182
|
+
web_block=web_block,
|
|
183
|
+
web_hits=web_hits,
|
|
184
|
+
mcp=mcp,
|
|
185
|
+
sources_summary=sources_summary,
|
|
186
|
+
timing_ms=timings,
|
|
187
|
+
)
|
|
188
|
+
_maybe_persist_trace(query, result)
|
|
189
|
+
return result
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"""Live web retrieval — confidence-gated Stage 2 search (Firecrawl / DuckDuckGo / SearXNG)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
import math
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from time import perf_counter
|
|
10
|
+
from urllib.parse import urlparse
|
|
11
|
+
|
|
12
|
+
from knowledge_base.config import cfg
|
|
13
|
+
from knowledge_base.connectors.search_backends import get_configured_backends
|
|
14
|
+
from knowledge_base.connectors.web_fetcher import fetch_url, is_safe_fetch_target
|
|
15
|
+
from knowledge_base.parsers.html_parser import parse_html_page
|
|
16
|
+
from knowledge_base.retrieval.reranker import rerank_web_pages
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class WebHit:
|
|
23
|
+
url: str
|
|
24
|
+
title: str
|
|
25
|
+
content: str
|
|
26
|
+
source_type: str
|
|
27
|
+
score: float = 0.5
|
|
28
|
+
metadata: dict = field(default_factory=dict)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _parse_domain_list(raw: str | tuple[str, ...] | None) -> tuple[str, ...]:
|
|
32
|
+
if not raw:
|
|
33
|
+
return ()
|
|
34
|
+
if isinstance(raw, tuple):
|
|
35
|
+
return tuple(s.strip() for s in raw if s and str(s).strip())
|
|
36
|
+
return tuple(s.strip() for s in str(raw).split(",") if s.strip())
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _domains_from_sources() -> tuple[str, ...]:
|
|
40
|
+
"""Resolve WEB_SOURCES aliases into host allowlist entries."""
|
|
41
|
+
raw_sources = getattr(cfg, "WEB_SOURCES", ())
|
|
42
|
+
if isinstance(raw_sources, str):
|
|
43
|
+
sources = [s.strip().lower() for s in raw_sources.split(",") if s.strip()]
|
|
44
|
+
else:
|
|
45
|
+
sources = [str(s).strip().lower() for s in raw_sources if str(s).strip()]
|
|
46
|
+
|
|
47
|
+
domains: list[str] = []
|
|
48
|
+
for source in sources:
|
|
49
|
+
if source == "kb_igel" and getattr(cfg, "KB_IGEL_ENABLED", True):
|
|
50
|
+
host = urlparse(str(getattr(cfg, "KB_IGEL_BASE_URL", "")).strip()).netloc.lower()
|
|
51
|
+
if host:
|
|
52
|
+
domains.append(host)
|
|
53
|
+
elif source == "community" and getattr(cfg, "COMMUNITY_ENABLED", True):
|
|
54
|
+
host = urlparse(str(getattr(cfg, "COMMUNITY_BASE_URL", "")).strip()).netloc.lower()
|
|
55
|
+
if host:
|
|
56
|
+
domains.append(host)
|
|
57
|
+
|
|
58
|
+
# Preserve order while deduplicating.
|
|
59
|
+
seen: set[str] = set()
|
|
60
|
+
out: list[str] = []
|
|
61
|
+
for d in domains:
|
|
62
|
+
if d not in seen:
|
|
63
|
+
out.append(d)
|
|
64
|
+
seen.add(d)
|
|
65
|
+
return tuple(out)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _fetch_page_content_httpx(url: str) -> tuple[str, str]:
|
|
69
|
+
"""Fallback: httpx GET + HTML parse."""
|
|
70
|
+
cache_ttl = int(getattr(cfg, "WEB_CACHE_TTL_SECONDS", 3600))
|
|
71
|
+
result = fetch_url(url, cache_ttl=cache_ttl, strip_promoted=True)
|
|
72
|
+
if result.status_code != 200 or not result.html:
|
|
73
|
+
return url, ""
|
|
74
|
+
|
|
75
|
+
parsed = parse_html_page(result.html, url, source_type="web_search")
|
|
76
|
+
title = parsed.title or url
|
|
77
|
+
content = parsed.raw_text or ""
|
|
78
|
+
return title, content
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _extract_crawl4ai_markdown(result: object) -> str:
|
|
82
|
+
"""Normalize markdown across Crawl4AI versions."""
|
|
83
|
+
for attr in ("markdown", "fit_markdown", "cleaned_html"):
|
|
84
|
+
val = getattr(result, attr, None)
|
|
85
|
+
if isinstance(val, str) and val.strip():
|
|
86
|
+
return val.strip()
|
|
87
|
+
md_v2 = getattr(result, "markdown_v2", None)
|
|
88
|
+
if md_v2 is not None:
|
|
89
|
+
raw = getattr(md_v2, "raw_markdown", None) or getattr(md_v2, "fit_markdown", None)
|
|
90
|
+
if isinstance(raw, str) and raw.strip():
|
|
91
|
+
return raw.strip()
|
|
92
|
+
return ""
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
async def _fetch_page_content_crawl4ai(url: str, query: str) -> tuple[str, str]:
|
|
96
|
+
"""Fetch URL with Crawl4AI Playwright crawler (LLM-ready markdown)."""
|
|
97
|
+
try:
|
|
98
|
+
from crawl4ai import AsyncWebCrawler
|
|
99
|
+
except ImportError as e:
|
|
100
|
+
raise ImportError("crawl4ai not installed") from e
|
|
101
|
+
|
|
102
|
+
timeout = int(getattr(cfg, "CRAWL4AI_TIMEOUT", 30))
|
|
103
|
+
run_config = None
|
|
104
|
+
try:
|
|
105
|
+
from crawl4ai import CrawlerRunConfig
|
|
106
|
+
|
|
107
|
+
filter_kwargs: dict = {}
|
|
108
|
+
if query.strip():
|
|
109
|
+
try:
|
|
110
|
+
from crawl4ai.content_filter_strategy import BM25ContentFilter
|
|
111
|
+
|
|
112
|
+
filter_kwargs["content_filter"] = BM25ContentFilter(user_query=query)
|
|
113
|
+
except ImportError:
|
|
114
|
+
try:
|
|
115
|
+
from crawl4ai.content_filter_strategy import PruningContentFilter
|
|
116
|
+
|
|
117
|
+
filter_kwargs["content_filter"] = PruningContentFilter()
|
|
118
|
+
except ImportError:
|
|
119
|
+
pass
|
|
120
|
+
run_config = CrawlerRunConfig(**filter_kwargs) if filter_kwargs else CrawlerRunConfig()
|
|
121
|
+
except ImportError:
|
|
122
|
+
run_config = None
|
|
123
|
+
|
|
124
|
+
async with AsyncWebCrawler(verbose=False) as crawler:
|
|
125
|
+
if run_config is not None:
|
|
126
|
+
result = await asyncio.wait_for(crawler.arun(url=url, config=run_config), timeout=timeout)
|
|
127
|
+
else:
|
|
128
|
+
result = await asyncio.wait_for(crawler.arun(url=url), timeout=timeout)
|
|
129
|
+
|
|
130
|
+
if not result or not getattr(result, "success", True):
|
|
131
|
+
return url, ""
|
|
132
|
+
|
|
133
|
+
title = getattr(result, "title", None) or url
|
|
134
|
+
markdown = _extract_crawl4ai_markdown(result)
|
|
135
|
+
return title, markdown
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _fetch_page_content_playwright(url: str) -> tuple[str, str]:
|
|
139
|
+
from knowledge_base.connectors.playwright_fetcher import fetch_page_playwright, playwright_available
|
|
140
|
+
|
|
141
|
+
if not playwright_available():
|
|
142
|
+
raise ImportError("playwright not installed")
|
|
143
|
+
timeout_ms = int(getattr(cfg, "CRAWL4AI_TIMEOUT", 30)) * 1000
|
|
144
|
+
return fetch_page_playwright(url, timeout_ms=timeout_ms)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _fetch_page_content(url: str, query: str = "") -> tuple[str, str]:
|
|
148
|
+
"""Fetch page content — Crawl4AI → Playwright → httpx fallback.
|
|
149
|
+
|
|
150
|
+
Crawl4AI uses an async crawler. To avoid conflicting with the running
|
|
151
|
+
uvicorn/FastAPI event loop, the coroutine is executed in a fresh thread
|
|
152
|
+
(which has no pre-existing loop) via ThreadPoolExecutor.
|
|
153
|
+
Playwright fallback is intentionally outside the CRAWL4AI_ENABLED guard
|
|
154
|
+
so it remains available when Crawl4AI is disabled or fails.
|
|
155
|
+
"""
|
|
156
|
+
if getattr(cfg, "CRAWL4AI_ENABLED", True):
|
|
157
|
+
try:
|
|
158
|
+
import concurrent.futures
|
|
159
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
|
|
160
|
+
return pool.submit(asyncio.run, _fetch_page_content_crawl4ai(url, query)).result()
|
|
161
|
+
except ImportError:
|
|
162
|
+
logger.debug("crawl4ai unavailable — trying Playwright fetcher")
|
|
163
|
+
except Exception as e:
|
|
164
|
+
logger.warning("crawl4ai fetch failed for %s: %s", url, e)
|
|
165
|
+
|
|
166
|
+
# Playwright fallback — runs independently of CRAWL4AI_ENABLED
|
|
167
|
+
try:
|
|
168
|
+
return _fetch_page_content_playwright(url)
|
|
169
|
+
except ImportError:
|
|
170
|
+
logger.debug("playwright unavailable — httpx fallback for %s", url)
|
|
171
|
+
except Exception as e:
|
|
172
|
+
logger.warning("playwright fetch failed for %s: %s — httpx fallback", url, e)
|
|
173
|
+
|
|
174
|
+
return _fetch_page_content_httpx(url)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def retrieve_web_context(
|
|
178
|
+
query: str,
|
|
179
|
+
*,
|
|
180
|
+
max_pages: int | None = None,
|
|
181
|
+
local_top_score: float | None = None,
|
|
182
|
+
) -> list[WebHit]:
|
|
183
|
+
"""
|
|
184
|
+
Stage 2 live web search — only runs when local RAG confidence is below threshold.
|
|
185
|
+
|
|
186
|
+
Backends (WEB_SEARCH_BACKENDS):
|
|
187
|
+
- firecrawl → self-hosted Firecrawl search (primary)
|
|
188
|
+
- ddgs → DuckDuckGo organic search (fallback)
|
|
189
|
+
- searxng → Self-hosted SearXNG (optional)
|
|
190
|
+
"""
|
|
191
|
+
if not getattr(cfg, "WEB_RETRIEVAL_ENABLED", False):
|
|
192
|
+
return []
|
|
193
|
+
if not query.strip():
|
|
194
|
+
return []
|
|
195
|
+
|
|
196
|
+
threshold = float(getattr(cfg, "CONFIDENCE_THRESHOLD", 0.75))
|
|
197
|
+
if local_top_score is not None and local_top_score >= threshold:
|
|
198
|
+
logger.info(
|
|
199
|
+
"confidence gate: local top score %.3f >= %.3f — skipping web search for %r",
|
|
200
|
+
local_top_score,
|
|
201
|
+
threshold,
|
|
202
|
+
query[:60],
|
|
203
|
+
)
|
|
204
|
+
return []
|
|
205
|
+
|
|
206
|
+
if local_top_score is not None:
|
|
207
|
+
logger.info(
|
|
208
|
+
"confidence gate: local top score %.3f < %.3f — escalating to web search for %r",
|
|
209
|
+
local_top_score,
|
|
210
|
+
threshold,
|
|
211
|
+
query[:60],
|
|
212
|
+
)
|
|
213
|
+
else:
|
|
214
|
+
logger.info("confidence gate: no local score — running web search for %r", query[:60])
|
|
215
|
+
|
|
216
|
+
max_pages = max_pages or int(getattr(cfg, "WEB_MAX_PAGES", 5))
|
|
217
|
+
min_score = float(getattr(cfg, "WEB_MIN_SCORE", 0.15))
|
|
218
|
+
include_domains = _parse_domain_list(getattr(cfg, "WEB_SEARCH_INCLUDE_DOMAINS", ()))
|
|
219
|
+
exclude_domains = _parse_domain_list(getattr(cfg, "WEB_SEARCH_EXCLUDE_DOMAINS", ()))
|
|
220
|
+
include_from_sources = _domains_from_sources()
|
|
221
|
+
if include_from_sources:
|
|
222
|
+
include_domains = tuple(dict.fromkeys((*include_domains, *include_from_sources)))
|
|
223
|
+
|
|
224
|
+
backends = get_configured_backends()
|
|
225
|
+
if not backends:
|
|
226
|
+
logger.warning("no web search backends configured (WEB_SEARCH_BACKENDS)")
|
|
227
|
+
return []
|
|
228
|
+
|
|
229
|
+
t0 = perf_counter()
|
|
230
|
+
seen_urls: set[str] = set()
|
|
231
|
+
candidates: list[tuple[str, str, str, str]] = [] # url, title, content, backend
|
|
232
|
+
|
|
233
|
+
per_backend = max(1, math.ceil(max_pages / max(len(backends), 1)))
|
|
234
|
+
for backend in backends:
|
|
235
|
+
try:
|
|
236
|
+
search_results = backend.search(
|
|
237
|
+
query,
|
|
238
|
+
max_results=per_backend,
|
|
239
|
+
include_domains=include_domains or None,
|
|
240
|
+
exclude_domains=exclude_domains or None,
|
|
241
|
+
)
|
|
242
|
+
except Exception as e:
|
|
243
|
+
logger.warning("web backend %s search failed: %s", backend.name, e)
|
|
244
|
+
continue
|
|
245
|
+
|
|
246
|
+
for sr in search_results:
|
|
247
|
+
if not is_safe_fetch_target(sr.url):
|
|
248
|
+
logger.warning("skipping unsafe web target from backend %s: %s", backend.name, sr.url)
|
|
249
|
+
continue
|
|
250
|
+
norm = sr.url.rstrip("/").lower()
|
|
251
|
+
if norm in seen_urls:
|
|
252
|
+
continue
|
|
253
|
+
seen_urls.add(norm)
|
|
254
|
+
|
|
255
|
+
# Firecrawl may return full markdown in snippet — skip re-fetch if substantial.
|
|
256
|
+
if sr.snippet and len(sr.snippet.strip()) >= 400 and sr.metadata.get("has_markdown"):
|
|
257
|
+
title = sr.title or sr.url
|
|
258
|
+
content = sr.snippet[:12000]
|
|
259
|
+
else:
|
|
260
|
+
title, content = _fetch_page_content(sr.url, query)
|
|
261
|
+
if not content and sr.snippet:
|
|
262
|
+
content = sr.snippet
|
|
263
|
+
if not title:
|
|
264
|
+
title = sr.title or sr.url
|
|
265
|
+
|
|
266
|
+
if len(content.strip()) < 40 and not sr.snippet:
|
|
267
|
+
continue
|
|
268
|
+
|
|
269
|
+
candidates.append((sr.url, title, content[:12000], backend.name))
|
|
270
|
+
|
|
271
|
+
if not candidates:
|
|
272
|
+
logger.info("retrieve_web_context('%s') → 0 candidates", query[:50])
|
|
273
|
+
return []
|
|
274
|
+
|
|
275
|
+
pages_for_rerank = [(url, title, content) for url, title, content, _ in candidates]
|
|
276
|
+
ranked = rerank_web_pages(query, pages_for_rerank)
|
|
277
|
+
score_by_url = {url: score for url, score in ranked}
|
|
278
|
+
|
|
279
|
+
hits: list[WebHit] = []
|
|
280
|
+
backend_by_url = {url: backend for url, _, _, backend in candidates}
|
|
281
|
+
for url, title, content, backend in candidates:
|
|
282
|
+
score = float(score_by_url.get(url, 0.0))
|
|
283
|
+
if score < min_score:
|
|
284
|
+
continue
|
|
285
|
+
hits.append(
|
|
286
|
+
WebHit(
|
|
287
|
+
url=url,
|
|
288
|
+
title=title,
|
|
289
|
+
content=content,
|
|
290
|
+
source_type="web_search",
|
|
291
|
+
score=score,
|
|
292
|
+
metadata={
|
|
293
|
+
"source_type": "web_search",
|
|
294
|
+
"canonical_url": url,
|
|
295
|
+
"web_source": backend_by_url.get(url, backend),
|
|
296
|
+
"confidence_gate": "escalated",
|
|
297
|
+
"local_top_score": local_top_score,
|
|
298
|
+
},
|
|
299
|
+
)
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
hits.sort(key=lambda h: h.score, reverse=True)
|
|
303
|
+
hits = hits[:max_pages]
|
|
304
|
+
|
|
305
|
+
elapsed = (perf_counter() - t0) * 1000
|
|
306
|
+
logger.info(
|
|
307
|
+
"retrieve_web_context('%s') → %d hits in %.0fms (backends=%s, gate=open)",
|
|
308
|
+
query[:50],
|
|
309
|
+
len(hits),
|
|
310
|
+
elapsed,
|
|
311
|
+
[b.name for b in backends],
|
|
312
|
+
)
|
|
313
|
+
return hits
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def format_web_block(hits: list[WebHit], *, max_chars: int = 6000) -> str:
|
|
317
|
+
"""Format web hits for LLM context injection."""
|
|
318
|
+
if not hits:
|
|
319
|
+
return ""
|
|
320
|
+
lines = ["—— Live web sources (Firecrawl / organic search) ——"]
|
|
321
|
+
used = 0
|
|
322
|
+
for hit in hits:
|
|
323
|
+
backend = hit.metadata.get("web_source", hit.source_type)
|
|
324
|
+
block = (
|
|
325
|
+
f"\n### [{backend}] {hit.title}\n"
|
|
326
|
+
f"URL: {hit.url}\n"
|
|
327
|
+
f"Relevance: {hit.score:.2f}\n\n"
|
|
328
|
+
f"{hit.content[:4000]}"
|
|
329
|
+
)
|
|
330
|
+
if used + len(block) > max_chars:
|
|
331
|
+
remain = max_chars - used - 80
|
|
332
|
+
if remain > 200:
|
|
333
|
+
lines.append(block[:remain] + "\n[...truncated]")
|
|
334
|
+
break
|
|
335
|
+
lines.append(block)
|
|
336
|
+
used += len(block)
|
|
337
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Post-generation validators for test case fidelity."""
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Post-generation checks: preserve gold testcase step count and avoid hollow expected results.
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from knowledge_base.generation.manual_tc_parser import ParsedStep
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_VAGUE_PHRASES = (
|
|
14
|
+
"works as expected",
|
|
15
|
+
"verify success",
|
|
16
|
+
"test passes",
|
|
17
|
+
"everything works",
|
|
18
|
+
"as expected.",
|
|
19
|
+
"proper behavior",
|
|
20
|
+
"configuration is correct",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def count_pytest_steps(python_code: str) -> int:
|
|
25
|
+
"""Count numbered step functions like `def TC001_step1` or `def QCL_4851_step2`."""
|
|
26
|
+
if not python_code:
|
|
27
|
+
return 0
|
|
28
|
+
# Primary: _stepN( before cleanup
|
|
29
|
+
n = len(re.findall(r"def\s+[A-Za-z0-9_]+_step\d+\s*\(", python_code))
|
|
30
|
+
if n:
|
|
31
|
+
return n
|
|
32
|
+
return len(re.findall(r"@allure\.step\(", python_code))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def validate_transform_fidelity(
|
|
36
|
+
parsed_steps: list,
|
|
37
|
+
python_code: str | None,
|
|
38
|
+
markdown: str | None,
|
|
39
|
+
) -> list[str]:
|
|
40
|
+
"""
|
|
41
|
+
For TRANSFORM mode: core steps in generated Python should match parsed manual step count.
|
|
42
|
+
Allows one extra step for cleanup pattern.
|
|
43
|
+
"""
|
|
44
|
+
warnings: list[str] = []
|
|
45
|
+
if not parsed_steps:
|
|
46
|
+
return warnings
|
|
47
|
+
|
|
48
|
+
target = len(parsed_steps)
|
|
49
|
+
if python_code:
|
|
50
|
+
got = count_pytest_steps(python_code)
|
|
51
|
+
if got == 0:
|
|
52
|
+
warnings.append("FIDELITY: No stepN functions detected in generated Python.")
|
|
53
|
+
elif got < target:
|
|
54
|
+
warnings.append(
|
|
55
|
+
f"FIDELITY: Generated {got} automation steps but manual TC has {target} — "
|
|
56
|
+
"possible dropped steps (review required)."
|
|
57
|
+
)
|
|
58
|
+
elif got > target + 1:
|
|
59
|
+
warnings.append(
|
|
60
|
+
f"FIDELITY: Generated {got} steps vs manual {target} — "
|
|
61
|
+
"possible invented steps (only +1 allowed for cleanup)."
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if markdown:
|
|
65
|
+
low = markdown.lower()
|
|
66
|
+
for phrase in _VAGUE_PHRASES:
|
|
67
|
+
if phrase in low:
|
|
68
|
+
warnings.append(f"FIDELITY: Markdown contains vague phrase: {phrase!r}")
|
|
69
|
+
break
|
|
70
|
+
|
|
71
|
+
return warnings
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def validate_expand_preservation(
|
|
75
|
+
parsed_steps: list,
|
|
76
|
+
markdown: str | None,
|
|
77
|
+
) -> list[str]:
|
|
78
|
+
"""Heuristic: original step titles should still appear in expanded markdown."""
|
|
79
|
+
out: list[str] = []
|
|
80
|
+
if not parsed_steps or not markdown:
|
|
81
|
+
return out
|
|
82
|
+
for s in parsed_steps[:20]:
|
|
83
|
+
title = (s.step_title or s.test_data or "")[:40].strip()
|
|
84
|
+
if len(title) > 12 and title.lower() not in markdown.lower():
|
|
85
|
+
out.append(
|
|
86
|
+
f"FIDELITY_EXPAND: Original step fragment not found in output: {title[:60]!r}…"
|
|
87
|
+
)
|
|
88
|
+
return out[:5]
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|