Bài 36.2: Chains và Agents trong LangChain

Bài 36.2: Chains và Agents trong LangChain
Chào mừng bạn quay trở lại với chuỗi bài viết về Lập trình Web Front-end kết hợp AI! Trong bài trước, chúng ta đã cùng tìm hiểu về cách tích hợp các Large Language Models (LLMs) cơ bản. Tuy nhiên, thực tế cho thấy, để xây dựng các ứng dụng AI thực sự mạnh mẽ và hữu ích, chúng ta cần làm nhiều hơn là chỉ gọi một mô hình ngôn ngữ đơn lẻ. Chúng ta cần một cách để kết nối nhiều thành phần lại với nhau, tạo thành một quy trình xử lý phức tạp hơn. Đây chính là lúc Chains và Agents của LangChain phát huy sức mạnh.
LangChain là một framework được thiết kế để giúp các nhà phát triển xây dựng các ứng dụng sử dụng các mô hình ngôn ngữ lớn. Nó cung cấp một cấu trúc linh hoạt để kết hợp các mô hình ngôn ngữ với các nguồn dữ liệu và công cụ khác. Trọng tâm của LangChain chính là hai khái niệm mà chúng ta sắp tìm hiểu: Chains và Agents.
Hãy cùng đi sâu vào từng khái niệm để hiểu rõ cách chúng hoạt động và làm thế nào để sử dụng chúng.
Chains (Chuỗi): Liên kết các khối lại với nhau
Hãy tưởng tượng bạn muốn làm một việc gì đó hơi phức tạp, ví dụ: lấy thông tin về một chủ đề từ một nguồn đáng tin cậy, sau đó tóm tắt thông tin đó và cuối cùng là dịch bản tóm tắt sang một ngôn ngữ khác. Đây là một quy trình gồm nhiều bước tuần tự. Nếu chỉ dùng một lời nhắc (prompt) duy nhất cho một LLM, có thể kết quả sẽ không như ý, hoặc mô hình sẽ gặp khó khăn khi xử lý tất cả các yêu cầu cùng lúc.
Chains trong LangChain ra đời để giải quyết vấn đề này. Về cơ bản, một Chain là một chuỗi các thành phần (components) được liên kết với nhau theo một thứ tự xác định, nơi đầu ra (output) của một thành phần trở thành đầu vào (input) cho thành phần tiếp theo.
Các thành phần trong một Chain có thể là:
- LLMs: Gọi một mô hình ngôn ngữ để xử lý văn bản.
- Prompt Templates: Tạo ra các lời nhắc (prompt) linh hoạt.
- Output Parsers: Trích xuất và định dạng thông tin từ đầu ra của LLM.
- Loaders/Retrievers: Tải dữ liệu từ các nguồn bên ngoài (file, database, web).
- Và nhiều thành phần khác.
Tại sao lại cần Chains?
- Phân rã nhiệm vụ phức tạp: Chia nhỏ một nhiệm vụ lớn thành các bước nhỏ hơn, dễ quản lý và xử lý.
- Tái sử dụng: Xây dựng các chuỗi logic có thể sử dụng lại cho nhiều tác vụ khác nhau.
- Cấu trúc và rõ ràng: Dễ dàng hình dung và debug luồng xử lý.
- Cải thiện độ tin cậy: Mỗi bước nhỏ có thể được kiểm soát và tối ưu hóa riêng biệt, dẫn đến kết quả cuối cùng chính xác hơn.
Các loại Chains phổ biến
LangChain cung cấp nhiều loại Chain khác nhau cho các mục đích sử dụng đa dạng. Một số loại cơ bản bao gồm:
- LLMChain: Đây là Chain cơ bản nhất, chỉ kết hợp một Prompt Template và một LLM. Nó lấy dữ liệu đầu vào, định dạng nó bằng Prompt Template, gửi đến LLM và trả về kết quả.
- SimpleSequentialChain: Liên kết nhiều LLMChain lại với nhau một cách tuần tự đơn giản, trong đó đầu ra của Chain này trở thành đầu vào duy nhất cho Chain tiếp theo.
- SequentialChain: Phiên bản nâng cao hơn của SimpleSequentialChain, cho phép quản lý nhiều đầu vào/đầu ra giữa các bước.
- RetrievalQA Chain: Kết hợp LLM với một Retriever (thành phần lấy dữ liệu từ cơ sở tri thức của bạn) để trả lời các câu hỏi dựa trên dữ liệu riêng.
Ví dụ minh họa với Chains
Hãy xem một ví dụ đơn giản với LLMChain
và sau đó là SimpleSequentialChain
.
Ví dụ 1: Sử dụng LLMChain (Chỉ định dạng Prompt và gọi LLM)
Giả sử bạn muốn tạo một lời giới thiệu cho một công ty khởi nghiệp dựa trên tên sản phẩm và lĩnh vực hoạt động.
# Import các thư viện cần thiết
from langchain_openai import ChatOpenAI # Sử dụng ChatOpenAI cho các mô hình chat
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
import os
# Thiết lập API Key (Hãy chắc chắn bạn đã thiết lập biến môi trường OPENAI_API_KEY)
# os.environ["OPENAI_API_KEY"] = "YOUR_API_KEY" # Bỏ comment và thay key nếu cần thiết
# Khởi tạo LLM
llm = ChatOpenAI(temperature=0.7) # Temperature = 0.7 giúp kết quả sáng tạo hơn một chút
# Định nghĩa Prompt Template
prompt_template = PromptTemplate(
input_variables=["product_name", "industry"],
template="Viết một đoạn giới thiệu ngắn hấp dẫn cho một công ty khởi nghiệp về {product_name} trong lĩnh vực {industry}."
)
# Tạo LLMChain
chain = LLMChain(llm=llm, prompt=prompt_template)
# Chạy Chain với đầu vào
input_data = {"product_name": "FitPal", "industry": "Theo dõi sức khỏe"}
response = chain.run(input_data)
print("Đoạn giới thiệu công ty:")
print(response)
Giải thích code:
- Chúng ta import
ChatOpenAI
,PromptTemplate
, vàLLMChain
. ChatOpenAI
là lớp để tương tác với các mô hình chat của OpenAI (ví dụ: gpt-3.5-turbo, gpt-4).PromptTemplate
định nghĩa cấu trúc của lời nhắc với các biến đầu vào (product_name
,industry
).LLMChain
kết nốillm
vàprompt_template
. Khichain.run()
được gọi, nó sẽ lấyinput_data
, điền vàoprompt_template
, gửi lời nhắc hoàn chỉnh đếnllm
, và trả về kết quả từllm
.
Ví dụ 2: Sử dụng SimpleSequentialChain (Nhiều bước tuần tự)
Bây giờ, hãy mở rộng ví dụ trên: sau khi có đoạn giới thiệu công ty, bạn muốn LLM đặt tên cho công ty dựa trên đoạn giới thiệu đó.
# Import các thư viện cần thiết
from langchain_openai import ChatOpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain, SimpleSequentialChain
import os
# Thiết lập API Key
# os.environ["OPENAI_API_KEY"] = "YOUR_API_KEY"
# Khởi tạo LLM (sử dụng lại LLM từ ví dụ trước)
llm = ChatOpenAI(temperature=0.7)
# Định nghĩa Chain 1: Viết giới thiệu công ty
prompt_template_intro = PromptTemplate(
input_variables=["product_name", "industry"],
template="Viết một đoạn giới thiệu ngắn hấp dẫn cho một công ty khởi nghiệp về {product_name} trong lĩnh vực {industry}."
)
chain_intro = LLMChain(llm=llm, prompt=prompt_template_intro, output_key="intro") # Đặt tên cho đầu ra
# Định nghĩa Chain 2: Đặt tên công ty dựa trên giới thiệu
prompt_template_name = PromptTemplate(
input_variables=["intro"], # Đầu vào là kết quả từ chain_intro
template="Dựa trên đoạn giới thiệu công ty sau:\n{intro}\nHãy đề xuất 3 cái tên sáng tạo cho công ty này."
)
chain_name = LLMChain(llm=llm, prompt=prompt_template_name, output_key="company_names") # Đặt tên cho đầu ra
# Kết hợp hai Chains bằng SimpleSequentialChain
# SimpleSequentialChain chỉ cần danh sách các chains theo thứ tự
overall_chain = SimpleSequentialChain(
chains=[chain_intro, chain_name],
verbose=True # Bật verbose để thấy quá trình chạy của từng bước
)
# Chạy chuỗi tổng thể với đầu vào của Chain đầu tiên
input_data_overall = {"product_name": "FitPal", "industry": "Theo dõi sức khỏe"}
response_overall = overall_chain.run(input_data_overall)
print("\nKết quả cuối cùng (Tên công ty được đề xuất):")
print(response_overall)
Giải thích code:
- Chúng ta tạo hai
LLMChain
riêng biệt:chain_intro
vàchain_name
. - Quan trọng: Output của
chain_intro
(được đặt tên là "intro" bằngoutput_key
) sẽ tự động trở thành input chochain_name
(được định nghĩa là "intro" trongprompt_template_name
).SimpleSequentialChain
tự động nối chúng lại. SimpleSequentialChain
được khởi tạo với danh sách các chains theo thứ tự thực thi.verbose=True
giúp bạn nhìn thấy từng bước trong console khi chuỗi chạy, rất hữu ích cho việc debug.- Khi
overall_chain.run()
được gọi, nó sẽ chạychain_intro
trước vớiinput_data_overall
, lấy kết quả, sau đó dùng kết quả đó làm đầu vào chochain_name
, và cuối cùng trả về kết quả từchain_name
.
Chains rất hữu ích cho các quy trình xử lý dữ liệu có các bước rõ ràng và cố định. Nhưng điều gì xảy ra khi bạn không biết trước cần bao nhiêu bước, hoặc bước tiếp theo phụ thuộc vào kết quả của bước hiện tại? Đây là lúc Agents xuất hiện.
Agents (Tác tử): Khi LLM cần hành động và suy luận
Trong thế giới thực, chúng ta thường cần tương tác với môi trường bên ngoài để hoàn thành một nhiệm vụ. Ví dụ: để trả lời câu hỏi "Ai là tổng thống hiện tại của Pháp và số dân của Paris là bao nhiêu?", bạn cần tra cứu thông tin (sử dụng công cụ tìm kiếm), sau đó có thể cần một công cụ khác để tính toán hoặc tổng hợp thông tin.
Agents trong LangChain đưa khái niệm này lên một tầm cao mới. Thay vì chỉ thực hiện một chuỗi các bước đã được xác định trước (như Chains), một Agent sử dụng một LLM làm "bộ não" suy luận để:
- Xác định hành động tiếp theo: Dựa trên yêu cầu của người dùng và kết quả từ các hành động trước đó.
- Chọn công cụ (Tool) phù hợp: Chọn từ một tập hợp các công cụ có sẵn (ví dụ: công cụ tìm kiếm, công cụ chạy code Python, công cụ truy cập database).
- Thực thi công cụ: Gọi công cụ đã chọn với các tham số thích hợp.
- Quan sát kết quả: Nhận đầu ra từ công cụ.
- Lặp lại: Tiếp tục suy luận và hành động cho đến khi hoàn thành nhiệm vụ hoặc xác định không thể hoàn thành.
Đây được gọi là chu trình Suy luận và Hành động (Reasoning and Acting - ReAct).
Các thành phần chính của một Agent
- LLM (Language Model): Là trái tim của Agent. Nó nhận vào lời nhắc, lịch sử tương tác, mô tả các công cụ có sẵn và dựa vào đó để suy luận xem cần làm gì tiếp theo.
- Tools (Công cụ): Các hàm hoặc API mà Agent có thể gọi để tương tác với thế giới bên ngoài. Các công cụ có thể là:
- Tìm kiếm trên web (Google Search, SERPAPI).
- Chạy code (Python REPL).
- Truy cập database.
- Gọi API tùy chỉnh.
- Tính toán toán học (
llm-math
).
- Agent Executor: Lớp điều phối chính. Nó nhận đầu vào từ người dùng, gửi đến LLM, phân tích đầu ra của LLM để xem Agent muốn thực hiện hành động nào và sử dụng công cụ nào, sau đó gọi công cụ đó và đưa kết quả trở lại cho LLM để Agent tiếp tục suy luận.
Tại sao lại cần Agents?
- Xử lý nhiệm vụ động: Giải quyết các vấn đề mà bạn không thể xác định trước tất cả các bước.
- Tương tác với môi trường bên ngoài: Truy cập thông tin cập nhật từ internet, chạy code, sử dụng các hệ thống khác.
- Linh hoạt và thích ứng: Agent có thể điều chỉnh chiến lược dựa trên kết quả thu được từ các công cụ.
Ví dụ minh họa với Agents
Hãy xem một ví dụ đơn giản về việc sử dụng Agent để trả lời một câu hỏi đòi hỏi tính toán.
# Import các thư viện cần thiết
from langchain_openai import ChatOpenAI
from langchain_community.tools import load_tools # Sử dụng langchain_community cho các công cụ tích hợp sẵn
from langchain.agents import initialize_agent, AgentType
import os
# Thiết lập API Key
# os.environ["OPENAI_API_KEY"] = "YOUR_API_KEY"
# Lưu ý: Một số công cụ (ví dụ: serpapi) cần API key riêng
# Khởi tạo LLM (sử dụng lại LLM)
llm = ChatOpenAI(temperature=0) # Temperature = 0 để kết quả nhất quán và tập trung vào suy luận
# Tải các công cụ mà Agent có thể sử dụng
# Ví dụ: "llm-math" cho phép thực hiện các phép toán
tools = load_tools(["llm-math"], llm=llm)
# Khởi tạo Agent
# AgentType.zero_shot_react_description là một loại agent phổ biến sử dụng mô hình ReAct
agent = initialize_agent(
tools,
llm,
agent=AgentType.zero_shot_react_description,
verbose=True # Bật verbose để xem quá trình suy luận và hành động của Agent
)
# Chạy Agent với một câu hỏi
question = "Nếu tôi có 100 đô la và giá của một cổ phiếu là 15 đô la, tôi có thể mua bao nhiêu cổ phiếu?"
response = agent.run(question)
print("\nCâu trả lời của Agent:")
print(response)
Giải thích code:
- Chúng ta import
ChatOpenAI
,load_tools
,initialize_agent
, vàAgentType
. load_tools(["llm-math"], llm=llm)
tải công cụ tính toán toán học. Công cụ này sẽ được cung cấp cho Agent.initialize_agent
tạo ra Agent.- Tham số đầu tiên là danh sách các
tools
mà Agent có thể sử dụng. - Tham số thứ hai là
llm
mà Agent sẽ dùng để suy luận. agent=AgentType.zero_shot_react_description
chỉ định loại Agent. Loại này hướng dẫn LLM suy nghĩ theo từng bước: Suy nghĩ (Thought), Hành động (Action), Đầu vào hành động (Action Input), Quan sát (Observation).verbose=True
hiển thị quá trình suy luận chi tiết của Agent trong console. Bạn sẽ thấy Agent nhận câu hỏi, suy nghĩ rằng cần dùng công cụ toán học, gọi công cụ với phép tính, nhận kết quả từ công cụ và sau đó đưa ra câu trả lời cuối cùng.
- Tham số đầu tiên là danh sách các
agent.run(question)
thực thi Agent với câu hỏi đã cho. Agent sẽ tự động quyết định cách sử dụng công cụ để trả lời câu hỏi.
Ví dụ này khá đơn giản, nhưng sức mạnh thực sự của Agents bộc lộ khi bạn cung cấp cho chúng nhiều công cụ phức tạp hơn, cho phép chúng tương tác với các hệ thống bên ngoài một cách thông minh.
Chains vs. Agents: Khi nào dùng gì?
Sau khi tìm hiểu cả hai khái niệm, câu hỏi đặt ra là: Khi nào nên sử dụng Chains và khi nào nên sử dụng Agents?
Sử dụng Chains khi:
- Quy trình xử lý của bạn có các bước rõ ràng, cố định và tuần tự.
- Bạn biết trước các thành phần cần thiết và thứ tự thực hiện của chúng.
- Nhiệm vụ không đòi hỏi sự tương tác động với các công cụ bên ngoài dựa trên kết quả trung gian.
- Bạn muốn một luồng xử lý dễ dự đoán và kiểm soát.
- Các ví dụ điển hình: Tóm tắt tài liệu, dịch văn bản sau khi tóm tắt, trích xuất thông tin theo cấu trúc định sẵn, xử lý dữ liệu qua nhiều bước biến đổi.
Sử dụng Agents khi:
- Nhiệm vụ đòi hỏi suy luận động và quyết định dựa trên kết quả trung gian.
- Bạn cần tương tác với các công cụ bên ngoài (tìm kiếm web, chạy code, truy cập API) để thu thập thông tin hoặc thực hiện hành động.
- Số lượng bước hoặc loại bước cần thực hiện không được xác định trước.
- Các ví dụ điển hình: Trả lời các câu hỏi phức tạp yêu cầu tra cứu thông tin, phân tích dữ liệu bằng cách viết và chạy code, tương tác với các hệ thống khác thông qua API, tạo ra chatbot có khả năng thực hiện hành động.
Nói một cách đơn giản: Chains làm theo công thức có sẵn, còn Agents tự tìm công thức (bằng cách suy luận và dùng công cụ) để giải bài toán.
Thậm chí, bạn có thể kết hợp cả hai! Một Agent có thể sử dụng một Chain như một trong các "công cụ" của nó, hoặc một Chain có thể bao gồm một Agent ở một bước nào đó để xử lý một phần nhiệm vụ phức tạp hơn.
Comments