Aller au contenu

Fonctions IA dans Trino — AKKO vs upstream

Deux familles indépendantes de fonctions IA sont utilisables dans Trino. Elles cohabitent sans collision et ciblent des besoins différents :

Famille Préfixe Invocation Fournie par
Plugin AKKO (cette plateforme) akko_ai_* globale, sans préfixe de catalogue plugin AKKO trino-ai-functions
Trino upstream ai_* llm.ai.<fonction>(...) via le catalogue llm connecteur de catalogue llm Trino 466+

TL;DR

Utilisez akko_ai_* pour toute charge de travail en production AKKO — elles passent par la stack gouvernée AKKO (cache Caffeine, OPA par rôle, spans d'audit Tempo, multimodal, embeddings). Utilisez la famille upstream llm.ai.* quand vous voulez un comportement Trino standard ou pour des démos comparatives. Les deux peuvent être activées en même temps.

Vue d'ensemble — 23 fonctions AKKO vs 7 upstream

Aspect AKKO akko_ai_* (plugin global) Trino upstream ai_* (llm.ai)
Invocation SELECT akko_ai_translate(...) SELECT llm.ai.ai_translate(...) (ou ai_translate(...) si llm.ai est dans le search_path)
Fonctions disponibles 23 — texte, multimodal, embeddings, admin 7 — texte uniquement
Fournisseur akko-ai-service → LiteLLM → Ollama/OpenAI/Anthropic/Mistral HTTP direct vers un seul fournisseur (OpenAI, Anthropic ou Ollama)
Cache par texte de requête Caffeine LRU, par worker, 10k entrées, TTL 1 h aucun
Circuit breaker oui — fail-closed après N erreurs, half-open automatique aucun
RBAC OPA par fonction oui — allow-list par rôle appliquée côté service pas intégrée
Trace d'audit spans Tempo + en-tête X-Akko-Ai-Function + Loki audit_type=AI_RBAC journalisé seulement côté fournisseur
Multimodal texte + image + audio + PDF texte uniquement
Embeddings + recherche sémantique akko_ai_embed, akko_ai_similarity, akko_ai_search (embed une fois par texte de requête distinct par worker — l'appel HTTP a lieu une fois quelle que soit la cardinalité) indisponible
Gestion d'erreur @SqlNullable — toute erreur renvoie SQL NULL, Trino jamais déstabilisé comportement par défaut du plugin (peut remonter en erreur de requête)
Modèle de coût le cache écrase N lignes avec le même texte en 1 appel HTTP 1 appel HTTP par ligne
Licence Apache 2.0, livrée avec AKKO Apache 2.0, livrée par Trino upstream

La famille AKKO akko_ai_* — 23 fonctions

Les 23 fonctions sont globales, enregistrées via Plugin.getFunctions(). On les appelle directement :

SELECT akko_ai_sentiment('I love this product');

Ne jamais préfixer avec ai.default. — ce chemin passe par le catalogue PostgreSQL ai (fallback PL/Python), pas par le plugin JVM natif. Si SELECT akko_ai_sentiment(...) renvoie « Function not registered », l'image custom akko-trino n'est pas déployée — vérifier kubectl describe pod -l app=trino.

Catégories

Catégorie Fonctions Coût
Scalaires texte (HTTP par entrée distincte) akko_ai_ask, akko_ai_sentiment, akko_ai_classify, akko_ai_summarize, akko_ai_translate, akko_ai_entities, akko_ai_anomaly, akko_ai_sql, akko_ai_risk, akko_ai_pii, akko_ai_sensitivity, akko_ai_language, akko_ai_keywords 1 appel LLM par valeur distincte (réduite par le cache)
Multimodal akko_ai_ocr, akko_ai_describe_image, akko_ai_parse_document, akko_ai_transcribe 1 appel multimodal par entrée
Embeddings akko_ai_embed 1 appel embed par texte distinct
Calcul vectoriel (local pur) akko_ai_similarity, akko_ai_search 0 appel HTTP par ligne (après le premier akko_ai_search)
Helpers admin akko_ai_stats, akko_ai_health, akko_ai_version, akko_ai_cache_clear, akko_ai_cb_reset aucun

Scalaires texte

Fonction Signature Exemple Renvoie
akko_ai_ask (question VARCHAR[, contexte VARCHAR]) akko_ai_ask('Qu''est-ce qu''un lakehouse?') réponse libre
akko_ai_sentiment (texte VARCHAR) akko_ai_sentiment('J''adore') POSITIVE / NEUTRAL / NEGATIVE
akko_ai_classify (texte VARCHAR, catégories VARCHAR) akko_ai_classify('serveur planté', 'bug,feature') une catégorie
akko_ai_summarize (texte VARCHAR[, n INT]) akko_ai_summarize(description, 2) résumé en N phrases
akko_ai_translate (texte VARCHAR[, langue_cible VARCHAR]) akko_ai_translate('Hello', 'French') texte traduit
akko_ai_entities (texte VARCHAR) akko_ai_entities('Tim Cook chez Apple') JSON array [{type, value}]
akko_ai_anomaly (valeur VARCHAR[, contexte VARCHAR]) akko_ai_anomaly('150000', 'moyenne 45000') JSON {is_anomaly, score, reason}
akko_ai_sql (question VARCHAR[, schéma VARCHAR]) akko_ai_sql('top clients', 'orders(id,total)') chaîne SQL
akko_ai_risk (profil VARCHAR) akko_ai_risk('solde=-5000,inactif') score 0–100
akko_ai_pii (texte VARCHAR) akko_ai_pii('Jean, jean@mail.com') texte masqué
akko_ai_sensitivity (texte VARCHAR) akko_ai_sensitivity('NIR 1 85 07...') classe RGPD (personal, sensitive, public)
akko_ai_language (texte VARCHAR) akko_ai_language('Bonjour') code ISO (fr, en…)
akko_ai_keywords (texte VARCHAR[, n INT]) akko_ai_keywords('détection fraude ML', 3) mots-clés séparés par virgules

Multimodal

Fonction Signature Rôle
akko_ai_ocr (image BYTES) image → texte extrait
akko_ai_describe_image (image BYTES[, prompt VARCHAR]) image → légende / description
akko_ai_parse_document (pdf BYTES) PDF → JSON structuré
akko_ai_transcribe (audio BYTES) audio → transcription

Embeddings et calcul vectoriel

Fonction Signature Rôle
akko_ai_embed (texte VARCHAR) → ARRAY<DOUBLE> embedding 768 dim via nomic-embed-text
akko_ai_similarity (a ARRAY<DOUBLE>, b ARRAY<DOUBLE>) → DOUBLE similarité cosinus, local pur — pas d'appel HTTP
akko_ai_search (embedding ARRAY<DOUBLE>, requête VARCHAR) → DOUBLE embed la requête une fois par worker (LRU 1024, TTL 1h), puis cosinus local

Admin et observabilité

Fonction Rôle
akko_ai_stats() JSON avec état du circuit breaker, latences p50/p95/p99, taux de hit cache, compteurs par fonction
akko_ai_health() UP / DOWN — joignabilité de l'AI Service
akko_ai_version() version du plugin (ex. 2026.04)
akko_ai_cache_clear() vide le cache de résultats (admin uniquement, vérifié via X-Trino-User)
akko_ai_cb_reset() force la fermeture du circuit breaker (admin uniquement)

Architecture

flowchart LR
    SQL[SQL Trino<br/>SELECT akko_ai_sentiment...] --> COORD[Coordinateur Trino<br/>JVM]
    COORD --> PLUGIN[Plugin<br/>trino-ai-functions]
    PLUGIN --> HC[Cache LRU Caffeine<br/>10 000 entrées - TTL 1h]
    HC -->|miss| HTTP[Client HTTP/2<br/>java.net.http]
    HTTP --> AIS[akko-ai-service<br/>FastAPI + check OPA]
    AIS --> LLM[Passerelle LiteLLM]
    LLM --> OLL[Ollama<br/>qwen2.5 - nomic-embed-text]
    LLM -.optionnel.-> CLOUD[OpenAI / Anthropic / Mistral]
    PLUGIN --> JMX[MBean JMX<br/>HdrHistogram]
    JMX --> PROM[Prometheus<br/>jmx_exporter]
    AIS --> TEMPO[Spans Tempo<br/>trace d'audit]

Chaque invocation akko_ai_*() :

  1. Cherche le résultat dans un cache LRU Caffeine scopé par utilisateur (user:function:sha256(args)).
  2. En cas de miss, ouvre une connexion HTTP/2 (pool de 16) vers l'AI Service avec X-Trino-User + X-Akko-Ai-Function: akko_ai_<nom>.
  3. S'authentifie avec un bearer token chargé depuis le Secret K8s akko-trino-ai.
  4. L'AI Service vérifie le rôle et l'allow-list OPA pour la fonction, enregistre un span Tempo, puis route l'appel via LiteLLM.
  5. Renvoie la chaîne parsée — ou SQL NULL (via @SqlNullable) en cas d'erreur. Trino n'est jamais déstabilisé.

Configuration

Tout se règle via les variables d'environnement du coordinateur Trino (dans helm/akko/charts/akko-trino/values.yaml) :

trino:
  env:
    AKKO_AI_SERVICE_URL: http://akko-akko-ai-service:8000
    AKKO_AI_SERVICE_TOKEN:
      valueFrom:
        secretKeyRef:
          name: akko-trino-ai
          key: token
    AKKO_AI_TIMEOUT_MS: "30000"
    AKKO_AI_CB_THRESHOLD: "5"
    AKKO_AI_CB_RECOVERY_MS: "60000"
    AKKO_AI_RETRY_MAX: "3"
    AKKO_AI_POOL_SIZE: "16"
    AKKO_AI_CACHE_SIZE: "10000"
    AKKO_AI_CACHE_TTL_S: "3600"
    AKKO_AI_VERIFY_TLS: "true"

Patterns d'usage

Enrichissement inline

SELECT id,
       akko_ai_sentiment(comment)                           AS sentiment,
       akko_ai_classify(comment, 'fraude,retention,support') AS sujet,
       akko_ai_pii(comment)                                 AS masque,
       akko_ai_embed(comment)                               AS vecteur
FROM   iceberg.banking.transactions
WHERE  ts > current_date - INTERVAL '7' DAY;

Matérialiser les embeddings une fois pour toutes

CREATE TABLE iceberg.kb.documents_vec AS
SELECT id, text, akko_ai_embed(text) AS embedding
FROM   iceberg.kb.documents;

Recherche sémantique (efficace)

akko_ai_search embed la requête une fois par worker (LRU Caffeine 1024) et calcule le cosinus localement :

SELECT id, text,
       akko_ai_search(embedding, 'comment rembourser une transaction ?') AS score
FROM   iceberg.kb.documents
ORDER  BY score DESC
LIMIT  10;

Équivalent mais 10 à 1000 × plus lent (ré-embed la requête ligne par ligne) — à éviter en production :

SELECT id, text,
       akko_ai_similarity(embedding, akko_ai_embed('comment rembourser une transaction ?')) AS score
FROM   iceberg.kb.documents
ORDER  BY score DESC
LIMIT  10;

Observabilité depuis SQL

SELECT akko_ai_stats();

-- JMX (nécessite le catalogue jmx de Trino)
SELECT *
FROM   jmx.current."dev.akko.trino.ai:type=aihttpclient";

RBAC

Les 23 UDF akko_ai_* court-circuitent le check OPA natif ExecuteFunction de Trino (Trino ne route pas les UDF de plugin à travers le SPI d'access-control). Le RBAC est donc appliqué au niveau de l'AI Service : le plugin transmet X-Trino-User + X-Akko-Ai-Function: akko_ai_<nom> à chaque appel HTTP, et l'AI Service résout le rôle AKKO de l'utilisateur via l'API admin Keycloak (cache 5 min) puis consulte la matrice par fonction.

Fail-closed : toute erreur de lookup, rôle manquant ou fonction hors de l'allow-list du rôle renvoie HTTP 403. Le plugin remonte 403 / 401 / 429 en PERMISSION_DENIED Trino (via io.trino.spi.security.AccessDeniedException), la requête échoue avec la même classe d'erreur qu'un SELECT refusé par OPA — pas de NULL silencieux. Les échecs d'infra (5xx, timeout) restent mappés en SQL NULL pour qu'une panne LLM transitoire ne casse pas les dashboards. Toute décision est tracée dans une ligne Loki structurée taguée audit_type=AI_RBAC et dans les compteurs Prometheus akko_ai_rbac_allowed_total / akko_ai_rbac_denied_total.

Source de vérité : helm/akko/charts/akko-ai-service/values.yaml — synchronisé avec helm/akko/charts/akko-opa/templates/configmap.yaml. Voir Admin / RBAC LLM pour la matrice complète.

akko_ai_similarity(a, b) est du calcul local pur (pas d'HTTP), donc tourne in-JVM pour tout rôle — filtrer la requête parente via RBAC de catalogue.

Quotas quotidiens par utilisateur : admin=∞, engineer=1000, analyst=500, steward=100, viewer=50. Au-delà : HTTP 429 → SQL NULL.

La famille upstream ai_* (catalogue Trino llm)

Depuis Trino 466, Trino embarque un catalogue de fonctions IA natives nommé llm. En Trino 480, le schéma ai expose sept fonctions :

Fonction Signature Renvoie
ai_gen (prompt VARCHAR) réponse libre
ai_translate (texte VARCHAR, cible VARCHAR) texte traduit
ai_classify (texte VARCHAR, catégories ARRAY<VARCHAR>) une catégorie
ai_analyze_sentiment (texte VARCHAR) label de sentiment
ai_extract (texte VARCHAR, schéma VARCHAR) extraction JSON
ai_fix_grammar (texte VARCHAR) texte corrigé
ai_mask (texte VARCHAR) texte avec PII masquées

Ces fonctions vivent dans un catalogue connecteur, donc la forme canonique est entièrement qualifiée :

SELECT llm.ai.ai_translate('Hello', 'French');

Chevauchement fonctionnel avec AKKO

Quatre noms se chevauchent : ai_translate / ai_classify / ai_analyze_sentiment / ai_extract upstream correspondent à peu près à akko_ai_translate / akko_ai_classify / akko_ai_sentiment / akko_ai_entities AKKO. AKKO utilise délibérément le préfixe akko_ai_* pour lever toute ambiguïté — voir ADR-026.

Activer le catalogue llm contre la passerelle LiteLLM souveraine AKKO

Le connecteur upstream laisse choisir parmi trois fournisseurs. AKKO fournit un modèle llm.properties qui le pointe vers la passerelle LiteLLM d'AKKO, pour que les fonctions upstream bénéficient du routage de modèle souverain (même si elles restent en dehors du cache, de l'OPA et de l'audit AKKO).

# etc/catalog/llm.properties — router l'IA upstream Trino via AKKO LiteLLM
connector.name=llm
llm.provider=openai
llm.openai.endpoint=http://akko-akko-litellm:4000
llm.openai.api-key=${ENV:AKKO_LITELLM_KEY}
llm.openai.model=qwen2.5:3b

À monter via Helm :

trino:
  catalogs:
    llm: |
      connector.name=llm
      llm.provider=openai
      llm.openai.endpoint=http://akko-akko-litellm:4000
      llm.openai.api-key=${ENV:AKKO_LITELLM_KEY}
      llm.openai.model=qwen2.5:3b
  env:
    AKKO_LITELLM_KEY:
      valueFrom:
        secretKeyRef:
          name: akko-litellm-api
          key: key

Après un rollout Trino, les deux familles sont accessibles :

-- Famille AKKO (cache + OPA + audit)
SELECT akko_ai_translate('Hello', 'French');

-- Famille upstream Trino (standard, sans cache, sans OPA)
SELECT llm.ai.ai_translate('Hello', 'French');

Quand utiliser laquelle

Situation Utiliser
Requête de production sur données gouvernées, dashboards, jobs planifiés akko_ai_*
Recherche sémantique, embeddings, multimodal akko_ai_* (upstream ne les propose pas)
Charge réglementée avec audit OPA par utilisateur akko_ai_*
Démo Trino standard ou comparaison upstream llm.ai.ai_*
Notebook ad hoc sans besoin de cache/RBAC les deux marchent ; akko_ai_* coûte moins sur requêtes multi-lignes grâce au cache

Dépannage

Symptôme Cause probable Résolution
akko_ai_sentiment(...) → « Function not registered » image custom akko-trino non déployée Vérifier que le pod Trino tourne akko-trino:2026.04, pas trinodb/trino. kubectl describe pod -l app=trino
Tous les akko_ai_* renvoient NULL circuit breaker OPEN après 5+ échecs SELECT akko_ai_stats() ; attendre 60 s ou appeler akko_ai_cb_reset()
401 / 403 dans les logs AI Service bearer token désynchronisé Resynchroniser le Secret akko-trino-ai ; helm upgrade pour redémarrer Trino
Première requête très lente Ollama en train de pull le modèle Attendre le job ollama-init ; kubectl logs -l app=akko-ollama
Latence élevée sur akko_ai_search texte de requête jamais caché Passer le texte en littéral constant (akko_ai_search(col, 'ma-requête'))
jmx.current vide catalogue jmx désactivé Ajouter jmx.properties au ConfigMap catalogue Trino
llm.ai.ai_translate absente catalogue llm non activé Ajouter llm.properties (voir modèle ci-dessus)

Voir aussi