LLM 1 — Tổng quan xây ứng dụng với LLM (Claude)

13 thg 7, 2026 7 lượt xem
#ai
#claude
#llm
#genai
#anthropic

LLM 1 — Tổng quan xây ứng dụng với LLM (Claude)

Đây là bài mở đầu của series LLM ứng dụng & RAG chuyên sâu. Mục tiêu không phải là dạy bạn "gõ prompt cho hay", mà là trang bị đủ nền tảng kỹ thuật để xây một ứng dụng LLM thật — hiểu model chạy thế nào, gọi nó qua code ra sao, chọn đúng model, và quan trọng nhất: biết khi nào nên dùng LLM và khi nào tuyệt đối không nên.

Toàn bộ series dùng Claude của Anthropic làm ví dụ (qua thư viện anthropic cho Python). Lý do đơn giản: API sạch, có adaptive thinking, tool use, structured output, vision đầy đủ, và context window lớn (tới 1M token). Nhưng những khái niệm nền tảng (token, context, sampling, RAG, eval) thì đúng cho mọi LLM.

Đây là danh mục AI. Các đoạn code Python trong bài là minh hoạ để bạn đọc hiểu hình dạng API, không phải sandbox SQL. Bạn cần một API key của Anthropic để chạy thật.


1. LLM là gì — mô hình dự đoán token tiếp theo

LLM (Large Language Model) là một mạng nơ-ron rất lớn dựa trên kiến trúc transformer. Bản chất công việc của nó cực kỳ đơn giản để phát biểu, nhưng cực kỳ mạnh khi mở rộng quy mô:

Cho một chuỗi văn bản đầu vào, dự đoán token tiếp theo có xác suất cao nhất. Lặp lại việc đó nhiều lần để sinh ra cả câu trả lời.

Đó gọi là next-token prediction (dự đoán token kế tiếp). Khi bạn hỏi Claude "Thủ đô của Pháp là gì?", model không "tra cứu" một cơ sở dữ liệu — nó lần lượt tính xác suất cho token kế tiếp: sau "Thủ đô của Pháp là" thì token "Paris" có xác suất rất cao, nên nó chọn. Rồi lặp lại cho tới khi kết thúc.

Huấn luyện diễn ra thế nào (rất gọn)

Một model như Claude trải qua vài giai đoạn:

  1. Pre-training (tiền huấn luyện): đọc một khối lượng văn bản khổng lồ (sách, web, code…) và học dự đoán token tiếp theo. Đây là nơi model "học" ngữ pháp, sự kiện, cách lập luận, cách viết code.
  2. Post-training / alignment: tinh chỉnh để model làm theo hướng dẫn, hữu ích, an toàn, và trả lời đúng định dạng hội thoại (vai user / assistant). Đây là lý do model biết "trả lời câu hỏi" chứ không chỉ "tiếp tục viết văn bản".

Điều quan trọng cho người xây app: model đóng băng kiến thức tại thời điểm huấn luyện (training cutoff). Nó không tự biết dữ liệu công ty bạn, không biết tin tức hôm nay, không truy cập file của bạn — trừ khi bạn đưa thông tin đó vào input (đây chính là nền tảng của RAG, sẽ nói ở bài sau).

Transformer và "attention" (mức khái niệm)

Transformer nổi bật nhờ cơ chế attention: khi xử lý mỗi token, model "chú ý" tới các token khác trong ngữ cảnh để hiểu quan hệ giữa chúng. Nhờ vậy nó nắm được ngữ cảnh xa (ví dụ đại từ "nó" ở cuối đoạn trỏ về danh từ ở đầu đoạn). Bạn không cần hiểu toán bên trong để xây app, nhưng cần nhớ một hệ quả thực tế: mọi thứ model biết trong một lượt gọi đều phải nằm trong ngữ cảnh (context) mà bạn gửi lên.


2. Token — không phải là từ

