Nếu bạn đã từng vật lộn với việc viết prompt cho một pipeline LLM production, chắc bạn biết cái vòng lặp đó: chỉnh một câu, test, chỉnh lại, rồi hy vọng model hiểu đúng ý mình. Tôi đã trải qua điều đó — ba tuần trong một project tối ưu hệ thống QA có retrieval-augmented, mà nửa thời gian là đào bới lại các prompt cũ. Rồi tôi tìm thấy DSPy, và workflow thay đổi hoàn toàn.
Bài viết này phân tích thực tế: DSPy thực sự mạnh ở đâu, yếu ở đâu, và cách xây dựng một pipeline hoạt động được từ đầu.
Prompt Engineering thủ công vs Cách tiếp cận khai báo của DSPy
Prompt engineering truyền thống hoạt động như thế này: bạn viết một chuỗi ký tự, nhúng hướng dẫn nhiệm vụ vào đó, thêm vài ví dụ, rồi hy vọng model diễn giải đúng ý. Khi độ chính xác giảm — vì bạn đổi model, thay đổi domain, hoặc phân phối dữ liệu thay đổi — bạn lại phải quay lại viết tay prompt từ đầu.
DSPy (Declarative Self-improving Python) có cách tiếp cận khác. Thay vì viết prompt, bạn định nghĩa những gì bạn muốn — các trường đầu vào, đầu ra và các ràng buộc — rồi để DSPy tự tìm cách diễn đạt hướng dẫn phù hợp. Nó biên dịch chương trình của bạn thành các prompt tối ưu thông qua một bộ optimizer gọi là Teleprompters.
Đây là sự khác biệt cốt lõi trong thực tế:
- Cách thủ công: Bạn tự quản lý chuỗi prompt. Khi đổi model là hỏng ngay. Tối ưu hóa phụ thuộc hoàn toàn vào con người và rất chậm.
- Cách DSPy: Bạn định nghĩa một signature (đầu vào/đầu ra). DSPy tự tạo và tối ưu prompt dựa trên dữ liệu có nhãn và hàm metric.
Phép so sánh giúp tôi hiểu ra: DSPy với prompt cũng như ORM với SQL. Bạn vẫn làm việc với cùng một hệ thống nền tảng, nhưng lớp trừu tượng xử lý những phần tẻ nhạt để bạn tập trung vào điều code thực sự đang làm.
Ưu và Nhược điểm: Đánh giá thực tế sau khi dùng trên Production
Những điểm DSPy làm tốt
- Tối ưu không phụ thuộc model: Đổi GPT-4 sang Claude hay Llama mà không cần viết lại prompt. Signature vẫn giữ nguyên; DSPy tự tối ưu lại cho model mới.
- Pipeline có thể tái tạo: Thay vì một thư mục đầy file markdown chứa prompt, bạn có code Python được quản lý version với logic rõ ràng.
- Module có thể kết hợp: Ghép nối
dspy.ChainOfThought,dspy.ReActvà các module tùy chỉnh như các mảnh Lego. Mỗi module tự xử lý việc tạo prompt của mình. - Cải thiện dựa trên metric: Định nghĩa một metric thành công (exact match, F1, custom scorer), cung cấp tập dữ liệu có nhãn nhỏ, và các optimizer của DSPy như
BootstrapFewShothayMIPROsẽ tự động tìm kiếm prompt tốt hơn.
Với một pipeline phân loại tài liệu tôi đã deploy lên production, độ chính xác duy trì ổn định ở mức khoảng 89% F1 qua hai lần GPT-4 nâng version. Trước khi dùng DSPy, mỗi lần update model nghĩa là ít nhất một ngày chỉnh prompt thủ công.
Những điểm còn hạn chế
- Đường cong học tập: DSPy có các khái niệm trừu tượng riêng — Signatures, Modules, Teleprompters. Hãy dành một đến hai ngày để làm quen trước khi nắm được tư duy của nó.
- Chi phí tối ưu hóa: Chạy
BootstrapFewShotWithRandomSearchsẽ thực hiện nhiều lần gọi LLM. Một lần chạy với 30 ví dụ training trên GPT-4 có thể tốn từ $5–$20 tùy kích thước dataset. Hãy cache tích cực. - Debug khó hơn: Khi pipeline hoạt động sai, prompt được tạo ra nằm ở vài lớp bên dưới. Bạn cần biết phải tìm ở đâu.
- Dataset nhỏ thì được, quá nhỏ thì không: Optimizer cần đủ ví dụ có nhãn để tìm ra tín hiệu. Ít hơn 20 ví dụ thì kết quả sẽ rất nhiễu.
Cài đặt khuyến nghị
Hãy thiết lập môi trường sạch trước khi động vào bất kỳ optimizer nào. DSPy hoạt động với Python 3.9+ và hỗ trợ sẵn OpenAI, Anthropic, Google, Ollama cục bộ và nhiều hơn nữa.
# Tạo môi trường ảo
python -m venv venv
source venv/bin/activate
# Cài đặt DSPy
pip install dspy-ai
# Cho backend OpenAI
pip install openai
# Cho backend Anthropic
pip install anthropic
Tiếp theo, cấu hình language model. DSPy dùng một đối tượng LM toàn cục — khai báo một lần ở đầu script và mọi module sẽ tự động nhận nó:
import dspy
# OpenAI
lm = dspy.LM('openai/gpt-4o-mini', api_key='sk-...')
# Hoặc Anthropic
# lm = dspy.LM('anthropic/claude-3-haiku-20240307', api_key='sk-ant-...')
# Hoặc Ollama cục bộ
# lm = dspy.LM('ollama_chat/llama3', api_base='http://localhost:11434')
dspy.configure(lm=lm)
Trong môi trường production, lấy API key từ biến môi trường. Không bao giờ hardcode thông tin xác thực trong file source.
import os
lm = dspy.LM('openai/gpt-4o-mini', api_key=os.environ['OPENAI_API_KEY'])
dspy.configure(lm=lm)
Hướng dẫn Triển khai: Xây dựng Pipeline Tối ưu
Bước 1 — Định nghĩa Signature
Một Signature cho DSPy biết đầu vào và đầu ra là gì. Không có văn bản prompt — chỉ là tên trường và mô tả tùy chọn:
import dspy
class ClassifySupport(dspy.Signature):
"""Phân loại ticket hỗ trợ khách hàng vào một danh mục."""
ticket_text: str = dspy.InputField(desc="Nội dung thô của ticket hỗ trợ khách hàng")
category: str = dspy.OutputField(desc="Một trong các giá trị: billing, technical, account, other")
confidence: str = dspy.OutputField(desc="high, medium, hoặc low")
Docstring đó sẽ trở thành một phần của prompt được tạo ra. Hãy viết chính xác — hướng dẫn mơ hồ ở đây sẽ tạo ra kết quả mơ hồ.
Bước 2 — Xây dựng Module
Bọc signature của bạn trong một module. Dùng dspy.ChainOfThought khi các bước lập luận trung gian giúp tăng độ chính xác:
class SupportClassifier(dspy.Module):
def __init__(self):
self.classify = dspy.ChainOfThought(ClassifySupport)
def forward(self, ticket_text):
return self.classify(ticket_text=ticket_text)
# Kiểm tra trước khi tối ưu hóa
classifier = SupportClassifier()
result = classifier(ticket_text="Tôi bị tính phí hai lần trong tháng này. Vui lòng hoàn tiền.")
print(result.category) # billing
print(result.confidence) # high
Bước 3 — Tối ưu hóa với Teleprompter
Đây là nơi DSPy thể hiện giá trị thực sự. Chuẩn bị một tập dữ liệu có nhãn và một hàm metric, rồi chạy optimizer:
from dspy.evaluate import Evaluate
from dspy.teleprompt import BootstrapFewShot
# Các ví dụ có nhãn
trainset = [
dspy.Example(ticket_text="Yêu cầu hoàn tiền do tính phí trùng lặp", category="billing").with_inputs("ticket_text"),
dspy.Example(ticket_text="Ứng dụng bị crash trên iOS 17", category="technical").with_inputs("ticket_text"),
dspy.Example(ticket_text="Làm thế nào để đặt lại mật khẩu?", category="account").with_inputs("ticket_text"),
dspy.Example(ticket_text="Khi nào gói đăng ký của tôi được gia hạn?", category="billing").with_inputs("ticket_text"),
dspy.Example(ticket_text="Không thể kết nối với API", category="technical").with_inputs("ticket_text"),
# Thêm 15-30 ví dụ để có kết quả đáng tin cậy
]
# Metric: khớp chính xác theo category
def accuracy_metric(example, prediction, trace=None):
return example.category.lower() == prediction.category.lower()
# Chạy optimizer
teleprompter = BootstrapFewShot(metric=accuracy_metric, max_bootstrapped_demos=4)
optimized_classifier = teleprompter.compile(SupportClassifier(), trainset=trainset)
# Lưu chương trình đã tối ưu
optimized_classifier.save("support_classifier_optimized.json")
Sau khi tối ưu, DSPy đã tự động chọn các ví dụ few-shot tốt nhất và có thể đã điều chỉnh cấu trúc hướng dẫn. Tải lại sau mà không cần chạy lại optimizer:
classifier = SupportClassifier()
classifier.load("support_classifier_optimized.json")
Bước 4 — Kết hợp nhiều Module
Pipeline thực tế thường có nhiều hơn một bước. DSPy kết hợp rất tốt:
class SummarizeTicket(dspy.Signature):
"""Tóm tắt một ticket hỗ trợ trong một câu."""
ticket_text: str = dspy.InputField()
summary: str = dspy.OutputField()
class FullSupportPipeline(dspy.Module):
def __init__(self):
self.summarize = dspy.Predict(SummarizeTicket)
self.classify = dspy.ChainOfThought(ClassifySupport)
def forward(self, ticket_text):
summary = self.summarize(ticket_text=ticket_text).summary
classification = self.classify(ticket_text=summary)
return classification
Mẹo Debug
Kết quả không như mong đợi? Hãy kiểm tra prompt thực tế mà DSPy tạo ra trước khi cho rằng logic của bạn sai:
# Bật chế độ verbose để xem những gì đang được gửi đến LLM
with dspy.context(lm=lm):
result = classifier(ticket_text="Thẻ của tôi bị từ chối")
print(dspy.inspect_history(n=1)) # Hiển thị lần gọi LLM gần nhất
Một điều cần biết: DSPy mặc định cache các lần gọi LLM. Trong quá trình phát triển, điều này tiết kiệm tiền. Trên production, hãy đảm bảo bạn hiểu khi nào cache bị invalidate nếu dữ liệu nền của bạn thay đổi.
Khi nào nên dùng DSPy
DSPy phù hợp nhất khi:
- Pipeline của bạn có nhiều lần gọi LLM nối tiếp nhau và khó điều chỉnh độc lập
- Bạn cần đổi model nền mà không gây regression
- Bạn có dữ liệu có nhãn (hoặc có thể tạo ra) để thúc đẩy quá trình tối ưu
- Tính ổn định của prompt qua các lần release quan trọng hơn throughput thô
Với các script chạy một lần hoặc task đơn giản chỉ gọi một lần, overhead không xứng đáng. Một prompt thủ công được viết tốt sẽ ra đời nhanh hơn. Nhưng với bất kỳ thứ gì cần được duy trì và phát triển qua nhiều tháng, cách tiếp cận khai báo hoàn toàn đáng để đầu tư — gánh nặng bảo trì thấp hơn, ít phiên debug lúc nửa đêm hơn, và các bản cập nhật model không phá vỡ pipeline của bạn.
Optimizer chạy một lần. Sau đó, pipeline của bạn chỉ là code Python mà bạn có thể test, quản lý version và deploy như bất kỳ service nào khác.

