Sesja 12: Warsztaty - System wyszukiwania i przetwarzania dokumentów

Praktyczna implementacja end-to-end

🎯 Cele warsztatów

  • Budowa kompletnego systemu wyszukiwania semantycznego
  • Integracja Azure Cognitive Search z przetwarzaniem dokumentów
  • Implementacja wyszukiwania hybrydowego (tekstowe + wektorowe)
  • Stworzenie interfejsu użytkownika dla systemu wyszukiwania

🏗️ Architektura systemu end-to-end

Kompletna platforma wyszukiwania

[PRZESYŁANIE DOKUMENTÓW] → [POTOK PRZETWARZANIA] → [INDEKS WYSZUKIWANIA] → [INTERFEJS UŻYTKOWNIKA]
              ↓                        ↓                        ↓                    ↓
    [BLOB STORAGE] → [USŁUGI KOGNITYWNE] → [BAZA WEKTOROWA] → [API WYSZUKIWANIA]
              ↓                        ↓                        ↓                    ↓
[ARCHIWUM] → [MONITOROWANIE] → [ANALITYKA] → [PANEL ADMINISTRATORA]

Kluczowe funkcje:

  1. Inteligentne przesyłanie - automatyczne wykrywanie typu, deduplikacja
  2. Zaawansowane przetwarzanie - OCR, analiza semantyczna, kategooryzacja
  3. Inteligentne indeksowanie - embeddingi semantyczne, pełnotekstowe
  4. Inteligentne wyszukiwanie - język naturalny, podobieństwo wektorowe

💻 Warsztat praktyczny

Implementacja kompletnego systemu

import asyncio
from azure.search.documents import SearchClient
from azure.search.documents.indexes import SearchIndexClient
from azure.search.documents.indexes.models import *
from azure.ai.formrecognizer import DocumentAnalysisClient
from sentence_transformers import SentenceTransformer

