Partager via


Collecter les commentaires des utilisateurs

La collecte et la journalisation des commentaires des utilisateurs sont essentielles pour comprendre la qualité réelle de votre application GenAI. MLflow fournit un moyen structuré de capturer des commentaires sous forme d’évaluations sur les traces, ce qui vous permet de suivre la qualité au fil du temps, d’identifier les domaines d’amélioration et de créer des jeux de données d’évaluation à partir de données de production.

Conditions préalables

Choisissez la méthode d’installation appropriée en fonction de votre environnement :

Production

Pour les déploiements de production, installez le mlflow-tracing package :

pip install --upgrade mlflow-tracing

Le mlflow-tracing package est optimisé pour une utilisation en production avec des dépendances minimales et de meilleures caractéristiques de performances.

Développement

Pour les environnements de développement, installez le paquet MLflow complet avec les composants supplémentaires de Databricks.

pip install --upgrade "mlflow[databricks]>=3.1"

Le package complet mlflow[databricks] inclut toutes les fonctionnalités nécessaires au développement et à l’expérimentation locaux sur Databricks.

L’API log_feedback est disponible dans les deux packages. Vous pouvez donc collecter les commentaires des utilisateurs, quelle que soit la méthode d’installation choisie.

Remarque

MLflow 3 est nécessaire pour collecter les commentaires des utilisateurs. MLflow 2.x n’est pas pris en charge en raison des limitations de performances et des fonctionnalités manquantes essentielles pour l’utilisation de production.

Pourquoi collecter les commentaires des utilisateurs ?

Les commentaires des utilisateurs fournissent une vérité sur les performances de votre application :

  1. Signaux de qualité réels - Comprendre comment les utilisateurs réels perçoivent les sorties de votre application
  2. Amélioration continue - Identifier les modèles dans les commentaires négatifs pour guider le développement
  3. Création de données d’apprentissage - Utiliser les commentaires pour créer des jeux de données d’évaluation de haute qualité
  4. Surveillance de la qualité - Suivre les métriques de satisfaction au fil du temps et sur différents segments d’utilisateurs
  5. Réglage précis des modèles - Tirer parti des données de commentaires pour améliorer vos modèles sous-jacents

Types de commentaire

MLflow prend en charge différents types de commentaires via son système d’évaluation :

Type de commentaires Descriptif Cas d’usage courants
Commentaires binaires Pouces simples vers le haut/vers le bas ou correct/incorrect Signaux rapides de satisfaction des utilisateurs
Scores numériques Évaluations à l’échelle (par exemple, 1 à 5 étoiles) Évaluation détaillée de la qualité
Commentaires catégoriels Options à choix multiples Classification des problèmes ou des types de réponse
Commentaires texte Commentaires de formulaire libre Explications détaillées pour les utilisateurs

Comprendre le modèle de données de commentaires

Dans MLflow, les commentaires utilisateur sont capturés à l’aide de l’entité Feedback , qui est un type d’évaluation pouvant être attaché à des traces ou à des étendues spécifiques. L’entité Feedback fournit un moyen structuré de stocker :

  • Valeur : retour réel (booléen, numérique, texte ou données structurées)
  • Source : Informations sur qui ou ce qui a fourni les commentaires (utilisateur humain, juge LLM ou code)
  • Justification : Explication facultative des commentaires
  • Métadonnées : contexte supplémentaire comme les horodatages ou les attributs personnalisés

La compréhension de ce modèle de données vous permet de concevoir des systèmes de collecte de commentaires efficaces qui s’intègrent en toute transparence aux fonctionnalités d’évaluation et de surveillance de MLflow. Pour plus d’informations sur le schéma d’entité Feedback et tous les champs disponibles, consultez la section Commentaires dans le modèle de données de suivi.

Collecte des commentaires des utilisateurs finaux

Lors de l’implémentation de la collecte de commentaires en production, vous devez lier les commentaires des utilisateurs à des traces spécifiques. Il existe deux approches que vous pouvez utiliser :

  1. Utilisation des ID de demande client - Générer vos propres ID uniques lors du traitement des demandes et les référencer ultérieurement pour obtenir des commentaires
  2. Utilisation des ID de trace MLflow : utilisez l’ID de trace généré automatiquement par MLflow

