mcpField
O
online-evals

online-evals

github.com/launchdarklyテスト・品質281.8k 回インストール1 日前に更新
リポジトリ

online-evals について

LLM ベースの評価器を LaunchDarkly の設定バリエーションに関連付け、応答を採点するスキルです。評価器の設定、SDK の評価方式、評価指標の確認方法を扱います。

主な機能

  1. 01評価器を設定バリエーションに関連付け
  2. 02組み込み・カスタム評価器の利用
  3. 03評価をサンプリングして応答を採点
  4. 04インラインまたはバックグラウンド評価
  5. 05Monitoring でスコアを確認

活用シーン

  1. 01設定への応答を正確性や関連性で採点
  2. 02カスタム評価器によるドメイン固有基準の評価
  3. 03評価指標を使った設定バリエーションの比較

MCP 対応

LaunchDarkly MCP サーバーのツールを使い、評価器を作成・設定してバリエーションに関連付けます。README では、MCP を使えないスクリプトや CI 向けの REST API 手順も説明しています。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/launchdarkly/ai-tooling --skill online-evals

インストール後、Claude Code で/online-evalsを入力して読み込まれたか確認します。

同梱ファイルREADME.md

詳細

Config のオンライン評価

config のバリエーションにジャッジを関連付け、LLM-as-a-judge の手法を使用して品質を自動採点します。ジャッジは応答を評価し、0.0 から 1.0 のスコアを返します。

前提条件

  • AgentControl が有効になっている LaunchDarkly アカウント
  • 環境に設定済みの、LaunchDarkly のリモートホスト型 MCP サーバー
  • バリエーションを持つ既存の config(configs-create スキルを使用)
  • アプリケーションからジャッジを実行する場合:launchdarkly-ai-server 0.3.0 以降(Python)または @launchdarkly/ai-node 0.3.0 以降(Node.js)、および config とジャッジが使用する各プロバイダー用のプロバイダーハンドラーパッケージ

API アクセス

ワークフローのすべての手順には LaunchDarkly MCP サーバーツールを使用してください。OAuth で認証されるため、トークンは不要です。REST API は、MCP を使用できないスクリプトや CI のみで使用してください(MCP を使わないスクリプトを参照)。その場合は、ユーザーにこのセッション用の API アクセストークンを貼り付けてもらい、以下の例の {api_token} に置き換えてください。環境変数、.env ファイル、エージェント/MCP の設定ファイルから認証情報を検索しないでください。

基本概念

ジャッジとは

ジャッジは、他の config からの応答を評価するジャッジモードの専用 config です。LLM を使って出力を採点し、構造化された結果を返します。

{
  "score": 0.85,
  "reasoning": "Answered correctly with one minor omission"
}
組み込みジャッジ

LaunchDarkly には、事前設定済みのジャッジが 3 つ用意されています。

ジャッジメトリックキー測定内容
正確性$ld:ai:judge:accuracy応答がどれだけ正確で、根拠に基づいているか
関連性$ld:ai:judge:relevanceユーザーのリクエストにどれだけ適切に対応しているか
有害性$ld:ai:judge:toxicity有害または安全でない表現(スコアが低いほど安全)
completion モードのみ

UI でジャッジを関連付けられるのは、completion モードの config のみです。API(ステップ 2)経由で関連付ける場合は、どの config でも使用でき、SDK は配信されたバリエーションに設定されたジャッジを実行します。

制限事項
  • ジャッジにジャッジを関連付けることはできません(再帰は不可)
  • 1 つのバリエーションに、同じメトリックキーを持つ複数のジャッジを関連付けることはできません
  • ジャッジのバリエーションでは、モデルパラメーターやツールを表示/編集できません

ワークフロー

ステップ 1:カスタムジャッジを作成する(任意)

特定のドメイン向けの評価を行うには、create-agentcontrol-config を使ってジャッジ用 config を作成します。

{
  "projectKey": "{projectKey}",
  "key": "security-judge",
  "name": "Security Judge",
  "mode": "judge",
  "evaluationMetricKey": "$ld:ai:judge:security",
  "isInverted": false
}

注: evaluationMetricKey は $ld:ai:judge: で始まる必要があります。有害性など、0.0 のほうが望ましいメトリックには isInverted: true を設定してください。

次に、create-agentcontrol-config-variation を使って評価プロンプトを含むバリエーションを追加します。list-agentcontrol-model-configs から modelConfigKey を取得してください。

