معرفی 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بازمیگرداند.