مقدمة LCEL
LCEL (لغة سلسلة التعبيرات) هي أداة تنسيق سلسلة الأوامر القوية التي تمكّنك من بناء سلاسل مهام معقدة من المكونات الأساسية، كما تدعم ميزات جاهزة للاستخدام مثل معالجة البث، والمعالجة المتوازية، وتسجيل الأحداث.
مثال بسيط: تعليمة + نموذج + محلل إخراج
في هذا المثال، سنقدم كيفية استخدام LCEL (لغة سلسلة التعبيرات) لربط ثلاث مكونات - قالب السؤال، والنموذج، ومحلل الإخراج - معًا لتشكيل سلاسل عمل كاملة لتنفيذ مهمة "سرد النكات". يبرز الكود كيفية إنشاء سلاسل واستخدام رمز الخط العمودي |
لربط مكونات مختلفة، كما يقدم دور كل مكون بجانب نتائج الإخراج.
أولاً، دعونا نرى كيفية ربط قالب السؤال والنموذج لإنشاء نكتة حول موضوع محدد:
تثبيت التبعيات
%pip install --upgrade --quiet langchain-core langchain-community langchain-openai
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
prompt = ChatPromptTemplate.from_template("أخبرني نكتة حول {topic}")
model = ChatOpenAI(model="gpt-4")
output_parser = StrOutputParser()
chain = prompt | model | output_parser
chain.invoke({"topic": "الآيس كريم"})
الناتج
"لماذا لا تدعو الحفلات الآيس كريم؟ لأنه يذوب عندما يكون حارًا!"
في هذا الكود، نستخدم LCEL لربط مكونات مختلفة في سلسلة:
chain = prompt | model | output_parser
رمز |
هنا مشابه لـ مشغل أنابيب UNIX، الذي يربط مكونات مختلفة معًا ويمرر إخراج مكون واحد كمدخل للمكون التالي.
في هذه السلسلة، يتم تمرير إدخال المستخدم إلى قالب السؤال، ثم يتم تمرير إخراج قالب السؤال إلى النموذج، وأخيرًا يتم تمرير إخراج النموذج إلى محلل الإخراج. دعونا نلقي نظرة على كل مكون بشكل منفصل لفهم ما يحدث بشكل أفضل.
1. قالب السؤال
prompt
هو BasePromptTemplate
الذي يقبل قاموس المتغيرات النموذجية ويولد PromptValue
. PromptValue
هو كائن مغلف يحتوي على السؤال، والذي يمكن تمريره إلى LLM
(كإدخال بشكل سلسلة) أو ChatModel
(كإدخال بشكل تسلسل رسائل). يمكن استخدامه مع أي نوع من النماذج اللغوية لأنه يحدد المنطق لتوليد BaseMessage
وتوليد السلاسل.
prompt_value = prompt.invoke({"topic": "الآيس كريم"})
prompt_value
الناتج
ChatPromptValue(messages=[HumanMessage(content='أخبرني نكتة حول الآيس كريم')])
أدناه، نحول النتيجة المنسقة للسؤال إلى صيغة الرسالة المستخدمة من قبل نماذج الدردشة:
prompt_value.to_messages()
الناتج
[HumanMessage(content='أخبرني نكتة حول الآيس كريم')]
يمكن أيضًا تحويلها مباشرة إلى سلسلة:
prompt_value.to_string()
الناتج
'إنسان: أخبرني نكتة حول الآيس كريم.'
2. النموذج
بعد ذلك، قم بتمرير PromptValue
إلى الـ model
. في هذا المثال، نموذجنا هو ChatModel
، مما يعني أنه سيولّد BaseMessage
.
حاول استدعاء الـ model
مباشرةً:
message = model.invoke(prompt_value)
message
الناتج:
AIMessage(content="لماذا لا يتم دعوة الآيس كريم إلى الحفلات؟\n\nلأنها دائماً تنقطع عندما تصبح الأمور ساخنة!")
إذا تم تعريف نموذجنا كنوع LLM
، فإنه سيخرج سلسلة نصية.
from langchain_openai.llms import OpenAI
llm = OpenAI(model="gpt-3.5-turbo-instruct")
llm.invoke(prompt_value)
النموذج يعيد:
'\n\nروبوت: لماذا توقفت شاحنة الآيس كريم عن العمل؟ لأنها خضعت لانصهار!'
3. محلل الإخراج
أخيرًا، قم بتمرير الإخراج من الـ model
إلى output_parser
، والذي هو BaseOutputParser
، وهذا يعني أنه يقبل سلسلة أو BaseMessage
كإدخال. StrOutputParser
يحول بشكل خاص أي إدخال إلى سلسلة بسيطة.
output_parser.invoke(message)
لماذا لا يتم دعوة الآيس كريم إلى الحفلات؟\n\nلأنها دائماً تنقطع عندما تصبح الأمور ساخنة!
4. العملية بأكملها
عملية التنفيذ كالتالي:
- استدعاء
chain.invoke({"topic": "ice cream"})
، وهو ما يعادل بدء سير العمل الذي قمنا بتعريفه وتمرير المعلمة{"topic": "ice cream"}
لتوليد نكتة عن "الآيس كريم". - تمرير معلمة الاستدعاء
{"topic": "ice cream"}
إلى المكون الأول في السلسلة،prompt
، الذي يقوم بتنسيق قالب النص المطلوب للحصول على الموجهقل لي نكتة صغيرة عن الآيس كريم
. - تمرير الموجه
قل لي نكتة صغيرة عن الآيس كريم
إلىmodel
(نموذج gpt4). - تمرير النتيجة المُرجعة من
model
إلى قارئ الإخراجoutput_parser
، الذي يقوم بتنسيق نتيجة النموذج وإرجاع المحتوى النهائي.
إذا كنت مهتمًا بنتائج أي مكون، يمكنك اختبار نسخة أصغر من السلسلة في أي وقت، مثل prompt
أو prompt | model
، لرؤية النتائج الوسيطة:
input = {"topic": "ice cream"}
prompt.invoke(input)
(prompt | model).invoke(input)
مثال بحث RAG
الآن، دعنا نشرح مثالًا أكثر تعقيدًا لمثال LCEL. سنقوم بتشغيل مثال لاسترجاع محسّن لتوليد سلاسل، من أجل إضافة بعض المعلومات الأساسية عند الإجابة على الأسئلة.
from langchain_community.vectorstores import DocArrayInMemorySearch
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
from langchain_openai.chat_models import ChatOpenAI
from langchain_openai.embeddings import OpenAIEmbeddings
vectorstore = DocArrayInMemorySearch.from_texts(
["harrison worked at kensho", "bears like to eat honey"],
embedding=OpenAIEmbeddings(),
)
retriever = vectorstore.as_retriever()
template = """أجب على السؤال بناءً فقط على السياق التالي:
{context}
السؤال: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
model = ChatOpenAI()
output_parser = StrOutputParser()
setup_and_retrieval = RunnableParallel(
{"context": retriever, "question": RunnablePassthrough()}
)
chain = setup_and_retrieval | prompt | model | output_parser
chain.invoke("أين عمل هاريسون؟")
في هذه الحالة، السلسلة المتكونة هي:
chain = setup_and_retrieval | prompt | model | output_parser
ببساطة، القالب المذكور أعلاه يقبل context
و question
كقيم يتم استبدالها في الموجه. قبل بناء القالب الموجه، نريد استرداد مستندات ذات صلة لاستخدامها كجزء من السياق.
كاختبار، نستخدم DocArrayInMemorySearch
لمحاكاة قاعدة بيانات الفيكتورات المستندة إلى الذاكرة، وتعريف استرجاع يمكنه الوصول إلى مستندات مماثلة استنادًا إلى الاستعلامات. هذا أيضًا مكون تشغيلي قابل للربط، لكن يمكنك أيضًا تجربة تشغيله بشكل منفصل:
retriever.invoke("أين عمل هاريسون؟")
ثم، نستخدم RunnableParallel
لإعداد المدخل للموجه، البحث عن المستندات باستخدام الاسترجاع، وتمرير سؤال المستخدم باستخدام RunnablePassthrough
:
setup_and_retrieval = RunnableParallel(
{"context": retriever, "question": RunnablePassthrough()}
)
لختصار، السلسلة الكاملة هي:
setup_and_retrieval = RunnableParallel(
{"context": retriever, "question": RunnablePassthrough()}
)
chain = setup_and_retrieval | prompt | model | output_parser
تكون العملية كالتالي:
- أولاً، إنشاء كائن
RunnableParallel
يحتوي على مدخلين. المدخل الأولcontext
سيحتوي على نتائج الوثائق التي تم استخراجها بواسطة الاسترجاع. المدخل الثانيquestion
سيحتوي على سؤال المستخدم الأصلي. لتمرير السؤال، نستخدمRunnablePassthrough
لنسخ هذا المدخل. - تمرير القاموس من الخطوة السابقة إلى مكون الـ
prompt
. يقبل الإدخال من المستخدم (أي،question
) بالإضافة إلى المستندات المُسترجعة (أي،context
)، ويُنشئ موجهًا ويُخرجPromptValue
. - المكون
model
يأخذ الموجه الذي تم إنشاؤه ويمرره إلى نموذج LLM التابع لشركة OpenAI للتقييم. الإخراج المُنتج بواسطة النموذج هو كائنChatMessage
. - أخيرًا، المكون
output_parser
يأخذChatMessage
، يحوله إلى سلسلة Python، ويُرجعه من طريقةinvoke
.