런 취소하기

런 취소하기

API를 통해 단일 런 또는 여러 런을 취소하고, interrupt와 rollback 동작 중에서 선택할 수 있어요. 이 가이드는 LangSmith Deployment API를 통해 에이전트의 런을 취소하는 방법을 다룹니다. ID로 단일 런을 취소하거나, 스레드 또는 상태로 여러 런을 취소할 수 있어요. 오래 걸리는 런이나 멈춘 런을 중지하거나, 사용자가 요청을 중단했을 때 특히 유용합니다.

출처: 문서

본문

이 가이드는 LangSmith Deployment API를 통해 에이전트의 런을 취소하는 방법을 다룹니다. ID로 단일 런을 취소하거나 스레드/상태로 여러 런을 취소할 수 있습니다. 취소는 오래 실행되거나 멈춘 런을 중지하거나, 사용자가 요청을 중단했을 때 유용합니다.

설정

클라이언트와 스레드를 만듭니다:

Python

from langgraph_sdk import get_client

client = get_client(url=<DEPLOYMENT_URL>)
assistant_id = "agent"
thread = await client.threads.create()

Javascript

import { Client } from "@langchain/langgraph-sdk";

const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
const assistantID = "agent";
const thread = await client.threads.create();

cURL

curl --request POST \
  --url <DEPLOYMENT_URL>/threads \
  --header 'Content-Type: application/json' \
  --data '{}'

단일 런 취소

다음 예제들은 런을 만들고, 다양한 옵션으로 취소하며, 각 경우에 무엇을 얻는지 보여주기 위해 런을 출력합니다. pending 또는 running 상태의 런을 취소할 수 있습니다. pending 또는 running 상태가 아닌 런을 취소하려고 하면 오류가 발생합니다.

interrupt로 취소 (기본값)

Interrupt는 런을 실행하는 작업자를 중지하고 런을 interrupted로 표시합니다. 아무것도 삭제되지 않습니다:

  • 런 레코드는 (interrupted 상태로) 유지됩니다. 가져와서 입력/출력을 검사하고 실행 기록을 볼 수 있습니다.
  • 해당 런의 모든 체크포인트는 저장된 채로 유지됩니다. 마지막 완료 단계의 스레드 상태가 보존됩니다.
  • 나중에 체크포인트에서 재개할 수 있거나(예: time travel), 부분 상태를 검사할 수 있습니다.

런을 중지하되 디버깅, 감사 또는 체크포인트에서 재개를 위해 유지하려면 interrupt를 사용하세요.

Python

run = await client.runs.create(
    thread["thread_id"],
    assistant_id,
    input={"messages": [{"role": "user", "content": "Long task"}]},
)
await client.runs.cancel(thread["thread_id"], run["run_id"])

run_after = await client.runs.get(thread["thread_id"], run["run_id"], wait=True)
print(run_after["status"])   # "interrupted"

Javascript

const run = await client.runs.create(
    thread["thread_id"],
    assistantID,
    { input: { messages: [{ role: "user", content: "Long task" }] } }
);
await client.runs.cancel(thread["thread_id"], run["run_id"], wait=true);

const runAfter = await client.runs.get(thread["thread_id"], run["run_id"]);
console.log(runAfter["status"]);   // "interrupted"

cURL

# Create a run (use the run_id and thread_id from the response)
curl --request POST \
  --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs \
  --header 'Content-Type: application/json' \
  --data '{"assistant_id": "agent", "input": {"messages": [{"role": "user", "content": "Summarize the docs"}]}}'

# Cancel with default action (interrupt)
curl --request POST \
  --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/<RUN_ID>/cancel?wait=true

# Get the run to see status "interrupted" and that the run still exists
curl --request GET \
  --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/<RUN_ID>

rollback으로 취소

rollback은 런을 중지한 다음 그 런과 체크포인트를 저장소에서 제거합니다:

  • 런 레코드가 삭제됩니다. 런은 해당 스레드의 런 목록이나 기록에 더 이상 나타나지 않습니다.
  • 해당 런이 만든 모든 체크포인트가 삭제됩니다. 스레드 상태는 런이 시작되기 전 상태로 되돌아갑니다 (마치 런이 실행된 적이 없는 것처럼).
  • rollback 후에는 런을 재개하거나 검사할 수 없습니다.

