LLM 2 — Prompt engineering & structured output

13 thg 7, 2026 7 lượt xem
#ai
#claude
#prompt-engineering
#llm
#few-shot
#structured-output

LLM 2 — Prompt engineering & structured output

bài Tổng quan chúng ta đã nắm được LLM là gì và cách gọi Claude qua API. Bài này đi sâu vào prompt engineering — nghệ thuật (và kỹ thuật) viết chỉ dẫn để mô hình làm đúng việc bạn muốn — cùng với structured output, cách ép Claude trả về JSON hợp lệ theo schema thay vì tự parse văn bản tự do.

Prompt engineering không phải là "câu thần chú". Nó là quá trình thiết kế đầu vào một cách có kỷ luật: đặt vai trò và ngữ cảnh đúng chỗ, nói rõ nhiệm vụ và định dạng mong muốn, cho ví dụ khi cần, và lặp lại dựa trên đo lường. Anthropic nói thẳng: trước khi tối ưu prompt, bạn cần tiêu chí thành công rõ ràngcách kiểm thử thực nghiệm (eval) — nếu không, bạn chỉ đang đoán. Ta sẽ nối lại chủ đề eval ở bài Đánh giá.

Tất cả ví dụ dưới đây dùng Claude / Anthropic SDK (Python). Danh mục AI, không có SQL sandbox — các khối code chỉ để minh hoạ, không có nhãn "chạy được".


1. Sơ đồ tổng thể: prompt được lắp ráp như thế nào

Một request tới Claude không chỉ là "câu hỏi". Nó là sự lắp ráp có trật tự của nhiều thành phần, và mô hình đọc chúng theo thứ tự đó.

Bốn ý cần nhớ từ sơ đồ này:

  • System prompt đặt "khung": bạn là ai, làm việc trong bối cảnh nào, ràng buộc gì.
  • Context (dữ liệu, tài liệu, ví dụ) nên được bọc trong thẻ XML để Claude phân biệt rõ đâu là chỉ dẫn, đâu là dữ liệu.
  • Task là câu hỏi/mệnh lệnh cụ thể, thường nằm ở lượt user.
  • Đầu ra có thể là văn bản tự do, hoặc — tốt hơn cho pipeline — là JSON có ràng buộc schema.

2. System prompt vs user prompt

Claude API là hội thoại nhiều lượt gồm các message với roleuser hoặc assistant, cộng thêm một trường system riêng ở cấp cao nhất.

Nguyên tắc phân vai:

Đặt vào systemĐặt vào user
Vai trò / persona ("Bạn là chuyên viên phân tích tín dụng")Nhiệm vụ cụ thể của lượt này
Ngữ cảnh nền, quy tắc chung, giọng điệuDữ liệu đầu vào cho nhiệm vụ
Ràng buộc bất biến (định dạng, ngôn ngữ, giới hạn)Câu hỏi/yêu cầu thực tế
import anthropic

client = anthropic.Anthropic()

response = client.messages.create(   # (minh hoạ)
    model="claude-opus-4-8",
    max_tokens=1024,
    system=(
        "Bạn là chuyên viên phân tích tín dụng của một ngân hàng. "
        "Trả lời bằng tiếng Việt, ngắn gọn, khách quan. "
        "Nếu dữ liệu không đủ để kết luận, hãy nói rõ là không đủ dữ liệu."
    ),
    messages=[
        {"role": "user", "content": "Khách hàng có thu nhập 20 triệu/tháng, "
                                    "dư nợ hiện tại 350 triệu. Đánh giá khả năng trả nợ."},
    ],
)

Vì sao tách? Vì system là phần ổn định giữa nhiều request — nó nên chứa những gì không đổi. Điều này cũng quan trọng cho prompt caching: những gì đứng trước và ít thay đổi (system prompt, danh sách tool) được cache lại, giảm chi phí đáng kể. Đừng nhét thứ thay đổi mỗi request (thời gian hiện tại, ID người dùng) vào đầu system prompt — nó sẽ vô hiệu hoá cache.

Đặt persona vào system giúp mô hình giữ vai trò xuyên suốt. Persona ở system bền hơn là nhắc "hãy đóng vai..." trong từng lượt user.


3. Sáu nguyên tắc viết prompt hiệu quả

