นี่คือข้อความของช่องทางปกติ (ไม่ใช่ token deltas)¶
OpenClaw มีเลเยอร์ “การสตรีม” แยกกันสองชั้น:
- Block streaming (ช่องทาง): ส่ง บล็อก ที่เสร็จสมบูรณ์ออกมาเมื่อผู้ช่วยเขียนเสร็จ เป็นข้อความปกติของช่องทาง (ไม่ใช่เดลตาโทเคน) การสตรีมร่างของ Telegram เป็นพื้นผิวการสตรีมแบบบางส่วนเพียงอย่างเดียว
- Token-ish streaming (เฉพาะ Telegram): อัปเดต บับเบิลแบบร่าง ด้วยข้อความบางส่วนระหว่างการสร้างคำตอบ; ข้อความสุดท้ายจะถูกส่งเมื่อจบ
ปัจจุบัน ไม่มีการสตรีมโทเคนจริง ไปยังข้อความของช่องทางภายนอก การสตรีมแบบร่างของ Telegram เป็นพื้นผิวการสตรีมบางส่วนเพียงอย่างเดียว สิ่งนี้ช่วยลด “สแปมบรรทัดเดียว” ขณะยังคงให้เอาต์พุตแบบค่อยเป็นค่อยไป
Block streaming (ข้อความของช่องทาง)¶
Block streaming จะส่งเอาต์พุตของผู้ช่วยเป็นชิ้นใหญ่ ๆ เมื่อพร้อมใช้งาน
Model output
└─ text_delta/events
├─ (blockStreamingBreak=text_end)
│ └─ chunker emits blocks as buffer grows
└─ (blockStreamingBreak=message_end)
└─ chunker flushes at message_end
└─ channel send (block replies)
คำอธิบายสัญลักษณ์:
text_delta/events: อีเวนต์สตรีมของโมเดล (อาจเบาบางสำหรับโมเดลที่ไม่สตรีม)chunker:EmbeddedBlockChunkerที่ใช้ขอบเขตต่ำ/สูง + ความต้องการจุดตัดchannel send: ข้อความขาออกจริง (การตอบแบบบล็อก)
การควบคุม:
agents.defaults.blockStreamingDefault:"on"/"off"(ปิดเป็นค่าเริ่มต้น)- การ override ต่อช่องทาง:
*.blockStreaming(และแบบต่อบัญชี) เพื่อบังคับ"on"/"off"ต่อช่องทาง agents.defaults.blockStreamingBreak:"text_end"หรือ"message_end"agents.defaults.blockStreamingChunk:{ minChars, maxChars, breakPreference? }agents.defaults.blockStreamingCoalesce:{ minChars?, maxChars?, idleMs? }(รวมบล็อกที่สตรีมก่อนส่ง)- เพดานสูงสุดของช่องทาง:
*.textChunkLimit(เช่นchannels.whatsapp.textChunkLimit) - โหมดการแบ่งชิ้นของช่องทาง:
*.chunkMode(lengthเป็นค่าเริ่มต้น,newlineจะแบ่งตามบรรทัดว่าง(ขอบเขตย่อหน้า)ก่อนแบ่งตามความยาว) - เพดานแบบ soft ของ Discord:
channels.discord.maxLinesPerMessage(ค่าเริ่มต้น 17) แบ่งคำตอบที่ยาวมากเพื่อหลีกเลี่ยงการตัดใน UI
ความหมายของขอบเขต:
text_end: สตรีมบล็อกทันทีที่ตัวแบ่งชิ้นปล่อยออกมา; flush ทุกครั้งที่text_endmessage_end: รอจนข้อความของผู้ช่วยเสร็จสิ้น แล้วจึง flush เอาต์พุตที่บัฟเฟอร์ไว้
message_end ยังคงใช้ตัวแบ่งชิ้นหากข้อความที่บัฟเฟอร์เกิน maxChars ดังนั้นจึงอาจปล่อยหลายชิ้นพร้อมกันในตอนท้าย
อัลกอริทึมการแบ่งชิ้น (ขอบเขตต่ำ/สูง)¶
การแบ่งชิ้นแบบบล็อกถูกทำโดย EmbeddedBlockChunker:
- ขอบเขตต่ำ: จะไม่ปล่อยจนกว่าบัฟเฟอร์ >=
minChars(เว้นแต่ถูกบังคับ) - ขอบเขตสูง: เลือกแบ่งก่อนถึง
maxChars; หากถูกบังคับ ให้แบ่งที่maxChars - ความต้องการจุดตัด:
paragraph→newline→sentence→whitespace→ แบ่งแบบแข็ง - โค้ดเฟนซ์: ห้ามแบ่งภายในเฟนซ์; เมื่อจำเป็นต้องแบ่งที่
maxCharsให้ปิดแล้วเปิดเฟนซ์ใหม่เพื่อคงความถูกต้องของ Markdown
maxChars จะถูกหนีบตาม textChunkLimit ของช่องทาง ดังนั้นจึงไม่สามารถเกินเพดานต่อช่องทางได้
การรวม (รวมบล็อกที่สตรีม)¶
เมื่อเปิด block streaming แล้ว OpenClaw สามารถ รวมชิ้นบล็อกที่ต่อเนื่องกัน ก่อนส่งออก เพื่อลด “สแปมบรรทัดเดี่ยว” ในขณะที่ยังให้เอาต์พุตแบบคืบหน้า สิ่งนี้ทำให้การตอบแบบหลายบับเบิลดูเป็นธรรมชาติมากขึ้น
- การรวมจะรอ ช่วงว่างที่ไม่มีกิจกรรม (
idleMs) ก่อน flush - บัฟเฟอร์ถูกจำกัดด้วย
maxCharsและจะ flush หากเกินค่า minCharsป้องกันไม่ให้ส่งชิ้นเล็ก ๆ จนกว่าจะมีข้อความสะสมเพียงพอ (การ flush ครั้งสุดท้ายจะส่งข้อความที่เหลือทั้งหมด)- ตัวเชื่อมถูกอนุมานจาก
blockStreamingChunk.breakPreference(paragraph→\n\n,newline→\n,sentence→ เว้นวรรค) - มีการ override ต่อช่องทางผ่าน
*.blockStreamingCoalesce(รวมถึงคอนฟิกแบบต่อบัญชี) - ค่าเริ่มต้นของการรวม
minCharsจะถูกปรับเป็น 1500 สำหรับ Signal/Slack/Discord เว้นแต่จะมีการ override
จังหวะเหมือนมนุษย์ระหว่างบล็อก¶
เมื่อเปิด block streaming คุณสามารถเพิ่ม การหน่วงแบบสุ่ม ระหว่าง การตอบแบบบล็อก (หลังบล็อกแรก) เพื่อให้การตอบหลายบับเบิลดูเป็นธรรมชาติมากขึ้น This makes multi-bubble responses feel more natural.
- คอนฟิก:
agents.defaults.humanDelay(override ต่อเอเจนต์ผ่านagents.list[].humanDelay) - โหมด:
off(ค่าเริ่มต้น),natural(800–2500ms),custom(minMs/maxMs) - ใช้กับ การตอบแบบบล็อก เท่านั้น ไม่รวมการตอบสุดท้ายหรือสรุปเครื่องมือ
“สตรีมเป็นชิ้นหรือส่งทั้งหมด”¶
ช่องทางที่ไม่ใช่ Telegram ก็ต้องตั้งค่า *.blockStreaming: true ด้วย
- สตรีมเป็นชิ้น:
blockStreamingDefault: "on"+blockStreamingBreak: "text_end"(ปล่อยไปตามที่สร้าง) ช่องทางที่ไม่ใช่ Telegram ต้องตั้ง*.blockStreaming: trueเพิ่ม Telegram สามารถสตรีมร่าง (channels.telegram.streamMode) โดยไม่ต้องใช้การตอบแบบบล็อก - สตรีมทั้งหมดตอนท้าย:
blockStreamingBreak: "message_end"(flush ครั้งเดียว อาจได้หลายชิ้นหากยาวมาก) - ไม่ใช้ block streaming:
blockStreamingDefault: "off"(ส่งเฉพาะการตอบสุดท้าย)
หมายเหตุของช่องทาง: สำหรับช่องทางที่ไม่ใช่ Telegram block streaming จะ ปิดอยู่เสมอ เว้นแต่
*.blockStreaming จะถูกตั้งค่าเป็น true อย่างชัดเจน Telegram สามารถสตรีมแบบร่าง
(channels.telegram.streamMode) ได้โดยไม่ต้องมีการตอบแบบบล็อก OpenClaw สร้าง system prompt แบบกำหนดเองสำหรับการรันเอเจนต์ทุกครั้ง
การเตือนตำแหน่งคอนฟิก: ค่าเริ่มต้นของ blockStreaming* อยู่ภายใต้
agents.defaults ไม่ใช่คอนฟิกราก
การสตรีมแบบร่างของ Telegram (ลักษณะคล้ายโทเคน)¶
Telegram เป็นช่องทางเดียวที่มีการสตรีมแบบร่าง:
- ใช้ Bot API
sendMessageDraftใน แชทส่วนตัวที่มีหัวข้อ channels.telegram.streamMode: "partial" | "block" | "off"partial: อัปเดตร่างด้วยข้อความสตรีมล่าสุดblock: อัปเดตร่างเป็นบล็อกที่แบ่งชิ้นแล้ว (กฎเดียวกับตัวแบ่งชิ้น)off: ไม่มีการสตรีมแบบร่าง- คอนฟิกการแบ่งชิ้นของร่าง (เฉพาะสำหรับ
streamMode: "block"):channels.telegram.draftChunk(ค่าเริ่มต้น:minChars: 200,maxChars: 800) - การสตรีมแบบร่างแยกจาก block streaming; การตอบแบบบล็อกปิดเป็นค่าเริ่มต้นและจะเปิดได้เฉพาะด้วย
*.blockStreaming: trueบนช่องทางที่ไม่ใช่ Telegram - การตอบสุดท้ายยังคงเป็นข้อความปกติ
/reasoning streamจะเขียนเหตุผลลงในบับเบิลแบบร่าง (เฉพาะ Telegram)
เมื่อการสตรีมแบบร่างทำงาน OpenClaw จะปิด block streaming สำหรับการตอบนั้นเพื่อหลีกเลี่ยงการสตรีมซ้ำซ้อน
Telegram (private + topics)
└─ sendMessageDraft (draft bubble)
├─ streamMode=partial → update latest text
└─ streamMode=block → chunker updates draft
└─ final reply → normal message
คำอธิบายสัญลักษณ์:
sendMessageDraft: บับเบิลแบบร่างของ Telegram (ไม่ใช่ข้อความจริง)final reply: การส่งข้อความ Telegram แบบปกติ