Bài 36.5: Bài tập thực hành ứng dụng LangChain

Sau khi đã khám phá tiềm năng to lớn của LangChain trong việc kết nối Mô hình Ngôn ngữ Lớn (LLM) với dữ liệu và các tác vụ phức tạp, đã đến lúc chúng ta xắn tay áo lên và đi sâu vào thực hành. LangChain không chỉ là một thư viện; nó là một khuôn khổ mạnh mẽ giúp chúng ta xây dựng các ứng dụng thực tế, đặc biệt là khi muốn tích hợp sức mạnh của AI vào các dự án web hiện đại của mình.

Bài viết này sẽ tập trung vào các bài tập thực hành cụ thể, giúp bạn hiểu rõ hơn về cách áp dụng các khái niệm của LangChain vào các kịch bản phổ biến. Chúng ta sẽ đi qua một số ứng dụng cơ bản đến nâng cao, với các đoạn mã minh họa ngắn gọn và giải thích chi tiết.

Bài Tập 1: Tương Tác Cơ Bản với LLM (Prompting)

Đây là nền tảng. Trước khi xây dựng bất cứ thứ gì phức tạp, chúng ta cần biết cách gửi một câu hỏi (prompt) đến LLM và nhận lại phản hồi. LangChain cung cấp các interface nhất quán để làm việc với nhiều loại LLM khác nhau.

  • Mục tiêu: Gửi một prompt đơn giản và in kết quả.
  • Công cụ LangChain: ChatModels hoặc LLMs.
# Cài đặt thư viện (nếu chưa có)
# pip install langchain-openai

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate

# Khởi tạo mô hình (thay thế model_name và API key nếu cần)
# Đảm bảo bạn đã thiết lập biến môi trường OPENAI_API_KEY hoặc truyền trực tiếp key
llm = ChatOpenAI(model="gpt-4o-mini")

# Tạo một prompt template để định dạng câu hỏi của chúng ta
prompt = ChatPromptTemplate.from_messages([
    ("system", "Bạn là một trợ lý hữu ích."),
    ("user", "{input}")
])

# Kết hợp prompt và model thành một chuỗi đơn giản (Chain)
chain = prompt | llm

# Gửi prompt và nhận kết quả
result = chain.invoke({"input": "Viết một câu tục ngữ về sự kiên trì."})

print(result.content)
  • Giải thích Code:

    • ChatOpenAI: Đại diện cho một mô hình chatbot từ OpenAI. LangChain hỗ trợ nhiều nhà cung cấp khác nhau (Anthropic, Google, v.v.).
    • ChatPromptTemplate.from_messages: Tạo một template cho prompt, cho phép bạn định nghĩa các vai trò (system, user, assistant) một cách rõ ràng. {input} là biến sẽ được thay thế bằng câu hỏi thực tế của người dùng.
    • prompt | llm: Đây là cú pháp của LangChain Expression Language (LCEL), tạo ra một chuỗi (chain) đơn giản. Đầu ra của prompt sẽ trở thành đầu vào của llm. LCEL là cách hiện đại và linh hoạt để xây dựng các luồng xử lý phức tạp.
    • chain.invoke({"input": ...}): Thực thi chuỗi với đầu vào được cung cấp dưới dạng dictionary.
    • result.content: Trích xuất nội dung văn bản từ phản hồi của LLM.
  • Ứng dụng Web: Bạn có thể sử dụng đoạn code này ở backend của ứng dụng web (ví dụ: trong một API endpoint) để xử lý các yêu cầu từ frontend (người dùng nhập câu hỏi vào một ô text, gửi lên backend, backend dùng LangChain gọi LLM và trả kết quả về hiển thị).

Bài Tập 2: Xây Dựng Chuỗi Tác Vụ Liên Tiếp (Chains)