Présentation du flux de collecte de commentaires

Les deux approches suivent un modèle similaire :

  1. Pendant la demande initiale : votre application génère un ID de demande client unique ou récupère l’ID de trace généré par MLflow

  2. Après avoir reçu la réponse : l’utilisateur peut fournir des commentaires en référençant l’un ou l’autre des ID. Les deux approches suivent un modèle similaire.

  3. Pendant la demande initiale : votre application génère un ID de demande client unique ou récupère l’ID de trace généré par MLflow

  4. Après avoir reçu la réponse : l’utilisateur peut fournir des commentaires en référençant l’un ou l’autre ID

  5. Les commentaires sont consignés : l’API de log_feedback MLflow crée une évaluation attachée à la trace d’origine

  6. Analyse et surveillance : vous pouvez interroger et analyser les commentaires sur toutes les traces

Implémentation d’une collecte de commentaires

Approche 1 : Utilisation des ID de trace MLflow

L’approche la plus simple consiste à utiliser l’ID de trace que MLflow génère automatiquement pour chaque trace. Vous pouvez récupérer cet ID pendant le traitement des demandes et le renvoyer au client :

Implémentation du back-end

import mlflow
from fastapi import FastAPI, Query
from mlflow.client import MlflowClient
from mlflow.entities import AssessmentSource
from pydantic import BaseModel
from typing import Optional

app = FastAPI()

class ChatRequest(BaseModel):
    message: str

class ChatResponse(BaseModel):
    response: str
    trace_id: str  # Include the trace ID in the response

@app.post("/chat", response_model=ChatResponse)
def chat(request: ChatRequest):
    """
    Process a chat request and return the trace ID for feedback collection.
    """
    # Your GenAI application logic here
    response = process_message(request.message)  # Replace with your actual processing logic

    # Get the current trace ID
    trace_id = mlflow.get_current_active_span().trace_id

    return ChatResponse(
        response=response,
        trace_id=trace_id
    )

class FeedbackRequest(BaseModel):
    is_correct: bool  # True for thumbs up, False for thumbs down
    comment: Optional[str] = None

@app.post("/feedback")
def submit_feedback(
    trace_id: str = Query(..., description="The trace ID from the chat response"),
    feedback: FeedbackRequest = ...,
    user_id: Optional[str] = Query(None, description="User identifier")
):
    """
    Collect user feedback using the MLflow trace ID.
    """
    # Log the feedback directly using the trace ID
    mlflow.log_feedback(
        trace_id=trace_id,
        name="user_feedback",
        value=feedback.is_correct,
        source=AssessmentSource(
            source_type="HUMAN",
            source_id=user_id
        ),
        rationale=feedback.comment
    )

    return {
        "status": "success",
        "trace_id": trace_id,
    }

Exemple d’implémentation de l'interface utilisateur

Voici un exemple d’implémentation frontale pour une application Basée sur React :

// React example for chat with feedback
import React, { useState } from 'react';

