피드백과 어노테이션 큐 프로그래매틱하게 관리하기

피드백과 어노테이션 큐 프로그래매틱하게 관리하기

LangSmith SDK를 사용하면 피드백 구성과 어노테이션 큐 루브릭을 프로그래매틱하게 관리하고, 검토를 위해 런과 스레드를 큐에 추가할 수 있어요. 조직 수준에서 재사용 가능한 피드백 스키마(예: 정확도 점수, 통과/실패 판정)를 정의한 뒤, 커스텀 지침과 함께 특정 큐에 할당할 수 있답니다. 이를 통해 버전 관리, 프로젝트 전반의 자동화, 일관성을 얻을 수 있어서 CI/CD 파이프라인이나 여러 환경 간 평가 구성을 복제할 때 특히 유용해요.

출처: 문서

본문

LangSmith SDK를 사용해 피드백 구성과 어노테이션 큐 루브릭을 프로그래매틱하게 관리하고, 검토를 위해 런과 스레드를 큐에 추가하세요. 조직 수준에서 재사용 가능한 피드백 스키마(예: 정확도 점수 또는 통과/실패 판정)를 정의한 다음, 커스텀 지침과 함께 특정 큐에 할당하세요. 이를 통해 버전 제어, 프로젝트 간 자동화, 일관성을 확보할 수 있습니다—특히 CI/CD 파이프라인이나 여러 환경에서 평가 구성을 복제할 때 유용합니다.

코드: 이 가이드는 Python과 TypeScript SDK를 사용합니다. 설치 및 설정은 Python SDK 문서TypeScript SDK 문서를 참고하세요.

참고: LangSmith UI에서 검토하면서 개별 런에 자유 형식 수용 기준을 작성하려면 Assertions 사용을 참고하세요.

피드백 레이어

LangSmith는 구조화된 인간 피드백을 위해 3계층 아키텍처를 사용합니다:

  1. 피드백 구성(Feedback configs): 평가 메트릭의 스키마를 설정하는 조직 전반의 피드백 키 정의입니다. 예를 들어 "accuracy"를 연속 0–1 점수로, "correctness"를 통과/실패 범주형 선택으로 정의할 수 있습니다. 이 구성은 조직의 모든 어노테이션 큐에서 재사용할 수 있습니다.
  2. 어노테이션 큐 루브릭 항목: 특정 큐에서 을 검토할 때 어노테이터가 작성해야 하는 피드백 구성을 결정하는 큐별 할당입니다. 각 루브릭 항목에는 커스텀 설명, 특정 점수 값에 대한 지침, 피드백 필수/선택 여부를 포함할 수 있습니다.
  3. 피드백(Feedback): 어노테이터가 특정 에 제출하는 개별 점수와 값입니다. 정의한 스키마를 사용해 수집된 실제 평가 데이터입니다. LangSmith의 피드백에 대해 자세히 알아보세요.

피드백 구성

피드백 구성 만들기

피드백 구성은 피드백 키의 스키마를 정의합니다—연속 점수, 범주형 선택, 또는 자유 형식 텍스트. 고유한 키는 조직 내에서 각 구성을 식별하고, 어노테이터가 해당 메트릭에 대해 피드백을 제출하는 방식을 지정합니다.

참고: 이미 존재하는 동일한 구성으로 create_feedback_config를 호출하면 기존 구성을 반환합니다. 같은 키에 다른 구성이 이미 존재하면 시스템은 400 오류를 발생시킵니다.

from langsmith import Client

client = Client()

# Continuous score
client.create_feedback_config(
    "accuracy",
    feedback_config={
        "type": "continuous",
        "min": 0,
        "max": 1,
    },
    is_lower_score_better=False,
)

# Categorical
client.create_feedback_config(
    "correctness",
    feedback_config={
        "type": "categorical",
        "categories": [
            {"value": 1, "label": "Pass"},
            {"value": 0, "label": "Fail"},
        ],
    },
)

# Freeform text
client.create_feedback_config(
    "notes",
    feedback_config={"type": "freeform"},
)
import { Client } from "langsmith";

const client = new Client();

// Continuous score
await client.createFeedbackConfig({
  feedbackKey: "accuracy",
  feedbackConfig: { type: "continuous", min: 0, max: 1 },
  isLowerScoreBetter: false,
});

