Udostępnij za pośrednictwem


Zbieranie opinii użytkowników

Zbieranie i rejestrowanie opinii użytkowników jest niezbędne do zrozumienia rzeczywistej jakości aplikacji GenAI. Platforma MLflow zapewnia ustrukturyzowany sposób przechwytywania opinii jako ocen dotyczących śladów, umożliwiając śledzenie jakości w czasie, identyfikowanie obszarów poprawy i tworzenie zestawów danych oceny na podstawie danych produkcyjnych.

Wymagania wstępne

Wybierz odpowiednią metodę instalacji na podstawie środowiska:

Produkcja

W przypadku wdrożeń produkcyjnych zainstaluj mlflow-tracing pakiet:

pip install --upgrade mlflow-tracing

Pakiet mlflow-tracing jest zoptymalizowany pod kątem użycia w środowisku produkcyjnym z minimalnymi zależnościami i lepszą charakterystyką wydajności.

Rozwój

W przypadku środowisk deweloperskich zainstaluj pełny pakiet MLflow przy użyciu dodatków usługi Databricks:

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

Pełny mlflow[databricks] pakiet zawiera wszystkie funkcje potrzebne do lokalnego programowania i eksperymentowania w usłudze Databricks.

Interfejs log_feedback API jest dostępny w obu pakietach, dzięki czemu można zbierać opinie użytkowników niezależnie od wybranej metody instalacji.

Uwaga / Notatka

Platforma MLflow 3 jest wymagana do zbierania opinii użytkowników. Środowisko MLflow 2.x nie jest obsługiwane z powodu ograniczeń wydajności i brakujących funkcji niezbędnych do użycia w środowisku produkcyjnym.

Dlaczego warto zbierać opinie użytkowników?

Opinie użytkowników zapewniają podstawowe informacje na temat wydajności aplikacji:

  1. Rzeczywiste sygnały jakości — informacje o tym, jak rzeczywiste użytkownicy postrzegają dane wyjściowe aplikacji
  2. Ciągłe ulepszanie — identyfikowanie wzorców w negatywnych opiniach w celu kierowania rozwojem
  3. Tworzenie danych treningowych — tworzenie zestawów danych oceny wysokiej jakości przy użyciu opinii
  4. Monitorowanie jakości — śledzenie metryk zadowolenia w czasie i w różnych segmentach użytkowników
  5. Dostrajanie modelu — wykorzystanie danych opinii w celu ulepszenia modeli bazowych

Typy opinii

Platforma MLflow obsługuje różne typy opinii za pośrednictwem systemu oceny:

Typ opinii Opis Typowe przypadki użycia
Opinie binarne Po prostu kciuki w górę/w dół lub poprawnie/niepoprawnie Sygnały szybkiego zadowolenia użytkowników
Wyniki liczbowe Oceny na dużą skalę (np. 1–5 gwiazdek) Szczegółowa ocena jakości
Opinie podzielone na kategorie Wiele opcji wyboru Klasyfikowanie problemów lub typów odpowiedzi
Opinia tekstowa Komentarze w dowolnej formie Szczegółowe wyjaśnienia użytkowników

Zrozumienie modelu danych opinii

W rozwiązaniu MLflow opinie użytkowników są przechwytywane przy użyciu jednostki Feedback , która jest typem oceny , który może być dołączony do śladów lub określonych zakresów. Encja Opinii zapewnia ustrukturyzowany sposób przechowywania:

  • Wartość: rzeczywista opinia (dane logiczne, liczbowe, tekstowe lub ustrukturyzowane)
  • Źródło: informacje o tym, kto lub co dostarczyło opinię (użytkownik ludzki, sędzia LLM lub kod)
  • Uzasadnienie: opcjonalne wyjaśnienie opinii
  • Metadane: Dodatkowy kontekst, taki jak znaczniki czasu lub atrybuty niestandardowe

Zrozumienie tego modelu danych pomaga zaprojektować efektywne systemy zbierania opinii, które bezproblemowo integrują się z możliwościami oceny i monitorowania platformy MLflow. Aby uzyskać szczegółowe informacje na temat schematu jednostki Opinie zwrotne i wszystkich dostępnych pól, zobacz sekcję Opinie zwrotne w modelu danych Tracing.

Zbieranie opinii użytkowników końcowych

Podczas implementowania zbierania opinii w środowisku produkcyjnym należy połączyć opinie użytkowników z określonymi śladami. Istnieją dwa podejścia, których można użyć:

  1. Używanie identyfikatorów żądań klienta — generowanie własnych unikatowych identyfikatorów podczas przetwarzania żądań i odwoływania się do nich później w celu uzyskania opinii
  2. Używanie identyfikatorów śledzenia MLflow — użyj identyfikatora śledzenia generowanego automatycznie przez bibliotekę MLflow

Omówienie przepływu zbierania opinii

Oba podejścia są zgodne z podobnym wzorcem:

  1. Podczas początkowego żądania: Aplikacja generuje unikatowy identyfikator żądania klienta lub pobiera identyfikator śledzenia wygenerowany przez platformę MLflow

  2. Po otrzymaniu odpowiedzi: użytkownik może przekazać opinię, odwołując się do jednego z identyfikatorów. Oba podejścia kierują się podobnym wzorcem.

  3. Podczas początkowego żądania: Aplikacja generuje unikatowy identyfikator żądania klienta lub pobiera identyfikator śledzenia wygenerowany przez platformę MLflow

  4. Po otrzymaniu odpowiedzi: użytkownik może przekazać opinię, odwołując się do dowolnego identyfikatora

  5. Opinia jest rejestrowana: API MLflow log_feedback tworzy ocenę powiązaną z oryginalnym śladem

  6. Analiza i monitorowanie: możesz wykonywać zapytania i analizować opinie we wszystkich zapisach

Implementowanie zbierania opinii

Podejście 1. Używanie identyfikatorów śledzenia MLflow

Najprostszym podejściem jest użycie identyfikatora śledzenia generowanego automatycznie przez platformę MLflow dla każdego śladu. Ten identyfikator można pobrać podczas przetwarzania żądania i zwrócić go do klienta:

Implementacja warstwy serwerowej

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,
    }

Przykład implementacji frontonu

Poniżej przedstawiono przykład implementacji frontonu dla aplikacji opartej na platformie 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>
  );
}

Podejście 2. Używanie identyfikatorów żądań klienta

Aby uzyskać większą kontrolę nad śledzeniem żądań, możesz użyć własnych unikatowych identyfikatorów żądań klienta. Takie podejście jest przydatne w przypadku konieczności obsługi własnego systemu śledzenia żądań lub integracji z istniejącą infrastrukturą:

Takie podejście wymaga zaimplementowania śledzenia żądań, w którym każdy ślad ma client_request_id atrybut. Aby uzyskać więcej informacji na temat dołączania identyfikatorów żądań klienta do swoich śladów podczas początkowego żądania, zobacz Dodawanie kontekstu do śladów.

Implementacja warstwy serwerowej

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,
    }

Przykład implementacji frontonu

Poniżej przedstawiono przykład implementacji frontonu dla aplikacji opartej na platformie React. W przypadku korzystania z identyfikatorów żądań klienta fronton musi przechowywać następujące identyfikatory i zarządzać nimi:

// 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>
  );
}

Kluczowe szczegóły implementacji

AssessmentSource: AssessmentSource obiekt określa, kto lub co dostarczyło opinię:

  • source_type: Może to być wartość "HUMAN" dla opinii użytkowników lub "LLM_JUDGE" na potrzeby zautomatyzowanej oceny
  • source_id: identyfikuje określonego użytkownika lub systemu przekazującego opinię

Przechowywanie opinii: Opinie są przechowywane jako oceny na śladzie, co oznacza:

  • Jest on trwale skojarzony z określoną interakcją
  • Można odpytywać je wraz z danymi śledzenia.
  • Jest on widoczny w interfejsie użytkownika platformy MLflow podczas wyświetlania śladu

Obsługa różnych typów opinii

Możesz rozszerzyć dowolne z podejść, aby obsługiwać bardziej złożone opinie. Oto przykład użycia identyfikatorów śledzenia:

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
    }

Obsługa informacji zwrotnej za pomocą odpowiedzi strumieniowych

W przypadku korzystania z odpowiedzi przesyłanych strumieniowo (zdarzeniaServer-Sent lub WebSocket) identyfikator śledzenia nie jest dostępny, dopóki strumień nie zostanie ukończony. Stanowi to unikatowe wyzwanie dla zbierania opinii, które wymaga innego podejścia.

Dlaczego przesyłanie strumieniowe jest inne