class IntelligentDocumentSearchSystem:
    def __init__(self, search_endpoint, form_recognizer_endpoint, storage_connection):
        self.search_client = SearchClient(search_endpoint, "documents", credential)
        self.index_client = SearchIndexClient(search_endpoint, credential)
        self.form_recognizer = DocumentAnalysisClient(form_recognizer_endpoint, credential)
        self.embedding_model = SentenceTransformer('all-MiniLM-L6-v2')
        self.storage_connection = storage_connection
        
    def create_search_index(self):
        """Utworzenie indeksu wyszukiwania z polami wektorowymi"""
        
        fields = [
            SimpleField(name="id", type=SearchFieldDataType.String, key=True),
            SearchableField(name="title", type=SearchFieldDataType.String),
            SearchableField(name="content", type=SearchFieldDataType.String),
            SearchableField(name="summary", type=SearchFieldDataType.String),
            SimpleField(name="document_type", type=SearchFieldDataType.String, filterable=True),
            SimpleField(name="upload_date", type=SearchFieldDataType.DateTimeOffset, sortable=True),
            SimpleField(name="file_size", type=SearchFieldDataType.Int64),
            SearchableField(
                name="contentVector", 
                type=SearchFieldDataType.Collection(SearchFieldDataType.Single),
                searchable=True, 
                vector_search_dimensions=384,  # dla all-MiniLM-L6-v2
                vector_search_profile_name="myHnswProfile"
            )
        ]
        
        # Konfiguracja wyszukiwania wektorowego
        vector_search = VectorSearch(
            profiles=[VectorSearchProfile(
                name="myHnswProfile",
                algorithm_configuration_name="myHnsw"
            )],
            algorithms=[HnswAlgorithmConfiguration(
                name="myHnsw",
                parameters=HnswParameters(
                    metric=VectorSearchAlgorithmMetric.COSINE,
                    m=4,
                    ef_construction=400,
                    ef_search=500
                )
            )]
        )
        
        # Konfiguracja semantic search
        semantic_search = SemanticSearch(
            configurations=[SemanticConfiguration(
                name="default",
                prioritized_fields=SemanticPrioritizedFields(
                    title_field=SemanticField(field_name="title"),
                    content_fields=[
                        SemanticField(field_name="content"),
                        SemanticField(field_name="summary")
                    ]
                )
            )]
        )
        
        index = SearchIndex(
            name="intelligent-documents",
            fields=fields,
            vector_search=vector_search,
            semantic_search=semantic_search
        )
        
        self.index_client.create_or_update_index(index)
        return index
    
    async def process_and_index_document(self, document_url, metadata=None):
        """Przetwarzanie i indeksowanie dokumentu"""
        
        try:
            # Krok 1: Analiza dokumentu z Form Recognizer
            analysis_result = await self._analyze_document_content(document_url)
            
            # Krok 2: Wydobywanie i czyszczenie tekstu
            clean_content = self._clean_extracted_text(analysis_result["content"])
            
            # Krok 3: Generowanie podsumowania
            summary = await self._generate_summary(clean_content)
            
            # Krok 4: Klasyfikacja typu dokumentu
            document_type = self._classify_document_type(analysis_result)
            
            # Krok 5: Generowanie embeddingów
            content_embedding = self.embedding_model.encode(clean_content).tolist()
            
            # Krok 6: Przygotowanie dokumentu do indeksowania
            search_document = {
                "id": self._generate_document_id(document_url),
                "title": self._extract_title(analysis_result, document_url),
                "content": clean_content,
                "summary": summary,
                "contentVector": content_embedding,
                "document_type": document_type,
                "upload_date": datetime.utcnow().isoformat(),
                "file_size": len(clean_content),
                "metadata": metadata or {}
            }
            
            # Krok 7: Indeksowanie
            result = await self.search_client.upload_documents([search_document])
            
            return {
                "document_id": search_document["id"],
                "status": "indexed",
                "document_type": document_type,
                "content_length": len(clean_content),
                "indexing_result": result
            }
            
        except Exception as e:
            return {
                "document_url": document_url,
                "status": "error",
                "error": str(e)
            }
    
    async def intelligent_search(self, query, search_options=None):
        """Inteligentne wyszukiwanie hybrydowe"""
        
        options = search_options or {}
        
        # Generowanie embedding zapytania
        query_vector = self.embedding_model.encode(query).tolist()
        
        # Konfiguracja wyszukiwania hybrydowego
        search_results = await self.search_client.search(
            search_text=query,
            vector_queries=[VectorizedQuery(
                vector=query_vector,
                k_nearest_neighbors=options.get("k", 10),
                fields="contentVector"
            )],
            query_type=QueryType.SEMANTIC,
            semantic_configuration_name="default",
            query_caption=QueryCaptionType.EXTRACTIVE,
            query_answer=QueryAnswerType.EXTRACTIVE,
            filter=options.get("filter"),
            order_by=options.get("order_by"),
            top=options.get("top", 10),
            include_total_count=True
        )
        
        # Formatowanie wyników
        formatted_results = []
        async for result in search_results:
            formatted_result = {
                "id": result["id"],
                "title": result["title"],
                "content_preview": result["content"][:200] + "...",
                "document_type": result["document_type"],
                "score": result["@search.score"],
                "highlights": result.get("@search.highlights", {}),
                "captions": result.get("@search.captions", []),
                "answers": result.get("@search.answers", [])
            }
            formatted_results.append(formatted_result)
        
        return {
            "query": query,
            "total_results": search_results.get_count(),
            "results": formatted_results,
            "search_type": "hybrid_semantic"
        }
    
    def create_search_interface(self):
        """Tworzenie prostego interfejsu wyszukiwania"""
        
        interface_code = '''
<!DOCTYPE html>
<html>
<head>
    <title>Intelligent Document Search</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; }
        .search-box { width: 100%; padding: 15px; font-size: 16px; border: 2px solid #ddd; border-radius: 8px; }
        .filters { margin: 20px 0; }
        .filter-group { display: inline-block; margin-right: 20px; }
        .results { margin-top: 30px; }
        .result-item { border: 1px solid #eee; padding: 20px; margin-bottom: 15px; border-radius: 8px; }
        .result-title { font-size: 18px; font-weight: bold; color: #0066cc; }
        .result-preview { margin: 10px 0; color: #555; }
        .result-meta { font-size: 12px; color: #888; }
        .highlight { background-color: yellow; }
        .loading { text-align: center; padding: 50px; }
    </style>
</head>
<body>
    <h1>🔍 Intelligent Document Search</h1>
    
    <div class="search-container">
        <input type="text" class="search-box" id="searchQuery" 
               placeholder="Wpisz zapytanie w języku naturalnym..." 
               onkeypress="handleKeyPress(event)">
        
        <div class="filters">
            <div class="filter-group">
                <label>Typ dokumentu:</label>
                <select id="documentType">
                    <option value="">Wszystkie</option>
                    <option value="invoice">Faktury</option>
                    <option value="contract">Umowy</option>
                    <option value="report">Raporty</option>
                    <option value="manual">Instrukcje</option>
                </select>
            </div>
            
            <div class="filter-group">
                <label>Data od:</label>
                <input type="date" id="dateFrom">
            </div>
            
            <div class="filter-group">
                <button onclick="performSearch()">🔍 Szukaj</button>
            </div>
        </div>
    </div>
    
    <div id="results" class="results"></div>
    
    <script>
        async function performSearch() {
            const query = document.getElementById('searchQuery').value;
            const documentType = document.getElementById('documentType').value;
            const dateFrom = document.getElementById('dateFrom').value;
            
            if (!query.trim()) return;
            
            document.getElementById('results').innerHTML = '<div class="loading">Wyszukiwanie...</div>';
            
            try {
                const response = await fetch('/api/search', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({
                        query: query,
                        filters: {
                            document_type: documentType,
                            date_from: dateFrom
                        }
                    })
                });
                
                const results = await response.json();
                displayResults(results);
                
            } catch (error) {
                document.getElementById('results').innerHTML = 
                    '<div style="color: red;">Błąd wyszukiwania: ' + error.message + '</div>';
            }
        }
        
        function displayResults(searchResults) {
            const resultsDiv = document.getElementById('results');
            
            if (!searchResults.results || searchResults.results.length === 0) {
                resultsDiv.innerHTML = '<div>Nie znaleziono dokumentów.</div>';
                return;
            }
            
            let html = `<div><strong>Znaleziono ${searchResults.total_results} wyników</strong></div>`;
            
            searchResults.results.forEach(result => {
                html += `
                    <div class="result-item">
                        <div class="result-title">${result.title}</div>
                        <div class="result-preview">${result.content_preview}</div>
                        <div class="result-meta">
                            Typ: ${result.document_type} | 
                            Trafność: ${Math.round(result.score * 100)}%
                        </div>
                    </div>
                `;
            });
            
            resultsDiv.innerHTML = html;
        }
        
        function handleKeyPress(event) {
            if (event.key === 'Enter') {
                performSearch();
            }
        }
    </script>
</body>
</html>
        '''
        
        return interface_code

