Giới thiệu về LCEL
LCEL (Ngôn ngữ biểu diễn Chuỗi Ngôn ngữ) là một công cụ quản lý quy trình làm việc mạnh mẽ cho phép bạn xây dựng chuỗi nhiệm vụ phức tạp từ các thành phần cơ bản và hỗ trợ các tính năng tích hợp sẵn như xử lý luồng, xử lý song song và ghi nhật ký.
Ví dụ cơ bản: Prompt + Mô hình + Bộ phân tích Kết quả
Trong ví dụ này, chúng ta sẽ thể hiện cách sử dụng LCEL (Ngôn ngữ biểu diễn Chuỗi Ngôn ngữ) để kết nối ba thành phần - mẫu nhắc nhở, mô hình và bộ phân tích kết quả - với nhau để tạo thành một chuỗi công việc hoàn chỉnh để thực hiện nhiệm vụ "kể chuyện cười." Đoạn mã thể hiện cách tạo ra các chuỗi, sử dụng ký hiệu đường ống |
để kết nối các thành phần khác nhau và giới thiệu vai trò của mỗi thành phần cùng với kết quả đầu ra.
Trước hết, hãy xem cách kết nối mẫu nhắc nhở và mô hình để tạo ra một câu chuyện cười về một chủ đề cụ thể:
Cài đặt các phụ thuộc
%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("Kể cho tôi nghe một câu chuyện cười về {chủ_đề}")
model = ChatOpenAI(model="gpt-4")
output_parser = StrOutputParser()
chain = prompt | model | output_parser
chain.invoke({"chủ_đề": "kem"})
Kết quả
"Tại sao không có bữa tiệc nào mời kem? Bởi vì nó tan chảy khi trời nóng!"
Trong đoạn mã này, chúng ta sử dụng LCEL để kết nối các thành phần khác nhau vào một chuỗi:
chain = prompt | model | output_parser
Ký hiệu |
ở đây tương tự như toán tử ống Unix, nó kết nối các thành phần khác nhau với nhau và chuyển đầu ra của một thành phần thành đầu vào của thành phần tiếp theo.
Trong chuỗi này, đầu vào của người dùng được chuyển đến mẫu nhắc nhở, sau đó đầu ra của mẫu nhắc nhở được chuyển đến mô hình, và cuối cùng đầu ra của mô hình được chuyển đến bộ phân tích kết quả. Hãy xem xét từng thành phần một để hiểu rõ hơn về điều gì đang diễn ra.
1. Mẫu nhắc nhở
prompt
là một BasePromptTemplate
chấp nhận từ điển biến mẫu và tạo ra một PromptValue
. PromptValue
là một đối tượng bao bọc chứa mẫu nhắc nhở, có thể được chuyển đến LLM
(dưới dạng đầu vào dưới dạng chuỗi) hoặc ChatModel
(dưới dạng đầu vào dưới dạng chuỗi thông điệp). Nó có thể được sử dụng với bất kỳ loại mô hình ngôn ngữ nào vì nó xác định logic để tạo ra BaseMessage
và tạo ra chuỗi.
prompt_value = prompt.invoke({"chủ_đề": "kem"})
prompt_value
Kết quả
ChatPromptValue(messages=[HumanMessage(content='Kể cho tôi nghe một câu chuyện cười về kem')])
Dưới đây, chúng ta chuyển đổi kết quả định dạng mẫu nhắc nhở thành định dạng thông điệp được sử dụng bởi các mô hình trò chuyện:
prompt_value.to_messages()
Kết quả
[HumanMessage(content='Kể cho tôi nghe một câu chuyện cười về kem')]
Nó cũng có thể được chuyển đổi trực tiếp thành một chuỗi:
prompt_value.to_string()
Kết quả
'Human: Kể cho tôi nghe một câu chuyện cười về kem.'
2. Mô hình
Tiếp theo, chuyển PromptValue
đến model
. Trong ví dụ này, model
của chúng ta là một ChatModel
, có nghĩa là nó sẽ tạo ra một BaseMessage
.
Hãy thử gọi model
trực tiếp:
message = model.invoke(prompt_value)
message
Trả về:
AIMessage(content="Tại sao không có bữa tiệc nào mời kem?\n\nBởi vì nó tan chảy khi trời nóng!")
Nếu model
của chúng ta được định nghĩa dưới dạng LLM
, nó sẽ tạo ra một chuỗi.
from langchain_openai.llms import OpenAI
llm = OpenAI(model="gpt-3.5-turbo-instruct")
llm.invoke(prompt_value)
Mô hình trả về:
'\n\nBot: Tại sao xe kem bị hỏng? Bởi vì nó đã trải qua một trạng thái tan chảy!'
3. Bộ phân tích Kết quả
Cuối cùng, chuyển đầu ra từ model
của chúng ta đến output_parser
, đó là một BaseOutputParser
, có nghĩa là nó chấp nhận một chuỗi hoặc BaseMessage
làm đầu vào. StrOutputParser
cụ thể chuyển đổi bất kỳ đầu vào nào thành một chuỗi đơn giản.
output_parser.invoke(message)
Tại sao không có bữa tiệc nào mời kem? Bởi vì nó tan chảy khi trời nóng!
4. Toàn bộ quy trình
Quá trình thực thi diễn ra như sau:
- Gọi
chain.invoke({"topic": "ice cream"})
, tương đương với việc khởi động quy trình mà chúng ta đã xác định và truyền tham số{"topic": "ice cream"}
để tạo một câu chuyện cười về "kem." - Truyền tham số gọi
{"topic": "ice cream"}
vào thành phần đầu tiên của chuỗi,prompt
, có nhiệm vụ định dạng mẫu nhắc để nhận được mẫu nhắcKể cho tôi một câu chuyện nhỏ về kem
. - Truyền mẫu nhắc
Kể cho tôi một câu chuyện nhỏ về kem
vàomodel
(mô hình gpt4). - Truyền kết quả trả về từ
model
vào trình phân tíchoutput_parser
, có nhiệm vụ định dạng kết quả của mô hình và trả về nội dung cuối cùng.
Nếu bạn quan tâm đến kết quả của bất kỳ thành phần nào, bạn có thể kiểm tra phiên bản nhỏ hơn của chuỗi bất kỳ lúc nào, chẳng hạn như prompt
hoặc prompt | model
, để xem kết quả trung gian:
input = {"topic": "ice cream"}
prompt.invoke(input)
(prompt | model).invoke(input)
Ví dụ Tìm kiếm RAG
Tiếp theo, chúng ta sẽ giải thích một ví dụ LCEL phức tạp hơn một chút. Chúng ta sẽ chạy một ví dụ về tìm kiếm nâng cao để tạo chuỗi, nhằm thêm một số thông tin nền khi trả lời câu hỏi.
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 = """Trả lời câu hỏi dựa chỉ trên ngữ cảnh sau đây:
{context}
Câu hỏi: {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("harrison làm việc ở đâu?")
Trong trường hợp này, chuỗi đã được tạo thành như sau:
chain = setup_and_retrieval | prompt | model | output_parser
Để nói một cách đơn giản, mẫu nhắc trên chấp nhận context
và question
như giá trị để thay thế trong mẫu nhắc. Trước khi xây dựng mẫu nhắc, chúng ta muốn truy xuất tài liệu liên quan để sử dụng làm phần của ngữ cảnh.
Làm thử, chúng ta sử dụng DocArrayInMemorySearch
để mô phỏng một cơ sở dữ liệu vector dựa trên bộ nhớ, xác định một bộ truy xuất có thể truy xuất tài liệu tương tự dựa trên truy vấn. Đây cũng là một thành phần runnable có thể chuỗi, nhưng bạn cũng có thể thử chạy nó một cách riêng lẻ:
retriever.invoke("harrison làm việc ở đâu?")
Sau đó, chúng ta sử dụng RunnableParallel
để chuẩn bị đầu vào cho mẫu nhắc, tìm kiếm tài liệu bằng cách sử dụng trình truy xuất và truyền câu hỏi của người dùng bằng cách sử dụng RunnablePassthrough
:
setup_and_retrieval = RunnableParallel(
{"context": retriever, "question": RunnablePassthrough()}
)
Tóm lại, chuỗi hoàn chỉnh như sau:
setup_and_retrieval = RunnableParallel(
{"context": retriever, "question": RunnablePassthrough()}
)
chain = setup_and_retrieval | prompt | model | output_parser
Quá trình diễn ra như sau:
- Đầu tiên, tạo một đối tượng
RunnableParallel
chứa hai mục. Mục đầu tiêncontext
sẽ bao gồm kết quả tài liệu mà trình truy xuất trích xuất. Mục thứ haiquestion
sẽ chứa câu hỏi ban đầu của người dùng. Để truyền câu hỏi, chúng ta sử dụngRunnablePassthrough
để sao chép mục này. - Truyền từ điển từ bước trước vào thành phần
prompt
. Nó chấp nhận đầu vào của người dùng (tức làquestion
) cũng như các tài liệu truy xuất (tức làcontext
), xây dựng một mẫu nhắc và đầu ra mộtPromptValue
. - Thành phần
model
lấy mẫu nhắc đã tạo và chuyển nó cho mô hình LLM của OpenAI để đánh giá. Đầu ra được tạo ra bởi mô hình là một đối tượngChatMessage
. - Cuối cùng, thành phần
output_parser
lấy mộtChatMessage
, chuyển đổi thành chuỗi Python và trả về từ phương thứcinvoke
.