{"queue_id": 122, "relative_path": "Systemstruktur/YouTube-Graphiti-Import-Retry-Provider-Rate-Limit.md", "title": "YouTube-Graphiti-Import: Retry, Provider-Rate-Limit und Betriebsstatus", "import_key": "b6a952209ed1efa96262018725d724c7acff4d84acf74d4522afab24e5e63fab", "chunk_count": 10, "created_nodes": 11, "created_relationships": 19, "body": null, "chunks": [{"chunk_key": "1ea424b5ebac85b7628ef427ba27481484a21aecd9d234442e4d5a61f2cbfee4", "chunk_index": 0, "heading": "YouTube-Graphiti-Import: Retry, Provider-Rate-Limit und Betriebsstatus", "text": "# YouTube-Graphiti-Import: Retry, Provider-Rate-Limit und Betriebsstatus", "text_hash": "4f05ee8a8f4015d401830da09a17d97cb0c848d4a1e4d39a0d1046dcf5bca59a"}, {"chunk_key": "c8f69eeab7d3a8aae6f13dc30308bca37822fcf5631c7e14ddcdefae2f57584a", "chunk_index": 1, "heading": "Zweck und Systemkontext", "text": "# YouTube-Graphiti-Import: Retry, Provider-Rate-Limit und Betriebsstatus\n## Zweck und Systemkontext\n\nDer YouTube-Research-Dienst speichert Videos in `/opt/struktur/youtube-research/knowledge.db`. Die Obsidian-Dateien liegen unter `/opt/obsidian-vault/YouTube-Research/`. Die Queue `graphiti_import_queue` nimmt Änderungen auf und übergibt sie an Graphiti unter `http://127.0.0.1:8644/episodes`. Graphiti extrahiert über OpenRouter Wissensknoten und speichert Episoden und Entitäten in Neo4j (`graphiti-neo4j`).\n\nRelevante Komponenten:\n\n- `/opt/struktur/graphiti/youtube_graphiti_queue.py`\n- `/opt/struktur/graphiti/youtube_graphiti_status.py`\n- `/opt/struktur/graphiti/service/main.py`\n- `/opt/struktur/graphiti/docker-compose.yml`\n- `youtube-research-graphiti-worker.service`\n- `youtube-research-graphiti-worker.timer`\n- `/opt/struktur/youtube-research/knowledge.db`", "text_hash": "febd3dcefed346f89557d550157810dacbfc41248e0a89f7f44cd089363014d7"}, {"chunk_key": "3d9a2a52b28fb176f484c5f415c462a004e4d944a7f7d5718613e92b8bea510f", "chunk_index": 2, "heading": "Ausgangsproblem", "text": "r-compose.yml`\n- `youtube-research-graphiti-worker.service`\n- `youtube-research-graphiti-worker.timer`\n- `/opt/struktur/youtube-research/knowledge.db`\n## Ausgangsproblem\n\nNeue Videos wurden erkannt und eingereiht. Acht Videos wurden nach fünf Versuchen auf `failed` gesetzt. `curl --fail` verbarg die HTTP-Antwort und lieferte nur Exit-Code 22; Timeouts lieferten Exit-Code 28. Graphiti gab den eigentlichen OpenRouter-Fehler HTTP 429 als HTTP 500 weiter. Der Provider meldete `free-models-per-min`, Limit 16 Requests pro Minute. Ein temporärer Providerfehler wurde dadurch fälschlich wie ein permanenter Fehler behandelt.\n\nZusätzlich bestanden interne OpenAI-SDK-Retries neben Queue-Retries, eine nicht ausreichende Readiness-Prüfung und das Risiko, dass eine leere Shell-Variable die geschützte `.env`-Konfiguration überschreibt.", "text_hash": "695e52abf2a4054258d50320cf09d34aeb81fe647f69a829a104d1ab3ce61093"}, {"chunk_key": "6142fe238188db411e319330c6f8a45f37ff19d4012266e00065b13f080554b2", "chunk_index": 3, "heading": "Umgesetzte Reparaturen", "text": "ue-Retries, eine nicht ausreichende Readiness-Prüfung und das Risiko, dass eine leere Shell-Variable die geschützte `.env`-Konfiguration überschreibt.\n## Umgesetzte Reparaturen\n\n- Queue-Felder für Versuchszahl, Fehlerklasse, Fehlermeldung, HTTP-Status, letzte/nächste Ausführung und Abschlusszeit.\n- Status `retry_wait` für temporäre Fehler und `failed_permanent` nur für nachgewiesen dauerhafte Fehler.\n- Backoff: 1 Minute, 5 Minuten, 15 Minuten, 1 Stunde, 4 Stunden, danach 12 Stunden.\n- Verwaiste `processing`-Jobs werden nach 30 Minuten freigegeben.\n- Reconcile verarbeitet den gesamten YouTube-Research-Bestand.\n- `curl --fail` entfernt; HTTP-Status, Exit-Code, Laufzeit und Antwortkörper werden erfasst.\n- `max_retries=0` beim OpenAI-SDK; die Queue ist die kontrollierte äußere Retry-Ebene.\n- Dateibasierter Limiter vor jedem internen Graphiti-LLM-Aufruf, maximal 12 Aufrufe pro 60 Sekunden und nur ein aktiver Aufruf.\n- Globale Pause bei Provider-429 anhand von Reset-/Retry-Zeit.\n- Provider-429 wird als Graphiti-HTTP-429 weitergegeben.\n- Readiness prüft Graphiti-Initialisierung und Neo4j-Verbindung.\n- Die Compose-Konfiguration überschreibt den geschützten Schlüssel nicht mehr mit einer leeren Shell-Variable.", "text_hash": "1996c9f17012defa5bddde034a9603dd7639bc80fdf0f5d164a2db0d1f9a327d"}, {"chunk_key": "14d20abbbba7a466f1b1865803651da17367122151bf129f348ebd6f1bd27dbe", "chunk_index": 4, "heading": "Betriebslogik", "text": "i-Initialisierung und Neo4j-Verbindung.\n- Die Compose-Konfiguration überschreibt den geschützten Schlüssel nicht mehr mit einer leeren Shell-Variable.\n## Betriebslogik\n\nTimeouts, Netzwerkfehler, HTTP 408/425/429 und HTTP 5xx bleiben retryfähig. HTTP 400/401/403/404/409/422 müssen einzeln bewertet werden. Graphiti-/Neo4j-Ausfall öffnet den Circuit Breaker; der Worker verbraucht dann keine weiteren Jobs. Ein Provider-429 pausiert fällige Jobs global. Verwaiste Locks werden automatisch zurückgesetzt. Die Queue schützt durch `(obsidian_path, content_hash)` vor Duplikaten.\n\nEin Queue-Status `done` gilt erst dann als fachlich bestätigt, wenn die zugehörige Episode in Neo4j beziehungsweise Graphiti nachgewiesen ist.\n\n**Temporäre Provider-, Netzwerk-, Timeout-, Graphiti- oder Neo4j-Fehler dürfen niemals allein dazu führen, dass ein importierbarer Datensatz dauerhaft aus der automatischen Verarbeitung fällt.**", "text_hash": "9fdb8e7fede26b09fb6d0b70ddb9ca8c3a75243e7647c52ab261cc4e3ba0f599"}, {"chunk_key": "d16341072660f4d0b649f2ccbbbf85d68d5316818b42bd82d494ebe30461564d", "chunk_index": 5, "heading": "Aktueller geprüfter Stand", "text": "Graphiti- oder Neo4j-Fehler dürfen niemals allein dazu führen, dass ein importierbarer Datensatz dauerhaft aus der automatischen Verarbeitung fällt.**\n## Aktueller geprüfter Stand\n\nStand der letzten Prüfung: 2026-07-19.\n\n- Videos in `knowledge.db`: 910\n- Queue-Zeilen: 874\n- eindeutige Queue-YouTube-IDs: 823\n- erfolgreiche Queue-Zeilen: 675\n- erfolgreich importierte eindeutige YouTube-IDs: 628\n- `done`: 675\n- `retry_wait`: 199\n- `failed_permanent`: 0\n- Timer: aktiv\n- letzter Worker-Lauf: Exit-Code 0\n- Graphiti/Neo4j-Readiness war zwischenzeitlich `ready=true`\n\nDie acht betroffenen IDs sind weiterhin nicht abgeschlossen und stehen auf `retry_wait`: `aircAruvnKk`, `rfscVS0vtbw`, `8drwMxob22k`, `KBoaJwZJLUs`, `fr5SyBAgn5E`, `7oaaqyKq1CU`, `RMbrRHl7l1U`, `81pDusm5nZE`. Für jede wurden in Neo4j null Episoden nachgewiesen.\n\nDer zwischenzeitliche neue Blocker war ein Provider-Authentifizierungsfehler: Graphiti protokollierte HTTP 500 mit OpenRouter-Fehler HTTP 401 `Missing Authentication header`. Ursache war die uneinheitliche Variablenführung: Der Container erhielt keinen wirksamen `OPENROUTER_API_KEY`; `main.py` las zuvor nur `DEEPSEEK_API_KEY`, während Compose einen unpassenden beziehungsweise leeren Wert verwendete. Zusätzlich überschreibt die geschützte Carlo-`.env` ein nicht unterstütztes Embedding-Modell, weshalb `EMBED_MODEL` in Compose explizit auf den installierten lokalen Wert gesetzt wurde.", "text_hash": "b5182790062ba86e7c9c0dfeae844346657e7bd449f0e96f964c50841d364a5f"}, {"chunk_key": "c814dfae9a2a8d9aa8de59e85555d17bd1ec6e7110aee5c62f35881fd1a1c005", "chunk_index": 6, "heading": "Aktueller geprüfter Stand", "text": "zte Carlo-`.env` ein nicht unterstütztes Embedding-Modell, weshalb `EMBED_MODEL` in Compose explizit auf den installierten lokalen Wert gesetzt wurde.\nReparatur: `main.py` liest `OPENROUTER_API_KEY` bevorzugt, startet bei leerem Schlüssel nicht und führt beim Start einen echten minimalen Provider-Test aus. `/health` meldet `provider_authenticated` und `provider_rate_limited` getrennt. Compose nutzt die geschützte Providerquelle und setzt das kompatible lokale Embedding-Modell explizit. Der echte Test gegen OpenRouter mit dem Produktivmodell antwortete danach mit HTTP 200; der Schlüssel wurde nur maskiert geprüft (gesetzt, Länge 74, Fingerprint `e16c11f4cedd`, Anfang `sk-o`, Ende `059`). Keine Secretwerte wurden ausgegeben oder dokumentiert.\n\nNach dem Recreate meldet Graphiti: `ready=true`, `neo4j=true`, `provider_configured=true`, `provider_authenticated=true`, `provider_rate_limited=false`. Es wurde keine ID manuell auf `done` gesetzt und keine Episode künstlich erzeugt.", "text_hash": "dd9f299d4993eb62153f81990d071a16bdc37531f661f50fa2a688c856de435d"}, {"chunk_key": "0adaa9d12f6e14e6d46b1d6db426d4e36cdc3f990d2fd2ea7d8f558572757585", "chunk_index": 7, "heading": "Diagnosebefehle", "text": "true`, `provider_authenticated=true`, `provider_rate_limited=false`. Es wurde keine ID manuell auf `done` gesetzt und keine Episode künstlich erzeugt.\n## Diagnosebefehle\n\n```bash\npython3 /opt/struktur/graphiti/youtube_graphiti_status.py\ncurl -sS http://127.0.0.1:8644/health\nsystemctl status youtube-research-graphiti-worker.timer youtube-research-graphiti-worker.service --no-pager -l\njournalctl -u youtube-research-graphiti-worker.service -n 50 --no-pager\nsqlite3 /opt/struktur/youtube-research/knowledge.db \"SELECT graphiti_status,COUNT(*) FROM graphiti_import_queue GROUP BY graphiti_status;\"\ndocker logs --since 2h graphiti-service\n```\n\nDie Neo4j-Prüfung erfolgt über `MATCH (n:Episodic) ... RETURN count(n)` im Container `graphiti-neo4j`; Schlüssel und Provider-Secrets werden nicht dokumentiert.", "text_hash": "3e454a478e9f20d69f037ffa296b0180c60bba951528dc23928955d6fdaab4e0"}, {"chunk_key": "d97326df0eb4eacdc838614b72deb459c7237599998198917c1f90118eb211a5", "chunk_index": 8, "heading": "Geänderte Dateien", "text": "Prüfung erfolgt über `MATCH (n:Episodic) ... RETURN count(n)` im Container `graphiti-neo4j`; Schlüssel und Provider-Secrets werden nicht dokumentiert.\n## Geänderte Dateien\n\n- `youtube_graphiti_queue.py`: Retry-, Fehler-, Circuit-Breaker-, Reconcile- und Stale-Lock-Logik.\n- `youtube_graphiti_status.py`: eindeutige Trennung von Queue-Zeilen, IDs, Erfolgen und offenen Statuswerten.\n- `service/main.py`: Provider-Limiter, SDK-Retry-Deaktivierung, 429-Weitergabe, globale Providerpause und Readiness.\n- `docker-compose.yml`: keine Überschreibung des geschützten Provider-Schlüssels durch leere Shell-Variable.", "text_hash": "112bd14dd56697500e503702d6bc65488e7dbd2b24a6971b1e2fac7c82ac24db"}, {"chunk_key": "cb8a95f34e6189d1b0bc3d15de7d4b71841eb52f9d1e5b73b4b5db0502f59afa", "chunk_index": 9, "heading": "Verbindlicher Reststatus", "text": "abe, globale Providerpause und Readiness.\n- `docker-compose.yml`: keine Überschreibung des geschützten Provider-Schlüssels durch leere Shell-Variable.\n## Verbindlicher Reststatus\n\nDie Reparatur der Fehlerbehandlung und der Provider-Authentifizierung ist umgesetzt. Ein echter OpenRouter-Test antwortete mit HTTP 200; Graphiti meldet `provider_authenticated=true` und `provider_rate_limited=false`. Die acht Videos sind jedoch noch nicht abgeschlossen, weil sie regulär bis 11:03 UTC im Backoff warten und bislang jeweils null Neo4j-Episoden haben. Der Timer/Worker darf sie erst zum fälligen Termin verarbeiten; es wurde nichts manuell vorgezogen. API-Schlüssel dürfen niemals in Obsidian, Logs oder Statusausgaben erscheinen.", "text_hash": "7a8444afa0e926972d0b1c81581d641ece9e1877b2cc3e8e36394ea0d3ac0478"}], "elapsed_s": 0.032}