Update README with webhook status

This commit is contained in:
Ján Pták 2026-06-04 19:15:20 +02:00
parent 44eb3cd679
commit 64b828c46e
3 changed files with 115 additions and 139 deletions

178
README.md
View File

@ -2,7 +2,7 @@
Agent pre manažment záverečných prác nad repozitárom `zpwiki`. Agent pre manažment záverečných prác nad repozitárom `zpwiki`.
Projekt rieši základnú službu pre indexovanie a vyhľadávanie v Markdown súboroch zo školského repozitára záverečných prác. Cieľom je vytvoriť samostatné API, ktoré vie načítať obsah `zpwiki`, spracovať metadata, rozdeliť dokumenty na chunky, vyhľadávať v nich a neskôr sa napojiť na OpenWebUI, RAG, znalostný graf a Gitea webhook. Projekt zatiaľ rieši základnú časť systému pre vyhľadávanie v Markdown súboroch zo školského repozitára záverečných prác. Cieľom je vytvoriť samostatnú službu, ktorá vie indexovať obsah `zpwiki`, vyhľadávať v ňom a neskôr sa napojí na OpenWebUI, RAG, znalostný graf a webhook synchronizáciu.
## Aktuálny stav ## Aktuálny stav
@ -17,16 +17,8 @@ Zatiaľ je implementované:
7. rozlíšenie režimu vyhľadávania: 7. rozlíšenie režimu vyhľadávania:
1. `person` pre mená osôb, napríklad `jan ptak`, 1. `person` pre mená osôb, napríklad `jan ptak`,
2. `topic` pre tematické dopyty, napríklad `rag agent` alebo `knowledge graph`, 2. `topic` pre tematické dopyty, napríklad `rag agent` alebo `knowledge graph`,
8. FastAPI backend s endpointmi: 8. FastAPI backend s endpointmi `/health` a `/search`,
1. `/health`, 9. automatická Swagger dokumentácia API.
2. `/search`,
3. `/sync`,
9. automatická Swagger dokumentácia API,
10. Dockerfile pre zostavenie API kontajnera,
11. `docker-compose.yml` pre spustenie služby,
12. mount repozitára `zpwiki` do kontajnera,
13. environment premenná `ZPWIKI_ROOT`,
14. reindexovanie dát cez endpoint `/sync`.
## Štruktúra projektu ## Štruktúra projektu
@ -39,14 +31,10 @@ dp-zp-agent/
│ ├── scan_zpwiki.py │ ├── scan_zpwiki.py
│ ├── build_chunks.py │ ├── build_chunks.py
│ ├── build_sqlite_index.py │ ├── build_sqlite_index.py
│ ├── rebuild_index.py
│ ├── search_chunks.py │ ├── search_chunks.py
│ └── search_db.py │ └── search_db.py
├── data/ ├── data/
├── Dockerfile
├── docker-compose.yml
├── requirements.txt ├── requirements.txt
├── .dockerignore
├── .gitignore ├── .gitignore
└── README.md └── README.md
``` ```
@ -91,18 +79,6 @@ Nakoniec sa vytvorí SQLite index:
python scripts/build_sqlite_index.py python scripts/build_sqlite_index.py
``` ```
Alebo sa dá celý index obnoviť jedným príkazom:
```bash
python scripts/rebuild_index.py
```
S voliteľným `git pull` pred reindexovaním:
```bash
python scripts/rebuild_index.py --pull
```
## Testovanie vyhľadávania v termináli ## Testovanie vyhľadávania v termináli
Vyhľadávanie podľa osoby: Vyhľadávanie podľa osoby:
@ -123,7 +99,7 @@ Vyhľadávanie podľa znalostného grafu:
python scripts/search_db.py "knowledge graph" python scripts/search_db.py "knowledge graph"
``` ```
## Spustenie API lokálne ## Spustenie API
FastAPI server sa spustí príkazom: FastAPI server sa spustí príkazom:
@ -145,24 +121,6 @@ curl -X POST http://127.0.0.1:8000/search \
-d '{"query":"jan ptak","limit":5}' -d '{"query":"jan ptak","limit":5}'
``` ```
Reindexovanie cez API:
```bash
curl -X POST http://127.0.0.1:8000/sync \
-H "Content-Type: application/json" \
-d '{"pull_git":false}'
```
Reindexovanie aj s `git pull`:
```bash
curl -X POST http://127.0.0.1:8000/sync \
-H "Content-Type: application/json" \
-d '{"pull_git":true}'
```
Poznámka: pri Docker verzii môže `pull_git:true` vyžadovať vyriešenie SSH prístupu ku Gitu. Zatiaľ je bezpečné používať hlavne `pull_git:false`.
## Swagger UI ## Swagger UI
FastAPI automaticky generuje Swagger dokumentáciu API. FastAPI automaticky generuje Swagger dokumentáciu API.
@ -173,68 +131,26 @@ Po spustení servera je dostupná na adrese:
http://127.0.0.1:8000/docs http://127.0.0.1:8000/docs
``` ```
V Swagger UI je možné testovať endpointy `/health`, `/search` a `/sync` priamo z prehliadača. V Swagger UI je možné testovať endpointy `/health` a `/search` priamo z prehliadača.
## Spustenie cez Docker ## Čo ešte treba dorobiť
Služba sa dá spustiť cez Docker Compose: ### 1. Dockerizácia aplikácie
Treba vytvoriť:
1. `Dockerfile`,
2. `docker-compose.yml`,
3. jednoduchý návod na spustenie cez Docker,
4. volume alebo mount pre dáta a SQLite databázu.
Cieľ je, aby sa služba dala spustiť jedným príkazom:
```bash ```bash
docker compose up --build docker compose up --build
``` ```
Po spustení je API dostupné na: ### 2. Upratanie kódu do modulov
```text
http://127.0.0.1:8000
```
Swagger UI:
```text
http://127.0.0.1:8000/docs
```
Health check:
```bash
curl http://127.0.0.1:8000/health
```
Vyhľadávanie:
```bash
curl -X POST http://127.0.0.1:8000/search \
-H "Content-Type: application/json" \
-d '{"query":"knowledge graph","limit":5}'
```
Reindexovanie:
```bash
curl -X POST http://127.0.0.1:8000/sync \
-H "Content-Type: application/json" \
-d '{"pull_git":false}'
```
V `docker-compose.yml` je repozitár `zpwiki` pripojený ako volume:
```yaml
volumes:
- ./data:/app/data
- ../zpwiki:/zpwiki
```
A cesta k nemu je nastavená cez environment premennú:
```yaml
environment:
- ZPWIKI_ROOT=/zpwiki
```
## Čo ešte treba dorobiť
### 1. Upratanie kódu do modulov
Aktuálne je veľká časť logiky priamo v `app/main.py`. Neskôr treba kód rozdeliť napríklad takto: Aktuálne je veľká časť logiky priamo v `app/main.py`. Neskôr treba kód rozdeliť napríklad takto:
@ -250,20 +166,19 @@ app/
Cieľ je, aby API, vyhľadávanie, databáza a synchronizácia neboli v jednom veľkom súbore. Cieľ je, aby API, vyhľadávanie, databáza a synchronizácia neboli v jednom veľkom súbore.
### 2. Vylepšenie synchronizácie so `zpwiki` ### 3. Synchronizácia so `zpwiki`
Aktuálne endpoint `/sync` vie obnoviť celý index. Neskôr treba pridať efektívnejšiu synchronizáciu. Treba pridať mechanizmus, ktorý bude vedieť aktualizovať dáta zo školského repozitára.
Treba dorobiť: Plánované časti:
1. zistenie aktuálneho commitu, 1. skript pre `git pull`,
2. uloženie posledného spracovaného commitu, 2. zistenie aktuálneho commitu,
3. detekciu zmenených Markdown súborov, 3. detekcia zmenených Markdown súborov,
4. reindexovanie iba zmenených dokumentov, 4. reindexovanie zmenených dokumentov,
5. uloženie stavu synchronizácie do databázy, 5. uloženie stavu synchronizácie do databázy.
6. logovanie výsledkov synchronizácie.
### 3. Webhook endpoint pre Gitea ### 4. Webhook endpoint pre Gitea
Treba vytvoriť endpoint napríklad: Treba vytvoriť endpoint napríklad:
@ -275,12 +190,11 @@ Tento endpoint má:
1. prijať webhook z Gitea, 1. prijať webhook z Gitea,
2. overiť secret alebo podpis webhooku, 2. overiť secret alebo podpis webhooku,
3. spracovať push event, 3. spustiť synchronizáciu repozitára,
4. spustiť synchronizáciu repozitára, 4. spustiť reindexovanie zmenených súborov,
5. spustiť reindexovanie, 5. zapísať výsledok do logu alebo tabuľky synchronizácie.
6. vrátiť výsledok synchronizácie.
### 4. OpenWebUI integrácia ### 5. OpenWebUI integrácia
Treba napojiť API na OpenWebUI. Treba napojiť API na OpenWebUI.
@ -293,7 +207,7 @@ Možné riešenia:
Cieľ je, aby používateľ mohol v OpenWebUI položiť otázku a agent použil vyhľadávanie nad `zpwiki`. Cieľ je, aby používateľ mohol v OpenWebUI položiť otázku a agent použil vyhľadávanie nad `zpwiki`.
### 5. Embeddingy a vektorové vyhľadávanie ### 6. Embeddingy a vektorové vyhľadávanie
Aktuálne vyhľadávanie je fulltextové a skórovacie. Ďalší krok je pridať embeddingy. Aktuálne vyhľadávanie je fulltextové a skórovacie. Ďalší krok je pridať embeddingy.
@ -305,14 +219,14 @@ Treba dorobiť:
4. vektorové vyhľadávanie, 4. vektorové vyhľadávanie,
5. porovnanie fulltextového a vektorového vyhľadávania. 5. porovnanie fulltextového a vektorového vyhľadávania.
Možné databázy alebo nástroje: Možné databázy:
1. PostgreSQL plus pgvector, 1. PostgreSQL plus pgvector,
2. Qdrant, 2. Qdrant,
3. ChromaDB, 3. ChromaDB,
4. FAISS ako jednoduchý lokálny prototyp. 4. FAISS ako jednoduchý lokálny prototyp.
### 6. RAG odpovede s citáciami ### 7. RAG odpovede s citáciami
Treba doplniť generovanie odpovede pomocou jazykového modelu. Treba doplniť generovanie odpovede pomocou jazykového modelu.
@ -326,7 +240,7 @@ Postup:
Cieľ je, aby agent nehalucinoval a vedel ukázať, z ktorých dokumentov odpovedal. Cieľ je, aby agent nehalucinoval a vedel ukázať, z ktorých dokumentov odpovedal.
### 7. Znalostný graf ### 8. Znalostný graf
Treba vytvoriť štruktúrovaný graf nad dátami zo `zpwiki`. Treba vytvoriť štruktúrovaný graf nad dátami zo `zpwiki`.
@ -348,7 +262,7 @@ Základné vzťahy:
5. práca je podobná inej práci, 5. práca je podobná inej práci,
6. práca patrí do roka alebo obdobia. 6. práca patrí do roka alebo obdobia.
### 8. GraphRAG ### 9. GraphRAG
Treba prepojiť RAG a znalostný graf. Treba prepojiť RAG a znalostný graf.
@ -360,7 +274,7 @@ GraphRAG časť má umožniť:
4. analýzu tém podľa tagov, rokov a kategórií, 4. analýzu tém podľa tagov, rokov a kategórií,
5. kombináciu textového, vektorového a grafového vyhľadávania. 5. kombináciu textového, vektorového a grafového vyhľadávania.
### 9. Vyhodnotenie systému ### 10. Vyhodnotenie systému
Treba pripraviť testovaciu sadu otázok a porovnať viacero prístupov. Treba pripraviť testovaciu sadu otázok a porovnať viacero prístupov.
@ -389,7 +303,7 @@ Sledované vlastnosti:
5. čas odpovede, 5. čas odpovede,
6. čas reindexovania po zmene v Gite. 6. čas reindexovania po zmene v Gite.
### 10. Dokumentácia do diplomovej práce ### 11. Dokumentácia do diplomovej práce
Treba priebežne písať: Treba priebežne písať:
@ -400,20 +314,10 @@ Treba priebežne písať:
5. ako funguje `zpwiki`, 5. ako funguje `zpwiki`,
6. návrh architektúry systému, 6. návrh architektúry systému,
7. návrh databázy a indexu, 7. návrh databázy a indexu,
8. návrh synchronizácie, 8. návrh webhook synchronizácie,
9. návrh webhook integrácie, 9. návrh integrácie s OpenWebUI,
10. návrh integrácie s OpenWebUI, 10. popis experimentov a vyhodnotenia.
11. popis experimentov a vyhodnotenia.
## Najbližší praktický krok ## Najbližší praktický krok
Najbližšie treba spraviť Gitea webhook endpoint. Najbližšie treba spraviť Docker nasadenie aktuálneho FastAPI prototypu.
Poradie najbližších krokov:
1. commitnúť aktuálne zmeny,
2. pushnúť projekt na KEMT Git,
3. vytvoriť endpoint `POST /webhook/gitea`,
4. pridať overenie secretu alebo podpisu webhooku,
5. napojiť webhook na `/sync`,
6. otestovať webhook lokálne alebo cez verejne dostupný tunel.

