بث المخرجات في LangChain
طبّقوا بث tokens عبر سلاسل LCEL كي يعرض تطبيقكم كل كلمة عند وصولها بدلًا من انتظار الاستجابة الكاملة، مما يحسّن زمن الاستجابة المتصوَّر.
بث المخرجات في LangChain درس مجاني في AI Engineering Academy على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في AI Engineering Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة AI Engineering Academy 4 دروس في المجموع.
أهمية البث
من دون البث، يحدّق المستخدمون في شاشة فارغة أثناء انتظار انتهاء نموذج LLM من التوليد، وقد يستغرق ذلك من 5 إلى 30 ثانية للاستجابات الطويلة. ومع البث، تظهر الرموز المميزة فور توليدها، مما يوفر ملاحظات فورية. وهذا يحسّن الإحساس بسرعة الاستجابة بدرجة كبيرة. ينشر LCEL في LangChain البث عبر السلسلة بأكملها تلقائيًا عند استدعاء .stream().
البث الأساسي باستخدام .stream()
توفّر كل سلسلة LCEL دالة .stream() تُعيد مكرّرًا من الأجزاء. وبالنسبة إلى سلسلة تنتهي بـ StrOutputParser، يكون كل جزء مقطعًا نصيًا. تكرّر على الأجزاء وتطبعها أو تمررها عند وصولها. يحدث البث على مستوى HTTP، إذ تُمرَّر كل رموز مميزة من OpenAI API عبر المحلّل فور وصولها.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
chain = (
ChatPromptTemplate.from_template('Explain {topic} in detail.')
| ChatOpenAI(model='gpt-4o-mini')
| StrOutputParser()
)
# Stream tokens to stdout
for chunk in chain.stream({'topic': 'quantum entanglement'}):
print(chunk, end='', flush=True)
print() # final newlineالبث غير المتزامن باستخدام .astream()
تمثل .astream() النسخة غير المتزامنة من .stream(). وهي تُعيد مكرّرًا غير متزامن تستهلكه باستخدام async for. وهذا هو الأسلوب الصحيح في FastAPI وStarlette وأطر الويب غير المتزامنة الأخرى، حيث يكون معالج الطلب coroutine. أما استخدام البث المتزامن داخل معالج غير متزامن فسيحجب حلقة الأحداث.
import asyncio
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
chain = (
ChatPromptTemplate.from_template('Write a poem about {subject}')
| ChatOpenAI(model='gpt-4o-mini')
| StrOutputParser()
)
async def stream_response():
async for chunk in chain.astream({'subject': 'the ocean'}):
print(chunk, end='', flush=True)
asyncio.run(stream_response())البث في FastAPI باستخدام StreamingResponse
في FastAPI، تغلّف مولّدًا غير متزامن داخل StreamingResponse مع media_type='text/plain' لبث الرموز المميزة النصية إلى المتصفح. ولأحداث الخادم المرسلة (SSE)، استخدم media_type='text/event-stream' ونسّق كل جزء على شكل data: ...\n\n. عندئذٍ يستقبل المتصفح الرموز المميزة فور توليدها دون انتظار اكتمال الاستجابة.
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
app = FastAPI()
async def generate_stream(topic: str):
async for chunk in chain.astream({'topic': topic}):
yield chunk
@app.get('/stream')
async def stream_endpoint(topic: str):
return StreamingResponse(
generate_stream(topic),
media_type='text/plain'
)
# SSE format for frontend EventSource
async def sse_stream(topic: str):
async for chunk in chain.astream({'topic': topic}):
yield f'data: {chunk}\n\n'البث عبر الخطوات الوسيطة
تنشر سلاسل LCEL البث عبر كل خطوة تدعمه. ويُراعي StrOutputParser البث ويمرر الأجزاء فورًا. لكن بعض المحللات، مثل JsonOutputParser، يجب أن تخزّن المخرجات الكاملة مؤقتًا قبل تحليلها، مما يوقف البث. يوضح LangChain ذلك بجلاء: إذا لم تكن الخطوة متوافقة مع البث، فإنها تجمع المخرجات قبل تمريرها إلى الخطوة التالية.
from langchain_core.output_parsers import JsonOutputParser
# This chain does NOT stream token by token
# JsonOutputParser must buffer the full response before parsing JSON
json_chain = (
ChatPromptTemplate.from_template('Return JSON: {task}')
| ChatOpenAI(model='gpt-4o-mini')
| JsonOutputParser() # buffers until complete
)
# But partial JSON streaming IS possible with streaming_json_parser
for partial in json_chain.stream({'task': 'list 3 colors'}):
print(partial) # prints partial dict as it fills inastream_events للتحكم الدقيق
توفر .astream_events() واجهة بث أكثر تفصيلًا، إذ تصدر أحداثًا لكل خطوة في السلسلة، وليس للمخرجات النهائية فقط. يحتوي كل حدث على الحقل kind (on_chain_start وon_llm_stream وon_chain_end) وحمولة data. ويتيح لك ذلك بث نتائج استدعاءات الأدوات والاستدلال الوسيط والمخرجات النهائية بشكل منفصل إلى أجزاء مختلفة من واجهة المستخدم.
async def stream_with_events(question: str):
async for event in chain.astream_events(
{'question': question},
version='v2'
):
kind = event['event']
if kind == 'on_llm_stream':
chunk = event['data']['chunk'].content
print(chunk, end='', flush=True)
elif kind == 'on_chain_end':
print('\n[Done]')
elif kind == 'on_tool_start':
print(f'\n[Tool: {event["name"]}]')تخزين المخرجات المبثوثة مؤقتًا
قد تحتاج أحيانًا إلى بث الرموز المميزة إلى المستخدم والاحتفاظ بالاستجابة الكاملة في الوقت نفسه لأغراض التسجيل أو المعالجة الإضافية. استخدم .astream() مع قائمة لتجميع الأجزاء. اضمم الأجزاء بعد انتهاء الحلقة للحصول على النص الكامل. يتيح لك هذا النمط عرض المخرجات المتدفقة في الوقت الفعلي، مع تخزين الاستجابة الكاملة للتحليلات أو التخزين المؤقت أو التقييم.
async def stream_and_capture(question: str) -> str:
full_response = []
async for chunk in chain.astream({'question': question}):
print(chunk, end='', flush=True) # stream to user
full_response.append(chunk) # also collect
print() # newline
complete = ''.join(full_response)
await log_response(question, complete) # log full text
return completeالبث مع استدعاءات الأدوات
عندما ينشئ النموذج استدعاء أداة ضمن استجابة مبثوثة، تصل وسيطات الدالة على شكل أجزاء من الرموز المميزة. يجب تخزين سلسلة وسيطات JSON مؤقتًا حتى يكتمل استدعاء الأداة، ثم تنفيذه. يتولى LangChain ذلك تلقائيًا في منفّذات الوكلاء، لكن إذا كنت تبني حلقة بث مخصصة، فعليك التحقق من finish_reason وتجميع أجزاء tool_call.function.arguments.
from openai import AsyncOpenAI
client = AsyncOpenAI()
async def stream_with_tools(prompt: str):
tool_call_buffer = {}
async with client.chat.completions.stream(
model='gpt-4o-mini',
messages=[{'role': 'user', 'content': prompt}],
tools=[weather_tool_schema]
) as stream:
async for chunk in stream:
delta = chunk.choices[0].delta
if delta.tool_calls:
for tc in delta.tool_calls:
idx = tc.index
if idx not in tool_call_buffer:
tool_call_buffer[idx] = ''
if tc.function.arguments:
tool_call_buffer[idx] += tc.function.argumentsالإلغاء والمهلة الزمنية مع البث
تحتاج الاستجابات المبثوثة الطويلة إلى دعم الإلغاء. في Python غير المتزامن، يمكنك إلغاء asyncio.Task الذي يغلّف البث. وفي FastAPI، يتولى الإطار تلقائيًا إلغاء الطلب عند انقطاع اتصال العميل عند استخدام StreamingResponse. عيّن مهلة زمنية عبر المعامل timeout في عميل OpenAI، أو لفّ البث باستخدام asyncio.wait_for() لإيقافه بعد مدة قصوى.
import asyncio
async def stream_with_timeout(question: str, timeout: float = 30.0):
async def _stream():
async for chunk in chain.astream({'question': question}):
yield chunk
try:
async for chunk in asyncio.timeout(_stream(), timeout):
print(chunk, end='', flush=True)
except asyncio.TimeoutError:
print('\n[Stream timed out after 30 seconds]')
except asyncio.CancelledError:
print('\n[Stream cancelled by client disconnect]')SSE من جهة العميل باستخدام JavaScript
في الواجهة الأمامية، تستهلك واجهة EventSource API الأصلية في المتصفح الأحداث المرسلة من الخادم. عندما تصدر نقطة نهاية FastAPI أجزاءً على شكل data: token\n\n، تطلق EventSource حدث message لكل جزء. أضف كل رمز مميز إلى DOM عند وصوله لإنشاء تأثير الكتابة التدريجي. ولمزيد من التحكم، يوفّر fetch() مع response.body.getReader() وصولًا كاملًا إلى البث.
// Frontend JavaScript (not Python)
const source = new EventSource('/stream?topic=quantum+computing');
const outputDiv = document.getElementById('output');
source.onmessage = (event) => {
outputDiv.textContent += event.data;
};
source.onerror = () => {
source.close();
outputDiv.textContent += ' [done]';
};
// Alternative: fetch with ReadableStream
const response = await fetch('/stream?topic=ai');
const reader = response.body.getReader();
while (true) {
const {done, value} = await reader.read();
if (done) break;
outputDiv.textContent += new TextDecoder().decode(value);
}أفضل ممارسات البث
اتبع أفضل الممارسات التالية عند تنفيذ البث: استخدم دائمًا flush=True عند الطباعة إلى stdout لمنع التخزين المؤقت. عيّن stream_usage=True إذا كنت تحتاج إلى حسابات دقيقة للرموز المميزة أثناء البث. أرسل علامة النهاية data: [DONE]\n\n في نهاية تدفقات SSE حتى يعرف العميل متى يغلق الاتصال. اختبر نقاط نهاية البث باستخدام curl --no-buffer للتحقق من وصول الرموز المميزة تدريجيًا.
# Complete SSE endpoint with DONE sentinel
async def sse_generator(question: str):
try:
async for chunk in chain.astream({'question': question}):
# Escape any newlines in the chunk
safe_chunk = chunk.replace('\n', ' ')
yield f'data: {safe_chunk}\n\n'
finally:
yield 'data: [DONE]\n\n'
@app.get('/chat/stream')
async def chat_stream(question: str):
return StreamingResponse(
sse_generator(question),
media_type='text/event-stream',
headers={'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no'}
)تحقق سريع
اختبر مدى فهمك لبث المخرجات في LangChain.
مراجعة الدرس
تعلمت في هذا الدرس أن stream() و astream() يتيحان لك التكرار على أجزاء الرموز المميزة أثناء توليدها، مما يلغي الانتظار الطويل للاستجابة الكاملة؛ وأن StreamingResponse في FastAPI بتنسيق SSE يوصّل الرموز المميزة إلى عملاء المتصفح في الوقت الفعلي؛ وأن astream_events() يوفر نقاط ربط دقيقة للأحداث في كل خطوة من السلسلة، بما في ذلك استدعاءات الأدوات والمخرجات الوسيطة. سنستكشف بعد ذلك إدارة الذاكرة للمحادثات متعددة الأدوار.
تعلم Python مع معلم ذكاء اصطناعي — مجانًا
اكتب وقم بتشغيل أكوادك الفعلية في المتصفح، واحصل على مساعدة فورية من معلم ذكاء اصطناعي متاح 24/7، واستمر من حيث توقفت على الويب أو في التطبيق.
- الدورات
- 30
- الدروس
- 120
الأسئلة الشائعة
هل درس «بث المخرجات في LangChain» مجاني؟
نعم — نص درس «بث المخرجات في LangChain» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة AI Engineering Academy، انتقل إلى CoddyKit PRO. تتضمن دورة AI Engineering Academy 4 دروس في المجموع.
ماذا ستتعلم في «بث المخرجات في LangChain»؟
طبّقوا بث tokens عبر سلاسل LCEL كي يعرض تطبيقكم كل كلمة عند وصولها بدلًا من انتظار الاستجابة الكاملة، مما يحسّن زمن الاستجابة المتصوَّر. تتمرن على AI Engineering Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ AI Engineering Academy؟
لا تُشترط خبرة سابقة. AI Engineering Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.
كم من الوقت يستغرق درس «بث المخرجات في LangChain»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس AI Engineering Academy هذا؟
نعم. كل درس في AI Engineering Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- بنية LangChain وتجريداتها الأساسية
- بناء السلاسل باستخدام LCEL
- السلاسل المتفرعة والمتوازية
- بث المخرجات في LangChain