런과 그 효과를 완전히 버리려면(예: 사용자가 요청을 중단하고 부분 작업을 유지할 필요가 없을 때) rollback을 사용하세요.

Python

run = await client.runs.create(
    thread["thread_id"],
    assistant_id,
    input={"messages": [{"role": "user", "content": "Long task"}]},
)
await client.runs.cancel(thread["thread_id"], run["run_id"], action="rollback", wait=True)

# Throws an error because the run is deleted
try:
    await client.runs.get(thread["thread_id"], run["run_id"])
except Exception:
    print("Run was correctly deleted")

Javascript

const run = await client.runs.create(
    thread["thread_id"],
    assistantID,
    { input: { messages: [{ role: "user", content: "Long task" }] } }
);
await client.runs.cancel(thread["thread_id"], run["run_id"], wait=true, action="rollback");

// Throws an error because the run is deleted
try {
    await client.runs.get(thread["thread_id"], run["run_id"]);
} catch (e) {
    console.log("Run was correctly deleted");
}

cURL

# Create a run, then cancel with rollback
curl --request POST \
  --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs \
  --header 'Content-Type: application/json' \
  --data '{"assistant_id": "agent", "input": {"messages": [{"role": "user", "content": "Summarize the docs"}]}}'

curl --request POST \
  --url "<DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/<RUN_ID>/cancel?action=rollback"

# Throws an error because the run is deleted
curl --request GET \
  --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/<RUN_ID>

wait로 취소

기본적으로 취소 요청은 취소가 요청된 후 반환되며 런은 비동기적으로 취소됩니다. wait=True는 취소 요청이 런이 완전히 취소될 때까지 블로킹되게 합니다. 취소된 후 런의 최종 상태를 알고 싶을 때 유용합니다(예: 어떤 체크포인트가 생성되었는지, 최종 출력이 무엇인지).

Python

run = await client.runs.create(
    thread["thread_id"],
    assistant_id,
    input={"messages": [{"role": "user", "content": "Long task"}]},
)
# Cancel the run asynchronously
await client.runs.cancel(thread["thread_id"], run["run_id"])
# Get the status of the run
run_after = await client.runs.get(thread["thread_id"], run["run_id"])
print(run_after["status"])  # "pending" or "running"

# Wait for the run to be properly cancelled
await client.runs.join(thread["thread_id"], run["run_id"])
run_after = await client.runs.get(thread["thread_id"], run["run_id"])
print(run_after["status"])  # "interrupted"

Javascript

const run = await client.runs.create(
    thread["thread_id"],
    assistantID,
    { input: { messages: [{ role: "user", content: "Long task" }] } }
);
// Cancel the run asynchronously
await client.runs.cancel(thread["thread_id"], run["run_id"]);
// Get the status of the run
const runRunning = await client.runs.get(thread["thread_id"], run["run_id"])
console.log(runRunning["status"])  // "pending" or "running"

// Wait for the run to be properly cancelled
await client.runs.join(thread["thread_id"], run["run_id"])
const runInterrupted = await client.runs.get(thread["thread_id"], run["run_id"])
console.log(runInterrupted["status"])  // "interrupted"

cURL

# Create a run
curl --request POST \
  --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs \
  --header 'Content-Type: application/json' \
  --data '{"assistant_id": "agent", "input": {"messages": [{"role": "user", "content": "Summarize the docs"}]}}'

# Cancel the run asynchronously
curl --request POST \
  --url "<DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/<RUN_ID>/cancel"

# Get the status of the run, should be "pending" or "running" until cancellation completes, then "interrupted"
curl --request GET \
  --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/<RUN_ID>

여러 런 취소

벌크 취소 엔드포인트를 사용해 한 요청으로 여러 런을 취소합니다. interrupt와 rollback 동작이 모두 지원됩니다.

스레드 ID와 런 ID로 취소

ID를 전달해 특정 런들을 취소합니다.

Python

run1 = await client.runs.create(
    thread["thread_id"],
    assistant_id,
    input={"messages": [{"role": "user", "content": "First request"}]},
)
run2 = await client.runs.create(
    thread["thread_id"],
    assistant_id,
    input={"messages": [{"role": "user", "content": "Second request"}]},
    multitask_strategy="enqueue",
)

await client.runs.cancel_many(
    thread_id=thread["thread_id"],
    run_ids=[run1["run_id"], run2["run_id"]]
)

# Wait for the runs to be cancelled
await client.runs.join(thread["thread_id"], run2["run_id"])
runs_after = await client.runs.list(thread["thread_id"])
for run in runs_after:
    if run["run_id"] in (run1["run_id"], run2["run_id"]):
        print(run["run_id"], run["status"])  # "interrupted"

Javascript

// Bulk delete by run IDs is not supported in the Javascript SDK

cURL

# Create two runs (capture run_id from each response)
curl --request POST \
  --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs \
  --header 'Content-Type: application/json' \
  --data '{"assistant_id": "agent", "input": {"messages": [{"role": "user", "content": "First request"}]}}'

curl --request POST \
  --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs \
  --header 'Content-Type: application/json' \
  --data '{"assistant_id": "agent", "input": {"messages": [{"role": "user", "content": "Second request"}]}}'

# Cancel both by run IDs
curl --request POST \
  --url "<DEPLOYMENT_URL>/runs/cancel?action=interrupt" \
  --header 'Content-Type: application/json' \
  --data '{"thread_id": "<THREAD_ID>", "run_ids": ["<RUN_ID_1>", "<RUN_ID_2>"]}'

# List runs to confirm
curl --request GET \
  --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs

상태로 취소

배포의 모든 스레드에서 특정 상태와 일치하는 모든 런을 취소합니다. 유효한 상태 옵션은 pending, running, 또는 all입니다.

Python

run1 = await client.runs.create(
    thread["thread_id"],
    assistant_id,
    input={"messages": [{"role": "user", "content": "First request"}]},
)
thread2 = await client.threads.create()
run2 = await client.runs.create(
    thread2["thread_id"],
    assistant_id,
    input={"messages": [{"role": "user", "content": "Second request"}]},
)

await client.runs.cancel_many(
    status="running",
)

# Wait for the runs to be cancelled
await client.runs.join(thread2["thread_id"], run2["run_id"])
run_after = await client.runs.get(thread["thread_id"], run1["run_id"])
print(run_after["status"])  # running run is now "interrupted"
run_after2 = await client.runs.get(thread2["thread_id"], run2["run_id"])
print(run_after2["status"])  # runs are cancelled across all threads

Javascript

// Bulk delete by status is not supported in the Javascript SDK

cURL

# Create a run
curl --request POST \
  --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs \
  --header 'Content-Type: application/json' \
  --data '{"assistant_id": "agent", "input": {"messages": [{"role": "user", "content": "First request"}]}}'

# Create a second thread
curl --request POST \
  --url <DEPLOYMENT_URL>/threads \
  --header 'Content-Type: application/json' \
  --data '{}'

# Create a run in the second thread
curl --request POST \
  --url <DEPLOYMENT_URL>/threads/<THREAD_ID_2>/runs \
  --header 'Content-Type: application/json' \
  --data '{"assistant_id": "agent", "input": {"messages": [{"role": "user", "content": "Second request"}]}}'

# Cancel all running runs
curl --request POST \
  --url "<DEPLOYMENT_URL>/runs/cancel?action=interrupt" \
  --header 'Content-Type: application/json' \
  --data '{"status": "running"}'

# Get the status of the runs to confirm
curl --request GET \
  --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/<RUN_ID_1>
curl --request GET \
  --url <DEPLOYMENT_URL>/threads/<THREAD_ID_2>/runs/<RUN_ID_2>

연결 끊김 시 취소

스트리밍으로 런을 시작하거나 런을 기다릴 때 on_disconnect="cancel"을 설정하면 클라이언트가 연결을 끊을 때 런이 취소됩니다. 이렇게 하면 사용자가 앱을 닫거나 연결이 끊겼을 때 진행 중인 런이 남지 않습니다.