// Categorical
await client.createFeedbackConfig({
  feedbackKey: "correctness",
  feedbackConfig: {
    type: "categorical",
    categories: [
      { value: 1, label: "Pass" },
      { value: 0, label: "Fail" },
    ],
  },
});

// Freeform text
await client.createFeedbackConfig({
  feedbackKey: "notes",
  feedbackConfig: { type: "freeform" },
});
  • Continuous ("accuracy"): 0부터 1까지의 숫자 척도를 정의합니다. is_lower_score_better 매개변수는 낮은 값이 더 나은 성능을 나타내는지 여부를 표시합니다. 등급 척도나 백분율 기반 메트릭에 연속 구성을 사용하세요.
  • Categorical ("correctness"): 연결된 값을 가진 미리 정의된 옵션을 제공합니다. 각 범주에는 (점수 및 분석에 사용되는) value와 (어노테이터에게 표시되는) label이 필요합니다. 이진 선택이나 다중 클래스 분류에 범주형 구성을 사용하세요.
  • Freeform ("notes"): 미리 정의된 구조가 없는 개방형 텍스트 입력을 허용합니다. 정성적 관찰이나 설명에 자유 형식 구성을 사용하세요.

피드백 구성 나열

list_feedback_configs로 피드백 구성을 가져와 조직에서 사용 가능한 평가 기준을 확인하세요. 모든 구성을 나열하거나 특정 키로 필터링할 수 있습니다. 반환된 각 구성 객체에는 키, 유형, 구성 세부 정보(min/max 또는 categories 등), 그리고 is_lower_score_better 같은 메타데이터가 포함됩니다:

# List all configs
for config in client.list_feedback_configs():
    print(f"{config.feedback_key}: {config.feedback_config}")

# Filter by specific keys
for config in client.list_feedback_configs(
    feedback_key=["accuracy", "correctness"]
):
    print(config.feedback_key)
// List all configs
for await (const config of client.listFeedbackConfigs()) {
  console.log(`${config.feedback_key}: ${JSON.stringify(config.feedback_config)}`);
}

// Filter by specific keys
for await (const config of client.listFeedbackConfigs({
  feedbackKeys: ["accuracy", "correctness"],
})) {
  console.log(config.feedback_key);
}

피드백 구성 업데이트

update_feedback_config로 특정 필드를 업데이트해 기존 피드백 구성을 수정합니다. 이 메서드는 제공한 필드만 변경하고 나머지는 그대로 둡니다. 다른 구성 설정을 보존하는 부분 업데이트입니다:

client.update_feedback_config(
    "accuracy",
    is_lower_score_better=True,
)
await client.updateFeedbackConfig("accuracy", {
  isLowerScoreBetter: true,
});

피드백 구성 삭제

delete_feedback_config로 조직에서 피드백 구성을 제거합니다. 이는 소프트 삭제를 수행하며, 구성을 삭제된 것으로 표시하지만 시스템에서 영구적으로 제거하지는 않습니다. 필요하면 나중에 같은 키로 구성을 다시 만들 수 있습니다:

client.delete_feedback_config("accuracy")
await client.deleteFeedbackConfig("accuracy");

어노테이션 큐 루브릭 항목

루브릭 항목은 피드백 구성을 특정 어노테이션 큐에 할당합니다. 어노테이터가 해당 큐의 을 검토할 때 보는 피드백 양식과, 각 양식이 필수인지 선택인지를 제어합니다.

루브릭 항목으로 큐 만들기

create_annotation_queue로 어노테이션 큐를 만들고 루브릭 항목을 통해 피드백 구성을 할당합니다. 각 루브릭 항목은 키로 피드백 구성을 참조하고, 이 특정 큐에서 어노테이터에게 어떻게 보일지 커스터마이즈합니다.

예시는 세 개의 루브릭 항목으로 큐를 만듭니다. 큐 수준의 rubric_instructions는 어노테이션 인터페이스 상단에 표시되는 일반 지침을 제공합니다:

queue = client.create_annotation_queue(
    name="QA Review Queue",
    description="Review LLM outputs for accuracy and correctness",
    rubric_instructions="Score each response. Add notes for anything unusual.",
    rubric_items=[
        {
            "feedback_key": "accuracy",
            "description": "How accurate is the response?",
            "score_descriptions": {
                "0": "Completely wrong",
                "1": "Perfectly accurate",
            },
            "is_required": True,
        },
        {
            "feedback_key": "correctness",
            "description": "Did the response pass or fail?",
            "value_descriptions": {
                "Pass": "Factually correct",
                "Fail": "Contains errors",
            },
            "is_required": True,
        },
        {
            "feedback_key": "notes",
            "description": "Any additional observations",
            "is_required": False,
        },
    ],
)
const queue = await client.createAnnotationQueue({
  name: "QA Review Queue",
  description: "Review LLM outputs for accuracy and correctness",
  rubricInstructions: "Score each response. Add notes for anything unusual.",
  rubricItems: [
    {
      feedback_key: "accuracy",
      description: "How accurate is the response?",
      score_descriptions: { "0": "Completely wrong", "1": "Perfectly accurate" },
      is_required: true,
    },
    {
      feedback_key: "correctness",
      description: "Did the response pass or fail?",
      value_descriptions: { Pass: "Factually correct", Fail: "Contains errors" },
      is_required: true,
    },
    {
      feedback_key: "notes",
      description: "Any additional observations",
      is_required: false,
    },
  ],
});
  • feedback_key: 기존 피드백 구성의 키 (먼저 생성하세요).
  • description: 이 메트릭에 대한 어노테이터를 위한 큐별 지침.
  • score_descriptions / value_descriptions: 특정 값이 무엇을 의미하는지 설명하는 선택적 라벨 (연속 구성에는 score_descriptions, 범주형에는 value_descriptions 사용).
  • is_required: 어노테이터가 제출 전에 이 피드백을 완료해야 하는지 여부.

기존 큐의 루브릭 항목 업데이트

update_annotation_queue로 어노테이션 큐에 할당된 루브릭 항목을 수정합니다. 이 작업은 전체 루브릭 항목 목록을 교체하므로, 유지하려는 모든 항목을 포함해야 합니다—포함하지 않은 항목은 제거됩니다.

큐를 만들 때 얻거나 큐를 나열해 얻은 큐 ID가 필요합니다:

참고: 루브릭 항목을 업데이트하면 전체 목록이 교체됩니다. 유지하려는 모든 항목을 포함하세요.

client.update_annotation_queue(
    queue.id,
    rubric_items=[
        {"feedback_key": "accuracy", "is_required": True},
        {"feedback_key": "correctness", "is_required": True},
        {
            "feedback_key": "tone",
            "description": "Is the tone appropriate?",
            "is_required": False,
        },
    ],
)
await client.updateAnnotationQueue(queue.id, {
  rubricItems: [
    { feedback_key: "accuracy", is_required: true },
    { feedback_key: "correctness", is_required: true },
    { feedback_key: "tone", description: "Is the tone appropriate?", is_required: false },
  ],
});

큐에 런과 스레드 추가

어노테이션 큐 items 리소스로 단일 런 어노테이션 큐에 스레드를 추가하세요. 단일 요청은 런 항목과 스레드 항목의 혼합 배치를 허용하므로, 이 메서드는 UI 추가 흐름이 지원하는 모든 것을 포함합니다.

참고: items 리소스는 Python의 경우 langsmith>=0.10.13, TypeScript의 경우 langsmith>=0.8.8 이상이 필요하며, 0.16.14 이상 버전의 LangSmith 백엔드가 제공합니다.

각 항목은 item_typeRUN 또는 THREAD로 설정합니다:

  • RUN 항목run_id가 필요합니다. 또한 런을 직접 찾을 수 있게 하는 project_id(프로젝트 UUID)와 start_time도 제공하세요.
  • THREAD 항목thread_idproject_id가 필요합니다.

이 ID들을 SDK로 해결하세요:

  • project_id: read_project로 이름으로 프로젝트를 찾아 id를 읽습니다. 예: client.read_project(project_name="my-project").id.
  • run_id: client.runs.query()로 런을 쿼리합니다. 각 런은 RUN 항목에 필요한 id, project_id, start_time 필드를 노출합니다.
  • thread_id: client.threads.query()로 스레드를 쿼리합니다. 각 결과는 thread_id를 노출합니다.

LangSmith UI에서도 이 ID들을 찾을 수 있습니다:

  • project_id: 트레이싱 프로젝트에서 프로젝트 이름 옆의 ID 배지를 클릭해 프로젝트 UUID를 복사합니다.
  • run_id: Details 뷰에서 런을 열고 런 이름 옆의 ID 배지를 클릭해 런 ID를 복사합니다.
  • thread_id: 트레이싱 프로젝트의 Threads 뷰에서 Thread ID 열에서 값을 복사합니다.

