การแนะนำ LCEL

LCEL (LangChain Expression Language) เป็นเครื่องมือการจัดการการทำงานโดยระบบที่มีความสามารถที่สามารถช่วยให้คุณสร้างงานทำงานที่ซับซ้อนขึ้นจากส่วนประกอบพื้นฐาน ๆ และรองรับคุณลักษณะพร้อมใช้งานอย่างเช่นการประมวลผลแบบสตรีม, การประมวลผลแบบขนาน, และการบันทึกข้อมูล.

ตัวอย่างพื้นฐาน: โปรโมท + โมเดล + ตัววิเคราะห์ผลลัพธ์

ในตัวอย่างนี้, เราจะสาธิตวิธีการใช้ LCEL (LangChain Expression Language) เชื่อมต่อสามส่วน - แบบแม่แบบโปรโมต, โมเดล, และตัววิเคราะห์ผลลัพธ์ - เข้าด้วยกันเพื่อสร้างการทำงานที่สมบูรณ์สำหรับการปฏิบัติงาน "การพูดตลก" โค้ดสาธิตวิธีการสร้างโพรง, ใช้สัญลักษณ์ | เชื่อมต่อส่วนประกอบต่าง ๆ และแนะนำบทบาทของแต่ละส่วนพร้อมกับผลลัพธ์ที่ได้

ก่อนอื่น, ไปดูวิธีการเชื่อมโยงแบบแม่แบบโปรโมตและโมเดลเพื่อสร้างตำราเกี่ยวกับหัวข้อที่เฉพาะเจาะจง:

ติดตั้ง dependencies

%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("Tell me a joke about {topic}")
model = ChatOpenAI(model="gpt-4")
output_parser = StrOutputParser()

chain = prompt | model | output_parser

chain.invoke({"topic": "ไอศกรีม"})

ผลลัพธ์

"ทำไมงานเลี้ยงไม่เชิญไอศกรีม? เพราะมันละลายเมื่ออากาศร้อน!"

ในโค้ดนี้, เราใช้ LCEL เชื่อมต่อส่วนประกอบต่าง ๆ เข้าด้วยกัน:

chain = prompt | model | output_parser

สัญลักษณ์ | ที่นี่คล้ายกับ Unix pipe operator, ที่เชื่อมต่อส่วนประกอบต่าง ๆ พร้อม โปรยผลลัพธ์ของส่วนประกอบหนึ่งเป็นอินพุตของส่วนประกอบถัดไป.

ในโพรงนี้, ข้อมูลนำเข้าของผู้ใช้ถูกส่งต่อไปยังแบบแม่แบบโปรโมต, จากนั้นผลลัพธ์ของแบบแม่แบบโปรโมตถูกส่งต่อไปยังโมเดล, และสุดท้ายผลลัพธ์ของโมเดลถูกส่งต่อไปยังตัววิเคราะห์ผลลัพธ์. มาดูแต่ละส่วนํโดยแยกกันเพื่อเข้าใจที่ดีขึ้น.

1. โปรโมต

prompt คือ BasePromptTemplate ซึ่งรับพารามิเตอร์แบบแม่แบบและสร้าง PromptValue. PromptValue เป็นอัตราการป้อนห่ัังที่บรรจุโปรโมตได้, ซึ่งสามารถถูกส่งไปยัง LLM (เป็นอินพุตในรูปแบบข้อความ) หรือ ChatModel (เป็นอินพุตในรูปแบบข้อความตามลำดับ). มันสามารถใช้กับแบบโมเดลภาษาใดก็ได้เพราะมันกำหนดตรรกะสำหรับการสร้าง BaseMessge และการสร้างสตริง.

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. กระบวนการทั้งหมด

