Skip to main content
Control plane API는 LangSmith Deployment의 일부입니다. Control plane API를 사용하면 LangGraph Server 배포를 프로그래밍 방식으로 생성, 관리 및 자동화할 수 있습니다. 예를 들어, 사용자 정의 CI/CD 워크플로의 일부로 활용할 수 있습니다.

API Reference

전체 Control Plane API 레퍼런스 문서 보기

호스트

클라우드 데이터 지역별 control plane 호스트:
USEU
https://api.host.langchain.comhttps://eu.api.host.langchain.com
참고: 자체 호스팅되는 LangSmith 배포의 경우 control plane에 대한 사용자 정의 호스트가 있습니다. Control plane API는 /api-host 경로에서 액세스할 수 있습니다. 예를 들어, http(s)://<host>/api-host/v2/deployments입니다. 자세한 내용은 여기를 참조하세요.

인증

Control plane API로 인증하려면 X-Api-Key 헤더를 유효한 LangSmith API 키로 설정하세요. curl 명령어 예시:
curl --request GET \
  --url http://localhost:8124/v2/deployments \
  --header 'X-Api-Key: LANGSMITH_API_KEY'

버전 관리

각 엔드포인트 경로에는 버전(예: v1, v2)이 접두사로 붙습니다.

빠른 시작

  1. POST /v2/deployments를 호출하여 새 Deployment를 생성합니다. 응답 본문에는 Deployment ID(id)와 최신(그리고 첫 번째) 리비전의 ID(latest_revision_id)가 포함됩니다.
  2. GET /v2/deployments/{deployment_id}를 호출하여 Deployment를 조회합니다. URL의 deployment_id를 Deployment ID(id) 값으로 설정합니다.
  3. GET /v2/deployments/{deployment_id}/revisions/{latest_revision_id}를 호출하여 statusDEPLOYED가 될 때까지 리비전 status를 폴링합니다.
  4. PATCH /v2/deployments/{deployment_id}를 호출하여 배포를 업데이트합니다.

예제 코드

다음은 control plane API를 조율하여 배포를 생성, 업데이트 및 삭제하는 방법을 보여주는 Python 예제 코드입니다.
import os
import time

import requests
from dotenv import load_dotenv


load_dotenv()

# 필수 환경 변수
CONTROL_PLANE_HOST = os.getenv("CONTROL_PLANE_HOST")
LANGSMITH_API_KEY = os.getenv("LANGSMITH_API_KEY")
INTEGRATION_ID = os.getenv("INTEGRATION_ID")
MAX_WAIT_TIME = 1800  # 30분


def get_headers() -> dict:
    """control plane API 요청에 대한 공통 헤더를 반환합니다."""
    return {
        "X-Api-Key": LANGSMITH_API_KEY,
    }


def create_deployment() -> str:
    """배포를 생성합니다. 배포 ID를 반환합니다."""
    headers = get_headers()
    headers["Content-Type"] = "application/json"

    deployment_name = "my_deployment"

    request_body = {
        "name": deployment_name,
        "source": "github",
        "source_config": {
            "integration_id": INTEGRATION_ID,
            "repo_url": "https://github.com/langchain-ai/langgraph-example",
            "deployment_type": "dev",
            "build_on_push": False,
            "custom_url": None,
            "resource_spec": None,
        },
        "source_revision_config": {
            "repo_ref": "main",
            "langgraph_config_path": "langgraph.json",
            "image_uri": None,
        },
        "secrets": [
            {
                "name": "OPENAI_API_KEY",
                "value": "test_openai_api_key",
            },
            {
                "name": "ANTHROPIC_API_KEY",
                "value": "test_anthropic_api_key",
            },
            {
                "name": "TAVILY_API_KEY",
                "value": "test_tavily_api_key",
            },
        ],
    }

    response = requests.post(
        url=f"{CONTROL_PLANE_HOST}/v2/deployments",
        headers=headers,
        json=request_body,
    )

    if response.status_code != 201:
        raise Exception(f"Failed to create deployment: {response.text}")

    deployment_id = response.json()["id"]
    print(f"Created deployment {deployment_name} ({deployment_id})")
    return deployment_id