Đây là khái niệm dễ hiểu sai nhất với người mới. Model không làm việc trên "từ" hay "ký tự", mà trên token — những mảnh văn bản do một bộ tách token (tokenizer) sinh ra.

  • Một token thường tương ứng khoảng 3–4 ký tự tiếng Anh, hoặc một từ ngắn.
  • Một từ dài, một từ tiếng Việt có dấu, hay code có thể bị tách thành nhiều token.
  • Khoảng trắng, dấu câu, xuống dòng cũng tốn token.

Ví dụ trực giác (không chính xác tuyệt đối):

"Hello, world!"        →  khoảng 4 token
"unbelievable"         →  có th3-4 token (un / believ / able ...)
"Xin chào các bạn"     →  tiếng Việt thường tốn nhiều token hơn tiếng Anh cùng nghĩa

Tại sao token quan trọng? Vì mọi thứ trong thế giới LLM đều tính theo token:

  • Giới hạn ngữ cảnh tính bằng token.
  • Chi phí (giá tiền) tính theo token input và token output.
  • Tốc độ phụ thuộc số token phải sinh ra.

Đừng dùng công cụ đếm token của model khác (ví dụ tiktoken của OpenAI) để ước lượng cho Claude — nó đếm sai. Anthropic SDK có endpoint đếm token chính xác cho từng model Claude:

# (minh hoạ) đếm token chính xác cho một model Claude cụ thể
import anthropic

client = anthropic.Anthropic()

resp = client.messages.count_tokens(
    model="claude-opus-4-8",
    messages=[{"role": "user", "content": open("tai_lieu.md").read()}],
)
print(resp.input_tokens)  # số token thực của input

3. Context window — giới hạn "trí nhớ ngắn hạn"

Context window là tổng số token mà model có thể "nhìn thấy" trong một lần gọi — bao gồm cả input lẫn output. Đây là "bàn làm việc" của model: mọi thứ bạn muốn model xử lý (system prompt + lịch sử hội thoại + tài liệu + câu hỏi) cộng với phần trả lời nó sinh ra, tất cả phải nằm gọn trong cửa sổ này.

Với các model Claude hiện tại, context window rất lớn (nhiều model lên tới 1 triệu token), nhưng vẫn là hữu hạn. Hệ quả thực tế:

  • Đừng "nhồi" cả cơ sở dữ liệu vào prompt. Ngay cả khi vừa đủ, chi phí sẽ cao và có thể chậm. Thay vào đó, chỉ đưa phần liên quan (đây là lý do RAG tồn tại — bài /article/llm-04-rag).
  • Hội thoại dài sẽ đầy dần. Mỗi lượt bạn phải gửi lại toàn bộ lịch sử (API là stateless — không tự nhớ). Khi gần chạm giới hạn, cần tóm tắt / nén (compaction) hoặc cắt bớt.
  • max_tokens là mức trần cho phần OUTPUT, tách biệt với context window. Nếu đặt quá thấp, câu trả lời bị cắt giữa chừng.

Sơ đồ: một lời gọi LLM đi qua context window

Điểm mấu chốt của sơ đồ: model chỉ biết những gì nằm trong context window bạn gửi lên. Không có gì bên ngoài đó tồn tại với nó trong lượt gọi này.


4. Temperature & sampling — vì sao LLM không tất định

Ở bước sinh mỗi token, model không chỉ ra một token duy nhất — nó cho ra phân phối xác suất trên toàn bộ từ vựng. Việc chọn token nào từ phân phối đó gọi là sampling.

  • Temperature thấp → chọn "an toàn" hơn, thiên về token xác suất cao nhất → output ổn định, ít sáng tạo, phù hợp phân loại / trích xuất.
  • Temperature cao → phân phối "phẳng" hơn, model dám chọn token ít khả năng hơn → output đa dạng, sáng tạo hơn nhưng dễ lệch.

Hệ quả cực kỳ quan trọng cho người xây app: LLM không tất định (non-deterministic). Cùng một input có thể cho ra output khác nhau giữa các lần gọi. Đây không phải bug — đó là bản chất của sampling. Nó ảnh hưởng tới cách bạn testđánh giá ứng dụng (sẽ nói ở bài eval).

