{"product_id":"claude-streaming-api-real-time-response-cho-ứng-dụng-chat","title":"Claude Streaming API — Real-time response cho ứng dụng chat","description":"\n\u003cp\u003eKhi người dùng gửi một câu hỏi cho Claude, họ không muốn đợi 10-30 giây để nhận toàn bộ câu trả lời. Streaming API giải quyết vấn đề này bằng cách gửi từng phần (token) của câu trả lời ngay khi chúng được tạo ra, giống như cách ChatGPT hay Claude.ai hiển thị text \"chạy\" từ từ trên màn hình. Bài viết này hướng dẫn bạn triển khai Streaming API từ backend đến frontend.\u003c\/p\u003e\n\n\u003ch2\u003eTại sao cần Streaming?\u003c\/h2\u003e\n\u003cp\u003eCó ba lý do chính khiến streaming trở thành tiêu chuẩn cho ứng dụng AI chat:\u003c\/p\u003e\n\u003cul\u003e\n  \u003cli\u003e\n\u003cstrong\u003eTime to First Token (TTFT):\u003c\/strong\u003e Với non-streaming, người dùng không thấy gì cho đến khi toàn bộ response hoàn tất (có thể 10-30 giây). Với streaming, token đầu tiên xuất hiện trong 0.5-2 giây, tạo cảm giác phản hồi tức thì\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003ePerceived performance:\u003c\/strong\u003e Dù tổng thời gian tạo response là như nhau, streaming khiến người dùng cảm thấy ứng dụng nhanh hơn vì họ bắt đầu đọc ngay khi text xuất hiện\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eEarly abort:\u003c\/strong\u003e Người dùng có thể đọc phần đầu response và hủy nếu câu trả lời không đúng hướng, tiết kiệm token và chi phí API\u003c\/li\u003e\n\u003c\/ul\u003e\n\n\u003ch2\u003eServer-Sent Events (SSE) — Nền tảng của Streaming\u003c\/h2\u003e\n\u003cp\u003eClaude Streaming API sử dụng Server-Sent Events (SSE), một giao thức web chuẩn cho phép server push data đến client qua HTTP connection đơn hướng.\u003c\/p\u003e\n\u003cp\u003eKhác biệt với WebSocket (two-way communication), SSE là one-way: server gửi events đến client. Điều này phù hợp hoàn hảo cho AI chat vì client gửi 1 request và server stream response về.\u003c\/p\u003e\n\n\u003ch3\u003eCấu trúc SSE event từ Claude API\u003c\/h3\u003e\n\u003cp\u003eClaude API gửi các event types sau trong quá trình streaming:\u003c\/p\u003e\n\u003cul\u003e\n  \u003cli\u003e\n\u003cstrong\u003emessage_start:\u003c\/strong\u003e Bắt đầu message, chứa metadata (model, usage)\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003econtent_block_start:\u003c\/strong\u003e Bắt đầu một content block (text hoặc tool_use)\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003econtent_block_delta:\u003c\/strong\u003e Phần nội dung tiếp theo (delta text)\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003econtent_block_stop:\u003c\/strong\u003e Kết thúc content block\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003emessage_delta:\u003c\/strong\u003e Metadata cuối message (stop_reason, usage)\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003emessage_stop:\u003c\/strong\u003e Kết thúc toàn bộ message\u003c\/li\u003e\n\u003c\/ul\u003e\n\n\u003ch2\u003eTriển khai với Python\u003c\/h2\u003e\n\n\u003ch3\u003eCài đặt\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003epip install anthropic\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch3\u003eStreaming cơ bản với Python SDK\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003eimport anthropic\n\nclient = anthropic.Anthropic()\n\n# Streaming cơ bản\nwith client.messages.stream(\n    model=\"claude-sonnet-4-20250514\",\n    max_tokens=1024,\n    messages=[\n        {\"role\": \"user\", \"content\": \"Giải thích blockchain trong 5 câu.\"}\n    ]\n) as stream:\n    for text in stream.text_stream:\n        print(text, end=\"\", flush=True)\n\nprint()  # Newline sau khi stream kết thúc\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch3\u003eStreaming với event handling chi tiết\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003eimport anthropic\n\nclient = anthropic.Anthropic()\n\n# Xử lý từng event type\nwith client.messages.stream(\n    model=\"claude-sonnet-4-20250514\",\n    max_tokens=1024,\n    messages=[\n        {\"role\": \"user\", \"content\": \"Viết đoạn code Python sort algorithm.\"}\n    ]\n) as stream:\n    for event in stream:\n        if event.type == \"content_block_delta\":\n            if event.delta.type == \"text_delta\":\n                print(event.delta.text, end=\"\", flush=True)\n        elif event.type == \"message_start\":\n            print(f\"[Model: {event.message.model}]\")\n        elif event.type == \"message_delta\":\n            print(f\"\n[Tokens used: {event.usage.output_tokens}]\")\n        elif event.type == \"message_stop\":\n            print(\"\n[Stream completed]\")\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch3\u003eStreaming trong FastAPI backend\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003efrom fastapi import FastAPI\nfrom fastapi.responses import StreamingResponse\nimport anthropic\nimport json\n\napp = FastAPI()\nclient = anthropic.Anthropic()\n\nasync def generate_stream(user_message: str):\n    \"\"\"Generator function that yields SSE events to client.\"\"\"\n    with client.messages.stream(\n        model=\"claude-sonnet-4-20250514\",\n        max_tokens=2048,\n        messages=[\n            {\"role\": \"user\", \"content\": user_message}\n        ]\n    ) as stream:\n        for text in stream.text_stream:\n            # Format as SSE event\n            data = json.dumps({\"type\": \"text\", \"content\": text})\n            yield f\"data: {data}\n\n\"\n\n    # Signal stream end\n    yield f\"data: {json.dumps({'type': 'done'})}\n\n\"\n\n@app.post(\"\/api\/chat\")\nasync def chat(request: dict):\n    user_message = request.get(\"message\", \"\")\n    return StreamingResponse(\n        generate_stream(user_message),\n        media_type=\"text\/event-stream\",\n        headers={\n            \"Cache-Control\": \"no-cache\",\n            \"Connection\": \"keep-alive\",\n            \"X-Accel-Buffering\": \"no\",  # Disable nginx buffering\n        }\n    )\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eTriển khai với Node.js\u003c\/h2\u003e\n\n\u003ch3\u003eCài đặt\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003enpm install @anthropic-ai\/sdk\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch3\u003eStreaming cơ bản với Node.js SDK\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003eimport Anthropic from \"@anthropic-ai\/sdk\";\n\nconst client = new Anthropic();\n\nasync function main() {\n  const stream = client.messages.stream({\n    model: \"claude-sonnet-4-20250514\",\n    max_tokens: 1024,\n    messages: [\n      { role: \"user\", content: \"Giải thích AI trong 5 câu.\" }\n    ]\n  });\n\n  \/\/ Event-based handling\n  stream.on(\"text\", (text) =\u0026gt; {\n    process.stdout.write(text);\n  });\n\n  stream.on(\"message\", (message) =\u0026gt; {\n    console.log(\"\n[Total tokens:\", message.usage.output_tokens, \"]\");\n  });\n\n  \/\/ Wait for stream to finish\n  const finalMessage = await stream.finalMessage();\n  console.log(\"[Stop reason:\", finalMessage.stop_reason, \"]\");\n}\n\nmain();\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch3\u003eStreaming trong Express.js backend\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003eimport express from \"express\";\nimport Anthropic from \"@anthropic-ai\/sdk\";\n\nconst app = express();\nconst client = new Anthropic();\n\napp.use(express.json());\n\napp.post(\"\/api\/chat\", async (req, res) =\u0026gt; {\n  const { message } = req.body;\n\n  \/\/ Set SSE headers\n  res.setHeader(\"Content-Type\", \"text\/event-stream\");\n  res.setHeader(\"Cache-Control\", \"no-cache\");\n  res.setHeader(\"Connection\", \"keep-alive\");\n  res.setHeader(\"X-Accel-Buffering\", \"no\");\n\n  try {\n    const stream = client.messages.stream({\n      model: \"claude-sonnet-4-20250514\",\n      max_tokens: 2048,\n      messages: [{ role: \"user\", content: message }]\n    });\n\n    stream.on(\"text\", (text) =\u0026gt; {\n      const data = JSON.stringify({ type: \"text\", content: text });\n      res.write(\"data: \" + data + \"\\n\\n\");\n    });\n\n    stream.on(\"error\", (error) =\u0026gt; {\n      const data = JSON.stringify({\n        type: \"error\",\n        content: error.message\n      });\n      res.write(\"data: \" + data + \"\\n\\n\");\n      res.end();\n    });\n\n    stream.on(\"end\", () =\u0026gt; {\n      res.write(\"data: \" + JSON.stringify({ type: \"done\" }) + \"\\n\\n\");\n      res.end();\n    });\n\n    \/\/ Handle client disconnect\n    req.on(\"close\", () =\u0026gt; {\n      stream.abort();\n    });\n  } catch (error) {\n    res.write(\"data: \" + JSON.stringify({\n      type: \"error\",\n      content: error.message\n    }) + \"\\n\\n\");\n    res.end();\n  }\n});\n\napp.listen(3000, () =\u0026gt; console.log(\"Server running on port 3000\"));\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eFrontend: Render streaming response\u003c\/h2\u003e\n\n\u003ch3\u003eVanilla JavaScript với EventSource\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003e\/\/ Frontend code - kết nối với SSE endpoint\nasync function sendMessage(userMessage) {\n  const responseDiv = document.getElementById(\"response\");\n  responseDiv.textContent = \"\";\n\n  const response = await fetch(\"\/api\/chat\", {\n    method: \"POST\",\n    headers: { \"Content-Type\": \"application\/json\" },\n    body: JSON.stringify({ message: userMessage })\n  });\n\n  const reader = response.body.getReader();\n  const decoder = new TextDecoder();\n\n  while (true) {\n    const { done, value } = await reader.read();\n    if (done) break;\n\n    const chunk = decoder.decode(value);\n    const lines = chunk.split(\"\n\");\n\n    for (const line of lines) {\n      if (line.startsWith(\"data: \")) {\n        const data = JSON.parse(line.slice(6));\n\n        if (data.type === \"text\") {\n          responseDiv.textContent += data.content;\n        } else if (data.type === \"done\") {\n          console.log(\"Stream completed\");\n        } else if (data.type === \"error\") {\n          responseDiv.textContent += \"\n[Error: \" + data.content + \"]\";\n        }\n      }\n    }\n  }\n}\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch3\u003eUI rendering patterns\u003c\/h3\u003e\n\u003cp\u003eKhi render streaming text, có một số patterns quan trọng để UI mượt mà:\u003c\/p\u003e\n\u003cul\u003e\n  \u003cli\u003e\n\u003cstrong\u003eAppend-only rendering:\u003c\/strong\u003e Chỉ append text mới, không re-render toàn bộ nội dung mỗi khi nhận token mới. Với React, dùng ref thay vì state cho text accumulation\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eMarkdown rendering:\u003c\/strong\u003e Nếu response chứa Markdown, cần render incremental. Thư viện như marked hoặc markdown-it có thể parse partial Markdown\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eAuto-scroll:\u003c\/strong\u003e Tự động scroll xuống cuối khi text mới xuất hiện, nhưng dừng auto-scroll nếu user đã scroll lên để đọc\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eCursor animation:\u003c\/strong\u003e Hiển thị blinking cursor ở cuối text đang stream để cho thấy response chưa hoàn tất\u003c\/li\u003e\n\u003c\/ul\u003e\n\n\u003ch2\u003eXử lý lỗi Mid-Stream (Error Recovery)\u003c\/h2\u003e\n\u003cp\u003eStreaming có thể bị gián đoạn giữa chừng do network issues, rate limiting, hoặc server errors. Cần xử lý graceful.\u003c\/p\u003e\n\n\u003ch3\u003eCác loại lỗi thường gặp\u003c\/h3\u003e\n\u003cul\u003e\n  \u003cli\u003e\n\u003cstrong\u003eNetwork disconnect:\u003c\/strong\u003e Mất kết nối internet giữa chừng\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eRate limiting (429):\u003c\/strong\u003e Vượt quá giới hạn requests per minute\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eOverloaded (529):\u003c\/strong\u003e Server Claude đang quá tải\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eTimeout:\u003c\/strong\u003e Response quá dài, vượt quá timeout setting\u003c\/li\u003e\n\u003c\/ul\u003e\n\n\u003ch3\u003eStrategy xử lý lỗi\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003eimport anthropic\nimport time\n\ndef stream_with_retry(messages, max_retries=3):\n    \"\"\"Stream with exponential backoff retry.\"\"\"\n    client = anthropic.Anthropic()\n    accumulated_text = \"\"\n\n    for attempt in range(max_retries):\n        try:\n            with client.messages.stream(\n                model=\"claude-sonnet-4-20250514\",\n                max_tokens=4096,\n                messages=messages\n            ) as stream:\n                for text in stream.text_stream:\n                    accumulated_text += text\n                    yield text\n\n            # Stream completed successfully\n            return\n\n        except anthropic.APIStatusError as e:\n            if e.status_code == 429:\n                # Rate limited - wait and retry\n                wait_time = 2 ** attempt\n                print(f\"\nRate limited. Waiting {wait_time}s...\")\n                time.sleep(wait_time)\n            elif e.status_code == 529:\n                # Overloaded - wait longer\n                wait_time = 5 * (attempt + 1)\n                print(f\"\nServer overloaded. Waiting {wait_time}s...\")\n                time.sleep(wait_time)\n            else:\n                raise\n\n        except anthropic.APIConnectionError:\n            # Network error - retry with accumulated context\n            wait_time = 2 ** attempt\n            print(f\"\nConnection lost. Retrying in {wait_time}s...\")\n            time.sleep(wait_time)\n\n            if accumulated_text:\n                # Continue from where we left off\n                messages = messages + [\n                    {\"role\": \"assistant\", \"content\": accumulated_text},\n                    {\"role\": \"user\", \"content\": \"Hãy tiếp tục từ chỗ bạn dừng lại.\"}\n                ]\n\n    raise Exception(\"Max retries exceeded\")\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eStreaming với Tool Use\u003c\/h2\u003e\n\u003cp\u003eKhi Claude sử dụng tools (function calling) trong streaming mode, flow phức tạp hơn vì response có thể chứa cả text blocks và tool_use blocks.\u003c\/p\u003e\n\n\u003ch3\u003eXử lý streaming tool use trong Python\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003eimport anthropic\nimport json\n\nclient = anthropic.Anthropic()\n\ntools = [\n    {\n        \"name\": \"get_weather\",\n        \"description\": \"Get current weather for a location\",\n        \"input_schema\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"location\": {\n                    \"type\": \"string\",\n                    \"description\": \"City name, e.g. 'Ho Chi Minh City'\"\n                }\n            },\n            \"required\": [\"location\"]\n        }\n    }\n]\n\ndef handle_tool_call(tool_name, tool_input):\n    \"\"\"Execute tool and return result.\"\"\"\n    if tool_name == \"get_weather\":\n        # Simulate API call\n        return {\"temperature\": 32, \"condition\": \"sunny\"}\n    return {\"error\": \"Unknown tool\"}\n\n# First stream - may contain tool use\ncurrent_tool_name = None\ncurrent_tool_input = \"\"\n\nwith client.messages.stream(\n    model=\"claude-sonnet-4-20250514\",\n    max_tokens=1024,\n    tools=tools,\n    messages=[\n        {\"role\": \"user\",\n         \"content\": \"Thời tiết TP.HCM hôm nay thế nào?\"}\n    ]\n) as stream:\n    for event in stream:\n        if event.type == \"content_block_start\":\n            if hasattr(event.content_block, \"type\"):\n                if event.content_block.type == \"tool_use\":\n                    current_tool_name = event.content_block.name\n                    current_tool_input = \"\"\n                    print(f\"[Calling tool: {current_tool_name}]\")\n\n        elif event.type == \"content_block_delta\":\n            if event.delta.type == \"text_delta\":\n                print(event.delta.text, end=\"\", flush=True)\n            elif event.delta.type == \"input_json_delta\":\n                current_tool_input += event.delta.partial_json\n\n        elif event.type == \"content_block_stop\":\n            if current_tool_name:\n                # Execute tool\n                tool_input = json.loads(current_tool_input)\n                result = handle_tool_call(\n                    current_tool_name, tool_input\n                )\n                print(f\"[Tool result: {result}]\")\n                current_tool_name = None\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eĐếm token trong quá trình Stream\u003c\/h2\u003e\n\u003cp\u003eTheo dõi token usage trong streaming giúp bạn kiểm soát chi phí và tuân thủ giới hạn context.\u003c\/p\u003e\n\n\u003cpre\u003e\u003ccode\u003eimport anthropic\n\nclient = anthropic.Anthropic()\n\nwith client.messages.stream(\n    model=\"claude-sonnet-4-20250514\",\n    max_tokens=1024,\n    messages=[\n        {\"role\": \"user\", \"content\": \"Viết bài thơ về Hà Nội.\"}\n    ]\n) as stream:\n    for event in stream:\n        if event.type == \"message_start\":\n            input_tokens = event.message.usage.input_tokens\n            print(f\"[Input tokens: {input_tokens}]\")\n\n        elif event.type == \"content_block_delta\":\n            if event.delta.type == \"text_delta\":\n                print(event.delta.text, end=\"\", flush=True)\n\n        elif event.type == \"message_delta\":\n            output_tokens = event.usage.output_tokens\n            print(f\"\n[Output tokens: {output_tokens}]\")\n\n            # Calculate cost (Claude Sonnet pricing)\n            input_cost = input_tokens * 3 \/ 1_000_000\n            output_cost = output_tokens * 15 \/ 1_000_000\n            total_cost = input_cost + output_cost\n            print(f\"[Estimated cost: ${total_cost:.4f}]\")\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eProduction Checklist\u003c\/h2\u003e\n\u003cp\u003eTrước khi deploy streaming API lên production, hãy kiểm tra các mục sau:\u003c\/p\u003e\n\n\u003ch3\u003eBackend\u003c\/h3\u003e\n\u003cul\u003e\n  \u003cli\u003e\n\u003cstrong\u003eTimeout configuration:\u003c\/strong\u003e Set timeout phù hợp (60-120 giây cho long responses). Đảm bảo reverse proxy (nginx, CloudFlare) không timeout sớm hơn\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eBuffering disabled:\u003c\/strong\u003e Nginx, CloudFlare và các proxy thường buffer response. Cần disable buffering cho SSE endpoints\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eConnection limits:\u003c\/strong\u003e Mỗi streaming connection giữ 1 HTTP connection mở. Cần tính toán concurrent connections\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eRate limiting:\u003c\/strong\u003e Implement rate limiting ở application level, không chỉ dựa vào Claude API rate limits\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eLogging:\u003c\/strong\u003e Log start\/end của mỗi stream, token usage, errors\u003c\/li\u003e\n\u003c\/ul\u003e\n\n\u003ch3\u003eFrontend\u003c\/h3\u003e\n\u003cul\u003e\n  \u003cli\u003e\n\u003cstrong\u003eCancel button:\u003c\/strong\u003e Cho phép user hủy stream giữa chừng (gọi AbortController)\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eLoading state:\u003c\/strong\u003e Hiển thị loading indicator trước khi token đầu tiên xuất hiện\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eError UI:\u003c\/strong\u003e Hiển thị error message thân thiện khi stream bị lỗi\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eReconnection:\u003c\/strong\u003e Tự động retry khi mất kết nối tạm thời\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eMemory management:\u003c\/strong\u003e Với conversation dài, cần quản lý DOM elements để tránh memory leak\u003c\/li\u003e\n\u003c\/ul\u003e\n\n\u003ch3\u003eNginx configuration cho SSE\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003e# nginx.conf - Cấu hình cho SSE streaming\nlocation \/api\/chat {\n    proxy_pass http:\/\/localhost:3000;\n    proxy_http_version 1.1;\n    proxy_set_header Connection \"\";\n\n    # Disable buffering for SSE\n    proxy_buffering off;\n    proxy_cache off;\n\n    # Timeout settings\n    proxy_read_timeout 120s;\n    proxy_send_timeout 120s;\n\n    # Disable gzip for SSE (can cause buffering)\n    gzip off;\n}\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eSo sánh Streaming vs Non-Streaming\u003c\/h2\u003e\n\u003cp\u003eKhông phải mọi use case đều cần streaming. Dưới đây là hướng dẫn khi nào nên dùng:\u003c\/p\u003e\n\u003cul\u003e\n  \u003cli\u003e\n\u003cstrong\u003eNên dùng Streaming:\u003c\/strong\u003e Chat interfaces, content generation, code generation — bất kỳ khi nào user nhìn vào response đang được tạo\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eKhông cần Streaming:\u003c\/strong\u003e Background processing, batch operations, API-to-API calls nơi không có người dùng chờ đợi\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eCân nhắc:\u003c\/strong\u003e Khi cần parse toàn bộ response trước khi xử lý (ví dụ: JSON output), streaming thêm complexity mà không nhiều benefit\u003c\/li\u003e\n\u003c\/ul\u003e\n\n\u003ch2\u003eStreaming với Extended Thinking\u003c\/h2\u003e\n\u003cp\u003eClaude hỗ trợ extended thinking (suy nghĩ sâu) kết hợp với streaming. Khi bật extended thinking, bạn sẽ nhận được thinking blocks trước content blocks.\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003eimport anthropic\n\nclient = anthropic.Anthropic()\n\nwith client.messages.stream(\n    model=\"claude-sonnet-4-20250514\",\n    max_tokens=16000,\n    thinking={\n        \"type\": \"enabled\",\n        \"budget_tokens\": 10000\n    },\n    messages=[\n        {\"role\": \"user\",\n         \"content\": \"Phân tích chiến lược kinh doanh cho startup edtech tại VN.\"}\n    ]\n) as stream:\n    for event in stream:\n        if event.type == \"content_block_start\":\n            if event.content_block.type == \"thinking\":\n                print(\"[Thinking...]\")\n            elif event.content_block.type == \"text\":\n                print(\"[Response:]\")\n\n        elif event.type == \"content_block_delta\":\n            if event.delta.type == \"thinking_delta\":\n                # Optionally show thinking to user\n                pass\n            elif event.delta.type == \"text_delta\":\n                print(event.delta.text, end=\"\", flush=True)\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eBước tiếp theo\u003c\/h2\u003e\n\u003cp\u003eBạn đã nắm được cách triển khai Claude Streaming API từ backend đến frontend, bao gồm xử lý lỗi, tool use, và token counting. Bước tiếp theo là tích hợp streaming vào ứng dụng chat của bạn và tối ưu trải nghiệm người dùng. Khám phá thêm các hướng dẫn kỹ thuật tại \u003ca href=\"\/collections\/nang-cao\"\u003eThư viện Nâng cao\u003c\/a\u003e.\u003c\/p\u003e\n","brand":"Minh Tuấn","offers":[{"title":"Default Title","offer_id":66959058534445,"sku":null,"price":0.0,"currency_code":"VND","in_stock":true}],"thumbnail_url":"\/\/cdn.shopify.com\/s\/files\/1\/0763\/9531\/5245\/files\/claude-streaming-api-real-time-response-cho-_ng-d_ng-chat.jpg?v=1782892440","url":"https:\/\/claudeae.com\/products\/claude-streaming-api-real-time-response-cho-%e1%bb%a9ng-d%e1%bb%a5ng-chat","provider":"Claude.vn","version":"1.0","type":"link"}