Anthropic tóm gọn: hãy làm prompt của bạn rõ ràng, có ví dụ, cấu trúc tốt, và nói rõ định dạng. Cụ thể:

3.1. Rõ ràng và cụ thể

Hãy nghĩ Claude như một cộng sự thông minh nhưng vừa mới vào việc, không biết ngữ cảnh nội bộ của bạn. Nói rõ mục tiêu, đối tượng đọc, và điều bạn KHÔNG muốn.

# Kém (minh hoạ)
Tóm tắt tài liệu này.

# Tốt (minh hoạ)
Tóm tắt tài liệu dưới đây thành đúng 3 gạch đầu dòng cho cấp quản lý.
Mỗi dòng tối đa 20 từ, tập trung vào rủi ro và số liệu. Không thêm mở đầu.

3.2. Chia bước

Với nhiệm vụ phức tạp, liệt kê các bước theo thứ tự (dùng số hoặc gạch đầu dòng). Mô hình bám theo cấu trúc tốt hơn là một đoạn văn dài mập mờ.

3.3. Cho ví dụ

Một ví dụ input→output cụ thể thường "dạy" mô hình nhanh hơn nhiều so với mô tả trừu tượng (xem mục Few-shot bên dưới).

3.4. Nói rõ định dạng mong muốn

"Trả về JSON", "chỉ trả về nhãn, không giải thích", "dùng bảng Markdown" — càng cụ thể càng ít phải hậu xử lý. Khi cần chắc chắn 100%, dùng structured output (mục 7).

3.5. Cho model "lối thoát"

Đây là mẹo giảm hallucination rất mạnh. Nếu bạn cho phép mô hình nói "tôi không biết" hoặc "không đủ dữ liệu", nó sẽ ít bịa hơn khi thật sự không có căn cứ.

Chỉ trả lời dựa trên tài liệu được cung cấp. Nếu tài liệu không chứa
câu trả lời, hãy trả lời đúng chữ: "Không tìm thấy trong tài liệu."

3.6. Đặt câu hỏi/nhiệm vụ ở cuối

Khi có nhiều ngữ cảnh dài (tài liệu, ví dụ), hãy đặt dữ liệu trước, câu hỏi sau. Với văn bản dài, Claude thường bám vào chỉ dẫn ở gần cuối tốt hơn.


4. Few-shot: dạy bằng ví dụ

Few-shot (hay multishot) nghĩa là đưa vài cặp input → output mẫu ngay trong prompt, rồi mới đưa input thật. Nó đặc biệt hữu ích khi:

  • Định dạng đầu ra có quy ước riêng khó mô tả bằng lời.
  • Bạn muốn giọng điệu/phong cách nhất quán.
  • Nhiệm vụ phân loại có ranh giới tinh tế giữa các nhãn.
system = "Bạn phân loại phản hồi khách hàng thành: TÍCH_CỰC, TIÊU_CỰC, TRUNG_TÍNH."

few_shot = """<examples>
<example>
<input>Nhân viên tư vấn rất nhiệt tình, tôi rất hài lòng.</input>
<output>TÍCH_CỰC</output>
</example>
<example>
<input>Chờ đợi quá lâu, app liên tục lỗi.</input>
<output>TIÊU_CỰC</output>
</example>
<example>
<input>Ứng dụng dùng được, không có gì đặc biệt.</input>
<output>TRUNG_TÍNH</output>
</example>
</examples>

Phân loại phản hồi sau. Chỉ trả về nhãn, không giải thích.
<input>Giao diện ổn nhưng phí duy trì hơi cao.</input>"""

response = client.messages.create(   # (minh hoạ)
    model="claude-opus-4-8",
    max_tokens=16,
    system=system,
    messages=[{"role": "user", "content": few_shot}],
)

Lưu ý: ví dụ few-shot nằm ở giữa hội thoại (trong nội dung user) vẫn hợp lệ. Điều bị hạn chế là prefill lượt assistant cuối cùng — sẽ nói ở mục 6.

Bao nhiêu ví dụ là đủ? Bắt đầu với 2–5 ví dụ đa dạng, bao gồm cả các trường hợp biên. Ví dụ mâu thuẫn nhau còn hại hơn không có ví dụ.


5. Chain-of-thought (CoT) và extended thinking

Với bài toán cần suy luận (toán, logic, phân tích nhiều bước), việc buộc mô hình suy nghĩ từng bước trước khi kết luận cải thiện độ chính xác rõ rệt.

Cách thủ công (prompt CoT)

Hãy giải bài toán này. Trước tiên trình bày lập luận từng bước
trong thẻ <suy_luan></suy_luan>, sau đó đưa đáp án cuối cùng
trong thẻ <dap_an></dap_an>.

Tách rõ phần suy luận và phần đáp án giúp bạn dễ dàng lấy đúng kết quả cuối cùng.

Cách của Claude: adaptive thinking

Các mô hình Claude đời mới (Opus 4.6+, và Opus 4.8 hiện tại) hỗ trợ extended thinking ở chế độ adaptive — mô hình tự quyết định khi nào và suy nghĩ bao nhiêu, thay vì bạn phải chèn chỉ dẫn CoT bằng tay.

response = client.messages.create(   # (minh hoạ)
    model="claude-opus-4-8",
    max_tokens=16000,
    thinking={"type": "adaptive", "display": "summarized"},
    output_config={"effort": "high"},   # low | medium | high | max
    messages=[{"role": "user", "content": "Giải: một khoản vay 500 triệu, "
                                           "lãi 10%/năm, trả đều 5 năm. Tiền lãi tổng?"}],
)

for block in response.content:
    if block.type == "thinking":
        print("Suy luận:", block.thinking)
    elif block.type == "text":
        print("Đáp án:", block.text)

Vài điểm cần lưu ý (dựa trên tài liệu Anthropic):

  • Trên Opus 4.8/4.7 và Fable 5, chỉ dùng thinking={"type": "adaptive"}. Kiểu cũ {"type": "enabled", "budget_tokens": N} bị loại bỏ và trả về lỗi 400.
  • Mặc định phần suy luận bị ẩn (display mặc định là "omitted"). Đặt display: "summarized" nếu bạn muốn hiển thị tóm tắt suy luận cho người dùng.
  • Tham số effort (low/medium/high/max) điều chỉnh độ sâu suy nghĩ và chi phí token — với bài khó, high thường là điểm cân bằng tốt.

Khi nào dùng CoT/thinking? Khi lỗi của bạn là lỗi suy luận (tính sai, bỏ bước). Với nhiệm vụ đơn giản (phân loại, trích xuất), CoT chỉ làm tốn token mà không thêm giá trị.


6. Thẻ XML — "đặc sản" hợp gu Claude

Claude được huấn luyện để bám theo thẻ XML rất tốt. Dùng thẻ để phân tách rõ các phần của prompt giúp mô hình không nhầm lẫn giữa chỉ dẫn, dữ liệu, và ví dụ.

Các thẻ thường dùng: <instructions>, <document>, <example>, <context>, <data>. Tên thẻ không cố định — quan trọng là bạn dùng nhất quán và tham chiếu đúng tên trong chỉ dẫn.

prompt = """<instructions>
Trích xuất thông tin khách hàng từ email dưới đây. Chỉ dùng thông tin
có trong email, không suy diễn. Nếu thiếu trường nào, để trống.
</instructions>

<email>
Chào anh/chị, tôi là Trần Văn Minh, cần mở tài khoản gói Doanh Nghiệp.
Liên hệ: [email protected]. Rất mong được tư vấn sớm.
</email>

Trả về theo định dạng:
<ket_qua>
<ho_ten>...</ho_ten>
<email>...</email>
<goi>...</goi>
</ket_qua>"""

response = client.messages.create(   # (minh hoạ)
    model="claude-opus-4-8",
    max_tokens=512,
    messages=[{"role": "user", "content": prompt}],
)

Lợi ích của thẻ XML:

  • Chống prompt injection nhẹ: bọc dữ liệu người dùng trong <email>...</email> giúp Claude hiểu đó là dữ liệu để xử lý, không phải chỉ dẫn để tuân theo. (Đây chỉ là lớp phòng thủ mềm; phòng thủ nghiêm túc ta bàn ở bài production.)
  • Dễ lấy kết quả: khi model trả về <ho_ten>...</ho_ten>, bạn có thể tách chính xác bằng regex đơn giản. Dù vậy, nếu cần JSON chuẩn, structured output (mục 7) tốt hơn.

7. Prefill — và giới hạn trên model mới