Các tác vụ AI thực tế thường yêu cầu nhiều bước. Ví dụ: tóm tắt một văn bản rồi sau đó dịch bản tóm tắt đó sang ngôn ngữ khác. LangChain giúp chúng ta xâu chuỗi các bước này lại với nhau một cách dễ dàng.

  • Mục tiêu: Tạo một chuỗi gồm 2 bước: tóm tắt văn bản và sau đó dịch bản tóm tắt.
  • Công cụ LangChain: LCEL để kết nối các component.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

# Khởi tạo mô hình
llm = ChatOpenAI(model="gpt-4o-mini")

# Bước 1: Tóm tắt
summarize_prompt = ChatPromptTemplate.from_messages([
    ("system", "Tóm tắt văn bản sau đây một cách ngắn gọn và súc tích:\n\n{document}"),
    ("user", "{input}")
])
summarize_chain = summarize_prompt | llm | StrOutputParser()

# Bước 2: Dịch bản tóm tắt
translate_prompt = ChatPromptTemplate.from_messages([
    ("system", "Dịch văn bản sau sang tiếng Việt:\n\n{summary}"),
    ("user", "{input}") # input ở đây là văn bản gốc, nhưng chúng ta chỉ cần summary
])
translate_chain = translate_prompt | llm | StrOutputParser()

# Kết hợp hai chuỗi con thành một chuỗi lớn hơn
# Use LCEL's .invoke() or .batch() or define the final chain
# Let's show a simple sequential approach for clarity, or build a chain that takes initial input
# A better way using LCEL's combined chains:
full_chain = {
    "summary": summarize_chain,
    "input": lambda x: x["input"] # Pass the original input if needed by translate, though translate_prompt only needs summary
} | translate_chain # The output of the first part becomes input for the second part

# Văn bản mẫu
text_to_process = "LangChain is a framework designed to simplify the creation of applications using large language models. It helps with components like prompt management, chains, agents, and retrieval."

# Thực thi chuỗi lớn
# The structure above passes the output of summarize_chain as "summary" to the next step's prompt
result = full_chain.invoke({"input": text_to_process})

print(result) # Kết quả là bản dịch của bản tóm tắt
  • Giải thích Code:

    • Chúng ta tạo hai ChatPromptTemplate và hai chuỗi con (summarize_chain, translate_chain), mỗi chuỗi sử dụng StrOutputParser() để đảm bảo kết quả là chuỗi văn bản đơn giản.
    • full_chain: Đây là cách kết hợp các chuỗi con bằng LCEL. Chúng ta định nghĩa một dictionary mà key "summary" sẽ lấy kết quả từ summarize_chain, và key "input" lấy giá trị input ban đầu. Quan trọng: Đầu ra của dictionary này (chứa "summary") sau đó được đưa làm input cho translate_chain. LangChain đủ thông minh để khớp các biến nếu tên phù hợp (hoặc bạn có thể ánh xạ rõ ràng hơn).
    • chain.invoke(): Thực thi toàn bộ chuỗi từ đầu đến cuối.
  • Ứng dụng Web: Xử lý văn bản phức tạp từ người dùng (ví dụ: họ dán một bài báo dài, backend tóm tắt và dịch tự động).

Bài Tập 3: Kết Nối với Dữ Liệu Bên Ngoài (Retrieval - RAG)

Các LLM có kiến thức đến từ dữ liệu huấn luyện của chúng, thường là kiến thức công khai và không cập nhật. Để trả lời câu hỏi về dữ liệu riêng tư, cập nhật hoặc cụ thể, chúng ta cần kết nối LLM với nguồn dữ liệu bên ngoài. Kỹ thuật phổ biến là Retrieval Augmented Generation (RAG).

  • Mục tiêu: Xây dựng một hệ thống có thể trả lời câu hỏi dựa trên nội dung của một tài liệu cụ thể.
  • Công cụ LangChain: DocumentLoaders, TextSplitters, Embeddings, VectorStores, Retrievers.
