معرفی 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)
چرا هیچ‌وقت بستنی به مهمانی‌ها دعوت نمی‌شوند؟ چون همیشه وقتی چیزها گرم می‌شود، آب می‌شود!

۴. کل فرایند

فرایند اجرا به صورت زیر است:

  1. chain.invoke({"topic": "ice cream"}) را صدا بزنید که معادل شروع کردن گردش کاری تعریف شده ما می‌باشد و پارامتر {"topic": "ice cream"} را برای تولید شوخی در مورد "بستنی" منتقل می‌کند.
  2. پارامتر فراخوانی شده {"topic": "ice cream"} را به اولین جزوه زنجیره، یعنی prompt منتقل کنید که الگوی پرسش را برای بدست آوردن پرسش Tell me a little joke about ice cream قالب بندی می‌کند.
  3. پرسش Tell me a little joke about ice cream را به model (مدل gpt4) منتقل کنید.
  4. نتیجه حاصل از 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

فرآیند به صورت زیر است:

  1. اولین‌بار یک شیء RunnableParallel را شامل دو ورودی ایجاد کنید. ورودی اول context حاوی نتایج سندی استخراج شده توسط بازیاب است. ورودی دوم question شامل سوال اصلی کاربر است. برای منتقل کردن سوال از RunnablePassthrough برای کپی این ورودی استفاده می‌کنیم.
  2. دیکشنری از مرحله قبل را به مؤلفه prompt منتقل کنید. این مؤلفه ورودی کاربر (یعنی question) و همچنین اسناد بازیابی شده (یعنی context) را می‌پذیرد، الگوی پرسش را ساخته و یک PromptValue را خروجی می‌دهد.
  3. مؤلفه model الگوی تولید شده را برمی‌گیرد و به مدل LLM آنلاین OpenAI برای ارزیابی منتقل می‌کند. خروجی تولید شده توسط مدل یک شیء ChatMessage است.
  4. در نهایت، مؤلفه output_parser یک ChatMessage را بگیرد، آن را به یک رشته Python تبدیل می‌کند و از متد invoke بازمی‌گرداند.