Prefill là kỹ thuật viết sẵn phần đầu câu trả lời của assistant để định hướng định dạng. Ví dụ điển hình: prefill { để ép mô hình bắt đầu bằng JSON, hoặc prefill một tag để bỏ phần mở đầu thừa.

Trên các mô hình cũ, bạn làm như sau:

# Kiểu prefill cũ — KHÔNG còn hoạt động trên Claude đời mới (minh hoạ)
messages = [
    {"role": "user", "content": "Trích tên và email dưới dạng JSON."},
    {"role": "assistant", "content": "{"},   # prefill mở đầu JSON
]

Lưu ý quan trọng (Claude 4.6+): Prefill lượt assistant cuối cùng bị loại bỏ trên Fable 5, Opus 4.6, Opus 4.7, Opus 4.8 và Sonnet 4.6 — request kiểu này trả về lỗi 400. (Thêm message assistant ở giữa hội thoại để làm ví dụ few-shot thì vẫn được; chỉ prefill ở lượt cuối là bị chặn.)

Vậy thay thế bằng gì? Anthropic khuyến nghị:

Trước đây prefill để...Thay bằng
Ép ra JSON đúng schemaStructured output (output_config.format) — mục 7
Ép ra một nhãn phân loạiTool có trường enum, hoặc structured output
Bỏ câu mở đầu ("Đây là...")Chỉ dẫn trong system: "Trả lời trực tiếp, không mở đầu."

Nói cách khác: với model mới, đừng dùng prefill để ép định dạng nữa — hãy dùng structured output.


8. Structured output: ép JSON hợp lệ theo schema

Đây là bước tiến quan trọng nhất cho pipeline production. Thay vì "xin" mô hình trả về JSON rồi cầu nguyện nó hợp lệ (và tự parse tay, tự bắt lỗi), bạn ràng buộc đầu ra bằng schema — Claude dùng constrained decoding để đảm bảo JSON đúng cấu trúc.

Cách 1 — output_config.format với JSON Schema

response = client.messages.create(   # (minh hoạ)
    model="claude-opus-4-8",
    max_tokens=1024,
    messages=[{
        "role": "user",
        "content": "Trích xuất: Trần Văn Minh ([email protected]) "
                   "muốn gói Doanh Nghiệp, quan tâm API và SDK, cần demo.",
    }],
    output_config={
        "format": {
            "type": "json_schema",
            "schema": {
                "type": "object",
                "properties": {
                    "ho_ten": {"type": "string"},
                    "email":  {"type": "string", "format": "email"},
                    "goi":    {"type": "string"},
                    "quan_tam": {"type": "array", "items": {"type": "string"}},
                    "can_demo": {"type": "boolean"},
                },
                "required": ["ho_ten", "email", "goi", "can_demo"],
                "additionalProperties": False,
            },
        }
    },
)

import json
text = next(b.text for b in response.content if b.type == "text")
data = json.loads(text)   # đảm bảo là JSON hợp lệ theo schema

Cách 2 — messages.parse() với Pydantic (khuyến nghị)

Sạch hơn: định nghĩa schema bằng Pydantic, SDK tự sinh JSON Schema và tự validate, trả về object đã kiểu hoá.

from pydantic import BaseModel
from typing import List

class ThongTinKhach(BaseModel):     # (minh hoạ)
    ho_ten: str
    email: str
    goi: str
    quan_tam: List[str]
    can_demo: bool

response = client.messages.parse(
    model="claude-opus-4-8",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Trích xuất: Trần Văn Minh "
                                           "([email protected]) muốn gói Doanh Nghiệp..."}],
    output_format=ThongTinKhach,
)

khach = response.parsed_output   # đối tượng ThongTinKhach đã validate
print(khach.ho_ten, khach.email, khach.can_demo)

messages.parse() là cách được Anthropic khuyến nghị: bạn không phải tự json.loads rồi tự bắt lỗi định dạng.

Cách 3 — Strict tool use

Nếu bạn đang dùng tool (function calling), đặt strict: True trên định nghĩa tool để đảm bảo input của tool đúng schema. Chi tiết tool use ở bài Tool use & agents.