Lưu ý với dòng Claude mới nhất: các model như claude-opus-4-8, claude-opus-4-7claude-fable-5 không nhận các tham số sampling thủ công (temperature, top_p, top_k) nữa — gửi lên sẽ báo lỗi 400. Cách "điều chỉnh hành vi" trên các model này là viết prompt rõ ràng và dùng tham số effort (độ sâu suy nghĩ). Nếu bạn thấy tài liệu cũ hướng dẫn temperature=0 để "ổn định", hãy hiểu rằng nó chưa bao giờ đảm bảo output y hệt, và trên model mới thì không dùng được nữa.


5. Gọi Claude qua Anthropic SDK (Python)

Đây là "Hello World" của việc xây app LLM. Cài thư viện chính thức:

pip install anthropic

Đặt API key vào biến môi trường ANTHROPIC_API_KEY (đừng hardcode key vào code). Lời gọi tối giản:

# (minh hoạ) một lời gọi Claude cơ bản nhất
import anthropic

client = anthropic.Anthropic()  # tự đọc ANTHROPIC_API_KEY từ môi trường

response = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=1024,
    messages=[
        {"role": "user", "content": "Thủ đô của Pháp là gì?"}
    ],
)

# response.content là một DANH SÁCH các "content block".
# Luôn kiểm tra block.type trước khi đọc .text
for block in response.content:
    if block.type == "text":
        print(block.text)

Vài điều cần nắm về cấu trúc lời gọi:

  • model: chuỗi ID của model (mục 6).
  • max_tokens: trần token cho phần trả lời. Bắt buộc phải có.
  • messages: danh sách hội thoại theo lượt, mỗi phần tử có role"user" hoặc "assistant". Phần tử đầu tiên phải là user. API stateless — mỗi lần gọi bạn gửi lại toàn bộ lịch sử.
  • response.content là một danh sách block, không phải một chuỗi. Đọc .text bừa mà không kiểm tra .type sẽ vỡ khi có block khác (thinking, tool_use…).

System prompt — đặt vai trò và quy tắc

System prompt là chỗ bạn định nghĩa "model là ai, tuân theo quy tắc gì". Nó là tham số riêng, không phải một message:

# (minh hoạ) system prompt định hình hành vi
response = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=1024,
    system="Bạn là trợ lý kỹ thuật. Trả lời ngắn gọn, luôn kèm ví dụ code Python.",
    messages=[{"role": "user", "content": "Làm sao đọc file JSON?"}],
)

Hội thoại nhiều lượt

Vì API không tự nhớ, bạn tự quản lý lịch sử bằng cách nối các message:

# (minh hoạ) hội thoại nhiều lượt — tự gửi lại toàn bộ lịch sử
messages = [
    {"role": "user", "content": "Tên tôi là An."},
    {"role": "assistant", "content": "Chào An! Rất vui được gặp bạn."},
    {"role": "user", "content": "Tên tôi là gì?"},  # Claude nhớ "An" vì ta gửi lại
]

response = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=1024,
    messages=messages,
)

Streaming — cho output dài

Với câu trả lời dài (hoặc max_tokens lớn), lời gọi không-streaming dễ chạm timeout HTTP của SDK. Streaming trả về từng phần ngay khi model sinh ra, vừa tránh timeout vừa cho trải nghiệm "gõ dần" tự nhiên. Cách gọn nhất: dùng helper .stream() rồi lấy kết quả đầy đủ bằng .get_final_message():

# (minh hoạ) streaming cho output dài, dùng .get_final_message()
with client.messages.stream(
    model="claude-opus-4-8",
    max_tokens=8000,
    messages=[{"role": "user", "content": "Viết một bài phân tích dài về..."}],
) as stream:
    # in dần từng đoạn text khi model sinh ra
    for text in stream.text_stream:
        print(text, end="", flush=True)

    # sau khi stream xong, lấy toàn bộ message (usage, stop_reason, ...)
    final = stream.get_final_message()
    print("\n\nSố token output:", final.usage.output_tokens)

Quy tắc thực tế: mặc định streaming cho bất kỳ request nào có thể sinh output dài hoặc max_tokens lớn. .get_final_message() cho bạn message hoàn chỉnh y như gọi thường, nhưng có bảo vệ chống timeout.