추가된 런 항목의 트레이스 보존 기간을 연장하려면 extend_trace_retention=True를 전달하세요. 응답은 추가된 항목당 하나의 항목이 있는 items 배열을 포함한 봉투(envelope)입니다.

참고: Python에서 annotation_queues.items.create는 비동기이므로 이벤트 루프 안에서 await하세요.

import asyncio

async def main():
    queue_name = "<queue_name>"
    project_id = "<project_id>"
    run_id = "<run_id>"
    thread_id = "<thread_id>"

    # Look up the annotation queue by name to get its ID.
    queue = next(client.list_annotation_queues(name=queue_name), None)
    if queue is None:
        raise SystemExit(f"No annotation queue named {queue_name!r}")

    # Fetch the run to add by id + project_id. runs.retrieve() requires
    # `selects` (uppercase RunSelectField names) to return fields like
    # start_time.
    run = await client.runs.retrieve(
        run_id=run_id,
        project_id=project_id,
        selects=["ID", "PROJECT_ID", "START_TIME"],
    )

    response = await client.annotation_queues.items.create(
        str(queue.id),
        items=[
            {
                "item_type": "RUN",
                "run_id": run_id,
                "project_id": project_id,
                "start_time": run.start_time,
            },
            {
                "item_type": "THREAD",
                "thread_id": thread_id,
                "project_id": project_id,
            },
        ],
    )

    # response.items contains one entry per added item.
    for item in response.items or []:
        print(item.id, item.item_type)

asyncio.run(main())
const queueName = "<queue_name>";
const projectId = "<project_id>";
const runId = "<run_id>";
const threadId = "<thread_id>";

// Look up the annotation queue by name to get its ID.
let queue;
for await (const q of client.listAnnotationQueues({ name: queueName })) {
  queue = q;
  break;
}
if (!queue) {
  throw new Error(`No annotation queue named ${queueName}`);
}

// Fetch the run to add by id + project_id. retrieve() requires `selects`
// (uppercase RunSelectField names) to return fields like start_time.
const run = await client.runs.retrieve(runId, {
  project_id: projectId,
  selects: ["ID", "PROJECT_ID", "START_TIME"],
});

const response = await client.annotationQueues.items.create(queue.id, {
  items: [
    {
      item_type: "RUN",
      run_id: runId,
      project_id: projectId,
      start_time: run.start_time,
    },
    {
      item_type: "THREAD",
      thread_id: threadId,
      project_id: projectId,
    },
  ],
});

// response.items contains one entry per added item.
for (const item of response.items ?? []) {
  console.log(item.id, item.item_type);
}

참고: 런만 추가하는 경우 add_runs_to_annotation_queue도 계속 작동하며 스레드를 추가하지 않을 때 가장 간단한 옵션입니다. 스레드 또는 혼합 런/스레드 배치를 추가하는 새 코드는 items 리소스를 사용해야 합니다.

피드백 구성 유형 (상세)

Continuous

연속 구성은 최소값과 최대값을 가진 숫자 등급 척도를 정의합니다. 어노테이터는 범위 내에서 아무 값이나 선택할 수 있어, 정확도, 품질, 관련성 같은 차원을 숫자 척도로 평가하는 데 이상적입니다:

# Simple continuous score
client.create_feedback_config(
    "accuracy",
    feedback_config={
        "type": "continuous",
        "min": 0,
        "max": 1,
    },
)

# Continuous with labeled points on the scale
client.create_feedback_config(
    "quality",
    feedback_config={
        "type": "continuous",
        "min": 1,
        "max": 5,
        "categories": [
            {"value": 1, "label": "Poor"},
            {"value": 3, "label": "Average"},
            {"value": 5, "label": "Excellent"},
        ],
    },
)
await client.createFeedbackConfig({
  feedbackKey: "accuracy",
  feedbackConfig: { type: "continuous", min: 0, max: 1 },
});

await client.createFeedbackConfig({
  feedbackKey: "quality",
  feedbackConfig: {
    type: "continuous",
    min: 1,
    max: 5,
    categories: [
      { value: 1, label: "Poor" },
      { value: 3, label: "Average" },
      { value: 5, label: "Excellent" },
    ],
  },
});

