1. Rốt cuộc tác nhân là gì và để làm gì
Tác nhân không phải phần bắt buộc của ChatGPT App: bạn có thể tạo vô số ứng dụng hay mà không dùng LLM‑agents. Tuy nhiên, tôi có ba lý do thuyết phục để kể cho bạn về chúng.
Tác nhân — là cách tuyệt vời để thêm trí tuệ vào backend ứng dụng của bạn. Chọn quà thông minh, phân tích mong muốn dạng văn bản của người dùng. Các kịch bản tìm kiếm, phân tích, xử lý và tổng hợp phức tạp — tất cả đều rất dễ làm với LLM‑agents.
ChatGPT đã phát hành AgentsSDK của mình cho TS và Python. Nó rất tốt. Orchestration tác nhân có sẵn out‑of‑the‑box. Với một bài toán phức tạp sẽ làm việc không chỉ một tác nhân mà cả một đội. Đây là hướng đi rất tiềm năng.
Và còn có mục tiêu học tập. ChatGPT gọi mcp-tools theo đúng cách mà LLM‑agents gọi các tools của chúng. Ngay khi bạn hiểu LLM‑agents vận hành thế nào, bạn sẽ hiểu, ví dụ, cách làm state machine phía mô hình trong ứng dụng. Đồng thời, việc học AgentsSDK cho bạn hình dung về cách ChatGPT SDK sẽ hoạt động trong tương lai.
Vậy bắt đầu nhé.
LLM‑agent là gì
Nếu ChatGPT App là “front” đẹp và tiện của dịch vụ bạn bên trong ChatGPT, còn máy chủ MCP là “động cơ” với công cụ và business logic, thì tác nhân — giống như một điều phối viên thông minh, có thể:
- đọc mục tiêu;
- tự quyết định nên gọi công cụ nào và theo thứ tự nào;
- hỏi thêm dữ liệu khi cần;
- lặp lại bước nếu có lỗi;
- đi đến kết quả cuối cùng rõ ràng.
Theo cách diễn đạt gần với tài liệu chính thức của Agents SDK, tác nhân — là một chương trình. Có quyền truy cập LLM và bộ công cụ, nó có thể tự lập kế hoạch các bước để đạt mục tiêu và thực thi chúng qua các tool‑call.
Nếu liên hệ với những gì bạn đã có:
- Trong ChatGPT App thông thường, mô hình ChatGPT điều phối việc gọi các MCP‑tools của bạn trực tiếp.
- LLM‑agent trên backend cũng có mô tả nhiệm vụ, bộ công cụ và tự quyết định sẽ gọi tools nào, làm bao nhiêu bước, khi nào dừng và trả về kết quả nào.
Trong ngữ cảnh GiftGenius của chúng ta, điều này có thể trông như sau:
- Ứng dụng không có tác nhân: mô hình gọi trực tiếp searchGifts, rồi filterByBudget, rồi getDetails — mỗi lần lại suy nghĩ lại từ đầu;
- Ứng dụng có tác nhân: ChatGPT gọi mcp‑tool, còn backend giao nhiệm vụ cho tác nhân: “Tìm top‑5 món quà cho hồ sơ này”. Tác nhân thực hiện vài bước: thu thập thông tin bổ sung, gọi các công cụ tìm kiếm khác nhau, lọc, sắp xếp, dựng các thẻ cuối cùng và trả về câu trả lời đã được cấu trúc sẵn.
ChatGPT và LLM‑agent trên backend — giống giám đốc công ty và nhân viên. ChatGPT có nhiều tự do hơn: nó giao tiếp với người dùng và quyết định những nhiệm vụ mang tính chiến lược nào cần khởi động (gọi mcp tools). LLM‑agent thì chỉ làm việc ở backend, không tương tác với người dùng, nhưng cũng “suy nghĩ” và có thể gọi tool của riêng nó. Kiểu như một ChatGPT bản tối giản.
2. Tác nhân gồm những gì: LLM, chỉ dẫn, công cụ và trạng thái
Thuận tiện nhất là nghĩ về tác nhân theo nhiều lớp.
Thứ nhất, dưới nắp capô vẫn là LLM. Có thể là GPT‑5.1 hoặc mô hình khác mà Agents SDK dùng. Nó sinh văn bản, lên kế hoạch bước, chọn công cụ — nói ngắn gọn là “suy nghĩ”, nhưng trong bối cảnh orchestration của bạn.
Thứ hai, phía trên mô hình là chỉ dẫn. Đây là system prompt của tác nhân, thiết lập vai trò, ranh giới, phong cách, cách dùng công cụ. Bạn từng làm điều tương tự cho ChatGPT App, nhưng giờ điều đó áp cho một tác nhân riêng.
Thứ ba, có bộ công cụ của tác nhân. Đó có thể là:
- hàm trên TypeScript (function calling cổ điển);
- các công cụ HTTP/REST;
- wrapper bao quanh MCP‑tools của bạn để tác nhân có thể truy cập cùng backend như ChatGPT App;
- các công cụ “hosted” tích hợp của chính OpenAI (ví dụ web‑search, nếu bạn bật).
Và cuối cùng là các quy tắc làm việc với trạng thái và bước: trạng thái phiên (session state) được lưu ra sao, kết quả trung gian được lưu thế nào, vòng lặp bị giới hạn thế nào. Sâu hơn về chủ đề này chúng ta sẽ nói ở bài tiếp theo về bộ nhớ và trạng thái, nhưng ngay bây giờ hữu ích là ghi nhớ rằng tác nhân không phải “truy vấn dùng một lần”, mà là một tiến trình có thể dài với việc lưu tiến độ.
Nếu nhìn dưới góc độ lập trình viên TypeScript, trong đầu sẽ hiện ra một đối tượng kiểu như sau (pseudocode, gần với Agents SDK TS):
const giftAgent = new Agent({
model: "gpt-5.1",
systemPrompt: giftAgentPrompt,
tools: { searchGifts, filterGifts, checkoutDraft },
// cài đặt bộ nhớ, giới hạn số bước, v.v.
});
Hiện chưa đi sâu vào API chính xác, điều quan trọng là hình dung: mô hình, chỉ dẫn, công cụ và cài đặt hành vi ở cùng một nơi.
3. Vai trò thông điệp: system / user / assistant / tool trong thế giới tác nhân
Bạn đã quen với các vai trò kinh điển system, user, assistant và tool từ Chat Completions. Trong Agents SDK chúng được giữ nguyên, nhưng mang ý nghĩa ứng dụng hơn.
Vai trò system đặt ra bản thân nhân cách và sứ mệnh của tác nhân. Ví dụ, với GiftGenius‑agent có thể là: “Bạn là tác nhân gợi ý quà tặng. Nhiệm vụ của bạn là trong số bước tối thiểu chọn 3–7 phương án quà phù hợp dựa trên hồ sơ người nhận và ngân sách, sau đó chuẩn bị JSON có cấu trúc cho widget”. Tại đây bạn cũng nêu các giới hạn: không được làm gì (ví dụ không thực hiện mua hàng thật nếu chưa có bước riêng) và phải làm việc với công cụ thế nào.
Vai trò user trong bối cảnh tác nhân — không nhất thiết là “con người sống”. Thường đó là “nhiệm vụ” dành cho tác nhân: mục tiêu do App của bạn, dịch vụ hay tác nhân khác đưa ra. Ví dụ, ChatGPT App có thể gọi tác nhân với thông điệp user: “Hãy chọn 5 ý tưởng quà cho đồng nghiệp là lập trình viên, ngân sách $50, dịp — sinh nhật”.
Vai trò assistant — là những gì chính mô hình “nói” bên trong tác nhân. Ở đây có thể là suy luận trung gian, kế hoạch, cũng như câu trả lời cuối. Nhiệm vụ của bạn là cấu hình system prompt sao cho các thông điệp này hữu ích và được log khi cần.
Vai trò tool (hoặc tương đương trong SDK cụ thể) mô tả kết quả gọi công cụ: “qua MCP tìm thấy 50 sản phẩm”, “API trả lỗi timeout”, “DB trả về hồ sơ người dùng”. Những thông điệp này cùng với thông điệp assistant tạo thành lịch sử vòng lặp run của tác nhân.
Tiện nhất là gói gọn trong một bảng nhỏ:
| Vai trò | Ai nói | Ví dụ trong ngữ cảnh GiftGenius |
|---|---|---|
|
Bạn (với tư cách là lập trình viên tác nhân) | “Bạn là tác nhân gợi ý quà tặng…” |
|
Lời gọi bên ngoài (App, tác nhân khác) | “Hãy chọn 5 món quà trong mức $50…” |
|
Mô hình bên trong tác nhân | “Kế hoạch: 1) hỏi chi tiết…” |
|
Kết quả của công cụ được gọi | “searchGifts trả về 20 phương án…” |
Cấu trúc này quan trọng vì chính trên cơ sở đó hình thành vòng lặp run — nhân vật chính của bài hôm nay.
4. Cách LLM gọi hàm trên backend của bạn
Khi quen với chế độ “hỏi–đáp”, có vẻ như LLM hoạt động theo sơ đồ đơn giản: nhận văn bản → mô hình trả lời bằng văn bản. Thực ra bên trong phức tạp hơn một chút và đó chính là lý do function calling hoạt động.
Mô hình không nhận một câu hỏi, mà nhận danh sách thông điệp — lịch sử hội thoại. Ở đó đã có tất cả phát ngôn trước: chỉ dẫn hệ thống (“bạn là ai và có thể/không thể gì”), thông điệp của bạn, câu trả lời trước của mô hình, kết quả công cụ. Ở mỗi bước mô hình nhìn vào toàn bộ dòng như log chat, và quyết định: “Cần thêm thông điệp nào vào cuối?”.
Đây là ý then chốt: LLM luôn thực hiện một bước — bổ sung thông điệp kế tiếp vào cuối lịch sử. Nó không “thay đổi quá khứ” hay sửa thông điệp cũ, mà chỉ nối tiếp danh sách. Bạn viết câu hỏi, mô hình trả lời. Bạn thêm câu hỏi thứ hai, mô hình lại trả lời nhưng có xét đến toàn bộ lịch sử hội thoại.
Function calling cũng dựa trên nguyên lý đó. Thay vì “chạy hàm” trực tiếp, mô hình làm như sau:
- nhìn thấy danh sách các công cụ/tools khả dụng và mô tả của chúng cùng với lịch sử hội thoại;
- quyết định: “Bây giờ hợp lý hơn là không chỉ trả lời bằng văn bản, mà trước hết gọi công cụ này”;
- và như thông điệp tiếp theo bổ sung vào lịch sử không phải câu trả lời văn bản thông thường, mà là thông điệp đặc biệt có định dạng “tôi muốn gọi tool này với các tham số như sau”.
Sau đó không còn là mô hình, mà là backend của bạn đọc thông điệp mới này ở cuối lịch sử, hiểu rằng đó là yêu cầu gọi hàm, và gọi công cụ cần thiết. Tiếp đến bổ sung vào lịch sử một thông điệp nữa — với kết quả từ tool, rồi lại gửi danh sách thông điệp đầy đủ cho mô hình. Mô hình lại nhìn toàn bộ dòng và bổ sung bước kế: hoặc thêm một lần gọi nữa, hoặc câu trả lời cuối dễ đọc cho con người.
Tức là:
- với Q&A thông thường: “thông điệp tiếp theo” = câu trả lời văn bản;
- với function calling: “thông điệp tiếp theo” = chỉ dẫn gọi hàm hoặc câu trả lời sau khi dùng hàm.
Không có “lệnh kỳ diệu riêng để gọi hàm” — đó chỉ là một dạng đặc biệt của thông điệp kế tiếp mà mô hình thêm vào cuối chuỗi.
Mô hình không gọi hàm backend của bạn qua public API. Nó chỉ “viết vào chat” rằng muốn gọi một hàm với tham số nào. Còn backend của bạn gọi hàm cục bộ, và trả lời của hàm được thêm vào chat. Và vòng lặp lại bắt đầu.
5. Vòng lặp run của tác nhân: nó “suy nghĩ” từng bước thế nào
Thực chất LLM‑agent — là một đối tượng/thuật toán trên máy chủ của bạn, kích hoạt vòng lặp run của tác nhân — vòng lặp mở rộng “câu hỏi → suy nghĩ → có thể thực hiện hành động → lại suy nghĩ → … → câu trả lời cuối”. Trong tài liệu OpenAI đôi khi còn gọi agent loop hoặc mẫu ReAct (Reason + Act + Observe).
Ở cấp độ khái niệm, một lần run của tác nhân trông như sau:
- Tác nhân nhận đầu vào: chỉ dẫn hệ thống, nhiệm vụ (thông điệp user), có thể có — trạng thái hiện tại.
- Mô hình tạo ra một bước: hoặc câu trả lời văn bản, hoặc kế hoạch và quyết định gọi một hay nhiều công cụ.
- Nếu mô hình chọn tool‑call, tác nhân gọi công cụ tương ứng trong code (có thể là hàm cục bộ, MCP‑tool, yêu cầu HTTP, truy cập DB, v.v.).
- Kết quả của công cụ được thêm vào lịch sử như thông điệp tool.
- Vòng lặp quay lại mô hình với ngữ cảnh mới. Mô hình quyết định làm gì tiếp: tiếp tục lập kế hoạch, gọi công cụ khác hoặc kết thúc nhiệm vụ với câu trả lời cuối.
- Khi mô hình kết thúc run một cách tường minh hoặc theo điều kiện dừng, tác nhân trả về kết quả cuối cho phía gọi.
Có thể minh họa bằng một sơ đồ nhỏ như sau:
flowchart TD
A[Khởi động run: mục tiêu + system] --> B[Gọi mô hình]
B --> C{Mô hình muốn
trả lời bằng văn bản
hay gọi tool?}
C --> D["Câu trả lời dạng văn bản
(assistant)"]
D --> E{Nhiệm vụ đã hoàn thành?}
E -->|Có| F[Kết quả cuối cùng]
E -->|Chưa| B
C --> G["Tool-call
(mô tả lời gọi)"]
G --> H[Gọi hàm / MCP / HTTP]
H --> I["Kết quả tool
(tool message)"]
I --> B
Nếu chuyển thành TypeScript pseudocode giản lược (khá xa API thực, nhưng đúng về logic), sẽ giống như:
async function runAgent(goal: string) {
let context = buildInitialContext(goal);
while (!isFinished(context)) {
const decision = await callLLM(context); // bước của tác nhân
if (decision.type === "tool_call") { // gọi hàm?
const toolResult = await callTool(decision.tool, decision.args); // gọi hàm cục bộ
context = appendToolResult(context, toolResult); // thêm kết quả vào cuối danh sách
} else {
context = appendAssistantMessage(context, decision.message);
}
enforceLimits(context); // giới hạn số bước/thời gian/vòng lặp
}
return extractFinalResult(context);
}
Agents SDK gánh phần lớn việc vặt: lưu lịch sử, đóng gói tool‑calls, logic lặp lại, v.v. Bạn chỉ cần cấu hình và hiện thực các công cụ.
Run vs step
Quan trọng là phân biệt hai khái niệm:
- run — một lần chạy tác nhân cho một mục tiêu nào đó: “chọn quà cho trường hợp này”;
- step — một bước trong vòng lặp run: một lần gọi mô hình, có thể dẫn đến câu trả lời văn bản hoặc tool‑call.
Trong giám sát (monitoring) bạn sẽ thấy nhiều bước bên trong một run, còn giới hạn về an toàn và chi phí thường đặt hoặc “theo run”, hoặc “theo bước”.
Giờ khi đã rõ tác nhân sống thế nào trong một run và bước đi theo vòng lặp run, hãy xem nơi nào thực sự cần tất cả những thứ này, và nơi nào chỉ cần tools đơn giản là đủ.
5. Ở đâu trong GiftGenius cần tác nhân và ở đâu là thừa
Trước khi lao vào viết tác nhân cho mọi thứ, hãy tự hỏi một cách trung thực: “Nó có thực sự cần ở đây không?”.
Kịch bản tốt cho tác nhân — là nhiệm vụ nhiều bước có nhánh rẽ, lặp lại và logic mà bất tiện nếu chỉ giữ trong prompt.
Trong GiftGenius, một nhiệm vụ như vậy có thể là “trình hướng dẫn chọn quà thông minh”, nơi:
- biết hỏi bổ sung các chi tiết quan trọng (giới tính người nhận, sở thích, mức độ thân thiết);
- có thể truy cập nhiều nguồn hàng (các vendor khác nhau qua MCP‑tools);
- lọc và xếp hạng kết quả;
- khi nguồn lỗi thì thử lại hoặc đi theo đường dự phòng;
- trả về không chỉ danh sách văn bản, mà là danh sách có cấu trúc các ứng viên với giải thích và liên kết đến SKU trong product feed.
Ở đây tác nhân thực sự hữu ích như “nhạc trưởng”, đặc biệt nếu sau đó bạn muốn bổ sung kịch bản voice/Realtime hoặc thương mại phức tạp (ACP).
Còn với lời gọi đơn giản getGiftDetails(giftId) thì không cần tác nhân: MCP‑tool thông thường được ChatGPT gọi trực tiếp đã đủ. Tương tự với các kịch bản “một bước” đơn giản như “mô tả món quà này dựa trên văn bản của thẻ sản phẩm”.
Cách tiếp cận hợp lý chung: nếu có thể mô tả kịch bản như “một tool bình thường” — nhiều khả năng không cần tác nhân. Còn nếu bạn bắt đầu mô tả rõ ràng một workflow nhiều bước với kiểm tra và thử lại, rất có thể tác nhân sẽ làm bạn hài lòng.
6. Tính xác định: làm sao để hành vi của tác nhân có thể dự đoán
Tính xác định trong thế giới LLM‑agents — khá hóc. Về lý thuyết, với cùng đầu vào và cài đặt, bạn muốn có cùng kế hoạch hành động và cùng chuỗi tool‑calls. Trên thực tế, mô hình vẫn là ngẫu nhiên, nhưng bạn có vài đòn bẩy để điều khiển tính dự đoán.
Thứ nhất, kinh điển: nhiệt độ và các tham số sinh khác. Nhiệt độ càng thấp, càng ít sáng tạo và mô hình càng “ngoan”. Với tác nhân gợi ý quà, nhiều khả năng bạn muốn có mức tự do không bằng 0 nhưng cũng không quá cao, nếu không mô hình sẽ mỗi sáng nghĩ ra cách mới để gọi cùng một công cụ.
Thứ hai, chỉ dẫn hệ thống rõ ràng. Nếu bạn mô tả mơ hồ như “bạn có thể gọi các công cụ khác nhau và làm gì cũng được”, đừng ngạc nhiên khi tác nhân lúc thì nhảy qua API, lúc lại cố trả lời “từ trên trời rơi xuống”. Tốt hơn nhiều là mô tả tường minh: khi nào nên gọi công cụ, tham số nào hợp lệ, diễn giải lỗi thế nào và trong trường hợp nào cần kết thúc nhiệm vụ.
Ví dụ, system prompt cho GiftGenius‑agent có thể bao gồm đoạn:
Nếu bạn chưa có hồ sơ đầy đủ của người nhận (tuổi, giới tính, dịp, ngân sách ước lượng),
hãy hỏi làm rõ qua kênh hướng tới người dùng và đợi câu trả lời.
Chỉ sau đó mới gọi công cụ search_gifts với hồ sơ đã điền.
Đừng bịa sản phẩm, luôn dựa vào kết quả từ công cụ.
Những chỉ dẫn như vậy giảm tính biến thiên của quyết định và làm hành vi nhất quán hơn.
Thứ ba, thiết kế bản thân công cụ. Nếu bạn có ba công cụ “gần như cùng một việc” là tìm quà, mô hình tất yếu lúc chọn cái này, lúc cái khác. Tốt hơn là thiết kế công cụ với phạm vi trách nhiệm rõ ràng, không trùng lặp và ghi rõ điều đó trong mô tả.
Cuối cùng, có thể dùng guardrails — các quy tắc và schema kiểm tra hành động của tác nhân và kết quả từ mô hình. Trong Agents SDK có hỗ trợ sẵn kiểm tra và giới hạn, bao gồm cấu trúc dữ liệu đầu ra. Nếu mô hình cố sinh thứ gì đó không theo schema, bạn có thể nhẹ nhàng chỉnh hoặc thậm chí lặp lại bước.
Ví dụ nhỏ: cố định định dạng kết quả
Giả sử bạn cần tác nhân luôn trả về JSON có trường gifts, bên trong là các đối tượng có id, title và score. Bạn có thể:
- mô tả schema này ở cấp tác nhân;
- chỉ định rằng đầu ra cuối phải tuân thủ nó;
- khi vi phạm — lặp lại bước hoặc trả lỗi an toàn.
Pseudocode:
const giftResultSchema = z.object({
gifts: z.array(z.object({
id: z.string(),
title: z.string(),
score: z.number().min(0).max(1),
}))
});
// Trong cấu hình của tác nhân
const agent = new Agent({
/* ... */
outputSchema: giftResultSchema,
});
Khi mô hình cố trả về thứ gì đó kỳ lạ, runner sẽ báo lỗi validate, và bạn có thể hoặc yêu cầu mô hình thử lại, hoặc log sự cố.
7. Tính idempotent: vì sao tác nhân có thể gọi API của bạn hai lần
Nếu tính xác định nói về “kế hoạch giống nhau khi cùng đầu vào”, thì tính idempotent — nói về an toàn khi lặp lại. Trong bối cảnh tác nhân, điều này cực kỳ quan trọng vì hai lý do.
Thứ nhất, bạn có thêm một tầng retry: không chỉ HTTP clients và load balancers, mà bản thân tác nhân có thể quyết định lặp lại lời gọi công cụ nếu gặp lỗi hoặc kết quả chưa đầy đủ. Thứ hai, trong kịch bản production thực tế có thêm webhooks, hàng đợi, kênh streaming — và bạn có thể vô tình xử lý cùng một bước logic nhiều lần.
Bạn đã bàn về idempotency ở cấp MCP‑tools: không thực hiện thanh toán đôi, không tạo cùng một đơn hai lần, dùng idempotency keys trong yêu cầu. Bây giờ là tương tự, nhưng nhân lên với bản chất nhiều bước của tác nhân.
Hãy tưởng tượng GiftGenius có công cụ create_checkout_session, dựa vào danh sách quà đã chọn để tạo draft‑checkout trong ACP/Stripe. Nếu tác nhân quyết định lặp lại lời gọi này do lỗi mạng, bạn chắc chắn không muốn có hai đơn riêng và hai lần trừ tiền.
Cần phải:
- nghĩ ra idempotency key bên ngoài cho mỗi hành động logic (ví dụ, runId + stepIndex hoặc checkoutDraftId được sinh rõ ràng);
- truyền nó vào backend/ACP‑endpoint của bạn;
- phía backend kiểm tra xem bạn đã xử lý key này chưa, và trả về kết quả đã lưu thay vì thực thi lần nữa.
Ví dụ giả trên TypeScript:
async function createCheckoutDraft(runId: string, payload: DraftPayload) {
const key = `gift-checkout-${runId}`;
const existing = await findDraftByKey(key);
if (existing) return existing;
const draft = await stripe.checkout.sessions.create({
/* ... */,
idempotencyKey: key, // hoặc lớp idempotency của riêng bạn ở phía trên
});
await saveDraftWithKey(key, draft);
return draft;
}
Giờ đây, ngay cả khi tác nhân vì lý do nào đó gọi công cụ này hai lần với cùng runId, code của bạn vẫn idempotent: cùng một bước logic → cùng một kết quả thực tế.
“Kiểm tra trước rồi mới hành động”
Mẫu idempotency phổ biến thứ hai — kiểm tra trạng thái trước rồi mới hành động. Ví dụ, trước khi tạo đơn, hãy kiểm tra xem đã tồn tại đơn với clientReferenceId đó hoặc cùng bộ tham số hay chưa. Điều này đặc biệt tiện trong các workflow dài, nơi tác nhân có thể “quên” rằng mình đã làm gì đó ở bước trước.
Safe‑mode/Fake‑mode
Trong giai đoạn phát triển, hữu ích khi có “chế độ an toàn” cho các công cụ nguy hiểm: thay vì hành động thật, chúng chỉ log điều sẽ làm và trả về kết quả giả. Với tác nhân, đây là cách tiện lợi để chạy vòng lặp run trong môi trường thật mà không mạo hiểm tiền bạc hay dữ liệu.
8. Thực hành nhỏ: mô tả tác nhân GiftGenius bằng ngôn ngữ tự nhiên
Chúng ta đã nói về vòng lặp run, tính xác định và idempotency của công cụ. Hãy tạm rời code một phút và xem tất cả ghép lại thành kịch bản sống động thế nào.
Giờ hữu ích là làm một bài tập nhỏ trên giấy (hoặc trong đầu), không code.
Hãy tưởng tượng bạn mô tả một tác nhân đơn giản:
-
: bạn là trợ lý gợi ý quà; luôn làm rõ các chi tiết quan trọng, không bịa sản phẩm, chỉ dùng kết quả từ công cụ.system -
: tôi muốn quà cho đồng nghiệp trong mức $50.user
Hãy mô tả bằng lời các bước tác nhân đó nên làm.
Kịch bản điển hình có thể như sau.
- Trước tiên tác nhân kiểm tra thông tin đã đủ chưa. Nếu chưa, nó đặt câu hỏi làm rõ: đồng nghiệp làm khoảng lĩnh vực nào (designer, developer, manager), có điều cấm kỵ gì không (đồ uống có cồn, quà hài hước), có hạn chế giao hàng nào không. Câu trả lời được đưa vào session state hoặc tham số gọi công cụ.
- Sau đó tác nhân gọi công cụ search_gifts với hồ sơ đã điền: “đồng nghiệp là lập trình viên, ngân sách 50, danh mục — thiết bị và văn phòng”. Công cụ trả danh sách ứng viên kèm giá, danh mục và ID sản phẩm.
- Tiếp theo tác nhân có thể gọi công cụ bổ sung filter_gifts_by_constraints nếu phát hiện một phần sản phẩm không thể giao tới vùng cần thiết, hoặc lọc ngay trong prompt của mình. Sau đó nó sắp xếp theo mức liên quan và chi phí, có thể thêm chú thích (“phù hợp nếu đồng nghiệp thích cà phê”, “tốt cho làm việc từ xa”).
- Cuối cùng, tác nhân chuẩn bị câu trả lời có cấu trúc cho ChatGPT App: danh sách 5–7 món quà với mô tả ngắn, gợi ý sử dụng và liên kết đến Checkout (hoặc bước tiếp theo — tạo draft‑checkout).
Ở đâu cần tool‑calls? Rõ ràng là trong tìm kiếm và lọc sản phẩm, kiểm tra khả dụng và tạo draft‑checkout. Những bước nào phải idempotent? Trước hết là mọi thao tác liên quan tới đơn hàng và tiền — tạo draft‑checkout, có thể cả ghi lịch sử vào DB.
9. Lỗi thường gặp khi bước đầu làm việc với tác nhân
Lỗi số 1: coi tác nhân là “một ChatGPT thứ hai không giới hạn”.
Đôi khi ta muốn đưa thêm cho mô hình một prompt và gọi đó là “tác nhân”. Kết quả là một thứ sinh nhiều văn bản, gọi công cụ một cách hỗn loạn và khó kiểm soát. Để tránh điều này, quan trọng là mô tả rõ vai trò của tác nhân trong system, giới hạn danh sách công cụ và nghĩ về nó như một nhạc trưởng có sứ mệnh cụ thể, chứ không phải “vũ trụ sinh văn bản thứ hai”.
Lỗi số 2: công cụ không idempotent.
Lập trình viên thường chuyển các HTTP handler cũ sang dưới tác nhân “nguyên xi”, không tính đến việc runner giờ có thể tự động lặp lại lời gọi. Với thanh toán và đơn hàng, điều này có thể gây hậu quả rất khó chịu. Cách đúng — ngay từ đầu thiết kế công cụ sao cho gọi lại với cùng key logic không dẫn tới hành động lặp.
Lỗi số 3: cài đặt mô hình quá sáng tạo.
Nhiệt độ cao rất hợp để bịa lời chúc hay thơ, nhưng với tác nhân cần điều phối quy trình nhiều bước một cách tin cậy, đó là con đường dẫn tới hành vi khó lường: mô hình sẽ mỗi lần chọn công cụ khác nhau, tạo kế hoạch khác nhau và đôi khi quên luôn rằng nó có tools. Hãy coi tác nhân là thực thể “phục vụ” và giữ chúng trong chế độ nghiêm ngặt hơn.
Lỗi số 4: công cụ “cho mọi trường hợp”.
Đôi khi muốn làm một tool vạn năng kiểu execute_any_sql hoặc do_anything_with_orders, rồi đưa cho tác nhân. Kết hợp với tính sáng tạo của LLM, đó gần như là mối đe dọa bảo mật đảm bảo xảy ra. Tốt hơn nhiều là có vài công cụ chuyên biệt với hợp đồng và quyền hạn rõ ràng, hơn là một công cụ “toàn năng” với toàn quyền.
Lỗi số 5: không có tiêu chí dừng run rõ ràng.
Nếu không nói cho tác nhân biết khi nào cần dừng, nó có thể rơi vào vòng lặp vô tận hoặc bán vô tận: lại kiểm tra kết quả, lại hỏi người dùng, lại thử gọi công cụ với cùng lỗi. Thường điều này chỉ lộ ra dưới tải, khi một phụ thuộc không ổn định. Cách đúng — đặt giới hạn số bước, thời gian run và số lần lặp lại với cùng lỗi, cũng như mô tả trong system rằng tác nhân phải “thành thật bỏ cuộc” khi đã cạn phương án hợp lý.
Lỗi số 6: nhét mọi thứ vào trạng thái tác nhân.
Vì Agents SDK đơn giản hóa việc làm việc với session state, có cám dỗ nhét vào đó mọi thứ: tài liệu lớn, log thô, dữ liệu nhạy cảm. Điều này làm phình to ngữ cảnh, tăng chi phí và tạo rủi ro bảo mật. Trạng thái tác nhân chỉ nên lưu thứ thực sự cần cho việc tiếp tục; còn lại — ở DB, log và các lớp khác, với lưu ý về quyền riêng tư.
Lỗi số 7: cố dùng tác nhân ở nơi tool MCP đơn giản là đủ.
Đôi khi lập trình viên bắt đầu với tác nhân, ngay cả khi nhiệm vụ chỉ là gọi một hàm và trả kết quả. Điều này thêm độ phức tạp nơi không cần: xuất hiện vòng lặp run, trạng thái, log bổ sung và điểm lỗi tiềm ẩn. Nếu kịch bản gói gọn trong một tool‑call mà không cần workflow phức tạp, tốt hơn cứ để vậy và chỉ dùng tác nhân khi thật sự xuất hiện tính nhiều bước.
GO TO FULL VERSION