Người mới bước vào thế giới LLM thường khởi đầu bằng việc “chat cho vui”, nhưng rất nhanh sẽ gặp một câu hỏi lớn hơn là làm sao để nó không chỉ trả lời mà còn biết làm việc. Từ khoảnh khắc đó, họ bắt đầu chạm vào khái niệm agent, tức một hệ thống biết nhận mục tiêu, biết chia nhỏ nhiệm vụ, biết dùng công cụ và biết tự kiểm tra kết quả để làm cho ra việc.

Bài đầu tiên này đóng vai trò nền móng cho cả series. Chúng ta sẽ không đi xa ngay mà chỉ tập trung vào một việc cụ thể là dựng một khung chat tối thiểu nhưng đúng chuẩn để về sau nâng cấp thành agent mà không phải viết lại từ đầu. Khi khung này đã đứng vững, chúng ta sẽ thấy hai yếu tố quyết định chất lượng ngay từ giai đoạn đầu nằm ở cách đóng gói ngữ cảnh bằng messages và cách viết prompt sao cho rõ ràng, có kỷ luật.

Từ khung chat đó, các bài tiếp theo sẽ mở rộng theo một mạch tự nhiên. Chúng ta sẽ lần lượt gắn thêm những bộ phận cần thiết để agent trở thành một hệ thống làm việc đúng nghĩa, từ việc định vai và nguyên tắc hành vi, bổ sung bộ nhớ và kiến thức, cho tới khả năng gọi công cụ, tổ chức vòng lặp lập kế hoạch và thực thi, rồi cuối cùng là cách đánh giá và hiệu chỉnh để agent ổn định khi dùng trong thực tế.

1) LLM và lý do nên dùng dạng chat/messages

LLM, hay Large Language Model, là mô hình tạo sinh ngôn ngữ. Khi chúng ta đưa vào một yêu cầu, nó sẽ sinh ra câu trả lời dưới dạng văn bản. Điều đáng lưu ý là LLM phản hồi rất mạnh theo ngữ cảnh, nghĩa là cùng một câu hỏi nhưng nếu bối cảnh khác nhau thì câu trả lời cũng có thể khác. Vì vậy, trong nhiều hệ thống hiện đại, chúng ta không chỉ gửi một câu hỏi đơn lẻ mà gửi một cấu trúc hội thoại gọi là messages.

Cú pháp phổ biến của messages là một danh sách các đối tượng, mỗi đối tượng có rolecontent, ví dụ như {"role": "system", "content": "..."}{"role": "user", "content": "..."}. Trong đó, system đóng vai trò như luật giao tiếp, nơi chúng ta đặt giọng điệu, mức độ chi tiết, định dạng đầu ra, cũng như tiêu chí đúng sai mà chúng ta muốn model tuân thủ. user là phần yêu cầu cụ thể của người dùng, tức câu hỏi hoặc nhiệm vụ cần xử lý. Khi cần hội thoại nhất quán hơn, chúng ta có thể bổ sung thêm các tin nhắn trước đó để model nhìn thấy lịch sử và bám theo mạch trao đổi.

Khi dùng dạng chat dựa trên messages, chúng ta thực chất đang định hình hành vi của model trước khi đặt câu hỏi. Đây là nền tảng quan trọng để về sau xây dựng các hệ thống hội thoại nghiêm túc như trợ giảng, chatbot, hay agent, bởi chúng ta kiểm soát được bối cảnh và quy tắc trả lời thay vì phó mặc cho một câu hỏi rời rạc.

2) Minh họa Kết nối LLM qua API với Python

Sau đây chúng ta sẽ dùng Python và OpenAI để minh họa cách làm việc của LLM: nạp khóa, tạo client, gửi messages, đọc câu trả lời.

Bước 1: Chuẩn bị môi trường và khóa bí mật

Chúng ta cài hai thư viện: một để gọi API, một để nạp biến môi trường từ file .env.

pip install openai python-dotenv

Tạo file .env cùng thư mục với code:

LLM_KEY="YOUR_KEY_HERE"

Chúng ta dùng biến LLM_KEY để chứa key từ nhà cung cấp.

Bước 2: Viết chương trình chat đơn giản

Tạo file chat_demo.py:

