Fix critical bugs from Deep Dive Audit (Phase 2)
**Porque foi feita essa alteração?**
Resolução de bugs críticos identificados na Segunda Passagem de Auditoria (Deep Dive):
1. 'rag_pipeline.py': Correção de SyntaxError (await em função síncrona) convertendo pipeline de ingestão para async.
2. 'pipeline.py': Remoção de campos duplicados na instanciação de 'AuditLog' que causavam erro de sintaxe/lógica.
3. 'zabbix_connector.py': Correção de query N+1 em 'get_neighbor_alerts' e adição de import faltante 'time'.
4. 'test_rag_pipeline.py': Atualização dos testes para suportar async e mocking correto do 'OllamaClient'.
**Quais testes foram feitos?**
- 'py_compile': Verificação de sintaxe em todos os arquivos modificados.
- 'flake8': Verificação de linting (apenas warnings de whitespace ignorados).
- 'pytest':
- 'tests/test_rag_pipeline.py': Passou (13 testes).
- 'tests/test_pipeline.py': Passou (6 testes).
- 'tests/test_zabbix.py': Passou (9 testes).
**A alteração gerou um novo teste que precisa ser implementado no pipeline de testes?**
Sim, os testes do 'rag_pipeline' foram modernizados para 'asyncio' e devem ser mantidos no CI.
This commit is contained in:
parent
f69b990fa5
commit
3ad3161519
|
|
@ -108,8 +108,9 @@ Este documento serve como o roteiro técnico detalhado para a implementação do
|
||||||
- [x] **Baixa:** Validação dinâmica de domínios em `validators.py`
|
- [x] **Baixa:** Validação dinâmica de domínios em `validators.py`
|
||||||
- [ ] **Refinamento e Correção:**
|
- [ ] **Refinamento e Correção:**
|
||||||
- [x] Verificar todas as alterações
|
- [x] Verificar todas as alterações
|
||||||
- [ ] **Segunda Passagem de Auditoria (Deep Dive)**:
|
- [x] **Segunda Passagem de Auditoria (Deep Dive)**:
|
||||||
- [ ] Análise de regressão e pontos cegos pós-correção
|
- [x] Análise de regressão e pontos cegos pós-correção
|
||||||
|
- Resultado: [AUDIT_DEEP_DIVE.md](file:///C:/Users/joao.goncalves/.gemini/antigravity/brain/0ae8ff87-2359-49bb-951c-6f6c593ee5db/AUDIT_DEEP_DIVE.md)
|
||||||
- [ ] Validar ausência de regressões
|
- [ ] Validar ausência de regressões
|
||||||
|
|
||||||
## Fase 7: Homologação e Go-Live 🔄
|
## Fase 7: Homologação e Go-Live 🔄
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ from typing import Optional
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
from src.agents.triage_agent import TriageAgent, TriageResult, get_triage_agent
|
from src.agents.triage_agent import TriageResult, get_triage_agent
|
||||||
from src.agents.specialist_agent import SpecialistAgent, SpecialistResponse, get_specialist_agent
|
from src.agents.specialist_agent import SpecialistResponse, get_specialist_agent
|
||||||
from src.database.connection import get_db_manager
|
from src.database.connection import get_db_manager
|
||||||
from src.models import AuditLog, ResolutionStatus, TicketContext
|
from src.models import AuditLog, ResolutionStatus, TicketContext
|
||||||
from src.security import sanitize_text
|
from src.security import sanitize_text
|
||||||
|
|
@ -276,9 +276,6 @@ class TicketPipeline:
|
||||||
|
|
||||||
# Create log
|
# Create log
|
||||||
return AuditLog(
|
return AuditLog(
|
||||||
ticket_id=ticket_id,
|
|
||||||
tenant_id=triage.tenant.id if triage.tenant else "UNKNOWN",
|
|
||||||
sender_email=sanitize_text(sender_email),
|
|
||||||
ticket_id=ticket_id,
|
ticket_id=ticket_id,
|
||||||
tenant_id=triage.tenant.id if triage.tenant else "UNKNOWN",
|
tenant_id=triage.tenant.id if triage.tenant else "UNKNOWN",
|
||||||
sender_email=sanitize_text(sender_email),
|
sender_email=sanitize_text(sender_email),
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@ infrastructure diagnostics and root cause analysis.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional, Any
|
import time
|
||||||
|
from typing import Optional
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from zabbix_utils import ZabbixAPI
|
from zabbix_utils import ZabbixAPI
|
||||||
|
|
@ -284,8 +285,7 @@ class ZabbixConnector:
|
||||||
if not neighbor_ids:
|
if not neighbor_ids:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# Get problems for neighbor hosts
|
# Get problems for neighbor hosts (using selectHosts to avoid N+1)
|
||||||
import time
|
|
||||||
time_from = int(time.time()) - (time_window_minutes * 60)
|
time_from = int(time.time()) - (time_window_minutes * 60)
|
||||||
|
|
||||||
problems = self._api.problem.get(
|
problems = self._api.problem.get(
|
||||||
|
|
@ -294,13 +294,17 @@ class ZabbixConnector:
|
||||||
recent=True,
|
recent=True,
|
||||||
sortfield="eventid",
|
sortfield="eventid",
|
||||||
sortorder="DESC",
|
sortorder="DESC",
|
||||||
|
selectHosts=["hostid", "host"], # Fetch host info in same query
|
||||||
output=["eventid", "objectid", "severity", "name",
|
output=["eventid", "objectid", "severity", "name",
|
||||||
"acknowledged", "clock"]
|
"acknowledged", "clock"]
|
||||||
)
|
)
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
for p in problems:
|
for p in problems:
|
||||||
host_info = self._get_host_for_trigger(p.get("objectid"))
|
# Extract host info from payload (no extra API call)
|
||||||
|
hosts = p.get("hosts", [])
|
||||||
|
host_info = hosts[0] if hosts else {}
|
||||||
|
|
||||||
result.append(Problem(
|
result.append(Problem(
|
||||||
event_id=p["eventid"],
|
event_id=p["eventid"],
|
||||||
host_id=host_info.get("hostid", ""),
|
host_id=host_info.get("hostid", ""),
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ Processes Markdown and PDF documents, extracts text,
|
||||||
generates embeddings and indexes in Qdrant.
|
generates embeddings and indexes in Qdrant.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
import re
|
||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
|
|
@ -94,7 +94,7 @@ class RAGIngestionPipeline:
|
||||||
self._qdrant = get_qdrant_client()
|
self._qdrant = get_qdrant_client()
|
||||||
self._ollama = get_ollama_client()
|
self._ollama = get_ollama_client()
|
||||||
|
|
||||||
def ingest_directory(
|
async def ingest_directory(
|
||||||
self,
|
self,
|
||||||
directory: str,
|
directory: str,
|
||||||
tenant_id: str,
|
tenant_id: str,
|
||||||
|
|
@ -137,7 +137,7 @@ class RAGIngestionPipeline:
|
||||||
logger.info(f"Found {len(files)} documents in {directory}")
|
logger.info(f"Found {len(files)} documents in {directory}")
|
||||||
|
|
||||||
for filepath in files:
|
for filepath in files:
|
||||||
result = self.ingest_file(
|
result = await self.ingest_file(
|
||||||
filepath=str(filepath),
|
filepath=str(filepath),
|
||||||
tenant_id=tenant_id,
|
tenant_id=tenant_id,
|
||||||
doc_type=doc_type
|
doc_type=doc_type
|
||||||
|
|
@ -146,7 +146,7 @@ class RAGIngestionPipeline:
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def ingest_file(
|
async def ingest_file(
|
||||||
self,
|
self,
|
||||||
filepath: str,
|
filepath: str,
|
||||||
tenant_id: str,
|
tenant_id: str,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import pytest
|
||||||
import tempfile
|
import tempfile
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest.mock import Mock, patch
|
from unittest.mock import Mock, patch, AsyncMock
|
||||||
|
|
||||||
from src.flywheel.rag_pipeline import (
|
from src.flywheel.rag_pipeline import (
|
||||||
RAGIngestionPipeline,
|
RAGIngestionPipeline,
|
||||||
|
|
@ -42,10 +42,16 @@ class TestRAGPipeline:
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def pipeline(self):
|
def pipeline(self):
|
||||||
"""Create pipeline with mocked Qdrant."""
|
"""Create pipeline with mocked Qdrant and Ollama."""
|
||||||
with patch('src.flywheel.rag_pipeline.get_qdrant_client') as mock:
|
with patch('src.flywheel.rag_pipeline.get_qdrant_client') as mock_qdrant, \
|
||||||
mock.return_value = Mock()
|
patch('src.flywheel.rag_pipeline.get_ollama_client') as mock_ollama:
|
||||||
mock.return_value.upsert_document = Mock(return_value=True)
|
mock_qdrant.return_value = Mock()
|
||||||
|
mock_qdrant.return_value.upsert_document = Mock(return_value=True)
|
||||||
|
|
||||||
|
# Mock Ollama client for embeddings
|
||||||
|
mock_ollama.return_value = Mock()
|
||||||
|
mock_ollama.return_value.get_embeddings = AsyncMock(return_value=[0.1] * 384)
|
||||||
|
|
||||||
return RAGIngestionPipeline()
|
return RAGIngestionPipeline()
|
||||||
|
|
||||||
def test_sanitize_removes_scripts(self, pipeline):
|
def test_sanitize_removes_scripts(self, pipeline):
|
||||||
|
|
@ -120,16 +126,19 @@ class TestRAGPipeline:
|
||||||
assert id1 != id2
|
assert id1 != id2
|
||||||
assert id1 == id3 # Same inputs should give same ID
|
assert id1 == id3 # Same inputs should give same ID
|
||||||
|
|
||||||
def test_generate_embedding(self, pipeline):
|
@pytest.mark.asyncio
|
||||||
"""Test embedding generation."""
|
async def test_generate_embedding(self, pipeline):
|
||||||
emb = pipeline._generate_embedding("test content")
|
"""Test embedding generation via Ollama."""
|
||||||
|
emb = await pipeline._generate_embedding("test content")
|
||||||
|
|
||||||
assert len(emb) == 384
|
# Embedding should be returned from mock
|
||||||
assert all(-1 <= v <= 1 for v in emb)
|
assert isinstance(emb, list)
|
||||||
|
assert len(emb) > 0
|
||||||
|
|
||||||
def test_ingest_file_not_found(self, pipeline):
|
@pytest.mark.asyncio
|
||||||
|
async def test_ingest_file_not_found(self, pipeline):
|
||||||
"""Test ingestion of non-existent file."""
|
"""Test ingestion of non-existent file."""
|
||||||
result = pipeline.ingest_file(
|
result = await pipeline.ingest_file(
|
||||||
filepath="/nonexistent/file.md",
|
filepath="/nonexistent/file.md",
|
||||||
tenant_id="tenant-001"
|
tenant_id="tenant-001"
|
||||||
)
|
)
|
||||||
|
|
@ -137,14 +146,15 @@ class TestRAGPipeline:
|
||||||
assert result.success is False
|
assert result.success is False
|
||||||
assert "not found" in result.error.lower()
|
assert "not found" in result.error.lower()
|
||||||
|
|
||||||
def test_ingest_file_success(self, pipeline):
|
@pytest.mark.asyncio
|
||||||
|
async def test_ingest_file_success(self, pipeline):
|
||||||
"""Test successful file ingestion."""
|
"""Test successful file ingestion."""
|
||||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f:
|
with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f:
|
||||||
f.write("# Test Document\n\nThis is a test about Linux servers.")
|
f.write("# Test Document\n\nThis is a test about Linux servers.")
|
||||||
filepath = f.name
|
filepath = f.name
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = pipeline.ingest_file(
|
result = await pipeline.ingest_file(
|
||||||
filepath=filepath,
|
filepath=filepath,
|
||||||
tenant_id="tenant-001"
|
tenant_id="tenant-001"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue