次の方法で共有


ユーザーフィードバックを収集する

GenAI アプリケーションの実際の品質を理解するには、ユーザーフィードバックの収集とログ記録が不可欠です。 MLflow は、フィードバックをトレースの評価としてキャプチャする構造化された方法を提供し、時間の経過に伴う品質の追跡、改善の領域の特定、運用データからの評価データセットの構築を可能にします。

トレース評価

[前提条件]

環境に基づいて適切なインストール方法を選択します。

生産

運用環境の展開の場合は、 mlflow-tracing パッケージをインストールします。

pip install --upgrade mlflow-tracing

mlflow-tracing パッケージは、最小限の依存関係とパフォーマンス特性を備えた運用環境での使用に最適化されています。

発達

開発環境の場合は、Databricks の追加機能を使用して完全な MLflow パッケージをインストールします。

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

完全な mlflow[databricks] パッケージには、Databricks でのローカル開発と実験に必要なすべての機能が含まれています。

log_feedback API は両方のパッケージで使用できるため、選択したインストール方法に関係なくユーザーフィードバックを収集できます。

ユーザー フィードバックを収集するために MLflow 3 が必要です。 MLflow 2.x は、パフォーマンスの制限と、運用環境での使用に不可欠な機能がないため、サポートされていません。

ユーザーフィードバックを収集する理由

ユーザーフィードバックは、アプリケーションのパフォーマンスに関する根拠を提供します。

  1. 実際の品質信号 - 実際のユーザーがアプリケーションの出力をどのように認識するかを理解する
  2. 継続的な改善 - 負のフィードバックのパターンを特定して開発をガイドする
  3. トレーニング データの作成 - フィードバックを使用して高品質の評価データセットを構築する
  4. 品質監視 - 時間の経過と異なるユーザー セグメント間で満足度メトリックを追跡する
  5. モデルの微調整 - フィードバック データを活用して基になるモデルを改善する

フィードバックの種類

MLflow は、評価システムを通じてさまざまな種類のフィードバックをサポートしています。

フィードバックの種類 説明 一般的なユース ケース
バイナリ・フィードバック いいね/サムズダウンまたは 正しい/正しくない 迅速なユーザー満足度シグナル
数値スコア スケールでの評価 (例: 1 ~ 5 つ星) 詳細な品質評価
カテゴリに関するフィードバック 複数選択オプション 問題または応答の種類の分類
テキストフィードバック 自由形式のコメント ユーザーの詳細な説明

フィードバック データ モデルについて

MLflow では、ユーザー フィードバックは Feedback エンティティを使用してキャプチャされます。これは、トレースまたは特定のスパンにアタッチできる 評価 の一種です。 Feedback エンティティは、次を格納するための構造化された方法を提供します。

  • : 実際のフィードバック (ブール値、数値、テキスト、または構造化データ)
  • ソース: フィードバックを提供したユーザーまたは何に関する情報 (人間のユーザー、LLM の判事、またはコード)
  • 根拠: フィードバックの省略可能な説明
  • メタデータ: タイムスタンプやカスタム属性などの追加のコンテキスト

このデータ モデルを理解することは、MLflow の評価および監視機能とシームレスに統合する効果的なフィードバック収集システムを設計するのに役立ちます。 Feedback エンティティ スキーマと使用可能なすべてのフィールドの詳細については、「 トレース データ モデル」の「フィードバック」セクションを参照してください。

利用者のフィードバック収集

運用環境でフィードバック収集を実装する場合は、ユーザーフィードバックを特定のトレースにリンクする必要があります。 次の 2 つの方法を使用できます。

  1. クライアント要求 ID の使用 - 要求を処理するときに独自の一意の ID を生成し、後でフィードバックのために参照する
  2. MLflow トレース ID の使用 - MLflow によって自動的に生成されたトレース ID を使用する

フィードバック収集フローについて

どちらの方法も同様のパターンに従います。

  1. 最初の要求時: アプリケーションが一意のクライアント要求 ID を生成するか、MLflow で生成されたトレース ID を取得します

  2. 応答を受け取った後: ユーザーは、いずれかの ID を参照してフィードバックを提供できます。どちらの方法も同様のパターンに従います。

  3. 最初の要求時: アプリケーションが一意のクライアント要求 ID を生成するか、MLflow で生成されたトレース ID を取得します

  4. 応答を受信した後: ユーザーは、いずれかの ID を参照してフィードバックを提供できます

  5. フィードバックがログに記録されます。MLflow の log_feedback API によって、元のトレースにアタッチされた評価が作成されます

  6. 分析と監視: すべてのトレースでフィードバックのクエリと分析を行うことができます

フィードバック収集の実装

方法 1: MLflow トレース ID の使用

最も簡単な方法は、MLflow が各トレースに対して自動的に生成するトレース ID を使用することです。 要求処理中にこの ID を取得し、クライアントに返すことができます。

バックエンドの実装

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

