Initial commit: SearXNG + Firecrawl + HHEM API stack
Self-hosted Docker Compose stack for DGX Spark (ARM64/aarch64, CPU-only): - SearXNG on port 8889 with JSON API enabled, rate limiting off - Firecrawl (built from local source) on port 3002, no API key required - HHEM API (FastAPI + vectara/hallucination_evaluation_model) on port 8881 - Portainer stack YAML using dgx_net external network - build-images.sh to pre-build local images before Portainer deploy - HF model cache bind-mounted from /home/sparky/LLMs/huggingface Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
dd44a2c2b1
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# Docker build artifacts
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Don't commit secrets or local overrides
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
149
Portainer-Stack.yaml
Normal file
149
Portainer-Stack.yaml
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
services:
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# 1. SearXNG — meta-search with JSON API
|
||||||
|
# ===========================================================================
|
||||||
|
searxng:
|
||||||
|
image: docker.io/searxng/searxng:latest
|
||||||
|
container_name: searxng-8889
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "8889:8080"
|
||||||
|
volumes:
|
||||||
|
- /home/sparky/Docker/searXNG Firecrawl HHEM /searxng:/etc/searxng:rw
|
||||||
|
- searxng-cache:/var/cache/searxng:rw
|
||||||
|
environment:
|
||||||
|
- SEARXNG_BASE_URL=http://localhost:8889/
|
||||||
|
cap_drop:
|
||||||
|
- ALL
|
||||||
|
cap_add:
|
||||||
|
- CHOWN
|
||||||
|
- SETGID
|
||||||
|
- SETUID
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "wget -qO- http://localhost:8080/healthz || exit 1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 20s
|
||||||
|
networks:
|
||||||
|
- dgx_net
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# 2. Firecrawl — web scraper (self-hosted, no API key required)
|
||||||
|
# Images must be pre-built: run build-images.sh before deploying this stack
|
||||||
|
# ===========================================================================
|
||||||
|
|
||||||
|
firecrawl-redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
container_name: firecrawl-redis
|
||||||
|
restart: unless-stopped
|
||||||
|
command: redis-server --bind 0.0.0.0 --save 60 1 --loglevel warning
|
||||||
|
volumes:
|
||||||
|
- firecrawl-redis-data:/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "ping"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
networks:
|
||||||
|
- dgx_net
|
||||||
|
|
||||||
|
firecrawl-postgres:
|
||||||
|
image: firecrawl-postgres:local
|
||||||
|
container_name: firecrawl-postgres
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=postgres
|
||||||
|
- POSTGRES_PASSWORD=postgres
|
||||||
|
- POSTGRES_DB=postgres
|
||||||
|
volumes:
|
||||||
|
- firecrawl-pg-data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "pg_isready", "-U", "postgres"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
networks:
|
||||||
|
- dgx_net
|
||||||
|
|
||||||
|
firecrawl-playwright:
|
||||||
|
image: firecrawl-playwright:local
|
||||||
|
container_name: firecrawl-playwright
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- PORT=3000
|
||||||
|
shm_size: "2gb"
|
||||||
|
networks:
|
||||||
|
- dgx_net
|
||||||
|
|
||||||
|
firecrawl-api:
|
||||||
|
image: firecrawl-api:local
|
||||||
|
container_name: firecrawl-api-3002
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "3002:3002"
|
||||||
|
environment:
|
||||||
|
- USE_DB_AUTHENTICATION=false
|
||||||
|
- REDIS_URL=redis://firecrawl-redis:6379
|
||||||
|
- REDIS_RATE_LIMIT_URL=redis://firecrawl-redis:6379
|
||||||
|
- PLAYWRIGHT_MICROSERVICE_URL=http://firecrawl-playwright:3000/scrape
|
||||||
|
- NUQ_DATABASE_URL=postgres://postgres:postgres@firecrawl-postgres:5432/postgres
|
||||||
|
- HOST=0.0.0.0
|
||||||
|
- PORT=3002
|
||||||
|
- NODE_ENV=production
|
||||||
|
- BULL_AUTH_KEY=aitools-bull-changeme
|
||||||
|
- SEARXNG_ENDPOINT=http://searxng:8080
|
||||||
|
command: node dist/src/harness.js --start-docker
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
|
ulimits:
|
||||||
|
nofile:
|
||||||
|
soft: 65535
|
||||||
|
hard: 65535
|
||||||
|
depends_on:
|
||||||
|
- firecrawl-redis
|
||||||
|
- firecrawl-postgres
|
||||||
|
- firecrawl-playwright
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "wget -qO- http://localhost:3002/health || exit 1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 5
|
||||||
|
start_period: 60s
|
||||||
|
networks:
|
||||||
|
- dgx_net
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# 3. HHEM API — Vectara hallucination evaluator (CPU-only, ARM64)
|
||||||
|
# Images must be pre-built: run build-images.sh before deploying this stack
|
||||||
|
# ===========================================================================
|
||||||
|
hhem-api:
|
||||||
|
image: hhem-api:local
|
||||||
|
container_name: hhem-api-8881
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "8881:8881"
|
||||||
|
volumes:
|
||||||
|
# Bind-mount the shared HF model cache so the HHEM model is only
|
||||||
|
# downloaded once and lives alongside all other LLMs on this host.
|
||||||
|
- /home/sparky/LLMs/huggingface:/hf-cache
|
||||||
|
environment:
|
||||||
|
- HF_HOME=/hf-cache
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "wget -qO- http://localhost:8881/health || exit 1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 120s
|
||||||
|
networks:
|
||||||
|
- dgx_net
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
searxng-cache:
|
||||||
|
firecrawl-redis-data:
|
||||||
|
firecrawl-pg-data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
dgx_net:
|
||||||
|
external: true
|
||||||
241
README.md
Normal file
241
README.md
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
# AI Tools Stack — SearXNG + Firecrawl + HHEM
|
||||||
|
|
||||||
|
Self-hosted Docker Compose stack for the DGX Spark (ARM64/aarch64, CPU-only).
|
||||||
|
Runs alongside the existing n8n automation stack and exposes three services for agent use.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/Docker/searXNG\ Firecrawl\ HHEM\ \
|
||||||
|
|
||||||
|
# First run: build Firecrawl from source (takes ~5–10 min)
|
||||||
|
docker compose build
|
||||||
|
|
||||||
|
# Start everything
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# Check status
|
||||||
|
docker compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
## Services & Ports
|
||||||
|
|
||||||
|
| Service | Port | Container name | Purpose |
|
||||||
|
|------------|------|-------------------|---------|
|
||||||
|
| SearXNG | 8888 | `searxng` | Meta-search (JSON API) |
|
||||||
|
| Firecrawl | 3002 | `firecrawl-api` | Web scraper / crawler |
|
||||||
|
| HHEM API | 8881 | `hhem-api` | Hallucination evaluator |
|
||||||
|
|
||||||
|
All services share the `ai-tools` Docker network.
|
||||||
|
n8n reaches them at `http://host.docker.internal:<port>` (or `http://<DGX-IP>:<port>`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. SearXNG — Meta-Search
|
||||||
|
|
||||||
|
**Base URL:** `http://localhost:8888`
|
||||||
|
|
||||||
|
### Search (JSON)
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /search?q=<query>&format=json&categories=general
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example curl:**
|
||||||
|
```bash
|
||||||
|
curl "http://localhost:8888/search?q=Claude+AI&format=json" | jq '.results[0]'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response shape:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"query": "Claude AI",
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"title": "...",
|
||||||
|
"url": "https://...",
|
||||||
|
"content": "...",
|
||||||
|
"engine": "google",
|
||||||
|
"score": 1.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### n8n HTTP Request node
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
|-------|-------|
|
||||||
|
| Method | GET |
|
||||||
|
| URL | `http://host.docker.internal:8888/search` |
|
||||||
|
| Query params | `q` = `{{ $json.query }}`, `format` = `json`, `categories` = `general` |
|
||||||
|
| Response | JSON |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Firecrawl — Web Scraper
|
||||||
|
|
||||||
|
**Base URL:** `http://localhost:3002`
|
||||||
|
**No API key required** (self-hosted, `USE_DB_AUTHENTICATION=false`).
|
||||||
|
|
||||||
|
### Scrape a single URL
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /v1/scrape
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"url": "https://example.com",
|
||||||
|
"formats": ["markdown"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example curl:**
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:3002/v1/scrape \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"url": "https://example.com", "formats": ["markdown"]}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response shape:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"markdown": "# Example Domain\n\n...",
|
||||||
|
"metadata": {
|
||||||
|
"title": "Example Domain",
|
||||||
|
"sourceURL": "https://example.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Crawl a site (async)
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /v1/crawl
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"url": "https://example.com",
|
||||||
|
"limit": 10,
|
||||||
|
"scrapeOptions": { "formats": ["markdown"] }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns a `jobId`. Poll `GET /v1/crawl/<jobId>` for results.
|
||||||
|
|
||||||
|
### n8n HTTP Request node (scrape)
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
|-------|-------|
|
||||||
|
| Method | POST |
|
||||||
|
| URL | `http://host.docker.internal:3002/v1/scrape` |
|
||||||
|
| Body (JSON) | `{"url": "{{ $json.url }}", "formats": ["markdown"]}` |
|
||||||
|
| Response | JSON |
|
||||||
|
|
||||||
|
### Queue admin UI
|
||||||
|
|
||||||
|
`http://localhost:3002/admin/aitools-bull-changeme/queues`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. HHEM API — Hallucination Evaluator
|
||||||
|
|
||||||
|
**Base URL:** `http://localhost:8881`
|
||||||
|
**Model:** `vectara/hallucination_evaluation_model` (183 M BERT-based, CPU-only)
|
||||||
|
|
||||||
|
> On first start the model is downloaded from HuggingFace (~700 MB).
|
||||||
|
> Cached in the `hhem-model-cache` volume for subsequent restarts.
|
||||||
|
|
||||||
|
### Score
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /score
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"source": "The Eiffel Tower is in Paris, France.",
|
||||||
|
"generated": "The Eiffel Tower is located in Paris."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"score": 0.9741,
|
||||||
|
"label": "grounded"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Score range | Meaning |
|
||||||
|
|-------------|---------|
|
||||||
|
| > 0.5 | **Grounded** — generated text is faithful to source |
|
||||||
|
| ≤ 0.5 | **Hallucinated** — generated text contradicts or invents facts |
|
||||||
|
|
||||||
|
**Health check:**
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8881/health
|
||||||
|
# {"status":"ok","model":"vectara/hallucination_evaluation_model","loaded":true}
|
||||||
|
```
|
||||||
|
|
||||||
|
### n8n HTTP Request node
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
|-------|-------|
|
||||||
|
| Method | POST |
|
||||||
|
| URL | `http://host.docker.internal:8881/score` |
|
||||||
|
| Body (JSON) | `{"source": "{{ $json.scrapedContent }}", "generated": "{{ $json.llmAnswer }}"}` |
|
||||||
|
| Response | JSON |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## n8n Agentic Pipeline Example
|
||||||
|
|
||||||
|
This stack is designed for a **Search → Scrape → Validate** pipeline:
|
||||||
|
|
||||||
|
```
|
||||||
|
1. [HTTP Request] → SearXNG: search for the user's query, extract top URL
|
||||||
|
2. [HTTP Request] → Firecrawl /v1/scrape: scrape the top result into markdown
|
||||||
|
3. [LLM Node] → Generate an answer grounded in the scraped content
|
||||||
|
4. [HTTP Request] → HHEM /score: validate the answer against the scraped source
|
||||||
|
5. [If] → Route: score > 0.5 → return answer | score ≤ 0.5 → flag/retry
|
||||||
|
```
|
||||||
|
|
||||||
|
### n8n expression for step 4 body:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"source": "{{ $('Firecrawl Scrape').item.json.data.markdown }}",
|
||||||
|
"generated": "{{ $('LLM').item.json.text }}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Build Notes (ARM64 / aarch64)
|
||||||
|
|
||||||
|
- **SearXNG**: official image is multi-arch (ARM64 supported natively).
|
||||||
|
- **Redis**: `redis:7-alpine` is multi-arch.
|
||||||
|
- **Firecrawl**: built from source at `/home/sparky/Docker/firecrawl/git/firecrawl/`.
|
||||||
|
Dockerfile targets Node 22 slim which supports ARM64.
|
||||||
|
- **HHEM API**: `python:3.11-slim` + PyTorch CPU. PyPI provides `linux_aarch64` wheels for torch 2.x.
|
||||||
|
|
||||||
|
## Rebuilding after Firecrawl updates
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git -C ~/Docker/firecrawl/git/firecrawl pull
|
||||||
|
docker compose build firecrawl-playwright firecrawl-api
|
||||||
|
docker compose up -d firecrawl-playwright firecrawl-api
|
||||||
|
```
|
||||||
|
|
||||||
|
## Connecting n8n to this stack
|
||||||
|
|
||||||
|
If n8n does not already have `host.docker.internal` available, add to the n8n service in its compose file:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then use `http://host.docker.internal:<port>` in all n8n HTTP Request nodes.
|
||||||
23
build-images.sh
Executable file
23
build-images.sh
Executable file
@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Run this ONCE before deploying the Portainer stack.
|
||||||
|
# Builds all custom images and tags them for use without a registry.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
FIRECRAWL_SRC="/home/sparky/Docker/firecrawl/git/firecrawl"
|
||||||
|
HHEM_SRC="/home/sparky/Docker/searXNG Firecrawl HHEM /hhem-api"
|
||||||
|
|
||||||
|
echo "=== [1/4] Building firecrawl-postgres:local ==="
|
||||||
|
docker build -t firecrawl-postgres:local "$FIRECRAWL_SRC/apps/nuq-postgres"
|
||||||
|
|
||||||
|
echo "=== [2/4] Building firecrawl-playwright:local ==="
|
||||||
|
docker build -t firecrawl-playwright:local "$FIRECRAWL_SRC/apps/playwright-service-ts"
|
||||||
|
|
||||||
|
echo "=== [3/4] Building firecrawl-api:local ==="
|
||||||
|
docker build -t firecrawl-api:local "$FIRECRAWL_SRC/apps/api"
|
||||||
|
|
||||||
|
echo "=== [4/4] Building hhem-api:local ==="
|
||||||
|
docker build -t hhem-api:local "$HHEM_SRC"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "All images built. You can now deploy the Portainer stack."
|
||||||
|
docker images | grep -E "firecrawl|hhem-api"
|
||||||
178
docker-compose.yml
Normal file
178
docker-compose.yml
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
name: ai-tools-stack
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Shared build env for Firecrawl services (all built from local source)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
x-firecrawl-build: &firecrawl-build
|
||||||
|
build:
|
||||||
|
context: /home/sparky/Docker/firecrawl/git/firecrawl/apps/api
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
|
||||||
|
x-firecrawl-env: &firecrawl-env
|
||||||
|
USE_DB_AUTHENTICATION: "false"
|
||||||
|
REDIS_URL: redis://firecrawl-redis:6379
|
||||||
|
REDIS_RATE_LIMIT_URL: redis://firecrawl-redis:6379
|
||||||
|
PLAYWRIGHT_MICROSERVICE_URL: http://firecrawl-playwright:3000/scrape
|
||||||
|
NUQ_DATABASE_URL: postgres://postgres:postgres@firecrawl-postgres:5432/postgres
|
||||||
|
BULL_AUTH_KEY: "aitools-bull-changeme"
|
||||||
|
HOST: "0.0.0.0"
|
||||||
|
PORT: "3002"
|
||||||
|
NODE_ENV: production
|
||||||
|
# Wire SearXNG so Firecrawl's /search uses it instead of direct Google
|
||||||
|
SEARXNG_ENDPOINT: http://searxng:8080
|
||||||
|
|
||||||
|
networks:
|
||||||
|
ai-tools:
|
||||||
|
name: ai-tools
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
searxng-cache:
|
||||||
|
firecrawl-redis-data:
|
||||||
|
firecrawl-pg-data:
|
||||||
|
hhem-model-cache:
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# 1. SearXNG — meta-search engine with JSON API
|
||||||
|
# =========================================================================
|
||||||
|
searxng:
|
||||||
|
image: docker.io/searxng/searxng:latest
|
||||||
|
container_name: searxng
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "8888:8080"
|
||||||
|
volumes:
|
||||||
|
- ./searxng:/etc/searxng:rw
|
||||||
|
- searxng-cache:/var/cache/searxng:rw
|
||||||
|
environment:
|
||||||
|
- SEARXNG_BASE_URL=http://localhost:8888/
|
||||||
|
cap_drop:
|
||||||
|
- ALL
|
||||||
|
cap_add:
|
||||||
|
- CHOWN
|
||||||
|
- SETGID
|
||||||
|
- SETUID
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "wget -qO- http://localhost:8080/healthz || exit 1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 20s
|
||||||
|
networks:
|
||||||
|
- ai-tools
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# 2. Firecrawl — web scraper (self-hosted, no external API key)
|
||||||
|
# Built from local source at /home/sparky/Docker/firecrawl/git/firecrawl/
|
||||||
|
# =========================================================================
|
||||||
|
|
||||||
|
firecrawl-redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
container_name: firecrawl-redis
|
||||||
|
restart: unless-stopped
|
||||||
|
command: redis-server --bind 0.0.0.0 --save 60 1 --loglevel warning
|
||||||
|
volumes:
|
||||||
|
- firecrawl-redis-data:/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "ping"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
networks:
|
||||||
|
- ai-tools
|
||||||
|
|
||||||
|
firecrawl-postgres:
|
||||||
|
build:
|
||||||
|
context: /home/sparky/Docker/firecrawl/git/firecrawl/apps/nuq-postgres
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: firecrawl-postgres
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
POSTGRES_DB: postgres
|
||||||
|
volumes:
|
||||||
|
- firecrawl-pg-data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "pg_isready", "-U", "postgres"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
networks:
|
||||||
|
- ai-tools
|
||||||
|
|
||||||
|
firecrawl-playwright:
|
||||||
|
build:
|
||||||
|
context: /home/sparky/Docker/firecrawl/git/firecrawl/apps/playwright-service-ts
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: firecrawl-playwright
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
PORT: 3000
|
||||||
|
shm_size: "2gb"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "wget -qO- http://localhost:3000/health || exit 1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 30s
|
||||||
|
networks:
|
||||||
|
- ai-tools
|
||||||
|
|
||||||
|
firecrawl-api:
|
||||||
|
<<: *firecrawl-build
|
||||||
|
container_name: firecrawl-api
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "3002:3002"
|
||||||
|
environment:
|
||||||
|
<<: *firecrawl-env
|
||||||
|
command: node dist/src/harness.js --start-docker
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
|
ulimits:
|
||||||
|
nofile:
|
||||||
|
soft: 65535
|
||||||
|
hard: 65535
|
||||||
|
depends_on:
|
||||||
|
firecrawl-redis:
|
||||||
|
condition: service_healthy
|
||||||
|
firecrawl-postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
firecrawl-playwright:
|
||||||
|
condition: service_started
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "wget -qO- http://localhost:3002/health || exit 1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 5
|
||||||
|
start_period: 60s
|
||||||
|
networks:
|
||||||
|
- ai-tools
|
||||||
|
|
||||||
|
# =========================================================================
|
||||||
|
# 3. HHEM API — hallucination evaluator (Vectara, CPU-only)
|
||||||
|
# =========================================================================
|
||||||
|
hhem-api:
|
||||||
|
build:
|
||||||
|
context: ./hhem-api
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: hhem-api
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "8881:8881"
|
||||||
|
volumes:
|
||||||
|
# Cache downloaded model weights across restarts
|
||||||
|
- hhem-model-cache:/root/.cache/huggingface
|
||||||
|
environment:
|
||||||
|
- HF_HOME=/root/.cache/huggingface
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "wget -qO- http://localhost:8881/health || exit 1"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 120s # model download on first start
|
||||||
|
networks:
|
||||||
|
- ai-tools
|
||||||
28
hhem-api/Dockerfile
Normal file
28
hhem-api/Dockerfile
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
wget \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install PyTorch CPU-only.
|
||||||
|
# The PyTorch whl/cpu index only has x86 wheels; on aarch64 we fall back to
|
||||||
|
# regular PyPI which publishes native aarch64 wheels for torch 2.x.
|
||||||
|
RUN pip install --no-cache-dir \
|
||||||
|
--extra-index-url https://download.pytorch.org/whl/cpu \
|
||||||
|
torch
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir \
|
||||||
|
transformers==4.44.2 \
|
||||||
|
fastapi==0.115.0 \
|
||||||
|
"uvicorn[standard]==0.30.6" \
|
||||||
|
pydantic==2.9.2 \
|
||||||
|
sentencepiece \
|
||||||
|
accelerate
|
||||||
|
|
||||||
|
COPY app.py .
|
||||||
|
|
||||||
|
EXPOSE 8881
|
||||||
|
|
||||||
|
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8881", "--workers", "1"]
|
||||||
76
hhem-api/app.py
Normal file
76
hhem-api/app.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
from contextlib import asynccontextmanager
|
||||||
|
from fastapi import FastAPI, HTTPException
|
||||||
|
from pydantic import BaseModel
|
||||||
|
import torch
|
||||||
|
from transformers import AutoTokenizer, AutoModelForSequenceClassification
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
MODEL_ID = "vectara/hallucination_evaluation_model"
|
||||||
|
|
||||||
|
tokenizer = None
|
||||||
|
model = None
|
||||||
|
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def lifespan(app: FastAPI):
|
||||||
|
global tokenizer, model
|
||||||
|
logger.info(f"Loading model {MODEL_ID} ...")
|
||||||
|
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
|
||||||
|
model = AutoModelForSequenceClassification.from_pretrained(MODEL_ID)
|
||||||
|
model.eval()
|
||||||
|
logger.info("Model ready.")
|
||||||
|
yield
|
||||||
|
del model
|
||||||
|
del tokenizer
|
||||||
|
|
||||||
|
|
||||||
|
app = FastAPI(title="HHEM API", version="1.0.0", lifespan=lifespan)
|
||||||
|
|
||||||
|
|
||||||
|
class ScoreRequest(BaseModel):
|
||||||
|
source: str
|
||||||
|
generated: str
|
||||||
|
|
||||||
|
|
||||||
|
class ScoreResponse(BaseModel):
|
||||||
|
score: float
|
||||||
|
label: str
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
def health():
|
||||||
|
return {"status": "ok", "model": MODEL_ID, "loaded": model is not None}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/score", response_model=ScoreResponse)
|
||||||
|
def score(req: ScoreRequest):
|
||||||
|
if model is None or tokenizer is None:
|
||||||
|
raise HTTPException(status_code=503, detail="Model not loaded yet")
|
||||||
|
|
||||||
|
inputs = tokenizer(
|
||||||
|
req.source,
|
||||||
|
req.generated,
|
||||||
|
return_tensors="pt",
|
||||||
|
truncation=True,
|
||||||
|
max_length=512,
|
||||||
|
padding=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
with torch.no_grad():
|
||||||
|
logits = model(**inputs).logits
|
||||||
|
probs = torch.softmax(logits, dim=-1)
|
||||||
|
|
||||||
|
# Resolve which class index maps to "Factually Consistent"
|
||||||
|
id2label = model.config.id2label # e.g. {0: "Hallucinated", 1: "Factually Consistent"}
|
||||||
|
factual_idx = next(
|
||||||
|
(idx for idx, name in id2label.items() if "consistent" in name.lower() or "factual" in name.lower()),
|
||||||
|
1, # fallback: assume class 1 is the positive (consistent) class
|
||||||
|
)
|
||||||
|
|
||||||
|
factual_score = probs[0][factual_idx].item()
|
||||||
|
label = "grounded" if factual_score > 0.5 else "hallucinated"
|
||||||
|
|
||||||
|
return ScoreResponse(score=round(factual_score, 4), label=label)
|
||||||
93
searxng/settings.yml
Normal file
93
searxng/settings.yml
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
general:
|
||||||
|
debug: false
|
||||||
|
instance_name: "SearXNG (local)"
|
||||||
|
enable_metrics: false
|
||||||
|
|
||||||
|
search:
|
||||||
|
safe_search: 0
|
||||||
|
autocomplete: ""
|
||||||
|
default_lang: "auto"
|
||||||
|
ban_time_on_fail: 5
|
||||||
|
max_ban_time_on_fail: 120
|
||||||
|
|
||||||
|
# JSON format is required for agent/API access
|
||||||
|
formats:
|
||||||
|
- html
|
||||||
|
- json
|
||||||
|
|
||||||
|
server:
|
||||||
|
port: 8080
|
||||||
|
bind_address: "0.0.0.0"
|
||||||
|
base_url: false
|
||||||
|
# Disable rate limiter so local agents (n8n, Firecrawl) can query freely
|
||||||
|
limiter: false
|
||||||
|
public_instance: false
|
||||||
|
# Change this to any random 32+ character string
|
||||||
|
secret_key: "n8n-ai-tools-searxng-secret-key-dgx-spark-local-2024"
|
||||||
|
image_proxy: false
|
||||||
|
|
||||||
|
ui:
|
||||||
|
static_use_hash: true
|
||||||
|
default_locale: ""
|
||||||
|
query_in_title: false
|
||||||
|
infinite_scroll: false
|
||||||
|
center_alignment: false
|
||||||
|
default_theme: simple
|
||||||
|
theme_args:
|
||||||
|
simple_style: auto
|
||||||
|
|
||||||
|
# Outgoing HTTP settings — no proxy needed on local network
|
||||||
|
outgoing:
|
||||||
|
request_timeout: 15.0
|
||||||
|
max_request_timeout: 30.0
|
||||||
|
useragent_suffix: ""
|
||||||
|
pool_connections: 100
|
||||||
|
pool_maxsize: 20
|
||||||
|
enable_http2: true
|
||||||
|
|
||||||
|
# Enabled engines — good balance of quality and reliability
|
||||||
|
engines:
|
||||||
|
- name: bing
|
||||||
|
engine: bing
|
||||||
|
shortcut: b
|
||||||
|
disabled: false
|
||||||
|
|
||||||
|
- name: duckduckgo
|
||||||
|
engine: duckduckgo
|
||||||
|
shortcut: d
|
||||||
|
disabled: false
|
||||||
|
|
||||||
|
- name: google
|
||||||
|
engine: google
|
||||||
|
shortcut: g
|
||||||
|
disabled: false
|
||||||
|
|
||||||
|
- name: brave
|
||||||
|
engine: brave
|
||||||
|
shortcut: br
|
||||||
|
disabled: false
|
||||||
|
|
||||||
|
- name: wikipedia
|
||||||
|
engine: wikipedia
|
||||||
|
shortcut: wp
|
||||||
|
disabled: false
|
||||||
|
|
||||||
|
- name: github
|
||||||
|
engine: github
|
||||||
|
shortcut: gh
|
||||||
|
disabled: true
|
||||||
|
|
||||||
|
- name: arxiv
|
||||||
|
engine: arxiv
|
||||||
|
shortcut: ax
|
||||||
|
disabled: true
|
||||||
|
|
||||||
|
categories_as_tabs:
|
||||||
|
general:
|
||||||
|
engines:
|
||||||
|
- bing
|
||||||
|
- duckduckgo
|
||||||
|
- google
|
||||||
|
- brave
|
||||||
|
language: all
|
||||||
|
safe_search: 0
|
||||||
Loading…
Reference in New Issue
Block a user