Contesto
LlamaCppProvider è già un client OpenAI: POSTa a
{base_url}/v1/chat/completions, manda Authorization: Bearer quando la chiave
c'è, e usa response_format: {"type": "json_object"}
(knowledge-graph-api/pipeline/llm/llamacpp_provider.py). Lo stesso vale per
l'embedder, che chiama /v1/embeddings (pipeline/embedder.py).
Il problema non è il protocollo, è il nome. KG_LLM_PROVIDER=llamacpp e le
env LLAMACPP_* suggeriscono che serva un llama-server, mentre lo stesso
codice funziona contro qualsiasi endpoint OpenAI-compatibile: un gateway LiteLLM,
vLLM, TGI, o OpenAI stesso.
Il costo è concreto. In limen
il sidecar viene puntato al gateway LiteLLM su :8091, e la configurazione
risultante è questa:
KG_LLM_PROVIDER: llamacpp
LLAMACPP_BASE_URL: http://host.docker.internal:8091 # non è llama.cpp
LLAMACPP_EXTRACTION_MODEL: extract # è una chiave di routing
LLAMACPP_EMBEDDING_BASE_URL: http://host.docker.internal:8091
Chi legge quel compose fra sei mesi conclude che dietro c'è un llama-server, e
sbaglia. Ed è la stessa correzione già fatta in
mcp-geo-server,
dove però il caso era peggiore: là i provider ollama parlavano /api/chat
nativo e il gateway era irraggiungibile, quindi è servito un provider nuovo
(GEO_LLM_PROVIDER=openai, commit 25e478a). Qui il codice va già bene: manca
solo che si chiami come ciò che fa.
Design
pipeline/llm/openai_provider.py — KG_LLM_PROVIDER=openai, con
OPENAI_BASE_URL / OPENAI_LLM_MODEL / OPENAI_EXTRACTION_MODEL /
OPENAI_API_KEY. Può essere LlamaCppProvider rinominato: il corpo non
cambia.
llamacpp resta un alias retrocompatibile che risolve allo stesso
provider, con un log di deprecazione una volta all'avvio. Non è
future-proofing: i deploy esistenti (incluso limen) impostano già
LLAMACPP_*, e romperli senza preavviso trasforma un rename in un incidente.
- Stessa cosa per gli embedding:
EMBEDDING_PROVIDER=openai +
OPENAI_EMBEDDING_BASE_URL / OPENAI_EMBEDDING_MODEL, con llamacpp come
alias.
base_url senza /v1, come oggi: il provider accoda /v1/... da sé.
Se si decide il contrario va documentato, perché l'SDK OpenAI ufficiale fa
l'opposto (accoda solo il path) — in mcp-geo-server OPENAI_BASE_URL
include /v1 proprio per quel motivo, e la differenza fra i due repo va
scritta o diventa un'ora persa.
- Chiave assente: un gateway senza master key va servito senza header
Authorization (comportamento attuale, corretto). Nota che l'SDK OpenAI
ufficiale invece rifiuta di costruire il client senza credenziale — se in
futuro si passa all'SDK, serve un placeholder.
REDIS_VECTOR_DIM: nessun cambiamento. La guardia introdotta in
storage/redis_vector.py (fallisce se l'indice esistente ha una larghezza
diversa) resta il presidio giusto e non va indebolita cambiando provider.
Fuori scope
- Supporto a più modelli per ruolo (estrazione vs sintesi): oggi non serve.
- Migrazione all'SDK
openai: httpx diretto basta e tiene le dipendenze basse.
Acceptance criteria
⚠️ Prerequisito: la CI è rossa e blocca la pubblicazione delle immagini
Questa issue non è utilizzabile end-to-end finché la CI non torna verde, perché
docker-publish.yml è gated sulla CI:
on:
workflow_run:
workflows: ["CI"]
types: [completed]
if: ${{ github.event.workflow_run.conclusion == 'success' }}
Catena verificata il 2026-09-03:
| Passo |
Esito |
CI su main (a1b03eb) |
failure — ruff check ., 21 errori |
docker-publish conseguente |
skipped |
ghcr.io/.../kg-api:latest |
ancora costruita da eab6c0b (11 giugno) |
Quindi il codice llama.cpp/OpenAI è su main (PR #5) ma non è
nell'immagine pubblicata, e chi la usa ottiene il comportamento pre-#5.
La causa dei 21 errori non è codice nuovo: requirements.txt:38 dichiara
ruff>=0.4.0, la CI risolve alla 0.16.1 e le regole abilitate da allora
(BLE001 ×12, B008 ×2, I001 ×3, DTZ001 ×2, RUF022, ASYNC230)
illuminano codice preesistente. Misurato su un worktree pulito:
| Ref |
Errori (ruff 0.16.1) |
eab6c0b … a1b03eb (main) |
21, distribuzione identica |
Fix: pinnare ruff==0.16.1 (o un range chiuso) e sistemare i 21 rilievi,
oppure ignorarli esplicitamente in [tool.ruff] con motivazione. Un linter non
pinnato rende la CI una funzione della data, non del codice.
Vale una issue separata — questa dipende da quella per l'effetto pratico, non
per l'implementazione.
Contesto
LlamaCppProviderè già un client OpenAI: POSTa a{base_url}/v1/chat/completions, mandaAuthorization: Bearerquando la chiavec'è, e usa
response_format: {"type": "json_object"}(
knowledge-graph-api/pipeline/llm/llamacpp_provider.py). Lo stesso vale perl'embedder, che chiama
/v1/embeddings(pipeline/embedder.py).Il problema non è il protocollo, è il nome.
KG_LLM_PROVIDER=llamacppe leenv
LLAMACPP_*suggeriscono che serva unllama-server, mentre lo stessocodice funziona contro qualsiasi endpoint OpenAI-compatibile: un gateway LiteLLM,
vLLM, TGI, o OpenAI stesso.
Il costo è concreto. In
limenil sidecar viene puntato al gateway LiteLLM su
:8091, e la configurazionerisultante è questa:
Chi legge quel compose fra sei mesi conclude che dietro c'è un llama-server, e
sbaglia. Ed è la stessa correzione già fatta in
mcp-geo-server,dove però il caso era peggiore: là i provider
ollamaparlavano/api/chatnativo e il gateway era irraggiungibile, quindi è servito un provider nuovo
(
GEO_LLM_PROVIDER=openai, commit25e478a). Qui il codice va già bene: mancasolo che si chiami come ciò che fa.
Design
pipeline/llm/openai_provider.py—KG_LLM_PROVIDER=openai, conOPENAI_BASE_URL/OPENAI_LLM_MODEL/OPENAI_EXTRACTION_MODEL/OPENAI_API_KEY. Può essereLlamaCppProviderrinominato: il corpo noncambia.
llamacppresta un alias retrocompatibile che risolve allo stessoprovider, con un log di deprecazione una volta all'avvio. Non è
future-proofing: i deploy esistenti (incluso
limen) impostano giàLLAMACPP_*, e romperli senza preavviso trasforma un rename in un incidente.EMBEDDING_PROVIDER=openai+OPENAI_EMBEDDING_BASE_URL/OPENAI_EMBEDDING_MODEL, conllamacppcomealias.
base_urlsenza/v1, come oggi: il provider accoda/v1/...da sé.Se si decide il contrario va documentato, perché l'SDK OpenAI ufficiale fa
l'opposto (accoda solo il path) — in
mcp-geo-serverOPENAI_BASE_URLinclude
/v1proprio per quel motivo, e la differenza fra i due repo vascritta o diventa un'ora persa.
Authorization(comportamento attuale, corretto). Nota che l'SDK OpenAIufficiale invece rifiuta di costruire il client senza credenziale — se in
futuro si passa all'SDK, serve un placeholder.
REDIS_VECTOR_DIM: nessun cambiamento. La guardia introdotta instorage/redis_vector.py(fallisce se l'indice esistente ha una larghezzadiversa) resta il presidio giusto e non va indebolita cambiando provider.
Fuori scope
openai:httpxdiretto basta e tiene le dipendenze basse.Acceptance criteria
KG_LLM_PROVIDER=openai+OPENAI_BASE_URLfunziona contro un gatewayOpenAI-compatibile (estrazione JSON valida).
EMBEDDING_PROVIDER=openaifunziona su/v1/embeddings.KG_LLM_PROVIDER=llamacppcontinua a funzionare identico, con log dideprecazione.
tests/test_llm_providers.py)..env.exampleeREADME.mdaggiornati; documentata la convenzione sulsuffisso
/v1.ruff check .+pytest tests/verdi.Questa issue non è utilizzabile end-to-end finché la CI non torna verde, perché
docker-publish.ymlè gated sulla CI:Catena verificata il 2026-09-03:
main(a1b03eb)ruff check ., 21 erroridocker-publishconseguenteghcr.io/.../kg-api:latesteab6c0b(11 giugno)Quindi il codice llama.cpp/OpenAI è su
main(PR #5) ma non ènell'immagine pubblicata, e chi la usa ottiene il comportamento pre-#5.
La causa dei 21 errori non è codice nuovo:
requirements.txt:38dichiararuff>=0.4.0, la CI risolve alla0.16.1e le regole abilitate da allora(
BLE001×12,B008×2,I001×3,DTZ001×2,RUF022,ASYNC230)illuminano codice preesistente. Misurato su un worktree pulito:
eab6c0b…a1b03eb(main)Fix: pinnare
ruff==0.16.1(o un range chiuso) e sistemare i 21 rilievi,oppure ignorarli esplicitamente in
[tool.ruff]con motivazione. Un linter nonpinnato rende la CI una funzione della data, non del codice.
Vale una issue separata — questa dipende da quella per l'effetto pratico, non
per l'implementazione.