6. Chọn model Claude — dùng đúng ID

Anthropic có nhiều model với cân bằng khác nhau giữa trí tuệ ↔ tốc độ ↔ chi phí. Dùng chính xác các ID sau (đừng bịa tên hay thêm hậu tố ngày tháng):

ModelID (dùng chuỗi này)Khi nào dùng
Claude Opus 4.8claude-opus-4-8Mặc định. Mạnh nhất tầm Opus — dùng cho hầu hết tác vụ, agent, coding, lập luận dài.
Claude Sonnet 4.6claude-sonnet-4-6Cân bằng tốc độ / trí tuệ. Workload sản xuất khối lượng lớn, tóm tắt, phân loại.
Claude Haiku 4.5claude-haiku-4-5Nhanh & rẻ nhất. Tác vụ đơn giản, nhạy tốc độ (phân loại nhanh, gợi ý…).
Claude Fable 5claude-fable-5Model cao cấp nhất khi cần — reasoning khó nhất, agentic dài hơi. Giá cao hơn Opus.

Nguyên tắc: mặc định claude-opus-4-8. Chỉ hạ xuống Sonnet/Haiku khi bạn có lý do rõ ràng (chi phí, tốc độ, tác vụ đơn giản) — đó là quyết định của bạn, không phải mặc định hạ cấp để tiết kiệm. Chỉ dùng claude-fable-5 khi thực sự cần model mạnh nhất.

# (minh hoạ) chọn model theo tác vụ
# Phân loại nhanh, rẻ:
client.messages.create(model="claude-haiku-4-5", max_tokens=64,
                       messages=[{"role": "user", "content": "Phân loại tích cực/tiêu cực: 'Dịch vụ tệ.'"}])

# Tác vụ chung, mạnh nhất tầm Opus (mặc định):
client.messages.create(model="claude-opus-4-8", max_tokens=2048,
                       messages=[{"role": "user", "content": "Giải thích quantum computing."}])

Adaptive thinking — cho tác vụ phức tạp

Với tác vụ cần lập luận nhiều bước (toán, phân tích, agent), bật adaptive thinking: model tự quyết định khi nào và suy nghĩ bao nhiêu. Trên các model 4.6 trở lên không dùng budget_tokens nữa (gửi lên sẽ 400):

# (minh hoạ) bật adaptive thinking cho tác vụ phức tạp
response = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=8000,
    thinking={"type": "adaptive"},        # model tự điều chỉnh độ sâu suy nghĩ
    output_config={"effort": "high"},     # low | medium | high | max
    messages=[{"role": "user", "content": "Giải từng bước: một đội có 12 người..."}],
)

for block in response.content:
    if block.type == "thinking":
        print("[suy nghĩ]", block.thinking)
    elif block.type == "text":
        print("[trả lời]", block.text)

Trong đó effort điều chỉnh độ sâu suy nghĩ và tổng token tiêu thụ: high thường là điểm cân bằng tốt; dùng max khi độ chính xác quan trọng hơn chi phí; low cho tác vụ đơn giản.


7. Định giá theo token (định tính)

Bạn trả tiền theo token inputtoken output, với vài quy luật định tính đủ dùng ở giai đoạn này:

  • Output đắt hơn input. Model phải "làm việc" để sinh ra mỗi token output, nên giá mỗi token output cao hơn token input (thường vài lần).
  • Context dài tốn hơn. Prompt càng nhiều token input → càng nhiều tiền và (thường) càng chậm. Đây là lý do "nhồi cả tài liệu vào prompt" là ý tồi khi có thể lọc trước.
  • Model mạnh hơn đắt hơn. Opus/Fable đắt hơn Sonnet, Sonnet đắt hơn Haiku. Chọn model vừa đủ cho tác vụ.

Việc tối ưu chi phí thật sự (prompt caching, batch, chọn model theo route, đo lường) sẽ được nói kỹ ở bài production — /article/llm-07-production. Ở đây chỉ cần nhớ trực giác: input rẻ hơn output, context dài tốn hơn.


8. Các khả năng chính của LLM (điểm qua)