{
  "projectKey": "{projectKey}",
  "configKey": "security-judge",
  "key": "default",
  "name": "Default",
  "modelConfigKey": "OpenAI.gpt-4o-mini",
  "modelName": "gpt-4o-mini",
  "parameters": {"temperature": 0.3},
  "messages": [
    {
      "role": "system",
      "content": "You are a security auditor. Score from 0.0 to 1.0:\n- 1.0: No security issues\n- 0.7-0.9: Minor issues\n- 0.4-0.6: Moderate issues\n- 0.1-0.3: Serious vulnerabilities\n- 0.0: Critical vulnerabilities\n\nCheck for: SQL injection, XSS, hardcoded secrets, command injection.\n\n{{message_history}}"
    }
  ]
}

重要: ジャッジのプロンプトは {{message_history}} で終わるようにしてください。SDK はこの変数に評価対象の会話を設定します。そのため、この変数がないプロンプトでは採点対象の応答が参照されません。スコアと理由を出力するための形式は SDK が追加します。

ステップ 2:バリエーションにジャッジを関連付ける

judgeConfiguration を指定して update-agentcontrol-config-variation を使用します。get-agentcontrol-config から variationKey を取得してください。

{
  "projectKey": "{projectKey}",
  "configKey": "{configKey}",
  "variationKey": "{variationKey}",
  "judgeConfiguration": {
    "judges": [
      {"judgeConfigKey": "security-judge", "samplingRate": 1.0},
      {"judgeConfigKey": "api-contract-judge", "samplingRate": 0.5}
    ]
  }
}

重要: judges 配列は、既存のジャッジの関連付けをすべて置き換えます。空の配列を指定すると、すべてのジャッジが削除されます。

ステップ 3:ジャッジにフォールスルーを設定する

各ジャッジ config では、実行する各環境でフォールスルーを有効なバリエーションに設定する必要があります。config のデフォルトは「無効」バリエーションです。update-agentcontrol-config-rollout を使用してください。

{
  "projectKey": "{projectKey}",
  "configKey": "security-judge",
  "env": "production",
  "rolloutType": "variation",
  "variationKey": "default"
}

続いて、ジャッジに対して get-agentcontrol-config-health を実行し、ターゲティング可能であることを確認します。

MCP を使わないスクリプト

CI や MCP サーバーを使用できないスクリプトでは、REST API を通じて同じ 3 つの手順を実行します。config では turnTargetingOn は機能しないため、set_fallthrough は updateFallthroughVariationOrRollout を送信します。

import requests
import os
from typing import Optional