Những điều cần biết về structured output

  • Model hỗ trợ: Claude Fable 5, Opus 4.8, Sonnet 4.6, Haiku 4.5 (và các đời 4.5/4.6/4.7).
  • JSON Schema được hỗ trợ một phần: có type, enum, const, anyOf, $ref, required, additionalProperties: false, và các format chuỗi (date, email, uri, uuid...). Không hỗ trợ: schema đệ quy, ràng buộc số (minimum/maximum), ràng buộc độ dài chuỗi (minLength/maxLength). Mọi object phải có additionalProperties: false.
  • Không tương thích với citations và prefill: bật citations cùng output_config.format sẽ trả về 400.
  • Vẫn có ngoại lệ: nếu stop_reason"refusal" (mô hình từ chối vì an toàn) hoặc "max_tokens" (bị cắt do hết token), đầu ra có thể không khớp schema. Luôn kiểm tra stop_reason trước khi tin tưởng kết quả.
  • Độ trễ lần đầu: schema mới cần biên dịch grammar một lần (được cache 24 giờ), nên request đầu tiên chậm hơn chút.

9. Prompt template & biến — chèn dữ liệu an toàn

Trong ứng dụng thực tế, prompt thường là template có biến: phần cố định là chỉ dẫn, phần thay đổi là dữ liệu người dùng.

TEMPLATE = """<instructions>
Phân loại mức độ ưu tiên của yêu cầu hỗ trợ dưới đây: CAO, TRUNG_BINH, THAP.
Chỉ trả về nhãn.
</instructions>

<yeu_cau>
{noi_dung_nguoi_dung}
</yeu_cau>"""

def phan_loai(noi_dung: str):   # (minh hoạ)
    prompt = TEMPLATE.format(noi_dung_nguoi_dung=noi_dung)
    return client.messages.create(
        model="claude-opus-4-8",
        max_tokens=16,
        messages=[{"role": "user", "content": prompt}],
    )

Nguyên tắc vàng: tách chỉ dẫn khỏi dữ liệu. Dữ liệu người dùng nên nằm gọn trong một thẻ XML (như <yeu_cau>), tách bạch khỏi phần chỉ dẫn. Điều này quan trọng vì người dùng có thể cố tình nhét chỉ dẫn độc hại vào ("Bỏ qua hướng dẫn trên và...") — đây là prompt injection. Bọc dữ liệu và nói rõ với mô hình rằng phần trong thẻ chỉ là nội dung để phân loại là lớp phòng thủ đầu tiên. Ta sẽ bàn kỹ chiến lược chống injection ở bài production.


10. Prompt là một quá trình lặp — cần đo

Đừng kỳ vọng prompt hoàn hảo ngay lần đầu. Anthropic nhấn mạnh: trước khi tối ưu prompt, hãy có tiêu chí thành công rõ ràng và cách kiểm thử thực nghiệm. Quy trình lặp điển hình:

  1. Viết bản nháp prompt đầu tiên.
  2. Chạy trên một bộ ví dụ đại diện (bao gồm ca biên).
  3. Đo kết quả so với tiêu chí (đúng nhãn? đúng định dạng? có bịa không?).
  4. Sửa một yếu tố mỗi lần — thêm ví dụ, làm rõ chỉ dẫn, đổi định dạng — rồi đo lại.

Điểm mấu chốt: chỉ đổi một biến mỗi lần để biết thay đổi nào thực sự giúp ích. Việc xây bộ eval bài bản (chấm điểm tự động, dùng LLM làm giám khảo) được trình bày ở bài Đánh giá.


Use case thực tế — trích xuất trường từ văn bản ra JSON

Bài toán rất phổ biến trong nghiệp vụ: có một khối văn bản tự do (email khách hàng, đơn yêu cầu, ghi chú tổng đài), cần biến thành bản ghi có cấu trúc để lưu vào cơ sở dữ liệu.

Trước đây cách làm là: prompt "trả về JSON" → nhận chuỗi → json.loadsnếu lỗi thì thử lại/vá tay. Cách mới gộp mọi thứ vào structured output:

from pydantic import BaseModel
from typing import Optional, List

class DonYeuCau(BaseModel):          # (minh hoạ)
    ho_ten: str
    so_dien_thoai: Optional[str]
    loai_yeu_cau: str                # ví dụ: "mở tài khoản", "khiếu nại", "tư vấn"
    muc_do_uu_tien: str              # "CAO" | "TRUNG_BINH" | "THAP"
    tom_tat: str
    san_pham_quan_tam: List[str]

