Files
Benjamin Boenisch 414e0f5ec0
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 28s
CI / test-go-edu-search (push) Successful in 27s
CI / test-python-klausur (push) Successful in 1m45s
CI / test-python-agent-core (push) Successful in 16s
CI / test-nodejs-website (push) Successful in 21s
feat: edu-search-service migriert, voice-service/geo-service entfernt
- edu-search-service von breakpilot-pwa nach breakpilot-lehrer kopiert (ohne vendor)
- opensearch + edu-search-service in docker-compose.yml hinzugefuegt
- voice-service aus docker-compose.yml entfernt (jetzt in breakpilot-core)
- geo-service aus docker-compose.yml entfernt (nicht mehr benoetigt)
- CI/CD: edu-search-service zu Gitea Actions und Woodpecker hinzugefuegt
  (Go lint, test mit go mod download, build, SBOM)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 18:36:38 +01:00

410 lines
14 KiB
Markdown

# 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": ["<em>Lehrplan</em> für das Fach <em>Mathematik</em>..."]
}
],
"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