메시지 스트리밍하기
메시지 스트리밍하기 (Streaming messages)
스트리밍(streaming)을 쓰면 Message를 만들 때 "stream": true를 설정해서 응답을 server-sent events(SSE) 방식으로 조금씩 받아볼 수 있어요. 모델이 토큰을 만들어내는 대로 즉시 전달받으니, 사용자에게 더 자연스러운 경험을 줄 수 있답니다. SDK와 원시 HTTP 로우 레벨 API로 각각 어떻게 스트리밍하는지 살펴볼게요.
출처: 문서
본문
SDK로 스트리밍하기
Python SDK와 TypeScript SDK는 여러 가지 방식으로 스트리밍을 지원해요. PHP SDK는 createStream()을 통해 스트리밍을 제공해요. Python SDK는 동기(sync)와 비동기(async) 스트림을 모두 지원해요. 각 SDK의 문서에서 자세한 내용을 확인해 주세요.
client = anthropic.Anthropic()
with client.messages.stream(
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}],
model="claude-opus-5-5",
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
const client = new Anthropic();
await client.messages
.stream({
messages: [{ role: "user", content: "Hello" }],
model: "claude-opus-5-5",
max_tokens: 1024
})
.on("text", (text) => {
console.log(text);
});
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus5_5,
MaxTokens = 1024,
Messages = [new() { Role = Role.User, Content = "Hello" }]
};
await foreach (var msg in client.Messages.CreateStreaming(parameters))
{
Console.Write(msg);
}
client := anthropic.NewClient()
stream := client.Messages.NewStreaming(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus5_5,
MaxTokens: 1024,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello")),
},
})
for stream.Next() {
event := stream.Current()
switch eventVariant := event.AsAny().(type) {
case anthropic.ContentBlockDeltaEvent:
switch deltaVariant := eventVariant.Delta.AsAny().(type) {
case anthropic.TextDelta:
fmt.Print(deltaVariant.Text)
}
}
}
if err := stream.Err(); err != nil {
log.Fatal(err)
}
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_5_5)
.maxTokens(1024L)
.addUserMessage("Hello")
.build();
try (var streamResponse = client.messages().createStreaming(params)) {
streamResponse.stream().forEach(event -> {
event.contentBlockDelta().ifPresent(deltaEvent ->
deltaEvent.delta().text().ifPresent(td ->
System.out.print(td.text())
)
);
});
}
$client = new Client();
$stream = $client->messages->createStream(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => 'Hello']
],
model: 'claude-opus-5-5',
);
foreach ($stream as $message) {
echo $message;
}
client = Anthropic::Client.new
stream = client.messages.stream(
model: "claude-opus-5-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello" }]
)
stream.text.each { |text| print(text) }
이벤트를 직접 다루지 않고 최종 메시지 얻기
텍스트가 도착하는 대로 처리할 필요가 없다면, SDK들이 제공하는 방식으로 스트리밍을 내부에서 쓰면서도 완전한 Message 객체(마치 .create()가 반환하는 것과 동일)를 돌려받을 수 있어요. 큰 max_tokens 값의 요청에서 특히 유용한데, 그런 경우 SDK들이 HTTP 타임아웃을 피하려고 스트리밍을 요구하거든요.
client = anthropic.Anthropic()
with client.messages.stream(
max_tokens=128000,
messages=[{"role": "user", "content": "Write a detailed analysis..."}],
model="claude-opus-5-5",
) as stream:
message = stream.get_final_message()
for block in message.content:
if block.type == "text":
print(block.text)
const client = new Anthropic();
const stream = client.messages.stream({
max_tokens: 128000,
messages: [{ role: "user", content: "Write a detailed analysis..." }],
model: "claude-opus-5-5"
});
const message = await stream.finalMessage();
const textBlock = message.content.find((block) => block.type === "text");
if (textBlock && textBlock.type === "text") {
console.log(textBlock.text);
}
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus5_5,
MaxTokens = 128000,
Messages = [new() { Role = Role.User, Content = "Write a detailed analysis..." }]
};
var fullText = "";
await foreach (var msg in client.Messages.CreateStreaming(parameters))
{
fullText += msg;
}
Console.WriteLine(fullText);
client := anthropic.NewClient()
stream := client.Messages.NewStreaming(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus5_5,
MaxTokens: 128000,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Write a detailed analysis...")),
},
})
message := anthropic.Message{}
for stream.Next() {
event := stream.Current()
if err := message.Accumulate(event); err != nil {
log.Fatal(err)
}
}
if err := stream.Err(); err != nil {
log.Fatal(err)
}
for _, block := range message.Content {
if textBlock, ok := block.AsAny().(anthropic.TextBlock); ok {
fmt.Println(textBlock.Text)
}
}
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_5_5)
.maxTokens(128000L)
.addUserMessage("Write a detailed analysis...")
.build();
MessageAccumulator accumulator = MessageAccumulator.create();
try (var streamResponse = client.messages().createStreaming(params)) {
streamResponse.stream().forEach(accumulator::accumulate);
}
Message message = accumulator.message();
message.content().stream()
.flatMap(block -> block.text().stream())
.forEach(textBlock -> System.out.println(textBlock.text()));
$client = new Client();
$stream = $client->messages->createStream(
maxTokens: 128000,
messages: [
['role' => 'user', 'content' => 'Write a detailed analysis...']
],
model: 'claude-opus-5-5',
);
$fullText = '';
foreach ($stream as $event) {
if ($event->type === 'content_block_delta' && $event->delta->type === 'text_delta') {
$fullText .= $event->delta->text;
}
}
echo $fullText;
client = Anthropic::Client.new
message = client.messages.stream(
model: "claude-opus-5-5",
max_tokens: 128000,
messages: [{ role: "user", content: "Write a detailed analysis..." }]
).accumulated_message
message.content.each do |block|
puts block.text if block.type == :text
end
.stream() 호출은 SSE로 HTTP 연결을 유지하다가, .get_final_message()(Python)나 .finalMessage()(TypeScript)가 모든 이벤트를 모아 완전한 Message 객체를 돌려줘요. Go에서는 스트림 루프 안에서 message.Accumulate(event)를 호출해 같은 완전한 Message를 만들고, Java에서는 MessageAccumulator.create()로 만든 뒤 각 이벤트마다 accumulator.accumulate(event)를 호출해요. C#에서는 스트림의 .Aggregate() 확장 메서드를 await 해 완전한 Message를 얻거나, MessageContentAggregator를 .CollectAsync()에 넘겨 이벤트를 다루면서 누적할 수 있어요. Ruby에서는 스트림에 .accumulated_message를 호출하고, PHP SDK에서는 스트림 이벤트를 직접 순회하며 응답을 누적합니다.
이벤트 타입
각 SSE 이벤트는 명명된 이벤트 타입과 함께 관련 JSON 데이터를 포함해요. 각 이벤트는 SSE 이벤트 이름(예: event: message_stop)을 쓰고, 데이터 안에는 일치하는 type 이벤트를 담아요.
각 스트림은 다음의 이벤트 흐름을 사용해요:
message_start: 비어 있는content를 가진Message객체를 담아요.thinking-binding-controls-2026-08-01베타 헤더 아래에서는 이Message객체가input_transformations배열도 지녀요. 중간에 서버 측 폴백이 일어난 뒤에는 마지막message_delta이벤트가 서빙 모델의 항목들과 함께 배열을 다시 전달해요.- 일련의 콘텐츠 블록. 각각
content_block_start, 하나 이상의content_block_delta이벤트, 그리고content_block_stop이벤트를 가져요. 각 콘텐츠 블록은 최종 Messagecontent배열에서의 인덱스에 해당하는index를 지녀요. 예외가 하나 있는데, 서버 측 폴백 응답 중에는 각 모델 경계에서fallback콘텐츠 블록이content_block_start와content_block_stop쌍으로 도착하고 그 사이에 델타는 없어요. - 하나 이상의
message_delta이벤트. 최종Message객체에 대한 최상위 변경을 나타내요. - 마지막
message_stop이벤트.
Ping 이벤트
이벤트 스트림에는 얼마든지 많은 ping 이벤트가 포함될 수 있어요.
오류 이벤트
API는 스트림 안에서 오류를 이따금 보낼 수 있어요. 예를 들어 사용률이 높은 동안에는 overloaded_error를 받을 수 있는데, 이는 비스트리밍 상황의 HTTP 529에 해당해요:
event: error
data: {"type": "error", "error": {"type": "overloaded_error", "message": "Overloaded"}}
기타 이벤트
버저닝 정책에 따라 새 이벤트 타입이 추가될 수 있으니, 모르는 이벤트 타입도 우아하게 처리하도록 코드를 작성하세요.
콘텐츠 블록 델타 타입
각 content_block_delta 이벤트는 주어진 index의 content 블록을 갱신하는 delta 타입을 담아요.
텍스트 델타
text 콘텐츠 블록 델타는 다음과 같아요:
event: content_block_delta
data: {"type": "content_block_delta","index": 0,"delta": {"type": "text_delta", "text": "ello frien"}}
입력 JSON 델타
tool_use 콘텐츠 블록의 델타는 블록의 input 필드 갱신과 대응해요. 최대한 세밀한 단위를 지원하기 위해 델타는 *부분 JSON 문자열(partial JSON strings)*이고, 최종 tool_use.input은 항상 *객체(object)*예요.
content_block_stop 이벤트를 받으면 문자열 델타를 누적한 뒤 Pydantic 같은 라이브러리로 부분 JSON 파싱을 하거나, SDK가 제공하는 헬퍼(파싱된 증분 값에 접근)를 써서 JSON을 파싱할 수 있어요.
tool_use 콘텐츠 블록 델타는 다음과 같아요:
event: content_block_delta
data: {"type": "content_block_delta","index": 1,"delta": {"type": "input_json_delta","partial_json": "{\"location\": \"San Fra"}}
알아두세요: 현재 모델들은 input에서 한 번에 완전한 키-값 속성 하나만 내보내는 것을 지원해요. 그래서 도구를 쓸 때 모델이 작업하는 동안 스트리밍 이벤트 사이에 지연이 생길 수 있어요. input 키와 값이 누적되면 여러 content_block_delta 이벤트로 잘게 쪼갠 부분 JSON 형태로 내보내지는데, 이는 미래 모델에서 더 세밀한 단위를 자동으로 지원하기 위한 형식이에요.
사고(thinking) 델타
thinking을 스트리밍과 함께 쓸 때, thinking_delta 이벤트로 사고 콘텐츠를 받아요. 이 델타들은 thinking 콘텐츠 블록의 thinking 필드와 대응해요.
사고 콘텐츠의 경우, content_block_stop 이벤트 직전에 특별한 signature_delta 이벤트가 전송돼요. 이 서명은 thinking 블록의 무결성을 검증하는 데 쓰여요.
thinking 설정에 display: "omitted"를 설정하면 어떤 thinking 텍스트도 스트리밍되지 않아요. thinking 블록이 열리고, 빈 thinking 문자열을 가진 thinking_delta 하나와 signature_delta 하나를 받은 뒤 닫혀요. display: "updates"(베타)에서는 추론 블록이 같은 방식으로 스트리밍되고, 오직 일부 모델이 도구 호출 사이에 쓰는 진행 업데이트만 텍스트를 담은 thinking_delta 이벤트로 스트리밍돼요. thinking 표시 제어를 참고하세요.
전형적인 thinking 델타는 다음과 같아요:
event: content_block_delta
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "thinking_delta", "thinking": "I need to find the GCD of 1071 and 462 using the Euclidean algorithm.\n\n1071 = 2 × 462 + 147"}}
서명 델타는 다음과 같아요:
event: content_block_delta
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "signature_delta", "signature": "EqQBCgIYAhIM1gbcDa9GJwZA2b3hGgxBdjrkzLoky3dl1pkiMOYds..."}}
전체 HTTP 스트림 응답
스트리밍 모드를 쓸 땐 클라이언트 SDK를 사용하세요. 하지만 직접 API 통합을 만들고 있다면 이 이벤트들을 직접 처리해야 해요.
스트림 응답은 다음으로 구성돼요:
-
message_start이벤트 -
여러 개일 수 있는 콘텐츠 블록. 각각은 다음을 포함해요:
content_block_start이벤트- 여러 개일 수 있는
content_block_delta이벤트 content_block_stop이벤트
-
하나 이상의
message_delta이벤트 -
message_stop이벤트
응답 전체에 ping 이벤트가 흩어져 있을 수 있어요. 형식에 대한 자세한 내용은 이벤트 타입을 참고하세요.
기본 스트리밍 요청
ant messages create --stream --format jsonl \
--model claude-opus-5-5 \
--max-tokens 256 \
--message '{role: user, content: Hello}'
client = anthropic.Anthropic()
with client.messages.stream(
model="claude-opus-5-5",
messages=[{"role": "user", "content": "Hello"}],
max_tokens=256,
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
const client = new Anthropic();
const stream = client.messages.stream({
model: "claude-opus-5-5",
messages: [{ role: "user", content: "Hello" }],
max_tokens: 256
});
for await (const event of stream) {
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
process.stdout.write(event.delta.text);
}
}
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus5_5,
MaxTokens = 256,
Messages = [new() { Role = Role.User, Content = "Hello" }]
};
await foreach (var msg in client.Messages.CreateStreaming(parameters))
{
Console.Write(msg);
}
client := anthropic.NewClient()
stream := client.Messages.NewStreaming(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus5_5,
MaxTokens: 256,
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Hello")),
},
})
for stream.Next() {
event := stream.Current()
switch eventVariant := event.AsAny().(type) {
case anthropic.ContentBlockDeltaEvent:
switch deltaVariant := eventVariant.Delta.AsAny().(type) {
case anthropic.TextDelta:
fmt.Print(deltaVariant.Text)
}
}
}
if err := stream.Err(); err != nil {
log.Fatal(err)
}
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_5_5)
.maxTokens(256L)
.addUserMessage("Hello")
.build();
try (var streamResponse = client.messages().createStreaming(params)) {
streamResponse.stream().forEach(event -> {
event.contentBlockDelta().ifPresent(deltaEvent ->
deltaEvent.delta().text().ifPresent(td ->
System.out.print(td.text())
)
);
});
}
$client = new Client();
$stream = $client->messages->createStream(
maxTokens: 256,
messages: [
['role' => 'user', 'content' => 'Hello']
],
model: 'claude-opus-5-5',
);
foreach ($stream as $message) {
echo $message;
}
client = Anthropic::Client.new
stream = client.messages.stream(
model: "claude-opus-5-5",
messages: [{ role: "user", content: "Hello" }],
max_tokens: 256
)
stream.text.each { |text| print(text) }
event: message_start
data: {"type": "message_start", "message": {"id": "msg_1nZdL29xx5MUA1yADyHTEsnR8uuvGzszyY", "type": "message", "role": "assistant", "content": [], "model": "claude-opus-5-5", "stop_reason": null, "stop_sequence": null, "usage": {"input_tokens": 25, "output_tokens": 1}}}
event: content_block_start
data: {"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}}
event: ping
data: {"type": "ping"}
event: content_block_delta
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": "Hello"}}
event: content_block_delta
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": "!"}}
event: content_block_stop
data: {"type": "content_block_stop", "index": 0}
event: message_delta
data: {"type": "message_delta", "delta": {"stop_reason": "end_turn", "stop_sequence":null}, "usage": {"output_tokens": 15}}
event: message_stop
data: {"type": "message_stop"}
도구 사용과 함께하는 스트리밍 요청
이 요청은 Claude에게 도구로 날씨를 보고하라고 요청해요.
ant messages create --stream --format jsonl <<'YAML'
model: claude-opus-5
max_tokens: 1024
tools:
- name: get_weather
description: Get the current weather in a given location
input_schema:
type: object
properties:
location:
type: string
description: The city and state, e.g. San Francisco, CA
required:
- location
tool_choice:
type: any
messages:
- role: user
content: What is the weather like in San Francisco?
YAML
client = anthropic.Anthropic()
tools = [
{
"name": "get_weather",
"description": "Get the current weather in a given location",
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA",
}
},
"required": ["location"],
},
}
]
with client.messages.stream(
model="claude-opus-5",
max_tokens=1024,
tools=tools,
tool_choice={"type": "any"},
messages=[
{"role": "user", "content": "What is the weather like in San Francisco?"}
],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
const client = new Anthropic();
const tools: Anthropic.Tool[] = [
{
name: "get_weather",
description: "Get the current weather in a given location",
input_schema: {
type: "object",
properties: {
location: {
type: "string",
description: "The city and state, e.g. San Francisco, CA"
}
},
required: ["location"]
}
}
];
const stream = client.messages.stream({
model: "claude-opus-5",
max_tokens: 1024,
tools: tools,
tool_choice: { type: "any" },
messages: [
{
role: "user",
content: "What is the weather like in San Francisco?"
}
]
});
for await (const event of stream) {
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
process.stdout.write(event.delta.text);
}
}
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus5,
MaxTokens = 1024,
Tools = [
new ToolUnion(new Tool()
{
Name = "get_weather",
Description = "Get the current weather in a given location",
InputSchema = new InputSchema()
{
Properties = new Dictionary<string, JsonElement>
{
["location"] = JsonSerializer.SerializeToElement(new { type = "string", description = "The city and state, e.g. San Francisco, CA" }),
},
Required = ["location"],
},
}),
],
ToolChoice = new ToolChoiceAny(),
Messages = [
new() { Role = Role.User, Content = "What is the weather like in San Francisco?" }
]
};
await foreach (var msg in client.Messages.CreateStreaming(parameters))
{
Console.Write(msg);
}
client := anthropic.NewClient()
stream := client.Messages.NewStreaming(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus5,
MaxTokens: 1024,
Tools: []anthropic.ToolUnionParam{
{OfTool: &anthropic.ToolParam{
Name: "get_weather",
Description: anthropic.String("Get the current weather in a given location"),
InputSchema: anthropic.ToolInputSchemaParam{
Properties: map[string]any{
"location": map[string]any{
"type": "string",
"description": "The city and state, e.g. San Francisco, CA",
},
},
Required: []string{"location"},
},
}},
},
ToolChoice: anthropic.ToolChoiceUnionParam{OfAny: &anthropic.ToolChoiceAnyParam{}},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("What is the weather like in San Francisco?")),
},
})
for stream.Next() {
event := stream.Current()
switch eventVariant := event.AsAny().(type) {
case anthropic.ContentBlockDeltaEvent:
switch deltaVariant := eventVariant.Delta.AsAny().(type) {
case anthropic.TextDelta:
fmt.Print(deltaVariant.Text)
}
}
}
if err := stream.Err(); err != nil {
log.Fatal(err)
}
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_5)
.maxTokens(1024L)
.addTool(Tool.builder()
.name("get_weather")
.description("Get the current weather in a given location")
.inputSchema(Tool.InputSchema.builder()
.properties(JsonValue.from(Map.of(
"location", Map.of(
"type", "string",
"description", "The city and state, e.g. San Francisco, CA"
)
)))
.putAdditionalProperty("required", JsonValue.from(List.of("location")))
.build())
.build())
.toolChoice(ToolChoice.ofAny(ToolChoiceAny.builder().build()))
.addUserMessage("What is the weather like in San Francisco?")
.build();
try (var streamResponse = client.messages().createStreaming(params)) {
streamResponse.stream().forEach(event -> {
event.contentBlockDelta().ifPresent(deltaEvent ->
deltaEvent.delta().text().ifPresent(td ->
System.out.print(td.text())
)
);
});
}
$client = new Client();
$stream = $client->messages->createStream(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => 'What is the weather like in San Francisco?']
],
model: 'claude-opus-5',
toolChoice: ['type' => 'any'],
tools: [
[
'name' => 'get_weather',
'description' => 'Get the current weather in a given location',
'input_schema' => [
'type' => 'object',
'properties' => [
'location' => [
'type' => 'string',
'description' => 'The city and state, e.g. San Francisco, CA'
]
],
'required' => ['location']
]
]
],
);
foreach ($stream as $message) {
echo $message;
}
client = Anthropic::Client.new
tools = [
{
name: "get_weather",
description: "Get the current weather in a given location",
input_schema: {
type: "object",
properties: {
location: {
type: "string",
description: "The city and state, e.g. San Francisco, CA"
}
},
required: ["location"]
}
}
]
stream = client.messages.stream(
model: "claude-opus-5",
max_tokens: 1024,
tools: tools,
tool_choice: { type: "any" },
messages: [
{ role: "user", content: "What is the weather like in San Francisco?" }
]
)
stream.text.each { |text| print(text) }
event: message_start
data: {"type":"message_start","message":{"id":"msg_014p7gG3wDgGV9EUtLvnow3U","type":"message","role":"assistant","model":"claude-opus-5","stop_sequence":null,"usage":{"input_tokens":472,"output_tokens":2},"content":[],"stop_reason":null}}
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
event: ping
data: {"type": "ping"}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Okay"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":","}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" let"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"'s"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" check"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" the"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" weather"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" for"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" San"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" Francisco"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":","}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" CA"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":":"}}
event: content_block_stop
data: {"type":"content_block_stop","index":0}
event: content_block_start
data: {"type":"content_block_start","index":1,"content_block":{"type":"tool_use","id":"toolu_01T1x1fJ34qAmk2tNTrN7Up6","name":"get_weather","input":{}}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"{\"location\":"}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":" \"San"}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":" Francisc"}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"o,"}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":" CA\"}"}}
event: content_block_stop
data: {"type":"content_block_stop","index":1}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"tool_use","stop_sequence":null},"usage":{"output_tokens":89}}
event: message_stop
data: {"type":"message_stop"}
thinking과 함께하는 스트리밍 요청
이 요청은 thinking과 스트리밍을 함께 켜요. display: "summarized" 설정은 Claude의 추론 전체가 아니라 요약된 사고 흐름을 스트리밍해요.
ant messages create --stream --format jsonl \
--model claude-opus-5-5 \
--max-tokens 20000 \
--thinking '{type: adaptive, display: summarized}' \
--message '{role: user, content: What is the greatest common divisor of 1071 and 462?}'
client = anthropic.Anthropic()
with client.messages.stream(
model="claude-opus-5-5",
max_tokens=20000,
thinking={"type": "adaptive", "display": "summarized"},
messages=[
{
"role": "user",
"content": "What is the greatest common divisor of 1071 and 462?",
}
],
) as stream:
for event in stream:
if event.type == "content_block_delta":
delta = event.delta
match delta.type:
case "thinking_delta":
print(delta.thinking, end="", flush=True)
case "text_delta":
print(delta.text, end="", flush=True)
const client = new Anthropic();
const stream = client.messages.stream({
model: "claude-opus-5-5",
max_tokens: 20000,
thinking: { type: "adaptive", display: "summarized" },
messages: [
{
role: "user",
content: "What is the greatest common divisor of 1071 and 462?"
}
]
});
for await (const event of stream) {
if (event.type === "content_block_delta") {
switch (event.delta.type) {
case "thinking_delta":
process.stdout.write(event.delta.thinking);
break;
case "text_delta":
process.stdout.write(event.delta.text);
break;
}
}
}
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus5_5,
MaxTokens = 20000,
Thinking = new ThinkingConfigAdaptive { Display = Display.Summarized },
Messages = [new() { Role = Role.User, Content = "What is the greatest common divisor of 1071 and 462?" }]
};
await foreach (var msg in client.Messages.CreateStreaming(parameters))
{
Console.Write(msg);
}
client := anthropic.NewClient()
stream := client.Messages.NewStreaming(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus5_5,
MaxTokens: 20000,
Thinking: anthropic.ThinkingConfigParamUnion{
OfAdaptive: &anthropic.ThinkingConfigAdaptiveParam{
Display: anthropic.ThinkingConfigAdaptiveDisplaySummarized,
},
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("What is the greatest common divisor of 1071 and 462?")),
},
})
for stream.Next() {
event := stream.Current()
switch eventVariant := event.AsAny().(type) {
case anthropic.ContentBlockDeltaEvent:
switch deltaVariant := eventVariant.Delta.AsAny().(type) {
case anthropic.ThinkingDelta:
fmt.Print(deltaVariant.Thinking)
case anthropic.TextDelta:
fmt.Print(deltaVariant.Text)
}
}
}
if err := stream.Err(); err != nil {
log.Fatal(err)
}
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_5_5)
.maxTokens(20000L)
.thinking(ThinkingConfigAdaptive.builder()
.display(ThinkingConfigAdaptive.Display.SUMMARIZED)
.build())
.addUserMessage("What is the greatest common divisor of 1071 and 462?")
.build();
try (var streamResponse = client.messages().createStreaming(params)) {
streamResponse.stream().forEach(event -> {
event.contentBlockDelta().ifPresent(deltaEvent -> {
deltaEvent.delta().thinking().ifPresent(td ->
IO.print(td.thinking())
);
deltaEvent.delta().text().ifPresent(td ->
IO.print(td.text())
);
});
});
}
$client = new Client();
$stream = $client->messages->createStream(
maxTokens: 20000,
messages: [
['role' => 'user', 'content' => 'What is the greatest common divisor of 1071 and 462?']
],
model: 'claude-opus-5-5',
thinking: ['type' => 'adaptive', 'display' => 'summarized'],
);
foreach ($stream as $message) {
echo $message;
}
client = Anthropic::Client.new
stream = client.messages.stream(
model: "claude-opus-5-5",
max_tokens: 20000,
thinking: { type: "adaptive", display: "summarized" },
messages: [
{ role: "user", content: "What is the greatest common divisor of 1071 and 462?" }
]
)
stream.each do |event|
if event.is_a?(Anthropic::Models::RawContentBlockDeltaEvent)
delta = event.delta
case delta
when Anthropic::Models::ThinkingDelta
print(delta.thinking)
when Anthropic::Models::TextDelta
print(delta.text)
end
end
end
event: message_start
data: {"type": "message_start", "message": {"id": "msg_01...", "type": "message", "role": "assistant", "content": [], "model": "claude-opus-5-5", "stop_reason": null, "stop_sequence": null}}
event: content_block_start
data: {"type": "content_block_start", "index": 0, "content_block": {"type": "thinking", "thinking": "", "signature": ""}}
event: content_block_delta
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "thinking_delta", "thinking": "I need to find the GCD of 1071 and 462 using the Euclidean algorithm.\n\n1071 = 2 × 462 + 147"}}
event: content_block_delta
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "thinking_delta", "thinking": "\n462 = 3 × 147 + 21"}}
event: content_block_delta
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "thinking_delta", "thinking": "\n147 = 7 × 21 + 0"}}
event: content_block_delta
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "thinking_delta", "thinking": "\nThe remainder is 0, so GCD(1071, 462) = 21."}}
event: content_block_delta
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "signature_delta", "signature": "EqQBCgIYAhIM1gbcDa9GJwZA2b3hGgxBdjrkzLoky3dl1pkiMOYds..."}}
event: content_block_stop
data: {"type": "content_block_stop", "index": 0}
event: content_block_start
data: {"type": "content_block_start", "index": 1, "content_block": {"type": "text", "text": ""}}
event: content_block_delta
data: {"type": "content_block_delta", "index": 1, "delta": {"type": "text_delta", "text": "The greatest common divisor of 1071 and 462 is **21**."}}
event: content_block_stop
data: {"type": "content_block_stop", "index": 1}
event: message_delta
data: {"type": "message_delta", "delta": {"stop_reason": "end_turn", "stop_sequence": null}}
event: message_stop
data: {"type": "message_stop"}
웹 검색 도구 사용과 함께하는 스트리밍 요청
이 요청은 Claude에게 현재 날씨 정보를 웹에서 검색하라고 요청해요.
ant messages create --stream --format jsonl \
--model claude-opus-5-5 \
--max-tokens 1024 \
--tool '{type: web_search_20250305, name: web_search, max_uses: 5}' \
--message '{role: user, content: What is the weather like in New York City today?}'
client = anthropic.Anthropic()
with client.messages.stream(
model="claude-opus-5-5",
max_tokens=1024,
tools=[{"type": "web_search_20250305", "name": "web_search", "max_uses": 5}],
messages=[
{"role": "user", "content": "What is the weather like in New York City today?"}
],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
const client = new Anthropic();
const stream = client.messages.stream({
model: "claude-opus-5-5",
max_tokens: 1024,
tools: [{ type: "web_search_20250305", name: "web_search", max_uses: 5 }],
messages: [{ role: "user", content: "What is the weather like in New York City today?" }]
});
for await (const event of stream) {
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
process.stdout.write(event.delta.text);
}
}
using Anthropic;
using Anthropic.Models.Messages;
AnthropicClient client = new();
var parameters = new MessageCreateParams
{
Model = Model.ClaudeOpus5_5,
MaxTokens = 1024,
Tools = [new ToolUnion(new WebSearchTool20250305() { MaxUses = 5 })],
Messages = [new() { Role = Role.User, Content = "What is the weather like in New York City today?" }]
};
await foreach (var msg in client.Messages.CreateStreaming(parameters))
{
Console.Write(msg);
}
client := anthropic.NewClient()
stream := client.Messages.NewStreaming(context.TODO(), anthropic.MessageNewParams{
Model: anthropic.ModelClaudeOpus5_5,
MaxTokens: 1024,
Tools: []anthropic.ToolUnionParam{
{
OfWebSearchTool20250305: &anthropic.WebSearchTool20250305Param{
MaxUses: anthropic.Int(5),
},
},
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("What is the weather like in New York City today?")),
},
})
for stream.Next() {
event := stream.Current()
switch eventVariant := event.AsAny().(type) {
case anthropic.ContentBlockDeltaEvent:
switch deltaVariant := eventVariant.Delta.AsAny().(type) {
case anthropic.TextDelta:
fmt.Print(deltaVariant.Text)
}
}
}
if err := stream.Err(); err != nil {
log.Fatal(err)
}
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_5_5)
.maxTokens(1024L)
.addTool(WebSearchTool20250305.builder()
.maxUses(5L)
.build())
.addUserMessage("What is the weather like in New York City today?")
.build();
try (var streamResponse = client.messages().createStreaming(params)) {
streamResponse.stream().forEach(event -> {
event.contentBlockDelta().ifPresent(deltaEvent ->
deltaEvent.delta().text().ifPresent(td ->
System.out.print(td.text())
)
);
});
}
$client = new Client();
$stream = $client->messages->createStream(
maxTokens: 1024,
messages: [
['role' => 'user', 'content' => 'What is the weather like in New York City today?']
],
model: 'claude-opus-5-5',
tools: [
['type' => 'web_search_20250305', 'name' => 'web_search', 'max_uses' => 5]
],
);
foreach ($stream as $message) {
echo $message;
}
client = Anthropic::Client.new
stream = client.messages.stream(
model: :"claude-opus-5-5",
max_tokens: 1024,
tools: [
{
type: "web_search_20250305",
name: "web_search",
max_uses: 5
}
],
messages: [
{
role: "user",
content: "What is the weather like in New York City today?"
}
]
)
stream.text.each { |text| print(text) }
event: message_start
data: {"type":"message_start","message":{"id":"msg_01G...","type":"message","role":"assistant","model":"claude-opus-5-5","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":2679,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":3}}}
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"I'll check"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" the current weather in New York City for you"}}
event: ping
data: {"type": "ping"}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"."}}
event: content_block_stop
data: {"type":"content_block_stop","index":0}
event: content_block_start
data: {"type":"content_block_start","index":1,"content_block":{"type":"server_tool_use","id":"srvtoolu_014hJH82Qum7Td6UV8gDXThB","name":"web_search","input":{}}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"{\"query"}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"\":\""}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":" \"weather"}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":" NY"}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"C to"}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"day\"}"}}
event: content_block_stop
data: {"type":"content_block_stop","index":1 }
event: content_block_start
data: {"type":"content_block_start","index":2,"content_block":{"type":"web_search_tool_result","tool_use_id":"srvtoolu_014hJH82Qum7Td6UV8gDXThB","content":[{"type":"web_search_result","title":"Weather in New York City in May 2025 (New York) - detailed Weather Forecast for a month","url":"https://world-weather.info/forecast/usa/new_york/may-2025/","encrypted_content":"Ev0DCioIAxgCIiQ3NmU4ZmI4OC1k...","page_age":null},...]}}
event: content_block_stop
data: {"type":"content_block_stop","index":2}
event: content_block_start
data: {"type":"content_block_start","index":3,"content_block":{"type":"text","text":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":3,"delta":{"type":"text_delta","text":"Here's the current weather information for New York"}}
event: content_block_delta
data: {"type":"content_block_delta","index":3,"delta":{"type":"text_delta","text":" City:\n\n# Weather"}}
event: content_block_delta
data: {"type":"content_block_delta","index":3,"delta":{"type":"text_delta","text":" in New York City"}}
event: content_block_delta
data: {"type":"content_block_delta","index":3,"delta":{"type":"text_delta","text":"\n\n"}}
...
event: content_block_stop
data: {"type":"content_block_stop","index":17}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":10682,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":510,"server_tool_use":{"web_search_requests":1}}}
event: message_stop
data: {"type":"message_stop"}
오류 복구
Claude 4.5 이하
Claude 4.5 모델 이하에서는 네트워크 문제, 타임아웃, 기타 오류로 중단된 스트리밍 요청을 중단된 지점부터 재개해서 복구할 수 있어요. 이 방식은 전체 응답을 다시 처리하는 수고를 덜어줘요.
기본적인 복구 전략은 다음과 같아요:
- 부분 응답 캡처하기: 오류가 발생하기 전에 성공적으로 받은 모든 콘텐츠를 저장하세요.
- 이어서 보낼 요청 구성하기: 부분 어시스턴트 응답을 새 어시스턴트 메시지의 시작으로 포함하는 새 API 요청을 만드세요.
- 스트리밍 재개하기: 중단된 지점부터 나머지 응답을 계속 받으세요.
Claude 4.6 이상
Claude 4.6 이상 모델에서도 같은 캡처-후-재개 전략을 쓰지만 2단계가 달라져요: 부분 응답을 어시스턴트 메시지에 넣는 대신, 모델에게 중단된 지점부터 계속하라고 지시하는 사용자 메시지를 추가해요.
- 부분 응답 캡처하기: 오류가 발생하기 전에 성공적으로 받은 모든 콘텐츠를 저장하세요.
- 이어서 보낼 요청 구성하기: 부분 응답과 계속하라는 지시를 담은 사용자 메시지로 새 API 요청을 만드세요. 예를 들면:
Your previous response was interrupted and ended with [previous_response]. Continue from where you left off. - 스트리밍 재개하기: 중단된 지점부터 나머지 응답을 계속 받으세요.
오류 복구 모범 사례
- SDK 기능 사용하기: SDK에 내장된 메시지 누적과 오류 처리 기능을 활용하세요.
- 콘텐츠 타입 처리하기: 메시지에 여러 콘텐츠 블록(
text,tool_use,thinking)이 들어갈 수 있다는 점을 알고 있어야 해요. 도구 사용과 확장 thinking 블록은 부분적으로 복구할 수 없어요. 스트리밍은 가장 최근 텍스트 블록부터 재개할 수 있습니다.