function ChatWithFeedback() {
  const [message, setMessage] = useState('');
  const [response, setResponse] = useState('');
  const [traceId, setTraceId] = useState(null);
  const [feedbackSubmitted, setFeedbackSubmitted] = useState(false);

  const sendMessage = async () => {
    try {
      const res = await fetch('/chat', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ message }),
      });

      const data = await res.json();
      setResponse(data.response);
      setTraceId(data.trace_id);
      setFeedbackSubmitted(false);
    } catch (error) {
      console.error('Chat error:', error);
    }
  };

  const submitFeedback = async (isCorrect, comment = null) => {
    if (!traceId || feedbackSubmitted) return;

    try {
      const params = new URLSearchParams({
        trace_id: traceId,
        ...(userId && { user_id: userId }),
      });

      const res = await fetch(`/feedback?${params}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          is_correct: isCorrect,
          comment: comment,
        }),
      });

      if (res.ok) {
        setFeedbackSubmitted(true);
        // Optionally show success message
      }
    } catch (error) {
      console.error('Feedback submission error:', error);
    }
  };

  return (
    <div>
      <input value={message} onChange={(e) => setMessage(e.target.value)} placeholder="Ask a question..." />
      <button onClick={sendMessage}>Send</button>

      {response && (
        <div>
          <p>{response}</p>
          <div className="feedback-buttons">
            <button onClick={() => submitFeedback(true)} disabled={feedbackSubmitted}>
              👍
            </button>
            <button onClick={() => submitFeedback(false)} disabled={feedbackSubmitted}>
              👎
            </button>
          </div>
          {feedbackSubmitted && <span>Thanks for your feedback!</span>}
        </div>
      )}
    </div>
  );
}

Approche 2 : Utilisation des ID de demande client

Pour plus de contrôle sur le suivi des demandes, vous pouvez utiliser vos propres ID de demande client uniques. Cette approche est utile lorsque vous devez gérer votre propre système de suivi des demandes ou l’intégrer à l’infrastructure existante :

Cette approche vous oblige à implémenter le suivi des demandes où chaque trace a un client_request_id attribut. Consultez le guide d’observabilité de production avec traçage pour plus d’informations sur la façon d’attacher des identifiants de requêtes client à vos traces au cours de la requête initiale.

Implémentation du back-end

import mlflow
from fastapi import FastAPI, Query, Request
from mlflow.client import MlflowClient
from mlflow.entities import AssessmentSource
from pydantic import BaseModel
from typing import Optional
import uuid

app = FastAPI()

class ChatRequest(BaseModel):
    message: str

class ChatResponse(BaseModel):
    response: str
    client_request_id: str  # Include the client request ID in the response

@app.post("/chat", response_model=ChatResponse)
def chat(request: ChatRequest):
    """
    Process a chat request and set a client request ID for later feedback collection.
    """
    # Sample: Generate a unique client request ID
    # Normally, this ID would be your app's backend existing ID for this interaction
    client_request_id = f"req-{uuid.uuid4().hex[:8]}"

    # Attach the client request ID to the current trace
    mlflow.update_current_trace(client_request_id=client_request_id)

    # Your GenAI application logic here
    response = process_message(request.message)  # Replace with your actual processing logic

    return ChatResponse(
        response=response,
        client_request_id=client_request_id
    )

class FeedbackRequest(BaseModel):
    is_correct: bool  # True for thumbs up, False for thumbs down
    comment: Optional[str] = None

@app.post("/feedback")
def submit_feedback(
    request: Request,
    client_request_id: str = Query(..., description="The request ID from the original interaction"),
    feedback: FeedbackRequest = ...
):
    """
    Collect user feedback for a specific interaction.

    This endpoint:
    1. Finds the trace using the client request ID
    2. Logs the feedback as an MLflow assessment
    3. Adds tags for easier querying and filtering
    """
    client = MlflowClient()

    # Find the trace using the client request ID
    experiment = client.get_experiment_by_name("/Shared/production-app")
    traces = client.search_traces(
        experiment_ids=[experiment.experiment_id],
        filter_string=f"attributes.client_request_id = '{client_request_id}'",
        max_results=1
    )

    if not traces:
        return {"status": "error", "message": "Unexpected error: request not found"}, 500

    # Log the feedback as an assessment
    # Assessments are the structured way to attach feedback to traces
    mlflow.log_feedback(
        trace_id=traces[0].info.trace_id,
        name="user_feedback",
        value=feedback.is_correct,
        source=AssessmentSource(
            source_type="HUMAN",  # Indicates this is human feedback
            source_id=request.headers.get("X-User-ID")  # Link feedback to the user who provided it
        ),
        rationale=feedback.comment  # Optional explanation from the user
    )

    return {
        "status": "success",
        "trace_id": traces[0].info.trace_id,
    }

Exemple d’implémentation de l'interface utilisateur

Vous trouverez ci-dessous un exemple d’implémentation frontale pour une application React. Lorsque vous utilisez des ID de demande client, votre serveur frontal doit stocker et gérer ces ID :

// React example with session-based request tracking
import React, { useState, useEffect } from 'react';

function ChatWithRequestTracking() {
  const [message, setMessage] = useState('');
  const [conversations, setConversations] = useState([]);
  const [sessionId] = useState(() => `session-${Date.now()}`);

  const sendMessage = async () => {
    try {
      const res = await fetch('/chat', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-Session-ID': sessionId,
        },
        body: JSON.stringify({ message }),
      });

      const data = await res.json();

      // Store conversation with request ID
      setConversations((prev) => [
        ...prev,
        {
          id: data.client_request_id,
          message: message,
          response: data.response,
          timestamp: new Date(),
          feedbackSubmitted: false,
        },
      ]);

      setMessage('');
    } catch (error) {
      console.error('Chat error:', error);
    }
  };

  const submitFeedback = async (requestId, isCorrect, comment = null) => {
    try {
      const params = new URLSearchParams({
        client_request_id: requestId,
      });

      const res = await fetch(`/feedback?${params}`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-User-ID': getUserId(), // Your user identification method
        },
        body: JSON.stringify({
          is_correct: isCorrect,
          comment: comment,
        }),
      });

      if (res.ok) {
        // Mark feedback as submitted
        setConversations((prev) =>
          prev.map((conv) => (conv.id === requestId ? { ...conv, feedbackSubmitted: true } : conv)),
        );
      }
    } catch (error) {
      console.error('Feedback submission error:', error);
    }
  };

  return (
    <div>
      <div className="chat-history">
        {conversations.map((conv) => (
          <div key={conv.id} className="conversation">
            <div className="user-message">{conv.message}</div>
            <div className="bot-response">{conv.response}</div>
            <div className="feedback-section">
              <button onClick={() => submitFeedback(conv.id, true)} disabled={conv.feedbackSubmitted}>
                👍
              </button>
              <button onClick={() => submitFeedback(conv.id, false)} disabled={conv.feedbackSubmitted}>
                👎
              </button>
              {conv.feedbackSubmitted && <span>✓ Feedback received</span>}
            </div>
          </div>
        ))}
      </div>

      <div className="chat-input">
        <input
          value={message}
          onChange={(e) => setMessage(e.target.value)}
          onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
          placeholder="Type your message..."
        />
        <button onClick={sendMessage}>Send</button>
      </div>
    </div>
  );
}

Détails de l’implémentation clé

AssessmentSource : L’objet AssessmentSource identifie qui ou ce qui a fourni les commentaires :

  • source_type: peut être « HUMAIN » pour les commentaires des utilisateurs ou « LLM_JUDGE » pour l’évaluation automatisée
  • source_id: identifie l’utilisateur ou le système spécifique fournissant des commentaires

Stockage des commentaires : les commentaires sont stockés en tant qu’évaluations sur la trace, ce qui signifie :

  • Il est associé définitivement à l’interaction spécifique
  • Il peut être interrogé en même temps que les données de traçage
  • Il est visible dans l’interface utilisateur MLflow lors de l’affichage de la trace

Gestion de différents types de commentaires

Vous pouvez étendre l’une ou l’autre approche pour prendre en charge des commentaires plus complexes. Voici un exemple utilisant des ID de trace :

from mlflow.entities import AssessmentSource

@app.post("/detailed-feedback")
def submit_detailed_feedback(
    trace_id: str,
    accuracy: int = Query(..., ge=1, le=5, description="Accuracy rating from 1-5"),
    helpfulness: int = Query(..., ge=1, le=5, description="Helpfulness rating from 1-5"),
    relevance: int = Query(..., ge=1, le=5, description="Relevance rating from 1-5"),
    user_id: str = Query(..., description="User identifier"),
    comment: Optional[str] = None
):
    """
    Collect multi-dimensional feedback with separate ratings for different aspects.
    Each aspect is logged as a separate assessment for granular analysis.
    """
    # Log each dimension as a separate assessment
    dimensions = {
        "accuracy": accuracy,
        "helpfulness": helpfulness,
        "relevance": relevance
    }

    for dimension, score in dimensions.items():
        mlflow.log_feedback(
            trace_id=trace_id,
            name=f"user_{dimension}",
            value=score / 5.0,  # Normalize to 0-1 scale
            source=AssessmentSource(
                source_type="HUMAN",
                source_id=user_id
            ),
            rationale=comment if dimension == "accuracy" else None
        )

    return {
        "status": "success",
        "trace_id": trace_id,
        "feedback_recorded": dimensions
    }

Gestion des retours avec des réponses en flux continu

Lorsque vous utilisez des flux de données en continu (Server-Sent Événements ou WebSockets), l'identifiant de trace n’est pas disponible tant que le flux n’est pas terminé. Cela présente un défi unique pour la collecte de commentaires qui nécessite une approche différente.

Pourquoi la diffusion en continu est différente

Dans les modèles de demande-réponse traditionnels, vous recevez ensemble la réponse complète et l’ID de trace. Avec la diffusion en continu :

  1. Les jetons arrivent de manière incrémentielle : la réponse est générée au fil du temps sous forme de flux de jetons par le LLM
  2. La finalisation de la trace est différée : l’ID de trace n’est généré qu’une fois l’ensemble du flux terminé
  3. L’interface utilisateur de commentaires doit attendre : les utilisateurs ne peuvent pas fournir de commentaires tant qu’ils n’ont pas la réponse complète et l’ID de trace

Implémentation du back-end avec SSE

Voici comment implémenter la diffusion en continu avec la remise d’ID de trace à la fin du flux :

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import mlflow
import json
import asyncio
from typing import AsyncGenerator

@app.post("/chat/stream")
async def chat_stream(request: ChatRequest):
    """
    Stream chat responses with trace ID sent at completion.
    """
    async def generate() -> AsyncGenerator[str, None]:
        try:
            # Start MLflow trace
            with mlflow.start_span(name="streaming_chat") as span:
                # Update trace with request metadata
                mlflow.update_current_trace(
                    request_message=request.message,
                    stream_start_time=datetime.now().isoformat()
                )

                # Stream tokens from your LLM
                full_response = ""
                async for token in your_llm_stream_function(request.message):
                    full_response += token
                    yield f"data: {json.dumps({'type': 'token', 'content': token})}\n\n"
                    await asyncio.sleep(0.01)  # Prevent overwhelming the client

                # Log the complete response to the trace
                span.set_attribute("response", full_response)
                span.set_attribute("token_count", len(full_response.split()))

                # Get trace ID after completion
                trace_id = span.trace_id

                # Send trace ID as final event
                yield f"data: {json.dumps({'type': 'done', 'trace_id': trace_id})}\n\n"

        except Exception as e:
            # Log error to trace if available
            if mlflow.get_current_active_span():
                mlflow.update_current_trace(error=str(e))

            yield f"data: {json.dumps({'type': 'error', 'error': str(e)})}\n\n"

    return StreamingResponse(
        generate(),
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "X-Accel-Buffering": "no",  # Disable proxy buffering
        }
    )

Implémentation frontale pour la diffusion en continu

Gérez les événements de streaming et activez les commentaires uniquement après avoir reçu l’ID de trace :

// React hook for streaming chat with feedback
import React, { useState, useCallback } from 'react';

function useStreamingChat() {
  const [isStreaming, setIsStreaming] = useState(false);
  const [streamingContent, setStreamingContent] = useState('');
  const [traceId, setTraceId] = useState(null);
  const [error, setError] = useState(null);

  const sendStreamingMessage = useCallback(async (message) => {
    // Reset state
    setIsStreaming(true);
    setStreamingContent('');
    setTraceId(null);
    setError(null);

    try {
      const response = await fetch('/chat/stream', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ message }),
      });

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      const reader = response.body.getReader();
      const decoder = new TextDecoder();
      let buffer = '';

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;

        buffer += decoder.decode(value, { stream: true });
        const lines = buffer.split('\n');

        // Keep the last incomplete line in the buffer
        buffer = lines.pop() || '';

        for (const line of lines) {
          if (line.startsWith('data: ')) {
            try {
              const data = JSON.parse(line.slice(6));

              switch (data.type) {
                case 'token':
                  setStreamingContent((prev) => prev + data.content);
                  break;
                case 'done':
                  setTraceId(data.trace_id);
                  setIsStreaming(false);
                  break;
                case 'error':
                  setError(data.error);
                  setIsStreaming(false);
                  break;
              }
            } catch (e) {
              console.error('Failed to parse SSE data:', e);
            }
          }
        }
      }
    } catch (error) {
      setError(error.message);
      setIsStreaming(false);
    }
  }, []);

  return {
    sendStreamingMessage,
    streamingContent,
    isStreaming,
    traceId,
    error,
  };
}

// Component using the streaming hook
function StreamingChatWithFeedback() {
  const [message, setMessage] = useState('');
  const [feedbackSubmitted, setFeedbackSubmitted] = useState(false);
  const { sendStreamingMessage, streamingContent, isStreaming, traceId, error } = useStreamingChat();

  const handleSend = () => {
    if (message.trim()) {
      setFeedbackSubmitted(false);
      sendStreamingMessage(message);
      setMessage('');
    }
  };

  const submitFeedback = async (isPositive) => {
    if (!traceId || feedbackSubmitted) return;

    try {
      const response = await fetch(`/feedback?trace_id=${traceId}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          is_correct: isPositive,
          comment: null,
        }),
      });

      if (response.ok) {
        setFeedbackSubmitted(true);
      }
    } catch (error) {
      console.error('Feedback submission failed:', error);
    }
  };

  return (
    <div className="streaming-chat">
      <div className="chat-messages">
        {streamingContent && (
          <div className="message assistant">
            {streamingContent}
            {isStreaming && <span className="typing-indicator">...</span>}
          </div>
        )}
        {error && <div className="error-message">Error: {error}</div>}
      </div>

      {/* Feedback buttons - only enabled when trace ID is available */}
      {streamingContent && !isStreaming && traceId && (
        <div className="feedback-section">
          <span>Was this response helpful?</span>
          <button onClick={() => submitFeedback(true)} disabled={feedbackSubmitted} className="feedback-btn positive">
            👍 Yes
          </button>
          <button onClick={() => submitFeedback(false)} disabled={feedbackSubmitted} className="feedback-btn negative">
            👎 No
          </button>
          {feedbackSubmitted && <span className="feedback-thanks">Thank you!</span>}
        </div>
      )}

      <div className="chat-input-section">
        <input
          type="text"
          value={message}
          onChange={(e) => setMessage(e.target.value)}
          onKeyPress={(e) => e.key === 'Enter' && !isStreaming && handleSend()}
          placeholder="Type your message..."
          disabled={isStreaming}
        />
        <button onClick={handleSend} disabled={isStreaming || !message.trim()}>
          {isStreaming ? 'Streaming...' : 'Send'}
        </button>
      </div>
    </div>
  );
}