# Cài đặt thêm thư viện
# pip install langchain-chroma beautifulsoup4 # hoặc thư viện vectorstore/loader khác
# pip install langchain-openai # for embeddings

from langchain_community.document_loaders import WebBaseLoader # Ví dụ: load từ web
from langchain_community.embeddings import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain.chains import create_retrieval_chain
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate

# Bước 1: Tải và xử lý tài liệu
# Thay thế URL bằng đường dẫn đến tài liệu của bạn (có thể là file PDF, text, etc.)
loader = WebBaseLoader("https://lilianweng.github.io/posts/2023-06-23-rag-paper/") # Ví dụ về bài viết về RAG
docs = loader.load()

# Chia nhỏ tài liệu thành các đoạn nhỏ
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
splits = text_splitter.split_documents(docs)

# Tạo Embedding và lưu vào Vector Store
# OpenAIEmbeddings cần OPENAI_API_KEY
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(documents=splits, embedding=embeddings)

# Tạo Retriever
retriever = vectorstore.as_retriever()

# Bước 2: Xây dựng Chuỗi RAG
# Prompt cho việc kết hợp câu hỏi và các đoạn văn bản liên quan
system_prompt = (
    "Trả lời câu hỏi của người dùng dựa trên ngữ cảnh sau đây:\n\n"
    "{context}"
)
prompt = ChatPromptTemplate.from_messages([
    ("system", system_prompt),
    ("user", "{input}")
])

# Chuỗi kết hợp tài liệu và trả lời
question_answer_chain = create_stuff_documents_chain(ChatOpenAI(model="gpt-4o-mini"), prompt)

# Chuỗi RAG đầy đủ: lấy tài liệu -> trả lời câu hỏi dựa trên tài liệu
rag_chain = create_retrieval_chain(retriever, question_answer_chain)

# Bước 3: Đặt câu hỏi
question = "What is RAG?"
response = rag_chain.invoke({"input": question})

print(response["answer"])
# print(response["context"]) # Bạn có thể xem các đoạn context được tìm thấy
  • Giải thích Code:

    • Tải & Xử lý: WebBaseLoader là một ví dụ về cách tải dữ liệu. RecursiveCharacterTextSplitter giúp chia tài liệu lớn thành các phần nhỏ hơn (chunks) để dễ xử lý.
    • Embedding & Vector Store: OpenAIEmbeddings chuyển đổi văn bản thành các vector số. Chroma.from_documents tạo một cơ sở dữ liệu vector (Vector Store) từ các đoạn văn bản và vector tương ứng. Vector Store giúp tìm kiếm các đoạn văn bản liên quan đến câu hỏi của người dùng.
    • Retriever: vectorstore.as_retriever() tạo một component có khả năng tìm kiếm trong Vector Store.
    • Chuỗi RAG:
      • create_stuff_documents_chain: Xây dựng chuỗi lấy các tài liệu tìm được và nhồi (stuff) chúng vào prompt cùng với câu hỏi của người dùng để LLM tạo ra câu trả lời.
      • create_retrieval_chain: Kết hợp retriever (tìm kiếm tài liệu) với chuỗi trả lời (tạo câu trả lời từ tài liệu tìm được). Khi .invoke({"input": question}) được gọi, retriever sẽ chạy trước, tìm các đoạn tài liệu liên quan, sau đó các tài liệu này được truyền vào question_answer_chain cùng với câu hỏi ban đầu để LLM tạo câu trả lời cuối cùng.
  • Ứng dụng Web: Xây dựng chatbot hỗ trợ khách hàng trả lời các câu hỏi thường gặp (FAQ) dựa trên tài liệu hướng dẫn nội bộ, xây dựng công cụ tìm kiếm ngữ nghĩa trên blog/website của bạn, hoặc tạo trợ lý nghiên cứu dựa trên bộ sưu tập tài liệu cá nhân.

