update-1.6.2
ci / test (services/knowledge) (push) Successful in 47s
ci / test (services/agent) (push) Successful in 55s
ci / test (platform/neuroforge) (push) Successful in 56s
ci / docker-build (push) Failing after 1m10s
ci / test (services/control) (push) Successful in 1m26s
release-tag / release-image (push) Successful in 6m43s
ci / test (services/knowledge) (push) Successful in 47s
ci / test (services/agent) (push) Successful in 55s
ci / test (platform/neuroforge) (push) Successful in 56s
ci / docker-build (push) Failing after 1m10s
ci / test (services/control) (push) Successful in 1m26s
release-tag / release-image (push) Successful in 6m43s
This commit is contained in:
@@ -74,6 +74,12 @@ OPENAI_API_KEY=
|
||||
# qwen3:8b statt gemma3 setzen. Das Modell muss vorher in Ollama vorhanden sein.
|
||||
OLLAMA_MODEL=gemma3
|
||||
OLLAMA_EMBEDDING_MODEL=embeddinggemma
|
||||
# Optional: Bearer token sent as Authorization: Bearer <token> to Ollama-compatible endpoints.
|
||||
OLLAMA_API_KEY=
|
||||
# Optional NeuroForge GPU-worker override; falls back to OLLAMA_API_KEY.
|
||||
NEUROFORGE_WORKER_OLLAMA_API_KEY=
|
||||
# Optional NeuroForge-only override; takes precedence over OLLAMA_API_KEY.
|
||||
NEUROFORGE_OLLAMA_API_KEY=
|
||||
OLLAMA_TIMEOUT=10m
|
||||
OLLAMA_MAX_CONCURRENT=1
|
||||
OLLAMA_NUM_PREDICT=768
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Remote NeuroForge subagent host (v1.6.1)
|
||||
IMAGE_TAG=1.6.1
|
||||
# Remote NeuroForge subagent host (v1.6.2)
|
||||
IMAGE_TAG=1.6.2
|
||||
NEUROFORGE_MASTER_URL=https://neuroforge.internal.example
|
||||
NEUROFORGE_WORKER_TOKEN=CHANGE_ME_WORKER_TOKEN_SHARED_WITH_MASTER
|
||||
NEUROFORGE_WORKER_HEARTBEAT_INTERVAL=15s
|
||||
@@ -16,5 +16,10 @@ NEUROFORGE_GPU_WORKER_CONCURRENCY=1
|
||||
NEUROFORGE_WORKER_OLLAMA_URL=http://host.docker.internal:11434
|
||||
OLLAMA_MODEL=gemma4
|
||||
OLLAMA_EMBEDDING_MODEL=embeddinggemma
|
||||
# Optional: Bearer token sent as Authorization: Bearer <token> to Ollama-compatible endpoints.
|
||||
OLLAMA_API_KEY=
|
||||
NEUROFORGE_OLLAMA_NUM_CTX=8192
|
||||
OLLAMA_KEEP_ALIVE=10m
|
||||
|
||||
# Optional GPU-worker-specific override; falls back to OLLAMA_API_KEY.
|
||||
NEUROFORGE_WORKER_OLLAMA_API_KEY=
|
||||
|
||||
+8
-55
@@ -1,4 +1,4 @@
|
||||
name: mega-ci
|
||||
name: ci
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
go-quality:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -18,69 +18,22 @@ jobs:
|
||||
- services/knowledge
|
||||
- services/control
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: ${{ matrix.module }}/go.mod
|
||||
cache-dependency-path: ${{ matrix.module }}/go.mod
|
||||
|
||||
cache: false
|
||||
- name: Test
|
||||
working-directory: ${{ matrix.module }}
|
||||
run: go test ./...
|
||||
|
||||
- name: Vet
|
||||
working-directory: ${{ matrix.module }}
|
||||
run: go vet ./...
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{ matrix.module }}
|
||||
run: go build ./...
|
||||
|
||||
static-release-gates:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install PyYAML
|
||||
run: python3 -m pip install --user PyYAML
|
||||
|
||||
- name: Static production preflight
|
||||
run: ./scripts/preflight.sh --static
|
||||
|
||||
- name: Compose environment isolation
|
||||
run: ./scripts/check-compose-env.py
|
||||
|
||||
- name: Secret scan
|
||||
run: ./scripts/secret-scan.sh
|
||||
|
||||
- name: YAML syntax check
|
||||
shell: bash
|
||||
run: |
|
||||
python3 - <<'PY'
|
||||
from pathlib import Path
|
||||
import yaml
|
||||
files = list(Path('.').rglob('*.yml')) + list(Path('.').rglob('*.yaml'))
|
||||
for p in files:
|
||||
if '.git' in p.parts:
|
||||
continue
|
||||
with p.open(encoding='utf-8') as f:
|
||||
yaml.safe_load(f)
|
||||
print(f'parsed {len(files)} YAML files')
|
||||
PY
|
||||
|
||||
docker-build:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [go-quality, static-release-gates]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build all project images
|
||||
- uses: actions/checkout@v4
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- name: Build all release images
|
||||
run: docker buildx bake --set '*.output=type=cacheonly'
|
||||
|
||||
+16
-119
@@ -1,4 +1,4 @@
|
||||
name: mega-release
|
||||
name: release
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -10,136 +10,33 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
REGISTRY: git.send.nrw/sendnrw
|
||||
RUNNER_TOOL_CACHE: /toolcache
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Validate immutable release version
|
||||
id: meta
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
REF_NAME="${GITEA_REF_NAME:-${GITHUB_REF_NAME:-}}"
|
||||
VERSION="${REF_NAME#v}"
|
||||
test -n "$VERSION"
|
||||
FILE_VERSION="$(tr -d '\r\n' < VERSION)"
|
||||
test "$VERSION" = "$FILE_VERSION" || {
|
||||
echo "Tag version $VERSION does not match VERSION=$FILE_VERSION" >&2
|
||||
exit 1
|
||||
}
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/setup-qemu-action@v3
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
config-inline: |
|
||||
[registry."git.send.nrw"]
|
||||
http = true
|
||||
insecure = true
|
||||
|
||||
- name: Login to registry
|
||||
uses: docker/login-action@v3
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
registry: git.send.nrw
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
|
||||
- name: Push NeuroForge server
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ./platform/neuroforge
|
||||
file: ./platform/neuroforge/Dockerfile
|
||||
target: server
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/glpi-neuroforge-mega-neuroforge:${{ steps.meta.outputs.version }}
|
||||
|
||||
- name: Push NeuroForge worker
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ./platform/neuroforge
|
||||
file: ./platform/neuroforge/Dockerfile
|
||||
target: worker
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/glpi-neuroforge-mega-neuroforge-worker:${{ steps.meta.outputs.version }}
|
||||
|
||||
- name: Push Agent
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ./services/agent
|
||||
file: ./services/agent/Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/glpi-neuroforge-mega-agent:${{ steps.meta.outputs.version }}
|
||||
|
||||
- name: Push Agent data-init
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ./services/agent
|
||||
file: ./services/agent/Dockerfile
|
||||
target: data-init
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/glpi-neuroforge-mega-agent-data-init:${{ steps.meta.outputs.version }}
|
||||
|
||||
- name: Push Knowledge
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ./services/knowledge
|
||||
file: ./services/knowledge/Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/glpi-neuroforge-mega-knowledge:${{ steps.meta.outputs.version }}
|
||||
|
||||
- name: Push Control
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ./services/control
|
||||
file: ./services/control/Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: ${{ env.REGISTRY }}/glpi-neuroforge-mega-control:${{ steps.meta.outputs.version }}
|
||||
|
||||
release-archive:
|
||||
needs: release-images
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Validate and package
|
||||
- name: Resolve immutable release version
|
||||
id: meta
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
REF_NAME="${GITEA_REF_NAME:-${GITHUB_REF_NAME:-}}"
|
||||
VERSION="${REF_NAME#v}"
|
||||
test "$VERSION" = "$(tr -d '\r\n' < VERSION)"
|
||||
./scripts/secret-scan.sh
|
||||
find . -type f ! -path './.git/*' ! -name MANIFEST.sha256 -print0 \
|
||||
| sort -z \
|
||||
| xargs -0 sha256sum > MANIFEST.sha256
|
||||
ROOT_DIR="$(pwd)"
|
||||
PKG_DIR="../glpi-neuroforge-mega-v${VERSION}-gitea"
|
||||
rm -rf "$PKG_DIR"
|
||||
mkdir -p "$PKG_DIR"
|
||||
tar --exclude=.git -cf - . | (cd "$PKG_DIR" && tar -xf -)
|
||||
cd ..
|
||||
zip -qr "glpi-neuroforge-mega-v${VERSION}-gitea.zip" "glpi-neuroforge-mega-v${VERSION}-gitea"
|
||||
sha256sum "glpi-neuroforge-mega-v${VERSION}-gitea.zip" > "glpi-neuroforge-mega-v${VERSION}-gitea.zip.sha256"
|
||||
|
||||
- name: Upload release archive
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: glpi-neuroforge-mega-${{ gitea.ref_name }}
|
||||
path: |
|
||||
../glpi-neuroforge-mega-*.zip
|
||||
../glpi-neuroforge-mega-*.zip.sha256
|
||||
VERSION="$(git describe --tags --exact-match HEAD | sed 's/^v//')"
|
||||
test -n "$VERSION"
|
||||
test "$(cat VERSION)" = "$VERSION"
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
- name: Build and push all release images
|
||||
env:
|
||||
IMAGE_TAG: ${{ steps.meta.outputs.version }}
|
||||
run: docker buildx bake --push
|
||||
|
||||
+18
-29
@@ -1,10 +1,8 @@
|
||||
# Build & Release — GLPI NeuroForge Mega v1.6.2
|
||||
# Build and Release
|
||||
|
||||
The repository has one canonical image/release pipeline. Nested service workflows are intentionally not used.
|
||||
## Repository image set
|
||||
|
||||
## Project images
|
||||
|
||||
`docker buildx bake` builds all six immutable project images:
|
||||
The repository publishes six immutable project images:
|
||||
|
||||
- `glpi-neuroforge-mega-neuroforge`
|
||||
- `glpi-neuroforge-mega-neuroforge-worker`
|
||||
@@ -13,42 +11,33 @@ The repository has one canonical image/release pipeline. Nested service workflow
|
||||
- `glpi-neuroforge-mega-knowledge`
|
||||
- `glpi-neuroforge-mega-control`
|
||||
|
||||
Default registry/tag:
|
||||
The default registry is `git.send.nrw/sendnrw`.
|
||||
|
||||
```text
|
||||
git.send.nrw/sendnrw/<image>:1.6.2
|
||||
```
|
||||
## Local build
|
||||
|
||||
Override without editing the bake file:
|
||||
From repository root:
|
||||
|
||||
```bash
|
||||
IMAGE_TAG=1.6.2 REGISTRY=git.send.nrw/sendnrw docker buildx bake
|
||||
docker buildx bake
|
||||
```
|
||||
|
||||
## Gitea Actions
|
||||
Override registry/tag when required:
|
||||
|
||||
`.gitea/workflows/ci.yml` runs test/vet/build for all four Go modules, static release checks and Docker builds.
|
||||
|
||||
`.gitea/workflows/release.yml` runs only on immutable `v*` tags. The tag must exactly match the root `VERSION` file. It pushes all six project images with the version tag only; no production dependency on `latest` is introduced.
|
||||
|
||||
Required registry secrets:
|
||||
|
||||
```text
|
||||
DOCKER_USERNAME
|
||||
DOCKER_PASSWORD
|
||||
```bash
|
||||
REGISTRY=registry.example/org IMAGE_TAG=1.6.2 docker buildx bake
|
||||
```
|
||||
|
||||
Release example:
|
||||
## CI
|
||||
|
||||
`.gitea/workflows/ci.yml` runs `go test ./...` and `go vet ./...` for NeuroForge, Agent, Knowledge and Control and then performs a Buildx Bake build of the full image set.
|
||||
|
||||
## Release
|
||||
|
||||
Set `VERSION` and all compose/deployment tags to the desired immutable version, then push a matching tag:
|
||||
|
||||
```bash
|
||||
git tag v1.6.2
|
||||
git push origin v1.6.2
|
||||
```
|
||||
|
||||
## Local source gate
|
||||
|
||||
```bash
|
||||
./scripts/release-gate.sh
|
||||
```
|
||||
|
||||
This executes static production checks, Compose environment isolation, secret scanning, graph reproducibility, test/vet/build for every module, and targeted race checks.
|
||||
`.gitea/workflows/release.yml` verifies that the git tag matches `VERSION` and pushes all six images with tag `1.6.2`. Project production compose files do not require `latest`.
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
# Distributed Deployment — v1.6.2
|
||||
# v1.6.2 Distributed Deployment Bundle
|
||||
|
||||
The distributed deployment is part of the canonical release, not a separate code fork.
|
||||
This is the unchanged v1.6.2 application release plus ready-to-edit deployment kits under `deployments/`. No application code or persisted data format was changed.
|
||||
|
||||
- `deployments/master`: authoritative NeuroForge Master, GLPI Agent, Knowledge, Control, optional Research, Prometheus and Grafana.
|
||||
- `deployments/cpu-subagent`: remote CPU execution node for graph/relink work.
|
||||
- `deployments/gpu-subagent`: remote GPU execution node plus Ollama for chat/embedding work.
|
||||
|
||||
All three roles ship with complete `.env` templates containing `CHANGE_ME_...` placeholders. The same real `NEUROFORGE_WORKER_TOKEN` must be configured on Master and both worker roles.
|
||||
|
||||
Recommended startup order: GPU subagent, CPU subagent, then Master. Keep the NeuroForge data volume authoritative on the Master only.
|
||||
|
||||
The v1.6.1 recovery/OOM fixes are included unchanged in v1.6.2. Do not delete the existing `neuroforge-data` volume when upgrading.
|
||||
See `deployments/README.md`.
|
||||
|
||||
+645
-640
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,13 @@
|
||||
# GLPI NeuroForge Mega v1.6.2
|
||||
# GLPI NeuroForge Mega v1.6.1
|
||||
|
||||
> Release: **v1.6.2** · kanonischer Vollrelease mit Full-Mega-, Distributed- und Standalone-Betriebsmodi. Die v1.6.1 Recovery/OOM-Härtung ist vollständig enthalten.
|
||||
|
||||
v1.6.2 konsolidiert die zuvor getrennten Pakete wieder in **ein vollständiges Monorepo**: NeuroForge Master, CPU/GPU-Subagents, GLPI Agent, Knowledge, Control, Ollama, Research sowie eigenständig betreibbare Agent-/Knowledge-/Ollama-Core-Kits und Prometheus/Grafana-Beispiele.
|
||||
> Release: **v1.6.1** · Crash-/Recovery-Hardening für große Knowledge-Korpora auf Basis der v1.6.0 Master/Subagent- und n:m-Graph-Architektur.
|
||||
|
||||
Ein kontrolliertes Monorepo aus **GLPI AI Agent**, **GLPI AI Knowledgebase** und **NeuroForge + SQAR**. Ziel ist nicht ein untrennbarer Monolith, sondern eine gemeinsame Plattform mit klaren Zuständigkeiten, getrennten Credentials und nachvollziehbaren Failure-Modi.
|
||||
|
||||
|
||||
## Crash-/Recovery-Hardening (seit v1.6.1, in v1.6.2 enthalten)
|
||||
## Crash-/Recovery-Hardening (v1.6.1)
|
||||
|
||||
Die in v1.6.1 eingeführte Härtung behebt einen RAM-/Recovery-Fehler, der bei großen Graph-Backfills nach mehreren Stunden Laufzeit auftreten konnte. Erfolgreiche `vector.relink`-Jobs verwerfen ihre großen transienten Vector-Payloads unmittelbar nach dem autoritativen Master-Apply; ein Queue-Payload-Budget verhindert neue ungebremste Speicherbelegung. Beim ersten Start migriert NeuroForge alte v1.6.0-Checkpoints streaming, bevor `state.json` vollständig in den RAM geladen wird.
|
||||
v1.6.1 behebt einen RAM-/Recovery-Fehler, der bei großen Graph-Backfills nach mehreren Stunden Laufzeit auftreten konnte. Erfolgreiche `vector.relink`-Jobs verwerfen ihre großen transienten Vector-Payloads unmittelbar nach dem autoritativen Master-Apply; ein Queue-Payload-Budget verhindert neue ungebremste Speicherbelegung. Beim ersten Start migriert NeuroForge alte v1.6.0-Checkpoints streaming, bevor `state.json` vollständig in den RAM geladen wird.
|
||||
|
||||
HNSW-Deltas werden nicht mehr über einen vollständigen Deep-Copy aller Vektoren erzeugt. Checkpoints persistieren den Index vor `state.json`, sodass ein Crash während der Indexpersistenz den autoritativen Checkpoint nicht vor den verwendbaren Index ziehen kann. Bei Containerstarts mit explizitem `-listen` ist sofort eine Bootstrap-Liveness-/Startup-Seite erreichbar; Logs zeigen die aktuelle Recovery-Phase (`checkpoint.precompact`, `memory-segments.scan`, `wal.replay`, `hnsw.snapshot.load`, `hnsw.rebuild`, ...).
|
||||
|
||||
@@ -34,22 +32,6 @@ Lokale Standardrollen:
|
||||
|
||||
Zusätzliche Hosts können mit `docker-compose.subagent.yml` angebunden werden. Das Control Center bleibt read-only und zeigt Master-, Worker-, Queue- und Graphzustand. Details: [`docs/MASTER-SUBAGENT-ORCHESTRATOR.md`](docs/MASTER-SUBAGENT-ORCHESTRATOR.md).
|
||||
|
||||
|
||||
## Deployment-Modi
|
||||
|
||||
Das Repository enthält bewusst mehrere, voneinander entkoppelte Betriebsformen:
|
||||
|
||||
- `docker-compose.yml`: vollständiger Mega-Stack mit lokalem CPU- und GPU-Worker.
|
||||
- `deployments/master`: autoritativer Master ohne lokale Worker, für getrennte CPU-/GPU-Hosts.
|
||||
- `deployments/cpu-subagent`: abgesetzter CPU-Worker für `cpu,vector.relink`.
|
||||
- `deployments/gpu-subagent`: abgesetzter GPU-Worker plus Ollama für `gpu,model.chat,model.embed`.
|
||||
- `deployments/agent`: GLPI Agent standalone mit lokalem Vector-Backend, ohne NeuroForge-Zwang.
|
||||
- `deployments/knowledge`: Knowledge standalone.
|
||||
- `deployments/ollama`: Ollama standalone.
|
||||
- `deployments/combined`: Agent + Knowledge + Ollama ohne NeuroForge.
|
||||
|
||||
Details: [`deployments/README.md`](deployments/README.md).
|
||||
|
||||
## Unified Graph Explorer (v1.4.0)
|
||||
|
||||
Das read-only Control Center visualisiert Runtime/Trust, Ticket-Evidence, Learning-Lineage, Research-Provenance, einen redigierten NeuroForge-Brain-Graph sowie einen reproduzierbaren Engineering-Graph aus Go-AST und Compose. Für Dateien/Symbole/Routen gibt es zusätzlich eine statische Change-Impact-/Blast-Radius-Sicht. 2D ist der operative Default; 3D ist ein optionaler, gebundener Explorer.
|
||||
@@ -270,3 +252,15 @@ NEUROFORGE_GOAL_LEARNING_ENABLED=true
|
||||
```
|
||||
|
||||
Damit bleibt rohes Chat-/Assistant-Lernen weiterhin deaktiviert.
|
||||
|
||||
## v1.6.2: vollständige Deployment-Rollen und Ollama Bearer Auth
|
||||
|
||||
Das Repository enthält neben dem vollständigen Mega-Compose eigenständige Kits unter `deployments/` für `master`, `cpu-subagent`, `gpu-subagent`, `agent`, `knowledge`, `ollama` und `combined`. Damit können der produktive GLPI-Agent/Knowledge-Core und die NeuroForge-Orchestrierung getrennt oder gemeinsam betrieben werden.
|
||||
|
||||
Für Ollama-kompatible Endpunkte hinter einem Bearer-geschützten Gateway kann optional gesetzt werden:
|
||||
|
||||
```env
|
||||
OLLAMA_API_KEY=
|
||||
```
|
||||
|
||||
Agent, Knowledge, NeuroForge und modellfähige Subagents senden den Wert dann als `Authorization: Bearer ...`. NeuroForge kennt zusätzlich `NEUROFORGE_OLLAMA_API_KEY` und `NEUROFORGE_WORKER_OLLAMA_API_KEY` als rollenbezogene Overrides. Details: `docs/OLLAMA-BEARER-AUTH.md`.
|
||||
|
||||
+49
-28
@@ -1,38 +1,59 @@
|
||||
# GLPI NeuroForge Mega v1.6.2
|
||||
|
||||
## Consolidated production release
|
||||
v1.6.2 is the consolidated production release that reunifies the full Mega repository after the stripped-core packaging experiment and adds authenticated Ollama-compatible endpoints.
|
||||
|
||||
v1.6.2 is the canonical full repository release. It keeps the v1.6.1 runtime/data format and recovery/OOM hotfixes, and consolidates all previously split deployment variants into one coherent tree.
|
||||
## Complete deployment set
|
||||
|
||||
### Included deployment modes
|
||||
The repository now ships all supported operating modes together:
|
||||
|
||||
- Full Mega stack with local CPU/GPU workers.
|
||||
- Distributed Master with remote CPU and GPU subagents.
|
||||
- Standalone GLPI Agent using a local vector backend.
|
||||
- Standalone Knowledge service.
|
||||
- Standalone Ollama.
|
||||
- Combined Agent + Knowledge + Ollama core stack without NeuroForge.
|
||||
- Prometheus/Grafana example monitoring on the distributed Master, including payload/queue/graph metrics and alerts.
|
||||
- full single-host Mega stack,
|
||||
- distributed Master,
|
||||
- remote CPU subagent,
|
||||
- remote GPU subagent with Ollama,
|
||||
- standalone GLPI Agent,
|
||||
- standalone Knowledge service,
|
||||
- standalone Ollama,
|
||||
- combined Agent + Knowledge + Ollama core,
|
||||
- Prometheus/Grafana example monitoring on the Master.
|
||||
|
||||
### Packaging / CI corrections
|
||||
The v1.6.1 recovery/OOM hardening remains included, including startup pre-compaction of legacy relink payloads, bounded durable-job payload retention, lower graph-backfill pressure, visible startup phases, stricter corrupted-state handling and HNSW checkpoint memory reductions.
|
||||
|
||||
- Restores the complete v1.6.1 NeuroForge recovery hardening and graph/orchestrator source tree.
|
||||
- Includes complete `.env` files with safe placeholders for Master, CPU subagent, GPU subagent and standalone roles.
|
||||
- Removes embedded real credentials from deployment examples.
|
||||
- Replaces conflicting release workflows with one root CI workflow and one immutable tag release workflow.
|
||||
- CI covers NeuroForge, Agent, Knowledge and Control plus all six project images.
|
||||
- `docker-bake.hcl` builds NeuroForge server/worker, Agent, Agent data-init, Knowledge and Control.
|
||||
- Removes nested/legacy workflow copies and stale duplicate compose files.
|
||||
- Regenerates a single repository manifest for the exact final archive.
|
||||
## Ollama Bearer authentication
|
||||
|
||||
### Runtime safety inherited from v1.6.1
|
||||
A shared optional variable is now supported:
|
||||
|
||||
- Streaming compaction of legacy terminal `vector.relink` payloads before normal state loading.
|
||||
- Completed relink payload/result blobs are discarded after successful Master apply and during WAL recovery.
|
||||
- Bounded pending durable job payload bytes.
|
||||
- HNSW delta checkpoints avoid full graph/vector deep copies.
|
||||
- Index-first checkpoint ordering and fail-closed authoritative JSON recovery.
|
||||
- `/livez` and a bootstrap `/admin` page are available while store recovery is still running.
|
||||
- Conservative graph backfill and job-retention defaults for large Knowledge corpora.
|
||||
```env
|
||||
OLLAMA_API_KEY=
|
||||
```
|
||||
|
||||
There is no intentional persisted-data-format break from v1.6.1 to v1.6.2.
|
||||
When non-empty, clients send:
|
||||
|
||||
```http
|
||||
Authorization: Bearer <OLLAMA_API_KEY>
|
||||
```
|
||||
|
||||
for Ollama-compatible `/api/tags`, `/api/chat` and `/api/embed` requests.
|
||||
|
||||
Supported clients:
|
||||
|
||||
- GLPI Agent, including every node in its Ollama pool,
|
||||
- Knowledge AI fallback,
|
||||
- NeuroForge provider and readiness/provider-health checks,
|
||||
- NeuroForge GPU/model subagents.
|
||||
|
||||
NeuroForge also supports role-specific overrides:
|
||||
|
||||
```env
|
||||
NEUROFORGE_OLLAMA_API_KEY=
|
||||
NEUROFORGE_WORKER_OLLAMA_API_KEY=
|
||||
```
|
||||
|
||||
The role-specific value takes precedence over `OLLAMA_API_KEY` for the corresponding NeuroForge component.
|
||||
|
||||
The key is stored as a NeuroForge secret when configured through `NEUROFORGE_OLLAMA_API_KEY`; admin APIs expose only configured/masked status unless secret reveal is explicitly enabled.
|
||||
|
||||
Native Ollama does not itself become authenticated merely by setting this variable. The option is intended for an authenticated Ollama-compatible gateway/reverse proxy or another Ollama-compatible endpoint that validates Bearer tokens. Direct native Ollama deployments should normally leave it empty.
|
||||
|
||||
## CI/release consolidation
|
||||
|
||||
Gitea workflows now live only at repository root and cover all four Go modules. The release workflow builds and pushes the six immutable project images. `docker-bake.hcl` contains the same complete image set. Production compose files remain pinned through `IMAGE_TAG`; no `latest` project image is required.
|
||||
|
||||
+11
-24
@@ -1,29 +1,16 @@
|
||||
# Deployment Kits — GLPI NeuroForge Mega v1.6.2
|
||||
# Distributed Deployment Kits (v1.6.2)
|
||||
|
||||
This repository intentionally supports both the complete NeuroForge platform and stripped standalone operation.
|
||||
This directory contains three independent deployment kits:
|
||||
|
||||
## Complete / distributed platform
|
||||
- `master/` - authoritative NeuroForge Master plus Agent, Knowledge, Control, optional SearXNG and optional Prometheus/Grafana.
|
||||
- `cpu-subagent/` - CPU worker for `vector.relink` / graph convergence.
|
||||
- `gpu-subagent/` - GPU worker plus local Ollama for `model.chat` and `model.embed`.
|
||||
|
||||
- `master/` — authoritative NeuroForge Master plus GLPI Agent, Knowledge, Control, optional SearXNG, Prometheus and Grafana. Remote CPU/GPU workers connect to this node.
|
||||
- `cpu-subagent/` — disposable CPU worker with `cpu,vector.relink` capabilities.
|
||||
- `gpu-subagent/` — disposable GPU worker plus Ollama with `gpu,model.chat,model.embed` capabilities.
|
||||
The generated `NEUROFORGE_WORKER_TOKEN` is identical in all three `.env` files. Replace the RFC 5737 example IP addresses (`192.0.2.x`) with real reachable addresses before starting.
|
||||
|
||||
The Master is the only authoritative owner of NeuroForge state. CPU/GPU workers use leases, heartbeats and fenced job completion.
|
||||
Recommended order:
|
||||
1. GPU subagent: `docker compose --profile monitoring up -d`
|
||||
2. CPU subagent: `docker compose --profile monitoring up -d`
|
||||
3. Master: fill GLPI credentials, then `docker compose --profile research --profile monitoring up -d`
|
||||
|
||||
## Standalone core operation
|
||||
|
||||
- `agent/` — GLPI Agent with local Knowledge vector backend; no NeuroForge, Control or Research dependency.
|
||||
- `knowledge/` — standalone Knowledge editor/service.
|
||||
- `ollama/` — standalone Ollama runtime.
|
||||
- `combined/` — Agent + Knowledge + Ollama on one host, still without NeuroForge.
|
||||
|
||||
The standalone Agent and Knowledge kits share `runtime/knowledge` by default. The Agent mounts it read-only; Knowledge mounts it read-write.
|
||||
|
||||
## Release rules
|
||||
|
||||
- Project images are pinned by `IMAGE_TAG=1.6.2`; production compose files do not require `latest`.
|
||||
- Replace every `CHANGE_ME_...` placeholder before startup.
|
||||
- Never use `docker compose down -v` during an in-place upgrade unless loss of persistent state is intended.
|
||||
- For a v1.6.0/v1.6.1 NeuroForge data volume, keep the volume: v1.6.2 includes the v1.6.1 recovery/OOM hotfixes and startup compaction path.
|
||||
|
||||
See each role's README/preflight and the root `README.md` for startup order.
|
||||
Only the Master holds authoritative NeuroForge state. Workers are disposable execution nodes.
|
||||
|
||||
@@ -24,6 +24,7 @@ services:
|
||||
DATA_DIR: /app/data
|
||||
KNOWLEDGE_DIR: /app/knowledge
|
||||
KNOWLEDGE_VECTOR_BACKEND: local
|
||||
OLLAMA_API_KEY: ${OLLAMA_API_KEY:-}
|
||||
OUTCOME_LEARNING_ENABLED: "false"
|
||||
OUTCOME_RETRIEVAL_ENABLED: "false"
|
||||
BRAIN_ACTIVITY_URL: ""
|
||||
|
||||
@@ -33,6 +33,7 @@ services:
|
||||
DATA_DIR: /app/data
|
||||
KNOWLEDGE_DIR: /app/knowledge
|
||||
OLLAMA_URL: http://ollama:11434
|
||||
OLLAMA_API_KEY: ${OLLAMA_API_KEY:-}
|
||||
OLLAMA_URLS: http://ollama:11434
|
||||
KNOWLEDGE_VECTOR_BACKEND: local
|
||||
OUTCOME_LEARNING_ENABLED: "false"
|
||||
|
||||
@@ -39,6 +39,8 @@ services:
|
||||
NEUROFORGE_WORKER_MAX_CONCURRENCY: ${NEUROFORGE_GPU_WORKER_CONCURRENCY:-1}
|
||||
NEUROFORGE_WORKER_HEARTBEAT_INTERVAL: ${NEUROFORGE_WORKER_HEARTBEAT_INTERVAL:-15s}
|
||||
NEUROFORGE_WORKER_OLLAMA_URL: http://ollama:11434
|
||||
NEUROFORGE_WORKER_OLLAMA_API_KEY: ${NEUROFORGE_WORKER_OLLAMA_API_KEY:-}
|
||||
OLLAMA_API_KEY: ${OLLAMA_API_KEY:-}
|
||||
NEUROFORGE_WORKER_OLLAMA_CHAT_MODEL: ${OLLAMA_MODEL:-gemma4}
|
||||
NEUROFORGE_WORKER_OLLAMA_EMBEDDING_MODEL: ${OLLAMA_EMBEDDING_MODEL:-embeddinggemma}
|
||||
NEUROFORGE_WORKER_OLLAMA_NUM_CTX: ${NEUROFORGE_OLLAMA_NUM_CTX:-8192}
|
||||
|
||||
@@ -11,6 +11,7 @@ services:
|
||||
BACKUP_DIR: /data/backups
|
||||
STAGING_DIR: /data/staging
|
||||
LISTEN_ADDR: :8080
|
||||
OLLAMA_API_KEY: ${OLLAMA_API_KEY:-}
|
||||
BRAIN_ACTIVITY_URL: ""
|
||||
BRAIN_ACTIVITY_API_KEY: ""
|
||||
ports:
|
||||
|
||||
@@ -47,6 +47,8 @@ services:
|
||||
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
|
||||
NEUROFORGE_OLLAMA_URL: ${OLLAMA_BASE_URL:?Set Ollama URL reachable from master,
|
||||
normally GPU subagent host}
|
||||
NEUROFORGE_OLLAMA_API_KEY: ${NEUROFORGE_OLLAMA_API_KEY:-}
|
||||
OLLAMA_API_KEY: ${OLLAMA_API_KEY:-}
|
||||
NEUROFORGE_OLLAMA_CHAT_MODEL: ${OLLAMA_MODEL:-gemma3}
|
||||
NEUROFORGE_OLLAMA_EMBEDDING_MODEL: ${OLLAMA_EMBEDDING_MODEL:-embeddinggemma}
|
||||
NEUROFORGE_OLLAMA_NUM_CTX: ${NEUROFORGE_OLLAMA_NUM_CTX:-8192}
|
||||
@@ -317,6 +319,7 @@ services:
|
||||
OLLAMA_THINK: ${OLLAMA_THINK:-}
|
||||
OLLAMA_TIMEOUT: ${OLLAMA_TIMEOUT:-}
|
||||
OLLAMA_URL: ${OLLAMA_BASE_URL:?Set Ollama URL reachable from master}
|
||||
OLLAMA_API_KEY: ${OLLAMA_API_KEY:-}
|
||||
OLLAMA_URLS: ${OLLAMA_URLS:?Set one or more Ollama URLs reachable from master}
|
||||
OUTCOME_LEARNING_ENABLED: ${OUTCOME_LEARNING_ENABLED:-true}
|
||||
OUTCOME_LEARNING_FAIL_OPEN: ${OUTCOME_LEARNING_FAIL_OPEN:-false}
|
||||
@@ -389,6 +392,7 @@ services:
|
||||
BASIC_AUTH_PASSWORD: ${BASIC_AUTH_PASSWORD:?Set the Knowledge web password}
|
||||
AI_FALLBACK_ENABLED: ${AI_FALLBACK_ENABLED:-false}
|
||||
OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:?Set Ollama URL reachable from master}
|
||||
OLLAMA_API_KEY: ${OLLAMA_API_KEY:-}
|
||||
OLLAMA_MODEL: ${OLLAMA_MODEL:-gemma3}
|
||||
OLLAMA_TIMEOUT: ${OLLAMA_TIMEOUT:-10m}
|
||||
OLLAMA_MAX_CONCURRENT: ${OLLAMA_MAX_CONCURRENT:-2}
|
||||
|
||||
@@ -63,7 +63,7 @@ groups:
|
||||
labels: {severity: warning}
|
||||
annotations:
|
||||
summary: "NeuroForge pending job payload memory is high"
|
||||
description: "Pending durable job payload/result bytes exceed 100 MiB for 5 minutes; backfill should remain below the v1.6.2 safety budget."
|
||||
description: "Pending durable job payload/result bytes exceed 100 MiB for 5 minutes; backfill should remain below the v1.6.1 safety budget."
|
||||
|
||||
- alert: NeuroForgeTerminalPayloadHigh
|
||||
expr: neuroforge_job_payload_bytes{state="terminal"} > 67108864
|
||||
@@ -71,4 +71,4 @@ groups:
|
||||
labels: {severity: warning}
|
||||
annotations:
|
||||
summary: "NeuroForge terminal job payload retention is high"
|
||||
description: "Terminal job payload/result bytes exceed 64 MiB. Completed vector.relink jobs should be compacted automatically in v1.6.2."
|
||||
description: "Terminal job payload/result bytes exceed 64 MiB. Completed vector.relink jobs should be compacted automatically in v1.6.1."
|
||||
|
||||
+22
-23
@@ -7,51 +7,50 @@ variable "REGISTRY" {
|
||||
}
|
||||
|
||||
group "default" {
|
||||
targets = [
|
||||
"neuroforge",
|
||||
"neuroforge-worker",
|
||||
"agent",
|
||||
"agent-data-init",
|
||||
"knowledge",
|
||||
"control",
|
||||
]
|
||||
targets = ["neuroforge", "neuroforge-worker", "agent", "agent-data-init", "knowledge", "control"]
|
||||
}
|
||||
|
||||
target "neuroforge" {
|
||||
context = "./platform/neuroforge"
|
||||
context = "./platform/neuroforge"
|
||||
dockerfile = "Dockerfile"
|
||||
target = "server"
|
||||
tags = ["${REGISTRY}/glpi-neuroforge-mega-neuroforge:${IMAGE_TAG}"]
|
||||
target = "server"
|
||||
tags = ["${REGISTRY}/glpi-neuroforge-mega-neuroforge:${IMAGE_TAG}"]
|
||||
platforms = ["linux/amd64"]
|
||||
}
|
||||
|
||||
target "neuroforge-worker" {
|
||||
context = "./platform/neuroforge"
|
||||
context = "./platform/neuroforge"
|
||||
dockerfile = "Dockerfile"
|
||||
target = "worker"
|
||||
tags = ["${REGISTRY}/glpi-neuroforge-mega-neuroforge-worker:${IMAGE_TAG}"]
|
||||
target = "worker"
|
||||
tags = ["${REGISTRY}/glpi-neuroforge-mega-neuroforge-worker:${IMAGE_TAG}"]
|
||||
platforms = ["linux/amd64"]
|
||||
}
|
||||
|
||||
target "agent" {
|
||||
context = "./services/agent"
|
||||
context = "./services/agent"
|
||||
dockerfile = "Dockerfile"
|
||||
tags = ["${REGISTRY}/glpi-neuroforge-mega-agent:${IMAGE_TAG}"]
|
||||
tags = ["${REGISTRY}/glpi-neuroforge-mega-agent:${IMAGE_TAG}"]
|
||||
platforms = ["linux/amd64"]
|
||||
}
|
||||
|
||||
target "agent-data-init" {
|
||||
context = "./services/agent"
|
||||
context = "./services/agent"
|
||||
dockerfile = "Dockerfile"
|
||||
target = "data-init"
|
||||
tags = ["${REGISTRY}/glpi-neuroforge-mega-agent-data-init:${IMAGE_TAG}"]
|
||||
target = "data-init"
|
||||
tags = ["${REGISTRY}/glpi-neuroforge-mega-agent-data-init:${IMAGE_TAG}"]
|
||||
platforms = ["linux/amd64"]
|
||||
}
|
||||
|
||||
target "knowledge" {
|
||||
context = "./services/knowledge"
|
||||
context = "./services/knowledge"
|
||||
dockerfile = "Dockerfile"
|
||||
tags = ["${REGISTRY}/glpi-neuroforge-mega-knowledge:${IMAGE_TAG}"]
|
||||
tags = ["${REGISTRY}/glpi-neuroforge-mega-knowledge:${IMAGE_TAG}"]
|
||||
platforms = ["linux/amd64"]
|
||||
}
|
||||
|
||||
target "control" {
|
||||
context = "./services/control"
|
||||
context = "./services/control"
|
||||
dockerfile = "Dockerfile"
|
||||
tags = ["${REGISTRY}/glpi-neuroforge-mega-control:${IMAGE_TAG}"]
|
||||
tags = ["${REGISTRY}/glpi-neuroforge-mega-control:${IMAGE_TAG}"]
|
||||
platforms = ["linux/amd64"]
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ name: neuroforge-subagents
|
||||
services:
|
||||
cpu-subagent:
|
||||
profiles: [cpu]
|
||||
image: git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge-worker:${IMAGE_TAG:?Set IMAGE_TAG, e.g. 1.6.1}
|
||||
image: git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge-worker:${IMAGE_TAG:?Set IMAGE_TAG, e.g. 1.6.2}
|
||||
command:
|
||||
- -server
|
||||
- ${NEUROFORGE_MASTER_URL:?Set the reachable NeuroForge master URL}
|
||||
@@ -29,7 +29,7 @@ services:
|
||||
|
||||
gpu-subagent:
|
||||
profiles: [gpu]
|
||||
image: git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge-worker:${IMAGE_TAG:?Set IMAGE_TAG, e.g. 1.6.1}
|
||||
image: git.send.nrw/sendnrw/glpi-neuroforge-mega-neuroforge-worker:${IMAGE_TAG:?Set IMAGE_TAG, e.g. 1.6.2}
|
||||
command:
|
||||
- -server
|
||||
- ${NEUROFORGE_MASTER_URL:?Set the reachable NeuroForge master URL}
|
||||
@@ -43,6 +43,8 @@ services:
|
||||
NEUROFORGE_WORKER_MAX_CONCURRENCY: ${NEUROFORGE_GPU_WORKER_CONCURRENCY:-1}
|
||||
NEUROFORGE_WORKER_HEARTBEAT_INTERVAL: ${NEUROFORGE_WORKER_HEARTBEAT_INTERVAL:-15s}
|
||||
NEUROFORGE_WORKER_OLLAMA_URL: ${NEUROFORGE_WORKER_OLLAMA_URL:?Set Ollama URL reachable from the GPU subagent}
|
||||
NEUROFORGE_WORKER_OLLAMA_API_KEY: ${NEUROFORGE_WORKER_OLLAMA_API_KEY:-}
|
||||
OLLAMA_API_KEY: ${OLLAMA_API_KEY:-}
|
||||
NEUROFORGE_WORKER_OLLAMA_CHAT_MODEL: ${OLLAMA_MODEL:-gemma3}
|
||||
NEUROFORGE_WORKER_OLLAMA_EMBEDDING_MODEL: ${OLLAMA_EMBEDDING_MODEL:-embeddinggemma}
|
||||
NEUROFORGE_WORKER_OLLAMA_NUM_CTX: ${NEUROFORGE_OLLAMA_NUM_CTX:-8192}
|
||||
|
||||
@@ -50,6 +50,8 @@ services:
|
||||
NEUROFORGE_CLUSTER_TOKEN: ${NEUROFORGE_CLUSTER_TOKEN:-}
|
||||
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
|
||||
NEUROFORGE_OLLAMA_URL: ${OLLAMA_BASE_URL:-http://ollama:11434}
|
||||
NEUROFORGE_OLLAMA_API_KEY: ${NEUROFORGE_OLLAMA_API_KEY:-}
|
||||
OLLAMA_API_KEY: ${OLLAMA_API_KEY:-}
|
||||
NEUROFORGE_OLLAMA_CHAT_MODEL: ${OLLAMA_MODEL:-gemma3}
|
||||
NEUROFORGE_OLLAMA_EMBEDDING_MODEL: ${OLLAMA_EMBEDDING_MODEL:-embeddinggemma}
|
||||
NEUROFORGE_OLLAMA_NUM_CTX: ${NEUROFORGE_OLLAMA_NUM_CTX:-8192}
|
||||
@@ -182,6 +184,8 @@ services:
|
||||
NEUROFORGE_WORKER_MAX_CONCURRENCY: ${NEUROFORGE_GPU_WORKER_CONCURRENCY:-1}
|
||||
NEUROFORGE_WORKER_HEARTBEAT_INTERVAL: ${NEUROFORGE_WORKER_HEARTBEAT_INTERVAL:-15s}
|
||||
NEUROFORGE_WORKER_OLLAMA_URL: ${OLLAMA_BASE_URL:-http://ollama:11434}
|
||||
NEUROFORGE_WORKER_OLLAMA_API_KEY: ${NEUROFORGE_WORKER_OLLAMA_API_KEY:-}
|
||||
OLLAMA_API_KEY: ${OLLAMA_API_KEY:-}
|
||||
NEUROFORGE_WORKER_OLLAMA_CHAT_MODEL: ${OLLAMA_MODEL:-gemma3}
|
||||
NEUROFORGE_WORKER_OLLAMA_EMBEDDING_MODEL: ${OLLAMA_EMBEDDING_MODEL:-embeddinggemma}
|
||||
NEUROFORGE_WORKER_OLLAMA_NUM_CTX: ${NEUROFORGE_OLLAMA_NUM_CTX:-8192}
|
||||
@@ -376,6 +380,7 @@ services:
|
||||
OLLAMA_THINK: ${OLLAMA_THINK:-}
|
||||
OLLAMA_TIMEOUT: ${OLLAMA_TIMEOUT:-}
|
||||
OLLAMA_URL: ${OLLAMA_BASE_URL:-http://ollama:11434}
|
||||
OLLAMA_API_KEY: ${OLLAMA_API_KEY:-}
|
||||
OLLAMA_URLS: ${OLLAMA_URLS:-http://ollama:11434}
|
||||
OUTCOME_LEARNING_ENABLED: ${OUTCOME_LEARNING_ENABLED:-true}
|
||||
OUTCOME_LEARNING_FAIL_OPEN: ${OUTCOME_LEARNING_FAIL_OPEN:-false}
|
||||
@@ -449,6 +454,7 @@ services:
|
||||
BASIC_AUTH_PASSWORD: ${BASIC_AUTH_PASSWORD:?Set the Knowledge web password}
|
||||
AI_FALLBACK_ENABLED: ${AI_FALLBACK_ENABLED:-false}
|
||||
OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://ollama:11434}
|
||||
OLLAMA_API_KEY: ${OLLAMA_API_KEY:-}
|
||||
OLLAMA_MODEL: ${OLLAMA_MODEL:-gemma3}
|
||||
OLLAMA_TIMEOUT: ${OLLAMA_TIMEOUT:-10m}
|
||||
OLLAMA_MAX_CONCURRENT: ${OLLAMA_MAX_CONCURRENT:-2}
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
# Migration v1.6.1 -> v1.6.2
|
||||
|
||||
v1.6.2 is primarily a packaging/deployment consolidation release. It retains the v1.6.1 NeuroForge recovery and persisted-state behavior.
|
||||
No data-volume migration is required. Keep `neuroforge-data`, Agent data and Knowledge directories intact.
|
||||
|
||||
1. Back up the persistent data volumes/directories.
|
||||
2. Set `IMAGE_TAG=1.6.2` in the selected deployment `.env`.
|
||||
3. Keep existing `neuroforge-data`, Agent data and Knowledge files; do not delete volumes.
|
||||
4. Replace deployment examples with the v1.6.2 variants, preserving real secrets locally.
|
||||
5. Recreate project containers so changed environment/configuration is applied.
|
||||
6. Check `/livez`, `/readyz`, Agent `/api/status`, worker status, graph convergence and Prometheus alerts.
|
||||
1. Set `IMAGE_TAG=1.6.2`.
|
||||
2. Copy the new optional Ollama variables into your `.env`:
|
||||
|
||||
The distributed roles now live alongside the standalone Agent/Knowledge/Ollama roles in the same canonical repository.
|
||||
```env
|
||||
OLLAMA_API_KEY=
|
||||
NEUROFORGE_OLLAMA_API_KEY=
|
||||
NEUROFORGE_WORKER_OLLAMA_API_KEY=
|
||||
```
|
||||
|
||||
Leave them empty for an unauthenticated native Ollama endpoint.
|
||||
3. If Ollama is protected by a Bearer-aware proxy, normally set only `OLLAMA_API_KEY`. Use the two NeuroForge overrides only when the Master and model worker need different credentials.
|
||||
4. Pull/recreate services without deleting volumes:
|
||||
|
||||
```bash
|
||||
docker compose --profile research pull
|
||||
docker compose --profile research up -d --force-recreate --remove-orphans
|
||||
```
|
||||
5. For distributed deployments, use the complete `.env` shipped under `deployments/master`, `deployments/cpu-subagent`, and `deployments/gpu-subagent` and preserve the same `NEUROFORGE_WORKER_TOKEN` on Master and subagents.
|
||||
|
||||
The v1.6.1 OOM/recovery fixes are part of v1.6.2 and must not be removed when merging older deployment files.
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# Ollama Bearer authentication
|
||||
|
||||
v1.6.2 can call Ollama-compatible endpoints protected by an HTTP Bearer token.
|
||||
|
||||
## Shared configuration
|
||||
|
||||
```env
|
||||
OLLAMA_API_KEY=CHANGE_ME
|
||||
```
|
||||
|
||||
Every supported Ollama request then carries:
|
||||
|
||||
```http
|
||||
Authorization: Bearer CHANGE_ME
|
||||
```
|
||||
|
||||
This applies to health/model discovery (`/api/tags`), chat (`/api/chat`) and embeddings (`/api/embed`).
|
||||
|
||||
## NeuroForge overrides
|
||||
|
||||
```env
|
||||
NEUROFORGE_OLLAMA_API_KEY=
|
||||
NEUROFORGE_WORKER_OLLAMA_API_KEY=
|
||||
```
|
||||
|
||||
`NEUROFORGE_OLLAMA_API_KEY` overrides the shared key for the NeuroForge Master/provider. `NEUROFORGE_WORKER_OLLAMA_API_KEY` overrides it for model-capable NeuroForge workers. If the override is empty, workers fall back to `OLLAMA_API_KEY`; the Master also accepts `OLLAMA_API_KEY` as a shared alias when no NeuroForge-specific key is supplied.
|
||||
|
||||
## Agent pools
|
||||
|
||||
The GLPI Agent uses one `OLLAMA_API_KEY` for all URLs in `OLLAMA_URLS`. This is intentional for a pool behind one common authentication boundary. Deploy separate Agent instances or a common gateway if individual nodes require unrelated credentials.
|
||||
|
||||
## Native Ollama
|
||||
|
||||
The environment variable configures the **clients**, not the bundled native Ollama server. Native Ollama does not gain access control from this setting alone. To require authentication, place Ollama behind an authentication-capable reverse proxy/gateway and point `OLLAMA_URL`, `OLLAMA_URLS` or `OLLAMA_BASE_URL` at that endpoint.
|
||||
|
||||
Do not put credentials into Ollama URLs. Keep URLs credential-free and use the Bearer variable.
|
||||
+3
-2
@@ -20,7 +20,8 @@
|
||||
"worker_to_neuroforge": "worker_token",
|
||||
"research_to_kb_staging": "kb_integration_token",
|
||||
"control_to_agent_graph": "control_read_token",
|
||||
"control_to_neuroforge_graph": "app_api_key_read_only_endpoints"
|
||||
"control_to_neuroforge_graph": "app_api_key_read_only_endpoints",
|
||||
"ollama": "optional bearer token via OLLAMA_API_KEY; role-specific NeuroForge overrides supported"
|
||||
},
|
||||
"knowledge_export": {
|
||||
"format": "Obsidian Markdown + YAML frontmatter + Wikilinks",
|
||||
@@ -28,7 +29,7 @@
|
||||
"schema": "Wiki/Schema.md",
|
||||
"glpi_relations": "KnowbaseItem_Item when exposed by GLPI OpenAPI"
|
||||
},
|
||||
"version": "1.5.6",
|
||||
"version": "1.6.2",
|
||||
"controlled_learning": {
|
||||
"raw_chat_auto_learning": false,
|
||||
"validated_outcomes": [
|
||||
|
||||
@@ -21,3 +21,4 @@ b031ea42356a022d7bb03e7e2bda23c4ceddb66ccc59a5d1f0ebe787a790b829 v1.5.1-to-v1.5
|
||||
24bb0da9bf77a0e8b692989b05c00b623ed9a558b9b5050c24e396e1da41d0f8 v1.5.8-to-v1.5.9.diff
|
||||
e579d88ed2846cf5bc306cad45502af3edc61bd1c5bb5a2272979feff23dca62 v1.5.9-to-v1.6.0.diff
|
||||
0cdce94c11a931e11c3c908f2fa3dfa733c5c602ac00f7f89e41b8c301574766 v1.6.0-to-v1.6.1.diff
|
||||
0b09d93f115608f4ab6ee47e0a085f254eaeb8914fe1415fe73dccea69e5d166 v1.6.1-to-v1.6.2.diff
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,8 @@
|
||||
# Generate strong random values, e.g. openssl rand -hex 32
|
||||
# Optional: Bearer token sent as Authorization: Bearer <token> to Ollama-compatible endpoints.
|
||||
OLLAMA_API_KEY=
|
||||
# Optional NeuroForge GPU-worker override; falls back to OLLAMA_API_KEY.
|
||||
NEUROFORGE_WORKER_OLLAMA_API_KEY=
|
||||
NEUROFORGE_ADMIN_TOKEN=replace-with-random-admin-token
|
||||
NEUROFORGE_APP_API_KEY=replace-with-random-app-key
|
||||
NEUROFORGE_WORKER_TOKEN=replace-with-random-worker-token
|
||||
@@ -7,3 +11,6 @@ NEUROFORGE_CLUSTER_TOKEN=replace-with-shared-random-cluster-token
|
||||
|
||||
# Optional. You can also set this through the web interface.
|
||||
OPENAI_API_KEY=
|
||||
|
||||
# Optional NeuroForge-specific override; takes precedence over OLLAMA_API_KEY.
|
||||
NEUROFORGE_OLLAMA_API_KEY=
|
||||
|
||||
@@ -234,6 +234,7 @@ func run() (retErr error) {
|
||||
changed := false
|
||||
for name, dst := range map[string]*string{
|
||||
"OPENAI_API_KEY": &sec.OpenAIAPIKey,
|
||||
"NEUROFORGE_OLLAMA_API_KEY": &sec.OllamaAPIKey,
|
||||
"NEUROFORGE_ADMIN_TOKEN": &sec.AdminToken,
|
||||
"NEUROFORGE_APP_API_KEY": &sec.AppAPIKey,
|
||||
"NEUROFORGE_INTEGRATION_TOKEN": &sec.IntegrationToken,
|
||||
@@ -247,6 +248,14 @@ func run() (retErr error) {
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
// OLLAMA_API_KEY is a convenient shared alias. The NeuroForge-specific
|
||||
// variable wins when both are set.
|
||||
if strings.TrimSpace(os.Getenv("NEUROFORGE_OLLAMA_API_KEY")) == "" {
|
||||
if v := strings.TrimSpace(os.Getenv("OLLAMA_API_KEY")); v != "" {
|
||||
sec.OllamaAPIKey = v
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
if err := s.UpdateSecrets(sec); err != nil {
|
||||
return err
|
||||
|
||||
@@ -86,6 +86,7 @@ type workerConfig struct {
|
||||
MaxConcurrency int
|
||||
Hostname string
|
||||
OllamaURL string
|
||||
OllamaAPIKey string
|
||||
OllamaChatModel string
|
||||
OllamaEmbedModel string
|
||||
OllamaNumCtx int
|
||||
@@ -145,6 +146,7 @@ func main() {
|
||||
Heartbeat: heartbeat, ResourceClass: resource, Capabilities: caps,
|
||||
MaxConcurrency: maxConcurrency, Hostname: hostname(),
|
||||
OllamaURL: strings.TrimRight(firstNonEmpty(os.Getenv("NEUROFORGE_WORKER_OLLAMA_URL"), os.Getenv("OLLAMA_BASE_URL"), os.Getenv("OLLAMA_URL")), "/"),
|
||||
OllamaAPIKey: firstNonEmpty(os.Getenv("NEUROFORGE_WORKER_OLLAMA_API_KEY"), os.Getenv("OLLAMA_API_KEY")),
|
||||
OllamaChatModel: firstNonEmpty(os.Getenv("NEUROFORGE_WORKER_OLLAMA_CHAT_MODEL"), os.Getenv("OLLAMA_MODEL")),
|
||||
OllamaEmbedModel: firstNonEmpty(os.Getenv("NEUROFORGE_WORKER_OLLAMA_EMBEDDING_MODEL"), os.Getenv("OLLAMA_EMBEDDING_MODEL")),
|
||||
OllamaNumCtx: envInt("NEUROFORGE_WORKER_OLLAMA_NUM_CTX", 8192),
|
||||
@@ -342,7 +344,7 @@ func ollamaEmbed(ctx context.Context, c *http.Client, cfg workerConfig, p modelE
|
||||
Embeddings [][]float32 `json:"embeddings"`
|
||||
PromptEvalCount int64 `json:"prompt_eval_count"`
|
||||
}
|
||||
if err := postOllama(ctx, c, cfg.OllamaURL+"/api/embed", body, &resp); err != nil {
|
||||
if err := postOllama(ctx, c, cfg.OllamaURL+"/api/embed", cfg.OllamaAPIKey, body, &resp); err != nil {
|
||||
return modelResult{}, err
|
||||
}
|
||||
if len(resp.Embeddings) == 0 || len(resp.Embeddings[0]) == 0 {
|
||||
@@ -385,19 +387,22 @@ func ollamaChat(ctx context.Context, c *http.Client, cfg workerConfig, p modelCh
|
||||
PromptEvalCount int64 `json:"prompt_eval_count"`
|
||||
EvalCount int64 `json:"eval_count"`
|
||||
}
|
||||
if err := postOllama(ctx, c, cfg.OllamaURL+"/api/chat", body, &resp); err != nil {
|
||||
if err := postOllama(ctx, c, cfg.OllamaURL+"/api/chat", cfg.OllamaAPIKey, body, &resp); err != nil {
|
||||
return modelResult{}, err
|
||||
}
|
||||
return modelResult{Text: strings.TrimSpace(resp.Message.Content), Usage: modelUsage{InputTokens: resp.PromptEvalCount, OutputTokens: resp.EvalCount}, Provider: "ollama", Model: model, NodeID: cfg.ID}, nil
|
||||
}
|
||||
|
||||
func postOllama(ctx context.Context, c *http.Client, url string, body any, out any) error {
|
||||
func postOllama(ctx context.Context, c *http.Client, url, apiKey string, body any, out any) error {
|
||||
raw, _ := json.Marshal(body)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(raw))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if token := strings.TrimSpace(apiKey); token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
}
|
||||
resp, err := c.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPostOllamaSendsBearerToken(t *testing.T) {
|
||||
const token = "ollama-secret"
|
||||
var auth string
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
auth = r.Header.Get("Authorization")
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"ok": true})
|
||||
}))
|
||||
defer srv.Close()
|
||||
var out map[string]any
|
||||
if err := postOllama(context.Background(), srv.Client(), srv.URL, token, map[string]any{"x": 1}, &out); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if auth != "Bearer "+token {
|
||||
t.Fatalf("authorization=%q", auth)
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ services:
|
||||
NEUROFORGE_METRICS_TOKEN: ${NEUROFORGE_METRICS_TOKEN}
|
||||
NEUROFORGE_CLUSTER_TOKEN: ${NEUROFORGE_CLUSTER_TOKEN:-}
|
||||
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
|
||||
NEUROFORGE_OLLAMA_API_KEY: ${NEUROFORGE_OLLAMA_API_KEY:-}
|
||||
OLLAMA_API_KEY: ${OLLAMA_API_KEY:-}
|
||||
volumes:
|
||||
- neuroforge-data:/app/data
|
||||
restart: unless-stopped
|
||||
@@ -59,6 +61,8 @@ services:
|
||||
NEUROFORGE_WORKER_CAPABILITIES: gpu,model.chat,model.embed
|
||||
NEUROFORGE_WORKER_MAX_CONCURRENCY: ${NEUROFORGE_GPU_WORKER_CONCURRENCY:-1}
|
||||
NEUROFORGE_WORKER_OLLAMA_URL: ${OLLAMA_BASE_URL:-http://ollama:11434}
|
||||
NEUROFORGE_WORKER_OLLAMA_API_KEY: ${NEUROFORGE_WORKER_OLLAMA_API_KEY:-}
|
||||
OLLAMA_API_KEY: ${OLLAMA_API_KEY:-}
|
||||
NEUROFORGE_WORKER_OLLAMA_CHAT_MODEL: ${OLLAMA_MODEL:-gemma3}
|
||||
NEUROFORGE_WORKER_OLLAMA_EMBEDDING_MODEL: ${OLLAMA_EMBEDDING_MODEL:-embeddinggemma}
|
||||
depends_on:
|
||||
|
||||
@@ -419,6 +419,7 @@ type Config struct {
|
||||
|
||||
type Secrets struct {
|
||||
OpenAIAPIKey string `json:"openai_api_key"`
|
||||
OllamaAPIKey string `json:"ollama_api_key,omitempty"`
|
||||
AppAPIKey string `json:"app_api_key"`
|
||||
IntegrationToken string `json:"integration_token,omitempty"`
|
||||
ControlReadToken string `json:"control_read_token,omitempty"`
|
||||
|
||||
@@ -693,7 +693,7 @@ func (s *Server) adminPutModelRouting(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (s *Server) adminSecretsStatus(w http.ResponseWriter, r *http.Request) {
|
||||
sec := s.store.Secrets()
|
||||
s.json(w, 200, map[string]any{"openai_configured": sec.OpenAIAPIKey != "", "app_key_configured": sec.AppAPIKey != "", "integration_token_configured": sec.IntegrationToken != "", "control_read_token_configured": sec.ControlReadToken != "", "worker_token_configured": sec.WorkerToken != "", "metrics_token_configured": sec.MetricsToken != "", "shard_tokens": len(sec.ShardAPIToken), "cluster_token_configured": sec.ClusterToken != ""})
|
||||
s.json(w, 200, map[string]any{"openai_configured": sec.OpenAIAPIKey != "", "ollama_api_key_configured": sec.OllamaAPIKey != "", "app_key_configured": sec.AppAPIKey != "", "integration_token_configured": sec.IntegrationToken != "", "control_read_token_configured": sec.ControlReadToken != "", "worker_token_configured": sec.WorkerToken != "", "metrics_token_configured": sec.MetricsToken != "", "shard_tokens": len(sec.ShardAPIToken), "cluster_token_configured": sec.ClusterToken != ""})
|
||||
}
|
||||
func maskedSecret(v string) string {
|
||||
if v == "" {
|
||||
@@ -708,18 +708,19 @@ func (s *Server) adminGetSecrets(w http.ResponseWriter, r *http.Request) {
|
||||
sec := s.store.Secrets()
|
||||
reveal := r.URL.Query().Get("reveal") == "1" && s.store.Config().Security.AllowSecretReveal
|
||||
if reveal {
|
||||
s.json(w, 200, map[string]any{"revealed": true, "app_api_key": sec.AppAPIKey, "integration_token": sec.IntegrationToken, "control_read_token": sec.ControlReadToken, "worker_token": sec.WorkerToken, "metrics_token": sec.MetricsToken, "shard_api_tokens": sec.ShardAPIToken, "cluster_token": sec.ClusterToken})
|
||||
s.json(w, 200, map[string]any{"revealed": true, "ollama_api_key": sec.OllamaAPIKey, "app_api_key": sec.AppAPIKey, "integration_token": sec.IntegrationToken, "control_read_token": sec.ControlReadToken, "worker_token": sec.WorkerToken, "metrics_token": sec.MetricsToken, "shard_api_tokens": sec.ShardAPIToken, "cluster_token": sec.ClusterToken})
|
||||
return
|
||||
}
|
||||
maskedShards := map[string]string{}
|
||||
for k, v := range sec.ShardAPIToken {
|
||||
maskedShards[k] = maskedSecret(v)
|
||||
}
|
||||
s.json(w, 200, map[string]any{"revealed": false, "reveal_allowed": s.store.Config().Security.AllowSecretReveal, "app_api_key": maskedSecret(sec.AppAPIKey), "integration_token": maskedSecret(sec.IntegrationToken), "control_read_token": maskedSecret(sec.ControlReadToken), "worker_token": maskedSecret(sec.WorkerToken), "metrics_token": maskedSecret(sec.MetricsToken), "shard_api_tokens": maskedShards, "cluster_token": maskedSecret(sec.ClusterToken)})
|
||||
s.json(w, 200, map[string]any{"revealed": false, "reveal_allowed": s.store.Config().Security.AllowSecretReveal, "ollama_api_key": maskedSecret(sec.OllamaAPIKey), "app_api_key": maskedSecret(sec.AppAPIKey), "integration_token": maskedSecret(sec.IntegrationToken), "control_read_token": maskedSecret(sec.ControlReadToken), "worker_token": maskedSecret(sec.WorkerToken), "metrics_token": maskedSecret(sec.MetricsToken), "shard_api_tokens": maskedShards, "cluster_token": maskedSecret(sec.ClusterToken)})
|
||||
}
|
||||
func (s *Server) adminPutSecrets(w http.ResponseWriter, r *http.Request) {
|
||||
var q struct {
|
||||
OpenAIAPIKey string `json:"openai_api_key,omitempty"`
|
||||
OllamaAPIKey string `json:"ollama_api_key,omitempty"`
|
||||
AppAPIKey string `json:"app_api_key,omitempty"`
|
||||
IntegrationToken string `json:"integration_token,omitempty"`
|
||||
ControlReadToken string `json:"control_read_token,omitempty"`
|
||||
@@ -737,8 +738,13 @@ func (s *Server) adminPutSecrets(w http.ResponseWriter, r *http.Request) {
|
||||
_, ok := os.LookupEnv(name)
|
||||
return ok && strings.TrimSpace(os.Getenv(name)) != ""
|
||||
}
|
||||
if q.OllamaAPIKey != "" && (envLocked("NEUROFORGE_OLLAMA_API_KEY") || envLocked("OLLAMA_API_KEY")) {
|
||||
s.err(w, http.StatusConflict, fmt.Errorf("Ollama API key is environment-managed and cannot be changed through the admin API"))
|
||||
return
|
||||
}
|
||||
for name, value := range map[string]string{
|
||||
"OPENAI_API_KEY": q.OpenAIAPIKey,
|
||||
"NEUROFORGE_OLLAMA_API_KEY": q.OllamaAPIKey,
|
||||
"NEUROFORGE_APP_API_KEY": q.AppAPIKey,
|
||||
"NEUROFORGE_INTEGRATION_TOKEN": q.IntegrationToken,
|
||||
"NEUROFORGE_CONTROL_READ_TOKEN": q.ControlReadToken,
|
||||
@@ -754,6 +760,9 @@ func (s *Server) adminPutSecrets(w http.ResponseWriter, r *http.Request) {
|
||||
if q.OpenAIAPIKey != "" {
|
||||
sec.OpenAIAPIKey = q.OpenAIAPIKey
|
||||
}
|
||||
if q.OllamaAPIKey != "" {
|
||||
sec.OllamaAPIKey = q.OllamaAPIKey
|
||||
}
|
||||
if q.AppAPIKey != "" {
|
||||
sec.AppAPIKey = q.AppAPIKey
|
||||
}
|
||||
@@ -899,7 +908,7 @@ func configuredModelAvailable(models map[string]bool, configured string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func checkConfiguredOllamaModels(ctx context.Context, cfg core.Config) (bool, any) {
|
||||
func checkConfiguredOllamaModels(ctx context.Context, cfg core.Config, apiKey string) (bool, any) {
|
||||
type tagsResponse struct {
|
||||
Models []struct {
|
||||
Name string `json:"name"`
|
||||
@@ -917,6 +926,9 @@ func checkConfiguredOllamaModels(ctx context.Context, cfg core.Config) (bool, an
|
||||
details[node.ID] = err.Error()
|
||||
continue
|
||||
}
|
||||
if token := strings.TrimSpace(apiKey); token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
details[node.ID] = err.Error()
|
||||
@@ -977,7 +989,7 @@ func (s *Server) readyz(w http.ResponseWriter, r *http.Request) {
|
||||
if s.readinessOllamaLive {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 4*time.Second)
|
||||
var detail any
|
||||
ollamaLiveReady, detail = checkConfiguredOllamaModels(ctx, cfg)
|
||||
ollamaLiveReady, detail = checkConfiguredOllamaModels(ctx, cfg, s.store.Secrets().OllamaAPIKey)
|
||||
cancel()
|
||||
components["ollama_live_models"] = detail
|
||||
}
|
||||
|
||||
@@ -307,7 +307,7 @@ func (r *Router) chatOllama(ctx context.Context, o core.OllamaServer, model, ins
|
||||
}
|
||||
requestCtx, cancel := optionalTimeout(ctx, o.RequestTimeoutSeconds)
|
||||
defer cancel()
|
||||
if err := r.doJSON(requestCtx, "POST", cleanBase(o.BaseURL)+"/api/chat", "", body, &out); err != nil {
|
||||
if err := r.doJSON(requestCtx, "POST", cleanBase(o.BaseURL)+"/api/chat", r.store.Secrets().OllamaAPIKey, body, &out); err != nil {
|
||||
return ChatResult{}, fmt.Errorf("ollama %s: %w", o.Name, err)
|
||||
}
|
||||
if strings.TrimSpace(out.Message.Content) == "" {
|
||||
@@ -327,7 +327,7 @@ func (r *Router) embedOllama(ctx context.Context, o core.OllamaServer, model, te
|
||||
}
|
||||
requestCtx, cancel := optionalTimeout(ctx, o.RequestTimeoutSeconds)
|
||||
defer cancel()
|
||||
if err := r.doJSON(requestCtx, "POST", cleanBase(o.BaseURL)+"/api/embed", "", body, &out); err != nil {
|
||||
if err := r.doJSON(requestCtx, "POST", cleanBase(o.BaseURL)+"/api/embed", r.store.Secrets().OllamaAPIKey, body, &out); err != nil {
|
||||
return EmbedResult{}, fmt.Errorf("ollama %s: %w", o.Name, err)
|
||||
}
|
||||
if len(out.Embeddings) == 0 || len(out.Embeddings[0]) == 0 {
|
||||
@@ -459,6 +459,9 @@ func (r *Router) Health(ctx context.Context) []map[string]any {
|
||||
}
|
||||
healthCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
req, _ := http.NewRequestWithContext(healthCtx, "GET", cleanBase(o.BaseURL)+"/api/tags", nil)
|
||||
if token := strings.TrimSpace(r.store.Secrets().OllamaAPIKey); token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
}
|
||||
resp, err := r.http.Do(req)
|
||||
if err != nil {
|
||||
cancel()
|
||||
|
||||
@@ -111,3 +111,48 @@ func TestOllamaExplicitRequestTimeoutStillWorks(t *testing.T) {
|
||||
t.Fatalf("configured timeout was not enforced promptly: %v", time.Since(start))
|
||||
}
|
||||
}
|
||||
|
||||
func TestOllamaBearerTokenIsSentToChatAndHealth(t *testing.T) {
|
||||
const token = "ollama-secret"
|
||||
var chatAuth, tagsAuth string
|
||||
fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/api/chat":
|
||||
chatAuth = r.Header.Get("Authorization")
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"message": map[string]any{"content": "ok"}})
|
||||
case "/api/tags":
|
||||
tagsAuth = r.Header.Get("Authorization")
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"models": []map[string]any{{"name": "chat"}, {"name": "embed"}}})
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}))
|
||||
defer fake.Close()
|
||||
s, err := store.New(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s.Close()
|
||||
cfg := s.Config()
|
||||
cfg.Ollama = []core.OllamaServer{{ID: "secure", Name: "secure", BaseURL: fake.URL, ChatModel: "chat", EmbeddingModel: "embed", Weight: 1, Enabled: true}}
|
||||
cfg.Routing.ChatProvider = "ollama"
|
||||
if err := s.UpdateConfig(cfg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sec := s.Secrets()
|
||||
sec.OllamaAPIKey = token
|
||||
if err := s.UpdateSecrets(sec); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r := NewRouter(s)
|
||||
if _, err := r.Chat(context.Background(), "ollama", "chat", "", "hello", 32); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = r.Health(context.Background())
|
||||
if chatAuth != "Bearer "+token {
|
||||
t.Fatalf("chat authorization=%q", chatAuth)
|
||||
}
|
||||
if tagsAuth != "Bearer "+token {
|
||||
t.Fatalf("tags authorization=%q", tagsAuth)
|
||||
}
|
||||
}
|
||||
|
||||
+19
-28
@@ -23,14 +23,29 @@ fi
|
||||
|
||||
# Private keys and common live-token shapes must not be committed. Placeholders in
|
||||
# templates/docs are intentionally allowed.
|
||||
# Distributed deployment bundles intentionally contain three complete `.env`
|
||||
# templates. They must remain placeholder-only; every other private env/key file
|
||||
# is still forbidden.
|
||||
if grep -E '(^|/)\.env$|\.pem$|\.p12$|\.pfx$|(^|/)id_rsa$|(^|/)id_ed25519$' "$FILES" \
|
||||
| grep -Ev '^deployments/(master|cpu-subagent|gpu-subagent|agent|knowledge|ollama|combined)/\.env$' >/dev/null; then
|
||||
| grep -Ev '^deployments/[^/]+/\.env$' >/dev/null; then
|
||||
bad "private environment/key material found"
|
||||
fi
|
||||
|
||||
# Deployment role .env files are intentionally shipped as complete templates.
|
||||
# Reject them if a sensitive field is populated with anything other than an
|
||||
# empty value or an explicit CHANGE_ME placeholder.
|
||||
for f in deployments/*/.env; do
|
||||
[ -f "$f" ] || continue
|
||||
awk -F= '
|
||||
/^[[:space:]]*#/ || NF < 2 { next }
|
||||
$1 ~ /(PASSWORD|SECRET|TOKEN|API_KEY|CLIENT_SECRET)$/ {
|
||||
v=$0; sub(/^[^=]*=/,"",v)
|
||||
if (v != "" && v !~ /^CHANGE_ME/) {
|
||||
print FILENAME ": unsafe populated secret field " $1 > "/dev/stderr"
|
||||
bad=1
|
||||
}
|
||||
}
|
||||
END { exit bad ? 1 : 0 }
|
||||
' "$f" || bad "deployment .env contains a populated secret: $f"
|
||||
done
|
||||
|
||||
if [ "$HAVE_GIT" = true ]; then
|
||||
if git grep -nE -- '-----BEGIN (RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----' -- ':!*.example' ':!*.md' >"$KEYS" 2>/dev/null; then
|
||||
cat "$KEYS" >&2; bad "private key material found"
|
||||
@@ -51,30 +66,6 @@ else
|
||||
fi
|
||||
fi
|
||||
|
||||
# Checked-in deployment .env files are templates, never live configuration.
|
||||
for envf in \
|
||||
deployments/master/.env \
|
||||
deployments/cpu-subagent/.env \
|
||||
deployments/gpu-subagent/.env \
|
||||
deployments/agent/.env \
|
||||
deployments/knowledge/.env \
|
||||
deployments/ollama/.env \
|
||||
deployments/combined/.env; do
|
||||
[ -f "$envf" ] || continue
|
||||
if awk -F= '
|
||||
/^[[:space:]]*#/ || NF < 2 { next }
|
||||
{
|
||||
key=$1; sub(/^[[:space:]]+/, "", key); sub(/[[:space:]]+$/, "", key)
|
||||
val=$0; sub(/^[^=]*=/, "", val)
|
||||
if (key ~ /(TOKEN|PASSWORD|SECRET|CLIENT_ID|CLIENT_SECRET|API_KEY)$/ && val != "" && val !~ /^CHANGE_ME/) {
|
||||
print FILENAME ":" NR ": live-looking secret in " key > "/dev/stderr";
|
||||
bad=1
|
||||
}
|
||||
}
|
||||
END { exit bad ? 1 : 0 }
|
||||
' "$envf"; then :; else bad "deployment template contains a non-placeholder secret: $envf"; fi
|
||||
done
|
||||
|
||||
# Reject accidental binary blobs outside explicitly expected assets.
|
||||
while IFS= read -r f; do
|
||||
[ -f "$f" ] || continue
|
||||
|
||||
@@ -290,6 +290,8 @@ OLLAMA_REQUIRE_EMBEDDING_MODEL=true
|
||||
# OLLAMA_MODEL=qwen3:8b
|
||||
# Embedding-Modell für RAG.
|
||||
OLLAMA_EMBEDDING_MODEL=embeddinggemma
|
||||
# Optional: Bearer token sent as Authorization: Bearer <token> to Ollama-compatible endpoints.
|
||||
OLLAMA_API_KEY=
|
||||
|
||||
# Modellspezifisches Retrieval-Prompting.
|
||||
# auto = Modell automatisch erkennen; für embeddinggemma empfohlen.
|
||||
@@ -799,4 +801,4 @@ QUEUE_SIZE=256
|
||||
# Ticketpipelines gleichzeitig verteilen, wie Worker aktiv sind. Für drei
|
||||
# gleichartige Nodes ist WORKERS=3 ein sinnvoller Lasttest; jeder Node bleibt
|
||||
# zusätzlich durch OLLAMA_NODE_MAX_INFLIGHT begrenzt.
|
||||
WORKERS=2
|
||||
WORKERS=2
|
||||
|
||||
@@ -62,7 +62,7 @@ func main() {
|
||||
nodes = append(nodes, ollama.NodeConfig{Name: name, URL: nodeURL, Weight: weight})
|
||||
}
|
||||
o, err := ollama.NewPool(ollama.PoolConfig{
|
||||
Nodes: nodes, RoutingMode: cfg.OllamaRoutingMode, NodeMaxInflight: cfg.OllamaNodeMaxInflight,
|
||||
APIKey: cfg.OllamaAPIKey, Nodes: nodes, RoutingMode: cfg.OllamaRoutingMode, NodeMaxInflight: cfg.OllamaNodeMaxInflight,
|
||||
HealthInterval: cfg.OllamaNodeHealthInterval, FailureCooldown: cfg.OllamaNodeFailureCooldown,
|
||||
NodeRequestTimeout: cfg.OllamaNodeRequestTimeout, FailoverEnabled: cfg.OllamaFailoverEnabled,
|
||||
FailoverAttempts: cfg.OllamaFailoverAttempts, RequireSameModelDigest: cfg.OllamaRequireSameDigest,
|
||||
|
||||
@@ -8,6 +8,7 @@ services:
|
||||
DATA_DIR: /app/data
|
||||
KNOWLEDGE_DIR: /app/knowledge
|
||||
OLLAMA_URL: ${OLLAMA_URL:-http://ollama:11434}
|
||||
OLLAMA_API_KEY: ${OLLAMA_API_KEY:-}
|
||||
OLLAMA_TIMEOUT: ${OLLAMA_TIMEOUT:-10m}
|
||||
OLLAMA_NUM_PREDICT: ${OLLAMA_NUM_PREDICT:-768}
|
||||
OLLAMA_JSON_RETRIES: ${OLLAMA_JSON_RETRIES:-1}
|
||||
|
||||
@@ -24,6 +24,7 @@ services:
|
||||
DATA_DIR: /app/data
|
||||
KNOWLEDGE_DIR: /app/knowledge
|
||||
OLLAMA_URL: ${OLLAMA_URL:-http://ollama:11434}
|
||||
OLLAMA_API_KEY: ${OLLAMA_API_KEY:-}
|
||||
# Local CPU inference can take several minutes on the first request.
|
||||
OLLAMA_TIMEOUT: ${OLLAMA_TIMEOUT:-10m}
|
||||
OLLAMA_NUM_PREDICT: ${OLLAMA_NUM_PREDICT:-768}
|
||||
|
||||
@@ -39,6 +39,7 @@ type Config struct {
|
||||
GLPIAllowedStatusIDs []int64
|
||||
|
||||
OllamaURL string // legacy single-node value
|
||||
OllamaAPIKey string // optional Bearer token sent to every Ollama node
|
||||
OllamaURLs []string
|
||||
OllamaNodeNames []string
|
||||
OllamaNodeWeights []int
|
||||
@@ -246,6 +247,7 @@ func Load() (Config, error) {
|
||||
GLPIAllowInsecureHTTP: envBool("GLPI_ALLOW_INSECURE_HTTP", false),
|
||||
GLPIAllowedStatusIDs: envInt64List("GLPI_ALLOWED_STATUS_IDS", "1"),
|
||||
OllamaURL: strings.TrimRight(env("OLLAMA_URL", "http://ollama:11434"), "/"),
|
||||
OllamaAPIKey: strings.TrimSpace(os.Getenv("OLLAMA_API_KEY")),
|
||||
OllamaURLs: envStringListPreserveCase("OLLAMA_URLS", ""),
|
||||
OllamaNodeNames: envStringListPreserveCase("OLLAMA_NODE_NAMES", ""),
|
||||
OllamaNodeWeights: envIntListAllowEmpty("OLLAMA_NODE_WEIGHTS"),
|
||||
|
||||
@@ -29,6 +29,7 @@ type NodeConfig struct {
|
||||
}
|
||||
|
||||
type PoolConfig struct {
|
||||
APIKey string
|
||||
Nodes []NodeConfig
|
||||
RoutingMode string
|
||||
NodeMaxInflight int
|
||||
@@ -390,6 +391,7 @@ func (p *Pool) checkNode(ctx context.Context, n *poolNode) (string, string, erro
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
setBearer(req, p.cfg.APIKey)
|
||||
resp, err := p.http.Do(req)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
@@ -559,6 +561,7 @@ func (p *Pool) doPost(ctx context.Context, n *poolNode, path string, body []byte
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
setBearer(req, p.cfg.APIKey)
|
||||
resp, err := p.http.Do(req)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
@@ -574,6 +577,13 @@ func (p *Pool) doPost(ctx context.Context, n *poolNode, path string, body []byte
|
||||
return resp.StatusCode, raw, nil
|
||||
}
|
||||
|
||||
func setBearer(req *http.Request, token string) {
|
||||
token = strings.TrimSpace(token)
|
||||
if token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pool) selectNode(ctx context.Context, excluded map[string]struct{}, stage string) (*poolNode, int64, error) {
|
||||
for {
|
||||
nodes := p.orderedCandidates(excluded, stage)
|
||||
|
||||
@@ -388,3 +388,41 @@ func TestPoolFastestRecentProbesUnmeasuredNodes(t *testing.T) {
|
||||
t.Fatalf("fastest_recent must measure both nodes before preferring one: a=%d b=%d", callsA.Load(), callsB.Load())
|
||||
}
|
||||
}
|
||||
|
||||
func TestPoolSendsBearerToHealthAndInference(t *testing.T) {
|
||||
const token = "ollama-secret"
|
||||
var tagsAuth, embedAuth string
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/api/tags":
|
||||
tagsAuth = r.Header.Get("Authorization")
|
||||
tagsResponse(w, "chat-digest", "embed-digest")
|
||||
case "/api/embed":
|
||||
embedAuth = r.Header.Get("Authorization")
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"embeddings": [][]float64{{1, 0}}})
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}))
|
||||
defer srv.Close()
|
||||
c, err := NewPool(PoolConfig{
|
||||
APIKey: token, Nodes: []NodeConfig{{Name: "secure", URL: srv.URL, Weight: 1}}, RoutingMode: "least_inflight",
|
||||
NodeMaxInflight: 1, HealthInterval: time.Minute, FailureCooldown: time.Second, NodeRequestTimeout: time.Second,
|
||||
FailoverEnabled: false, FailoverAttempts: 1, RequireSameModelDigest: true, RequireEmbeddingModel: true, Model: "m", EmbeddingModel: "e",
|
||||
}, "m", "e", "de-DE", "formal", 128, time.Minute, false, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := c.Ping(context.Background()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := c.Embed(context.Background(), []string{"x"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tagsAuth != "Bearer "+token {
|
||||
t.Fatalf("tags authorization=%q", tagsAuth)
|
||||
}
|
||||
if embedAuth != "Bearer "+token {
|
||||
t.Fatalf("embed authorization=%q", embedAuth)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2464,7 +2464,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/config",
|
||||
"meta": {
|
||||
"exported": true,
|
||||
"line": 1063,
|
||||
"line": 1065,
|
||||
"path": "services/agent/internal/config/config.go"
|
||||
}
|
||||
},
|
||||
@@ -2476,7 +2476,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/config",
|
||||
"meta": {
|
||||
"exported": true,
|
||||
"line": 440,
|
||||
"line": 442,
|
||||
"path": "services/agent/internal/config/config.go"
|
||||
}
|
||||
},
|
||||
@@ -2488,7 +2488,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/config",
|
||||
"meta": {
|
||||
"exported": true,
|
||||
"line": 224,
|
||||
"line": 225,
|
||||
"path": "services/agent/internal/config/config.go"
|
||||
}
|
||||
},
|
||||
@@ -2500,7 +2500,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/config",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 1037,
|
||||
"line": 1039,
|
||||
"path": "services/agent/internal/config/config.go"
|
||||
}
|
||||
},
|
||||
@@ -2512,7 +2512,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/config",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 1208,
|
||||
"line": 1210,
|
||||
"path": "services/agent/internal/config/config.go"
|
||||
}
|
||||
},
|
||||
@@ -2524,7 +2524,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/config",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 1252,
|
||||
"line": 1254,
|
||||
"path": "services/agent/internal/config/config.go"
|
||||
}
|
||||
},
|
||||
@@ -2536,7 +2536,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/config",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 1241,
|
||||
"line": 1243,
|
||||
"path": "services/agent/internal/config/config.go"
|
||||
}
|
||||
},
|
||||
@@ -2548,7 +2548,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/config",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 1219,
|
||||
"line": 1221,
|
||||
"path": "services/agent/internal/config/config.go"
|
||||
}
|
||||
},
|
||||
@@ -2560,7 +2560,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/config",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 1230,
|
||||
"line": 1232,
|
||||
"path": "services/agent/internal/config/config.go"
|
||||
}
|
||||
},
|
||||
@@ -2572,7 +2572,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/config",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 1086,
|
||||
"line": 1088,
|
||||
"path": "services/agent/internal/config/config.go"
|
||||
}
|
||||
},
|
||||
@@ -2584,7 +2584,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/config",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 1108,
|
||||
"line": 1110,
|
||||
"path": "services/agent/internal/config/config.go"
|
||||
}
|
||||
},
|
||||
@@ -2596,7 +2596,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/config",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 1191,
|
||||
"line": 1193,
|
||||
"path": "services/agent/internal/config/config.go"
|
||||
}
|
||||
},
|
||||
@@ -2608,7 +2608,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/config",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 1052,
|
||||
"line": 1054,
|
||||
"path": "services/agent/internal/config/config.go"
|
||||
}
|
||||
},
|
||||
@@ -2620,7 +2620,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/config",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 1183,
|
||||
"line": 1185,
|
||||
"path": "services/agent/internal/config/config.go"
|
||||
}
|
||||
},
|
||||
@@ -2632,7 +2632,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/config",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 1130,
|
||||
"line": 1132,
|
||||
"path": "services/agent/internal/config/config.go"
|
||||
}
|
||||
},
|
||||
@@ -2644,7 +2644,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/config",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 1156,
|
||||
"line": 1158,
|
||||
"path": "services/agent/internal/config/config.go"
|
||||
}
|
||||
},
|
||||
@@ -2656,7 +2656,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/config",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 1046,
|
||||
"line": 1048,
|
||||
"path": "services/agent/internal/config/config.go"
|
||||
}
|
||||
},
|
||||
@@ -2668,7 +2668,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/config",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 1082,
|
||||
"line": 1084,
|
||||
"path": "services/agent/internal/config/config.go"
|
||||
}
|
||||
},
|
||||
@@ -2680,7 +2680,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/config",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 1023,
|
||||
"line": 1025,
|
||||
"path": "services/agent/internal/config/config.go"
|
||||
}
|
||||
},
|
||||
@@ -2692,7 +2692,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/config",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 1018,
|
||||
"line": 1020,
|
||||
"path": "services/agent/internal/config/config.go"
|
||||
}
|
||||
},
|
||||
@@ -2704,7 +2704,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/config",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 993,
|
||||
"line": 995,
|
||||
"path": "services/agent/internal/config/config.go"
|
||||
}
|
||||
},
|
||||
@@ -5716,7 +5716,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": true,
|
||||
"line": 272,
|
||||
"line": 273,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -5728,7 +5728,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": true,
|
||||
"line": 262,
|
||||
"line": 263,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -5740,7 +5740,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": true,
|
||||
"line": 243,
|
||||
"line": 244,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -5752,7 +5752,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 667,
|
||||
"line": 677,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -5764,7 +5764,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 388,
|
||||
"line": 389,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -5776,7 +5776,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 549,
|
||||
"line": 551,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -5788,7 +5788,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 596,
|
||||
"line": 606,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -5800,7 +5800,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 453,
|
||||
"line": 455,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -5812,7 +5812,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 280,
|
||||
"line": 281,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -5824,7 +5824,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 577,
|
||||
"line": 587,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -5836,7 +5836,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 680,
|
||||
"line": 690,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -5848,7 +5848,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": true,
|
||||
"line": 774,
|
||||
"line": 784,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -5860,7 +5860,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": true,
|
||||
"line": 731,
|
||||
"line": 741,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -5872,7 +5872,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 369,
|
||||
"line": 370,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -5896,7 +5896,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 709,
|
||||
"line": 719,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -5908,7 +5908,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 693,
|
||||
"line": 703,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -5920,7 +5920,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 763,
|
||||
"line": 773,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -5932,7 +5932,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 715,
|
||||
"line": 725,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -5944,7 +5944,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 438,
|
||||
"line": 440,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -5956,7 +5956,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 161,
|
||||
"line": 162,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -5992,7 +5992,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 703,
|
||||
"line": 713,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -6004,7 +6004,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 78,
|
||||
"line": 79,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -6016,7 +6016,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 540,
|
||||
"line": 542,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -6028,7 +6028,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 92,
|
||||
"line": 93,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -6040,7 +6040,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 104,
|
||||
"line": 105,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -6052,7 +6052,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 87,
|
||||
"line": 88,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -6064,7 +6064,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 135,
|
||||
"line": 136,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -6076,7 +6076,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 745,
|
||||
"line": 755,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -6088,7 +6088,19 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 740,
|
||||
"line": 750,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "function:github.com/example/glpi-ai-agent/internal/ollama:setBearer",
|
||||
"kind": "function",
|
||||
"label": "setBearer",
|
||||
"group": "engineering",
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 580,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -6112,7 +6124,7 @@
|
||||
"community": "github.com/example/glpi-ai-agent/internal/ollama",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 736,
|
||||
"line": 746,
|
||||
"path": "services/agent/internal/ollama/pool.go"
|
||||
}
|
||||
},
|
||||
@@ -7756,7 +7768,7 @@
|
||||
"community": "kb-editor/cmd/server",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 292,
|
||||
"line": 293,
|
||||
"path": "services/knowledge/cmd/server/main.go"
|
||||
}
|
||||
},
|
||||
@@ -7768,7 +7780,7 @@
|
||||
"community": "kb-editor/cmd/server",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 304,
|
||||
"line": 305,
|
||||
"path": "services/knowledge/cmd/server/main.go"
|
||||
}
|
||||
},
|
||||
@@ -7804,7 +7816,7 @@
|
||||
"community": "kb-editor/cmd/server",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 345,
|
||||
"line": 346,
|
||||
"path": "services/knowledge/cmd/server/main.go"
|
||||
}
|
||||
},
|
||||
@@ -7828,7 +7840,7 @@
|
||||
"community": "kb-editor/cmd/server",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 311,
|
||||
"line": 312,
|
||||
"path": "services/knowledge/cmd/server/main.go"
|
||||
}
|
||||
},
|
||||
@@ -7840,7 +7852,7 @@
|
||||
"community": "kb-editor/cmd/server",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 284,
|
||||
"line": 285,
|
||||
"path": "services/knowledge/cmd/server/main.go"
|
||||
}
|
||||
},
|
||||
@@ -7864,7 +7876,7 @@
|
||||
"community": "kb-editor/cmd/server",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 337,
|
||||
"line": 338,
|
||||
"path": "services/knowledge/cmd/server/main.go"
|
||||
}
|
||||
},
|
||||
@@ -7948,7 +7960,7 @@
|
||||
"community": "kb-editor/internal/aifallback",
|
||||
"meta": {
|
||||
"exported": true,
|
||||
"line": 40,
|
||||
"line": 41,
|
||||
"path": "services/knowledge/internal/aifallback/ollama.go"
|
||||
}
|
||||
},
|
||||
@@ -7960,7 +7972,7 @@
|
||||
"community": "kb-editor/internal/aifallback",
|
||||
"meta": {
|
||||
"exported": true,
|
||||
"line": 76,
|
||||
"line": 77,
|
||||
"path": "services/knowledge/internal/aifallback/ollama.go"
|
||||
}
|
||||
},
|
||||
@@ -7972,7 +7984,7 @@
|
||||
"community": "kb-editor/internal/aifallback",
|
||||
"meta": {
|
||||
"exported": true,
|
||||
"line": 106,
|
||||
"line": 107,
|
||||
"path": "services/knowledge/internal/aifallback/ollama.go"
|
||||
}
|
||||
},
|
||||
@@ -7984,7 +7996,7 @@
|
||||
"community": "kb-editor/internal/aifallback",
|
||||
"meta": {
|
||||
"exported": true,
|
||||
"line": 73,
|
||||
"line": 74,
|
||||
"path": "services/knowledge/internal/aifallback/ollama.go"
|
||||
}
|
||||
},
|
||||
@@ -7996,7 +8008,7 @@
|
||||
"community": "kb-editor/internal/aifallback",
|
||||
"meta": {
|
||||
"exported": true,
|
||||
"line": 74,
|
||||
"line": 75,
|
||||
"path": "services/knowledge/internal/aifallback/ollama.go"
|
||||
}
|
||||
},
|
||||
@@ -8008,7 +8020,7 @@
|
||||
"community": "kb-editor/internal/aifallback",
|
||||
"meta": {
|
||||
"exported": true,
|
||||
"line": 72,
|
||||
"line": 73,
|
||||
"path": "services/knowledge/internal/aifallback/ollama.go"
|
||||
}
|
||||
},
|
||||
@@ -8020,7 +8032,7 @@
|
||||
"community": "kb-editor/internal/aifallback",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 110,
|
||||
"line": 111,
|
||||
"path": "services/knowledge/internal/aifallback/ollama.go"
|
||||
}
|
||||
},
|
||||
@@ -9832,7 +9844,7 @@
|
||||
"community": "neuroforge/cmd/server",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 623,
|
||||
"line": 632,
|
||||
"path": "platform/neuroforge/cmd/server/main.go"
|
||||
}
|
||||
},
|
||||
@@ -9916,7 +9928,7 @@
|
||||
"community": "neuroforge/cmd/worker",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 255,
|
||||
"line": 257,
|
||||
"path": "platform/neuroforge/cmd/worker/main.go"
|
||||
}
|
||||
},
|
||||
@@ -9928,7 +9940,7 @@
|
||||
"community": "neuroforge/cmd/worker",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 416,
|
||||
"line": 421,
|
||||
"path": "platform/neuroforge/cmd/worker/main.go"
|
||||
}
|
||||
},
|
||||
@@ -9940,7 +9952,7 @@
|
||||
"community": "neuroforge/cmd/worker",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 454,
|
||||
"line": 459,
|
||||
"path": "platform/neuroforge/cmd/worker/main.go"
|
||||
}
|
||||
},
|
||||
@@ -9952,7 +9964,7 @@
|
||||
"community": "neuroforge/cmd/worker",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 507,
|
||||
"line": 512,
|
||||
"path": "platform/neuroforge/cmd/worker/main.go"
|
||||
}
|
||||
},
|
||||
@@ -9964,7 +9976,7 @@
|
||||
"community": "neuroforge/cmd/worker",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 495,
|
||||
"line": 500,
|
||||
"path": "platform/neuroforge/cmd/worker/main.go"
|
||||
}
|
||||
},
|
||||
@@ -9976,7 +9988,7 @@
|
||||
"community": "neuroforge/cmd/worker",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 486,
|
||||
"line": 491,
|
||||
"path": "platform/neuroforge/cmd/worker/main.go"
|
||||
}
|
||||
},
|
||||
@@ -9988,7 +10000,7 @@
|
||||
"community": "neuroforge/cmd/worker",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 468,
|
||||
"line": 473,
|
||||
"path": "platform/neuroforge/cmd/worker/main.go"
|
||||
}
|
||||
},
|
||||
@@ -10000,7 +10012,7 @@
|
||||
"community": "neuroforge/cmd/worker",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 239,
|
||||
"line": 241,
|
||||
"path": "platform/neuroforge/cmd/worker/main.go"
|
||||
}
|
||||
},
|
||||
@@ -10012,7 +10024,7 @@
|
||||
"community": "neuroforge/cmd/worker",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 446,
|
||||
"line": 451,
|
||||
"path": "platform/neuroforge/cmd/worker/main.go"
|
||||
}
|
||||
},
|
||||
@@ -10024,7 +10036,7 @@
|
||||
"community": "neuroforge/cmd/worker",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 100,
|
||||
"line": 101,
|
||||
"path": "platform/neuroforge/cmd/worker/main.go"
|
||||
}
|
||||
},
|
||||
@@ -10036,7 +10048,7 @@
|
||||
"community": "neuroforge/cmd/worker",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 105,
|
||||
"line": 106,
|
||||
"path": "platform/neuroforge/cmd/worker/main.go"
|
||||
}
|
||||
},
|
||||
@@ -10048,7 +10060,7 @@
|
||||
"community": "neuroforge/cmd/worker",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 110,
|
||||
"line": 111,
|
||||
"path": "platform/neuroforge/cmd/worker/main.go"
|
||||
}
|
||||
},
|
||||
@@ -10060,7 +10072,7 @@
|
||||
"community": "neuroforge/cmd/worker",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 478,
|
||||
"line": 483,
|
||||
"path": "platform/neuroforge/cmd/worker/main.go"
|
||||
}
|
||||
},
|
||||
@@ -10072,7 +10084,7 @@
|
||||
"community": "neuroforge/cmd/worker",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 120,
|
||||
"line": 121,
|
||||
"path": "platform/neuroforge/cmd/worker/main.go"
|
||||
}
|
||||
},
|
||||
@@ -10084,7 +10096,7 @@
|
||||
"community": "neuroforge/cmd/worker",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 354,
|
||||
"line": 356,
|
||||
"path": "platform/neuroforge/cmd/worker/main.go"
|
||||
}
|
||||
},
|
||||
@@ -10096,7 +10108,7 @@
|
||||
"community": "neuroforge/cmd/worker",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 332,
|
||||
"line": 334,
|
||||
"path": "platform/neuroforge/cmd/worker/main.go"
|
||||
}
|
||||
},
|
||||
@@ -10108,7 +10120,7 @@
|
||||
"community": "neuroforge/cmd/worker",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 189,
|
||||
"line": 191,
|
||||
"path": "platform/neuroforge/cmd/worker/main.go"
|
||||
}
|
||||
},
|
||||
@@ -10120,7 +10132,7 @@
|
||||
"community": "neuroforge/cmd/worker",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 421,
|
||||
"line": 426,
|
||||
"path": "platform/neuroforge/cmd/worker/main.go"
|
||||
}
|
||||
},
|
||||
@@ -10132,7 +10144,7 @@
|
||||
"community": "neuroforge/cmd/worker",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 394,
|
||||
"line": 396,
|
||||
"path": "platform/neuroforge/cmd/worker/main.go"
|
||||
}
|
||||
},
|
||||
@@ -10144,7 +10156,7 @@
|
||||
"community": "neuroforge/cmd/worker",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 235,
|
||||
"line": 237,
|
||||
"path": "platform/neuroforge/cmd/worker/main.go"
|
||||
}
|
||||
},
|
||||
@@ -10156,7 +10168,7 @@
|
||||
"community": "neuroforge/cmd/worker",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 279,
|
||||
"line": 281,
|
||||
"path": "platform/neuroforge/cmd/worker/main.go"
|
||||
}
|
||||
},
|
||||
@@ -10168,7 +10180,7 @@
|
||||
"community": "neuroforge/cmd/worker",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 178,
|
||||
"line": 180,
|
||||
"path": "platform/neuroforge/cmd/worker/main.go"
|
||||
}
|
||||
},
|
||||
@@ -10180,7 +10192,7 @@
|
||||
"community": "neuroforge/cmd/worker",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 227,
|
||||
"line": 229,
|
||||
"path": "platform/neuroforge/cmd/worker/main.go"
|
||||
}
|
||||
},
|
||||
@@ -12124,7 +12136,7 @@
|
||||
"community": "neuroforge/internal/core",
|
||||
"meta": {
|
||||
"exported": true,
|
||||
"line": 741,
|
||||
"line": 742,
|
||||
"path": "platform/neuroforge/internal/core/types.go"
|
||||
}
|
||||
},
|
||||
@@ -12340,7 +12352,7 @@
|
||||
"community": "neuroforge/internal/httpapi",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 827,
|
||||
"line": 836,
|
||||
"path": "platform/neuroforge/internal/httpapi/httpapi.go"
|
||||
}
|
||||
},
|
||||
@@ -12352,7 +12364,7 @@
|
||||
"community": "neuroforge/internal/httpapi",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 801,
|
||||
"line": 810,
|
||||
"path": "platform/neuroforge/internal/httpapi/httpapi.go"
|
||||
}
|
||||
},
|
||||
@@ -12388,7 +12400,7 @@
|
||||
"community": "neuroforge/internal/httpapi",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 823,
|
||||
"line": 832,
|
||||
"path": "platform/neuroforge/internal/httpapi/httpapi.go"
|
||||
}
|
||||
},
|
||||
@@ -12544,7 +12556,7 @@
|
||||
"community": "neuroforge/internal/httpapi",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 790,
|
||||
"line": 799,
|
||||
"path": "platform/neuroforge/internal/httpapi/httpapi.go"
|
||||
}
|
||||
},
|
||||
@@ -12616,7 +12628,7 @@
|
||||
"community": "neuroforge/internal/httpapi",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 785,
|
||||
"line": 794,
|
||||
"path": "platform/neuroforge/internal/httpapi/httpapi.go"
|
||||
}
|
||||
},
|
||||
@@ -12784,7 +12796,7 @@
|
||||
"community": "neuroforge/internal/httpapi",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 813,
|
||||
"line": 822,
|
||||
"path": "platform/neuroforge/internal/httpapi/httpapi.go"
|
||||
}
|
||||
},
|
||||
@@ -12808,7 +12820,7 @@
|
||||
"community": "neuroforge/internal/httpapi",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 816,
|
||||
"line": 825,
|
||||
"path": "platform/neuroforge/internal/httpapi/httpapi.go"
|
||||
}
|
||||
},
|
||||
@@ -13348,7 +13360,7 @@
|
||||
"community": "neuroforge/internal/httpapi",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 884,
|
||||
"line": 893,
|
||||
"path": "platform/neuroforge/internal/httpapi/httpapi.go"
|
||||
}
|
||||
},
|
||||
@@ -13384,7 +13396,7 @@
|
||||
"community": "neuroforge/internal/httpapi",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 949,
|
||||
"line": 961,
|
||||
"path": "platform/neuroforge/internal/httpapi/httpapi.go"
|
||||
}
|
||||
},
|
||||
@@ -13396,7 +13408,7 @@
|
||||
"community": "neuroforge/internal/httpapi",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 859,
|
||||
"line": 868,
|
||||
"path": "platform/neuroforge/internal/httpapi/httpapi.go"
|
||||
}
|
||||
},
|
||||
@@ -13456,7 +13468,7 @@
|
||||
"community": "neuroforge/internal/httpapi",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 837,
|
||||
"line": 846,
|
||||
"path": "platform/neuroforge/internal/httpapi/httpapi.go"
|
||||
}
|
||||
},
|
||||
@@ -13600,7 +13612,7 @@
|
||||
"community": "neuroforge/internal/httpapi",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 902,
|
||||
"line": 911,
|
||||
"path": "platform/neuroforge/internal/httpapi/httpapi.go"
|
||||
}
|
||||
},
|
||||
@@ -13612,7 +13624,7 @@
|
||||
"community": "neuroforge/internal/httpapi",
|
||||
"meta": {
|
||||
"exported": false,
|
||||
"line": 888,
|
||||
"line": 897,
|
||||
"path": "platform/neuroforge/internal/httpapi/httpapi.go"
|
||||
}
|
||||
},
|
||||
@@ -32967,6 +32979,12 @@
|
||||
"to": "function:github.com/example/glpi-ai-agent/internal/ollama:requestStage",
|
||||
"kind": "defines"
|
||||
},
|
||||
{
|
||||
"id": "file:services/agent/internal/ollama/pool.go-\u003efunction:github.com/example/glpi-ai-agent/internal/ollama:setBearer:defines",
|
||||
"from": "file:services/agent/internal/ollama/pool.go",
|
||||
"to": "function:github.com/example/glpi-ai-agent/internal/ollama:setBearer",
|
||||
"kind": "defines"
|
||||
},
|
||||
{
|
||||
"id": "file:services/agent/internal/ollama/pool.go-\u003efunction:github.com/example/glpi-ai-agent/internal/ollama:withStage:defines",
|
||||
"from": "file:services/agent/internal/ollama/pool.go",
|
||||
@@ -43734,6 +43752,12 @@
|
||||
"to": "function:github.com/example/glpi-ai-agent/internal/ollama:modelNameMatches",
|
||||
"kind": "calls"
|
||||
},
|
||||
{
|
||||
"id": "function:github.com/example/glpi-ai-agent/internal/ollama:Pool.checkNode-\u003efunction:github.com/example/glpi-ai-agent/internal/ollama:setBearer:calls",
|
||||
"from": "function:github.com/example/glpi-ai-agent/internal/ollama:Pool.checkNode",
|
||||
"to": "function:github.com/example/glpi-ai-agent/internal/ollama:setBearer",
|
||||
"kind": "calls"
|
||||
},
|
||||
{
|
||||
"id": "function:github.com/example/glpi-ai-agent/internal/ollama:Pool.checkNode-\u003epackage:encoding/json:calls_package",
|
||||
"from": "function:github.com/example/glpi-ai-agent/internal/ollama:Pool.checkNode",
|
||||
@@ -43769,6 +43793,12 @@
|
||||
"kind": "calls_package",
|
||||
"label": "TrimSpace"
|
||||
},
|
||||
{
|
||||
"id": "function:github.com/example/glpi-ai-agent/internal/ollama:Pool.doPost-\u003efunction:github.com/example/glpi-ai-agent/internal/ollama:setBearer:calls",
|
||||
"from": "function:github.com/example/glpi-ai-agent/internal/ollama:Pool.doPost",
|
||||
"to": "function:github.com/example/glpi-ai-agent/internal/ollama:setBearer",
|
||||
"kind": "calls"
|
||||
},
|
||||
{
|
||||
"id": "function:github.com/example/glpi-ai-agent/internal/ollama:Pool.doPost-\u003epackage:bytes:calls_package",
|
||||
"from": "function:github.com/example/glpi-ai-agent/internal/ollama:Pool.doPost",
|
||||
@@ -44018,6 +44048,13 @@
|
||||
"kind": "calls_package",
|
||||
"label": "Now"
|
||||
},
|
||||
{
|
||||
"id": "function:github.com/example/glpi-ai-agent/internal/ollama:setBearer-\u003epackage:strings:calls_package",
|
||||
"from": "function:github.com/example/glpi-ai-agent/internal/ollama:setBearer",
|
||||
"to": "package:strings",
|
||||
"kind": "calls_package",
|
||||
"label": "TrimSpace"
|
||||
},
|
||||
{
|
||||
"id": "function:github.com/example/glpi-ai-agent/internal/ollama:uniqueStrings-\u003epackage:strings:calls_package",
|
||||
"from": "function:github.com/example/glpi-ai-agent/internal/ollama:uniqueStrings",
|
||||
@@ -56620,6 +56657,13 @@
|
||||
"kind": "calls_package",
|
||||
"label": "NewRequestWithContext"
|
||||
},
|
||||
{
|
||||
"id": "function:neuroforge/internal/provider:Router.Health-\u003epackage:strings:calls_package",
|
||||
"from": "function:neuroforge/internal/provider:Router.Health",
|
||||
"to": "package:strings",
|
||||
"kind": "calls_package",
|
||||
"label": "TrimSpace"
|
||||
},
|
||||
{
|
||||
"id": "function:neuroforge/internal/provider:Router.chatOllama-\u003efunction:neuroforge/internal/provider:Router.doJSON:calls",
|
||||
"from": "function:neuroforge/internal/provider:Router.chatOllama",
|
||||
@@ -66863,10 +66907,10 @@
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"edges": 7273,
|
||||
"edges": 7278,
|
||||
"format_version": 1,
|
||||
"generator": "go-ast+compose",
|
||||
"modules": 4,
|
||||
"nodes": 1845
|
||||
"nodes": 1846
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# Betriebsmodus desselben Images:
|
||||
# Optional: Bearer token sent as Authorization: Bearer <token> to Ollama-compatible endpoints.
|
||||
OLLAMA_API_KEY=
|
||||
# editor = vollständiger Einzel-/Masseneditor mit Schreibzugriff
|
||||
# google = schreibgeschützte Helpdesk-Suchmaschine mit Artikel-Viewer
|
||||
APP_MODE=editor
|
||||
|
||||
@@ -269,6 +269,7 @@ func aiServiceFromEnv(mode string, st *staging.Store) (*aifallback.Service, time
|
||||
}
|
||||
svc, err := aifallback.New(aifallback.Config{
|
||||
BaseURL: envOr("OLLAMA_BASE_URL", "http://ollama:11434"),
|
||||
APIKey: strings.TrimSpace(os.Getenv("OLLAMA_API_KEY")),
|
||||
Model: strings.TrimSpace(os.Getenv("OLLAMA_MODEL")),
|
||||
Timeout: timeout,
|
||||
MaxConcurrent: maxConcurrent,
|
||||
|
||||
@@ -49,6 +49,7 @@ services:
|
||||
BASIC_AUTH_PASSWORD: "${SEARCH_AUTH_PASSWORD:-}"
|
||||
AI_FALLBACK_ENABLED: "${AI_FALLBACK_ENABLED:-false}"
|
||||
OLLAMA_BASE_URL: "${OLLAMA_BASE_URL:-http://ollama:11434}"
|
||||
OLLAMA_API_KEY: ${OLLAMA_API_KEY:-}
|
||||
OLLAMA_MODEL: "${OLLAMA_MODEL:-}"
|
||||
OLLAMA_TIMEOUT: "${OLLAMA_TIMEOUT:-10m}"
|
||||
OLLAMA_MAX_CONCURRENT: "${OLLAMA_MAX_CONCURRENT:-1}"
|
||||
|
||||
@@ -18,6 +18,7 @@ services:
|
||||
BASIC_AUTH_PASSWORD: "${BASIC_AUTH_PASSWORD:-}"
|
||||
AI_FALLBACK_ENABLED: "${AI_FALLBACK_ENABLED:-false}"
|
||||
OLLAMA_BASE_URL: "${OLLAMA_BASE_URL:-http://ollama:11434}"
|
||||
OLLAMA_API_KEY: ${OLLAMA_API_KEY:-}
|
||||
OLLAMA_MODEL: "${OLLAMA_MODEL:-}"
|
||||
OLLAMA_TIMEOUT: "${OLLAMA_TIMEOUT:-10m}"
|
||||
OLLAMA_MAX_CONCURRENT: "${OLLAMA_MAX_CONCURRENT:-1}"
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
type Config struct {
|
||||
BaseURL string
|
||||
Model string
|
||||
APIKey string
|
||||
Timeout time.Duration
|
||||
MaxConcurrent int
|
||||
AutoReply bool
|
||||
@@ -148,6 +149,9 @@ func (s *Service) askOllama(ctx context.Context, query string) (staging.Draft, e
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
if token := strings.TrimSpace(s.cfg.APIKey); token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
}
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
if errors.Is(err, context.DeadlineExceeded) || errors.Is(ctx.Err(), context.DeadlineExceeded) {
|
||||
|
||||
@@ -50,3 +50,27 @@ func TestGenerateUsesStructuredChatAndStoresResult(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateSendsOllamaBearerToken(t *testing.T) {
|
||||
const token = "ollama-secret"
|
||||
var auth string
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
auth = r.Header.Get("Authorization")
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"message": map[string]any{"content": `{"title":"T","text":"X","answer":"A","categories":[],"keywords":[]}`}})
|
||||
}))
|
||||
defer server.Close()
|
||||
st, err := staging.New(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
svc, err := New(Config{BaseURL: server.URL, Model: "test", APIKey: token, Timeout: time.Second, MaxConcurrent: 1, MinScore: 0.78}, st)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := svc.Generate(context.Background(), "secure ollama request"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if auth != "Bearer "+token {
|
||||
t.Fatalf("authorization=%q", auth)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user