def get_deployment(deployment_id: str) -> dict:
    """배포를 조회합니다."""
    response = requests.get(
        url=f"{CONTROL_PLANE_HOST}/v2/deployments/{deployment_id}",
        headers=get_headers(),
    )

    if response.status_code != 200:
        raise Exception(f"Failed to get deployment ID {deployment_id}: {response.text}")

    return response.json()


def list_revisions(deployment_id: str) -> list[dict]:
    """리비전을 나열합니다.

    반환 목록은 created_at을 기준으로 내림차순(최신순)으로 정렬됩니다.
    """
    response = requests.get(
        url=f"{CONTROL_PLANE_HOST}/v2/deployments/{deployment_id}/revisions",
        headers=get_headers(),
    )

    if response.status_code != 200:
        raise Exception(
            f"Failed to list revisions for deployment ID {deployment_id}: {response.text}"
        )

    return response.json()


def get_revision(
    deployment_id: str,
    revision_id: str,
) -> dict:
    """리비전을 조회합니다."""
    response = requests.get(
        url=f"{CONTROL_PLANE_HOST}/v2/deployments/{deployment_id}/revisions/{revision_id}",
        headers=get_headers(),
    )

    if response.status_code != 200:
        raise Exception(f"Failed to get revision ID {revision_id}: {response.text}")

    return response.json()


def patch_deployment(deployment_id: str) -> None:
    """배포를 패치합니다."""
    headers = get_headers()
    headers["Content-Type"] = "application/json"

    # source_revision_config가 포함되어 있으므로 새 리비전이 생성됩니다
    response = requests.patch(
        url=f"{CONTROL_PLANE_HOST}/v2/deployments/{deployment_id}",
        headers=headers,
        json={
            "source_config": {
                "build_on_push": True,
            },
            "source_revision_config": {
                "repo_ref": "main",
                "langgraph_config_path": "langgraph.json",
            },
        },
    )

    if response.status_code != 200:
        raise Exception(f"Failed to patch deployment: {response.text}")

    print(f"Patched deployment ID {deployment_id}")


def wait_for_deployment(deployment_id: str, revision_id: str) -> None:
    """리비전 상태가 DEPLOYED가 될 때까지 대기합니다."""
    start_time = time.time()
    revision, status = None, None
    while time.time() - start_time < MAX_WAIT_TIME:
        revision = get_revision(deployment_id, revision_id)
        status = revision["status"]
        if status == "DEPLOYED":
            break
        elif "FAILED" in status:
            raise Exception(f"Revision ID {revision_id} failed: {revision}")

        print(f"Waiting for revision ID {revision_id} to be DEPLOYED...")
        time.sleep(60)

    if status != "DEPLOYED":
        raise Exception(
            f"Timeout waiting for revision ID {revision_id} to be DEPLOYED: {revision}"
        )


def delete_deployment(deployment_id: str) -> None:
    """배포를 삭제합니다."""
    response = requests.delete(
        url=f"{CONTROL_PLANE_HOST}/v2/deployments/{deployment_id}",
        headers=get_headers(),
    )

    if response.status_code != 204:
        raise Exception(
            f"Failed to delete deployment ID {deployment_id}: {response.text}"
        )

    print(f"Deployment ID {deployment_id} deleted")


if __name__ == "__main__":
    # 배포를 생성하고 최신 리비전을 가져옵니다
    deployment_id = create_deployment()
    revisions = list_revisions(deployment_id)
    latest_revision = revisions["resources"][0]
    latest_revision_id = latest_revision["id"]

    # 최신 리비전이 DEPLOYED가 될 때까지 대기합니다
    wait_for_deployment(deployment_id, latest_revision_id)

    # 배포를 패치하고 최신 리비전을 가져옵니다
    patch_deployment(deployment_id)
    revisions = list_revisions(deployment_id)
    latest_revision = revisions["resources"][0]
    latest_revision_id = latest_revision["id"]

    # 최신 리비전이 DEPLOYED가 될 때까지 대기합니다
    wait_for_deployment(deployment_id, latest_revision_id)

    # 배포를 삭제합니다
    delete_deployment(deployment_id)

Connect these docs programmatically to Claude, VSCode, and more via MCP for real-time answers.
I