Prompt Engineering cho Large Language Model

Prompt Engineering được hiểu đơn giản là quá trình thiết kế Prompt để hướng Model tạo ra Output phù hợp hơn với mục tiêu của chúng ta. Trong bài viết này mình sẽ đi qua những phần quan trọng của Prompt Engineering dựa trên Chapter 6 của cuốn Hands-On Large Language Models của Jay Alammar và Maarten Grootendorst.

Prompt Engineering cho Large Language Model

Khi sử dụng một Large Language Model, chúng ta thường không thay đổi trực tiếp trọng số của Model mà thay đổi cách đưa yêu cầu vào Model. Cùng một câu hỏi, chỉ cần thay đổi cách viết Prompt thì kết quả trả về có thể khác nhau khá nhiều.

Prompt Engineering được hiểu đơn giản là quá trình thiết kế Prompt để hướng Model tạo ra Output phù hợp hơn với mục tiêu của chúng ta. Trong bài viết này mình sẽ đi qua những phần quan trọng của Prompt Engineering dựa trên Chapter 6 của cuốn Hands-On Large Language Models của Jay Alammar và Maarten Grootendorst.

I. Trước Prompt Engineering: Kiểm soát Model Output

Trước khi nói về cách viết Prompt, có một phần khá quan trọng là cách Model sinh ra Text. Với một Generative Model, Model không sinh toàn bộ câu trả lời cùng lúc. Tại mỗi bước, Model tạo ra một phân phối xác suất trên Vocabulary rồi lựa chọn Token tiếp theo. Cách lựa chọn này ảnh hưởng trực tiếp tới Output.

Hai tham số thường gặp là temperature và top_p. Temperature kiểm soát mức độ ngẫu nhiên của quá trình Sampling. Temperature thấp khiến Model ưu tiên những Token có xác suất cao, kết quả thường ổn định hơn. Temperature cao làm phân phối xác suất phẳng hơn và Output đa dạng hơn. Trong khi đó, top_p sử dụng Nucleus Sampling, tức thay vì xét toàn bộ Vocabulary, Model chỉ Sampling trong nhóm Token có tổng xác suất đạt một ngưỡng nhất định.

Ví dụ:

generation_config = {
"do_sample": True,
"temperature": 0.7,
"top_p": 0.9,
}

Nếu đang làm Classification, Information Extraction hay những Task cần Output ổn định, mình thường không cần quá nhiều tính ngẫu nhiên. Ngược lại với Creative Writing hay Brainstorming, Sampling có thể hữu ích hơn. Vì vậy chất lượng Output không chỉ phụ thuộc vào Prompt mà còn phụ thuộc vào cách chúng ta cấu hình Generation.

II. Thành phần cơ bản của một Prompt

Một Prompt đơn giản thường có hai thành phần chính: Instruction là Model cần làm gì, Data/Input là nội dung mà Model cần xử lý.

Ví dụ:

Phân loại Review sau thành Positive, Neutral hoặc Negative.

Review:
"Quán khá đẹp nhưng đồ ăn bình thường."

Ở đây, Instruction là yêu cầu phân loại Review thành Positive, Neutral hoặc Negative, còn Input chính là câu Review. Ta có thể bổ sung thêm Output Indicator để nói rõ Model phải trả về thứ gì.

Phân loại Review sau thành Positive, Neutral hoặc Negative.

Review:
"Quán khá đẹp nhưng đồ ăn bình thường."

Sentiment:

Việc này nghe khá đơn giản nhưng lại là nền tảng của phần lớn Prompt mà chúng ta sử dụng. Thay vì chỉ đưa dữ liệu cho Model rồi hy vọng nó tự hiểu cần làm gì, hãy xác định rõ Task, Input và Output mong muốn.

III. Instruction-based Prompting

