Skip to content

feat(llm): llama.cpp come motore predefinito per estrazione ed embedding - #5

Merged
gzileni merged 1 commit into
mainfrom
feat/llamacpp-inference
Aug 4, 2026
Merged

feat(llm): llama.cpp come motore predefinito per estrazione ed embedding#5
gzileni merged 1 commit into
mainfrom
feat/llamacpp-inference

Conversation

@gzileni

@gzileni gzileni commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Punta knowledge-graph al server di inferenza self-hosted llama.cpp, per l'estrazione e per gli embedding. Claude resta disponibile con KG_LLM_PROVIDER=anthropic, Ollama con =ollama.

Per l'estrazione, LlamaCppProvider usa /v1/chat/completions con response_format: json_object. Non è una richiesta cortese al modello: llama.cpp lo implementa vincolando il sampling con una grammatica JSON, quindi l'output è ben formato per costruzione. Conta, visto che il chiamante fa un json.loads secco.

⚠️ Richiede reindicizzazione

Qwen3-Embedding-0.6B produce 1024 dimensioni contro le 768 di nomic-embed-text. Vettori di modelli diversi non sono confrontabili e l'indice Redis è dimensionato sull'ampiezza del modello, quindi un'installazione con dati esistenti va ricostruita:

redis-cli FT.DROPINDEX kg_vectors DD   # DD cancella anche i documenti
# poi rieseguire l'ingestione

Per restare su Ollama senza reindicizzare: EMBEDDING_PROVIDER=ollama + REDIS_VECTOR_DIM=768.

Difetto preesistente corretto qui

create_index inghiottiva ogni eccezione. Con l'indice già esistente a 768 la creazione era quindi un no-op silenzioso, e il disallineamento non emergeva lì ma molto più tardi, come query che non restituivano nulla. Ora la larghezza dell'indice vivo viene confrontata con REDIS_VECTOR_DIM e l'avvio si interrompe stampando il comando da eseguire.

Altre due cose

Embedding separati dall'estrazione (EMBEDDING_PROVIDER distinto da KG_LLM_PROVIDER): l'estrazione si cambia liberamente, gli embedding no, perché dimensionano l'indice. Tenerli sulla stessa manopola invitava a cambiarli per sbaglio.

Il batch parte in una sola richiesta invece di un round-trip per testo (misurato: 64 testi / 1142 token in 0,58 s), e i vettori vengono riordinati per il campo index invece che per posizione — un batch permutato in silenzio attaccherebbe ogni vettore al chunk sbagliato, corruzione che nulla a valle intercetterebbe. C'è un test che lo verifica con un mock che restituisce le righe deliberatamente invertite.

La validazione della forma della risposta sta fuori dal retry: una risposta troncata è una violazione di contratto, non un guasto transitorio, e ritentarla ritardava solo l'errore di ~15 secondi.

Verifica

  • 32 test verdi, di cui 11 nuovi su embedder e provider
  • integrazione provata contro il server vivo: POST :8081/v1/embeddings → dim 1024
  • entrambi i docker-compose validati con docker compose config

🤖 Generated with Claude Code

Il server di inferenza self-hosted espone la forma OpenAI, quindi
LlamaCppProvider usa /v1/chat/completions con response_format
json_object: llama.cpp lo implementa vincolando il sampling con una
grammatica JSON, quindi l'output e' ben formato per costruzione e non
per buona volonta' del modello. Conta, visto che il chiamante fa un
json.loads secco.

Gli embedding sono selezionati separatamente (EMBEDDING_PROVIDER) da
KG_LLM_PROVIDER: l'estrazione si cambia liberamente, gli embedding no,
perche' dimensionano l'indice vettoriale. Il batch parte ora in
un'unica richiesta invece di un round-trip per testo, e i vettori sono
riordinati per "index" invece che per posizione: un batch permutato in
silenzio attaccherebbe ogni vettore al chunk sbagliato.

CAMBIO CHE RICHIEDE REINDICIZZAZIONE: Qwen3-Embedding-0.6B produce 1024
dimensioni contro le 768 di nomic-embed-text, quindi REDIS_VECTOR_DIM
passa a 1024 e un'installazione con dati esistenti va ricostruita.
create_index inghiottiva ogni eccezione: con l'indice gia' esistente a
768 la creazione era un no-op silenzioso e il disallineamento emergeva
come query che non restituivano nulla. Ora la larghezza viene
confrontata e l'avvio si interrompe stampando il FT.DROPINDEX da
eseguire. Chi vuole restare su Ollama dichiara EMBEDDING_PROVIDER=ollama
e REDIS_VECTOR_DIM=768.

La validazione della forma della risposta sta fuori dal retry: una
risposta troncata e' una violazione di contratto, non un guasto
transitorio, e ritentarla ritardava soltanto l'errore di ~15 secondi.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 998f4130c8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docker-compose.yml
Comment on lines +122 to +124
# Default engine: the self-hosted llama.cpp server on the host, reached via
# host.docker.internal — chat on :8080 (llama-swap), embeddings on :8081.
# Both are read from .env (LLAMACPP_BASE_URL / LLAMACPP_EMBEDDING_BASE_URL).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Update the root env file for the new inference defaults

When users follow the documented quick start (cp .env.example .env) and launch docker-compose.yml, these URLs are not actually provided because the root .env.example was left unchanged: it contains no LLAMACPP_* variables and still sets REDIS_VECTOR_DIM=768. The API therefore uses localhost:8080/8081 from inside its container while selecting llama.cpp, and creates a 768-dimensional index for the new 1024-dimensional embeddings; the GHCR compose also inherits the stale 768 value through variable substitution. Update the root template alongside the API-specific template so the default deployment can reach inference and build the correct index.

Useful? React with 👍 / 👎.

Comment thread docker-compose.ghcr.yml
KG_LLM_PROVIDER: "${KG_LLM_PROVIDER:-llamacpp}"
EMBEDDING_PROVIDER: "${EMBEDDING_PROVIDER:-llamacpp}"
REDIS_VECTOR_DIM: "${REDIS_VECTOR_DIM:-1024}"
LLAMACPP_BASE_URL: "${LLAMACPP_BASE_URL:-http://host.docker.internal:8080}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Move llama.cpp off the MCP host port

When the full production stack is run with the documented host llama.cpp server, this default collides with the MCP service: both compose files publish MCP as host 8080:8080, while llama.cpp is also expected to listen on host port 8080. Consequently either llama.cpp prevents the MCP container from binding or MCP occupies the address intended for chat completions. Assign one service a distinct host port and update LLAMACPP_BASE_URL consistently.

Useful? React with 👍 / 👎.

# "llamacpp" (default) → the self-hosted llama.cpp inference server.
# "ollama" → the legacy local-inference behaviour.
# "anthropic" → Claude (Haiku by default), opt-in.
KG_LLM_PROVIDER: str = "llamacpp"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Probe the selected inference providers in health checks

With this new default, GET /health still unconditionally requests OLLAMA_BASE_URL/api/tags in api/main.py and never checks either llama.cpp endpoint. A healthy default llama.cpp-only deployment is therefore reported as degraded, while an unrelated live Ollama instance can make health appear healthy even when extraction and embedding inference are unavailable. Select the health probes from KG_LLM_PROVIDER and EMBEDDING_PROVIDER.

Useful? React with 👍 / 👎.

@gzileni
gzileni merged commit a1b03eb into main Aug 4, 2026
4 of 6 checks passed
@gzileni gzileni self-assigned this Aug 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant