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,25 @@
|
|
|
1
|
+
"""TestRail 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 TestRailConnector(BaseConnector):
|
|
11
|
+
name = "testrail"
|
|
12
|
+
enabled = cfg.TESTRAIL_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,280 @@
|
|
|
1
|
+
"""Generic HTTP fetcher with in-memory cache for web retrieval."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import socket
|
|
7
|
+
import time
|
|
8
|
+
from ipaddress import ip_address
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from threading import Lock
|
|
11
|
+
from typing import Optional
|
|
12
|
+
from urllib.parse import quote_plus, urljoin, urlparse
|
|
13
|
+
|
|
14
|
+
from knowledge_base.config import cfg
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
_CACHE: dict[str, tuple[float, str]] = {}
|
|
19
|
+
_CACHE_LOCK = Lock()
|
|
20
|
+
_DEFAULT_HEADERS = {
|
|
21
|
+
"User-Agent": "IGEL-KB-Agent/1.0 (+https://kb.igel.com; internal-test-automation)",
|
|
22
|
+
"Accept": "text/html,application/xhtml+xml;q=0.9,*/*;q=0.8",
|
|
23
|
+
"Accept-Language": "en-US,en;q=0.9,de;q=0.8",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class FetchResult:
|
|
29
|
+
url: str
|
|
30
|
+
html: str
|
|
31
|
+
status_code: int
|
|
32
|
+
from_cache: bool = False
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def is_safe_fetch_target(url: str) -> bool:
|
|
36
|
+
"""Block unsafe SSRF targets unless explicitly enabled by config."""
|
|
37
|
+
parsed = urlparse(url)
|
|
38
|
+
if parsed.scheme not in ("http", "https"):
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
host = (parsed.hostname or "").strip().lower()
|
|
42
|
+
if not host:
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
allow_private = bool(getattr(cfg, "WEB_ALLOW_PRIVATE_HOSTS", False))
|
|
46
|
+
if host in {"localhost"} or host.endswith(".localhost") or host.endswith(".local"):
|
|
47
|
+
return allow_private
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
ip = ip_address(host)
|
|
51
|
+
except ValueError:
|
|
52
|
+
if allow_private:
|
|
53
|
+
return True
|
|
54
|
+
return _host_resolves_public_ips(host)
|
|
55
|
+
|
|
56
|
+
if allow_private:
|
|
57
|
+
return True
|
|
58
|
+
return _is_public_ip(ip)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _is_public_ip(ip) -> bool:
|
|
62
|
+
return not (
|
|
63
|
+
ip.is_private
|
|
64
|
+
or ip.is_loopback
|
|
65
|
+
or ip.is_link_local
|
|
66
|
+
or ip.is_multicast
|
|
67
|
+
or ip.is_reserved
|
|
68
|
+
or ip.is_unspecified
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _host_resolves_public_ips(host: str) -> bool:
|
|
73
|
+
"""Return True only when DNS answers resolve exclusively to public IPs."""
|
|
74
|
+
try:
|
|
75
|
+
infos = socket.getaddrinfo(host, None)
|
|
76
|
+
except Exception:
|
|
77
|
+
return False
|
|
78
|
+
if not infos:
|
|
79
|
+
return False
|
|
80
|
+
for info in infos:
|
|
81
|
+
addr = info[4][0]
|
|
82
|
+
try:
|
|
83
|
+
ip = ip_address(addr)
|
|
84
|
+
except ValueError:
|
|
85
|
+
return False
|
|
86
|
+
if not _is_public_ip(ip):
|
|
87
|
+
return False
|
|
88
|
+
return True
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _cache_get(url: str, ttl: int) -> Optional[str]:
|
|
92
|
+
with _CACHE_LOCK:
|
|
93
|
+
entry = _CACHE.get(url)
|
|
94
|
+
if not entry:
|
|
95
|
+
return None
|
|
96
|
+
ts, html = entry
|
|
97
|
+
if time.time() - ts > ttl:
|
|
98
|
+
_CACHE.pop(url, None)
|
|
99
|
+
return None
|
|
100
|
+
return html
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _cache_set(url: str, html: str) -> None:
|
|
104
|
+
with _CACHE_LOCK:
|
|
105
|
+
_CACHE[url] = (time.time(), html)
|
|
106
|
+
if len(_CACHE) > 256:
|
|
107
|
+
oldest = sorted(_CACHE.items(), key=lambda kv: kv[1][0])[:32]
|
|
108
|
+
for key, _ in oldest:
|
|
109
|
+
_CACHE.pop(key, None)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def strip_promoted_content(html: str, patterns: list[str] | tuple[str, ...] | None = None) -> str:
|
|
113
|
+
"""Remove ads, sponsored blocks, and promotional elements from HTML."""
|
|
114
|
+
if not html:
|
|
115
|
+
return html
|
|
116
|
+
try:
|
|
117
|
+
from bs4 import BeautifulSoup
|
|
118
|
+
except ImportError:
|
|
119
|
+
return html
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
soup = BeautifulSoup(html, "lxml")
|
|
123
|
+
except Exception:
|
|
124
|
+
soup = BeautifulSoup(html, "html.parser")
|
|
125
|
+
for tag in soup.find_all(["aside", "ins"]):
|
|
126
|
+
tag.decompose()
|
|
127
|
+
|
|
128
|
+
promo_patterns = list(patterns or getattr(cfg, "STRIP_PROMOTED_PATTERNS", ()))
|
|
129
|
+
for pattern in promo_patterns:
|
|
130
|
+
p = (pattern or "").strip().lower()
|
|
131
|
+
if not p:
|
|
132
|
+
continue
|
|
133
|
+
for el in soup.find_all(True):
|
|
134
|
+
classes = el.get("class") or []
|
|
135
|
+
class_str = " ".join(str(c) for c in classes).lower()
|
|
136
|
+
el_id = (el.get("id") or "").lower()
|
|
137
|
+
if p in class_str or p in el_id:
|
|
138
|
+
el.decompose()
|
|
139
|
+
return str(soup)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def fetch_url(
|
|
143
|
+
url: str,
|
|
144
|
+
*,
|
|
145
|
+
timeout: float = 20.0,
|
|
146
|
+
cache_ttl: int = 3600,
|
|
147
|
+
strip_promoted: bool = False,
|
|
148
|
+
) -> FetchResult:
|
|
149
|
+
"""Fetch a URL and return HTML using httpx with explicit safe-redirect handling."""
|
|
150
|
+
if not is_safe_fetch_target(url):
|
|
151
|
+
logger.warning("fetch_url blocked unsafe target: %s", url)
|
|
152
|
+
return FetchResult(url=url, html="", status_code=0)
|
|
153
|
+
|
|
154
|
+
cached = _cache_get(url, cache_ttl)
|
|
155
|
+
if cached is not None:
|
|
156
|
+
html = cached
|
|
157
|
+
if strip_promoted:
|
|
158
|
+
html = strip_promoted_content(html)
|
|
159
|
+
return FetchResult(url=url, html=html, status_code=200, from_cache=True)
|
|
160
|
+
|
|
161
|
+
html = ""
|
|
162
|
+
status = 0
|
|
163
|
+
try:
|
|
164
|
+
import httpx
|
|
165
|
+
|
|
166
|
+
with httpx.Client(follow_redirects=False, timeout=timeout, headers=_DEFAULT_HEADERS) as client:
|
|
167
|
+
current = url
|
|
168
|
+
max_redirects = 6
|
|
169
|
+
for _ in range(max_redirects + 1):
|
|
170
|
+
if not is_safe_fetch_target(current):
|
|
171
|
+
logger.warning("fetch_url blocked unsafe target: %s", current)
|
|
172
|
+
return FetchResult(url=url, html="", status_code=0)
|
|
173
|
+
resp = client.get(current)
|
|
174
|
+
if resp.is_redirect:
|
|
175
|
+
location = resp.headers.get("location")
|
|
176
|
+
if not location:
|
|
177
|
+
status = resp.status_code
|
|
178
|
+
html = resp.text
|
|
179
|
+
break
|
|
180
|
+
nxt = urljoin(current, location)
|
|
181
|
+
if not is_safe_fetch_target(nxt):
|
|
182
|
+
logger.warning("fetch_url blocked unsafe redirect target: %s -> %s", current, nxt)
|
|
183
|
+
return FetchResult(url=url, html="", status_code=0)
|
|
184
|
+
current = nxt
|
|
185
|
+
continue
|
|
186
|
+
status = resp.status_code
|
|
187
|
+
html = resp.text
|
|
188
|
+
break
|
|
189
|
+
else:
|
|
190
|
+
logger.warning("fetch_url exceeded redirect limit for %s", url)
|
|
191
|
+
return FetchResult(url=url, html="", status_code=0)
|
|
192
|
+
except ImportError:
|
|
193
|
+
logger.warning("httpx is required for fetch_url but is not installed")
|
|
194
|
+
return FetchResult(url=url, html="", status_code=0)
|
|
195
|
+
except Exception as e:
|
|
196
|
+
logger.warning("fetch_url failed for %s: %s", url, e)
|
|
197
|
+
return FetchResult(url=url, html="", status_code=status or 0)
|
|
198
|
+
|
|
199
|
+
if status == 200 and html:
|
|
200
|
+
if strip_promoted:
|
|
201
|
+
html = strip_promoted_content(html)
|
|
202
|
+
_cache_set(url, html)
|
|
203
|
+
return FetchResult(url=url, html=html, status_code=status)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def search_confluence_site(base_url: str, query: str, *, limit: int = 5) -> list[str]:
|
|
207
|
+
"""
|
|
208
|
+
Search a Confluence/Scroll Sites KB (e.g. kb.igel.com) via REST API.
|
|
209
|
+
Returns list of page URLs.
|
|
210
|
+
"""
|
|
211
|
+
base = base_url.rstrip("/")
|
|
212
|
+
cql = f'siteSearch ~ "{query.replace(chr(34), " ")}" AND type = page'
|
|
213
|
+
api = f"{base}/rest/api/content/search?cql={quote_plus(cql)}&limit={limit}"
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
import httpx
|
|
217
|
+
|
|
218
|
+
with httpx.Client(follow_redirects=False, timeout=20.0, headers=_DEFAULT_HEADERS) as client:
|
|
219
|
+
current = api
|
|
220
|
+
data = None
|
|
221
|
+
max_redirects = 6
|
|
222
|
+
for _ in range(max_redirects + 1):
|
|
223
|
+
if not is_safe_fetch_target(current):
|
|
224
|
+
logger.warning("Confluence search blocked unsafe target: %s", current)
|
|
225
|
+
return _fallback_search_urls(base, query, limit=limit)
|
|
226
|
+
resp = client.get(current)
|
|
227
|
+
if resp.is_redirect:
|
|
228
|
+
location = resp.headers.get("location")
|
|
229
|
+
if not location:
|
|
230
|
+
break
|
|
231
|
+
nxt = urljoin(current, location)
|
|
232
|
+
if not is_safe_fetch_target(nxt):
|
|
233
|
+
logger.warning("Confluence search blocked unsafe redirect target: %s -> %s", current, nxt)
|
|
234
|
+
return _fallback_search_urls(base, query, limit=limit)
|
|
235
|
+
current = nxt
|
|
236
|
+
continue
|
|
237
|
+
|
|
238
|
+
if resp.status_code != 200:
|
|
239
|
+
logger.debug("Confluence search HTTP %s for %s", resp.status_code, current)
|
|
240
|
+
return _fallback_search_urls(base, query, limit=limit)
|
|
241
|
+
data = resp.json()
|
|
242
|
+
break
|
|
243
|
+
|
|
244
|
+
if data is None:
|
|
245
|
+
logger.debug("Confluence search redirect resolution failed for %s", api)
|
|
246
|
+
return _fallback_search_urls(base, query, limit=limit)
|
|
247
|
+
except Exception as e:
|
|
248
|
+
logger.debug("Confluence search failed: %s", e)
|
|
249
|
+
return _fallback_search_urls(base, query, limit=limit)
|
|
250
|
+
|
|
251
|
+
urls: list[str] = []
|
|
252
|
+
for item in data.get("results", []):
|
|
253
|
+
links = item.get("_links", {})
|
|
254
|
+
webui = links.get("webui") or links.get("tinyui")
|
|
255
|
+
if webui:
|
|
256
|
+
urls.append(urljoin(base + "/", webui.lstrip("/")))
|
|
257
|
+
elif item.get("id"):
|
|
258
|
+
urls.append(f"{base}/pages/viewpage.action?pageId={item['id']}")
|
|
259
|
+
return urls[:limit]
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _fallback_search_urls(base_url: str, query: str, *, limit: int) -> list[str]:
|
|
263
|
+
"""Construct likely KB URLs from query keywords when REST search is unavailable."""
|
|
264
|
+
base = base_url.rstrip("/")
|
|
265
|
+
keywords = [w.lower() for w in query.split() if len(w) > 3][:4]
|
|
266
|
+
paths = [
|
|
267
|
+
"/en/igel-os-base-system",
|
|
268
|
+
"/en/universal-management-suite",
|
|
269
|
+
"/en/igel-cloud-gateway",
|
|
270
|
+
"/en/security-safety",
|
|
271
|
+
"/en/how-to-start-with-igel",
|
|
272
|
+
]
|
|
273
|
+
urls: list[str] = []
|
|
274
|
+
for kw in keywords:
|
|
275
|
+
for path in paths:
|
|
276
|
+
if kw in path.replace("-", " "):
|
|
277
|
+
urls.append(f"{base}{path}")
|
|
278
|
+
if not urls:
|
|
279
|
+
urls.append(f"{base}/search?query={quote_plus(query)}")
|
|
280
|
+
return urls[:limit]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Query/embedding cache with optional Redis backend."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
import threading
|
|
9
|
+
import time
|
|
10
|
+
from functools import lru_cache
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
_redis_client = None
|
|
15
|
+
_redis_lock = threading.Lock()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _redis():
|
|
19
|
+
global _redis_client
|
|
20
|
+
if _redis_client is not None:
|
|
21
|
+
return _redis_client
|
|
22
|
+
with _redis_lock:
|
|
23
|
+
if _redis_client is not None:
|
|
24
|
+
return _redis_client
|
|
25
|
+
try:
|
|
26
|
+
from knowledge_base.config import cfg
|
|
27
|
+
if not getattr(cfg, "REDIS_URL", ""):
|
|
28
|
+
return None
|
|
29
|
+
import redis
|
|
30
|
+
_redis_client = redis.from_url(cfg.REDIS_URL, decode_responses=True)
|
|
31
|
+
_redis_client.ping()
|
|
32
|
+
return _redis_client
|
|
33
|
+
except Exception as exc:
|
|
34
|
+
logger.debug("Redis unavailable: %s", exc)
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _key(prefix: str, payload: str) -> str:
|
|
39
|
+
h = hashlib.sha256(payload.encode()).hexdigest()[:24]
|
|
40
|
+
return f"igel:kb:{prefix}:{h}"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def cache_get(prefix: str, payload: str) -> str | None:
|
|
44
|
+
r = _redis()
|
|
45
|
+
if not r:
|
|
46
|
+
return None
|
|
47
|
+
try:
|
|
48
|
+
return r.get(_key(prefix, payload))
|
|
49
|
+
except Exception:
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def cache_set(prefix: str, payload: str, value: str, ttl: int) -> None:
|
|
54
|
+
r = _redis()
|
|
55
|
+
if not r:
|
|
56
|
+
return
|
|
57
|
+
try:
|
|
58
|
+
r.setex(_key(prefix, payload), ttl, value)
|
|
59
|
+
except Exception:
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@lru_cache(maxsize=512)
|
|
64
|
+
def embed_query_cached(query: str) -> tuple[float, ...] | None:
|
|
65
|
+
"""In-process LRU for embedding vectors (serialized as tuple)."""
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def store_embedding_cache(query: str, vector: list[float]) -> None:
|
|
70
|
+
embed_query_cached.cache_clear()
|
|
71
|
+
# LRU handled by caller storing in Redis for cross-process
|
|
72
|
+
payload = json.dumps(vector)
|
|
73
|
+
from knowledge_base.config import cfg
|
|
74
|
+
cache_set("embed", query.strip().lower(), payload, getattr(cfg, "EMBED_CACHE_TTL", 3600))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def get_embedding_cache(query: str) -> list[float] | None:
|
|
78
|
+
raw = cache_get("embed", query.strip().lower())
|
|
79
|
+
if not raw:
|
|
80
|
+
return None
|
|
81
|
+
try:
|
|
82
|
+
return json.loads(raw)
|
|
83
|
+
except Exception:
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_retrieval_cache(key: str) -> dict | None:
|
|
88
|
+
raw = cache_get("retrieve", key)
|
|
89
|
+
if not raw:
|
|
90
|
+
return None
|
|
91
|
+
try:
|
|
92
|
+
return json.loads(raw)
|
|
93
|
+
except Exception:
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def set_retrieval_cache(key: str, data: dict, ttl: int = 300) -> None:
|
|
98
|
+
cache_set("retrieve", key, json.dumps(data, default=str), ttl)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""MCP circuit breaker for external connectors."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
import threading
|
|
7
|
+
|
|
8
|
+
_lock = threading.Lock()
|
|
9
|
+
_failures: dict[str, int] = {}
|
|
10
|
+
_disabled_until: dict[str, float] = {}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def record_failure(source: str, *, threshold: int = 3, cooldown_sec: int = 300) -> None:
|
|
14
|
+
with _lock:
|
|
15
|
+
_failures[source] = _failures.get(source, 0) + 1
|
|
16
|
+
if _failures[source] >= threshold:
|
|
17
|
+
_disabled_until[source] = time.time() + cooldown_sec
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def record_success(source: str) -> None:
|
|
21
|
+
with _lock:
|
|
22
|
+
_failures[source] = 0
|
|
23
|
+
_disabled_until.pop(source, None)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def is_open(source: str) -> bool:
|
|
27
|
+
with _lock:
|
|
28
|
+
until = _disabled_until.get(source)
|
|
29
|
+
if until and time.time() < until:
|
|
30
|
+
return True
|
|
31
|
+
if until:
|
|
32
|
+
_disabled_until.pop(source, None)
|
|
33
|
+
return False
|