Bài Tập 4: Cho Phép LLM Sử Dụng Công Cụ (Agents)

Đôi khi, LLM cần thực hiện các hành động bên ngoài phạm vi của nó, như tìm kiếm thông tin trên internet, chạy code, gọi API, v.v. Agents trong LangChain cho phép LLM quyết định khi nàolàm thế nào để sử dụng các công cụ (Tools) này.

  • Mục tiêu: Tạo một agent có thể sử dụng công cụ tìm kiếm để trả lời các câu hỏi yêu cầu thông tin cập nhật.
  • Công cụ LangChain: Tools, Agents, AgentExecutor.
# Cài đặt thêm thư viện cho công cụ tìm kiếm (ví dụ: DuckDuckGo)
# pip install -U langchain-community duckduckgo-search

from langchain_openai import ChatOpenAI
from langchain_community.tools import DuckDuckGoSearch
from langchain import hub # Để lấy prompt cho agent
from langchain.agents import create_tool_calling_agent
from langchain.agents import AgentExecutor

# Khởi tạo mô hình
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) # temperature=0 để agent hành động quyết đoán hơn

# Khởi tạo công cụ tìm kiếm
search_tool = DuckDuckGoSearch()
tools = [search_tool]

# Lấy prompt mặc định cho tool-calling agent
prompt = hub.pull("hwchase17/tool-calling-agent")

# Tạo agent
agent = create_tool_calling_agent(llm, tools, prompt)

# Tạo Agent Executor (động cơ chạy agent, quản lý việc gọi tools)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True) # verbose=True để xem quá trình suy luận của agent

# Đặt câu hỏi yêu cầu thông tin cập nhật
question = "Thời tiết ở Hà Nội hôm nay thế nào?"

# Chạy agent
response = agent_executor.invoke({"input": question})

print(response["output"])
  • Giải thích Code:

    • DuckDuckGoSearch: Một công cụ tìm kiếm đơn giản. LangChain hỗ trợ rất nhiều loại công cụ khác (Google Search, Arxiv, Wikipedia, các công cụ tùy chỉnh của bạn, v.v.).
    • hub.pull("hwchase17/tool-calling-agent"): Tải một prompt chuẩn từ LangChain Hub, được thiết kế đặc biệt để hướng dẫn LLM sử dụng các công cụ được cung cấp.
    • create_tool_calling_agent: Tạo cấu trúc agent. Nó cần LLM, danh sách các công cụ có sẵn, và prompt hướng dẫn. LLM sẽ sử dụng prompt này để quyết định khi nào gọi công cụ nào với tham số gì.
    • AgentExecutor: Đây là "trái tim" của agent. Nó nhận agent và các công cụ. Khi bạn gọi .invoke(), AgentExecutor sẽ:
      1. Gửi prompt và câu hỏi đến LLM.
      2. LLM "suy nghĩ" và trả về lệnh gọi công cụ (tool call).
      3. AgentExecutor thực thi lệnh gọi công cụ đó.
      4. Lấy kết quả từ công cụ và đưa lại cho LLM.
      5. Lặp lại quá trình này cho đến khi LLM quyết định đã có đủ thông tin để đưa ra câu trả lời cuối cùng.
    • verbose=True giúp bạn thấy rõ ràng từng bước suy luận và hành động của agent.
  • Ứng dụng Web: Xây dựng trợ lý ảo trong ứng dụng web có thể thực hiện các tác vụ như đặt lịch, gửi email, tra cứu thông tin thị trường chứng khoán, hoặc tương tác với bất kỳ API nào mà bạn đóng gói thành công cụ.

Bài Tập 5: Duy Trì Trạng Thái Trò Chuyện (Memory)