フロントエンドの実装例

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

方法 2: クライアント要求 ID の使用

要求の追跡をより詳細に制御するために、独自の一意のクライアント要求 ID を使用できます。 この方法は、独自の要求追跡システムを維持する必要がある場合や、既存のインフラストラクチャと統合する必要がある場合に便利です。

この方法では、各トレースに client_request_id 属性がある要求追跡を実装する必要があります。 最初の要求中にクライアント要求 ID をトレースにアタッチする方法の詳細については、「 トレースにメタデータとユーザー フィードバックを追加する」を参照してください。

バックエンドの実装

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

フロントエンドの実装例

React ベースのアプリケーションのフロントエンド実装の例を次に示します。 クライアント要求 ID を使用する場合、フロントエンドは次の 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>
  );
}

主要な実装の詳細

AssessmentSource: AssessmentSource オブジェクトは、フィードバックを提供したユーザーまたは何を識別します。

  • source_type: ユーザー フィードバックの場合は "HUMAN"、自動評価の場合は "LLM_JUDGE" にすることができます。
  • source_id: フィードバックを提供する特定のユーザーまたはシステムを識別します

フィードバック ストレージ: フィードバックは、トレース上の評価として格納されます。つまり、次のことを意味します。

  • これは、特定の操作に永続的に関連付けられます
  • トレース データと共にクエリを実行できます。
  • トレースを表示すると、MLflow UI に表示されます

さまざまなフィードバックの種類の処理

どちらかのアプローチを拡張して、より複雑なフィードバックをサポートできます。 トレース ID の使用例を次に示します。

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
    }

ストリーミング応答を使用したフィードバックの処理

ストリーミング応答 (Server-Sent イベントまたは WebSocket) を使用する場合、トレース ID はストリームが完了するまで使用できません。 これは、異なるアプローチを必要とするフィードバック収集に固有の課題です。

ストリーミングが異なる理由

従来の要求/応答パターンでは、完全な応答とトレース ID を一緒に受け取ります。 ストリーミングの場合:

  1. トークンは段階的に到着します。応答は、LLM からのトークン ストリームとして時間の経過と同時に構築されます
  2. トレースの完了が遅延される: トレース ID は、ストリーム全体が完了した後にのみ生成されます
  3. フィードバック UI は待機する必要があります。ユーザーは、完全な応答とトレース ID の両方を取得するまでフィードバックを提供できません

SSE を使用したバックエンドの実装

ストリームの最後にトレース ID 配信を使用してストリーミングを実装する方法を次に示します。

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

ストリーミング用のフロントエンド実装

ストリーミング イベントを処理し、トレース 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>
  );
}

ストリーミングに関する主な考慮事項

ストリーミング応答を使用してフィードバック収集を実装する場合は、次の点に注意してください。

  1. トレース ID のタイミング: トレース ID は、ストリーミングが完了した後にのみ使用できます。 トレース ID が受信されるまでフィードバック コントロールを無効にして、これを適切に処理するように UI を設計します。

  2. イベント構造: type フィールドと一貫性のあるイベント形式を使用して、コンテンツ トークン、完了イベント、エラーを区別します。 これにより、イベントの解析と処理の信頼性が向上します。

  3. 状態管理: ストリーミング コンテンツとトレース ID の両方を個別に追跡します。 古いデータの問題を防ぐために、新しい各対話の開始時にすべての状態をリセットします。

  4. エラー処理: ストリームにエラー イベントを含め、エラーを適切に処理します。 可能な場合は、デバッグのためにエラーがトレースに記録されていることを確認します。

  5. バッファー管理:

    • X-Accel-Buffering: no ヘッダーを使用してプロキシ バッファリングを無効にする
    • 部分的な SSE メッセージを処理するための適切なライン バッファリングをフロントエンドに実装する
    • ネットワークの中断に対する再接続ロジックの実装を検討する
  6. パフォーマンスの最適化:

    • トークン間に小さな遅延 (asyncio.sleep(0.01)) を追加して、クライアントの負荷を抑える
    • 到着が早すぎる場合は複数のトークンをバッチ処理する
    • 低速クライアントのバックプレッシャ メカニズムの実装を検討する

フィードバック データの分析

フィードバックを収集したら、それを分析して、アプリケーションの品質とユーザーの満足度に関する分析情報を得ることができます。

トレース UI でのフィードバックの表示

トレースアセスメント UI

SDK を使用したフィードバックによるトレースの取得

トレース UI でのフィードバックの表示

追跡フィードバック

SDK を使用したフィードバックによるトレースの取得

まず、特定の時間枠からトレースを取得します。

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

SDK を使用したフィードバック パターンの分析

トレースからフィードバックを抽出して分析します。

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

多次元フィードバックの分析

評価に関するより詳細なフィードバックを得るために評価をしてください。

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

次のステップ

機能参照

このガイドの概念と機能の詳細については、次を参照してください。