Aller au contenu

Observation

piighost émet une trace OpenTelemetry à chaque dé-identification. Chaque appel ouvre un span racine et un span enfant par étape du pipeline. On voit ainsi où une valeur a été détectée, comment elle a été liée, quel jeton l'a remplacée et si le garde-fou a laissé passer. Le traçage est optionnel et n'est jamais requis pour dé-identifier.

La couture du tracer

Le pipeline ne parle jamais directement à un backend de traçage. Il appelle get_tracer() une fois à la construction, puis enregistre via le tracer retourné.

from piighost.observation import get_tracer

tracer = get_tracer()
with tracer.span("piighost.detect") as span:
    span.set_input(text)
    span.set_output(detections)
    span.set_attribute("count", len(detections))

get_tracer() retourne un tracer basé sur OpenTelemetry quand l'extra observation est installé, et un tracer no-op sinon. Le tracer no-op n'enregistre rien et ne coûte rien, donc le pipeline émet ses spans sans condition, sans garde autour de chaque appel. Contrairement aux autres dépendances optionnelles, l'absence de l'extra observation ne lève pas d'exception. get_tracer() se rabat alors sur le tracer no-op, parce que le traçage ne doit jamais bloquer la dé-identification.

Un span est un gestionnaire de contexte qui porte un payload d'entrée, un payload de sortie et des attributs scalaires. L'imbrication est implicite. Un span ouvert à l'intérieur d'un autre devient son enfant via le contexte ambiant d'OpenTelemetry. Le pipeline n'a donc pas à passer le span parent d'une étape à l'autre.

Un span par étape

AnonymizationPipeline.anonymize ouvre un span racine piighost.anonymize, puis un span enfant par étape exécutée. Une étape désactivée n'émet aucun span. L'arbre d'un run complet est le suivant.

SchémaSchéma

L'arbre des spans d'une dé-identification. Les étapes optionnelles n'apparaissent que si elles sont configurées.

Le span racine enregistre le texte d'entrée et le texte dé-identifié final. detect enregistre les détections et leur nombre. link enregistre les entités. render enregistre le texte dé-identifié et le nombre de jetons. guard enregistre s'il a levé un drapeau et les labels vus. Le pipeline de conversation diffère. Il exécute la résolution de chevauchement et l'expansion dans _detect, et la résolution d'entités dans _thread_tokens. Aucune de ces étapes n'obtient donc de span propre, et seuls detect, link et render restent sous la racine. Le span racine et le span detect portent aussi un attribut cache_hit et un langfuse.session.id. Le pipeline de conversation émet aussi un span piighost.deanonymize quand il restaure un texte.

Les spans s'imbriquent sous le span courant au moment de l'appel anonymize. Si vous ouvrez un span applicatif autour d'une conversation, chaque appel du pipeline s'affiche en dessous, et l'ensemble forme une seule trace.

Caviarder les payloads des traces

Par défaut un payload de span contient les données confidentielles (données personnelles, secrets) en clair. Le span detect enregistre Patrick. Le span racine enregistre le texte d'entrée, où Patrick figure en clair. C'est délibéré. Une trace avec les valeurs en clair est un jeu de données prêt à l'emploi pour évaluer la qualité de détection.

C'est aussi une fuite si le backend n'a pas à connaître les données confidentielles. Passez un observation_redactor au constructeur du pipeline. C'est une placeholder factory, qui caviarde chaque payload avant qu'il sorte du processus.

from piighost.components.placeholder import LabelPlaceholderFactory
from piighost.pipeline import AnonymizationPipeline

redactor = LabelPlaceholderFactory()
pipeline = AnonymizationPipeline(detector, observation_redactor=redactor)

Avec le masqueur défini, le span detect enregistre <<PERSON>> au lieu de Patrick, et le payload d'entrée montre le texte caviardé. Le compromis est direct. Une trace caviardée est sûre à envoyer vers n'importe quel backend mais ne peut plus servir de jeu de données d'annotation, puisque les valeurs en clair ont disparu.

observation_redactorPayloads des tracesSûr pour un backend non fiableUtilisable comme jeu de données
None (défaut)valeurs en clairnonoui
une placeholder factoryjetons caviardésouinon

Le traçage en clair reste le défaut, pour que les traces gardent leur valeur d'annotation. Il doit cependant rester un choix explicite. Sans masqueur et avec un tracer provider réellement configuré, le pipeline avertit une fois à la construction que ses traces portent des données confidentielles en clair. Passez trace_clear_text=True pour assumer le traçage en clair et taire l'avertissement, ou un observation_redactor pour caviarder les payloads.

pipeline = AnonymizationPipeline(
    detector=detector,
    trace_clear_text=True,  # I know traces carry clear PII, ship them to a trusted backend only
)

La corrélation avec un backend est de la configuration de déploiement, pas du code de la lib

piighost émet des spans OpenTelemetry standard et s'arrête là. Il ne fournit aucun adapter par backend. Le backend qui reçoit les spans relève de la configuration du SDK OpenTelemetry de l'application, posée une fois au déploiement, en dehors de piighost.

N'importe quel exporteur OTLP fonctionne tel quel. Les spans atteignent le TracerProvider que l'application a enregistré. Sans provider configuré, l'API OpenTelemetry est un no-op et les spans ne vont nulle part.

Langfuse est une cible courante parce que son SDK v3 est bâti sur OpenTelemetry. Pointez-le vers le processus et il capture les spans piighost à côté des siens. Son filtre d'export par défaut ne laisse passer que ses propres spans et ceux des instrumenteurs LLM connus. Admettez donc le scope d'instrumentation piighost via le prédicat should_export_span du SDK.

from langfuse import Langfuse


def export_piighost_spans(span) -> bool:
    scope = span.instrumentation_scope
    if scope is None:
        return False
    return (
        scope.name == "langfuse-sdk"
        or scope.name == "piighost"
        or scope.name.startswith("piighost.")
    )


client = Langfuse(should_export_span=export_piighost_spans)

Les payloads sont sérialisés sous les clés d'attributs que Langfuse mappe vers l'entrée et la sortie d'une observation. Langfuse les affiche donc dans ces deux champs. N'importe quel autre backend OTLP les montre comme de simples attributs de span. Ce câblage ne vit pas dans piighost. C'est le câblage SDK que vous faites déjà pour le reste de votre stack.

La version complète et exécutable, avec repli console quand aucun credential Langfuse n'est présent, est dans examples/observation/langfuse_tracing.py.

Voir aussi