Trong các ứng dụng chatbot hoặc trợ lý ảo, việc LLM nhớ lại các lượt trò chuyện trước đó là cực kỳ quan trọng để duy trì ngữ cảnh và cuộc hội thoại tự nhiên. LangChain cung cấp các module Memory cho mục đích này.

  • Mục tiêu: Xây dựng một chuỗi trò chuyện đơn giản có khả năng nhớ lại các câu trước đó.
  • Công cụ LangChain: Memory (ví dụ: ChatMessageHistory, ConversationBufferMemory), các chuỗi hoặc agent hỗ trợ memory.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.messages import HumanMessage, AIMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory

# Khởi tạo mô hình
llm = ChatOpenAI(model="gpt-4o-mini")

# Prompt có placeholder cho lịch sử trò chuyện
prompt = ChatPromptTemplate.from_messages([
    ("system", "Bạn là một trợ lý thân thiện."),
    ("placeholder", "{chat_history}"), # Placeholder cho lịch sử trò chuyện
    ("user", "{input}")
])

# Chuỗi cơ bản
chain = prompt | llm | StrOutputParser()

# Xây dựng chuỗi có Memory
chain_with_history = RunnableWithMessageHistory(
    chain,
    lambda session_id: ChatMessageHistory(session_id=session_id), # Hàm để lấy/tạo lịch sử cho mỗi session
    input_messages_key="input", # Key trong input dictionary chứa tin nhắn mới của người dùng
    history_messages_key="chat_history", # Key trong prompt template để điền lịch sử trò chuyện vào
)

# Thực hiện các lượt trò chuyện (sử dụng cùng một session_id)
session_id = "my_chat_session_123"

print("User: Xin chào, bạn khỏe không?")
response1 = chain_with_history.invoke(
    {"input": "Xin chào, bạn khỏe không?"},
    config={"configurable": {"session_id": session_id}}
)
print(f"AI: {response1}")

print("\nUser: Tên tôi là Nam. Bạn nhớ không?")
response2 = chain_with_history.invoke(
    {"input": "Tên tôi là Nam. Bạn nhớ không?"},
    config={"configurable": {"session_id": session_id}}
)
print(f"AI: {response2}") # LLM nên nhớ tên Nam từ câu trước

print("\nUser: Tên tôi là gì?")
response3 = chain_with_history.invoke(
    {"input": "Tên tôi là gì?"},
    config={"configurable": {"session_id": session_id}}
)
print(f"AI: {response3}") # LLM nên trả lời đúng tên Nam
  • Giải thích Code:

    • ChatMessageHistory: Là một cách đơn giản để lưu trữ lịch sử các cặp tin nhắn (HumanMessage, AIMessage).
    • RunnableWithMessageHistory: Wrapper mạnh mẽ từ LCEL. Nó tự động xử lý việc tải lịch sử trò chuyện (dựa trên session_id), thêm lịch sử đó vào prompt trước khi gọi chuỗi gốc (chain), và lưu lại cặp tin nhắn mới nhất vào lịch sử sau khi chuỗi gốc thực thi xong.
    • lambda session_id: ChatMessageHistory(...): Đây là hàm cung cấp một thể hiện ChatMessageHistory cho mỗi session_id. Trong ứng dụng thực tế, bạn sẽ thay thế nó bằng logic tải lịch sử từ database hoặc cache dựa trên session_id.
    • input_messages_key, history_messages_key: Cấu hình để RunnableWithMessageHistory biết lấy tin nhắn mới của người dùng từ đâu trong dictionary input và điền lịch sử trò chuyện vào placeholder nào trong prompt.
    • config={"configurable": {"session_id": session_id}}: Cách truyền session_id vào khi gọi invoke. RunnableWithMessageHistory sử dụng session_id này để quản lý lịch sử trò chuyện.
  • Ứng dụng Web: Xây dựng các chatbot tương tác, các trợ lý ảo cá nhân hóa trong ứng dụng web, nơi mỗi người dùng có một lịch sử trò chuyện riêng biệt được duy trì qua các phiên làm việc.

Comments

There are no comments at the moment.