Instruction Prompting là cách chúng ta mô tả trực tiếp nhiệm vụ mà Model cần thực hiện. Ví dụ với bài toán Summarization, Prompt có thể đơn giản là Tóm tắt nội dung sau trong tối đa 3 câu, còn với Information Extraction ta có thể yêu cầu Trích xuất Tên công ty, Người đại diện, Địa chỉ và Mã số thuế từ văn bản sau. Điểm quan trọng ở đây là Instruction nên mô tả đủ rõ Model cần làm gì và Output mong muốn như thế nào. Prompt chi tiết hơn không đồng nghĩa với Prompt càng dài càng tốt. Mục tiêu của Instruction Prompting là cung cấp vừa đủ thông tin để Model hiểu đúng Task, Constraint và Output mà chúng ta mong muốn.

IV. Prompt phức tạp hơn

Khi xây dựng Application sử dụng LLM, Prompt thực tế thường không chỉ có một Instruction. Ta có thể bổ sung thêm các thành phần như: Persona để xác định vai trò của Model, Context để cung cấp thông tin nền, Constraints để giới hạn hành vi và Output Format để kiểm soát cấu trúc kết quả.

Ví dụ Persona:

Bạn là một trợ lý chuyên phân tích tài liệu pháp luật Việt Nam.

Context: Dưới đây là các tài liệu được Retrieval từ Database:

Constraints: Chỉ sử dụng thông tin được cung cấp trong Context. Nếu không tìm thấy câu trả lời, hãy trả về "Không đủ thông tin".

Output Format:

Trả về JSON theo Format:
{
"answer": "...",
"source": "...",
"confidence": 0.0
}

Như vậy một Prompt hoàn chỉnh có thể bao gồm: Persona + Task + Context + Constraints + Input + Output Format. Không phải lúc nào cũng cần đầy đủ tất cả các thành phần trên. Với những Task đơn giản, việc nhét quá nhiều Instruction đôi khi chỉ khiến Prompt khó quản lý hơn. Theo mình nên bắt đầu từ Prompt đơn giản nhất có thể, sau đó bổ sung Constraint khi phát hiện những trường hợp Model trả lời sai.

V. In-context Learning

Một trong những khả năng khá thú vị của LLM là In-context Learning. Thay vì Fine-tune Model, chúng ta đưa một số Example trực tiếp vào Prompt để Model hiểu cách thực hiện Task. Có ba dạng thường gặp: Zero-shot, One-shot và Few-shot.

Zero-shot

Zero-shot không cung cấp bất kỳ Example nào.

Phân loại Sentiment:

Text:
"Ứng dụng khá tốt nhưng đôi lúc phản hồi chậm."

Sentiment:

Model phải tự hiểu Task hoàn toàn từ Instruction.

One-shot

One-shot cung cấp một Example để Model nhận biết Pattern.

Text:
"Dịch vụ rất tốt."

Sentiment:
Positive

Text:
"Ứng dụng khá tốt nhưng đôi lúc phản hồi chậm."

Sentiment:

Few-shot

Few-shot cung cấp nhiều Example hơn.

Text:
"Dịch vụ rất tốt."
Sentiment:
Positive

Text:
"Ứng dụng liên tục bị lỗi."
Sentiment:
Negative

Text:
"Sản phẩm sử dụng được, không có gì đặc biệt."
Sentiment:
Neutral

Text:
"Ứng dụng khá tốt nhưng đôi lúc phản hồi chậm."
Sentiment:

Few-shot Prompting đặc biệt hữu ích khi Task có Format hoặc cách phân loại riêng mà chỉ Instruction thôi chưa mô tả rõ được. Điểm mình thấy quan trọng ở đây là Example cũng là một phần của Prompt Engineering. Nếu Example không nhất quán hoặc chứa những trường hợp sai, Model cũng có thể học theo chính Pattern đó trong Context.

VI. Chain Prompting

Một Prompt rất dài chưa chắc là cách tốt nhất để giải quyết một bài toán phức tạp. Giả sử chúng ta muốn Model thực hiện lần lượt: đọc Feedback của khách hàng, xác định vấn đề chính, phân loại vấn đề, đề xuất hướng xử lý và viết phản hồi cho khách hàng. Ta hoàn toàn có thể đưa tất cả vào một Prompt duy nhất, nhưng một cách khác dễ kiểm soát hơn là chia thành nhiều Prompt nhỏ.