첫 번째 예시는 라벨 없는 0–1 척도를 보여줍니다. 두 번째 예시는 어노테이터가 서로 다른 값이 무엇을 의미하는지 이해하도록 척도에 라벨이 지정된 기준점("Poor", "Average", "Excellent")이 있는 categories를 추가하는 방법을 보여줍니다. 이 라벨들은 선택 사항이지만 어노테이터가 척도를 해석하는 방식의 일관성을 높일 수 있습니다.

Categorical

범주형 구성은 어노테이터가 선택할 수 있는 미리 정의된 개별 옵션 세트를 제공합니다. 각 범주에는 (점수 및 분석에 사용되는 숫자 식별자) value와 (어노테이터에게 표시되는 텍스트) label이 있어야 합니다. 최소 2개의 범주를 정의해야 합니다.

범주형 구성을 이진 결정(통과/실패, 정답/오답), 다중 클래스 분류(감정, 주제 범주), 또는 고정된 개별 옵션 세트가 있는 모든 평가에 사용하세요. 범주형 구성에는 min 또는 max를 설정하지 마세요:

# Binary pass/fail
client.create_feedback_config(
    "correctness",
    feedback_config={
        "type": "categorical",
        "categories": [
            {"value": 1, "label": "Pass"},
            {"value": 0, "label": "Fail"},
        ],
    },
)

# Multi-class
client.create_feedback_config(
    "sentiment",
    feedback_config={
        "type": "categorical",
        "categories": [
            {"value": 0, "label": "Negative"},
            {"value": 1, "label": "Neutral"},
            {"value": 2, "label": "Positive"},
        ],
    },
)
await client.createFeedbackConfig({
  feedbackKey: "correctness",
  feedbackConfig: {
    type: "categorical",
    categories: [
      { value: 1, label: "Pass" },
      { value: 0, label: "Fail" },
    ],
  },
});

await client.createFeedbackConfig({
  feedbackKey: "sentiment",
  feedbackConfig: {
    type: "categorical",
    categories: [
      { value: 0, label: "Negative" },
      { value: 1, label: "Neutral" },
      { value: 2, label: "Positive" },
    ],
  },
});

첫 번째 예시는 이진 통과/실패 구성을 보여줍니다. 두 번째 예시는 세 가지 옵션이 있는 감정에 대한 다중 클래스 구성을 보여줍니다. 숫자 값을 사용하면 범주형 피드백에서도 집계 점수를 계산할 수 있습니다.

Freeform

자유 형식 구성은 어노테이터가 미리 정의된 구조나 제약 없이 개방형 텍스트 피드백을 제공할 수 있게 합니다. 이 유형에는 min, max 또는 categories 필드가 없습니다—어노테이터는 원하는 어떤 텍스트든 입력할 수 있습니다.

자유 형식 피드백은 미묘한 인사이트를 포착하는 데 유용하지만, 구조화된 피드백 유형에 비해 집계하고 분석하기가 더 어렵습니다:

client.create_feedback_config(
    "notes",
    feedback_config={"type": "freeform"},
)
await client.createFeedbackConfig({
  feedbackKey: "notes",
  feedbackConfig: { type: "freeform" },
});

검증 규칙

유형 min/max categories 제약
continuous 선택사항 선택사항 (라벨 지정 척도 점) min < max; 범주 값은 [min, max] 범위 내
categorical 설정하면 안 됨 필수, 최소 2개 고유한 값과 라벨
freeform 설정하면 안 됨 설정하면 안 됨 해당 없음

레퍼런스

피드백 구성 유형

유형 필드 설명
continuous min, max 범위 내 숫자 점수
categorical categories ({value, label} 목록) 미리 정의된 옵션에서 선택
freeform 없음 자유 텍스트 입력

루브릭 항목 필드

필드 유형 설명
feedback_key string 필수. 기존 피드백 구성 키와 일치해야 합니다.
description string 이 항목에 대한 지침을 어노테이터에게 보여줍니다.
score_descriptions Record<string, string> 특정 점수 값에 대한 라벨 (continuous).
value_descriptions Record<string, string> 특정 범주 값에 대한 라벨 (categorical).
is_required boolean 어노테이터가 제출 전에 이 항목을 완료해야 하는지 여부. 기본값은 false.

더 알아보기

  • 어노테이션 큐의 UI 사용법은 Use annotation queues 문서를 참고하세요.
  • LangSmith 피드백 개념은 Feedback 문서를 확인해 보세요.