class AIConfigJudges:
    """Manager for config judge attachments"""

    def __init__(self, api_token: str, project_key: str):
        self.api_token = api_token
        self.project_key = project_key
        self.base_url = "https://app.launchdarkly.com/api/v2"
        self.headers = {
            "Authorization": api_token,
            "Content-Type": "application/json",
            "LD-API-Version": "beta"
        }

    def attach_judges(self, config_key: str, variation_key: str,
                      judges: list[dict]) -> dict:
        """
        Attach judges to a variation.

        Args:
            config_key: config key
            variation_key: Variation key
            judges: List of {"judgeConfigKey": str, "samplingRate": float}
        """
        url = f"{self.base_url}/projects/{self.project_key}/ai-configs/{config_key}/variations/{variation_key}"

        response = requests.patch(url, headers=self.headers, json={
            "judgeConfiguration": {"judges": judges}
        })

        if response.status_code == 200:
            print(f"[OK] Attached {len(judges)} judges to {config_key}/{variation_key}")
            return response.json()
        print(f"[ERROR] {response.status_code}: {response.text}")
        return {}

    def create_judge(self, key: str, name: str, metric_key: str,
                     system_prompt: str, model: str = "OpenAI.gpt-4o-mini",
                     is_inverted: bool = False) -> dict:
        """
        Create a judge config.

        Args:
            key: Judge config key
            name: Display name
            metric_key: Metric key for scoring; "security" is sent as "$ld:ai:judge:security"
            system_prompt: Evaluation instructions; {{message_history}} is appended if missing
            is_inverted: True if lower scores are better (e.g., toxicity)
        """
        # The API rejects a bare metric key
        if not metric_key.startswith("$ld:ai:judge:"):
            metric_key = f"$ld:ai:judge:{metric_key}"

        # Create config
        config_url = f"{self.base_url}/projects/{self.project_key}/ai-configs"
        response = requests.post(config_url, headers=self.headers, json={
            "key": key,
            "name": name,
            "mode": "judge",
            "evaluationMetricKey": metric_key,
            "isInverted": is_inverted
        })

        if response.status_code not in [200, 201]:
            print(f"[ERROR] Creating config: {response.text}")
            return {}

        # Without {{message_history}} the judge never sees the conversation it scores
        if "{{message_history}}" not in system_prompt:
            system_prompt = f"{system_prompt}\n\n{{{{message_history}}}}"

        # Create variation
        var_url = f"{self.base_url}/projects/{self.project_key}/ai-configs/{key}/variations"
        response = requests.post(var_url, headers=self.headers, json={
            "key": "default",
            "name": "Default",
            "messages": [{"role": "system", "content": system_prompt}],
            "modelConfigKey": model,
            "model": {"parameters": {"temperature": 0.3}}
        })

        if response.status_code in [200, 201]:
            print(f"[OK] Created judge: {key}")
            return response.json()
        print(f"[ERROR] Creating variation: {response.text}")
        return {}

    def set_fallthrough(self, config_key: str, environment: str,
                        variation_key: str = "default") -> bool:
        """
        Set fallthrough to enable a judge config.

        Note: turnTargetingOn doesn't work for configs. Instead, set the
        fallthrough from disabled (index 0) to the enabled variation.
        """
        # Get variation ID
        url = f"{self.base_url}/projects/{self.project_key}/ai-configs/{config_key}/targeting"
        response = requests.get(url, headers=self.headers)

        if response.status_code != 200:
            print(f"[ERROR] {response.status_code}: {response.text}")
            return False

        targeting = response.json()
        variation_id = None
        for var in targeting.get("variations", []):
            # The variation key is in value._ldMeta.variationKey, not on the item itself
            meta_key = ((var.get("value") or {}).get("_ldMeta") or {}).get("variationKey")
            if meta_key == variation_key or var.get("name") == variation_key:
                variation_id = var.get("_id")
                break

        if not variation_id:
            print(f"[ERROR] Variation '{variation_key}' not found")
            return False

        # Set fallthrough
        response = requests.patch(url, headers={
            **self.headers,
            "Content-Type": "application/json; domain-model=launchdarkly.semanticpatch"
        }, json={
            "environmentKey": environment,
            "instructions": [{
                "kind": "updateFallthroughVariationOrRollout",
                "variationId": variation_id
            }]
        })

        if response.status_code == 200:
            print(f"[OK] Fallthrough set for {config_key}")
            return True
        print(f"[ERROR] {response.status_code}: {response.text}")
        return False

SDK:インライン評価

invoke() は、配信対象のバリエーションにアタッチされたすべての評価器を実行し、それぞれのサンプリング率に従ってサンプリングし、スコアをメトリクスとして記録します。設定と評価器が使用する各プロバイダーのハンドラーを渡してください。設定は補完モードである必要があります。messages ハンドラーはエージェントモードの設定には適合しません。

import asyncio
from launchdarkly_ai_server import config, shutdown
from launchdarkly_ai_openai_messages import create_openai_messages_handler

config_key = "support-agent"  # completion mode

async def main():
    # Reads LD_SDK_KEY and OPENAI_API_KEY from the environment
    result = await config(
        key=config_key,
        handler=create_openai_messages_handler(),
    ).invoke("How can LaunchDarkly help me?", {"kind": "user", "key": "user-123"})

    print(result.response)
    for judge_key, judge in (result.judge_results or {}).items():
        print(judge_key, judge.score)

    # Flushes trailing events, including judge scores
    await shutdown()

asyncio.run(main())
import { config, shutdown } from '@launchdarkly/ai-node';
import { createOpenAIHandler } from '@launchdarkly/ai-openai-messages';

const result = await config({
  key: 'support-agent',
  handler: createOpenAIHandler(),
}).invoke('How can LaunchDarkly help me?', { kind: 'user', key: 'user-123' });

console.log(result.response);
for (const [judgeKey, judge] of Object.entries(result.judgeResults ?? {})) {
  console.log(judgeKey, judge.score);
}

await shutdown();

インライン評価器はリクエストにレイテンシーを追加します。リクエスト処理への影響を避けるには、バックグラウンドで実行してください。

SDK:バックグラウンド評価

skip_judges=True(skipJudges: true)を渡すと、invoke() は評価器を実行せず、サンプリングされた評価器を judge_tasks(judgeTasks)として返します。各タスクをワーカーに渡して run_judge で実行し、自分でスコアを記録してください。run_judge はスコアを記録しないためです。

from ldclient import Context
from launchdarkly_ai_server import config, get_client, run_judge
from launchdarkly_ai_openai_messages import create_openai_messages_handler

handler = create_openai_messages_handler()

config_key = "support-agent"  # completion mode

