ทำความเข้าใจการส่งโทเค็นแบบต่อเนื่อง
ทำความเข้าใจว่า API แบบต่อเนื่องส่งผลลัพธ์บางส่วนขณะสร้างอย่างไร พารามิเตอร์ stream=True ของ OpenAI ทำงานอย่างไร และการส่งแบบต่อเนื่องช่วยปรับปรุงประสบการณ์ผู้ใช้เมื่อใด
ทำความเข้าใจการส่งโทเค็นแบบต่อเนื่อง เป็นบทเรียน AI Engineering Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน AI Engineering Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส AI Engineering Academy มีบทเรียนทั้งหมด 4 บทเรียน
เหตุใดการสตรีมจึงสำคัญต่อประสบการณ์ผู้ใช้
หากไม่มีการสตรีม แอปพลิเคชันของคุณต้องรอให้ LLM สร้างคำตอบเสร็จสมบูรณ์ก่อนจึงจะแสดงสิ่งใดได้ — สำหรับคำตอบยาว ๆ มักใช้เวลา 5-30 วินาที เมื่อใช้การสตรีม โทเค็นแรกจะปรากฏภายใน 200-500 มิลลิวินาทีหลังส่งคำขอ และโทเค็นถัดไปจะทยอยเข้ามาขณะที่ระบบสร้างโทเค็นเหล่านั้น สิ่งนี้เปลี่ยนประสบการณ์ที่ผู้ใช้รับรู้จากการรอคอยให้เป็นเอฟเฟกต์การสร้างแบบสดที่น่ามีส่วนร่วม ช่วยเพิ่มการตอบสนองที่รับรู้ได้อย่างมาก แม้เวลาในการสร้างทั้งหมดจะเท่าเดิม
LLM สร้างโทเค็นอย่างไร
LLM มีลักษณะ ถดถอยอัตโนมัติ: สร้างข้อความทีละโทเค็น โดยโทเค็นใหม่แต่ละตัวขึ้นอยู่กับโทเค็นก่อนหน้าทั้งหมด เมื่อ API ได้รับคำขอ GPU จะเริ่มสุ่มตัวอย่างโทเค็นแรกทันทีหลังประมวลผลคำสั่งแล้ว โทเค็นถัดไปแต่ละตัวใช้เวลาใกล้เคียงกัน การสตรีมจะส่งโทเค็นแต่ละตัวไปยังไคลเอ็นต์ทันทีที่สุ่มตัวอย่างเสร็จ แทนที่จะเก็บโทเค็นทั้งหมดไว้แล้วส่งสตริงสมบูรณ์ในตอนท้าย
# Conceptual model of autoregressive generation
prompt = 'The capital of France is'
# Step 1: process full prompt, predict next token
# token_1 = sample(logits) → ' Paris'
# Step 2: append token_1 to context, predict next
# token_2 = sample(logits) → '.'
# Step 3: append token_2 to context, predict next
# token_3 = sample(logits) → '<|end|>'
# Total time: time_to_process_prompt + n_tokens * time_per_token
# With streaming: first token arrives after time_to_process_prompt (TTFT)
# Without streaming: everything arrives after TTFT + n_tokens * time_per_tokenTTFT และ TPOT: เมตริกเวลาแฝงสองรายการ
การสตรีมทำให้เกิดแนวคิดเกี่ยวกับเวลาแฝงสองแบบที่แตกต่างกัน TTFT (เวลาจนถึงโทเค็นแรก)คือช่วงหน่วงตั้งแต่ส่งคำขอจนได้รับโทเค็นแรก ซึ่งส่วนใหญ่เกิดจากเวลาประมวลผลคำสั่ง TPOT (เวลาต่อโทเค็นผลลัพธ์)คือช่วงเวลาระหว่างโทเค็นที่ต่อเนื่องกัน ซึ่งกำหนดโดยขนาดโมเดลและฮาร์ดแวร์ TTFT ส่งผลต่อความเร็วที่ส่วนติดต่อผู้ใช้ตอบสนอง ส่วน TPOT ส่งผลต่อความราบรื่นของการสตรีมข้อความ ควรติดตามทั้งสองค่าแยกจากกันในระบบสังเกตการณ์ของคุณ
import time
from openai import OpenAI
client = OpenAI()
def measure_streaming_latency(prompt: str):
t_start = time.perf_counter()
t_first_token = None
token_times = []
stream = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': prompt}],
stream=True,
)
for chunk in stream:
if chunk.choices[0].delta.content:
t_now = time.perf_counter()
if t_first_token is None:
t_first_token = t_now
print(f'TTFT: {(t_first_token - t_start) * 1000:.0f}ms')
else:
token_times.append(t_now - token_times[-1] if token_times else t_now - t_first_token)
token_times.append(t_now)
print(f'TPOT avg: {1000 * (token_times[-1] - t_first_token) / max(len(token_times)-1, 1):.1f}ms')พารามิเตอร์ stream=True
การเปิดใช้การสตรีมใน OpenAI SDK ต้องกำหนด stream=True ในการเรียก chat.completions.create ประเภทของคำตอบจะเปลี่ยนจากอ็อบเจกต์ ChatCompletion เป็นตัววนซ้ำ Stream[ChatCompletionChunk] แต่ละส่วนมี delta ซึ่งประกอบด้วยส่วนย่อยของสตริง content หรือ None เมื่อโทเค็นเป็นการเรียกเครื่องมือหรือเมื่อสตรีมกำลังสิ้นสุด
from openai import OpenAI
client = OpenAI()
# Non-streaming: wait for complete response
response = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': 'Explain RAG in one paragraph.'}],
)
full_text = response.choices[0].message.content
# Streaming: receive tokens incrementally
stream = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': 'Explain RAG in one paragraph.'}],
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta: # delta can be None for non-content chunks
print(delta, end='', flush=True)
print() # newline at endการสะสมคำตอบฉบับเต็ม
ในลำดับการทำงานของแอปพลิเคชันหลายกรณี คุณต้องทำทั้งการสตรีมโทเค็นไปยังส่วนติดต่อผู้ใช้เพื่อให้ตอบสนองได้รวดเร็ว และสะสมข้อความคำตอบฉบับเต็มไว้ประมวลผลต่อ เช่น การบันทึก การแคช หรือขั้นตอนถัดไปของไปป์ไลน์ รูปแบบนี้เรียบง่าย: วนซ้ำผ่านสตรีม พิมพ์หรือส่งต่อแต่ละส่วนไปยังไคลเอ็นต์ และต่อเนื้อหาเข้าด้วยกันเป็นสตริงฉบับเต็มในเวลาเดียวกัน
def stream_and_accumulate(prompt: str) -> str:
stream = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': prompt}],
stream=True,
)
full_text = ''
finish_reason = None
for chunk in stream:
choice = chunk.choices[0]
delta = choice.delta.content
if delta:
print(delta, end='', flush=True) # real-time display
full_text += delta # accumulate
if choice.finish_reason:
finish_reason = choice.finish_reason
print() # newline
print(f'Finished: {finish_reason}, total chars: {len(full_text)}')
return full_textการสตรีมพร้อมสถิติการใช้งาน
ตามค่าเริ่มต้น คำตอบแบบสตรีมจะไม่รวมสถิติการใช้โทเค็น (โทเค็นคำสั่ง โทเค็นคำตอบ) หากต้องการรวมข้อมูลดังกล่าว ให้ส่ง stream_options={'include_usage': True} ข้อมูลการใช้งานจะมาถึงในส่วนสุดท้ายหลังจากสตรีมเนื้อหาสิ้นสุดลง ข้อมูลนี้สำคัญต่อการติดตามค่าใช้จ่ายและการตรวจสอบขีดจำกัดอัตราในแอปพลิเคชันที่ใช้งานจริง
stream = client.chat.completions.create(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': 'What is a vector database?'}],
stream=True,
stream_options={'include_usage': True}, # include token counts
)
full_text = ''
usage = None
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
full_text += chunk.choices[0].delta.content
if chunk.usage: # arrives in the final chunk
usage = chunk.usage
if usage:
print(f'Prompt tokens: {usage.prompt_tokens}')
print(f'Completion tokens: {usage.completion_tokens}')
print(f'Total tokens: {usage.total_tokens}')เมื่อใดที่ไม่ควรใช้การสตรีม
การสตรีมไม่ใช่ตัวเลือกที่เหมาะสมเสมอไป ควรหลีกเลี่ยงการสตรีมเมื่อ: (1) คุณต้องการ คำตอบฉบับสมบูรณ์ก่อนดำเนินการใด ๆกับคำตอบนั้น เช่น การแยกวิเคราะห์ JSON หรือการตรวจจับการเรียกเครื่องมือ (2) คำตอบสั้นมาก (น้อยกว่า 30 โทเค็น) ซึ่งค่าใช้จ่ายของการสตรีมทำให้เกิดความล่าช้ามากกว่าที่ช่วยลดได้ หรือ (3) คุณกำลัง ประมวลผลเป็นชุดคำขอจำนวนมาก ซึ่งอัตราการประมวลผลสำคัญกว่าเวลาแฝงของคำตอบแต่ละรายการ ในกรณีเหล่านี้ การเรียกแบบไม่สตรีมมาตรฐานจะเรียบง่ายกว่าและรวดเร็วไม่ต่างกัน
การสตรีมด้วย API ของ Anthropic และ Gemini
การสตรีมมีให้ใช้ใน API ของผู้ให้บริการ LLM รายใหญ่ทั้งหมด ไม่ใช่เฉพาะ OpenAI รูปแบบการใช้งานคล้ายกัน แต่ส่วนติดต่อของ SDK แตกต่างกันเล็กน้อย SDK ภาษา Python ของ Anthropic ใช้ client.messages.stream() เป็นตัวจัดการบริบท ขณะที่ Gemini ใช้ generate_content(stream=True) เมื่อสร้างแอปพลิเคชันที่ไม่ขึ้นกับผู้ให้บริการ ให้แยกส่วนติดต่อการสตรีมไว้เบื้องหลังฟังก์ชันสร้างตัววนซ้ำร่วมกัน
import anthropic
ant_client = anthropic.Anthropic(api_key='YOUR_KEY')
# Anthropic streaming
with ant_client.messages.stream(
model='claude-sonnet-4-5',
max_tokens=1024,
messages=[{'role': 'user', 'content': 'Explain hybrid search briefly.'}],
) as stream:
for text in stream.text_stream:
print(text, end='', flush=True)
# Final message with usage stats
final_msg = stream.get_final_message()
print(f'\nInput tokens: {final_msg.usage.input_tokens}')
print(f'Output tokens: {final_msg.usage.output_tokens}')ส่วนติดต่อการสตรีมที่อาศัยตัวสร้าง
รูปแบบสถาปัตยกรรมที่เป็นระเบียบคือห่อการสตรีมไว้ใน ฟังก์ชันตัวสร้างของ Pythonที่ส่งคืนสตริงโทเค็นด้วยคำสั่ง yield วิธีนี้แยกตรรกะการสตรีมออกจากตรรกะการนำไปใช้ ผู้เรียกจึงสามารถวนซ้ำผ่านตัวสร้าง เขียนลงไฟล์ ส่งต่อไปยัง WebSocket หรือสะสมเป็นสตริงได้ โดยโค้ดการสตรีมไม่จำเป็นต้องรู้ว่าจะนำผลลัพธ์ไปใช้อย่างไร นี่คือรากฐานของ API แบบสตรีมที่ใช้งานจริงส่วนใหญ่
from typing import Generator
def stream_completion(
messages: list[dict],
model: str = 'gpt-4o-mini',
**kwargs,
) -> Generator[str, None, None]:
stream = client.chat.completions.create(
model=model,
messages=messages,
stream=True,
**kwargs,
)
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
yield delta
# Usage: pipe to stdout
for token in stream_completion([{'role': 'user', 'content': 'Hello!'}]):
print(token, end='', flush=True)
# Usage: accumulate
full = ''.join(stream_completion([{'role': 'user', 'content': 'Hello!'}]))การสตรีมในแอปพลิเคชันเทอร์มินัลและ CLI
ในแอปพลิเคชันเทอร์มินัล ผลลัพธ์แบบสตรีมจะดูเหมือนการพิมพ์ โดยอักขระแต่ละตัวปรากฏทันทีที่สร้างเสร็จ ข้อกำหนดสำคัญคือการใช้ flush=True ในการเรียก print ทุกครั้ง หากไม่ล้างบัฟเฟอร์ Python จะเก็บผลลัพธ์ไว้จนกว่าจะพบการขึ้นบรรทัดใหม่ ซึ่งทำให้จุดประสงค์ของการสตรีมสูญเปล่า คุณยังสามารถใช้ sys.stdout.write(token) ตามด้วย sys.stdout.flush() เพื่อควบคุมรูปแบบผลลัพธ์ได้มากขึ้น
import sys
def stream_to_terminal(messages: list[dict]):
stream = client.chat.completions.create(
model='gpt-4o-mini',
messages=messages,
stream=True,
)
token_count = 0
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
sys.stdout.write(delta) # no newline added
sys.stdout.flush() # MUST flush or output buffers
token_count += 1
print() # final newline
print(f'({token_count} tokens generated)')การสตรีมและการกู้คืนจากข้อผิดพลาด
การสตรีมทำให้การจัดการข้อผิดพลาดซับซ้อนขึ้น เนื่องจากความล้มเหลวอาจเกิดขึ้นระหว่างสตรีม หลังจากที่คุณส่งโทเค็นบางส่วนไปยังไคลเอ็นต์แล้ว รูปแบบที่แนะนำคือห่อการวนซ้ำผ่านสตรีมไว้ในบล็อก try/except และเมื่อเกิดข้อผิดพลาด ให้ส่งสัญญาณข้อผิดพลาดไปยังไคลเอ็นต์หรือปิดสตรีมอย่างเรียบร้อย ควรใช้ การหมดเวลากับสตรีมโดยรวมเสมอ เพื่อรองรับกรณีที่เซิร์ฟเวอร์เริ่มสตรีมแล้วหยุดลงระหว่างการสร้าง
import signal
def stream_with_timeout(messages, timeout_seconds=30):
def timeout_handler(signum, frame):
raise TimeoutError('LLM stream timed out')
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(timeout_seconds)
try:
stream = client.chat.completions.create(
model='gpt-4o-mini',
messages=messages,
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
yield delta
except TimeoutError:
yield '\n[Response timed out]'
except Exception as e:
yield f'\n[Error: {str(e)}]'
finally:
signal.alarm(0) # cancel timeoutตรวจสอบความเข้าใจอย่างรวดเร็ว
ทดสอบความเข้าใจของคุณเกี่ยวกับการสตรีมโทเค็นของ LLM จากบทเรียนนี้
ทบทวนบทเรียน
ในบทเรียนนี้ คุณได้เรียนรู้ว่า การสตรีมจะส่งโทเค็นแต่ละรายการที่สร้างขึ้นไปยังไคลเอ็นต์ทันทีที่สุ่มตัวอย่างเสร็จ ซึ่งช่วยเพิ่มความรู้สึกว่าระบบตอบสนองได้อย่างมาก TTFT และ TPOT เป็นเมตริกความหน่วงสำคัญสองรายการที่ควรติดตามแยกกัน และ stream=True จะเปลี่ยนการตอบกลับของ OpenAI SDK ให้เป็นตัววนซ้ำของชังก์ ซึ่งคุณประมวลผลด้วยลูป for ได้ ควรห่อสตรีมไว้ในฟังก์ชันตัวสร้างเพื่อให้มีอินเทอร์เฟซที่สะอาดและนำกลับมาใช้ใหม่ได้ บทถัดไป เราจะลงมือทำการสตรีมแบบอะซิงโครนัสด้วย Python SDK
คำถามที่พบบ่อย
บทเรียน “ทำความเข้าใจการส่งโทเค็นแบบต่อเนื่อง” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “ทำความเข้าใจการส่งโทเค็นแบบต่อเนื่อง” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส AI Engineering Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส AI Engineering Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “ทำความเข้าใจการส่งโทเค็นแบบต่อเนื่อง”
ทำความเข้าใจว่า API แบบต่อเนื่องส่งผลลัพธ์บางส่วนขณะสร้างอย่างไร พารามิเตอร์ stream=True ของ OpenAI ทำงานอย่างไร และการส่งแบบต่อเนื่องช่วยปรับปรุงประสบการณ์ผู้ใช้เมื่อใด คุณปฏิบัติ AI Engineering Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน AI Engineering Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน AI Engineering Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน
บทเรียน “ทำความเข้าใจการส่งโทเค็นแบบต่อเนื่อง” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน AI Engineering Academy นี้ได้ไหม
ได้ บทเรียน AI Engineering Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- ทำความเข้าใจการส่งโทเค็นแบบต่อเนื่อง
- การรับข้อมูลแบบต่อเนื่องด้วย Python SDK
- การส่งข้อมูลแบบต่อเนื่องใน FastAPI ด้วยเหตุการณ์ที่ส่งจากเซิร์ฟเวอร์
- การจัดการการเรียกใช้เครื่องมือในการตอบกลับแบบต่อเนื่อง