Prompt đầu tiên:

Xác định vấn đề chính trong Feedback sau:
...

Output của bước này được đưa vào Prompt tiếp theo:

Dựa trên vấn đề sau, hãy phân loại thành:
Payment, Account, Technical hoặc Other.

Problem:
...

Sau đó ta tiếp tục sử dụng Output để tạo phản hồi. Đây được gọi là Chain Prompting. Mỗi Prompt chỉ tập trung vào một nhiệm vụ nhỏ hơn nên thường dễ kiểm soát, Debug và đánh giá hơn.

Ý tưởng này cũng là nền tảng của khá nhiều Workflow sử dụng LLM. Thay vì xem LLM như một hàm duy nhất nhận Prompt rồi trả Answer, chúng ta có thể xây dựng một Pipeline gồm nhiều lần gọi Model với nhiệm vụ khác nhau.

VII. Reasoning với LLM

Đối với những câu hỏi đơn giản, Model có thể trả lời ngay. Nhưng với những bài toán cần nhiều bước suy luận, chúng ta có thể thiết kế Prompt để Model dành nhiều khả năng tính toán hơn cho quá trình giải quyết vấn đề. Chapter 6 giới thiệu ba kỹ thuật đáng chú ý: Chain-of-Thought, Self-Consistency và Tree-of-Thought.

Chain-of-Thought

Chain-of-Thought Prompting khuyến khích Model thực hiện các bước suy luận trung gian trước khi đưa ra kết quả.

Một Zero-shot Prompt đơn giản có thể yêu cầu:

Giải bài toán sau. Hãy phân tích bài toán theo từng bước trước khi đưa ra đáp án cuối cùng.
Ngoài ra, chúng ta có thể cung cấp Example chứa cách giải rồi yêu cầu Model áp dụng Pattern tương tự cho câu hỏi tiếp theo. Cách này thường hữu ích với những Task như toán học, Logic, Planning hoặc những bài toán cần nhiều bước xử lý. Tuy nhiên trong Application thực tế, thứ chúng ta quan tâm cuối cùng vẫn là chất lượng của Answer, không nhất thiết lúc nào cũng cần yêu cầu Model hiển thị toàn bộ quá trình Reasoning ra cho người dùng.

Self-Consistency

Do quá trình Sampling, cùng một Prompt có thể tạo ra nhiều Answer khác nhau. Self-Consistency tận dụng điều này bằng cách chạy cùng một bài toán nhiều lần, sau đó chọn Answer xuất hiện nhất quán nhất. Cách này có thể cải thiện độ ổn định với những Task Reasoning khó, nhưng đổi lại chúng ta phải gọi Model nhiều lần. Trong hệ thống sử dụng API, điều này đồng nghĩa với Latency và Cost đều tăng.

Tree-of-Thought

Tree-of-Thought mở rộng ý tưởng trên bằng cách cho Model xem xét nhiều hướng giải quyết khác nhau tại từng bước. Thay vì chỉ đi theo một chuỗi suy luận. Model có thể xem xét nhiều Candidate. Sau đó đánh giá Candidate nào khả thi hơn để tiếp tục mở rộng. Cách tiếp cận này phù hợp với những bài toán có nhiều hướng giải quyết như Planning, Search hoặc Puzzle, nhưng số lần gọi Model cũng tăng lên đáng kể.

Vì vậy mình không nghĩ đây là kỹ thuật cần sử dụng mặc định cho mọi Prompt. Nó phù hợp hơn khi giá trị của một Answer đúng đủ lớn để đánh đổi thêm Compute và Cost.

VIII. Kiểm soát Format Output

Trong Application thực tế, Output đẹp cho con người đọc đôi khi chưa đủ. Giả sử Backend cần nhận:

{
"intent": "search_restaurant",
"location": "Quận 1",
"max_price": 200000
}

Nếu Model trả về:

Theo tôi người dùng đang muốn tìm nhà hàng ở Quận 1...

thì dù nội dung đúng, chương trình phía sau vẫn khó sử dụng. Cách cơ bản là yêu cầu Format rõ ràng trong Prompt và cung cấp Example.

Trả về duy nhất JSON theo Format:

{
"intent": string,
"location": string | null,
"max_price": number | null
}

Chapter cũng đề cập tới Grammar-constrained Sampling, tức giới hạn những Token mà Model được phép sinh để Output tuân theo một Grammar nhất định. Khác với việc chỉ nói Please return JSON rồi hy vọng Model làm đúng, Constrained Generation kiểm soát trực tiếp quá trình Generation để Model không thể tạo ra Output ngoài cấu trúc cho phép.

Với những Pipeline mà Output của LLM được đưa trực tiếp vào Code, Database hay Tool khác, mình nghĩ việc kiểm soát cấu trúc Output quan trọng không kém nội dung của Prompt.

IX. Một số điều mình rút ra khi viết Prompt

Sau khi đọc phần Prompt Engineering, mình thấy có một số nguyên tắc khá thực tế: hãy nói rõ Model cần làm gì, vì Prompt mơ hồ thường tạo ra Output khó đoán; Example có giá trị rất lớn, nếu chỉ Instruction chưa đủ thì One-shot hoặc Few-shot thường là lựa chọn đơn giản trước khi nghĩ tới Fine-tuning; với bài toán phức tạp, chia thành nhiều bước thường dễ kiểm soát hơn một Prompt khổng lồ; cuối cùng, đừng chỉ tập trung vào Prompt mà quên các yếu tố khác như Sampling Parameters, Context, Model đang sử dụng và Output Format.

Prompt Engineering vẫn cần Evaluation. Việc đọc vài Output rồi thấy "có vẻ tốt" không đảm bảo Prompt hoạt động tốt trên hàng nghìn Input khác nhau. Nếu xây dựng một LLM Application nghiêm túc, mình nghĩ Prompt cũng nên được xem giống Code: có Version, Test Case và Evaluation Dataset riêng.

X. Kết luận

Prompt Engineering ban đầu có thể trông giống như việc tìm cách "nói chuyện với AI" tốt hơn, nhưng khi đi sâu hơn nó thực tế là cách chúng ta thiết kế Interface giữa Application và Language Model.

Một Prompt có thể bắt đầu rất đơn giản với:

Instruction + Input

sau đó phát triển thành:

Persona + Instruction + Context + Examples + Constraints + Output Format

Với những bài toán phức tạp hơn, chúng ta có thể tiếp tục kết hợp In-context Learning, Chain Prompting, Reasoning và Verification.

Điều mình rút ra là không có một Prompt Template tốt nhất cho mọi bài toán. Một Prompt tốt là Prompt mô tả đúng Task, cung cấp đủ Context, tạo ra Output có thể kiểm soát và quan trọng nhất là hoạt động ổn định trên dữ liệu thực tế.

Thay vì cố viết một "siêu Prompt" ngay từ đầu, có lẽ cách hợp lý hơn là bắt đầu đơn giản: Viết Prompt → Test → Xem lỗi → Bổ sung Constraint/Example → Evaluate lại.

Prompt Engineering lúc này không còn là thử vài câu chữ ngẫu nhiên, mà trở thành một quá trình Engineering thực sự.

Nguồn tham khảo

  1. Jay Alammar, Maarten Grootendorst, Hands-On Large Language Models, Chapter 6: Prompt Engineering.
  2. Official Hands-On Large Language Models Repository, Chapter 6 Notebook.

Được chọn theo chủ đề và các khái niệm xuất hiện trong bài này.

Xem tất cả bài viết

Thảo luận bài viết

Câu hỏi, ghi chú và góc nhìn của bạn về nội dung này.

0 bình luận