معرفی LCEL
LCEL (LangChain Expression Language) ابزار قدرتمندی برای ارکستراسیون گردش کار است که به شما امکان میدهد تا زنجیرههای وظایف پیچیده را از اجزا ابتدایی بسازید و از ویژگیهای فوریتی نظیر پردازش استریمینگ، پردازش موازی و ثبت وقایع پشتیبانی کند.
مثال ابتدایی: Prompt + Model + Output Parser
در این مثال، ما نشان خواهیم داد که چگونه از LCEL (LangChain Expression Language) برای اتصال سه جزء - الگوی prompt، مدل و تجزیهکننده خروجی - به یکدیگر برای ایجاد یک گردش کار کامل برای اجرای وظیفه "بیان شوخی" استفاده کنیم. کد نشان می دهد چگونه زنجیرهها را ایجاد کرد، از نماد لوله |
برای اتصال اجزا مختلف استفاده میکند و نقش هر جزء و نتایج خروجی را معرفی میکند.
ابتدا، بیایید ببینیم که چگونه الگوی prompt و مدل را به یکدیگر وصل کرده و یک شوخی درباره موضوع مشخصی ایجاد کنیم:
نصب وابستگیها
%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
نماد |
در اینجا شبیه به عملگر لوله یونیکس است، که اجزا مختلف را به یکدیگر وصل میکند و خروجی یک جزء را به عنوان ورودی جزء بعدی ارسال میکند.
در این زنجیره، ورودی کاربر به الگوی prompt منتقل میشود، سپس خروجی الگوی prompt به مدل منتقل شده، و در نهایت خروجی مدل به تجزیهکننده خروجی منتقل میشود. بیایید به طور جداگانه به هر جزء نگاه کنیم تا بهتر بفهمیم که چه اتفاقی میافتد.
1. Prompt
prompt
یک BasePromptTemplate
است که یک دیکشنری متغیر الگو را پذیرفته و یک PromptValue
تولید میکند. PromptValue
یک شیء پوشش یافته است که حاوی prompt است و میتواند به LLM
(به عنوان ورودی به صورت رشته) یا ChatModel
(به عنوان ورودی به صورت توالی پیام) منتقل شود. این میتواند با هر نوع مدل زبانی استفاده شود زیرا منطق تولید BaseMessage
و تولید رشتهها را تعریف میکند.
prompt_value = prompt.invoke({"topic": "بستنی"})
prompt_value
خروجی
ChatPromptValue(messages=[HumanMessage(content='یک شوخی درباره بستنی به من بگو')])
در زیر، نتیجه بهدستآمده از الگو را به شکل پیامهای استفاده شده توسط مدلهای چت تبدیل میکنیم:
prompt_value.to_messages()
خروجی
[HumanMessage(content='یک شوخی درباره بستنی به من بگو')]
همچنین میتواند مستقیما به رشته تبدیل شود:
prompt_value.to_string()
خروجی
'Human: یک شوخی درباره بستنی به من بگو.'
2. Model
سپس، PromptValue
را به model
منتقل کنید. در این مثال، مدل ما یک ChatModel
است، یعنی خروجی یک BaseMessage
است.
امتحان کردن فراخوانی model
به طور مستقیم:
message = model.invoke(prompt_value)
message
بازگشت:
AIMessage(content="چرا هیچوقت بستنی به مهمانیها دعوت نمیشوند؟\n\nچون همیشه وقتی چیزها گرم میشود، آب میشود!")
اگر model
ما به عنوان نوع LLM
تعریف شده باشد، یک رشته خواهد داد.
from langchain_openai.llms import OpenAI
llm = OpenAI(model="gpt-3.5-turbo-instruct")
llm.invoke(prompt_value)
خروجی مدل:
'\n\nBot: چرا ماشین بستنی خراب شد؟ چون یک ذوباندن رویش داشت!'
3. Output Parser
در نهایت، خروجی از مدل را به output_parser
منتقل کنید که یک BaseOutputParser
است، به این معنی که یک رشته یا BaseMessage
را به عنوان ورودی پذیرفته و تجزیه می کند. StrOutputParser
به طور خاص هر ورودی را به یک رشته ساده تبدیل میکند.
output_parser.invoke(message)
چرا هیچوقت بستنی به مهمانیها دعوت نمیشوند؟ چون همیشه وقتی چیزها گرم میشود، آب میشود!
۴. کل فرایند
فرایند اجرا به صورت زیر است:
-
chain.invoke({"topic": "ice cream"})
را صدا بزنید که معادل شروع کردن گردش کاری تعریف شده ما میباشد و پارامتر{"topic": "ice cream"}
را برای تولید شوخی در مورد "بستنی" منتقل میکند. - پارامتر فراخوانی شده
{"topic": "ice cream"}
را به اولین جزوه زنجیره، یعنیprompt
منتقل کنید که الگوی پرسش را برای بدست آوردن پرسشTell me a little joke about ice cream
قالب بندی میکند. - پرسش
Tell me a little joke about ice cream
را به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 = """Answer the question based only on the following context:
{context}
Question: {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("where did harrison work?")
در این مورد، زنجیره ترکیبشده به صورت زیر است:
chain = setup_and_retrieval | prompt | model | output_parser
به طور ساده، الگوی پرسش فوق اطلاعات context
و question
را به عنوان مقادیری برای جایگزین کردن در الگو میپذیرد. قبل از ساختن الگوی پرسش، ما میخواهیم اسناد مرتبط را برای استفاده به عنوان بخشی از زمینه بازیابی کنیم.
به عنوان یک تست، از DocArrayInMemorySearch
برای شبیهسازی یک پایگاه داده برداری مبتنی بر حافظه استفاده میکنیم تا یک بازیاب از مدل مشابه براساس پرس و جوها را مشخص کنیم. این نیز یک مؤلفه اجرایی قابل زنجیرهسازی است، اما شما میتوانید آن را به صورت جداگانه امتحان کنید:
retriever.invoke("where did harrison work?")
سپس، از 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
بازمیگرداند.