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:

  1. 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."
  2. 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ắc Kể cho tôi một câu chuyện nhỏ về kem.
  3. Truyền mẫu nhắc Kể cho tôi một câu chuyện nhỏ về kem vào model (mô hình gpt4).
  4. Truyền kết quả trả về từ model vào trình phân tích output_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 contextquestion 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:

  1. Đầu tiên, tạo một đối tượng RunnableParallel chứa hai mục. Mục đầu tiên context sẽ bao gồm kết quả tài liệu mà trình truy xuất trích xuất. Mục thứ hai question 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ụng RunnablePassthrough để sao chép mục này.
  2. 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ột PromptValue.
  3. 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ượng ChatMessage.
  4. Cuối cùng, thành phần output_parser lấy một ChatMessage, chuyển đổi thành chuỗi Python và trả về từ phương thức invoke.