docs: document interactive ci-ai-bot + current Jetson backend (#1)
This commit is contained in:
126
README.md
126
README.md
@@ -1,36 +1,46 @@
|
|||||||
# ci-ai — KI-Schicht für die Gitea-CI
|
# ci-ai — KI-Schicht für Gitea
|
||||||
|
|
||||||
Self-hosted Äquivalent zu **GitLab Duo** für Gitea + act_runner: automatische
|
Self-hosted Äquivalent zu **GitLab Duo** für Gitea: automatische PR-Reviews,
|
||||||
PR-Reviews, PR-Summaries, KI-erklärte Security-Funde und CI-Failure-Analyse.
|
PR-/Issue-Summaries, KI-erklärte Security-Funde, CI-Failure-Analyse — **und** ein
|
||||||
Läuft über den vorhandenen `gitea-runner` auf dem Pi.
|
interaktiver `@ci-ai`-Bot. KI-Backend ist der self-hosted **Jetson** (Ollama, GPU);
|
||||||
|
Anthropic bleibt als Alternative per Env-Var. Zwei Wege, dieselben Skripte:
|
||||||
|
|
||||||
|
- **CI-Workflows** (über den `gitea-runner`): laufen bei `push` / `pull_request` / Slash-Command.
|
||||||
|
- **Interaktiver Bot** (`bot/`, Webhook-Dienst): `@ci-ai`-Chat + Auto-PR-Review in
|
||||||
|
**Sekunden** — ohne Runner, ohne Clone, ohne NAT-Hairpin.
|
||||||
|
|
||||||
## Bausteine
|
## Bausteine
|
||||||
|
|
||||||
| Feature | Skript | Trigger |
|
| Feature | Skript | Trigger |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| A — PR-Review + Summary | `scripts/ai_review.py` | `pull_request` |
|
| A — PR-Review + Summary | `scripts/ai_review.py` | `pull_request` (Runner) |
|
||||||
| B — Security-Scan + KI-Erklärung (hart blockierend) | `security.yml` + `scripts/ai_explain.py` | `pull_request`, `push` |
|
| B — Security-Scan + KI-Erklärung (hart blockierend) | `security.yml` + `scripts/ai_explain.py` | `pull_request`, `push` |
|
||||||
| C — CI-Failure-Analyse | `if:failure()`-Job + `scripts/ai_explain.py` | Job-Fehler |
|
| C — CI-Failure-Analyse | `if:failure()`-Job + `scripts/ai_explain.py` | Job-Fehler |
|
||||||
| D — Test-Generierung (Vorschlag) | `scripts/ai_tests.py` | Kommentar `/gen-tests` (PR) |
|
| D — Test-Generierung (Vorschlag) | `scripts/ai_tests.py` | Kommentar `/gen-tests` (PR) |
|
||||||
| E — Issue-/PR-Zusammenfassung | `scripts/ai_summary.py` | Kommentar `/summarize` |
|
| E — Issue-/PR-Zusammenfassung | `scripts/ai_summary.py` | Kommentar `/summarize` |
|
||||||
|
| **F — Interaktiver Bot** (`@ci-ai`-Chat + Auto-Review) | `bot/app.py` | Gitea-Webhook (`issue_comment`, `pull_request`) |
|
||||||
|
|
||||||
**Slash-Commands** (D + E): in einem Issue/PR `/gen-tests` bzw. `/summarize` kommentieren
|
**Slash-Commands** (D + E): `/gen-tests` bzw. `/summarize` in einem Issue/PR kommentieren →
|
||||||
→ der `commands`-Job (siehe `caller.example.yml`) postet das Ergebnis als Kommentar.
|
der `commands`-Job (siehe `caller.example.yml`) postet das Ergebnis.
|
||||||
|
**`@ci-ai`** (F): `@ci-ai <Frage>` in einem Issue/PR → Antwort im Thread (mit Diff-Kontext).
|
||||||
|
|
||||||
`scripts/llm.py` ist die **provider-agnostische** LLM-Schicht — heute Claude
|
`scripts/llm.py` ist die **provider-agnostische** LLM-Schicht — Jetson (Ollama) im Betrieb,
|
||||||
(Anthropic), später der lokale Jetson (Ollama), umschaltbar per Env-Var.
|
Anthropic als Alternative, umschaltbar per Env-Var. Reine Standardbibliothek, kein `pip install`.
|
||||||
|
|
||||||
## Aufbau
|
## Aufbau
|
||||||
|
|
||||||
```
|
```
|
||||||
scripts/
|
scripts/
|
||||||
llm.py Provider-Abstraktion (anthropic | ollama), stdlib-only
|
llm.py Provider-Abstraktion (ollama | anthropic), stdlib-only
|
||||||
gitea_api.py PR-Diff holen + idempotente Marker-Kommentare
|
gitea_api.py PR-Diff/Thread holen, idempotente Marker-Kommentare, post_comment
|
||||||
context.py Gitea-Actions-Event-Kontext (GITHUB_*-Vars)
|
context.py Gitea-Actions-Event-Kontext (GITHUB_*-Vars)
|
||||||
ai_review.py Feature A
|
ai_review.py Feature A
|
||||||
ai_explain.py Features B & C
|
ai_explain.py Features B & C
|
||||||
ai_tests.py Feature D (/gen-tests)
|
ai_tests.py Feature D (/gen-tests)
|
||||||
ai_summary.py Feature E (/summarize)
|
ai_summary.py Feature E (/summarize)
|
||||||
|
bot/
|
||||||
|
app.py Feature F — interaktiver Webhook-Bot (FastAPI)
|
||||||
|
Dockerfile Container fürs Deployment (vom Repo-ROOT bauen)
|
||||||
.gitea/workflows/
|
.gitea/workflows/
|
||||||
reusable-security.yml on: workflow_call — zentraler Security-Scan
|
reusable-security.yml on: workflow_call — zentraler Security-Scan
|
||||||
reusable-ai-review.yml on: workflow_call — zentraler PR-Review
|
reusable-ai-review.yml on: workflow_call — zentraler PR-Review
|
||||||
@@ -40,60 +50,100 @@ workflows/
|
|||||||
*.example.yml standalone-Workflows (Alternative: kopieren)
|
*.example.yml standalone-Workflows (Alternative: kopieren)
|
||||||
```
|
```
|
||||||
|
|
||||||
Keine Abhängigkeiten — reine Python-Standardbibliothek, kein `pip install`.
|
## Interaktiver Bot (`bot/`)
|
||||||
|
|
||||||
## In ein Repo einbauen
|
Schlanker **FastAPI-Webhook-Dienst**, läuft als Docker-Container neben Gitea (auf dem Pi,
|
||||||
|
`web_network`). Erreicht die Gitea-API **intern** über `http://gitea:3000` (kein TLS, kein
|
||||||
|
Hairpin) und das KI-Backend (Jetson-Ollama) LAN-direkt → Antwort in Sekunden, ganz ohne
|
||||||
|
Runner/Clone. Teilt sich die Logik mit der CI-Variante (`scripts/{llm,gitea_api,ai_review}.py`).
|
||||||
|
|
||||||
|
**Events**
|
||||||
|
- `issue_comment` mit `@ci-ai <Frage>` → Chat-Antwort im Thread (Thread- + Diff-Kontext, neuer Kommentar).
|
||||||
|
- `pull_request` (opened / synchronized / reopened) → idempotentes KI-Review (gleicher Prompt wie Feature A).
|
||||||
|
|
||||||
|
**Sicherheit:** HMAC-Signaturcheck (`WEBHOOK_SECRET`); `BackgroundTasks` → sofort `200`,
|
||||||
|
LLM-Arbeit im Hintergrund (kein Gitea-Webhook-Timeout).
|
||||||
|
|
||||||
|
**Bauen** (vom Repo-ROOT — der Dockerfile kopiert `scripts/` + `bot/app.py`):
|
||||||
|
```bash
|
||||||
|
docker build -f bot/Dockerfile -t ci-ai-bot:latest .
|
||||||
|
```
|
||||||
|
|
||||||
|
**Deployment & Gitea-Webhook:** Der Bot wird als Service im `docker_stack` des Schwester-Repos
|
||||||
|
**ansible-eulernest** deployt — Details, Vars und Troubleshooting dort in
|
||||||
|
[`docs/ci-ai-bot.md`](https://git.eulernest.eu/user2595/ansible-eulernest/src/branch/master/docs/ci-ai-bot.md).
|
||||||
|
Webhook (Org- oder Repo-Ebene): URL `http://ci-ai-bot:8080/webhook`, Events `issue_comment` +
|
||||||
|
`pull_request`, Secret = `WEBHOOK_SECRET`. ⚠️ Giteas SSRF-Schutz blockt Zustellung an private
|
||||||
|
IPs — `[webhook] ALLOWED_HOST_LIST` muss den Bot-Host (`ci-ai-bot`) erlauben.
|
||||||
|
|
||||||
|
**Env-Vars (Bot)**
|
||||||
|
| Var | Beispiel | Zweck |
|
||||||
|
|---|---|---|
|
||||||
|
| `BOT_NAME` | `ci-ai` | Mention-Trigger (`@ci-ai`) |
|
||||||
|
| `WEBHOOK_SECRET` | — | HMAC-Secret, muss zum Gitea-Webhook passen (leer = Check aus) |
|
||||||
|
| `GITEA_TOKEN` | — | Token fürs Kommentieren (`write:issue`) |
|
||||||
|
| `GITEA_API` | `http://gitea:3000/api/v1` | Gitea-API (intern, kein Hairpin) |
|
||||||
|
| `AI_PROVIDER` / `AI_MODEL` / `AI_BASE_URL` | `ollama` / `coder` / `http://192.168.178.103:11434/v1` | KI-Backend (Jetson) |
|
||||||
|
|
||||||
|
## In ein Repo einbauen (CI-Workflows)
|
||||||
|
|
||||||
### Empfohlen: globaler Aufruf (reusable workflow)
|
### Empfohlen: globaler Aufruf (reusable workflow)
|
||||||
|
|
||||||
Nur **eine** Datei ins Ziel-Repo: `workflows/caller.example.yml` →
|
Nur **eine** Datei ins Ziel-Repo: `workflows/caller.example.yml` → `.gitea/workflows/ci-ai.yml`.
|
||||||
`.gitea/workflows/ci-ai.yml`. Sie ruft die zentrale Logik aus diesem Repo auf
|
Sie ruft die zentrale Logik auf (`uses: user2595/ci-ai/.gitea/workflows/reusable-*.yml@main`
|
||||||
(`uses: user2595/ci-ai/.gitea/workflows/reusable-*.yml@main` + `secrets: inherit`).
|
+ `secrets: inherit`). Logik-Updates passieren **einmal hier**, alle Repos ziehen automatisch nach.
|
||||||
Logik-Updates passieren **einmal hier**, alle Repos ziehen automatisch nach.
|
|
||||||
|
|
||||||
Einmalig zentral einzurichten:
|
Einmalig zentral:
|
||||||
|
|
||||||
1. Runner **instanzweit** registrieren (Admin → Actions → Runners), Label `eulernest`
|
1. Runner **instanzweit** registrieren (Admin → Actions → Runners), Label `eulernest`
|
||||||
— sonst haben Repos außerhalb der org keinen Runner.
|
— sonst haben Repos außerhalb der org keinen Runner.
|
||||||
2. **Anonymen Clone für public Repos** erlauben (`REQUIRE_SIGNIN_VIEW=false`) — der Job
|
2. **Anonymen Clone für public Repos** erlauben (`REQUIRE_SIGNIN_VIEW=false`) — der Job
|
||||||
klont `ci-ai` für die Skripte.
|
klont `ci-ai` für die Skripte.
|
||||||
3. `ANTHROPIC_API_KEY` als **Org-/Instanz-Actions-Secret** (KI-Schritte; ohne ihn laufen
|
3. KI-Backend = Jetson-Ollama (LAN, self-hosted) → **kein Secret nötig**. (Anthropic-Variante:
|
||||||
die Scans normal, die KI-Teile überspringen sich sauber).
|
`ANTHROPIC_API_KEY` als Org-/Instanz-Actions-Secret; ohne Key überspringen sich die KI-Teile sauber.)
|
||||||
4. Für „hart blockieren": `security`-Check in der Branch-Protection als *required*.
|
4. Für „hart blockieren": `security`-Check in der Branch-Protection als *required*.
|
||||||
|
|
||||||
### Alternative: standalone (kopieren)
|
### Alternative: standalone (kopieren)
|
||||||
|
|
||||||
`workflows/ai-review.example.yml` + `workflows/security.example.yml` direkt nach
|
`workflows/ai-review.example.yml` + `workflows/security.example.yml` direkt nach
|
||||||
`.gitea/workflows/` kopieren. Enthält die volle Logik (klont die Skripte selbst) —
|
`.gitea/workflows/` kopieren — volle Logik (klont die Skripte selbst), mehr Copy-Paste,
|
||||||
mehr Copy-Paste, aber unabhängig von reusable-workflow-Support.
|
aber unabhängig von reusable-workflow-Support.
|
||||||
|
|
||||||
## Umstieg auf den Jetson (später)
|
## KI-Backend: Jetson (self-hosted, im Betrieb)
|
||||||
|
|
||||||
Sobald das lokale Ollama auf dem Jetson läuft — nur die Workflow-Env ändern,
|
Das lokale Ollama auf dem Jetson Orin ist das aktive Backend — kein Anthropic-Key, keine
|
||||||
**kein Code-Change**:
|
Kosten, kein Datenabfluss. In Workflows **und** Bot per Env gesetzt:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
AI_PROVIDER: ollama
|
AI_PROVIDER: ollama
|
||||||
AI_MODEL: qwen-light
|
AI_MODEL: coder # qwen2.5-coder:7b
|
||||||
AI_BASE_URL: http://192.168.178.20:11434/v1
|
AI_BASE_URL: http://192.168.178.103:11434/v1
|
||||||
JETSON_API_KEY: ${{ secrets.JETSON_API_KEY }}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Provider-Wechsel = **nur Env**, kein Code-Change (`scripts/llm.py`). Anthropic-Variante:
|
||||||
|
`AI_PROVIDER=anthropic` + `ANTHROPIC_API_KEY`.
|
||||||
|
|
||||||
## Konfiguration (Env-Vars)
|
## Konfiguration (Env-Vars)
|
||||||
|
|
||||||
| Var | Default | Zweck |
|
> Hinweis: Die **Code-Defaults in `llm.py` sind noch der alte Stand** (Anthropic, Jetson-IP
|
||||||
|
> `.20`, `qwen-light`). Im Betrieb überschreiben Workflows + Bot diese per Env mit den
|
||||||
|
> Jetson-Werten (`ollama` / `coder` / `.103`). Ein Refresh der Defaults ist vorgemerkt.
|
||||||
|
|
||||||
|
| Var | Default (`llm.py`) | Im Betrieb |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `AI_PROVIDER` | `anthropic` | `anthropic` oder `ollama` |
|
| `AI_PROVIDER` | `anthropic` | **`ollama`** |
|
||||||
| `AI_MODEL` | `claude-3-5-haiku-latest` / `qwen-light` | Modell |
|
| `AI_MODEL` | `claude-3-5-haiku-latest` / `qwen-light` | **`coder`** |
|
||||||
| `ANTHROPIC_API_KEY` | — | Pflicht bei provider=anthropic |
|
| `AI_BASE_URL` | `http://192.168.178.20:11434/v1` *(veraltet)* | **`…178.103…`** |
|
||||||
| `AI_BASE_URL` | `http://192.168.178.20:11434/v1` | Ollama-Endpoint |
|
|
||||||
| `JETSON_API_KEY` | — | Bearer für Ollama (optional) |
|
| `JETSON_API_KEY` | — | Bearer für Ollama (optional) |
|
||||||
| `GITEA_API` | `https://git.eulernest.eu/api/v1` | Gitea-API-Basis |
|
| `ANTHROPIC_API_KEY` | — | nur bei provider=anthropic |
|
||||||
|
| `GITEA_API` | `https://git.eulernest.eu/api/v1` | Bot: intern `http://gitea:3000/api/v1` |
|
||||||
| `GITEA_TOKEN` / `GITEA_BOT_TOKEN` | — | Token fürs Kommentieren |
|
| `GITEA_TOKEN` / `GITEA_BOT_TOKEN` | — | Token fürs Kommentieren |
|
||||||
|
|
||||||
## Constraints
|
## Constraints
|
||||||
|
|
||||||
- Runner hat **capacity 1** → Jobs laufen seriell.
|
- **CI-Pfad:** Runner hat **capacity 1** → Jobs laufen seriell; Docker-Executor abgesichert
|
||||||
- Docker-Executor abgesichert (kein privileged, keine Host-Mounts) → Trivy
|
(kein privileged, keine Host-Mounts) → Trivy `fs`/`config` + Gitleaks laufen, Scans
|
||||||
`fs`/`config` + Gitleaks laufen; Scans *laufender* Container nicht (unnötig).
|
*laufender* Container nicht (unnötig).
|
||||||
- Kosten (Anthropic, Haiku-Klasse, gedeckelter Diff): Bruchteile eines Cent/PR.
|
- **Bot-Pfad:** umgeht Runner/Clone/Hairpin komplett (interner `gitea:3000`-Zugriff).
|
||||||
|
- **Modell:** `qwen2.5-coder:7b` (Jetson, GPU) — brauchbar, ein paar dt. Patzer, < Claude.
|
||||||
|
Kosten **0** pro Call (self-hosted). Anthropic-Variante: Bruchteile eines Cent/PR (Haiku-Klasse).
|
||||||
|
|||||||
29
bot/README.md
Normal file
29
bot/README.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# ci-ai-bot — interaktiver Gitea-Webhook-Bot
|
||||||
|
|
||||||
|
Feature **F** der ci-ai-Schicht: ein schlanker FastAPI-Dienst, der auf Gitea-Webhooks
|
||||||
|
reagiert und in **Sekunden** antwortet — ohne Runner, Clone oder NAT-Hairpin.
|
||||||
|
|
||||||
|
- `@ci-ai <Frage>` in einem Issue/PR → Chat-Antwort im Thread (Thread- + Diff-Kontext).
|
||||||
|
- `pull_request` opened/synchronized/reopened → idempotentes KI-Review.
|
||||||
|
|
||||||
|
Läuft als Container neben Gitea (`web_network`): Gitea-API **intern** über `http://gitea:3000`,
|
||||||
|
KI-Backend (Jetson-Ollama) LAN-direkt. Wiederverwendet `../scripts/{llm,gitea_api,ai_review}.py`.
|
||||||
|
|
||||||
|
## Bauen (vom Repo-ROOT, nicht aus `bot/`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -f bot/Dockerfile -t ci-ai-bot:latest .
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Dockerfile kopiert `scripts/` + `bot/app.py` → Build-Context muss das Repo-Root sein.
|
||||||
|
|
||||||
|
## Env
|
||||||
|
|
||||||
|
`BOT_NAME` · `WEBHOOK_SECRET` (HMAC) · `GITEA_TOKEN` · `GITEA_API` (intern `http://gitea:3000/api/v1`)
|
||||||
|
· `AI_PROVIDER` / `AI_MODEL` / `AI_BASE_URL` (Jetson). Endpunkte: `GET /healthz`, `POST /webhook`.
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
Als Service im `docker_stack` des Repos **ansible-eulernest** — Compose-Service, Vars, der
|
||||||
|
nötige Gitea-`ALLOWED_HOST_LIST`-Fix, Webhook-Einrichtung und Troubleshooting stehen dort in
|
||||||
|
`docs/ci-ai-bot.md`. Siehe auch die `bot/`-Sektion in der [Haupt-README](../README.md).
|
||||||
Reference in New Issue
Block a user