EMAIL = """Kính gửi bộ phận CSKH, tôi là Lê Thị Hoa, SĐT 0901234567.
Tôi bị trừ phí duy trì thẻ không rõ lý do trong 2 tháng liên tiếp và cần
được giải quyết gấp. Ngoài ra tôi cũng muốn tìm hiểu gói tiết kiệm online."""

response = client.messages.parse(
    model="claude-opus-4-8",
    max_tokens=1024,
    system=(
        "Bạn trích xuất thông tin từ email CSKH thành bản ghi có cấu trúc. "
        "Chỉ dùng thông tin có trong email; trường nào không có thì để trống/null. "
        "muc_do_uu_tien đặt CAO nếu khách dùng từ như 'gấp', 'khẩn'."
    ),
    messages=[{"role": "user", "content": f"<email>{EMAIL}</email>"}],
    output_format=DonYeuCau,
)

if response.stop_reason == "refusal":
    print("Mô hình từ chối xử lý.")
else:
    don = response.parsed_output
    print(don.ho_ten)            # "Lê Thị Hoa"
    print(don.muc_do_uu_tien)    # "CAO"  (do có từ "gấp")
    print(don.san_pham_quan_tam) # ["gói tiết kiệm online"]

Kết quả: một object Python đã kiểu hoá, sẵn sàng ghi DB — không có bước parse tay, không lo JSON hỏng. Đây chính là hình mẫu "prompt tốt + structured output" cho pipeline trích xuất. Với tài liệu lớn hơn cần tìm ngữ cảnh trước khi trích, ta ghép thêm RAG.


Ghi nhớ

  • System vs user: vai trò / ngữ cảnh / ràng buộc bất biến vào system; nhiệm vụ và dữ liệu của lượt hiện tại vào user. Giữ system ổn định để tận dụng prompt caching.
  • Sáu nguyên tắc: rõ ràng & cụ thể, chia bước, cho ví dụ, nói rõ định dạng, cho "lối thoát" (giảm hallucination), đặt câu hỏi ở cuối khi ngữ cảnh dài.
  • Few-shot: 2–5 ví dụ input→output đa dạng dạy mô hình nhanh hơn mô tả suông; ví dụ mâu thuẫn còn hại hơn không có.
  • Chain-of-thought: dùng cho bài toán suy luận. Trên Claude đời mới, ưu tiên adaptive thinking (thinking={"type":"adaptive"}) + effort, thay vì budget_tokens (đã bị loại bỏ, trả 400).
  • Thẻ XML: bọc <document>, <example>, <instructions> để Claude phân tách rõ chỉ dẫn khỏi dữ liệu; Claude bám thẻ rất tốt.
  • Prefill: định hướng đầu câu trả lời — nhưng prefill lượt assistant cuối bị loại bỏ trên Fable 5 / Opus 4.6+ / Sonnet 4.6 (lỗi 400). Thay bằng structured output hoặc chỉ dẫn system.
  • Structured output: dùng output_config.format (JSON Schema) hoặc messages.parse() (Pydantic) để ràng buộc JSON hợp lệ — ưu tiên cách này thay vì parse tay. Nhớ kiểm tra stop_reason ("refusal"/"max_tokens") và biết giới hạn của JSON Schema (không đệ quy, không minimum/maximum, additionalProperties: false).
  • Template & biến: tách chỉ dẫn khỏi dữ liệu người dùng, bọc dữ liệu trong thẻ XML — lớp phòng thủ đầu tiên chống prompt injection.
  • Lặp & đo: prompt là quá trình lặp; đổi một biến mỗi lần và đo bằng eval.

Đọc tiếp trong series:

Bài viết liên quan

Phân biệt AI/ML/DL, các kiểu học máy, quy trình ML end-to-end và thuật ngữ nền tảng.

13 thg 7, 2026 15

Chất lượng dữ liệu, xử lý thiếu/ngoại lai, mã hoá, chuẩn hoá, tạo đặc trưng và tránh data leakage.

13 thg 7, 2026 13

Mô hình ngôn ngữ lớn hoạt động ra sao, token, context window, tham số sinh, và kỹ thuật viết prompt.

13 thg 7, 2026 13

Retrieval-Augmented Generation, vector database, tool calling, agent và MCP để xây ứng dụng LLM thực tế.

13 thg 7, 2026 11