Sử dụng API văn bản
Mục tiêu
Phần tiêu đề “Mục tiêu”Sau bài này, bạn sẽ:
- Gọi được một mô hình ngôn ngữ lớn (large language model, LLM) từ Python qua API kiểu OpenAI, với danh sách
messagesgồm các vai tròsystem,user,assistant. - Hiểu vì sao API không nhớ cuộc trò chuyện, và vì sao chi phí tính theo token tăng nhanh khi hội thoại dài ra.
- Tự tính được softmax có nhiệt độ (temperature), top-k và top-p (nucleus sampling), và biết mỗi tham số đổi phân phối của token kế tiếp thế nào.
- Viết được code dùng thật: để SDK thử lại lỗi tạm thời có backoff, bắt các lỗi còn lại, đọc đầu ra JSON an toàn, và biết streaming hoạt động thế nào.
Cần biết trước
Phần tiêu đề “Cần biết trước”- Hàm, chuỗi, đọc/ghi file, thuộc phần Python (Đã học)
Trực giác
Phần tiêu đề “Trực giác”Bạn muốn chương trình Python của mình trả lời câu hỏi bằng tiếng Việt, tóm tắt văn bản hay trích thông tin từ đánh giá của khách. Bạn không cần huấn luyện mô hình nào: OpenAI, Google hay Groq chạy sẵn LLM trên máy chủ của họ. Chương trình của bạn gửi văn bản qua mạng tới API (application programming interface, giao diện lập trình ứng dụng) của họ, và nhận lại văn bản mô hình viết ra. AI Atlas chia việc dùng LLM thành bốn cấp: dùng mô hình có sẵn qua API, xây prompt/RAG/agent trên mô hình có sẵn, fine-tune, và pre-train từ đầu. Bài này ở cấp đầu tiên.
Gọi thử vài lần, bạn sẽ gặp ba điều lạ:
- Mô hình quên ngay. Bạn hỏi “Token là gì?”, rồi hỏi tiếp “Cho một ví dụ”. Nếu lần sau chỉ gửi câu thứ hai, mô hình không biết “ví dụ” về cái gì. Giống nhắn tin cho một người mất trí nhớ sau mỗi câu trả lời: bạn phải dán lại cả cuộc trò chuyện vào mỗi tin nhắn.
- Cùng câu hỏi, mỗi lần một câu trả lời. Mô hình viết từng mẩu chữ một, và mỗi mẩu được rút ngẫu nhiên theo một phân phối xác suất. Có những tham số vặn được độ ngẫu nhiên đó.
- Bạn trả tiền theo mẩu chữ. Các mẩu ấy gọi là token. Nhà cung cấp tính tiền và đặt giới hạn theo số token, cả token gửi đi lẫn token mô hình viết ra.
Bài toán của bài này là: gửi đúng thứ mô hình cần qua API, hiểu mô hình chọn từng token thế nào và vặn việc chọn đó bằng tham số nào, rồi viết code chịu được lỗi mạng, giới hạn số yêu cầu và đầu ra sai định dạng.
Lý thuyết
Phần tiêu đề “Lý thuyết”Một lần gọi API
Phần tiêu đề “Một lần gọi API”Bài dùng thư viện openai cho Python, một SDK (software development kit: bộ thư viện do nhà cung cấp viết sẵn), và endpoint Chat Completions (endpoint: một địa chỉ cụ thể của API, mỗi địa chỉ làm một việc). OpenAI khuyên ứng dụng mới dùng Responses API, nhưng vẫn hỗ trợ Chat Completions lâu dài, và Gemini, Groq, Ollama đều cung cấp endpoint này theo đúng kiểu của OpenAI. Chỉ cần đổi địa chỉ (base_url), key và tên mô hình, cùng một đoạn code chạy được với nhiều nhà cung cấp.
Một lần gọi gửi đi hai thứ chính: tên mô hình và danh sách messages. Mỗi phần tử của messages có một vai trò (role) và nội dung:
| Vai trò | Ai viết | Dùng để |
|---|---|---|
system | Bạn, người viết chương trình | Đặt vai, giọng điệu, quy tắc: “Bạn là trợ giảng, trả lời ngắn gọn”. Gọi là system prompt |
user | Người dùng | Câu hỏi, yêu cầu |
assistant | Mô hình, ở các lượt trước | Lời mô hình đã trả lời, bạn gửi lại để mô hình “nhớ” |
Hội thoại hai lượt ở ô code thứ hai của bài, đến lượt thứ hai, gửi đi danh sách sau:
| Vai trò | Nội dung |
|---|---|
system | Bạn là trợ giảng, trả lời ngắn gọn bằng tiếng Việt. |
user | Token trong mô hình ngôn ngữ là gì? Trả lời trong hai câu. |
assistant | (câu trả lời của lượt một, chương trình đã lưu lại) |
user | Cho một ví dụ với câu tiếng Việt. |
Nhờ ba dòng đầu, câu “Cho một ví dụ” mới có nghĩa. API không giữ trạng thái (stateless): máy chủ không lưu gì giữa hai lần gọi, nên mọi thứ mô hình cần biết phải nằm trong messages.
Với các mô hình mới, OpenAI gọi vai trò system là developer, nhưng Gemini, Groq, Ollama vẫn dùng system, nên code của bài dùng system.
Phản hồi trả về có ba chỗ bạn hay đọc: choices[0].message.content là chữ mô hình viết; choices[0].finish_reason cho biết vì sao mô hình dừng ("stop": tự kết thúc, "length": chạm trần số token); usage cho biết số token vào và ra.
Token và chi phí
Phần tiêu đề “Token và chi phí”Token là đơn vị văn bản mô hình đọc và viết. Nó không hẳn là từ, cũng không phải ký tự: một từ thường gặp có thể là một token, một từ hiếm bị cắt thành vài token. Mỗi mô hình có một cửa sổ ngữ cảnh (context window): số token tối đa của đầu vào cộng đầu ra trong một lần gọi.
Nhà cung cấp tính tiền theo token, thường tách hai giá: token vào (prompt, gồm cả lịch sử hội thoại) và token ra (completion). Vì lịch sử được gửi lại mỗi lượt, chi phí của một cuộc trò chuyện dài tăng nhanh hơn bạn nghĩ. Gọi là số token của system prompt, mỗi câu hỏi token, mỗi câu trả lời token. Ở lượt thứ , bạn gửi system prompt, lượt cũ (mỗi lượt một hỏi một đáp) và câu hỏi mới. Gọi số token vào ở lượt này là :
Cộng từ lượt 1 tới lượt . Hai số hạng và lặp lại lần. Số hạng giữa cần tổng :
Số hạng cuối có : tổng token vào tăng theo bình phương số lượt. Với , : lượt 1 gửi 70 token, lượt 10 gửi 970 token, và 10 lượt cộng lại là token vào. Vì thế chatbot thật thường cắt bớt hoặc tóm tắt lịch sử cũ.
Tham số max_tokens đặt trần số token mô hình được viết ra trong một lần gọi. Trần này chặn chi phí, nhưng không làm mô hình viết gọn lại: chạm trần thì câu bị cắt giữa chừng và finish_reason là "length". Muốn câu ngắn, hãy yêu cầu ngay trong prompt. OpenAI đã đổi tên tham số này thành max_completion_tokens, tên mới tính cả các token suy luận ẩn của mô hình reasoning (reasoning model: mô hình tự suy luận nhiều bước trước khi trả lời). Ollama chỉ liệt kê tên cũ; OpenAI và Groq vẫn nhận tên cũ nhưng đã đánh dấu bỏ dần (riêng mô hình reasoning của OpenAI thì từ chối). Vì thế code của bài không đặt trần này, để nhà cung cấp dùng mặc định.
Mô hình chọn token kế tiếp thế nào
Phần tiêu đề “Mô hình chọn token kế tiếp thế nào”LLM viết từng token một. Ở mỗi bước, mô hình đọc mọi token đã có rồi cho ra một con số cho mỗi token trong từ vựng, gọi là logit. Gọi từ vựng là , có token, và logit của token thứ là . Logit càng lớn, mô hình càng thấy token đó hợp làm token kế tiếp.
Ví dụ: sau “Hôm nay trời rất”, giả sử mô hình chỉ xét 10 ứng viên (cho dễ nhìn, coi mỗi từ là một token):
| Token | đẹp | nóng | lạnh | mát | nắng | oi | trong | xanh | ẩm | tối |
|---|---|---|---|---|---|---|---|---|---|---|
| Logit | 3.0 | 2.6 | 2.0 | 1.5 | 1.1 | 0.6 | 0.1 | −0.4 | −1.0 | −1.6 |
Bước 1: biến logit thành xác suất bằng softmax. Logit có thể âm và không cộng lại bằng 1, nên chưa phải xác suất. Hàm softmax lấy của từng logit (luôn dương), rồi chia cho tổng (để cộng lại bằng 1):
Bước 2: chia logit cho nhiệt độ trước softmax. Đây là núm vặn temperature:
cho lại đúng softmax ở Bước 1. Để thấy làm gì, so xác suất của hai token và . Hai phân số có cùng mẫu số nên mẫu số triệt tiêu, rồi dùng :
Công thức này cho ba điều:
- Thứ tự không đổi. thì tỉ số lớn hơn 1 ở mọi . Nhiệt độ không bao giờ đưa token kém lên trên token tốt.
- khuếch đại chênh lệch, phân phối nhọn hơn. “đẹp” và “nóng” chênh 0.4 logit. Ở , “đẹp” có khả năng gấp lần “nóng”; ở , gấp lần. Khi , token có logit lớn nhất chiếm gần hết xác suất: mô hình gần như luôn chọn nó, gọi là giải mã tham lam (greedy decoding).
- thu nhỏ chênh lệch, phân phối phẳng hơn. Token hiếm có nhiều cơ hội hơn: câu trả lời đa dạng hơn, nhưng cũng dễ lạc đề hơn.
Với 10 logit ở trên (số từ ô code thứ nhất):
| đẹp | nóng | lạnh | Top-p 0.9 giữ | |
|---|---|---|---|---|
| 0.5 | 0.599 | 0.269 | 0.081 | 3 token |
| 1 | 0.382 | 0.256 | 0.140 | 5 token |
| 1.5 | 0.286 | 0.219 | 0.147 | 7 token |
Bước 3 (tùy chọn): cắt đuôi bằng top-k. Từ vựng thật có hàng chục tới hàng trăm nghìn token. Mỗi token trong đuôi có xác suất nhỏ, nhưng cộng cả đuôi thì không nhỏ, nên thỉnh thoảng một token vô nghĩa được rút trúng. Top-k chỉ giữ token có xác suất lớn nhất (tập ), cho các token còn lại xác suất 0, rồi chuẩn hóa lại để tổng bằng 1:
Với ở : ba token đầu có tổng ; chia mỗi số cho tổng này được , , .
Bước 3’ (tùy chọn): cắt đuôi bằng top-p. Holtzman và cộng sự (2020) đề xuất cách này sau khi thấy hai thái cực đều hỏng: luôn chọn token xác suất cao nhất cho văn bản nhạt và lặp lại, còn rút mẫu trên cả từ vựng thì thỉnh thoảng lạc vào đuôi vô nghĩa. Top-k chặn được đuôi, nhưng cố định: khi mô hình rất chắc chắn, vẫn giữ hai token gần như vô lý; khi mô hình phân vân giữa hàng chục lựa chọn hợp lý, lại bỏ đi gần hết. Top-p, còn gọi là nucleus sampling, xếp token theo xác suất giảm dần rồi lấy tập nhỏ nhất có tổng xác suất từ ngưỡng trở lên:
rồi chuẩn hóa lại như top-k (ngưỡng không có chỉ số, khác xác suất của từng token). Ở , tổng cộng dồn là ; ; ; ; . Bốn token đầu mới được , nên phải lấy cả token thứ năm: top-p giữ 5 token. Bảng trên cho thấy cùng ngưỡng giữ 3, 5 hay 7 token tùy độ nhọn của phân phối. Tập giữ lại co giãn: ít token khi mô hình chắc, nhiều token khi mô hình phân vân.
Bước 4: rút một token theo phân phối cuối cùng, nối nó vào văn bản, rồi lặp lại từ đầu cho token sau.
Trong API. Chat Completions có temperature (OpenAI cho từ 0 tới 2) và top_p, nhưng không có top_k. OpenAI khuyên chỉ chỉnh một trong hai, vì cả hai cùng đổi phân phối nên khó đoán tác động riêng. temperature=0 hợp với trích xuất thông tin, nhưng không bảo đảm lần nào cũng ra đúng một câu: OpenAI ghi rõ tính tất định không được bảo đảm, và Groq đổi 0 thành 1e-8.
Streaming
Phần tiêu đề “Streaming”Mô hình viết từng token, nên không cần đợi viết xong mới gửi. Đặt stream=True, API trả về nhiều mảnh (chunk) theo thời gian, mỗi mảnh mang vài token mới trong choices[0].delta.content. Tổng thời gian gần như không đổi, nhưng chữ đầu tiên hiện sau một khoảnh khắc thay vì sau vài giây. Đoạn trích sau dùng client, MODEL, messages như ô hội thoại ở mục Ví dụ code:
stream = client.chat.completions.create(model=MODEL, messages=messages, stream=True)for chunk in stream: # choices rỗng: mảnh usage (khi bật include_usage), hoặc ở vài nhà cung cấp if not chunk.choices: continue text = chunk.choices[0].delta.content if text: # mảnh đầu, mảnh cuối có thể không mang chữ print(text, end="", flush=True) # flush: in ngay, không gom lạiMuốn biết số token khi stream, đặt thêm stream_options={"include_usage": True}: số token nằm ở một mảnh cuối có choices rỗng, và luồng đứt giữa chừng thì có thể không nhận được mảnh đó. SDK cũng không tự thử lại một luồng đã đứt: phần đã nhận là phần dở dang.
Lỗi, rate limit và retry
Phần tiêu đề “Lỗi, rate limit và retry”Gọi qua mạng thì sẽ có lúc lỗi. Mọi lỗi khi gọi API đều kế thừa openai.APIError. Các lỗi hay gặp:
| Mã HTTP | Lớp lỗi | Thường do | SDK tự thử lại? |
|---|---|---|---|
| — | APIConnectionError (lớp con APITimeoutError) | Không tới được máy chủ, hoặc quá thời gian chờ | Có |
| 400 | BadRequestError | Yêu cầu sai, ví dụ tham số nhà cung cấp không hỗ trợ | Không |
| 401 | AuthenticationError | Key sai, thiếu, hoặc key của nhà cung cấp khác | Không |
| 403 | PermissionDeniedError | Key không có quyền dùng mô hình hay tính năng đó | Không |
| 404 | NotFoundError | Sai tên mô hình hoặc sai base_url | Không |
| 429 | RateLimitError | Vượt rate limit (giới hạn số yêu cầu hay số token mỗi phút, mỗi ngày) | Có |
| ≥ 500 | InternalServerError | Lỗi phía máy chủ | Có |
Mọi lỗi có mã HTTP là APIStatusError, có thuộc tính status_code. SDK còn tự thử lại hai mã ít gặp là 408 (máy chủ chờ quá lâu) và 409 (xung đột). Quy tắc chung: chỉ thử lại lỗi tạm thời (mạng, 408, 409, 429, ≥ 500). Lỗi 400–404 là lỗi của chính yêu cầu, thử lại bao nhiêu lần cũng vậy.
Thử lại ngay lập tức thường vô ích vì máy chủ đang quá tải sẽ còn quá tải thêm, nên cách chuẩn là lùi theo cấp số nhân (exponential backoff): chờ lâu gấp đôi sau mỗi lần thất bại, có trần, và bớt đi một lượng ngẫu nhiên gọi là jitter để nhiều chương trình cùng bị lỗi không gõ cửa lại cùng một lúc. SDK openai 3.37 chờ khoảng 0.5, 1, 2, 4 rồi tối đa 8 giây giữa các lần, bớt ngẫu nhiên tới 25%, và theo header Retry-After (phần thông tin máy chủ gửi kèm phản hồi) nếu máy chủ gửi, trừ khi nó quá 120 giây thì báo lỗi luôn.
Bạn không phải tự viết vòng lặp này. OpenAI(max_retries=...) đặt số lần thử lại (mặc định 2, đặt 0 để tắt), OpenAI(timeout=...) đặt thời gian chờ mỗi lần gọi (mặc định 10 phút).
Đầu ra JSON
Phần tiêu đề “Đầu ra JSON”Muốn chương trình dùng được câu trả lời, bạn thường cần dữ liệu có cấu trúc, ví dụ JSON, chứ không phải đoạn văn. Có ba mức, chặt dần:
- Chỉ dặn trong prompt: “Chỉ trả về JSON với hai khóa…”. Dễ dùng nhất, nhưng mô hình đôi khi thêm câu dẫn hay bọc JSON trong khối code.
- JSON mode:
response_format={"type": "json_object"}bảo đảm chuỗi trả về là JSON hợp lệ, nhưng không bảo đảm đúng các khóa bạn muốn. Vẫn phải mô tả cấu trúc trong prompt, và chữ “JSON” phải xuất hiện đâu đó trongmessages(OpenAI báo lỗi nếu thiếu). - Đầu ra có cấu trúc (structured output):
{"type": "json_schema", ...}bảo đảm khớp một schema bạn đưa khi bậtstrict. OpenAI khuyên dùng khi mô hình hỗ trợ.
Mức hỗ trợ khác nhau giữa các nhà cung cấp, và JSON vẫn có thể bị cắt cụt khi chạm trần số token. Vì vậy, dù dùng mức nào, luôn đọc bằng json.loads trong try/except.
Giới hạn
Phần tiêu đề “Giới hạn”- Mỗi nhà cung cấp “kiểu OpenAI” bỏ bớt vài tính năng. Gửi trường không được hỗ trợ có thể bị bỏ qua im lặng, hoặc nhận lỗi 400 (bảng nhà cung cấp ở mục Ví dụ code).
- Câu trả lời có thể sai mà nghe vẫn rất tự tin. Bài Prompt Engineering và bài RAG và chatbot hiện đại bàn cách giảm chuyện này.
- Code ở đây chạy trong dòng lệnh. Đưa streaming lên giao diện web là việc của bài Làm giao diện với Gradio/Streamlit.
- Bài chưa nói logit từ đâu ra. Bài Xây dựng LLM đầu tiên dựng một mô hình nhỏ và cho thấy logit được tính thế nào.
Minh họa
Phần tiêu đề “Minh họa”Bảng trong hình là phân phối của token kế tiếp sau “Hôm nay trời rất”, với 10 logit ở mục Lý thuyết. Thanh đậm là xác suất cuối cùng, thanh mờ phía sau là xác suất trước khi cắt đuôi. Ba thanh trượt đổi , và . “Lấy 1 mẫu” rút một token và viết nó vào sau câu; “Lấy 100 mẫu” đếm số lần mỗi token được chọn. Hãy thử:
- Lúc đầu, , top-k và top-p đều tắt: “đẹp” 0.38, “nóng” 0.26, “lạnh” 0.14, và cả 10 token đều có thể được chọn. Bấm “Lấy 100 mẫu” vài lần: “đẹp” thắng nhiều nhất, nhưng thỉnh thoảng cả những token cuối bảng như “ẩm” hay “tối” cũng ra.
- Kéo top-k về 3: bảy token bị đánh dấu “đã loại”, ba token còn lại thành 0.49, 0.33, 0.18.
- Đưa top-k về 10, kéo top-p về 0.9: còn 5 token, “đẹp” thành 0.41. Giữ top-p 0.9 rồi kéo xuống 0.5: chỉ còn 3 token. Kéo lên 1.5: còn 7 token.
- Kéo về 0: “đẹp” có xác suất 1, mọi token khác bị loại. “Lấy 100 mẫu” ra 100 lần “đẹp”: đó là giải mã tham lam.
Thứ tự nhiệt độ, rồi top-k, rồi top-p trong hình là một lựa chọn để minh họa. Nhà cung cấp API có thể áp các bước theo thứ tự khác.
Hôm nay trời rất
| Token | Xác suất | Số lần | |
|---|---|---|---|
| đẹp | 0.38 đã loại | 0 | |
| nóng | 0.26 đã loại | 0 | |
| lạnh | 0.14 đã loại | 0 | |
| mát | 0.09 đã loại | 0 | |
| nắng | 0.06 đã loại | 0 | |
| oi | 0.03 đã loại | 0 | |
| trong | 0.02 đã loại | 0 | |
| xanh | 0.01 đã loại | 0 | |
| ẩm | 0.01 đã loại | 0 | |
| tối | 0.00 đã loại | 0 | |
Còn 10 trên 10 token có thể được chọn.
Mười token có thể đứng sau "Hôm nay trời rất", xếp từ khả năng cao nhất xuống thấp nhất. Logit là số tự đặt để minh họa, không do mô hình thật tính ra.
Ví dụ code
Phần tiêu đề “Ví dụ code”Code trong bài kiểm tra lần cuối tháng 10/2026, với Python 3.14 và openai 3.37. Ô thứ nhất chạy ngay trên trang bằng NumPy, không gọi mạng. Hai ô sau gọi API thật nên chạy trên máy bạn: bạn cần một key của nhà cung cấp và ba biến môi trường.
Chuẩn bị: nhà cung cấp và biến môi trường
Phần tiêu đề “Chuẩn bị: nhà cung cấp và biến môi trường”Code của bài không ghi tên nhà cung cấp hay tên mô hình. Nó đọc ba biến môi trường (environment variable): OPENAI_API_KEY (key), OPENAI_BASE_URL (địa chỉ API, bỏ trống thì dùng OpenAI) và LLM_MODEL (tên mô hình). Bạn chọn nhà cung cấp, tạo key trên trang của họ, và lấy tên một mô hình chat trong danh sách mô hình của họ (tên mô hình đổi rất nhanh, nên bài không ghi sẵn). Ba biến phải cùng một nhà cung cấp: key của Groq gửi tới máy chủ OpenAI cho lỗi 401, tên mô hình của nhà này gửi sang nhà kia thường cho lỗi 404.
| Nhà cung cấp | OPENAI_BASE_URL | Gói miễn phí | Ghi chú |
|---|---|---|---|
| OpenAI | để trống | Không ghi ở đây, xem trang giá | Mô hình reasoning: không nhận max_tokens (dùng max_completion_tokens), chỉ nhận temperature mặc định; xóa dòng temperature nếu gặp lỗi 400 |
| Google Gemini | https://generativelanguage.googleapis.com/v1beta/openai/ | Có bậc Free, hạn mức tính theo project, thêm key không tăng hạn mức (trang giá) | Phần tương thích thư viện OpenAI còn ở bản beta. Gemini 2.5 Pro và Gemini 3 luôn suy nghĩ trước khi trả lời: trần token nhỏ có thể cho câu trả lời rỗng |
| Groq | https://api.groq.com/openai/v1 | Có Free Plan (hạn mức) | Báo lỗi 400 nếu gửi logprobs, logit_bias; n phải bằng 1; đổi temperature=0 thành 1e-8 |
| Ollama (trên máy bạn) | http://localhost:11434/v1/ | Mô hình chạy bằng máy bạn, không tính tiền theo token | Key bắt buộc có nhưng bị bỏ qua: đặt ollama. Tải mô hình trước bằng ollama pull |
Các nhà cung cấp còn khác nhau ở JSON mode và embedding (vectơ biểu diễn nghĩa của văn bản, dùng ở bài RAG và chatbot hiện đại):
| Nhà cung cấp | JSON mode | Embeddings |
|---|---|---|
| OpenAI | json_object và json_schema | Có |
| Gemini | Trang tương thích chỉ có ví dụ dùng parse() | Có, /embeddings cùng base_url |
| Groq | json_object mọi mô hình, prompt phải có chữ “JSON”; json_schema một số mô hình | Không thấy trong trang Models |
| Ollama | Có response_format | Có, /v1/embeddings; tải mô hình embedding bằng ollama pull |
Kiểm tra lần cuối: 10/2026. Giá và hạn mức đổi thường xuyên, nên bài không ghi con số.
Đặt biến môi trường trong cửa sổ dòng lệnh bạn dùng để chạy code. Biến chỉ sống trong cửa sổ đó; mở cửa sổ mới thì đặt lại.
export OPENAI_API_KEY="<key-của-bạn>"export OPENAI_BASE_URL="<địa chỉ API của nhà cung cấp>"export LLM_MODEL="<tên mô hình>"pip install openai$env:OPENAI_API_KEY = "<key-của-bạn>"$env:OPENAI_BASE_URL = "<địa chỉ API của nhà cung cấp>"$env:LLM_MODEL = "<tên mô hình>"pip install openaiDùng OpenAI thì bỏ dòng OPENAI_BASE_URL. Không bao giờ dán key thẳng vào code: file code hay bị chia sẻ hoặc đẩy lên Git, còn biến môi trường thì không. Chạy mỗi ô bằng python ten_file.py; trên Windows, nếu chữ tiếng Việt in ra bị lỗi mã hóa, chạy python -X utf8 ten_file.py.
Lấy mẫu token bằng NumPy
Phần tiêu đề “Lấy mẫu token bằng NumPy”Ô này làm lại phần Lý thuyết trên 10 logit của “Hôm nay trời rất”: softmax có nhiệt độ ở ba mức , top-k với , rồi rút 1000 token với top-p .
Softmax có nhiệt độ, top-k, top-p
# Lấy mẫu token kế tiếp: softmax có nhiệt độ, rồi top-k hoặc top-p.import numpy as np
# Ngữ cảnh: "Hôm nay trời rất ___". Mười token ứng viên, logit giảm dần.tokens = ["đẹp", "nóng", "lạnh", "mát", "nắng", "oi", "trong", "xanh", "ẩm", "tối"]# logits: next-tokenz = np.array([3.0, 2.6, 2.0, 1.5, 1.1, 0.6, 0.1, -0.4, -1.0, -1.6])
def softmax_t(z, T): a = z / T e = np.exp(a - a.max()) # trừ max để exp không bị tràn số return e / e.sum()
def keep_and_renormalize(p, keep): q = np.zeros_like(p) q[keep] = p[keep] # token bị loại có xác suất 0 return q / q.sum() # chuẩn hóa lại cho tổng bằng 1
def top_k(p, k): order = np.argsort(p)[::-1] # chỉ số theo xác suất giảm dần return keep_and_renormalize(p, order[:k])
def top_p(p, threshold): order = np.argsort(p)[::-1] cum = np.cumsum(p[order]) n = np.searchsorted(cum, threshold) + 1 # tập nhỏ nhất có tổng >= p return keep_and_renormalize(p, order[:n])
for T in [0.5, 1.0, 1.5]: p = softmax_t(z, T) kept = np.count_nonzero(top_p(p, 0.9)) print(f"T = {T}: top 3 = {np.round(p[:3], 3)}, top-p 0.9 keeps {kept}")
print("top-k 3, T = 1:", np.round(top_k(softmax_t(z, 1.0), 3)[:4], 3))p = top_p(softmax_t(z, 1.0), 0.9)rng = np.random.default_rng(0)counts = np.bincount(rng.choice(10, size=1000, p=p), minlength=10)for i, tok in enumerate(tokens): print(f"{tok:>6}: p = {p[i]:.3f}, drawn {counts[i]}")Lần chạy đầu cần tải Python, khoảng 10 MB.
Ba dòng đầu khớp bảng ở mục Lý thuyết: top-p giữ 3, 5 rồi 7 token khi tăng từ 0.5 lên 1.5. Dòng top-k cho 0.491, 0.329, 0.18, và 0 từ token thứ tư trở đi. Với top-p ở , năm token còn lại có xác suất 0.415, 0.278, 0.153, 0.093, 0.062; trong 1000 lần rút, chúng được chọn 386, 287, 172, 96, 59 lần, sát với xác suất. Năm token cuối không lần nào được chọn, vì xác suất của chúng đã bị đặt về 0.
Hội thoại hai lượt
Phần tiêu đề “Hội thoại hai lượt”Ô này gửi hai câu hỏi, câu sau chỉ hiểu được nhờ câu trước. Hàm ask thêm câu hỏi vào messages, gọi API, rồi lưu câu trả lời vào messages với vai trò assistant.
Chạy trên máy bạn
Hội thoại hai lượt
- Cần cài
pip install openai- Biến môi trường
OPENAI_BASE_URL(tùy chọn),OPENAI_API_KEY,LLM_MODEL
# Hội thoại hai lượt. API không nhớ gì giữa hai lần gọi,# nên mỗi lần ta gửi lại cả danh sách messages.import os
from openai import OpenAI
client = OpenAI( base_url=os.environ.get("OPENAI_BASE_URL"), # bỏ trống: dùng OpenAI api_key=os.environ["OPENAI_API_KEY"],)MODEL = os.environ["LLM_MODEL"]
messages = [ {"role": "system", "content": "Bạn là trợ giảng, trả lời ngắn gọn bằng tiếng Việt."},]
def ask(question): messages.append({"role": "user", "content": question}) response = client.chat.completions.create( model=MODEL, messages=messages, temperature=0.3, # thấp: câu trả lời tập trung, ít bất ngờ ) choice = response.choices[0] answer = choice.message.content or "" # content có thể là None # Lưu lời model để lượt sau model "nhớ" được messages.append({"role": "assistant", "content": answer}) usage = response.usage print(f"Hỏi: {question}\nĐáp: {answer}") print(f"(token vào: {usage.prompt_tokens}, " f"token ra: {usage.completion_tokens}, " f"finish_reason: {choice.finish_reason})\n")
ask("Token trong mô hình ngôn ngữ là gì? Trả lời trong hai câu.")# Câu này chỉ hiểu được nhờ lượt trước nằm trong messagesask("Cho một ví dụ với câu tiếng Việt.")Khi chạy, hãy nhìn dòng trong ngoặc sau mỗi câu trả lời. Số token vào ở lượt hai lớn hơn hẳn lượt một, vì lượt hai gửi lại cả câu hỏi và câu trả lời của lượt một. finish_reason thường là stop. Thử thêm max_tokens=20 vào lời gọi để thấy length: câu trả lời bị cắt giữa chừng. or "" phòng khi content là None, ví dụ khi mô hình reasoning tiêu hết trần token vào phần suy nghĩ ẩn. Thử đổi messages=messages thành messages=[messages[0], messages[-1]] (chỉ gửi system và câu hỏi mới): ở lượt hai, mô hình không còn biết “ví dụ” về cái gì.
Trích JSON, có retry và bắt lỗi
Phần tiêu đề “Trích JSON, có retry và bắt lỗi”Ô cuối gộp mọi thứ cho một việc thật: đọc một đánh giá của khách và trả về JSON gồm cảm xúc và các khía cạnh được nhắc tới. temperature=0 vì đây là trích xuất. Client đặt max_retries=4 và timeout=30. System prompt có chữ “JSON”, như JSON mode đòi hỏi.
Chạy trên máy bạn
Trích JSON từ một đánh giá
- Cần cài
pip install openai- Biến môi trường
OPENAI_BASE_URL(tùy chọn),OPENAI_API_KEY,LLM_MODEL
# Trích thông tin từ một đánh giá thành JSON, có thử lại và bắt lỗi.import jsonimport os
import openaifrom openai import OpenAI
client = OpenAI( base_url=os.environ.get("OPENAI_BASE_URL"), # bỏ trống: dùng OpenAI api_key=os.environ["OPENAI_API_KEY"], max_retries=4, # SDK tự thử lại lỗi tạm thời, chờ lâu dần (mặc định 2) timeout=30, # giây chờ tối đa cho mỗi lần gọi (mặc định 600))MODEL = os.environ["LLM_MODEL"]
SYSTEM = ( "Bạn trích thông tin từ đánh giá của khách. Chỉ trả về một đối tượng " 'JSON có hai khóa: "cam_xuc" (một trong "tích cực", "tiêu cực", ' '"lẫn lộn") và "khia_canh" (danh sách các khía cạnh được nhắc tới).')review = "Quán đẹp, nhân viên dễ thương, nhưng đồ uống hơi đắt."
try: response = client.chat.completions.create( model=MODEL, messages=[ {"role": "system", "content": SYSTEM}, {"role": "user", "content": review}, ], temperature=0, # trích xuất: cần ổn định, không cần sáng tạo response_format={"type": "json_object"}, # JSON mode )except openai.RateLimitError: print("Lỗi 429: vượt rate limit dù đã thử lại. Chờ rồi chạy lại.")except openai.APIStatusError as e: # 400, 401, 404...: lỗi của yêu cầu, thử lại cũng không ích gì print(f"Lỗi {e.status_code}: {e.body}") # thông điệp của máy chủ print("Kiểm tra key, OPENAI_BASE_URL, LLM_MODEL. Lỗi 400: thử xóa " "response_format hoặc temperature.")except openai.APIConnectionError: print("Không kết nối được máy chủ, hoặc quá thời gian chờ.")else: text = response.choices[0].message.content try: data = json.loads(text) # JSON mode vẫn có thể bị cắt cụt except (json.JSONDecodeError, TypeError): # TypeError: text là None print("Không đọc được JSON:", text) else: print(json.dumps(data, ensure_ascii=False, indent=2))Khi mọi thứ ổn, chương trình in một đối tượng JSON có hai khóa cam_xuc và khia_canh. Các nhánh except đi từ cụ thể tới chung: RateLimitError là một loại APIStatusError, nên phải đứng trước. Nhánh APIStatusError in kèm thông điệp của máy chủ (e.body). Thử đặt sai LLM_MODEL để thấy nhánh lỗi 404, hoặc đặt OPENAI_BASE_URL về một cổng không có gì chạy, ví dụ http://127.0.0.1:9/v1: SDK thử lại 4 lần, chờ lâu dần, rồi mới in thông báo không kết nối được. Nhà cung cấp báo lỗi 400 với response_format hay temperature thì xóa dòng đó: prompt đã yêu cầu JSON, và json.loads vẫn kiểm tra kết quả.
Lỗi hay gặp
Phần tiêu đề “Lỗi hay gặp”Kiểm tra nhanh
Phần tiêu đề “Kiểm tra nhanh”Tóm tắt và đọc thêm
Phần tiêu đề “Tóm tắt và đọc thêm”Ý chính:
- Gọi LLM là gửi
modelvà listmessages(vai tròsystem,user,assistant) qua API. API không nhớ gì, nên mỗi lượt gửi lại cả lịch sử, và tổng token vào tăng theo bình phương số lượt. Đọccontent,finish_reason,usagetrong phản hồi. - Mô hình cho logit mỗi token; xác suất là . nhỏ thì nhọn, lớn thì phẳng, thứ tự không đổi. Top-k giữ token, top-p giữ tập nhỏ nhất có tổng xác suất , rồi chuẩn hóa lại và rút mẫu. API kiểu OpenAI có
temperature,top_p, không cótop_k. max_tokenslà trần chi phí, không làm câu ngắn lại.stream=Truecho chữ hiện dần; luồng đứt thì không tự thử lại.- Chỉ thử lại lỗi tạm thời (mạng, 408, 409, 429, ≥ 500), với exponential backoff có jitter: SDK làm sẵn qua
max_retries. Đầu ra JSON luôn đọc bằngjson.loadstrongtry/except. Key luôn để trong biến môi trường.
Đọc thêm:
- OpenAI (2026). Text generation và Error codes. Hướng dẫn chính thức về
messages, vai trò, Responses API, và cách xử lý lỗi 429. - OpenAI (2026). openai-python v3.37.0, README, các mục về xử lý lỗi, retry, timeout và streaming. Tài liệu của đúng phiên bản thư viện dùng trong bài.
- Holtzman, A., Buys, J., Du, L., Forbes, M., Choi, Y. (2020). The Curious Case of Neural Text Degeneration. ICLR 2020, mục 3 và 6. Bài báo đề xuất top-p, so sánh với chọn tham lam, beam search, top-k và nhiệt độ.
- Fan, A., Lewis, M., Dauphin, Y. (2018). Hierarchical Neural Story Generation. ACL 2018. Một trong những công trình đầu dùng lấy mẫu top-k để sinh truyện.
- Hinton, G., Vinyals, O., Dean, J. (2015). Distilling the Knowledge in a Neural Network, mục 2. Softmax có nhiệt độ , dùng cho chưng cất mô hình; cùng công thức với
temperaturekhi lấy mẫu.
Trình duyệt đang chặn lưu dữ liệu. Tiến độ sẽ mất khi bạn tải lại trang.
Nội dung theo giấy phép CC BY-NC-SA 4.0. Mã nguồn theo giấy phép MIT.Giới thiệu