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,170 @@
|
|
|
1
|
+
"""Live query bridge — MCP / external connectors at query time."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from time import perf_counter
|
|
8
|
+
|
|
9
|
+
from knowledge_base.config import cfg
|
|
10
|
+
from knowledge_base.connectors.base import BaseConnector
|
|
11
|
+
from knowledge_base.core.circuit_breaker import is_open, record_failure, record_success
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class ExternalHit:
|
|
18
|
+
source: str
|
|
19
|
+
title: str
|
|
20
|
+
url: str
|
|
21
|
+
content: str
|
|
22
|
+
score: float = 0.5
|
|
23
|
+
source_type: str = "external"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class ExternalContext:
|
|
28
|
+
hits: list[ExternalHit] = field(default_factory=list)
|
|
29
|
+
block: str = ""
|
|
30
|
+
sources_queried: list[str] = field(default_factory=list)
|
|
31
|
+
timing_ms: float = 0.0
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _mcp_sources() -> list[str]:
|
|
35
|
+
raw = getattr(cfg, "QUERY_MCP_SOURCES", ("jira", "confluence"))
|
|
36
|
+
if isinstance(raw, str):
|
|
37
|
+
return [s.strip().lower() for s in raw.split(",") if s.strip()]
|
|
38
|
+
return [s.strip().lower() for s in raw if s.strip()]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _connector_for(name: str):
|
|
42
|
+
# Keep this registry aligned with sync_scheduler CONNECTORS.
|
|
43
|
+
if name == "jira":
|
|
44
|
+
from knowledge_base.connectors.jira_connector import JiraConnector
|
|
45
|
+
return JiraConnector()
|
|
46
|
+
if name == "confluence":
|
|
47
|
+
from knowledge_base.connectors.confluence_connector import ConfluenceConnector
|
|
48
|
+
return ConfluenceConnector()
|
|
49
|
+
if name == "bitbucket":
|
|
50
|
+
from knowledge_base.connectors.bitbucket_connector import BitbucketConnector
|
|
51
|
+
return BitbucketConnector()
|
|
52
|
+
if name == "teamcity":
|
|
53
|
+
from knowledge_base.connectors.teamcity_connector import TeamCityConnector
|
|
54
|
+
return TeamCityConnector()
|
|
55
|
+
if name == "testrail":
|
|
56
|
+
from knowledge_base.connectors.testRail_connector import TestRailConnector
|
|
57
|
+
return TestRailConnector()
|
|
58
|
+
if name == "servicenow":
|
|
59
|
+
from knowledge_base.connectors.serviceNow_connector import ServiceNowConnector
|
|
60
|
+
return ServiceNowConnector()
|
|
61
|
+
if name == "kb_igel":
|
|
62
|
+
from knowledge_base.connectors.kb_igel_connector import KbIgelConnector
|
|
63
|
+
return KbIgelConnector()
|
|
64
|
+
if name == "community":
|
|
65
|
+
from knowledge_base.connectors.community_connector import CommunityConnector
|
|
66
|
+
return CommunityConnector()
|
|
67
|
+
logger.warning("Unsupported MCP source configured: %s", name)
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _has_query_impl(connector: BaseConnector) -> bool:
|
|
72
|
+
"""True when connector overrides query-time methods instead of base no-op stubs."""
|
|
73
|
+
return (
|
|
74
|
+
connector.__class__.search is not BaseConnector.search
|
|
75
|
+
or connector.__class__.fetch_page is not BaseConnector.fetch_page
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def query_mcp_sources(
|
|
80
|
+
query: str,
|
|
81
|
+
*,
|
|
82
|
+
max_hits: int = 3,
|
|
83
|
+
timeout_seconds: float | None = None,
|
|
84
|
+
) -> ExternalContext:
|
|
85
|
+
if not getattr(cfg, "QUERY_EXTERNAL_ENABLED", True) or not query.strip():
|
|
86
|
+
return ExternalContext()
|
|
87
|
+
|
|
88
|
+
hits: list[ExternalHit] = []
|
|
89
|
+
queried: list[str] = []
|
|
90
|
+
t0 = perf_counter()
|
|
91
|
+
deadline = perf_counter() + timeout_seconds if timeout_seconds and timeout_seconds > 0 else None
|
|
92
|
+
|
|
93
|
+
for name in _mcp_sources():
|
|
94
|
+
if deadline is not None and perf_counter() >= deadline:
|
|
95
|
+
logger.warning("MCP query budget exhausted before source=%s", name)
|
|
96
|
+
break
|
|
97
|
+
if len(hits) >= max_hits:
|
|
98
|
+
break
|
|
99
|
+
if is_open(name):
|
|
100
|
+
continue
|
|
101
|
+
connector = _connector_for(name)
|
|
102
|
+
if connector is None or not getattr(connector, "enabled", False):
|
|
103
|
+
continue
|
|
104
|
+
if not _has_query_impl(connector):
|
|
105
|
+
continue
|
|
106
|
+
queried.append(name)
|
|
107
|
+
try:
|
|
108
|
+
source_success = False
|
|
109
|
+
remaining = None
|
|
110
|
+
if deadline is not None:
|
|
111
|
+
remaining = max(0.05, deadline - perf_counter())
|
|
112
|
+
if remaining <= 0:
|
|
113
|
+
logger.warning("MCP query budget exhausted before search for source=%s", name)
|
|
114
|
+
break
|
|
115
|
+
|
|
116
|
+
urls = connector.search(query, limit=max_hits, timeout=remaining)
|
|
117
|
+
if urls:
|
|
118
|
+
for url in urls[:max_hits]:
|
|
119
|
+
if deadline is not None and perf_counter() >= deadline:
|
|
120
|
+
logger.warning("MCP query budget exhausted while processing source=%s", name)
|
|
121
|
+
break
|
|
122
|
+
if len(hits) >= max_hits:
|
|
123
|
+
break
|
|
124
|
+
remaining = None
|
|
125
|
+
if deadline is not None:
|
|
126
|
+
remaining = max(0.05, deadline - perf_counter())
|
|
127
|
+
if remaining <= 0:
|
|
128
|
+
logger.warning("MCP query budget exhausted before fetch_page for source=%s", name)
|
|
129
|
+
break
|
|
130
|
+
|
|
131
|
+
page = connector.fetch_page(url, timeout=remaining)
|
|
132
|
+
title = url
|
|
133
|
+
if page:
|
|
134
|
+
content = (page.get("raw_text") or page.get("content") or "").strip()[:6000]
|
|
135
|
+
title = page.get("title") or url
|
|
136
|
+
if content:
|
|
137
|
+
source_success = True
|
|
138
|
+
hits.append(ExternalHit(
|
|
139
|
+
source=name,
|
|
140
|
+
title=title,
|
|
141
|
+
url=url,
|
|
142
|
+
content=content,
|
|
143
|
+
source_type=page.get("source_type", name),
|
|
144
|
+
))
|
|
145
|
+
elif not urls:
|
|
146
|
+
logger.debug("MCP source %s returned no URLs", name)
|
|
147
|
+
|
|
148
|
+
if source_success:
|
|
149
|
+
record_success(name)
|
|
150
|
+
except Exception as e:
|
|
151
|
+
record_failure(name)
|
|
152
|
+
logger.warning("MCP source %s query failed: %s", name, e)
|
|
153
|
+
|
|
154
|
+
block = _format_block(hits)
|
|
155
|
+
elapsed = (perf_counter() - t0) * 1000
|
|
156
|
+
return ExternalContext(hits=hits, block=block, sources_queried=queried, timing_ms=elapsed)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _format_block(hits: list[ExternalHit], *, max_chars: int = 4000) -> str:
|
|
160
|
+
if not hits:
|
|
161
|
+
return ""
|
|
162
|
+
lines = ["—— MCP / external connector sources ——"]
|
|
163
|
+
used = 0
|
|
164
|
+
for hit in hits:
|
|
165
|
+
block = f"\n### [{hit.source}] {hit.title}\nURL: {hit.url}\n{hit.content[:2500]}"
|
|
166
|
+
if used + len(block) > max_chars:
|
|
167
|
+
break
|
|
168
|
+
lines.append(block)
|
|
169
|
+
used += len(block)
|
|
170
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"""Free/open-source web search backends for Stage 2 live web retrieval."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Any
|
|
9
|
+
from urllib.parse import quote_plus, urlparse
|
|
10
|
+
|
|
11
|
+
from knowledge_base.config import cfg
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class WebSearchResult:
|
|
18
|
+
url: str
|
|
19
|
+
title: str
|
|
20
|
+
snippet: str = ""
|
|
21
|
+
backend: str = ""
|
|
22
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SearchBackend(ABC):
|
|
26
|
+
name: str = "base"
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def search(
|
|
30
|
+
self,
|
|
31
|
+
query: str,
|
|
32
|
+
*,
|
|
33
|
+
max_results: int = 5,
|
|
34
|
+
include_domains: tuple[str, ...] | None = None,
|
|
35
|
+
exclude_domains: tuple[str, ...] | None = None,
|
|
36
|
+
) -> list[WebSearchResult]:
|
|
37
|
+
raise NotImplementedError
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _normalize_url(url: str) -> str:
|
|
41
|
+
parsed = urlparse(url.strip())
|
|
42
|
+
path = parsed.path.rstrip("/") or "/"
|
|
43
|
+
return f"{parsed.scheme}://{parsed.netloc.lower()}{path}"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _host_matches(host: str, pattern: str) -> bool:
|
|
47
|
+
p = pattern.strip().lower().lstrip(".")
|
|
48
|
+
if not p:
|
|
49
|
+
return False
|
|
50
|
+
return host == p or host.endswith("." + p)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _domain_allowed(
|
|
54
|
+
url: str,
|
|
55
|
+
include_domains: tuple[str, ...] | None,
|
|
56
|
+
exclude_domains: tuple[str, ...] | None,
|
|
57
|
+
) -> bool:
|
|
58
|
+
host = urlparse(url).netloc.lower()
|
|
59
|
+
if not host:
|
|
60
|
+
return False
|
|
61
|
+
if exclude_domains:
|
|
62
|
+
for pattern in exclude_domains:
|
|
63
|
+
if _host_matches(host, pattern):
|
|
64
|
+
return False
|
|
65
|
+
if include_domains:
|
|
66
|
+
return any(_host_matches(host, p) for p in include_domains if p.strip())
|
|
67
|
+
return True
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class DuckDuckGoBackend(SearchBackend):
|
|
71
|
+
"""Organic web search via duckduckgo-search / ddgs (free, no API key)."""
|
|
72
|
+
|
|
73
|
+
name = "ddgs"
|
|
74
|
+
|
|
75
|
+
def search(
|
|
76
|
+
self,
|
|
77
|
+
query: str,
|
|
78
|
+
*,
|
|
79
|
+
max_results: int = 5,
|
|
80
|
+
include_domains: tuple[str, ...] | None = None,
|
|
81
|
+
exclude_domains: tuple[str, ...] | None = None,
|
|
82
|
+
) -> list[WebSearchResult]:
|
|
83
|
+
if not query.strip():
|
|
84
|
+
return []
|
|
85
|
+
|
|
86
|
+
backend = getattr(cfg, "DDGS_BACKEND", "auto")
|
|
87
|
+
region = getattr(cfg, "DDGS_REGION", "wt-wt")
|
|
88
|
+
fetch_n = max(max_results * 3, max_results + 2)
|
|
89
|
+
|
|
90
|
+
raw_rows: list[dict[str, str]] = []
|
|
91
|
+
backends_to_try = [backend] if backend not in ("auto", "") else ["api", "html", "lite"]
|
|
92
|
+
last_err: Exception | None = None
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
try:
|
|
96
|
+
from ddgs import DDGS
|
|
97
|
+
except ImportError:
|
|
98
|
+
from duckduckgo_search import DDGS
|
|
99
|
+
|
|
100
|
+
ddgs = DDGS()
|
|
101
|
+
search_fn = getattr(ddgs, "text", None)
|
|
102
|
+
if search_fn is None:
|
|
103
|
+
return []
|
|
104
|
+
|
|
105
|
+
for be in backends_to_try:
|
|
106
|
+
try:
|
|
107
|
+
try:
|
|
108
|
+
raw_rows = list(
|
|
109
|
+
search_fn(
|
|
110
|
+
query,
|
|
111
|
+
region=region,
|
|
112
|
+
safesearch="moderate",
|
|
113
|
+
max_results=fetch_n,
|
|
114
|
+
backend=be,
|
|
115
|
+
)
|
|
116
|
+
or []
|
|
117
|
+
)
|
|
118
|
+
except TypeError:
|
|
119
|
+
raw_rows = list(
|
|
120
|
+
search_fn(
|
|
121
|
+
query,
|
|
122
|
+
region=region,
|
|
123
|
+
safesearch="moderate",
|
|
124
|
+
max_results=fetch_n,
|
|
125
|
+
)
|
|
126
|
+
or []
|
|
127
|
+
)
|
|
128
|
+
if raw_rows:
|
|
129
|
+
break
|
|
130
|
+
except Exception as e:
|
|
131
|
+
last_err = e
|
|
132
|
+
logger.debug("DuckDuckGo backend=%s failed: %s", be, e)
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
if not raw_rows and last_err is not None:
|
|
136
|
+
logger.warning("DuckDuckGo search failed after retries: %s", last_err)
|
|
137
|
+
return []
|
|
138
|
+
except Exception as e:
|
|
139
|
+
logger.warning("DuckDuckGo search failed: %s", e)
|
|
140
|
+
return []
|
|
141
|
+
|
|
142
|
+
results: list[WebSearchResult] = []
|
|
143
|
+
seen: set[str] = set()
|
|
144
|
+
for row in raw_rows:
|
|
145
|
+
url = (row.get("href") or row.get("url") or "").strip()
|
|
146
|
+
if not url or not url.startswith("http"):
|
|
147
|
+
continue
|
|
148
|
+
norm = _normalize_url(url)
|
|
149
|
+
if norm in seen:
|
|
150
|
+
continue
|
|
151
|
+
if not _domain_allowed(url, include_domains, exclude_domains):
|
|
152
|
+
continue
|
|
153
|
+
seen.add(norm)
|
|
154
|
+
results.append(
|
|
155
|
+
WebSearchResult(
|
|
156
|
+
url=url,
|
|
157
|
+
title=(row.get("title") or url)[:300],
|
|
158
|
+
snippet=(row.get("body") or row.get("snippet") or "")[:2000],
|
|
159
|
+
backend=self.name,
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
if len(results) >= max_results:
|
|
163
|
+
break
|
|
164
|
+
logger.info("DuckDuckGoBackend: %d results for %r", len(results), query[:60])
|
|
165
|
+
return results
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class SearXNGBackend(SearchBackend):
|
|
169
|
+
"""Self-hosted SearXNG metasearch (free, configurable, no promoted results)."""
|
|
170
|
+
|
|
171
|
+
name = "searxng"
|
|
172
|
+
|
|
173
|
+
def __init__(self) -> None:
|
|
174
|
+
self.base_url = (getattr(cfg, "SEARXNG_BASE_URL", "") or "").rstrip("/")
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def enabled(self) -> bool:
|
|
178
|
+
return bool(self.base_url)
|
|
179
|
+
|
|
180
|
+
def search(
|
|
181
|
+
self,
|
|
182
|
+
query: str,
|
|
183
|
+
*,
|
|
184
|
+
max_results: int = 5,
|
|
185
|
+
include_domains: tuple[str, ...] | None = None,
|
|
186
|
+
exclude_domains: tuple[str, ...] | None = None,
|
|
187
|
+
) -> list[WebSearchResult]:
|
|
188
|
+
if not self.enabled or not query.strip():
|
|
189
|
+
return []
|
|
190
|
+
|
|
191
|
+
api = f"{self.base_url}/search?q={quote_plus(query)}&format=json"
|
|
192
|
+
try:
|
|
193
|
+
import httpx
|
|
194
|
+
|
|
195
|
+
with httpx.Client(follow_redirects=True, timeout=20.0) as client:
|
|
196
|
+
resp = client.get(api)
|
|
197
|
+
if resp.status_code != 200:
|
|
198
|
+
logger.warning("SearXNG HTTP %s for %s", resp.status_code, api)
|
|
199
|
+
return []
|
|
200
|
+
data = resp.json()
|
|
201
|
+
except Exception as e:
|
|
202
|
+
logger.warning("SearXNG search failed: %s", e)
|
|
203
|
+
return []
|
|
204
|
+
|
|
205
|
+
results: list[WebSearchResult] = []
|
|
206
|
+
seen: set[str] = set()
|
|
207
|
+
for item in data.get("results", []):
|
|
208
|
+
url = (item.get("url") or "").strip()
|
|
209
|
+
if not url.startswith("http"):
|
|
210
|
+
continue
|
|
211
|
+
norm = _normalize_url(url)
|
|
212
|
+
if norm in seen:
|
|
213
|
+
continue
|
|
214
|
+
if not _domain_allowed(url, include_domains, exclude_domains):
|
|
215
|
+
continue
|
|
216
|
+
seen.add(norm)
|
|
217
|
+
results.append(
|
|
218
|
+
WebSearchResult(
|
|
219
|
+
url=url,
|
|
220
|
+
title=(item.get("title") or url)[:300],
|
|
221
|
+
snippet=(item.get("content") or item.get("snippet") or "")[:2000],
|
|
222
|
+
backend=self.name,
|
|
223
|
+
)
|
|
224
|
+
)
|
|
225
|
+
if len(results) >= max_results:
|
|
226
|
+
break
|
|
227
|
+
logger.info("SearXNGBackend: %d results for %r", len(results), query[:60])
|
|
228
|
+
return results
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class FirecrawlBackend(SearchBackend):
|
|
232
|
+
"""Web search via self-hosted Firecrawl (search + optional markdown in results)."""
|
|
233
|
+
|
|
234
|
+
name = "firecrawl"
|
|
235
|
+
|
|
236
|
+
def __init__(self) -> None:
|
|
237
|
+
self.base_url = (getattr(cfg, "FIRECRAWL_BASE_URL", "") or "").rstrip("/")
|
|
238
|
+
self.api_key = getattr(cfg, "FIRECRAWL_API_KEY", "") or ""
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def enabled(self) -> bool:
|
|
242
|
+
return bool(self.base_url)
|
|
243
|
+
|
|
244
|
+
def _search_sdk(self, query: str, max_results: int) -> list[dict[str, Any]]:
|
|
245
|
+
from firecrawl import Firecrawl
|
|
246
|
+
|
|
247
|
+
init_kwargs: dict[str, Any] = {}
|
|
248
|
+
if self.api_key:
|
|
249
|
+
init_kwargs["api_key"] = self.api_key
|
|
250
|
+
|
|
251
|
+
app = None
|
|
252
|
+
for param in ("api_url", "base_url"):
|
|
253
|
+
try:
|
|
254
|
+
app = Firecrawl(**{param: self.base_url, **init_kwargs})
|
|
255
|
+
break
|
|
256
|
+
except TypeError:
|
|
257
|
+
continue
|
|
258
|
+
if app is None:
|
|
259
|
+
app = Firecrawl(**init_kwargs)
|
|
260
|
+
|
|
261
|
+
resp = app.search(query, limit=max_results)
|
|
262
|
+
if isinstance(resp, dict):
|
|
263
|
+
data = resp.get("data") or resp.get("results") or []
|
|
264
|
+
elif isinstance(resp, list):
|
|
265
|
+
data = resp
|
|
266
|
+
else:
|
|
267
|
+
data = getattr(resp, "data", None) or []
|
|
268
|
+
return list(data or [])
|
|
269
|
+
|
|
270
|
+
def _search_rest(self, query: str, max_results: int) -> list[dict[str, Any]]:
|
|
271
|
+
import httpx
|
|
272
|
+
|
|
273
|
+
url = f"{self.base_url}/v1/search"
|
|
274
|
+
headers = {"Content-Type": "application/json"}
|
|
275
|
+
if self.api_key:
|
|
276
|
+
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
277
|
+
payload = {"query": query, "limit": max_results}
|
|
278
|
+
with httpx.Client(follow_redirects=True, timeout=60.0) as client:
|
|
279
|
+
resp = client.post(url, json=payload, headers=headers)
|
|
280
|
+
if resp.status_code != 200:
|
|
281
|
+
logger.warning("Firecrawl search HTTP %s: %s", resp.status_code, resp.text[:200])
|
|
282
|
+
return []
|
|
283
|
+
body = resp.json()
|
|
284
|
+
if isinstance(body, dict):
|
|
285
|
+
return list(body.get("data") or body.get("results") or [])
|
|
286
|
+
return []
|
|
287
|
+
|
|
288
|
+
def search(
|
|
289
|
+
self,
|
|
290
|
+
query: str,
|
|
291
|
+
*,
|
|
292
|
+
max_results: int = 5,
|
|
293
|
+
include_domains: tuple[str, ...] | None = None,
|
|
294
|
+
exclude_domains: tuple[str, ...] | None = None,
|
|
295
|
+
) -> list[WebSearchResult]:
|
|
296
|
+
if not self.enabled or not query.strip():
|
|
297
|
+
return []
|
|
298
|
+
|
|
299
|
+
raw_items: list[dict[str, Any]] = []
|
|
300
|
+
try:
|
|
301
|
+
raw_items = self._search_sdk(query, max_results=max_results)
|
|
302
|
+
except ImportError:
|
|
303
|
+
logger.debug("firecrawl-py not installed — using REST search")
|
|
304
|
+
except Exception as e:
|
|
305
|
+
logger.debug("Firecrawl SDK search failed: %s — trying REST", e)
|
|
306
|
+
|
|
307
|
+
if not raw_items:
|
|
308
|
+
try:
|
|
309
|
+
raw_items = self._search_rest(query, max_results=max_results)
|
|
310
|
+
except Exception as e:
|
|
311
|
+
logger.warning("Firecrawl REST search failed: %s", e)
|
|
312
|
+
return []
|
|
313
|
+
|
|
314
|
+
results: list[WebSearchResult] = []
|
|
315
|
+
seen: set[str] = set()
|
|
316
|
+
for item in raw_items:
|
|
317
|
+
url = (item.get("url") or item.get("link") or "").strip()
|
|
318
|
+
if not url.startswith("http"):
|
|
319
|
+
continue
|
|
320
|
+
norm = _normalize_url(url)
|
|
321
|
+
if norm in seen:
|
|
322
|
+
continue
|
|
323
|
+
if not _domain_allowed(url, include_domains, exclude_domains):
|
|
324
|
+
continue
|
|
325
|
+
seen.add(norm)
|
|
326
|
+
snippet = (
|
|
327
|
+
item.get("markdown")
|
|
328
|
+
or item.get("description")
|
|
329
|
+
or item.get("snippet")
|
|
330
|
+
or item.get("content")
|
|
331
|
+
or ""
|
|
332
|
+
)
|
|
333
|
+
results.append(
|
|
334
|
+
WebSearchResult(
|
|
335
|
+
url=url,
|
|
336
|
+
title=(item.get("title") or url)[:300],
|
|
337
|
+
snippet=str(snippet)[:8000],
|
|
338
|
+
backend=self.name,
|
|
339
|
+
metadata={"has_markdown": bool(item.get("markdown"))},
|
|
340
|
+
)
|
|
341
|
+
)
|
|
342
|
+
if len(results) >= max_results:
|
|
343
|
+
break
|
|
344
|
+
|
|
345
|
+
logger.info("FirecrawlBackend: %d results for %r", len(results), query[:60])
|
|
346
|
+
return results
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def get_configured_backends() -> list[SearchBackend]:
|
|
350
|
+
"""Return enabled search backends from WEB_SEARCH_BACKENDS config."""
|
|
351
|
+
raw = getattr(cfg, "WEB_SEARCH_BACKENDS", ("ddgs",))
|
|
352
|
+
names = {n.strip().lower() for n in raw if n.strip()}
|
|
353
|
+
backends: list[SearchBackend] = []
|
|
354
|
+
if "firecrawl" in names:
|
|
355
|
+
fc = FirecrawlBackend()
|
|
356
|
+
if fc.enabled:
|
|
357
|
+
backends.append(fc)
|
|
358
|
+
else:
|
|
359
|
+
logger.debug("Firecrawl configured but FIRECRAWL_BASE_URL is empty — skipping")
|
|
360
|
+
if "ddgs" in names or "duckduckgo" in names:
|
|
361
|
+
backends.append(DuckDuckGoBackend())
|
|
362
|
+
if "searxng" in names:
|
|
363
|
+
sx = SearXNGBackend()
|
|
364
|
+
if sx.enabled:
|
|
365
|
+
backends.append(sx)
|
|
366
|
+
else:
|
|
367
|
+
logger.debug("SearXNG configured but SEARXNG_BASE_URL is empty — skipping")
|
|
368
|
+
return backends
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""ServiceNow connector — Phase 8 stub."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
from knowledge_base.config import cfg
|
|
7
|
+
from knowledge_base.connectors.base import BaseConnector
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ServiceNowConnector(BaseConnector):
|
|
11
|
+
name = "servicenow"
|
|
12
|
+
enabled = cfg.SERVICENOW_ENABLED
|
|
13
|
+
supports_sync = False
|
|
14
|
+
|
|
15
|
+
def connect(self) -> bool:
|
|
16
|
+
return self.enabled
|
|
17
|
+
|
|
18
|
+
def sync(self, since: datetime | None = None) -> list[dict]:
|
|
19
|
+
return []
|
|
20
|
+
|
|
21
|
+
def ingest_to_kg(self, items: list[dict]) -> int:
|
|
22
|
+
return 0
|
|
23
|
+
|
|
24
|
+
def ingest_to_rag(self, items: list[dict]) -> int:
|
|
25
|
+
return 0
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Connector sync scheduler with incremental state and daemon mode."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import logging
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
|
|
11
|
+
from knowledge_base.connectors.bitbucket_connector import BitbucketConnector
|
|
12
|
+
from knowledge_base.connectors.community_connector import CommunityConnector
|
|
13
|
+
from knowledge_base.connectors.confluence_connector import ConfluenceConnector
|
|
14
|
+
from knowledge_base.connectors.jira_connector import JiraConnector
|
|
15
|
+
from knowledge_base.connectors.kb_igel_connector import KbIgelConnector
|
|
16
|
+
from knowledge_base.connectors.serviceNow_connector import ServiceNowConnector
|
|
17
|
+
from knowledge_base.connectors.teamcity_connector import TeamCityConnector
|
|
18
|
+
from knowledge_base.connectors.testRail_connector import TestRailConnector
|
|
19
|
+
from knowledge_base.db.client import init_pool
|
|
20
|
+
from knowledge_base.db.connector_sync import get_last_sync, update_sync_state
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
CONNECTORS = {
|
|
25
|
+
"jira": JiraConnector,
|
|
26
|
+
"confluence": ConfluenceConnector,
|
|
27
|
+
"bitbucket": BitbucketConnector,
|
|
28
|
+
"teamcity": TeamCityConnector,
|
|
29
|
+
"testrail": TestRailConnector,
|
|
30
|
+
"servicenow": ServiceNowConnector,
|
|
31
|
+
"kb_igel": KbIgelConnector,
|
|
32
|
+
"community": CommunityConnector,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def sync_source(name: str, *, dry_run: bool = False) -> dict:
|
|
37
|
+
cls = CONNECTORS.get(name)
|
|
38
|
+
if not cls:
|
|
39
|
+
raise ValueError(f"Unknown source: {name}")
|
|
40
|
+
connector = cls()
|
|
41
|
+
if not connector.enabled:
|
|
42
|
+
return {"source": name, "status": "disabled", "items": 0}
|
|
43
|
+
if not getattr(connector, "supports_sync", True):
|
|
44
|
+
return {"source": name, "status": "not_implemented", "items": 0}
|
|
45
|
+
|
|
46
|
+
if not connector.connect():
|
|
47
|
+
update_sync_state(name, status="connection_failed", advance_timestamp=False)
|
|
48
|
+
return {"source": name, "status": "connection_failed", "items": 0}
|
|
49
|
+
|
|
50
|
+
since = get_last_sync(name)
|
|
51
|
+
items = connector.sync(since=since)
|
|
52
|
+
if dry_run:
|
|
53
|
+
return {"source": name, "status": "dry_run", "items": len(items), "since": str(since)}
|
|
54
|
+
|
|
55
|
+
kg_count = connector.ingest_to_kg(items)
|
|
56
|
+
rag_count = connector.ingest_to_rag(items)
|
|
57
|
+
update_sync_state(name, status="synced")
|
|
58
|
+
return {"source": name, "status": "synced", "items": len(items), "kg": kg_count, "rag": rag_count}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def sync_all(*, dry_run: bool = False) -> list[dict]:
|
|
62
|
+
results = []
|
|
63
|
+
for name in CONNECTORS:
|
|
64
|
+
try:
|
|
65
|
+
results.append(sync_source(name, dry_run=dry_run))
|
|
66
|
+
except Exception as e:
|
|
67
|
+
update_sync_state(name, status="error", advance_timestamp=False)
|
|
68
|
+
results.append({"source": name, "status": "error", "error": str(e)})
|
|
69
|
+
return results
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def main() -> int:
|
|
73
|
+
logging.basicConfig(level=logging.INFO)
|
|
74
|
+
parser = argparse.ArgumentParser(description="IGEL KB connector sync scheduler")
|
|
75
|
+
parser.add_argument("--source", default="all", help="Connector name or 'all'")
|
|
76
|
+
parser.add_argument("--dry-run", action="store_true")
|
|
77
|
+
parser.add_argument("--daemon", action="store_true", help="Run sync loop")
|
|
78
|
+
parser.add_argument("--interval", type=int, default=3600, help="Daemon interval seconds")
|
|
79
|
+
args = parser.parse_args()
|
|
80
|
+
|
|
81
|
+
if args.interval <= 0:
|
|
82
|
+
parser.error("--interval must be a positive integer")
|
|
83
|
+
|
|
84
|
+
init_pool()
|
|
85
|
+
|
|
86
|
+
def _run_once():
|
|
87
|
+
if args.source == "all":
|
|
88
|
+
return sync_all(dry_run=args.dry_run)
|
|
89
|
+
return [sync_source(args.source, dry_run=args.dry_run)]
|
|
90
|
+
|
|
91
|
+
if args.daemon:
|
|
92
|
+
while True:
|
|
93
|
+
try:
|
|
94
|
+
rows = _run_once()
|
|
95
|
+
except Exception as e:
|
|
96
|
+
logger.exception("sync daemon cycle failed for source=%s: %s", args.source, e)
|
|
97
|
+
if args.source != "all" and args.source in CONNECTORS:
|
|
98
|
+
update_sync_state(args.source, status="error", advance_timestamp=False)
|
|
99
|
+
rows = [{"source": args.source, "status": "error", "error": str(e)}]
|
|
100
|
+
for r in rows:
|
|
101
|
+
print(r)
|
|
102
|
+
time.sleep(args.interval)
|
|
103
|
+
return 0
|
|
104
|
+
|
|
105
|
+
for r in _run_once():
|
|
106
|
+
print(r)
|
|
107
|
+
return 0
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
if __name__ == "__main__":
|
|
111
|
+
sys.exit(main())
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""TeamCity connector — Phase 8 stub."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
from knowledge_base.config import cfg
|
|
7
|
+
from knowledge_base.connectors.base import BaseConnector
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TeamCityConnector(BaseConnector):
|
|
11
|
+
name = "teamcity"
|
|
12
|
+
enabled = cfg.TEAMCITY_ENABLED
|
|
13
|
+
supports_sync = False
|
|
14
|
+
|
|
15
|
+
def connect(self) -> bool:
|
|
16
|
+
return self.enabled
|
|
17
|
+
|
|
18
|
+
def sync(self, since: datetime | None = None) -> list[dict]:
|
|
19
|
+
return []
|
|
20
|
+
|
|
21
|
+
def ingest_to_kg(self, items: list[dict]) -> int:
|
|
22
|
+
return 0
|
|
23
|
+
|
|
24
|
+
def ingest_to_rag(self, items: list[dict]) -> int:
|
|
25
|
+
return 0
|