Éléments clés à prendre en compte pour la diffusion en continu

Lors de l’implémentation d’une collecte de commentaires avec des réponses de diffusion en continu, gardez à l’esprit les points suivants :

  1. Minutage de l’ID de trace : l’ID de trace devient disponible uniquement après la fin de la diffusion en continu. Concevez votre interface utilisateur pour gérer cela correctement en désactivant les contrôles de commentaires jusqu’à ce que l’ID de trace soit reçu.

  2. Structure d’événements : utilisez un format d’événement cohérent avec un type champ pour faire la distinction entre les jetons de contenu, les événements d’achèvement et les erreurs. Cela rend l’analyse et la gestion des événements plus fiables.

  3. Gestion de l’état : effectuez le suivi du contenu de streaming et de l’ID de trace séparément. Réinitialisez tous les états au début de chaque nouvelle interaction pour empêcher les problèmes liés aux données obsolètes.

  4. Gestion des erreurs : incluez des événements d’erreur dans le flux pour gérer correctement les échecs. Vérifiez que les erreurs sont consignées dans la trace lorsque cela est possible pour le débogage.

  5. Gestion des mémoires tampons :

    • Utiliser l’en-tête X-Accel-Buffering: no pour désactiver la mise en mémoire tampon du proxy
    • Implémenter une mise en mémoire tampon de ligne appropriée dans le front-end pour gérer les messages SSE partiels
    • Envisagez d’implémenter une logique de reconnexion pour les interruptions réseau
  6. Optimisation des performances :

    • Ajouter de petits retards entre les jetons (asyncio.sleep(0.01)) pour éviter de submerger les clients
    • Regrouper plusieurs jetons s'ils arrivent trop vite
    • Envisagez d’implémenter des mécanismes de rétropression pour les clients lents