Một lời gọi messages.create là nền tảng, nhưng từ đó mở ra rất nhiều khả năng. Điểm nhanh (mỗi cái sẽ có bài / phần riêng):

  • Chat / hội thoại: cái bạn vừa thấy — nối message qua nhiều lượt.
  • Trích xuất / tóm tắt / phân loại: đưa văn bản phi cấu trúc vào, lấy ra thông tin có cấu trúc. Đây là "công việc thầm lặng" mà LLM cực giỏi và thường bị đánh giá thấp.
  • Tool use (function calling): bạn khai báo "công cụ" (hàm) với schema; model quyết định khi nào gọi và với tham số gì; code của bạn thực thi rồi trả kết quả về. Nền tảng của agent.
  • Structured output: ép model trả về JSON đúng theo schema (thay vì "văn xuôi kèm JSON"), để parse an toàn.
  • Vision: gửi kèm ảnh (base64 hoặc URL) và hỏi về nội dung ảnh — hữu ích cho xử lý tài liệu, ảnh chụp màn hình.

Những khả năng này sẽ được đào sâu ở các bài sau của series. Bài kế tiếp tập trung vào prompt engineering — cách viết prompt để lấy được đúng hành vi bạn muốn: /article/llm-02-prompting.


9. Khi nào dùng LLM — và khi nào KHÔNG

Đây là phần quan trọng nhất của bài. LLM là công cụ mạnh nhưng không phải búa cho mọi cái đinh. Dùng sai chỗ vừa tốn tiền, vừa chậm, vừa không đáng tin.

Nên dùng LLM khi bài toán có tính chất:

  • Ngôn ngữ tự nhiên: hiểu, sinh, dịch, tóm tắt văn bản của con người.
  • Phi cấu trúc / đa dạng: input muôn hình vạn trạng, không có quy tắc cố định (email khách hàng, bình luận, tài liệu tự do).
  • Không có lời giải "đúng tuyệt đối" duy nhất: viết nháp, gợi ý, phân loại theo ngữ nghĩa.
  • Khó viết luật thủ công: nếu bạn phải viết hàng nghìn if/else để bắt hết trường hợp thì LLM có thể tốt hơn.

KHÔNG nên dùng LLM khi:

  • Tính toán chính xác: cộng trừ nhân chia, tính thuế, tổng hoá đơn → viết code. LLM có thể sai số học và không tất định. (Nếu bắt buộc, cho model dùng công cụ tính toán/code execution — nhưng logic chính xác thì code luôn thắng.)
  • Luật lệ rõ ràng, xác định: "nếu tuổi ≥ 18 thì đủ điều kiện" → đó là một câu if, không phải một lời gọi LLM.
  • Tra cứu dữ liệu có cấu trúc: "khách hàng X có bao nhiêu đơn?" → truy vấn database, đừng hỏi LLM (nó không biết dữ liệu của bạn và có thể bịa).
  • Cần kết quả giống hệt mỗi lần: nghiệp vụ cần tất định thì sampling là kẻ thù.

Bắt đầu từ tier đơn giản nhất

Một sai lầm phổ biến của người mới: nhảy ngay vào xây "agent" phức tạp. Hãy bắt đầu từ mức đơn giản nhất giải quyết được bài toán:

  1. Một lời gọi (single call): phân loại, tóm tắt, trích xuất, hỏi-đáp. Một request, một response. Xử lý được đa số nhu cầu.
  2. Workflow: nhiều bước có logic do code của bạn điều phối, có thể dùng tool use.
  3. Agent: model tự quyết định trajectory của nó — chỉ dùng khi tác vụ thực sự cần khám phá mở, khó đặc tả trước, và giá trị đủ lớn để bù chi phí + độ trễ.

Chỉ leo lên tier phức tạp hơn khi tier đơn giản không đủ. Một prompt tốt + một lời gọi thường mạnh hơn bạn nghĩ.


10. Cảnh báo: hallucination, không tất định, cần đánh giá

