online-evals について
LLM ベースの評価器を LaunchDarkly の設定バリエーションに関連付け、応答を採点するスキルです。評価器の設定、SDK の評価方式、評価指標の確認方法を扱います。
主な機能
- 01評価器を設定バリエーションに関連付け
- 02組み込み・カスタム評価器の利用
- 03評価をサンプリングして応答を採点
- 04インラインまたはバックグラウンド評価
- 05Monitoring でスコアを確認
活用シーン
- 01設定への応答を正確性や関連性で採点
- 02カスタム評価器によるドメイン固有基準の評価
- 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-server0.3.0 以降(Python)または@launchdarkly/ai-node0.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% に設定すると、評価器を無効にできます。
結果の表示
- configs に移動し、設定を選択します
- Monitoring タブをクリックします
- ドロップダウンから Evaluator metrics を選択します
- バリエーション別、期間別にスコアを確認します
評価結果は、評価から 1~2 分以内に表示されます。
ガードレールと実験での使用
評価メトリクスは、次の機能と連携します。
- ガード付きロールアウト:スコアがしきい値を下回った場合に、一時停止またはロールバックします
- 実験:評価メトリクスを目標として、バリエーションを比較します
エラー処理
| ステータス | 原因 | 解決方法 |
|---|---|---|
| 404 | 設定/バリエーションが見つかりません | キーが存在することを確認してください |
| 400 | 評価器の設定が無効です | judgeConfigKey が存在することを確認してください |
| 403 | 権限が不足しています | MCP サーバーを再接続するか、API トークンの権限を確認してください |
| 422 | メトリクスキーが重複しています | 同じメトリクスキーを持つ複数の評価器はアタッチできません |
次のステップ
評価器をアタッチした後は、次の操作を行います。
- 評価器の設定で、有効なバリエーションへの フォールスルーを設定 します(必須)
- Monitoring タブで 結果を監視 します
- コストと評価範囲の要件に応じて サンプリングを調整 します
- 回帰を自動検出するために ガード付きロールアウトを設定 します
関連スキル
configs-create- 設定と評価器を作成しますconfigs-targeting- ターゲティングルールを設定しますconfigs-variations- バリエーションを管理します