บทที่ 5: Chains และ LCEL (LangChain Expression Language)
LCEL คืออะไร?
Section titled “LCEL คืออะไร?”LangChain Expression Language (LCEL) คือวิธีการสร้าง chains ใน LangChain โดยใช้ pipe operator (|) เพื่อเชื่อมต่อ components เข้าด้วยกัน
LCEL Syntax
Section titled “LCEL Syntax”หัวใจของ LCEL คือเครื่องหมายท่อ (|) ที่ใช้ส่งต่อข้อมูลจากตัวหนึ่งไปยังอีกตัวหนึ่งเหมือน UNIX pipe
graph LR
Input([Input]) --> Prompt[Prompt Template]
Prompt --> Model[LLM / ChatModel]
Model --> Parser[Output Parser]
Parser --> Output([Output Result])
คำอธิบาย: โค้ดตัวอย่างด้านล่างแสดงวิธีใช้งานด้วย Python ตามหัวข้อนี้แบบทีละขั้นตอน
chain = prompt | model | output_parser# ↓ ↓ ↓# สร้าง ส่งให้ แปลง# prompt LLM ผลลัพธ์Chain แรกของคุณ
Section titled “Chain แรกของคุณ”คำอธิบาย: โค้ดตัวอย่างด้านล่างแสดงวิธีใช้งานด้วย Python ตามหัวข้อนี้แบบทีละขั้นตอน
from langchain_openai import ChatOpenAIfrom langchain_core.prompts import ChatPromptTemplatefrom langchain_core.output_parsers import StrOutputParser
# 1. สร้าง componentsprompt = ChatPromptTemplate.from_messages([ ("system", "คุณเป็นผู้เชี่ยวชาญด้าน {topic}"), ("human", "{question}"),])
llm = ChatOpenAI(model="gpt-4o-mini")parser = StrOutputParser()
# 2. สร้าง chain ด้วย LCELchain = prompt | llm | parser
# 3. เรียกใช้result = chain.invoke({ "topic": "Python", "question": "อธิบาย decorator ให้หน่อย"})print(result)การทำงานของ Chain
Section titled “การทำงานของ Chain”คำอธิบาย: แผนภาพด้านล่างสรุปโฟลว์การทำงานให้เห็นภาพรวมของหัวข้อนี้อย่างชัดเจน
graph LR
Input["invoke({topic, question})"] --> Prompt["Prompt Template"]
Prompt --> LLM["LLM"]
LLM --> Parser["Parser"]
Parser --> Output["ผลลัพธ์ (string)"]
InputNote["dict -> messages"] -.-> Prompt
LLMNote["messages -> AIMessage"] -.-> LLM
ParserNote["AIMessage -> string"] -.-> Parser
RunnableLambda — Custom Functions
Section titled “RunnableLambda — Custom Functions”ใส่ function ของคุณเอง เข้าไปใน chain:
from langchain_core.runnables import RunnableLambda
# Custom functiondef format_output(text: str) -> str: """เพิ่ม emoji และ formatting""" lines = text.strip().split("\n") formatted = [] for i, line in enumerate(lines, 1): formatted.append(f"📌 {i}. {line.strip()}") return "\n".join(formatted)
# ใส่ใน chainchain = prompt | llm | StrOutputParser() | RunnableLambda(format_output)result = chain.invoke({ "topic": "Python", "question": "บอก 5 library ที่ต้องรู้"})print(result)# 📌 1. NumPy - สำหรับการคำนวณ# 📌 2. Pandas - สำหรับจัดการข้อมูล# ...RunnablePassthrough — ส่งต่อข้อมูล
Section titled “RunnablePassthrough — ส่งต่อข้อมูล”คำอธิบาย: โค้ดตัวอย่างด้านล่างแสดงวิธีใช้งานด้วย Python ตามหัวข้อนี้แบบทีละขั้นตอน
from langchain_core.runnables import RunnablePassthrough
# ส่งต่อ input โดยไม่เปลี่ยนแปลงchain = RunnablePassthrough() | prompt | llm | parser
# assign — เพิ่มข้อมูลเข้าไปใน inputchain = RunnablePassthrough.assign( word_count=lambda x: len(x["text"].split())) | prompt | llm | parserตัวอย่าง: เพิ่มข้อมูลก่อนส่งเข้า prompt
Section titled “ตัวอย่าง: เพิ่มข้อมูลก่อนส่งเข้า prompt”คำอธิบาย: โค้ดตัวอย่างด้านล่างแสดงวิธีใช้งานด้วย Python ตามหัวข้อนี้แบบทีละขั้นตอน
from langchain_core.runnables import RunnablePassthroughfrom datetime import datetime
def add_metadata(input_dict): """เพิ่ม metadata เข้าไปใน input""" return { **input_dict, "current_date": datetime.now().strftime("%Y-%m-%d"), "language": "Thai", }
prompt = ChatPromptTemplate.from_messages([ ("system", "วันที่: {current_date}. ตอบเป็นภาษา{language}"), ("human", "{question}"),])
chain = RunnableLambda(add_metadata) | prompt | llm | parserresult = chain.invoke({"question": "ข่าวเทคโนโลยีล่าสุด?"})RunnableParallel — ทำงานพร้อมกัน
Section titled “RunnableParallel — ทำงานพร้อมกัน”คำอธิบาย: โค้ดตัวอย่างด้านล่างแสดงวิธีใช้งานด้วย Python ตามหัวข้อนี้แบบทีละขั้นตอน
from langchain_core.runnables import RunnableParallel
# สร้าง chains ที่ทำงานพร้อมกันsummary_chain = summary_prompt | llm | parsersentiment_chain = sentiment_prompt | llm | parserkeywords_chain = keywords_prompt | llm | parser
# รันพร้อมกัน!parallel_chain = RunnableParallel( summary=summary_chain, sentiment=sentiment_chain, keywords=keywords_chain,)
result = parallel_chain.invoke({"text": "ข้อความยาวๆ ที่ต้องวิเคราะห์..."})print(result["summary"])print(result["sentiment"])print(result["keywords"])ตัวอย่าง: วิเคราะห์ข้อความหลายมิติ
Section titled “ตัวอย่าง: วิเคราะห์ข้อความหลายมิติ”คำอธิบาย: โค้ดตัวอย่างด้านล่างแสดงวิธีใช้งานด้วย Python ตามหัวข้อนี้แบบทีละขั้นตอน
from langchain_core.prompts import ChatPromptTemplatefrom langchain_core.runnables import RunnableParallelfrom langchain_core.output_parsers import StrOutputParserfrom langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini")parser = StrOutputParser()
# 3 chains ทำงานพร้อมกันanalyzer = RunnableParallel( summary=ChatPromptTemplate.from_template( "สรุปข้อความนี้ใน 2 ประโยค: {text}" ) | llm | parser,
mood=ChatPromptTemplate.from_template( "วิเคราะห์อารมณ์ของข้อความ (บวก/ลบ/กลาง): {text}" ) | llm | parser,
topics=ChatPromptTemplate.from_template( "ระบุหัวข้อหลักของข้อความ (3 หัวข้อ): {text}" ) | llm | parser,)
result = analyzer.invoke({ "text": "วันนี้ได้ลองใช้ LangChain ครั้งแรก รู้สึกตื่นเต้นมาก API ใช้งานง่ายดี"})print(f"📝 สรุป: {result['summary']}")print(f"😊 อารมณ์: {result['mood']}")print(f"🏷️ หัวข้อ: {result['topics']}")Branching — เลือก Chain ตามเงื่อนไข
Section titled “Branching — เลือก Chain ตามเงื่อนไข”คำอธิบาย: โค้ดตัวอย่างด้านล่างแสดงวิธีใช้งานด้วย Python ตามหัวข้อนี้แบบทีละขั้นตอน
from langchain_core.runnables import RunnableBranch
# สร้าง branch ตามเงื่อนไขbranch = RunnableBranch( # (เงื่อนไข, chain ที่จะรัน) (lambda x: "แปล" in x["task"], translate_chain), (lambda x: "สรุป" in x["task"], summarize_chain), (lambda x: "วิเคราะห์" in x["task"], analyze_chain), # default chain (ถ้าไม่ตรงเงื่อนไขไหนเลย) general_chain,)
# ใช้งานresult = branch.invoke({"task": "แปลข้อความนี้", "text": "Hello World"})Streaming — รับผลลัพธ์แบบ Real-time
Section titled “Streaming — รับผลลัพธ์แบบ Real-time”คำอธิบาย: โค้ดตัวอย่างด้านล่างแสดงวิธีใช้งานด้วย Python ตามหัวข้อนี้แบบทีละขั้นตอน
chain = prompt | llm | parser
# Stream ผลลัพธ์for chunk in chain.stream({ "topic": "การทำอาหาร", "question": "สูตรผัดกะเพราหมูสับ"}): print(chunk, end="", flush=True)Async Streaming
Section titled “Async Streaming”คำอธิบาย: โค้ดตัวอย่างด้านล่างแสดงวิธีใช้งานด้วย Python ตามหัวข้อนี้แบบทีละขั้นตอน
import asyncio
async def stream_response(): chain = prompt | llm | parser
async for chunk in chain.astream({ "topic": "Python", "question": "อธิบาย async/await" }): print(chunk, end="", flush=True)
asyncio.run(stream_response())Chain ซ้อน Chain
Section titled “Chain ซ้อน Chain”Sequential Chain — ต่อเนื่อง
Section titled “Sequential Chain — ต่อเนื่อง”คำอธิบาย: โค้ดตัวอย่างด้านล่างแสดงวิธีใช้งานด้วย Python ตามหัวข้อนี้แบบทีละขั้นตอน
# Chain 1: สร้างโครงร่างoutline_prompt = ChatPromptTemplate.from_template( "สร้างโครงร่างบทความเรื่อง: {topic}\nเขียน 5 หัวข้อย่อย")outline_chain = outline_prompt | llm | parser
# Chain 2: เขียนบทความจากโครงร่างarticle_prompt = ChatPromptTemplate.from_template( "เขียนบทความจากโครงร่างนี้:\n{outline}\n\nเขียนละเอียด แต่กระชับ")article_chain = article_prompt | llm | parser
# รวม chainfull_chain = ( {"outline": outline_chain, "topic": RunnablePassthrough()} | RunnableLambda(lambda x: {"outline": x["outline"]}) | article_chain)
result = full_chain.invoke({"topic": "AI ในอุตสาหกรรมอาหาร"})Pipeline Pattern (นิยมใช้ที่สุด)
Section titled “Pipeline Pattern (นิยมใช้ที่สุด)”คำอธิบาย: โค้ดตัวอย่างด้านล่างแสดงวิธีใช้งานด้วย Python ตามหัวข้อนี้แบบทีละขั้นตอน
from langchain_core.runnables import RunnablePassthrough
# Step 1: สร้างคำถามgenerate_questions = ChatPromptTemplate.from_template( "จากหัวข้อ '{topic}' สร้างคำถาม 3 ข้อ") | llm | parser
# Step 2: ตอบคำถามanswer_questions = ChatPromptTemplate.from_template( "ตอบคำถามเหล่านี้:\n{questions}") | llm | parser
# Step 3: สรุปsummarize = ChatPromptTemplate.from_template( "สรุปคำตอบทั้งหมดในย่อหน้าเดียว:\n{answers}") | llm | parser
# Pipelinepipeline = ( RunnablePassthrough.assign(questions=generate_questions) | RunnablePassthrough.assign(answers=answer_questions) | summarize)
result = pipeline.invoke({"topic": "Quantum Computing"})print(result)Error Handling — Fallback Chains
Section titled “Error Handling — Fallback Chains”คำอธิบาย: โค้ดตัวอย่างด้านล่างแสดงวิธีใช้งานด้วย Python ตามหัวข้อนี้แบบทีละขั้นตอน
from langchain_openai import ChatOpenAI
# Primary model (อาจจะแพงหรือช้า)primary_llm = ChatOpenAI(model="gpt-4o")
# Fallback model (ถูกกว่า, เร็วกว่า)fallback_llm = ChatOpenAI(model="gpt-4o-mini")
# สร้าง chain พร้อม fallbackchain = prompt | primary_llm.with_fallbacks([fallback_llm]) | parser
# ถ้า gpt-4o ล้มเหลว จะใช้ gpt-4o-mini แทนอัตโนมัติresult = chain.invoke({"topic": "AI", "question": "อธิบาย Transformer"})Retry Logic
Section titled “Retry Logic”คำอธิบาย: โค้ดตัวอย่างด้านล่างแสดงวิธีใช้งานด้วย Python ตามหัวข้อนี้แบบทีละขั้นตอน
# Retry สูงสุด 3 ครั้งchain = prompt | llm.with_retry( stop_after_attempt=3, wait_exponential_jitter=True) | parserRunnableConfig — กำหนดค่าการรัน
Section titled “RunnableConfig — กำหนดค่าการรัน”คำอธิบาย: โค้ดตัวอย่างด้านล่างแสดงวิธีใช้งานด้วย Python ตามหัวข้อนี้แบบทีละขั้นตอน
from langchain_core.runnables import RunnableConfig
config = RunnableConfig( max_concurrency=5, # จำกัดการทำงานพร้อมกัน run_name="my-chain", # ชื่อสำหรับ monitoring tags=["production"], # tags สำหรับ LangSmith metadata={"user_id": "123"},)
result = chain.invoke(input_data, config=config)สรุป LCEL Components
Section titled “สรุป LCEL Components”| Component | หน้าที่ | Syntax |
|---|---|---|
| (pipe) | เชื่อมต่อ components | a | b | c |
RunnableLambda | Custom function | RunnableLambda(fn) |
RunnablePassthrough | ส่งต่อ input | RunnablePassthrough() |
RunnableParallel | ทำงานพร้อมกัน | RunnableParallel(a=..., b=...) |
RunnableBranch | เลือกตามเงื่อนไข | RunnableBranch((cond, chain), ...) |
.with_fallbacks() | จัดการ error | llm.with_fallbacks([backup]) |
.with_retry() | ลองใหม่ | llm.with_retry(stop_after_attempt=3) |