🎯 Zadania warsztatowe

Projekt główny: Korporporacyjna baza wiedzy (120 min)

Scenariusz biznesowy: Firma ma 1000+ dokumentów w różnych formatach (PDF, Word, Excel, PowerPoint) i potrzebuje inteligentnego systemu wyszukiwania dla zespołów.

Wymagania:

  • Automatyczne przetwarzanie nowych dokumentów
  • Wyszukiwanie w języku naturalnym
  • Klasyfikacja i kategoryzacja
  • Interfejs web dla użytkowników końcowych

Implementacja krok po kroku:

Krok 1: Konfiguracja infrastruktury (30 min)

# Tworzenie zasobów Azure
az group create --name rg-document-search --location eastus

az search service create \
  --name doc-search-service \
  --resource-group rg-document-search \
  --sku standard

az cognitiveservices account create \
  --name form-recognizer-service \
  --resource-group rg-document-search \
  --kind FormRecognizer \
  --sku S0 \
  --location eastus

az storage account create \
  --name docstorage \
  --resource-group rg-document-search \
  --location eastus \
  --sku Standard_LRS

Krok 2: Przygotowanie danych (30 min)

  1. Przesłanie przykładowych dokumentów do Blob Storage
  2. Organizacja w folderach według typów
  3. Konfiguracja dostępu dla usług

Krok 3: Implementacja systemu (45 min)

  1. Stworzenie indeksu wyszukiwania
  2. Implementacja processingu dokumentów
  3. Batch processing dla istniejących dokumentów
  4. Testowanie wyszukiwania

Krok 4: Interface użytkownika (15 min)

  1. Deployment prostego interfejsu web
  2. Integracja z API wyszukiwania
  3. Testowanie end-to-end

Zadania dodatkowe

Zadanie 1: Optymalizacja wyszukiwania (30 min)

  • Dostrojenie parametrów wyszukiwania wektorowego
  • Konfiguracja semantic search
  • A/B testing różnych konfiguracji

Zadanie 2: Monitoring i analytics (20 min)

  • Implementacja logowania zapytań
  • Konfiguracja Application Insights
  • Dashboard z metrykami użytkowania

Zadanie 3: Scaling i performance (20 min)

  • Konfiguracja auto-scaling
  • Cache dla częstych zapytań
  • Optymalizacja kosztów

📊 Kryteria oceny projektu

Funkcjonalność (40 punktów)

  • System indeksuje różne typy dokumentów (10 pkt)
  • Wyszukiwanie hybrydowe działa poprawnie (15 pkt)
  • Interface użytkownika jest funkcjonalny (15 pkt)

Jakość techniczna (30 punktów)

  • Kod jest dobrze zorganizowany i udokumentowany (10 pkt)
  • Obsługa błędów i logging (10 pkt)
  • Wydajność i optymalizacja (10 pkt)

Innowacja i dodatkowe funkcje (20 punktów)

  • Dodatkowe filtry i opcje wyszukiwania (5 pkt)
  • Personalizacja wyników (5 pkt)
  • Dodatkowe integracje (5 pkt)
  • UI/UX improvements (5 pkt)

Prezentacja (10 punktów)

  • Demo działającego systemu (5 pkt)
  • Wyjaśnienie architektury i decyzji technicznych (5 pkt)

🏆 Rezultat warsztatów

Po ukończeniu warsztatów uczestnicy będą mieli:

  1. Działający system wyszukiwania - kompletna implementacja z rzeczywistymi dokumentami
  2. Praktyczna wiedza - doświadczenie z Azure Cognitive Search i Form Recognizer
  3. Gotowy kod - szablon do wykorzystania w przyszłych projektach
  4. Portfolio projekt - demonstracja umiejętności dla pracodawców

📚 Materiały dodatkowe

💡 Wskazówka

Każda sesja to 2 godziny intensywnej nauki z praktycznymi ćwiczeniami. Materiały można przeglądać w dowolnym tempie.

📈 Postęp

Śledź swój postęp w nauce AI i przygotowaniu do certyfikacji Azure AI-102. Każdy moduł buduje na poprzednim.