Colete feedback do usuário
Coletar e registrar o feedback do usuário é essencial para entender a qualidade real de seu aplicativo GenAI. MLflow fornece uma maneira estruturada de capturar feedback como avaliações em traços, permitindo que o senhor acompanhe a qualidade ao longo do tempo, identifique áreas de melhoria e crie um conjunto de dados de avaliação a partir dos dados de produção.
Pré-requisitos
Escolha o método de instalação apropriado com base no seu ambiente:
- Production
- Development
Para implementações de produção, instale o pacote mlflow-tracing
:
pip install --upgrade mlflow-tracing
O pacote mlflow-tracing
é otimizado para uso em produção com dependências mínimas e melhores características de desempenho.
Para ambientes de desenvolvimento, instale o pacote completo do MLflow com os extras do Databricks:
pip install --upgrade "mlflow[databricks]>=3.1"
O pacote completo do mlflow[databricks]
inclui todos os recursos necessários para o desenvolvimento local e a experimentação no Databricks.
O log_feedback
API está disponível em ambos os pacotes, de modo que o senhor pode coletar feedback do usuário independentemente do método de instalação escolhido.
O MLflow 3 é necessário para coletar feedback do usuário. MLflow O 2.x não é compatível devido a limitações de desempenho e à falta de recursos essenciais para uso em produção.
Por que coletar feedback do usuário?
O feedback do usuário fornece a verdade básica sobre o desempenho do seu aplicativo:
- Sinais de qualidade do mundo real - entenda como os usuários reais percebem as saídas do seu aplicativo
- Melhoria contínua - Identificar padrões no feedback negativo para orientar o desenvolvimento
- criação de dados de treinamento - Use o feedback para criar um conjunto de dados de avaliação de alta qualidade
- Monitoramento da qualidade - Acompanhe as métricas de satisfação ao longo do tempo e em diferentes segmentos de usuários
- Ajuste fino do modelo - Aproveite os dados de feedback para melhorar seus modelos subjacentes
Tipos de feedback
O MLflow oferece suporte a vários tipos de feedback por meio de seu sistema de avaliação:
Tipo de feedback | Descrição | Casos de uso comuns |
---|---|---|
Feedback binário | Simples polegares para cima/para baixo ou corretos/incorretos | Sinais rápidos de satisfação do usuário |
Pontuações numéricas | Classificações em uma escala (por exemplo, de 1 a 5 estrelas) | Avaliação detalhada da qualidade |
Feedback categórico | Opções de múltipla escolha | Classificação de problemas ou tipos de resposta |
Feedback de texto | Comentários de formato livre | Explicações detalhadas para o usuário |
Entendendo o modelo de dados de feedback
No MLflow, o feedback do usuário é capturado usando a entidade Feedback , que é um tipo de avaliação que pode ser anexada a traços ou períodos específicos. A entidade Feedback fornece uma forma estruturada de armazenar:
- Valor : O feedback real (booleano, numérico, texto ou dados estruturados)
- Fonte : informações sobre quem ou o que forneceu o feedback (usuário humano, juiz do LLM ou código)
- Justificativa : explicação opcional para o feedback
- Metadados : contexto adicional, como carimbos de data/hora ou atributos personalizados
A compreensão desse modelo de dados ajuda o senhor a projetar sistemas eficazes de coleta de feedback que se integram perfeitamente aos recursos de avaliação e monitoramento do MLflow. Para obter informações detalhadas sobre o esquema da entidade Feedback e todos os campos disponíveis, consulte a seção Feedback no Tracing Data Model.
Coleta de feedback do usuário final
Ao implementar a coleta de feedback na produção, você precisa vincular o feedback do usuário a rastreamentos específicos. Há duas abordagens que você pode usar:
- Usando IDs de solicitação de cliente - Gere seus próprios IDs exclusivos ao processar solicitações e consulte-os posteriormente para obter feedback
- Uso de IDs de rastreamento do MLflow - Use a ID de rastreamento gerada automaticamente pelo MLflow
Entendendo o fluxo de coleta de feedback
Ambas as abordagens seguem um padrão semelhante:
-
Durante a solicitação inicial : Seu aplicativo gera um ID exclusivo de solicitação do cliente ou recupera o ID de rastreamento gerado pelo MLFlow
-
Depois de receber a resposta : o usuário pode fornecer feedback referenciando qualquer ID Ambas as abordagens seguem um padrão semelhante:
-
Durante a solicitação inicial : Seu aplicativo gera um ID exclusivo de solicitação do cliente ou recupera o ID de rastreamento gerado pelo MLFlow
-
Depois de receber a resposta : o usuário pode fornecer feedback referenciando qualquer ID
-
Os comentários são registros : MLflow's
log_feedback
API cria uma avaliação anexada ao rastreamento original -
análise e monitoramento : O senhor pode consultar e analisar o feedback em todos os rastros
Implementar a coleta de feedback
- Approach 1: Using MLflow trace IDs
- Approach 2: Using client request IDs
A abordagem mais simples é usar o ID de rastreamento que o MLflow gera automaticamente para cada rastreamento. Você pode recuperar essa ID durante o processamento da solicitação e devolvê-la ao cliente:
Implementação de backend
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,
}
Exemplo de implementação de front-end
abaixo é um exemplo de implementação de front-end para um aplicativo baseado em 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 && Thanks for your feedback!</span>}
</div>
)}
</div>
);
}
Para ter mais controle sobre o acompanhamento das solicitações, o senhor pode usar seus próprios IDs exclusivos de solicitação do cliente. Essa abordagem é útil quando o senhor precisa manter seu próprio sistema de acompanhamento de solicitações ou integrar-se à infraestrutura existente:
Essa abordagem exige que o senhor implemente o acompanhamento de solicitações em que cada rastreamento tenha um atributo client_request_id
. Para obter mais informações sobre como anexar IDs de solicitação do cliente aos seus rastreamentos durante a solicitação inicial, consulte Adicionar contexto aos rastreamentos.
Implementação de backend
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,
}
Exemplo de implementação de front-end
abaixo é um exemplo de implementação de front-end para um aplicativo baseado em React. Ao usar IDs de solicitação do cliente, seu frontend precisa armazenar e gerenciar esses IDs:
// 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 && ✓ 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>
);
}
principais detalhes da implementação
AssessmentSource : O objeto AssessmentSource
identifica quem ou o que forneceu o feedback:
source_type
: Pode ser " HUMAN " para feedback do usuário ou " LLM_JUDGE " para avaliação automatizadasource_id
: identifica o usuário ou sistema específico que está fornecendo feedback
Armazenamento de feedback : o feedback é armazenado como avaliações no rastreamento, o que significa:
- Está permanentemente associado à interação específica
- Ele pode ser consultado junto com os dados de rastreamento
- Ele fica visível na interface do usuário do MLflow ao visualizar o rastreamento
Lidar com diferentes tipos de feedback
Você pode estender qualquer uma das abordagens para oferecer suporte a um feedback mais complexo. Aqui está um exemplo usando IDs de rastreamento:
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
}
Tratamento de feedback com respostas de transmissão
Ao usar respostas de transmissão (eventos enviados pelo servidor ou WebSockets), a ID do rastreamento não estará disponível até que a transmissão seja concluída. Isso representa um desafio único para a coleta de feedback que exige uma abordagem diferente.
Por que a transmissão é diferente
Nos padrões tradicionais de solicitação-resposta, você recebe a resposta completa e o ID de rastreamento juntos. Com transmissão:
- Os tokens chegam de forma incremental : A resposta é construída ao longo do tempo, conforme a transmissão tokens do LLM
- A conclusão do rastreamento é adiada : A ID do rastreamento só é gerada após o término de toda a transmissão
- A interface de usuário de feedback deve esperar : os usuários não podem fornecer feedback até que tenham a resposta completa e o ID de rastreamento
Implementação de back-end com SSE
Veja como implementar a transmissão com entrega de ID de rastreamento no final da transmissão:
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
}
)
Implementação de front-end para transmissão
Trate os eventos de transmissão e ative o feedback somente após receber a ID do rastreamento:
// 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>}
</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">
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 && 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>
);
}
considerações fundamentais para a transmissão
Ao implementar a coleta de feedback com respostas de transmissão, tenha em mente os seguintes pontos:
-
Tempo de ID do rastreamento : A ID do rastreamento só fica disponível após a conclusão da transmissão. Crie sua interface de usuário para lidar com isso normalmente, desativando os controles de feedback até que o ID de rastreamento seja recebido.
-
Estrutura do evento : Use um formato de evento consistente com um campo
type
para distinguir entre tokens de conteúdo, eventos de conclusão e erros. Isso torna a análise e o tratamento de eventos mais confiáveis. -
Gerenciamento de estado : Rastreie o conteúdo da transmissão e o ID de rastreamento separadamente. Reset todos os estados no início de cada nova interação para evitar problemas de dados desatualizados.
-
Tratamento de erros : Inclua eventos de erro na transmissão para tratar graciosamente as falhas. Certifique-se de que os erros sejam registrados no rastreamento quando possível para depuração.
-
Gerenciamento de buffer :
- Use o cabeçalho
X-Accel-Buffering: no
para desativar o buffer de proxy - Implemente o buffer de linha adequado no frontend para lidar com mensagens SSE parciais
- Considere a implementação da lógica de reconexão para interrupções de rede
- Use o cabeçalho
-
Otimização do desempenho :
- Adicione pequenos atrasos entre os tokens (
asyncio.sleep(0.01)
) para evitar sobrecarregar os clientes - lote vários tokens se eles chegarem muito rapidamente
- Considere a implementação de mecanismos de contrapressão para clientes lentos
- Adicione pequenos atrasos entre os tokens (
Analisando dados de feedback
Depois de coletar o feedback, o senhor pode analisá-lo para obter percepções sobre a qualidade do seu aplicativo e a satisfação do usuário.
Visualizando feedback na interface do usuário do Trace
Obtendo traços com feedback por meio do SDK
Visualizando feedback na interface do usuário do Trace
Obtendo traços com feedback por meio do SDK
Primeiro, recupere os traços de uma janela de tempo específica:
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
Análise de padrões de feedback por meio do SDK
Extraia e analise o feedback dos rastreamentos:
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")
Analisando o feedback multidimensional
Para obter um feedback mais detalhado com as classificações:
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")
Considerações de produção
Para implantações de produção, consulte nosso guia sobre observabilidade de produção com rastreamento, que abrange:
- Implementação do ponto de extremidade de coleta de feedback
- Vinculando feedback a rastreamentos usando IDs de solicitação do cliente
- Configuração do monitoramento de qualidade em tempo real
- Práticas recomendadas para processamento de feedback em alto volume
Próximas etapas
Continue sua jornada com estas ações recomendadas e o tutorial.
- Criar um conjunto de dados de avaliação - Usar o feedback coletado para criar um conjunto de dados de teste
- Use rastreamentos para melhorar a qualidade - Analise os padrões de feedback para identificar melhorias
- Configurar o monitoramento da produção - Monitorar as métricas de qualidade com base no feedback
Guia de referência
Explore a documentação detalhada dos conceitos e recursos mencionados neste guia.
- Registrando avaliações - Entenda como o feedback é armazenado como avaliações
- Modelo de dados de rastreamento - Saiba mais sobre avaliações e estrutura de rastreamento
- Traços de consulta via SDK - Técnicas avançadas para analisar feedback