Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Das Sammeln und Protokollieren von Benutzerfeedback ist unerlässlich, um die reale Qualität Ihrer GenAI-Anwendung zu verstehen. MLflow bietet eine strukturierte Möglichkeit, Feedback in Form von Bewertungen zu erfassen, um die Qualität im Laufe der Zeit nachzuverfolgen, Verbesserungsbereiche zu identifizieren und Evaluierungsdatensätze aus Produktionsdaten zu erstellen.
Voraussetzungen
Wählen Sie die entsprechende Installationsmethode basierend auf Ihrer Umgebung aus:
Produktion
Für Produktionsbereitstellungen installieren Sie das mlflow-tracing-Paket:
pip install --upgrade mlflow-tracing
Das Paket ist für den mlflow-tracing Produktionseinsatz mit minimalen Abhängigkeiten und besseren Leistungseigenschaften optimiert.
Entwicklung
Installieren Sie für Entwicklungsumgebungen das vollständige MLflow-Paket mit Databricks-Extras:
pip install --upgrade "mlflow[databricks]>=3.1"
Das vollständige mlflow[databricks] Paket enthält alle Features, die für die lokale Entwicklung und Das Experimentieren mit Databricks erforderlich sind.
Die log_feedback API ist in beiden Paketen verfügbar, sodass Sie Benutzerfeedback unabhängig davon sammeln können, welche Installationsmethode Sie auswählen.
Hinweis
MLflow 3 ist zum Sammeln von Benutzerfeedback erforderlich. MLflow 2.x wird aufgrund von Leistungsbeschränkungen und fehlenden Features für den Produktionseinsatz nicht unterstützt.
Warum Benutzerfeedback sammeln?
Benutzerfeedback liefert grundlegende Wahrheiten über die Leistung Ihrer Anwendung:
- Reale Qualitätssignale – Verstehen, wie tatsächliche Benutzer die Ausgaben Ihrer Anwendung wahrnehmen
- Kontinuierliche Verbesserung – Identifizieren von Mustern in negativem Feedback zur Anleitung der Entwicklung
- Erstellung von Schulungsdaten – Verwenden von Feedback zum Erstellen qualitativ hochwertiger Auswertungsdatensets
- Qualitätsüberwachung – Verfolgen von Zufriedenheitsmetriken im Laufe der Zeit und über verschiedene Benutzersegmente hinweg
- Modelloptimierung – Nutzen von Feedbackdaten zur Verbesserung Ihrer zugrunde liegenden Modelle
Arten von Feedback
MLflow unterstützt verschiedene Arten von Feedback über sein Bewertungssystem:
| Feedbacktyp | BESCHREIBUNG | Gängige Anwendungsfälle |
|---|---|---|
| Binäres Feedback | Einfach Daumen hoch/runter oder richtig/falsch | Schnelle Benutzerzufriedenheitssignale |
| Numerische Bewertungen | Bewertungen auf einer Skala (z. B. 1-5 Sterne) | Detaillierte Qualitätsbewertung |
| Kategorisches Feedback | Multiple-Choice-Optionen | Klassifizieren von Problemen oder Antworttypen |
| Textfeedback | Freiformkommentare | Ausführliche Erläuterungen für Benutzer |
Grundlegendes zum Feedbackdatenmodell
In MLflow wird das Benutzerfeedback mithilfe der Feedback-Entität erfasst, bei der es sich um eine Art von Bewertung handelt, die an Ablaufverfolgungen oder bestimmte Spannen angefügt werden kann. Die Feedback-Entität bietet eine strukturierte Möglichkeit zum Speichern:
- Wert: Das tatsächliche Feedback (boolescher Wert, numerischer Text oder strukturierter Daten)
- Quelle: Informationen darüber, wer oder was das Feedback bereitgestellt hat (menschlicher Benutzer, LLM-Richter oder Code)
- Begründung: Optionale Erklärung für das Feedback
- Metadaten: Zusätzlicher Kontext wie Zeitstempel oder benutzerdefinierte Attribute
Mithilfe dieses Datenmodells können Sie effektive Feedbacksammlungssysteme entwerfen, die nahtlos in die Auswertungs- und Überwachungsfunktionen von MLflow integriert werden. Ausführliche Informationen zum Feedback-Entitätsschema und allen verfügbaren Feldern finden Sie im Abschnitt "Feedback" in den Span-Konzepten.
Feedbacksammlung für Endbenutzer
Bei der Implementierung der Feedbacksammlung in der Produktion müssen Sie Benutzerfeedback mit bestimmten Spuren verknüpfen. Es gibt zwei Ansätze, die Sie verwenden können:
- Verwenden von Clientanforderungs-IDs – Generieren Sie beim Verarbeiten von Anforderungen eigene eindeutige IDs, und verweisen Sie später auf sie, um Feedback zu geben.
- Verwendung von MLflow-Ablaufverfolgungs-IDs – Verwenden Sie die von MLflow automatisch generierte Ablaufverfolgungs-ID
Grundlegendes zum Feedbacksammlungsablauf
Beide Ansätze folgen einem ähnlichen Muster:
Während der anfänglichen Anforderung: Ihre Anwendung generiert entweder eine eindeutige Clientanforderungs-ID oder ruft die vom MLflow generierte Ablaufverfolgungs-ID ab.
Nach Erhalt der Antwort: Der Benutzer kann Feedback geben, indem er auf eine der beiden IDs verweist. Beide Herangehensweisen folgen einem ähnlichen Muster:
Während der anfänglichen Anforderung: Ihre Anwendung generiert entweder eine eindeutige Clientanforderungs-ID oder ruft die vom MLflow generierte Ablaufverfolgungs-ID ab.
Nach Erhalt der Antwort: Der Benutzer kann Feedback geben, indem er auf eine der beiden IDs verweist.
Feedback wird protokolliert: Die MLflow-API
log_feedbackerstellt eine Bewertung, die der ursprünglichen Trace zugeordnet wird.Analyse und Überwachung: Sie können Feedback über alle Abläufe hinweg abfragen und analysieren
Implementieren der Feedbacksammlung
Ansatz 1: Verwenden von MLflow-Ablaufverfolgungs-IDs
Der einfachste Ansatz ist, die Trace-ID zu verwenden, die MLflow automatisch für jede Ablauffolge generiert. Sie können diese ID während der Anforderungsverarbeitung abrufen und an den Client zurückgeben:
Back-End-Implementierung
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,
}
Beispiel für die Frontend-Implementierung
Nachfolgend finden Sie ein Beispiel für die Front-End-Implementierung für eine React-basierte Anwendung:
// 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>
);
}
Ansatz 2: Verwenden von Clientanforderungs-IDs
Um mehr Kontrolle über die Anforderungsnachverfolgung zu erhalten, können Sie Ihre eigenen eindeutigen Clientanforderungs-IDs verwenden. Dieser Ansatz ist nützlich, wenn Sie Ihr eigenes Anforderungsverfolgungssystem verwalten oder in vorhandene Infrastruktur integrieren müssen:
Dieser Ansatz erfordert die Implementierung der Anforderungsnachverfolgung, bei der jede Ablaufverfolgung ein client_request_id Attribut hat. Weitere Informationen darüber, wie Sie Client-Anforderungs-IDs während der anfänglichen Anforderung an Ihre Ablaufverfolgungen anfügen, finden Sie unter Kontext zu Ablaufverfolgungen hinzufügen.
Back-End-Implementierung
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,
}
Beispiel für die Frontend-Implementierung
Nachfolgend finden Sie ein Beispiel für die Front-End-Implementierung für eine React-basierte Anwendung. Bei Verwendung von Clientanforderungs-IDs muss Ihr Frontend diese IDs speichern und verwalten:
// 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>
);
}
Wichtige Implementierungsdetails
AssessmentSource: Das AssessmentSource Objekt identifiziert, wer oder was das Feedback bereitgestellt hat:
-
source_type: Kann „BENUTZER“ für Benutzerfeedback oder „LLM_JUDGE“ für automatisierte Auswertung sein -
source_id: Identifiziert den spezifischen Benutzer oder das System, der Feedback gibt.
Feedback-Speicher: Feedback wird als Bewertungen auf dem Ablauf verfolgt gespeichert, was bedeutet:
- Sie ist dauerhaft mit der spezifischen Interaktion verknüpft.
- Es kann zusammen mit den Ablaufverfolgungsdaten abgefragt werden
- Es ist in der MLflow-Benutzeroberfläche sichtbar, wenn Sie den Ablauf verfolgen.
Behandeln verschiedener Feedbacktypen
Sie können beide Ansätze erweitern, um komplexeres Feedback zu unterstützen. Hier ist ein Beispiel mit Trace-IDs:
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
}
Umgang mit Feedback mit Streaming-Antworten
Wenn Sie Streaming-Antworten (Server-Sent Events oder WebSockets) verwenden, ist die Ablaufverfolgungs-ID erst verfügbar, wenn der Stream abgeschlossen ist. Dies stellt eine einzigartige Herausforderung für die Feedbacksammlung dar, die einen anderen Ansatz erfordert.
Warum Streaming anders ist
In traditionellen Anforderung/Antwort-Mustern empfangen Sie die vollständige Antwort und die Ablaufverfolgungs-ID zusammen. Mit Streaming:
- Token kommen inkrementell an: Die Antwort wird im Laufe der Zeit erstellt, während Token vom LLM gestreamt werden.
- Ablaufverfolgung ist zurückgestellt: Die Ablaufverfolgungs-ID wird erst nach dem Ende des gesamten Streams generiert.
- Die Feedback-UI muss warten: Benutzer können erst Feedback senden, wenn sie sowohl die komplette Antwort als auch die Trace-ID haben.
Back-End-Implementierung mit SSE
Vorgehensweise zum Implementieren von Streaming mit Lieferung der Ablaufverfolgung am Ende des Streams:
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
}
)
Frontend-Implementierung für Streaming
Handle die Streaming-Ereignisse und aktivieren Sie Feedback erst nach Empfang der Ablaufverfolgungs-ID:
// 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>
);
}
Wichtige Überlegungen zum Streaming
Beachten Sie bei der Implementierung der Feedbacksammlung mit Streamingantworten die folgenden Punkte:
Ablaufverfolgungs-ID-Zeitpunkt: Die Ablaufverfolgungs-ID ist erst verfügbar, nachdem das Streaming abgeschlossen ist. Entwerfen Sie Ihre Benutzeroberfläche so, dass sie dies elegant verarbeitet, indem Sie die Steuerungselemente deaktivieren, bis die Ablaufverfolgungs-ID empfangen wird.
Ereignisstruktur: Verwenden Sie ein konsistentes Ereignisformat mit einem
typeFeld, um zwischen Inhaltstoken, Abschlussereignissen und Fehlern zu unterscheiden. Dadurch wird die Analyse und Behandlung von Ereignissen zuverlässiger.Zustandsverwaltung: Verfolgen Sie sowohl den Streaming-Inhalt als auch die Trace-ID separat. Setzen Sie den gesamten Zustand am Anfang jeder neuen Interaktion zurück, um veraltete Datenprobleme zu verhindern.
Fehlerbehandlung: Schließen Sie Fehlerereignisse in den Datenstrom ein, um Fehler ordnungsgemäß zu behandeln. Stellen Sie sicher, dass Fehler nach Möglichkeit zur Ablaufverfolgung protokolliert werden, um das Debuggen zu erleichtern.
Pufferverwaltung:
- Verwenden von
X-Accel-Buffering: noHeadern zum Deaktivieren der Proxypufferung - Implementieren der richtigen Zeilenpufferung im Frontend zum Behandeln von partiellen SSE-Nachrichten
- Erwägen Sie die Implementierung einer Wiederverbindungslogik bei Netzwerkunterbrechungen.
- Verwenden von
Leistungsoptimierung:
- Kleine Verzögerungen zwischen Tokens hinzufügen (
asyncio.sleep(0.01)), um Clients nicht zu überfordern - Batchen Sie mehrere Tokens, wenn sie zu schnell ankommen
- Erwägen Sie die Implementierung von Backpressure-Mechanismen für langsame Clients
- Kleine Verzögerungen zwischen Tokens hinzufügen (
Analysieren von Feedbackdaten
Nachdem Sie Feedback gesammelt haben, können Sie es analysieren, um Erkenntnisse über die Qualität und Benutzerzufriedenheit Ihrer Anwendung zu gewinnen.
Anzeigen von Feedback in der Trace-Oberfläche
Abrufen von Ablaufverfolgungen mit Feedback mithilfe des SDK
Anzeigen von Feedback in der Trace-Oberfläche
Abrufen von Ablaufverfolgungen mit Feedback mithilfe des SDK
Rufen Sie zunächst Spuren aus einem bestimmten Zeitfenster ab:
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
Analysieren von Feedbackmustern mithilfe des SDK
Extrahiere und analysiere Feedback aus den Spuren.
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")
Analysieren von mehrdimensionalen Feedback
Ausführlicheres Feedback mit Bewertungen:
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")
Nächste Schritte
- Bereitstellen von Agents mit Ablaufverfolgung – Grundlegendes zur Protokollierung von Ablaufverfolgungen in der Produktion
- Erstellen von Auswertungsdatensätzen – Verwenden von gesammelten Feedback zum Erstellen von Testdatensätzen und Analysieren von Mustern für Qualitätsverbesserungen
- Einrichten der Produktionsüberwachung – Überwachen von Qualitätsmetriken basierend auf Feedback