DSPy 2026 - Lập trình LLM thay vì Prompt Engineering với Signatures, Modules, MIPROv2 và BootstrapFinetune cho Multi-Agent Production

Posted on: 4/15/2026 2:10:30 AM

Trong suốt ba năm đầu của kỷ nguyên LLM, cả ngành cùng làm một việc: viết prompt thật khéo. Mỗi team có "prompt engineer" riêng, mỗi repo có một thư mục prompts/ chứa hàng chục file text dài dằng dặc, mỗi lần đổi model là cả tuần tinh chỉnh lại. Nhưng đến cuối 2024, một dự án nghiên cứu từ Stanford NLP bắt đầu đặt câu hỏi ngược: Vì sao ta phải viết prompt bằng tay, trong khi ta có thể lập trình bài toán và để compiler sinh ra prompt cho ta? Dự án đó là DSPy — khung lập trình cho LLM nơi ta không viết chuỗi ký tự, ta viết signaturemodule, rồi để optimizer tìm ra prompt, few-shot example và thậm chí cả trọng số LoRA tốt nhất cho chúng. Đến 2026, DSPy đã trở thành một trong những cách tiếp cận tạo pipeline multi-agent ổn định nhất khi cần chạy qua nhiều model, nhiều nhà cung cấp và nhiều phiên bản dataset. Bài viết này đi sâu vào kiến trúc, các module cốt lõi, optimizer, cách tích hợp với Redis cho caching và ClickHouse cho observability, và những pattern production mà đội engineering nên nắm năm 2026.

40-60%Giảm số token prompt trung bình sau khi compile so với prompt viết tay
2-5xTăng accuracy điển hình trên benchmark RAG khi dùng MIPROv2
15 minThời gian chuyển toàn bộ pipeline từ GPT-4o sang Claude chỉ bằng đổi một dòng cấu hình
0 promptSố chuỗi prompt viết tay trong codebase DSPy thuần chuẩn

1. Vì sao DSPy là thay đổi thế hệ trong cách làm LLM

Muốn hiểu DSPy, hãy nhìn lại vòng đời một tính năng LLM truyền thống. Lập trình viên mở editor, viết một prompt khoảng 400-800 token mô tả nhiệm vụ, nhét vài ví dụ few-shot, chạy thử trên một bộ input mẫu, thấy sai ở edge case thì thêm câu "Hãy chắc chắn rằng..." vào prompt. Ba tuần sau, prompt dài 2000 token với mười lời nhắc nhở chồng lên nhau, không ai dám sửa vì sợ hồi quy, và mỗi khi nhà cung cấp cập nhật model thì mọi thứ phải làm lại từ đầu. Đây không phải là phần mềm — đó là một bộ sưu tập dị bản ngẫu nhiên của một loạt câu mô tả bài toán bằng tiếng Anh.

DSPy đặt câu hỏi: nếu ta coi pipeline LLM là một chương trình — với các đầu vào kiểu rõ ràng, các bước biến đổi có cấu trúc, và một hàm mục tiêu đo lường được — thì prompt chỉ còn là "mã sinh ra tự động" cho một máy ảo đặc biệt tên là LM. Giống như ngày xưa ta không viết assembly, ngày nay ta không nên viết prompt bằng tay. Ta viết signature (tương tự type signature trong Haskell), viết module (tương tự layer trong PyTorch) và gọi optimizer để compile — y hệt việc một compiler C tối ưu vòng lặp, một compiler PyTorch tối ưu graph.

Phát biểu cốt lõi của DSPy

Lập trình viên mô tả bài toán (input/output fields, metric), không mô tả cách giải. Optimizer sẽ tìm ra cách giải tốt nhất trong không gian {prompt, few-shot demos, weights} dựa trên metric và dataset. Khi đổi model, đổi data, đổi metric — ta chỉ chạy lại optimizer, không viết lại chuỗi tự nhiên.

2. Ba trụ cột: Signature, Module, Optimizer

Toàn bộ DSPy xoay quanh ba khái niệm. Nắm chắc chúng là nắm chắc framework.

graph LR
    S["Signature
input/output types
+ docstring"] --> M["Module
Predict / CoT / ReAct
+ compose thành Program"] M --> P["Program
một forward(...) hoàn chỉnh"] D["Trainset
<=200 mẫu"] --> O Me["Metric
hàm đo đúng/sai"] --> O["Optimizer
BootstrapFewShot / MIPROv2
BootstrapFinetune"] P --> O O --> CP["Compiled Program
prompt + demos + weights
được tối ưu"] CP --> Prod["Production runtime
module.forward(...)"] style S fill:#e94560,stroke:#fff,color:#fff style M fill:#0f3460,stroke:#fff,color:#fff style O fill:#0f3460,stroke:#fff,color:#fff style CP fill:#4CAF50,stroke:#fff,color:#fff

Mỗi mảnh giải một vấn đề khác nhau. Signature tách bạch đặc tả khỏi cách thực hiện — ta nói "hàm này nhận câu hỏi và trả ra câu trả lời dạng ngắn", không nói "hãy trả lời súc tích và chính xác". Module là đơn vị thực thi — cùng một signature có thể được phục vụ bởi Predict thuần, ChainOfThought bật reasoning, hoặc ReAct gọi tool. Optimizer là phần làm DSPy khác biệt — nó biến một pipeline viết thô thành phiên bản có prompt/demo/weights được tối ưu trên dataset nhỏ của bạn.

3. Signature — viết bài toán như viết type

Một signature trong DSPy trông như thế này:

import dspy

class AnswerWithCitation(dspy.Signature):
    """Trả lời câu hỏi dựa trên các đoạn ngữ cảnh, kèm trích dẫn đoạn nào."""
    context: list[str] = dspy.InputField(desc="Các đoạn văn liên quan, đã được truy xuất")
    question: str = dspy.InputField()
    answer: str = dspy.OutputField(desc="Câu trả lời ngắn, 1-3 câu")
    citation_ids: list[int] = dspy.OutputField(desc="Chỉ số 0-based của các đoạn được dùng")

Đây không chỉ là kiểu. Docstring trong lớp là mô tả bài toán ở mức ngữ nghĩa — DSPy dùng nó làm "core instruction" khi sinh prompt. Các field có kiểu Python chuẩn; list, dict, int, enum, thậm chí Pydantic model cho output có cấu trúc. Khi signature được dùng trong một Module, DSPy sẽ tự động:

  • Sinh chỉ dẫn về định dạng output dựa trên kiểu (ví dụ: với list[int] sẽ yêu cầu mảng JSON số nguyên).
  • Parse output LM về đúng kiểu và validate, retry với instruction cụ thể nếu sai.
  • Nối few-shot demos tự động khi optimizer cung cấp.

Tránh docstring mang tính "prompt engineering"

Trong DSPy, docstring chỉ nên mô tả nhiệm vụ, không chứa "hãy trả lời ngắn gọn, đừng dài dòng, đừng thêm lời dẫn...". Những chỉ dẫn đó nên để optimizer tự khám phá. Bạn chỉ nói "trả lời câu hỏi kèm citation", DSPy sẽ tìm ra cách nhắc model phù hợp nhất trên dataset của bạn.

4. Module — từ Predict đến ReAct

Module là đơn vị thực thi một signature. DSPy cung cấp sẵn một tập module đủ cho hầu hết nhu cầu:

  • dspy.Predict — gọi LM một lần, parse output. Đây là lớp nền tảng mà các module khác kế thừa.
  • dspy.ChainOfThought — thêm một field reasoning: str trước các output field. Model buộc phải sinh chuỗi suy luận trước khi trả lời. Là cải tiến nhỏ nhưng thường tăng 5-15% accuracy trên bài toán có reasoning.
  • dspy.ProgramOfThought — model sinh code Python, chạy code trong , lấy kết quả làm output. Rất mạnh cho bài toán tính toán, phân tích dữ liệu.
  • dspy.ReAct — vòng lặp Thought-Action-Observation với tool use. Module tự quản lý history và dừng khi có answer.
  • dspy.MultiChainComparison — gọi ChainOfThought nhiều lần với temperature > 0, sau đó aggregate. Xấp xỉ self-consistency.
  • dspy.Refine — gọi một module, kiểm tra output bằng một metric, nếu chưa đạt thì gọi lại kèm feedback. Dạng agent tự sửa đơn giản.

Một program DSPy là một class kế thừa dspy.Module, định nghĩa các submodule trong __init__ và compose trong forward:

class RAGAgent(dspy.Module):
    def __init__(self, k=5):
        super().__init__()
        self.retrieve = dspy.Retrieve(k=k)
        self.generate = dspy.ChainOfThought(AnswerWithCitation)

    def forward(self, question):
        ctx = self.retrieve(question).passages
        pred = self.generate(context=ctx, question=question)
        return dspy.Prediction(
            answer=pred.answer,
            citations=pred.citation_ids,
            context=ctx,
        )

Nhìn có vẻ không khác LangChain — nhưng điểm mấu chốt là không có chuỗi prompt nào xuất hiện. Khi chạy thô, DSPy sinh prompt tự động từ signature. Khi chạy sau khi compile, prompt đó đã được optimizer tinh chỉnh dựa trên trainset và metric.

5. Optimizer — nơi phép màu xảy ra

Optimizer (gốc gọi là "teleprompter") nhận vào một Program chưa compile, một trainset nhỏ, và một metric; trả về Program đã compile. Có nhiều optimizer ở các mức độ phức tạp khác nhau:

graph TB
    U["Uncompiled Program"] --> B1["LabeledFewShot
chèn demo cố định"] U --> B2["BootstrapFewShot
tự sinh demo bằng teacher model"] U --> B3["BootstrapFewShotWithRandomSearch
thử nhiều tập demo, chọn bộ tốt nhất"] U --> B4["MIPROv2
tối ưu đồng thời instruction + demo
bằng Bayesian optimization"] U --> B5["BootstrapFinetune
sinh dữ liệu training rồi finetune LoRA
cho open model"] B1 --> C["Compiled Program"] B2 --> C B3 --> C B4 --> C B5 --> C style U fill:#888,stroke:#fff,color:#fff style B4 fill:#e94560,stroke:#fff,color:#fff style B5 fill:#e94560,stroke:#fff,color:#fff style C fill:#4CAF50,stroke:#fff,color:#fff

Trong thực tế, ba optimizer được dùng nhiều nhất năm 2026 là:

  • BootstrapFewShot — cách rẻ nhất để có một Program nhỉnh hơn viết tay. Ý tưởng: dùng chính program chưa compile như một "teacher", chạy trên trainset, giữ lại các trace đi kèm metric cao, nhúng chúng làm few-shot demos. Hầu như miễn phí, chỉ tốn bằng N lần chạy trainset.
  • MIPROv2 — hiện là optimizer SOTA trong DSPy. Nó dùng Bayesian optimization để tìm đồng thời (a) instruction tốt nhất cho từng Predict layer, và (b) tập few-shot demos tốt nhất. MIPROv2 đặc biệt mạnh khi bạn có dataset vài chục đến vài trăm mẫu và metric đo được end-to-end. Giá là thời gian compile tính bằng chục phút đến vài giờ, cùng với một "prompt proposer model" thường là GPT-4o hoặc Claude.
  • BootstrapFinetune — khi đích đến là một open-weight model, DSPy sẽ dùng teacher mạnh sinh ra tập training, rồi finetune (thường là LoRA) trên student model. Pipeline DSPy vẫn y nguyên — chỉ weights bên dưới thay đổi.

Tập train nhỏ là đủ

Không giống training truyền thống, DSPy không học từ hàng chục nghìn mẫu. 50-200 mẫu chất lượng thường là đủ cho BootstrapFewShot và MIPROv2. Điểm nghẽn thật sự không phải số lượng mẫu mà là độ tin cậy của metric. Metric yếu thì optimizer cũng chỉ tối ưu sai hướng nhanh hơn.

6. Metric — cái giá thực sự của DSPy

Optimizer chỉ tốt bằng metric. Và viết metric tốt cho bài toán LLM là việc khó nhất — khó hơn viết prompt. Trong DSPy, metric là một hàm Python nhận (example, prediction, trace) và trả về số (hoặc bool). Có ba họ metric hay gặp:

  • Exact-match / containment — dùng khi output có ground truth rõ (ví dụ: câu trả lời nằm trong một tập đáp án).
  • Programmatic check — dùng cho bài toán có ràng buộc kiểu: JSON hợp lệ, mỗi citation có thật, tổng cộng <= N token, thuộc enum cho phép, v.v.
  • LLM-as-judge — dùng chính một signature DSPy khác để chấm điểm. Rất mềm dẻo nhưng phải viết cẩn thận; judge tồi là một nguồn optimize sai phổ biến.

Mẫu metric điển hình cho một RAG agent:

def rag_metric(example, pred, trace=None):
    ans_ok = example.answer.lower() in pred.answer.lower()
    cites_ok = all(0 <= c < len(pred.context) for c in pred.citations)
    format_ok = len(pred.answer.split()) <= 60
    return int(ans_ok and cites_ok and format_ok)

Lưu ý hàm này trả về số nguyên 0/1. Nhiều optimizer DSPy (BootstrapFewShot, MIPROv2) dùng tín hiệu rời rạc này để chọn trace "tốt" làm demo. Nếu metric của bạn là một hàm liên tục, hãy ngưỡng hóa về nhị phân trước khi đưa cho optimizer — hoặc dùng optimizer hỗ trợ continuous reward như dspy.BootstrapFewShotWithOptuna.

7. Retrieval Models — RAG theo kiểu DSPy

Trong DSPy, retrieval được mô hình hóa như một "Retrieval Model" (RM) — một client cung cấp hàm retrieve(query, k) -> list[Passage]. DSPy cung cấp RM cho hầu hết vector store phổ biến: Chroma, Weaviate, Pinecone, Qdrant, ColBERTv2, và đặc biệt từ 2025 có adapter cho Redis 8 Vector Set. Cấu hình toàn cục như sau:

import dspy

lm = dspy.LM("anthropic/claude-sonnet-4-6", max_tokens=1024)
rm = dspy.RedisVS(
    host="redis-cluster.internal",
    key="embeddings:knowledge",
    embed_model="text-embedding-3-small",
    k=5,
)
dspy.configure(lm=lm, rm=rm)

Sau đó mọi module gọi dspy.Retrieve(k=5) sẽ dùng Redis Vector Set ở dưới. Giá trị của mô hình này: pipeline DSPy không bị khóa vào một vector store cụ thể — đổi Redis sang Milvus chỉ là đổi một dòng dspy.configure.

8. Multi-Agent với DSPy — mỗi agent là một Program

Điểm mà DSPy tỏa sáng trong kiến trúc multi-agent là tính compose được. Mỗi agent là một dspy.Module độc lập, có signature riêng, có thể compile riêng trên dataset riêng. Một hệ multi-agent là một module cấp cao hơn — chỉ là một program gọi nhiều program khác.

graph LR
    U["User Query"] --> R["Router
Predict[QueryType]"] R -->|factual| RAG["RAGAgent
ChainOfThought"] R -->|code| CA["CodeAgent
ReAct + tools"] R -->|analysis| DA["DataAgent
ProgramOfThought"] RAG --> Agg["Aggregator
Predict[FinalAnswer]"] CA --> Agg DA --> Agg Agg --> Resp["Response
typed output"] style R fill:#e94560,stroke:#fff,color:#fff style Agg fill:#4CAF50,stroke:#fff,color:#fff

Mỗi agent có thể được compile với optimizer khác: Router dùng BootstrapFewShot vì signature ngắn, dữ liệu đơn giản; RAGAgent dùng MIPROv2 vì instruction phức tạp hơn; CodeAgent dùng BootstrapFinetune để nhỏ gọn và chạy nhanh trên Qwen2.5-Coder. Một program cha bọc tất cả lại và có thể được compile end-to-end — nghĩa là optimizer nhìn pipeline tổng thể và phân bổ few-shot/instruction cho từng layer dựa trên metric cuối cùng. Đây là điều LangChain không có: bạn không thể "compile" một chain LangChain; bạn chỉ có thể chạy nó.

9. Caching với Redis — cả prompt cache lẫn LM cache

DSPy có sẵn một LM cache nội bộ dựa trên hash của (model, prompt, temperature, stop). Mặc định nó lưu cục bộ bằng diskcache, phù hợp lúc phát triển. Ở production, bạn nên thay bằng Redis để chia sẻ cache giữa các worker:

import dspy
import redis

r = redis.Redis(host="redis-cache.internal", decode_responses=True)

def redis_cache(prompt_hash, fn):
    cached = r.get(f"dspy:lm:{prompt_hash}")
    if cached:
        return cached
    out = fn()
    r.setex(f"dspy:lm:{prompt_hash}", 86400, out)
    return out

dspy.settings.configure(lm=lm, cache=redis_cache)

Trên Redis 8 trở lên, ta còn có thêm một tầng nữa là semantic cache — dùng Vector Set để tìm prompt tương tự về ngữ nghĩa chứ không chỉ trùng byte. Điều này đặc biệt hữu ích cho các agent gặp câu hỏi tương tự viết theo nhiều cách khác nhau. Kết hợp hai tầng:

  • Lookup tầng 1 — exact cache: hash SHA1 của payload đầy đủ, TTL ngắn (vài giờ), hit rate rất cao với các request lặp hoàn toàn (ví dụ cùng một request đi qua retry).
  • Lookup tầng 2 — semantic cache: embed query, VSIM với threshold 0.92, TTL trung bình (vài ngày), bắt được các phiên bản paraphrase.
  • Miss cả hai: gọi LM thật, ghi kết quả vào cả hai tầng.

Coi chừng khi cache một agent có tool-use

Với ReAct hoặc các agent có side effect (gọi API ngoài, đổi database), cache nguyên câu trả lời là nguy hiểm — side effect sẽ không xảy ra ở lần cache hit. Chỉ cache các inner Predict layer (ví dụ: Predict sinh Thought, Predict parse Observation), không cache toàn bộ agent.forward.

10. Observability — đưa trace DSPy vào ClickHouse

Mỗi lần một module DSPy chạy, nó tạo ra một trace gồm: prompt cuối cùng gửi đến LM, output, tokens in/out, latency, metric (nếu có). DSPy expose qua dspy.settings.trace và nhiều integration qua OpenTelemetry. Để có dashboard production, pattern thực dụng là đẩy trace sang ClickHouse qua một collector OTLP hoặc push trực tiếp qua HTTP.

Schema tối thiểu cho bảng trace:

CREATE TABLE IF NOT EXISTS dspy_traces (
    ts            DateTime64(3) DEFAULT now64(3),
    run_id        String,
    program       LowCardinality(String),
    module        LowCardinality(String),
    signature     LowCardinality(String),
    model         LowCardinality(String),
    input_tokens  UInt32,
    output_tokens UInt32,
    latency_ms    UInt32,
    metric_score  Float32,
    compile_id    LowCardinality(String),
    error         String
) ENGINE = MergeTree
PARTITION BY toYYYYMM(ts)
ORDER BY (program, module, ts);

Trường compile_id rất đắt giá: mỗi khi ta chạy optimizer và lưu Program compile thành file, sinh ra một id. Dashboard so sánh accuracy/latency giữa hai compile_id trên cùng traffic thực tế là công cụ để quyết định rollout hay rollback một bản optimize. Đây là "A/B test cho prompt" ở cấp độ hệ thống, không còn là chuyện copy-paste hai file prompt rồi đoán.

11. Workflow compile — từ notebook đến production

Một trong những hiểu lầm phổ biến là coi DSPy như thư viện runtime thuần. Thực ra nó có vòng đời hai pha rất giống học máy truyền thống:

Pha 1 — Viết program
Kỹ sư viết signature, module, compose thành program. Chạy không compile, xác thực pipeline hoạt động end-to-end. Đây giống giai đoạn code Python trong PyTorch.
Pha 2 — Thu thập dataset và metric
Chuẩn bị trainset 50-200 mẫu và viết metric. Thời gian tốn ở đây thường nhiều hơn cả viết program.
Pha 3 — Compile với optimizer
Chạy BootstrapFewShot hoặc MIPROv2. Với MIPROv2, compile có thể mất 10 phút đến vài giờ tùy kích thước trainset và budget. Kết quả là một program với state đã điền (demos, instructions).
Pha 4 — Save / version / deploy
Lưu compiled program ra JSON bằng program.save(path). File này là artifact deploy — giống như checkpoint model trong ML. Versioning nên đi kèm Git với commit hash của code và hash của trainset.
Pha 5 — Load trong runtime
Service production load artifact bằng program.load(path) lúc khởi động. Sau đó mỗi request chỉ là program(question=...) — không còn compile, không còn optimizer, không còn teacher model.
Pha 6 — Re-compile theo nhịp
Khi dataset hoặc metric thay đổi đáng kể, hoặc khi nhà cung cấp ra model mới, chạy lại pha 3. Đây là nhịp "retrain" của pipeline DSPy, thường là hàng tuần đến hàng tháng.

12. DSPy so với LangChain, LlamaIndex và BAML

Tiêu chíDSPyLangChain / LangGraphLlamaIndexBAML
Triết lýProgram + optimizer; prompt là output của compileChain/graph thủ công, prompt viết tayIndex-first cho RAG, prompt viết taySchema-first DSL, prompt do runtime sinh
Optimizer tự độngCó — BootstrapFewShot, MIPROv2, BootstrapFinetuneKhông — cần framework ngoài (LangSmith prompt lab)KhôngKhông, có test-as-you-go
Typed I/OCó — Python type + PydanticMột phần qua tool schemaMột phầnCó — mạnh nhất, có test type
Multi-agent composeProgram chứa program, compile end-to-endGraph state machine rõ ràngAgent module cơ bảnKhông — tập trung lớp I/O
Đổi modelĐổi dspy.LM, re-compile là xongĐổi client; prompt có thể vẫn hợp lệĐổi LLM objectĐổi config generator
Learning curveCao ban đầu (cần hiểu signature/optimizer), dễ dần về sauTrung bình; nhiều khái niệmThấp cho RAG cơ bảnThấp cho việc output có cấu trúc
Phù hợp nhất choPipeline cần tối ưu, đo được, đổi model thường xuyênWorkflow graph phức tạp, statefulỨng dụng RAG đơn giản đến trung bìnhLớp IO có schema cần type-safe mạnh

Ba framework này không loại trừ nhau. Một stack production 2026 tương đối phổ biến là: BAML cho lớp IO có cấu trúc ở boundary (API schemas), DSPy cho các agent phức tạp cần tối ưu được, và LangGraph khi cần state machine dài với human-in-the-loop. Chọn một không có nghĩa là bỏ tất cả những cái còn lại.

13. Các pattern production đáng dùng năm 2026

13.1. Ensemble nhiều bản compile

Thay vì deploy một Compiled Program, deploy hai hoặc ba bản tối ưu khác nhau (khác seed, khác teacher, khác optimizer). Router nhận request, gọi song song, vote majority hoặc chọn theo confidence. Chi phí gấp 2-3 lần, accuracy tăng 3-10 điểm trên benchmark khó. Dùng khi chất lượng quan trọng hơn chi phí.

13.2. Shadow deploy

Version mới của compiled program chạy song song với version cũ trong production, nhưng chỉ version cũ trả về client. Trace cả hai vào ClickHouse. So sánh metric sau 24-72 giờ. Nếu new thắng, cutover. Quy trình này phát hiện hồi quy mà eval offline bỏ sót — ví dụ cách user thật hỏi khác cách dataset đặt câu hỏi.

13.3. Ba tầng cache: exact, semantic, prefix

Prefix cache (do nhà cung cấp LLM hỗ trợ qua prompt caching API) giảm chi phí token; exact cache Redis giảm cả latency; semantic cache Redis VS giảm cả hai khi paraphrase nhiều. Ba tầng kết hợp hạ tỷ lệ gọi LM thật xuống 40-60% trong nhiều workload retrieval.

13.4. Theo dõi metric drift

Lưu điểm metric thực tế (khi có feedback) vào ClickHouse. Mỗi tuần tính mean rolling trên từng compile_id. Khi điểm giảm quá một ngưỡng — ví dụ 5% — trigger recompile với trainset mới. Đây là vòng lặp "mlops cho prompt" thực sự khả thi chỉ khi optimizer được tự động hóa, và DSPy cung cấp sẵn điều đó.

13.5. Compile theo budget

MIPROv2 nhận tham số max_bootstrapped_demos, max_labeled_demos, num_trials. Với pipeline production, bắt đầu với budget nhỏ (num_trials=10) để có baseline nhanh, rồi tăng dần khi cần. Đừng chạy full budget ngay; mỗi lần MIPROv2 tốn có thể cả trăm USD tiền API cho teacher model.

14. Anti-pattern thường gặp

  • Viết instruction trong docstring rồi đòi optimizer làm gì đó khác — docstring dài quá ngăn không gian tối ưu. Giữ docstring gọn, để optimizer điền phần còn lại.
  • Metric là một hàm LLM-as-judge không được hiệu chỉnh — judge không khớp với ground truth thì optimizer cũng đi sai hướng. Luôn đo correlation giữa judge và con người trên ít nhất 30 mẫu trước khi dùng judge làm metric tối ưu.
  • Compile trên một model, deploy trên model khác — few-shot demos được tuning cho teacher; chạy với student yếu hơn có thể mất nhiều hơn là được. Luôn compile trên đúng model sẽ chạy ở production, hoặc dùng BootstrapFinetune để đồng bộ student.
  • Không lưu compiled program vào version control — file compiled là artifact cần được treat như binary release. Lưu vào S3/artifact registry kèm commit hash của code và hash của trainset.
  • Dùng DSPy cho pipeline 1 bước không metric — nếu bài toán chỉ là "gọi LLM parse JSON", BAML hoặc dspy.Predict thuần là đủ; không cần optimizer. DSPy có lợi nhất khi có metric rõ và dataset nhỏ.

15. Checklist trước khi đưa DSPy vào production

  • Đã có ít nhất 50 mẫu trainset và 50 mẫu devset độc lập.
  • Đã viết metric và đo correlation với đánh giá tay trên 30-50 mẫu.
  • Đã compile bằng BootstrapFewShot làm baseline trước khi dùng MIPROv2.
  • Đã lưu compiled program ra file JSON và đưa vào artifact registry.
  • Đã cấu hình Redis cache (exact + semantic) và đo hit rate trong shadow.
  • Đã expose trace sang ClickHouse và có dashboard so sánh theo compile_id.
  • Đã chuẩn bị quy trình rollback — load compiled program phiên bản cũ từ file artifact khi metric sụt.
  • Đã tài liệu hóa signature và metric trong repo — hai thứ này là "API" thực sự của pipeline DSPy.
  • Đã test với ít nhất hai nhà cung cấp LLM khác nhau để xác nhận tính linh hoạt; đừng phụ thuộc ngầm vào một model.
  • Đã thiết lập budget cảnh báo cho compile: mỗi MIPROv2 run có thể tốn tiền — biết trước con số đáng giá.

16. Tổng kết

DSPy không phải thư viện cung cấp thêm vài helper cho prompt. Nó là một phát biểu kiến trúc: pipeline LLM nên được lập trình, không được "prompt-engineer". Khi nhìn dưới góc độ này, mọi mảnh trong hệ multi-agent bỗng trở nên có cấu trúc: signature là interface, module là implementation, metric là test, optimizer là compiler, compiled program là artifact, và ClickHouse trace là monitoring. Đây là vòng đời phát triển mà các ngành kỹ thuật khác đã có từ lâu — điều duy nhất mới là nó lần đầu áp dụng được cho hệ thống sinh ngôn ngữ tự nhiên.

Với một đội engineering đang cân nhắc giữa "viết prompt hay hơn" và "chuyển sang lập trình prompt", câu hỏi không còn là "DSPy có tốt hơn không" mà là "bao giờ ta bắt đầu". Càng sớm đưa pipeline vào vòng đời compile-deploy-measure-recompile, càng sớm thoát khỏi trò chơi vô hạn của việc tinh chỉnh chuỗi chữ trong prompts.py. Và với cặp đôi Redis 8 (làm tầng cache và retrieval) cùng ClickHouse (làm tầng observability), các pipeline DSPy production có thể đạt được chi phí, độ trễ và độ tin cậy mà prompt engineering thủ công không bao giờ với tới. Năm 2026, câu hỏi đáng hỏi không phải "prompt của bạn là gì" mà là "compiled program của bạn được optimize lần cuối vào bao giờ, trên metric nào".

Nguồn tham khảo