Analyse des données de commentaires

Une fois que vous avez collecté des commentaires, vous pouvez l’analyser pour obtenir des insights sur la qualité et la satisfaction des utilisateurs de votre application.

Affichage des commentaires dans l’interface utilisateur de trace

Obtention de traces avec des commentaires via le Kit de développement logiciel (SDK)

Affichage des commentaires dans l’interface utilisateur de trace

commentaires de suivi

Obtention de traces avec des commentaires via le Kit de développement logiciel (SDK)

Tout d’abord, récupérez les traces à partir d’une fenêtre de temps spécifique :

from mlflow.client import MlflowClient
from datetime import datetime, timedelta

def get_recent_traces(experiment_name: str, hours: int = 24):
    """Get traces from the last N hours."""
    client = MlflowClient()

    # Calculate cutoff time
    cutoff_time = datetime.now() - timedelta(hours=hours)
    cutoff_timestamp_ms = int(cutoff_time.timestamp() * 1000)

    # Query traces
    traces = client.search_traces(
        experiment_names=[experiment_name],
        filter_string=f"trace.timestamp_ms > {cutoff_timestamp_ms}"
    )

    return traces

Analyse des modèles de commentaires via le Kit de développement logiciel (SDK)

Extrayez et analysez les retours des traces

def analyze_user_feedback(traces):
    """Analyze feedback patterns from traces."""

    client = MlflowClient()

    # Initialize counters
    total_traces = len(traces)
    traces_with_feedback = 0
    positive_count = 0
    negative_count = 0

    # Process each trace
    for trace in traces:
        # Get full trace details including assessments
        trace_detail = client.get_trace(trace.info.trace_id)

        if trace_detail.data.assessments:
            traces_with_feedback += 1

            # Count positive/negative feedback
            for assessment in trace_detail.data.assessments:
                if assessment.name == "user_feedback":
                    if assessment.value:
                        positive_count += 1
                    else:
                        negative_count += 1

    # Calculate metrics
    if traces_with_feedback > 0:
        feedback_rate = (traces_with_feedback / total_traces) * 100
        positive_rate = (positive_count / traces_with_feedback) * 100
    else:
        feedback_rate = 0
        positive_rate = 0

    return {
        "total_traces": total_traces,
        "traces_with_feedback": traces_with_feedback,
        "feedback_rate": feedback_rate,
        "positive_rate": positive_rate,
        "positive_count": positive_count,
        "negative_count": negative_count
    }

