feat(rag): optimize RAG pipeline — JSON-Mode, CoT, Hybrid Search, Re-Ranking, Cross-Reg Dedup, chunk 1024
Some checks failed
CI/CD / go-lint (push) Has been skipped
CI/CD / python-lint (push) Has been skipped
CI/CD / nodejs-lint (push) Has been skipped
CI/CD / test-go-ai-compliance (push) Failing after 42s
CI/CD / test-python-backend-compliance (push) Successful in 1m38s
CI/CD / test-python-document-crawler (push) Successful in 20s
CI/CD / test-python-dsms-gateway (push) Successful in 17s
CI/CD / validate-canonical-controls (push) Successful in 10s
CI/CD / Deploy (push) Has been skipped
Some checks failed
CI/CD / go-lint (push) Has been skipped
CI/CD / python-lint (push) Has been skipped
CI/CD / nodejs-lint (push) Has been skipped
CI/CD / test-go-ai-compliance (push) Failing after 42s
CI/CD / test-python-backend-compliance (push) Successful in 1m38s
CI/CD / test-python-document-crawler (push) Successful in 20s
CI/CD / test-python-dsms-gateway (push) Successful in 17s
CI/CD / validate-canonical-controls (push) Successful in 10s
CI/CD / Deploy (push) Has been skipped
Phase 1 (LLM Quality): - Add format=json to all Ollama payloads (obligation_extractor, control_generator, citation_backfill) - Add Chain-of-Thought analysis steps to Pass 0a/0b system prompts Phase 2 (Retrieval Quality): - Hybrid search via Qdrant Query API with RRF fusion + automatic text index (legal_rag.go) - Fallback to dense-only search if Query API unavailable - Cross-encoder re-ranking with BGE Reranker v2 (RERANK_ENABLED=false by default) - CPU-only PyTorch dependency to keep Docker image small Phase 3 (Data Layer): - Cross-regulation dedup pass (threshold 0.95) links controls across regulations - DedupResult.link_type field distinguishes dedup_merge vs cross_regulation - Chunk size defaults updated 512/50 → 1024/128 for new ingestions only - Existing collections and controls are NOT affected Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -30,7 +30,7 @@ class TestLicenseMapping:
|
||||
def test_rule1_eu_law(self):
|
||||
info = _classify_regulation("eu_2016_679")
|
||||
assert info["rule"] == 1
|
||||
assert info["name"] == "DSGVO"
|
||||
assert "DSGVO" in info["name"]
|
||||
assert info["source_type"] == "law"
|
||||
|
||||
def test_rule1_nist(self):
|
||||
@@ -42,7 +42,7 @@ class TestLicenseMapping:
|
||||
def test_rule1_german_law(self):
|
||||
info = _classify_regulation("bdsg")
|
||||
assert info["rule"] == 1
|
||||
assert info["name"] == "BDSG"
|
||||
assert "BDSG" in info["name"]
|
||||
assert info["source_type"] == "law"
|
||||
|
||||
def test_rule2_owasp(self):
|
||||
@@ -199,7 +199,7 @@ class TestAnchorFinder:
|
||||
async def test_rag_anchor_search_filters_restricted(self):
|
||||
"""Only Rule 1+2 sources are returned as anchors."""
|
||||
mock_rag = AsyncMock()
|
||||
mock_rag.search.return_value = [
|
||||
mock_rag.search_with_rerank.return_value = [
|
||||
RAGSearchResult(
|
||||
text="OWASP requirement",
|
||||
regulation_code="owasp_asvs",
|
||||
@@ -231,7 +231,7 @@ class TestAnchorFinder:
|
||||
|
||||
# Only OWASP should be returned (Rule 2), BSI should be filtered out (Rule 3)
|
||||
assert len(anchors) == 1
|
||||
assert anchors[0].framework == "OWASP ASVS"
|
||||
assert "OWASP ASVS" in anchors[0].framework
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_web_search_identifies_frameworks(self):
|
||||
@@ -1668,3 +1668,36 @@ class TestApplicabilityFields:
|
||||
control = pipeline._build_control_from_json(data, "SEC")
|
||||
assert "applicable_industries" not in control.generation_metadata
|
||||
assert "applicable_company_size" not in control.generation_metadata
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Tests: Ollama JSON-Mode
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class TestOllamaJsonMode:
|
||||
"""Verify that control_generator Ollama payloads include format=json."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ollama_payload_contains_format_json(self):
|
||||
"""_llm_ollama must send format='json' in the request payload."""
|
||||
from compliance.services.control_generator import _llm_ollama
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_response.json.return_value = {
|
||||
"message": {"content": '{"test": true}'}
|
||||
}
|
||||
|
||||
with patch("compliance.services.control_generator.httpx.AsyncClient") as mock_cls:
|
||||
mock_client = AsyncMock()
|
||||
mock_client.post.return_value = mock_response
|
||||
mock_cls.return_value.__aenter__ = AsyncMock(return_value=mock_client)
|
||||
mock_cls.return_value.__aexit__ = AsyncMock(return_value=False)
|
||||
|
||||
await _llm_ollama("test prompt", "system prompt")
|
||||
|
||||
mock_client.post.assert_called_once()
|
||||
call_kwargs = mock_client.post.call_args
|
||||
payload = call_kwargs.kwargs.get("json") or call_kwargs[1].get("json")
|
||||
assert payload["format"] == "json"
|
||||
|
||||
Reference in New Issue
Block a user