View File

@ -1,4 +1,7 @@
from pathlib import Path from pathlib import Path
import hashlib
import hmac
import json
import os import os
import re import re
import sqlite3 import sqlite3
@ -8,12 +11,13 @@ import time
import unicodedata import unicodedata
from collections import Counter from collections import Counter
from fastapi import FastAPI, HTTPException from fastapi import FastAPI, Header, HTTPException, Request
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
DB_FILE = Path("data/zp_index.sqlite") DB_FILE = Path("data/zp_index.sqlite")
ZPWIKI_ROOT = Path(os.getenv("ZPWIKI_ROOT", "../zpwiki")) ZPWIKI_ROOT = Path(os.getenv("ZPWIKI_ROOT", "../zpwiki"))
WEBHOOK_SECRET = os.getenv("WEBHOOK_SECRET", "dev-secret")
TECHNICAL_TERMS = { TECHNICAL_TERMS = {
@ -51,7 +55,7 @@ TECHNICAL_TERMS = {
app = FastAPI( app = FastAPI(
title="ZP Agent API", title="ZP Agent API",
description="API pre vyhľadávanie v repozitári záverečných prác zpwiki.", description="API pre vyhľadávanie v repozitári záverečných prác zpwiki.",
version="0.2.0", version="0.3.0",
) )
@ -338,6 +342,31 @@ def rebuild_index(pull_git: bool = False) -> dict:
} }
def verify_gitea_signature(raw_body: bytes, signature: str | None) -> bool:
if not signature:
return False
expected = hmac.new(
WEBHOOK_SECRET.encode("utf-8"),
raw_body,
hashlib.sha256,
).hexdigest()
signature = signature.strip()
if signature.startswith("sha256="):
signature = signature.replace("sha256=", "", 1)
return hmac.compare_digest(expected, signature)
def verify_simple_token(token: str | None) -> bool:
if not token:
return False
return hmac.compare_digest(token, WEBHOOK_SECRET)
@app.get("/health") @app.get("/health")
def health(): def health():
return { return {
@ -346,6 +375,7 @@ def health():
"database_path": str(DB_FILE), "database_path": str(DB_FILE),
"zpwiki_root": str(ZPWIKI_ROOT), "zpwiki_root": str(ZPWIKI_ROOT),
"zpwiki_exists": ZPWIKI_ROOT.exists(), "zpwiki_exists": ZPWIKI_ROOT.exists(),
"webhook_secret_configured": bool(WEBHOOK_SECRET),
} }
@ -377,3 +407,44 @@ def sync(request: SyncRequest):
"duration_seconds": result["duration_seconds"], "duration_seconds": result["duration_seconds"],
"counts": result["counts"], "counts": result["counts"],
} }
@app.post("/webhook/gitea")
async def gitea_webhook(
request: Request,
x_gitea_event: str | None = Header(default=None, alias="X-Gitea-Event"),
x_gitea_signature: str | None = Header(default=None, alias="X-Gitea-Signature"),
x_gitea_token: str | None = Header(default=None, alias="X-Gitea-Token"),
):
raw_body = await request.body()
signature_ok = verify_gitea_signature(raw_body, x_gitea_signature)
token_ok = verify_simple_token(x_gitea_token)
if not signature_ok and not token_ok:
raise HTTPException(
status_code=401,
detail="Invalid webhook signature or token",
)
try:
payload = json.loads(raw_body.decode("utf-8")) if raw_body else {}
except json.JSONDecodeError:
payload = {}
repository = payload.get("repository", {})
repository_name = repository.get("full_name") or repository.get("name")
try:
result = rebuild_index(pull_git=False)
except RuntimeError as error:
raise HTTPException(status_code=500, detail=str(error)) from error
return {
"status": "ok",
"event": x_gitea_event or "unknown",
"repository": repository_name,
"verified_by": "signature" if signature_ok else "token",
"duration_seconds": result["duration_seconds"],
"counts": result["counts"],
}

View File

@ -6,6 +6,7 @@ services:
- "8000:8000" - "8000:8000"
environment: environment:
- ZPWIKI_ROOT=/zpwiki - ZPWIKI_ROOT=/zpwiki
- WEBHOOK_SECRET=dev-secret
volumes: volumes:
- ./data:/app/data - ./data:/app/data
- ../zpwiki:/zpwiki - ../zpwiki:/zpwiki