result = await config(key=config_key, handler=handler, skip_judges=True).invoke(
    "How can LaunchDarkly help me?", {"kind": "user", "key": "user-123"}
)

# In your worker:
for task in result.judge_tasks or []:
    judged = await run_judge(task, [handler])
    if judged and task.evaluation_metric_key:
        get_client().track(
            task.evaluation_metric_key,
            Context.from_dict(task.user_context),
            judged.track_data,
            judged.score,
        )
import { config, getClient, runJudge } from '@launchdarkly/ai-node';
import { createOpenAIHandler } from '@launchdarkly/ai-openai-messages';

const handler = createOpenAIHandler();

const result = await config({ key: 'support-agent', handler, skipJudges: true }).invoke(
  'How can LaunchDarkly help me?',
  { kind: 'user', key: 'user-123' },
);

// In your worker:
for (const task of result.judgeTasks ?? []) {
  const judged = await runJudge(task, [handler]);
  if (judged && task.evaluationMetricKey) {
    getClient().track(task.evaluationMetricKey, task.userContext, judged.trackData, judged.score);
  }
}

注: Python では、task.user_context はプレーンな dict なので、track() を呼び出す前に Context.from_dict() で変換してください。track_data(trackData)には、すでに judgeConfigKey が含まれています。

SDK:カスタムパイプライン

アプリケーションがモデルを直接呼び出す場合は、その呼び出しを create_handler(createHandler)でハンドラーにラップしてください。すると invoke() は、組み込みハンドラーと同様に、コードとアタッチされた評価器を実行します。評価器にも、それぞれのプロバイダー用のハンドラーが必要です。

from launchdarkly_ai_server import config, create_handler, parse_template
from launchdarkly_ai_openai_messages import create_openai_messages_handler

config_key = "support-agent"  # completion mode

# run_my_agent is your existing pipeline; it returns the text and the provider's usage object
async def my_pipeline(ld_config, user_input, tool_handlers, variables, history):
    # Custom handlers fill {{name}} placeholders themselves
    messages = [{**m, "content": parse_template(m["content"], variables)} for m in ld_config.get("messages", [])]
    output, usage = await run_my_agent(messages, user_input)
    return {"output": output, "usage": {"input": usage.input_tokens, "output": usage.output_tokens}}

handlers = [
    create_handler(("Anthropic", "messages"), my_pipeline),  # the config's provider and mode
    create_openai_messages_handler(),                       # the judge's provider
]

result = await config(key=config_key, handler=handlers).invoke(user_input, context)

create_handler のプロバイダー名は、大文字と小文字も含めて設定のモデルプロバイダーと完全に一致している必要があります。また、最初の引数はタプルでなければなりません。ハンドラーの仕様全体については、tools スキルを参照してください。

サンプリング率

評価対象の応答ごとに、モデルプロバイダーへのリクエストが追加で送信されるため、トークン使用量とコストが増加します。まずは低いサンプリング率から始め、評価範囲を広げる必要がある場合にのみ引き上げてください。

バリエーションの Judges セクションから、いつでもサンプリング率を調整できます。また、サンプリング率を 0% に設定すると、評価器を無効にできます。

結果の表示

  1. configs に移動し、設定を選択します
  2. Monitoring タブをクリックします
  3. ドロップダウンから Evaluator metrics を選択します
  4. バリエーション別、期間別にスコアを確認します

評価結果は、評価から 1~2 分以内に表示されます。

ガードレールと実験での使用

評価メトリクスは、次の機能と連携します。

  • ガード付きロールアウト:スコアがしきい値を下回った場合に、一時停止またはロールバックします
  • 実験:評価メトリクスを目標として、バリエーションを比較します

エラー処理

ステータス原因解決方法
404設定/バリエーションが見つかりませんキーが存在することを確認してください
400評価器の設定が無効ですjudgeConfigKey が存在することを確認してください
403権限が不足していますMCP サーバーを再接続するか、API トークンの権限を確認してください
422メトリクスキーが重複しています同じメトリクスキーを持つ複数の評価器はアタッチできません

次のステップ

評価器をアタッチした後は、次の操作を行います。

  1. 評価器の設定で、有効なバリエーションへの フォールスルーを設定 します(必須)
  2. Monitoring タブで 結果を監視 します
  3. コストと評価範囲の要件に応じて サンプリングを調整 します
  4. 回帰を自動検出するために ガード付きロールアウトを設定 します

関連スキル

  • configs-create - 設定と評価器を作成します
  • configs-targeting - ターゲティングルールを設定します
  • configs-variations - バリエーションを管理します

参考資料

コメント