Skip to content

feat(llm): provider OpenAI-compatibile di prima classe (llamacpp → openai, con alias) #6

Description

@gzileni

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

  1. pipeline/llm/openai_provider.pyKG_LLM_PROVIDER=openai, con
    OPENAI_BASE_URL / OPENAI_LLM_MODEL / OPENAI_EXTRACTION_MODEL /
    OPENAI_API_KEY. Può essere LlamaCppProvider rinominato: il corpo non
    cambia.
  2. 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.
  3. Stessa cosa per gli embedding: EMBEDDING_PROVIDER=openai +
    OPENAI_EMBEDDING_BASE_URL / OPENAI_EMBEDDING_MODEL, con llamacpp come
    alias.
  4. 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.
  5. 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.
  6. 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

  • KG_LLM_PROVIDER=openai + OPENAI_BASE_URL funziona contro un gateway
    OpenAI-compatibile (estrazione JSON valida).
  • EMBEDDING_PROVIDER=openai funziona su /v1/embeddings.
  • KG_LLM_PROVIDER=llamacpp continua a funzionare identico, con log di
    deprecazione.
  • Test per entrambi i nomi (il factory è già coperto da
    tests/test_llm_providers.py).
  • .env.example e README.md aggiornati; documentata la convenzione sul
    suffisso /v1.
  • ruff check . + pytest tests/ verdi.

⚠️ 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) failureruff 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)
eab6c0ba1b03eb (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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions