Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
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 :
- Signaux de qualité réels - Comprendre comment les utilisateurs réels perçoivent les sorties de votre application
- Amélioration continue - Identifier les modèles dans les commentaires négatifs pour guider le développement
- Création de données d’apprentissage - Utiliser les commentaires pour créer des jeux de données d’évaluation de haute qualité
- Surveillance de la qualité - Suivre les métriques de satisfaction au fil du temps et sur différents segments d’utilisateurs
- 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 :
- 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
- 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 :
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
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.
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
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
Les commentaires sont consignés : l’API de
log_feedback
MLflow crée une évaluation attachée à la trace d’origineAnalyse 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 :
- 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
- 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é
- 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 :
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.
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.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.
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.
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
- Utiliser l’en-tête
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
- Ajouter de petits retards entre les jetons (
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
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.
- Créer des jeux de données d’évaluation - Utiliser les commentaires collectés pour créer des jeux de données de test
- Utiliser des traces pour améliorer la qualité - Analyser les modèles de commentaires pour identifier les améliorations
- Configurer la surveillance de la production - Surveiller les métriques de qualité en fonction des commentaires
Guides de référence
Explorez la documentation détaillée relative aux concepts et fonctionnalités mentionnés dans ce guide.
- Évaluations de journalisation - Comprendre comment les commentaires sont stockés en tant qu’évaluations
- Modèle de données de suivi - En savoir plus sur les évaluations et la structure de trace
- Traces de requêtes via SDK - Techniques avancées pour l’analyse des commentaires