# Example usage
traces = get_recent_traces("/Shared/production-genai-app", hours=24)
results = analyze_user_feedback(traces)

print(f"Feedback rate: {results['feedback_rate']:.1f}%")
print(f"Positive feedback: {results['positive_rate']:.1f}%")
print(f"Total feedback: {results['traces_with_feedback']} out of {results['total_traces']} traces")

Analyse des commentaires multidimensionnels

Pour obtenir des commentaires plus détaillés sur les évaluations :

def analyze_ratings(traces):
    """Analyze rating-based feedback."""

    client = MlflowClient()
    ratings_by_dimension = {}

    for trace in traces:
        trace_detail = client.get_trace(trace.info.trace_id)

        if trace_detail.data.assessments:
            for assessment in trace_detail.data.assessments:
                # Look for rating assessments
                if assessment.name.startswith("user_") and assessment.name != "user_feedback":
                    dimension = assessment.name.replace("user_", "")

                    if dimension not in ratings_by_dimension:
                        ratings_by_dimension[dimension] = []

                    ratings_by_dimension[dimension].append(assessment.value)

    # Calculate averages
    average_ratings = {}
    for dimension, scores in ratings_by_dimension.items():
        if scores:
            average_ratings[dimension] = sum(scores) / len(scores)

    return average_ratings

# Example usage
ratings = analyze_ratings(traces)
for dimension, avg_score in ratings.items():
    print(f"{dimension}: {avg_score:.2f}/1.0")

Considérations relatives à la production

Pour les déploiements de production, consultez notre guide sur l’observabilité de la production avec le suivi qui couvre :

  • Implémentation de points de terminaison de collecte de commentaires
  • Liaison de commentaires à des traces à l’aide d’ID de demande client
  • Configuration de la surveillance de la qualité en temps réel
  • Meilleures pratiques pour le traitement des commentaires à volume élevé

Étapes suivantes

Poursuivez votre parcours avec ces actions et tutoriels recommandés.

Guides de référence

Explorez la documentation détaillée relative aux concepts et fonctionnalités mentionnés dans ce guide.