Python

# With runs.wait: run is cancelled if the client disconnects
result = await client.runs.wait(
    thread["thread_id"],
    assistant_id,
    input={"messages": [{"role": "user", "content": "Long task"}]},
    on_disconnect="cancel",
)

# With runs.stream: run is cancelled if the client disconnects
async for chunk in client.runs.stream(
    thread["thread_id"],
    assistant_id,
    input={"messages": [{"role": "user", "content": "Long task"}]},
    on_disconnect="cancel",
):
    print(chunk)

# With runs.join: wait for an existing run; cancel if client disconnects
run = await client.runs.create(
    thread["thread_id"],
    assistant_id,
    input={"messages": [{"role": "user", "content": "Long task"}]},
)
await client.runs.join(
    thread["thread_id"],
    run["run_id"],
    on_disconnect="cancel",
)

# With runs.join_stream: join an existing run and stream; cancel if client disconnects
async for chunk in client.runs.join_stream(
    thread["thread_id"],
    run["run_id"],
    on_disconnect="cancel",
):
    print(chunk)

Javascript

// With runs.wait: run is cancelled if the client disconnects
const result = await client.runs.wait(
    thread["thread_id"],
    assistantID,
    { input: { messages: [{ role: "user", content: "Long task" }] }, onDisconnect: "cancel" }
);

// With runs.stream: run is cancelled if the client disconnects
const streamResponse = client.runs.stream(
    thread["thread_id"],
    assistantID,
    { input: { messages: [{ role: "user", content: "Long task" }] }, onDisconnect: "cancel" }
);
for await (const chunk of streamResponse) {
    console.log(chunk);
}

// With runs.join does not support cancel on disconnect in the Javascript SDK

// With runs.joinStream: join an existing run and stream; cancel if client disconnects
const joinStreamResponse = client.runs.joinStream(
    thread["thread_id"],
    run["run_id"],
    { cancelOnDisconnect: true }
);
for await (const chunk of joinStreamResponse) {
    console.log(chunk);
}

cURL

# runs.wait: create run and wait for output; cancel if client disconnects
curl --request POST \
  --url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/wait \
  --header 'Content-Type: application/json' \
  --data '{"assistant_id": "agent", "input": {"messages": [{"role": "user", "content": "Long task"}]}, "on_disconnect": "cancel"}'

# Create and stream a run; cancel if client disconnects
curl --request POST \
  --url "<DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream?on_disconnect=cancel" \
  --header 'Content-Type: application/json' \
  --data '{"assistant_id": "agent", "input": {"messages": [{"role": "user", "content": "Long task"}]}}'

# runs.join: wait on an existing run; cancel if client disconnects
curl --request GET \
  --url "<DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/<RUN_ID>/join?cancel_on_disconnect=cancel"

# runs.join_stream: join an existing run and stream; cancel if client disconnects
curl --request GET \
  --url "<DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/<RUN_ID>/stream?cancel_on_disconnect=cancel"

일반적인 시나리오

  • Human-in-the-loop 및 interrupt: 에이전트는 인간 입력을 위해 interrupt에서 일시 중지될 수 있습니다. 런 취소는 실행을 중지합니다. 이는 새 입력으로 재개할 수 있도록 런이 일시 중지되는 interrupt와는 다릅니다.
  • Time travel: interrupt 동작으로 취소한 후에는 런과 체크포인트가 여전히 사용 가능합니다. 체크포인트에서 재개(time travel)하여 실행을 재생하거나 분기할 수 있습니다.
  • Double-texting: 런이 진행 중일 때 사용자가 새 입력을 보내면, multitask 전략(enqueue, reject, interrupt, rollback)이 기존 런을 interrupt/rollback할지와 새 런을 어떻게 처리할지를 결정합니다. 애플리케이션에서 런을 명시적으로 취소하려면 이 페이지에 설명된 취소 API를 사용하세요.
  • Studio: Studio에서 런 UI의 Cancel 버튼을 사용해 현재 런을 취소합니다.

더 알아보기