SISTEM AKSES KOMPUTER

Streaming Respons Agen: Output Real-Time untuk Alur Kerja Multi-Langkah


Streaming Respons Agen: Output Real-Time untuk Alur Kerja Multi-Langkah

Agen Anda membutuhkan 20 detik untuk meneliti sebuah pertanyaan, memanggil tiga alat, dan menyintesis jawaban. Pengguna mengklik “Tanya” lalu menatap spinner selama 20 detik. Mereka tidak yakin apakah prosesnya berjalan. Mereka mempertimbangkan untuk me-refresh halaman. Mereka bertanya-tanya apakah harus memulai ulang.

Kini bayangkan ini: pengguna mengklik “Tanya” dan dalam 500 milidetik, mereka melihat kata-kata pertama agen muncul. Mereka menyaksikan agen mencari informasi—”🔍 Mencari database pesanan…”—dan melihat hasil tiba secara real time. Mereka membaca jawaban saat sedang ditulis, kalimat demi kalimat. Tetap 20 detik. Pengalaman yang sepenuhnya berbeda.

Streaming bukan sekadar fitur tambahan untuk agen yang menghadap pengguna—ini adalah kebutuhan UX. Time-to-first-token adalah metrik latensi terpenting dalam antarmuka agen. Pengguna yang melihat kemajuan bersabar. Pengguna yang tidak melihat apa-apa pergi. Studi demi studi dalam performa web menunjukkan bahwa latensi yang dirasakan lebih penting daripada latensi aktual, dan streaming adalah alat paling ampuh yang Anda miliki untuk mempersempit jarak di antara keduanya.

Dalam artikel ini, Anda akan mempelajari cara mengimplementasikan streaming real-time untuk agen AI multi-langkah—mulai dari pengiriman token-by-token dasar hingga transparansi tool call, pembaruan status, progressive disclosure, pemulihan error, dan pilihan transport layer.


Bagian 1: Dasar-Dasar Claude Streaming API

Sebelum Anda dapat melakukan streaming pada agen, Anda perlu melakukan streaming pada satu respons Claude. Mari mulai dengan dasar-dasarnya.

Stream vs. Non-Stream

Panggilan API non-streaming memblokir hingga seluruh respons dihasilkan, lalu mengembalikannya sekaligus. Panggilan streaming mengembalikan urutan peristiwa saat respons diproduksi, dimulai dari token pertama.

Perbedaan dalam kode sangat minimal. Perbedaan dalam pengalaman pengguna sangat besar.

Jenis-Jenis Event

Claude Streaming API memancarkan urutan terstruktur dari server-sent events:

  1. message_start — Berisi objek Message awal dengan metadata (model, role, penggunaan).
  2. content_block_start — Menandai awal blok konten (teks atau tool_use).
  3. content_block_delta — Berisi konten inkremental: fragmen teks atau JSON input alat sebagian.
  4. content_block_stop — Menandai akhir blok konten.
  5. message_delta — Pembaruan akhir pada pesan (alasan berhenti, penggunaan akhir).
  6. message_stop — Stream selesai.

Untuk respons teks, Anda akan menerima banyak event content_block_delta, masing-masing membawa potongan kecil teks (biasanya beberapa token).

Implementasi Streaming Dasar

Berikut contoh streaming sinkron menggunakan Anthropic Python SDK:

import anthropic
client = anthropic.Anthropic()
def stream_basic_response(user_message: str):
"""Stream a basic Claude response token by token."""
with client.messages.stream(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[{"role": "user", "content": user_message}],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
print() # Newline after stream completes
stream_basic_response("Explain quantum entanglement in simple terms.")

Dan versi async, yang akan Anda gunakan di server web produksi:

import anthropic
import asyncio
async_client = anthropic.AsyncAnthropic()
async def stream_basic_response_async(user_message: str):
"""Async streaming with the Anthropic SDK."""
async with async_client.messages.stream(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[{"role": "user", "content": user_message}],
) as stream:
async for text in stream.text_stream:
print(text, end="", flush=True)
print()
asyncio.run(stream_basic_response_async("Explain quantum entanglement in simple terms."))

Menangani Token Parsial dan Batas UTF-8

SDK menangani decoding UTF-8 untuk Anda, tetapi jika Anda bekerja dengan raw HTTP stream, perlu diketahui bahwa karakter multi-byte dapat terpisah di antara chunk. Selalu buffer raw bytes dan decode hanya ketika Anda memiliki urutan UTF-8 yang lengkap. Iterator text_stream dari SDK anthropic menangani ini secara otomatis—alasan lain untuk menggunakannya daripada mem-parsing raw SSE stream sendiri.


Bagian 2: Streaming Tool Calls

Streaming teks dasar adalah syarat minimum. Tantangan nyata—dan nilai nyata—datang ketika agen Anda menggunakan alat. Agen multi-langkah yang memanggil tiga alat secara berurutan bisa terasa mati selama eksekusi alat kecuali Anda menampilkan apa yang sedang terjadi.

Bagaimana Tool Call Muncul dalam Stream

Ketika Claude memutuskan untuk menggunakan alat, stream memancarkan event content_block_start dengan type: "tool_use", diikuti oleh event content_block_delta yang berisi fragmen JSON input alat. Setelah tool call lengkap dirakit, Anda mengeksekusi alat, menyuntikkan hasilnya, dan melanjutkan percakapan.

Wawasan kunci: Anda mengetahui nama alat segera setelah content_block_start tiba, bahkan sebelum JSON input lengkap. Ini berarti Anda dapat segera menampilkan sesuatu kepada pengguna seperti ”🔍 Mencari database pesanan…” tanpa menunggu tool call penuh.

Loop Agen Streaming Lengkap

Berikut loop agen streaming lengkap yang menampilkan pemanggilan alat secara real time:

import anthropic
import json
client = anthropic.Anthropic()
# Define tools
tools = [
{
"name": "search_orders",
"description": "Search customer orders by order ID or customer email.",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Order ID or email"},
},
"required": ["query"],
},
},
{
"name": "get_shipping_status",
"description": "Get real-time shipping status for an order.",
"input_schema": {
"type": "object",
"properties": {
"order_id": {"type": "string", "description": "The order ID"},
},
"required": ["order_id"],
},
},
]
def execute_tool(tool_name: str, tool_input: dict) -> str:
"""Execute a tool and return the result as a string."""
if tool_name == "search_orders":
# Simulated database lookup
return json.dumps({
"order_id": "ORD-12345",
"customer": "jane@example.com",
"items": ["Blue Widget x2", "Red Gadget x1"],
"total": "$47.99",
"status": "shipped",
})
elif tool_name == "get_shipping_status":
return json.dumps({
"order_id": tool_input["order_id"],
"carrier": "FedEx",
"tracking": "7891011",
"estimated_delivery": "2026-03-10",
"current_location": "Memphis, TN",
})
return json.dumps({"error": f"Unknown tool: {tool_name}"})
def stream_agent_response(user_message: str):
"""
Complete streaming agent loop with real-time tool call display.
"""
messages = [{"role": "user", "content": user_message}]
while True:
# Stream the model response
tool_calls = []
current_tool = None
with client.messages.stream(
model="claude-sonnet-4-20250514",
max_tokens=4096,
tools=tools,
messages=messages,
) as stream:
response = None
for event in stream:
# The SDK exposes raw events via iteration
pass
# Use the helper to collect text and tool use
response = stream.get_final_message()
# Process the response content blocks
for block in response.content:
if block.type == "text":
print(block.text, end="", flush=True)
elif block.type == "tool_use":
tool_name = block.name
tool_input = block.input
tool_id = block.id
# Show the user what's happening
print(f"\n⚙️ Calling tool: {tool_name}({json.dumps(tool_input)})")
# Execute the tool
result = execute_tool(tool_name, tool_input)
print(f"✅ Result received from {tool_name}")
tool_calls.append({
"tool_use_id": tool_id,
"tool_name": tool_name,
"tool_input": tool_input,
"result": result,
})
# If the model stopped because it wants to use tools, continue the loop
if response.stop_reason == "tool_use":
# Add assistant message with all content blocks
messages.append({"role": "assistant", "content": response.content})
# Add tool results
tool_results = []
for tc in tool_calls:
tool_results.append({
"type": "tool_result",
"tool_use_id": tc["tool_use_id"],
"content": tc["result"],
})
messages.append({"role": "user", "content": tool_results})
print("\n--- Continuing agent loop ---")
else:
# Model is done (stop_reason == "end_turn")
print()
break
# Run the agent
stream_agent_response("Where is my order #ORD-12345? When will it arrive?")

Untuk streaming token-by-token yang sebenarnya dengan deteksi tool call, Anda dapat mengiterasi event mentah:

def stream_agent_with_live_tokens(user_message: str):
"""Stream text tokens live while also detecting tool calls."""
messages = [{"role": "user", "content": user_message}]
while True:
collected_content = []
current_text = ""
current_tool_name = None
current_tool_input_json = ""
current_tool_id = None
stop_reason = None
with client.messages.stream(
model="claude-sonnet-4-20250514",
max_tokens=4096,
tools=tools,
---
## Artikel Terkait
- [Pola Penggunaan Alat: Membangun Antarmuka Agen-Alat yang Andal](/id/blog/agent-tool-use-patterns/)
- [Pola Multi-Agen: Orkestrator, Pekerja, dan Pipeline](/id/blog/multi-agent-patterns/)
- [Pemulihan Error Agen: 5 Pola untuk Keandalan Produksi](/id/blog/agent-error-recovery-patterns/)
- [Agen Otomasi Web: Kontrol Browser dengan Claude dan Computer Use](/id/blog/web-automation-agents-browser-control-with-claude-and-computer-use/)