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