# edu-search-service Spezialisierter Suchdienst für deutsche Bildungsinhalte - eine Alternative zu Tavily, optimiert für den deutschen Bildungssektor. ## Übersicht Der edu-search-service crawlt, extrahiert und indiziert Bildungsinhalte von deutschen Bildungsquellen (Kultusministerien, Bildungsserver, wissenschaftliche Studien, etc.) und stellt eine Such-API bereit. ### Features - **BM25 Keyword-Suche** mit German Analyzer (OpenSearch) - **Semantic Search** mit Embeddings (OpenAI oder Ollama) - **Hybrid Search** kombiniert BM25 + Vektor-Ähnlichkeit - **Automatisches Tagging** für Dokumenttyp, Fächer, Schulstufe, Bundesland - **Trust-Score** basierend auf Domain-Reputation und Content-Qualität - **Rate-Limited Crawler** mit robots.txt Respekt - **Admin API** für Seed-Verwaltung und Crawl-Steuerung ## Architektur ``` ┌─────────────────────────────────────────────────────────────────────┐ │ edu-search-service │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────┐ ┌───────────┐ ┌────────┐ ┌─────────┐ │ │ │ Crawler │───▶│ Extractor │───▶│ Tagger │───▶│ Indexer │ │ │ └─────────┘ └───────────┘ └────────┘ └─────────┘ │ │ │ │ │ │ ▼ ▼ │ │ ┌─────────┐ ┌────────────┐ │ │ │ Seeds │ │ OpenSearch │ │ │ └─────────┘ └────────────┘ │ │ │ │ │ ┌────────────┐ │ │ │ │ Search API │◀──────────────────┘ │ │ └────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ ``` ## Komponenten ### Crawler (`internal/crawler/`) - Rate-Limited HTTP Client (Standard: 0.2 req/sec pro Domain) - Denylist-Support für ungewünschte Domains - **Seeds aus Backend-API** (primär) oder lokale Seed-Files (Fallback) - URL-Normalisierung und Deduplication - Seed-Metadaten: Trust-Boost, Crawl-Tiefe, Kategorie, Bundesland - **Crawl-Status-Feedback** an Backend (Dokumentenzahl, Dauer, Fehler) ### Robots (`internal/robots/`) - **robots.txt Parser** mit Caching (24h TTL) - Unterstützt `Disallow`, `Allow`, `Crawl-delay` - Wildcard-Patterns (`*`) und End-Anchors (`$`) - User-Agent-spezifische Regeln - Leniente Behandlung bei fehlenden robots.txt ### Extractor (`internal/extractor/`) - HTML-Extraktion mit goquery - **PDF-Textextraktion** mit ledongthuc/pdf Bibliothek - `ExtractPDF()` - Standard-Extraktion mit GetPlainText - `ExtractPDFWithMetadata()` - Seiten-weise Extraktion für mehr Kontrolle - Fallback-Extraktion bei beschädigten PDFs - Automatische Titel-Erkennung (erste signifikante Zeile) - Heading-Erkennung (All-Caps, nummerierte Zeilen) - Metadaten-Extraktion (og:title, description, etc.) - Content-Feature-Berechnung (Ad-Density, Link-Density) - Sprach-Erkennung (Deutsch/Englisch) ### Tagger (`internal/tagger/`) - Regelbasiertes Tagging via YAML-Konfiguration - DocType-Erkennung (Lehrplan, Arbeitsblatt, Studie, etc.) - Fächer-Erkennung (Mathematik, Deutsch, etc.) - Schulstufen-Erkennung (Grundschule, Sek I/II, etc.) - Bundesland-Erkennung aus URL-Patterns - Trust-Score-Berechnung ### Quality (`internal/quality/`) - **Multi-Faktor Quality-Score** (0-1) - Content Length (20%) - Heading Structure (15%) - Link/Ad Quality (15%) - Text-to-HTML Ratio (15%) - Metadata Presence (10%) - Language Clarity (10%) - Content Freshness (10%) - PDF-Specific Signals (5%) - Konfigurierbare Gewichtungen - Date-Indicator-Extraktion für Frische-Bewertung ### Indexer (`internal/indexer/`) - OpenSearch 2.11 Client - German Analyzer für BM25 - Bulk-Indexierung - Custom Mapping für Bildungsdokumente ### Search (`internal/search/`) - Multi-Match Query mit Boosting - Filter für alle Taxonomie-Felder - Function-Score mit Trust/Quality-Boosting - Highlighting-Support - **Drei Suchmodi:** - `keyword` - Klassische BM25-Suche (Default) - `semantic` - Reine Vektor-Ähnlichkeitssuche (k-NN) - `hybrid` - Kombination aus BM25 und Vektor-Score ### Embedding (`internal/embedding/`) - **OpenAI Provider** - `text-embedding-3-small` (1536 Dimensionen) - **Ollama Provider** - Lokale Modelle (z.B. `nomic-embed-text`, 384-768 Dim.) - Batch-Embedding für effiziente Indexierung - Automatische Text-Kürzung (max. 30.000 Zeichen) ### Scheduler (`internal/scheduler/`) - **Automatisches Crawling** in konfigurierbaren Intervallen - Default: täglich um 2:00 Uhr (minimale Auswirkung) - Manuelles Triggern via Admin-API - Status-Tracking (letzter Lauf, nächster Lauf, Ergebnis) ## API Endpoints ### Public Endpoints | Method | Endpoint | Beschreibung | |--------|----------|--------------| | GET | `/v1/health` | Health Check (kein Auth) | | POST | `/v1/search` | Suche ausführen | | GET | `/v1/document` | Einzeldokument abrufen | ### Admin Endpoints (Auth erforderlich) | Method | Endpoint | Beschreibung | |--------|----------|--------------| | GET | `/v1/admin/seeds` | Alle Seeds abrufen | | POST | `/v1/admin/seeds` | Neuen Seed erstellen | | PUT | `/v1/admin/seeds/:id` | Seed aktualisieren | | DELETE | `/v1/admin/seeds/:id` | Seed löschen | | GET | `/v1/admin/stats` | Crawl-Statistiken | | POST | `/v1/admin/crawl/start` | Crawl starten | ## API Dokumentation ### POST /v1/search **Request Body:** ```json { "q": "Lehrplan Mathematik Gymnasium", "mode": "keyword", "limit": 10, "offset": 0, "filters": { "language": ["de"], "doc_type": ["Lehrplan"], "school_level": ["Gymnasium"], "state": ["BY", "NW"], "subjects": ["Mathematik"], "min_trust_score": 0.5 }, "include": { "snippets": true, "highlights": true } } ``` **Such-Modi (`mode`):** | Mode | Beschreibung | |------|--------------| | `keyword` | BM25-Textsuche (Default) | | `semantic` | Vektor-Ähnlichkeitssuche via Embeddings | | `hybrid` | Kombination: 70% BM25 + 30% Vektor-Score | > **Hinweis:** `semantic` und `hybrid` Modi erfordern `SEMANTIC_SEARCH_ENABLED=true` und konfigurierte Embedding-Provider. **Response:** ```json { "query_id": "q-12345", "results": [ { "doc_id": "uuid-...", "title": "Lehrplan Mathematik Gymnasium Bayern", "url": "https://www.isb.bayern.de/...", "domain": "isb.bayern.de", "language": "de", "doc_type": "Lehrplan", "school_level": "Gymnasium", "subjects": ["Mathematik"], "scores": { "bm25": 12.5, "trust": 0.85, "quality": 0.9, "final": 10.6 }, "snippet": "Der Lehrplan für das Fach Mathematik...", "highlights": ["Lehrplan für das Fach Mathematik..."] } ], "pagination": { "limit": 10, "offset": 0, "total_estimate": 156 } } ``` ### Filter-Optionen | Filter | Werte | |--------|-------| | `language` | `de`, `en` | | `doc_type` | `Lehrplan`, `Arbeitsblatt`, `Unterrichtsentwurf`, `Erlass_Verordnung`, `Pruefung_Abitur`, `Studie_Bericht`, `Sonstiges` | | `school_level` | `Grundschule`, `Sek_I`, `Gymnasium`, `Berufsschule`, `Hochschule`, `Alle`, `NA` | | `state` | `BW`, `BY`, `BE`, `BB`, `HB`, `HH`, `HE`, `MV`, `NI`, `NW`, `RP`, `SL`, `SN`, `ST`, `SH`, `TH` | | `subjects` | `Mathematik`, `Deutsch`, `Englisch`, `Geschichte`, `Physik`, `Biologie`, `Chemie`, etc. | ## Konfiguration ### Umgebungsvariablen | Variable | Beschreibung | Default | |----------|--------------|---------| | `PORT` | Server Port | `8084` | | `OPENSEARCH_URL` | OpenSearch URL | `http://opensearch:9200` | | `OPENSEARCH_USERNAME` | OpenSearch User | `admin` | | `OPENSEARCH_PASSWORD` | OpenSearch Passwort | `admin` | | `INDEX_NAME` | Index Name | `bp_documents_v1` | | `USER_AGENT` | Crawler User Agent | `BreakpilotEduCrawler/1.0` | | `RATE_LIMIT_PER_SEC` | Requests pro Sekunde/Domain | `0.2` | | `MAX_DEPTH` | Max Crawl-Tiefe | `4` | | `MAX_PAGES_PER_RUN` | Max Seiten pro Crawl | `500` | | `SEEDS_DIR` | Seed-Dateien Verzeichnis | `./seeds` | | `RULES_DIR` | Tagging-Regeln Verzeichnis | `./rules` | | `EDU_SEARCH_API_KEY` | API Key für Auth | `` | | `BACKEND_URL` | URL zum Python Backend | `http://backend:8000` | | `SEEDS_FROM_API` | Seeds aus API laden | `true` | | **Semantic Search** | | | | `SEMANTIC_SEARCH_ENABLED` | Semantic Search aktivieren | `false` | | `EMBEDDING_PROVIDER` | Provider: `openai`, `ollama`, `none` | `none` | | `OPENAI_API_KEY` | API Key für OpenAI Embeddings | `` | | `EMBEDDING_MODEL` | Embedding-Modell | `text-embedding-3-small` | | `EMBEDDING_DIMENSION` | Vektor-Dimension | `1536` | | `OLLAMA_URL` | Ollama Server URL | `http://ollama:11434` | | **Scheduler** | | | | `SCHEDULER_ENABLED` | Automatisches Crawling aktivieren | `false` | | `SCHEDULER_INTERVAL` | Crawl-Intervall | `24h` (täglich) | ## Installation & Start ### Docker (empfohlen) ```bash # Im edu-search-service Verzeichnis docker compose up -d # Logs anzeigen docker compose logs -f edu-search # Nur der Service (OpenSearch extern) docker build -t edu-search-service . docker run -p 8084:8084 \ -e OPENSEARCH_URL=http://host.docker.internal:9200 \ edu-search-service ``` ### Lokal (Entwicklung) ```bash # Dependencies installieren go mod download # Service starten go run cmd/server/main.go # Tests ausführen go test -v ./... ``` ## Seed-Kategorien | Kategorie | Beschreibung | Beispiele | |-----------|--------------|-----------| | `federal` | Bundesweite Institutionen | KMK, BMBF, IQB | | `states` | Landeskultusbehörden | Kultusministerien, Landesinstitute | | `science` | Wissenschaftliche Studien | PISA, IGLU, TIMSS | | `universities` | Hochschulen | Pädagogische Hochschulen | | `schools` | Schulen direkt | Schulhomepages | | `portals` | Bildungsportale | Lehrer-Online, 4teachers | | `eu` | EU-Bildungsprogramme | Erasmus+, Eurydice | | `authorities` | Schulbehörden | Regierungspräsidien | ## Tagging-Regeln Die YAML-Regeldateien im `rules/` Verzeichnis definieren das Tagging: - `doc_type_rules.yaml` - Dokumenttyp-Erkennung - `subject_rules.yaml` - Fächer-Erkennung - `level_rules.yaml` - Schulstufen-Erkennung - `trust_rules.yaml` - Trust-Score-Berechnung ### Beispiel: doc_type_rules.yaml ```yaml doc_types: Lehrplan: strong_terms: - Lehrplan - Kernlehrplan - Bildungsplan medium_terms: - Curriculum - Kompetenzerwartungen url_patterns: - /lehrplan - /kernlehrplan priority_order: - Pruefung_Abitur - Lehrplan - Arbeitsblatt ``` ## Projektstruktur ``` edu-search-service/ ├── cmd/ │ └── server/ │ └── main.go # Entry Point ├── internal/ │ ├── api/ │ │ └── handlers/ │ │ ├── handlers.go # Search & Health Handler │ │ └── admin_handlers.go # Admin API Handler │ ├── config/ │ │ └── config.go # Konfiguration │ ├── crawler/ │ │ ├── crawler.go # URL Fetcher │ │ └── api_client.go # Backend API Client (Seeds) │ ├── robots/ │ │ └── robots.go # robots.txt Parser & Checker │ ├── embedding/ │ │ └── embedding.go # Embedding Provider (OpenAI/Ollama) │ ├── extractor/ │ │ └── extractor.go # HTML/PDF Extraktion │ ├── indexer/ │ │ └── mapping.go # OpenSearch Indexer │ ├── pipeline/ │ │ └── pipeline.go # Crawl Orchestrierung │ ├── quality/ │ │ └── quality.go # Multi-Faktor Quality Scoring │ ├── scheduler/ │ │ └── scheduler.go # Automatisches Crawl-Scheduling │ ├── search/ │ │ └── search.go # Search Service (Keyword/Semantic/Hybrid) │ └── tagger/ │ └── tagger.go # Regelbasiertes Tagging ├── rules/ │ ├── doc_type_rules.yaml │ ├── subject_rules.yaml │ ├── level_rules.yaml │ └── trust_rules.yaml ├── seeds/ │ ├── federal.txt │ ├── states.txt │ └── denylist.txt ├── Dockerfile ├── docker-compose.yml ├── go.mod └── README.md ``` ## Abhängigkeiten | Package | Version | Beschreibung | Lizenz | |---------|---------|--------------|--------| | `github.com/gin-gonic/gin` | v1.9+ | HTTP Framework | MIT | | `github.com/opensearch-project/opensearch-go/v2` | v2.3+ | OpenSearch Client | Apache-2.0 | | `github.com/PuerkitoBio/goquery` | v1.8+ | HTML Parser | BSD-3-Clause | | `github.com/ledongthuc/pdf` | v0.0.0-20240201 | PDF Text Extraktion | MIT | | `gopkg.in/yaml.v3` | v3.0+ | YAML Parser | MIT | | `github.com/google/uuid` | v1.4+ | UUID Generation | BSD-3-Clause | | `golang.org/x/net` | v0.19+ | HTML Utilities | BSD-3-Clause | ## Tests ausführen ```bash # Alle Tests go test -v ./... # Mit Coverage go test -cover ./... # Nur Tagger Tests go test -v ./internal/tagger/... # Nur Crawler Tests go test -v ./internal/crawler/... ``` ## Lizenz Proprietär - BreakPilot GmbH ## Kontakt - Security Issues: security@breakpilot.com - Bugs: https://github.com/breakpilot/edu-search-service/issues