런 취소하기
런 취소하기
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 버튼을 사용해 현재 런을 취소합니다.
더 알아보기
- LangSmith Deployment API 레퍼런스는 Server API reference 문서를 참고하세요.
- interrupt에 대한 자세한 내용은 Add human in the loop 문서를 확인해 보세요.