กระบวนการการทำงานมีดังนี้:

  1. เรียกใช้ chain.invoke({"topic": "ไอศกรีม"}) ซึ่งเทียบเท่ากับการเริ่มกระบวนการทำงานที่เรากำหนดไว้ และส่งพารามิเตอร์ {"topic": "ไอศกรีม"} เพื่อสร้าง Joke เกี่ยวกับ "ไอศกรีม"
  2. ส่งพารามิเตอร์การเรียก {"topic": "ไอศกรีม"} ไปยัง component แรกของ chain คือ prompt ซึ่งจะจัดรูปแบบของ prompt template เพื่อให้ได้ prompt จำเป็นต้องมีล้อเล่นเล็กน้อยเกี่ยวกับไอศกรีม
  3. ส่ง prompt จำเป็นต้องมีล้อเล่นเล็กน้อยเกี่ยวกับไอศกรีม ไปยัง component model (gpt4 model)
  4. ส่งผลลัพธ์ที่ได้จาก model ไปยัง output_parser output parser เพื่อจัดรูปแบบผลลัพธ์จาก model และส่งค่ากลับมาที่ content สุดท้าย

หากคุณสนใจผลลัพธ์จาก component ไหน คุณสามารถทดสอบรันเวอร์ชันขนาดเล็กของ chain ได้ทุกเมื่อ เช่น prompt หรือ prompt | model เพื่อดูผลลัพธ์ชั่วคราว:

input = {"topic": "ไอศกรีม"}

prompt.invoke(input)

(prompt | model).invoke(input)

ตัวอย่างการค้นหา RAG

ถัดมา เรามาอธิบาย LCEL ที่เส้นทางการทำงานในระดับที่ซับซ้อนขึ้นเล็กน้อย โดยเราจะรันตัวอย่างการค้นหาเพื่อสร้าง chain เพื่อเพิ่มข้อมูลพื้นฐานเมื่อตอบคำถาม

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 ที่ชุดทำงานเข้าด้วยกันคือ:

chain = setup_and_retrieval | prompt | model | output_parser

อย่างง่ายๆ แล้ว prompt template ข้างต้นรับค่า context และ question เพื่อเปลี่ยนแทนใน prompt ก่อนสร้าง prompt template เราต้องการเรียกข้อมูลเอกสารที่เกี่ยวข้องเพื่อนำมาใช้เป็นส่วนหนึ่งของประวัติ.

เพื่อทดสอบเราใช้ DocArrayInMemorySearch เพื่อจำลองฐานข้อมูลเวกเตอร์ภายในหน่วยความจำ กำหนด retriever ที่สามารถดึงข้อมูลที่คล้ายคลึงกับคำคิดออกมาด้วยการค้นหาคำคิด

เป็นชุดเมื่อรัน แต่คุณยังสามารถทดสอบการวิ่งแยกออกมาเพื่อวิ่งดูได้อีกด้วย:

retriever.invoke("ฮาริสันทำงานที่ไหน?")

จากนั้น ทำการรัน RunnableParallel เพื่อเตรียมข้อมูลที่จะเอาไปทำ prompt ค้นหาเอกสารด้วย retriever และส่งคำถามของผู้ใช้ไปใน 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 object ที่มี 2 รายการ รายการแรก context จะรวมเอกสารที่ได้จาก retriever และรายการที่สอง question จะรวมคำถามเดิมของผู้ใช้ และจะสร้าง PromptValue
  2. ส่ง dictionary จากรายการก่อนหน้าไปยัง component prompt ซึ่งรับค่าจากผู้ใช้ (เช่น question) และเอกสารที่ได้จาก retriever (เช่น context) สร้าง prompt และผลลัพธ์ที่ได้คือ PromptValue
  3. ต่อมา model จะรับ prompt ที่ได้และส่งไปที่ OpenAI's LLM model เพื่อประเมิน ผลลัพธ์ที่ได้จาก model คือ ChatMessage object
  4. สุดท้าย output_parser จะรับ ChatMessage และแปลงเป็นสตริงของ Python และส่งค่ากลับมาจากเมธอด invoke