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