import os
from dotenv import load_dotenv
from openai import OpenAI

def read_secret() -> str:
    """
    Nạp biến môi trường từ .env và lấy khóa API.
    Tách riêng hàm này để dễ kiểm tra lỗi và tái sử dụng.
    """
    load_dotenv()
    token = os.getenv("LLM_KEY")  # bạn có thể đổi sang OPENAI_API_KEY nếu muốn
    if not token:
        raise RuntimeError("Chưa có LLM_KEY trong file .env")
    return token

def make_llm() -> OpenAI:
    """
    Tạo client để gọi LLM.
    Client giống như 'điện thoại' để bạn gọi lên dịch vụ LLM.
    """
    return OpenAI(api_key=read_secret())

def ask_llm(client: OpenAI, question: str, *, temp: float = 0.3) -> str:
    """
    Gửi yêu cầu theo dạng chat.
    - messages: gói ngữ cảnh hội thoại
    - temperature: độ ngẫu nhiên (thấp = ổn định)
    """
    dialog = [
        {"role": "system", "content": "Bạn là trợ giảng lập trình. Trả lời ngắn gọn, dễ hiểu, có ví dụ khi cần."},
        {"role": "user", "content": question},
    ]

    result = client.chat.completions.create(
        model="gpt-4.1-mini",   # chỉ là ví dụ; đổi theo model bạn có
        messages=dialog,
        temperature=temp,
    )

    # choices là danh sách phương án trả lời; thường lấy phần tử đầu tiên
    return result.choices[0].message.content

if __name__ == "__main__":
    llm = make_llm()
    reply = ask_llm(llm, "Giải thích 'temperature' trong LLM bằng 2 câu và 1 ví dụ.", temp=0.2)
    print(reply)

Vì sao phải có read_secret()?

Người mới hay gặp lỗi không chạy được chỉ vì quên khóa. Hàm này giúp phát hiện sớm và báo lỗi rõ ràng. Ngoài ra, tách riêng giúp chúng ta thay cách lấy khóa (từ file, từ hệ thống) mà không phải sửa toàn bộ chương trình.

Vì sao make_llm() lại tách riêng?

Vì trong dự án thật, chúng ta sẽ gọi LLM ở nhiều nơi. Tách phần tạo client giúp code gọn và dễ bảo trì.

ask_llm() quan trọng ở chỗ nào?

dialog và các tham số gọi model:

  • dialog là thứ quyết định câu trả lời có “đúng chất trợ giảng” hay không.
  • temperature giống núm điều chỉnh: muốn ổn định thì giảm xuống; muốn đa dạng ý tưởng thì tăng lên.
  • model là model chúng ta chọn.

Chạy chương trình:

python chat_demo.py

3) Chạy LLM local

Sau khi đã chat được qua API, chúng ta có một nhu cầu rất tự nhiên là chạy ngay trên máy để thử prompt nhanh hơn, hoặc để làm offline. Chúng ta có thể dùng LM Studio (hoặc công cụ tương đương) để:

  1. Tải một model về máy
  2. Chạy thử trong giao diện chat
  3. Bật chế độ server để chương trình của bạn gọi vào localhost

Cách làm này logic code không thay đổi. Chúng ta vẫn gửi messages, vẫn nhận choices[0].message.content, chỉ đổi nơi gửi request: từ dịch vụ online sang server local.

Ví dụ, giả sử server local đang chạy ở http://localhost:1234/v1:

from openai import OpenAI

def local_llm() -> OpenAI:
    """
    Tạo client trỏ về server LLM trên máy.
    api_key ở đây chỉ là giá trị minh họa.
    """
    return OpenAI(base_url="http://localhost:1234/v1", api_key="local")

def ask_local(question: str) -> str:
    client = local_llm()
    res = client.chat.completions.create(
        model="local-any",  # tên model local có thể khác, tùy công cụ bạn dùng
        messages=[
            {"role": "system", "content": "Bạn là gia sư. Trả lời rõ ràng, có ví dụ nhỏ."},
            {"role": "user", "content": question},
        ],
        temperature=0.4,
    )
    return res.choices[0].message.content

if __name__ == "__main__":
    print(ask_local("Viết 1 ví dụ giải thích delimiter trong prompt bằng 3 câu."))

Vì sao chỉ cần đổi base_url? Vì “chuẩn giao tiếp” vẫn là gửi một request chat đến một endpoint. Khi server local hỗ trợ endpoint tương thích, code của vẫn giữ nguyên cấu trúc.

4) Cách làm cho model trả lời đúng yêu cầu

Đến đây, chúng ta sẽ gặp vấn đề quan trọng hơn việc code: cách bạn đặt câu hỏi. Nhiều người mới hỏi kiểu “mơ hồ”, rồi thất vọng vì câu trả lời dài, chung chung, hoặc lệch mục tiêu.

Tư duy đặt câu hỏi rất giống cách giáo viên ra đề – không chỉ nói “làm bài”, mà phải nói rõ:

  • Bạn muốn đầu ra dạng gì?
  • Giới hạn độ dài ra sao?
  • Tiêu chí đúng là gì?
  • Nếu có dữ liệu, dữ liệu nằm ở đâu và cần xử lý phần nào?

4.1. Ví dụ trước và sau: từ mơ hồ sang rõ ràng

Prompt mơ hồ:

Giải thích prompt engineering.”

Kết quả thường dễ bị lan man vì model không biết chúng ta muốn: tóm tắt hay đào sâu, cho ai đọc, dài bao nhiêu.

Prompt rõ ràng hơn:

“Bạn là trợ giảng. Giải thích prompt engineering cho người mới trong đúng 4 câu. Câu cuối đưa 1 ví dụ prompt mẫu.

Kết quả lúc này sẽ khác biệt: câu trả lời gọn, đúng độ dài, có ví dụ.

Nếu cần bài viết hoặc tài liệu có format ổn định, chúng ta nên “chốt” định dạng:

Trả lời theo mẫu:
Định nghĩa: …
Vì sao quan trọng: …
Ví dụ prompt: …

Model sẽ dễ tuân thủ hơn nhiều so với việc chỉ nói “giải thích”.

4.2. Delimiter: công cụ chống lạc đề khi chúng ta đưa dữ liệu dài

Delimiter là “vạch ranh giới” giữa dữ liệu và yêu cầu.

Ví dụ:

Tóm tắt đoạn trong ... thành 3 câu. Không thêm ý ngoài đoạn đó.

Khi không dùng delimiter, model đôi khi không biết phần nào là dữ liệu, phần nào là chỉ dẫn. Với người mới học, delimiter giúp giảm lỗi nhanh nhất.

4.3. Yêu cầu theo từng bước: khi bài toán nhiều tầng

Nếu muốn kết quả “đi từ đơn giản đến hoàn chỉnh”, hãy yêu cầu theo bước. Ví dụ:

Bước 1: tóm tắt 1 câu.
Bước 2: chuyển thành checklist 5 dòng.
Bước 3: cho 1 ví dụ áp dụng.

Cách này đặc biệt hợp với tutorial, vì người đọc dễ theo dõi và chúng ta cũng dễ kiểm soát chất lượng.

4.4. Đưa ví dụ đầu ra (few-shot) để model bắt chước đúng format

Nếu muốn model luôn trả lời theo một mẫu, hãy đưa một ví dụ ngắn.

Ví dụ:

Hãy trả lời theo format giống ví dụ sau (chỉ là ví dụ format):

  • Khái niệm: …
  • Ví dụ: …
  • Lỗi thường gặp: …

Đây là cách tạo bài viết nhất quán giữa nhiều chủ đề.

Kết thúc

Trong bài này chúng ta đã chạy được một LLM (từ API và local) đơn giản và tìm hiểu cách thức để nâng cao chất lượng câu trả lời từ LLM. Từ các bài tiếp theo, chúng ta sẽ lần lượt gắn thêm những bộ phận cần thiết để agent trở thành một hệ thống làm việc đúng nghĩa, từ việc định vai và nguyên tắc hành vi, bổ sung bộ nhớ và kiến thức, cho tới khả năng gọi công cụ, tổ chức vòng lặp lập kế hoạch và thực thi, rồi cuối cùng là cách đánh giá và hiệu chỉnh để agent ổn định khi dùng trong thực tế.

Posted in ,