Ba rủi ro bạn phải khắc cốt ghi tâm khi đưa LLM vào sản phẩm thật:

  • Hallucination (bịa): LLM sinh token theo xác suất, nên đôi khi tạo ra thông tin nghe có vẻ đúng nhưng sai — trích dẫn không tồn tại, số liệu bịa, API không có thật. Với câu hỏi cần dữ kiện, hãy cung cấp nguồn trong context (RAG) và yêu cầu model chỉ trả lời dựa trên nguồn đó — thay vì tin vào "trí nhớ" của model.
  • Không tất định: cùng input có thể ra output khác nhau. Đừng viết test kiểu so khớp chuỗi cứng; hãy kiểm tra theo tính chất (có chứa trường bắt buộc? đúng nhãn? parse được JSON?).
  • Cần đánh giá (eval): không thể "chạy thử vài lần thấy ổn rồi deploy". Bạn cần một bộ eval — tập ví dụ + tiêu chí chấm — để đo chất lượng một cách khách quan và phát hiện hồi quy khi đổi prompt hay đổi model. Đây là chủ đề riêng của một bài sau trong series.

Ngoài ra, các model Claude mới có thể trả về stop_reason: "refusal" (từ chối vì lý do an toàn) — code của bạn nên kiểm tra response.stop_reason trước khi đọc content, đừng giả định lúc nào cũng có text.


Use case thực tế

  • Phân loại ticket hỗ trợ: hàng nghìn email khách hàng viết tự do → dùng claude-haiku-4-5 (một lời gọi) để gán nhãn (bug / thanh toán / yêu cầu tính năng). Rẻ, nhanh, thay thế cho việc viết hàng trăm luật regex mong manh.
  • Tóm tắt tài liệu nội bộ: đưa biên bản họp / báo cáo dài vào, lấy ra 5 gạch đầu dòng + hành động cần làm. Dùng claude-opus-4-8 với streaming vì output có thể dài.
  • Trích xuất có cấu trúc: biến hợp đồng PDF phi cấu trúc thành JSON (bên A, bên B, ngày hiệu lực, điều khoản chấm dứt) để đổ vào database — kết hợp structured output.
  • Trợ lý hỏi-đáp trên tài liệu công ty: đây chính là RAG — không nhồi cả kho tài liệu vào prompt, mà tìm đoạn liên quan rồi đưa vào context (xem bài RAG và pgvector).
  • KHÔNG dùng LLM cho: tính tổng hoá đơn (code), kiểm tra điều kiện đủ tuổi (một câu if), đếm số đơn của khách (một câu SQL). Dùng đúng công cụ.

Ghi nhớ

  • LLM = mạng transformer dự đoán token tiếp theo; kiến thức đóng băng tại thời điểm huấn luyện — muốn nó biết dữ liệu của bạn thì phải đưa vào context.
  • Đơn vị làm việc là token, không phải từ. Token quyết định giới hạn ngữ cảnh, chi phí, tốc độ. Đếm token bằng messages.count_tokens của Anthropic, đừng dùng tokenizer của model khác.
  • Context window = input + output, hữu hạn (nhiều model Claude tới 1M token). API stateless — tự gửi lại lịch sử mỗi lượt. max_tokens là trần cho output.
  • Sampling làm LLM không tất định. Model Claude mới nhất (claude-opus-4-8, claude-fable-5) không nhận temperature/top_p/top_k — điều chỉnh bằng prompt và effort.
  • Gọi Claude qua client.messages.create(model=..., max_tokens=..., messages=[...]); dùng system cho vai trò; streaming + .get_final_message() cho output dài.
  • Chọn model đúng: mặc định claude-opus-4-8; claude-sonnet-4-6 (cân bằng), claude-haiku-4-5 (nhanh/rẻ), claude-fable-5 (cao cấp nhất). Bật thinking={"type": "adaptive"} cho tác vụ phức tạp.
  • Dùng LLM cho ngôn ngữ tự nhiên / phi cấu trúc; KHÔNG dùng cho tính toán chính xác (→ code), luật rõ ràng (→ if), tra cứu (→ DB). Bắt đầu từ một lời gọi trước khi làm agent.
  • Luôn cảnh giác hallucination, không tất định, và cần eval trước khi tin vào output trong sản phẩm.

Bài tiếp theo & liên kết

Tham khảo thêm:

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 14

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