mirror of
https://github.com/Alkatrazz24/Funk-lab.git
synced 2026-07-08 13:44:42 +02:00
- CLAUDE.md : /srv/data NVMe (plus RAID5), structure k8s/ expliquée, namespace ai ajouté, Hermes data code/données séparés - README.md : roadmap complète (n8n/Open WebUI/monitoring opérationnels), Talos v1.13, vault vars complètes, rôles Ansible manquants, RAID5→NVMe - admin/ia/rag.md : 284→339 chunks, RAID5→NVMe - admin/ia/hermes.md : RAID5→NVMe (incidents 2026-05-13 et 2026-05-29) - admin/ia/litellm.md : qwen2.5→qwen3-8b, gap IaC CPU models documenté - admin/ia/llama_server.md : gap IaC instances CPU documenté - admin/index_knowledge.py : 5 chunks RAG mis à jour (Qwen2.5→Qwen3, RAID5→NVMe, vault vars + rôles complets) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
168 lines
9.6 KiB
Python
168 lines
9.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Index des connaissances Funk dans Qdrant (hermes_memory).
|
|
Exécuter avec : /srv/data/hermes/hermes-agent/venv/bin/python index_knowledge.py
|
|
"""
|
|
|
|
import requests
|
|
import uuid
|
|
from qdrant_client import QdrantClient
|
|
from qdrant_client.models import PointStruct, VectorParams, Distance
|
|
|
|
EMBEDDINGS_URL = "http://192.168.10.20:1234/v1/embeddings"
|
|
EMBEDDINGS_MODEL = "qwen3-8b"
|
|
QDRANT_HOST = "localhost"
|
|
QDRANT_PORT = 6333
|
|
COLLECTION = "hermes_memory"
|
|
VECTOR_DIM = 4096
|
|
|
|
# --- Connaissances à indexer ---
|
|
|
|
KNOWLEDGE = [
|
|
{
|
|
"category": "architecture",
|
|
"tags": ["homelab", "funk", "réseau", "machines"],
|
|
"text": "Funk homelab: storage-01 (192.168.10.1 LAN cluster, 192.168.1.200 LAN domestique, AlmaLinux 9.7) est passerelle + bastion + données. gpu-01 (192.168.10.20, AlmaLinux 9.7) héberge llama-server avec RX 6700XT. Cluster Kubernetes Talos sur compute-01 (192.168.10.11), compute-02 (192.168.10.12), compute-03 (192.168.10.13). storage-01 et gpu-01 sont hors du cluster k8s.",
|
|
},
|
|
{
|
|
"category": "architecture",
|
|
"tags": ["réseau", "kubernetes", "metallb", "dns"],
|
|
"text": "Réseau Kubernetes: CNI Flannel pods 10.42.0.0/16, services 10.43.0.0/16, MetalLB pool 192.168.10.200-230. Domaine lab.local avec wildcard DNS via dnsmasq sur storage-01. Ingress: <service>.lab.local vers IP MetalLB de Traefik.",
|
|
},
|
|
{
|
|
"category": "litellm",
|
|
"tags": ["litellm", "proxy", "modèle", "switch"],
|
|
"text": "LiteLLM tourne sur storage-01 port 4000. master_key doit valoir exactement lm-studio. Hermes utilise l'alias hermes-default. Pour switcher: sudo hermes-switch qwen (Qwen3-8B local gratuit) ou sudo hermes-switch claude (Claude Sonnet 4.6 ~1-2$ par session). Le script modifie /etc/litellm/config.yaml et redémarre le service.",
|
|
},
|
|
{
|
|
"category": "litellm",
|
|
"tags": ["litellm", "config", "modèles", "anthropic"],
|
|
"text": "LiteLLM config /etc/litellm/config.yaml expose: hermes-default (alias switchable), qwen3-8b (gpu-01:1234 GPU), qwen3-1.7b-system (gpu-01:1236 CPU), qwen3-1.7b-monitor (gpu-01:1237 CPU), claude-sonnet-4-6 (API Anthropic), claude-opus-4-7 (API Anthropic). ANTHROPIC_API_KEY dans l'environnement systemd via vault Ansible.",
|
|
},
|
|
{
|
|
"category": "hermes",
|
|
"tags": ["hermes", "config", "LM_API_KEY", "provider"],
|
|
"text": "LM_API_KEY doit valoir exactement lm-studio pour activer le provider LM Studio dans Hermes. Toute autre valeur échoue silencieusement. Le fichier /srv/data/hermes/.env peut écraser les variables systemd — Ansible gère les deux via lineinfile.",
|
|
},
|
|
{
|
|
"category": "hermes",
|
|
"tags": ["hermes", "config", "config.yaml", "provider"],
|
|
"text": "config.yaml Hermes doit contenir model.provider: lmstudio ET model.base_url: http://127.0.0.1:4000/v1. Sans provider, l'inférence échoue avec AuthError même si base_url est correct. model.default: hermes-default pointe sur l'alias LiteLLM.",
|
|
},
|
|
{
|
|
"category": "hermes",
|
|
"tags": ["hermes", "cli", "path", "git"],
|
|
"text": "Toujours lancer hermes -z depuis /srv/data/hermes pour éviter l'erreur PermissionError sur .git. Hermes remonte l'arborescence à la recherche d'un .git et échoue s'il tombe sur /home/ansible/.git sans permission. HERMES_HOME=/srv/data/hermes, données sur NVMe (bind-mount /home/data depuis 2026-05-29).",
|
|
},
|
|
{
|
|
"category": "hermes",
|
|
"tags": ["hermes", "mémoire", "sessions", "qdrant"],
|
|
"text": "Hermes a deux systèmes de mémoire: mémoire native dans /srv/data/hermes/memories/ (texte, automatique, recherche par mots-clés) et Qdrant (vectoriel, sémantique, skill mlops/qdrant installé). Le skill qdrant-vector-search est actif. qdrant-client v1.18 dans le venv /srv/data/hermes/hermes-agent/venv/.",
|
|
},
|
|
{
|
|
"category": "hermes",
|
|
"tags": ["hermes", "coût", "tokens", "claude"],
|
|
"text": "Coût Hermes avec Claude Sonnet 4.6: system prompt ~15000 tokens par échange, contexte s'accumule. Une session de debug ~375k tokens input = ~1.20$. Utiliser Qwen local (gratuit) pour les tâches courantes, Claude uniquement pour le debug complexe ponctuel.",
|
|
},
|
|
{
|
|
"category": "gpu",
|
|
"tags": ["rocm", "amd", "gfx1031", "HSA", "llama-server"],
|
|
"text": "gpu-01 RX 6700XT (gfx1031) non officiellement supportée par ROCm. Toujours définir HSA_OVERRIDE_GFX_VERSION=10.3.0 dans l'environnement llama-server et tout service ROCm. Sans cette variable ROCm échoue. Défini dans le service systemd llama-server via Ansible.",
|
|
},
|
|
{
|
|
"category": "gpu",
|
|
"tags": ["llama-server", "embeddings", "pooling", "vecteurs"],
|
|
"text": "llama-server doit être démarré avec --embeddings --pooling mean pour exposer /v1/embeddings compatible OpenAI. Sans --pooling mean: erreur 'Pooling type none is not OAI compatible'. Qwen3-8B produit des vecteurs de dimension 3584. Endpoint: http://192.168.10.20:1234/v1/embeddings.",
|
|
},
|
|
{
|
|
"category": "gpu",
|
|
"tags": ["modèles", "qwen", "gemma", "performance"],
|
|
"text": "Modèle actif gpu-01: Qwen3-8B Q4_K_M (~60 tok/s génération, ~300 tok/s prefill, ROCm 7.x natif). Contexte 32768 tokens (VRAM: ~5GB modèle + ~4GB KV cache = ~9GB sur 12GB). Alias API: qwen3-8b. Instances CPU: qwen3-1.7b-system sur :1236 et qwen3-1.7b-monitor sur :1237 (~21 tok/s CPU). Ne pas utiliser Gemma 4: crash ROCm >800 tokens. Dimension embeddings: 3584.",
|
|
},
|
|
{
|
|
"category": "postgresql",
|
|
"tags": ["postgresql", "pgdg", "PGDATA", "RAID5"],
|
|
"text": "PostgreSQL 16 installé depuis repo officiel pgdg sur storage-01. PGDATA sur NVMe: /srv/data/postgres (bind-mount /home/data depuis 2026-05-29). Override systemd dans /etc/systemd/system/postgresql-16.service.d/pgdata.conf. Auth: peer en local, scram-sha-256 pour connexions réseau. Bases: hermes, litellm, n8n, openwebui avec utilisateurs dédiés.",
|
|
},
|
|
{
|
|
"category": "qdrant",
|
|
"tags": ["qdrant", "api", "client", "python"],
|
|
"text": "Qdrant v1.13.4 sur storage-01 port 6333 (HTTP) et 6334 (gRPC). Données: /srv/data/qdrant. qdrant-client v1.18 installé dans /srv/data/hermes/hermes-agent/venv/. Utiliser client.query_points() et non client.search() (supprimé en v1.14+). Collection hermes_memory: dim=3584, distance=COSINE.",
|
|
},
|
|
{
|
|
"category": "firewall",
|
|
"tags": ["nftables", "firewall", "ports", "ansible"],
|
|
"text": "storage-01 utilise nftables géré centralement par le rôle gateway Ansible (nftables.conf.j2). Ne jamais ajouter de tâches firewalld dans les autres rôles. Ports ouverts cluster (192.168.10.0/24): 5432 PostgreSQL, 6333-6334 Qdrant, 8080 Hermes gateway, 9000-9001 MinIO. SSH ouvert depuis LAN domestique et cluster.",
|
|
},
|
|
{
|
|
"category": "ansible",
|
|
"tags": ["ansible", "vault", "secrets", "variables"],
|
|
"text": "Secrets Ansible dans group_vars/all/vault.yml: vault_anthropic_api_key, vault_pg_hermes_password, vault_pg_litellm_password, vault_pg_n8n_password, vault_pg_openwebui_password, vault_postfix_relay_password. Rôles idempotents: common, gateway, dnsmasq, nfs_server, postgresql, qdrant, minio, litellm, hermes_agent, rag, alertmanager_webhook, postfix_relay, node_exporter, llama_server, rocm. Playbooks: storage-01.yml et gpu-01.yml.",
|
|
},
|
|
{
|
|
"category": "ansible",
|
|
"tags": ["ansible", "tags", "déploiement", "commandes"],
|
|
"text": "Commandes Ansible: cd ansible/ && ansible-playbook -i inventory.yml playbooks/storage-01.yml --tags postgresql. Pour gpu-01: ansible-playbook -i inventory.yml playbooks/gpu-01.yml --tags llama_server. Dry-run: ajouter --check. Collections requises: ansible.posix, community.general, community.postgresql.",
|
|
},
|
|
]
|
|
|
|
|
|
def get_embedding(text: str) -> list[float]:
|
|
response = requests.post(
|
|
EMBEDDINGS_URL,
|
|
json={"model": EMBEDDINGS_MODEL, "input": [text]},
|
|
timeout=30,
|
|
)
|
|
response.raise_for_status()
|
|
return response.json()["data"][0]["embedding"]
|
|
|
|
|
|
def main():
|
|
client = QdrantClient(QDRANT_HOST, port=QDRANT_PORT)
|
|
|
|
# Recréer la collection proprement
|
|
existing = [c.name for c in client.get_collections().collections]
|
|
if COLLECTION in existing:
|
|
client.delete_collection(COLLECTION)
|
|
print(f"Collection '{COLLECTION}' supprimée (recréation propre)")
|
|
|
|
client.create_collection(
|
|
collection_name=COLLECTION,
|
|
vectors_config=VectorParams(size=VECTOR_DIM, distance=Distance.COSINE),
|
|
)
|
|
print(f"Collection '{COLLECTION}' créée (dim={VECTOR_DIM}, cosine)")
|
|
|
|
points = []
|
|
for i, item in enumerate(KNOWLEDGE):
|
|
print(f"[{i+1}/{len(KNOWLEDGE)}] Embedding: {item['text'][:60]}...")
|
|
embedding = get_embedding(item["text"])
|
|
points.append(
|
|
PointStruct(
|
|
id=i + 1,
|
|
vector=embedding,
|
|
payload={
|
|
"text": item["text"],
|
|
"category": item["category"],
|
|
"tags": item["tags"],
|
|
"source": "funk-sessions-2026-05",
|
|
},
|
|
)
|
|
)
|
|
|
|
client.upsert(collection_name=COLLECTION, points=points)
|
|
print(f"\nOK — {len(points)} chunks indexés dans '{COLLECTION}'")
|
|
|
|
# Test rapide
|
|
print("\n--- Test recherche: 'erreur hermes modele provider' ---")
|
|
query_vec = get_embedding("erreur hermes modele provider")
|
|
results = client.query_points(
|
|
collection_name=COLLECTION,
|
|
query=query_vec,
|
|
limit=3,
|
|
)
|
|
for r in results.points:
|
|
print(f" Score={r.score:.4f} [{r.payload['category']}] {r.payload['text'][:80]}...")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|