W tradycyjnych wzorcach odpowiedzi na żądanie otrzymujesz razem pełną odpowiedź i identyfikator śledzenia. Za pomocą przesyłania strumieniowego:

  1. Tokeny są dostarczane przyrostowo: odpowiedź jest tworzona stopniowo w miarę strumieniowania tokenów z usługi LLM
  2. Uzupełnianie śledzenia jest odroczone: identyfikator śledzenia generowany jest dopiero po zakończeniu całego strumienia
  3. Interfejs opinii musi czekać: użytkownicy nie mogą przeprowadzić opinii, dopóki nie otrzymają pełnej odpowiedzi i identyfikatora śledzenia

Implementacja backendu z użyciem SSE

Oto jak zaimplementować streaming z dostarczaniem identyfikatora śledzenia na końcu strumienia:

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
        }
    )

Implementacja frontonu na potrzeby przesyłania strumieniowego

Obsługuj zdarzenia przesyłania strumieniowego i włączaj opinie dopiero po otrzymaniu identyfikatora śledzenia.

// 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>
  );
}

Kluczowe zagadnienia dotyczące przesyłania strumieniowego

Podczas implementowania zbierania opinii przy użyciu odpowiedzi strumieniowych, należy pamiętać o następujących kwestiach:

  1. Czas identyfikatora śledzenia: identyfikator śledzenia jest dostępny dopiero po zakończeniu przesyłania strumieniowego. Zaprojektuj interfejs użytkownika, aby obsłużyć ten proces bezpiecznie, wyłączając kontrolki opinii do momentu odebrania identyfikatora śledzenia.

  2. Struktura zdarzeń: użyj spójnego formatu zdarzeń z polem type , aby odróżnić tokeny zawartości, zdarzenia ukończenia i błędy. Dzięki temu analizowanie i obsługa zdarzeń jest bardziej niezawodne.

  3. Zarządzanie stanem: Śledzenie zarówno zawartości przesyłania strumieniowego, jak i identyfikatora śledzenia oddzielnie. Zresetuj wszystkie stany na początku każdej nowej interakcji, aby zapobiec problemom ze nieaktualnymi danymi.

  4. Obsługa błędów: uwzględnij zdarzenia błędów w strumieniu, aby bezpiecznie obsłużyć błędy. Upewnij się, że błędy są rejestrowane w logach, w miarę możliwości dla celów debugowania.

  5. Zarządzanie buforem:

    • Wyłączanie buforowania serwera proxy za pomocą X-Accel-Buffering: no nagłówka
    • Implementacja prawidłowego buforowania liniowego w interfejsie użytkownika w celu obsługi częściowych wiadomości SSE
    • Rozważ zaimplementowanie logiki ponownego łączenia w przypadku przerw w działaniu sieci
  6. Optymalizacja wydajności:

    • Dodawanie małych opóźnień między tokenami (asyncio.sleep(0.01)), aby zapobiec przeciążeniu klientów
    • Wsaduj liczne tokeny, jeśli przybędą zbyt szybko
    • Rozważ zaimplementowanie mechanizmów przeciążenia zwrotnego dla powolnych klientów

Analizowanie danych opinii

Po zebraniu opinii możesz je przeanalizować, aby uzyskać szczegółowe informacje o jakości aplikacji i zadowoleniu użytkowników.

Wyświetlanie informacji zwrotnych w interfejsie użytkownika Trace

Pobieranie śladów z informacją zwrotną za pośrednictwem zestawu SDK

Wyświetlanie informacji zwrotnych w interfejsie użytkownika Trace

informacja zwrotna o śledzeniu

Pobieranie śladów z informacją zwrotną za pośrednictwem zestawu SDK

Najpierw pobierz ślady z określonego przedziału czasu:

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

Analizowanie wzorców opinii za pośrednictwem zestawu SDK

Wyodrębnianie i analizowanie opinii ze śladów:

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")

Analizowanie wielowymiarowych opinii

Aby uzyskać bardziej szczegółową opinię z ocenami:

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")

Zagadnienia dotyczące środowiska produkcyjnego

Aby zapoznać się z wdrożeniami produkcyjnymi, zapoznaj się z naszym przewodnikiem dotyczącym obserwacji środowiska produkcyjnego z śledzeniem , które obejmuje:

  • Implementowanie punktów końcowych zbierania opinii
  • Łączenie opinii ze śladami przy użyciu identyfikatorów żądań klienta
  • Konfigurowanie monitorowania jakości w czasie rzeczywistym
  • Najlepsze praktyki dotyczące przetwarzania opinii na dużą skalę

Dalsze kroki

Kontynuuj pracę z tymi zalecanymi akcjami i samouczkami.

Przewodniki referencyjne

Zapoznaj się ze szczegółową dokumentacją dotyczącą pojęć i funkcji wymienionych w tym przewodniku.