CodeGym /Các khóa học /ChatGPT Apps /Liên kết system‑prompt

Liên kết system‑prompt, mô tả công cụ và follow‑ups: chống ảo giác

ChatGPT Apps
Mức độ , Bài học
Có sẵn

1. Giới thiệu

Đôi khi trong các khóa học về prompt‑engineering, người ta đưa ra một câu thần chú:

«Đừng bịa ra dữ kiện và hãy nói “tôi không biết” nếu không có thông tin.»

Rất tiếc (hoặc cũng có thể là may mắn), đối với ChatGPT App thì điều đó không phải là “viên đạn bạc”. Lý do đơn giản: mô hình không chỉ nhìn thấy system‑prompt của bạn, mà còn:

  • danh sách các công cụ cùng mô tả và schema của chúng;
  • kết quả của các công cụ đó;
  • lịch sử hội thoại, bao gồm các follow‑up trước đó.

Nếu ba lớp này — system‑prompt, mô tả tools, mẫu follow‑up — mâu thuẫn với nhau hoặc chỉ đơn giản là “bỏ trống”, mô hình sẽ hành xử như một junior điển hình: chăm chỉ nhưng dễ bịa.

Trong khóa học, chúng ta dùng ý tưởng “bảo vệ ba lớp chống ảo giác” (defense in depth):

  • cấp 1 — quy tắc toàn cục trong system‑prompt;
  • cấp 2 — ràng buộc cục bộ trong định nghĩa từng công cụ;
  • cấp 3 — xử lý lỗi, kết quả trống và follow‑ups.

Trong bài giảng này, chúng ta sẽ ghép cả ba lớp thành một hợp đồng nhất quán cho ứng dụng học tập GiftGenius.

Insight

Trong kiến trúc ChatGPT Apps mới, trường system‑prompt đã biến mất — về mặt hình thức nó không còn nữa. Nhưng điều đó không có nghĩa bạn phải từ bỏ các hướng dẫn toàn cục cho mô hình. Có một mẹo: đối với ChatGPT, mô tả công cụ cũng là văn bản prompt giống như system truyền thống. Chính qua đó bạn có thể “trả lại” phần “firmware” tư duy đầy đủ cho ứng dụng.

Hãy tạo một công cụ nội bộ, ví dụ about hoặc about_app. Nó không nhằm được gọi thường xuyên, nhưng description của nó được mô hình đọc ngang hàng với các công cụ khác. Do vậy, bạn có thể nhúng toàn bộ system‑prompt vào phần mô tả. Tốt nhất đặt nó sau mô tả kỹ thuật ngắn của chính công cụ. Kết quả là ngay khi khởi động, mô hình nhận system‑prompt của bạn “nguyên bản” — và áp dụng cho toàn bộ hội thoại và lời gọi tools tiếp theo.

Để dễ bảo trì, nên thêm phiên bản rõ ràng ở đầu system‑prompt, ví dụ SYSTEM_PROMPT_VERSION: v3. Điều này giúp tìm ra lý do mô hình hành xử khác đi: bạn sẽ thấy ngay nó đang chạy trên bộ hướng dẫn nào. Nếu trong production xuất hiện hành vi lạ, bạn dễ hiểu lỗi thuộc về phiên bản prompt cũ hay mới.

Ví dụ:

tool description 

### Global assistant behavior for the entire GiftGenius App (ver 3.01)
*(system-level guidelines, not user-facing text)*

system prompt text

2. Những kiểu ảo giác ChatGPT App có thể mắc phải (trong ví dụ danh mục quà tặng)

Muốn biết cách chữa, cần gọi đúng tên bệnh. Trong ngữ cảnh danh mục (quà tặng, hàng hóa, gói dịch vụ), thường gặp nhất là các kiểu ảo giác sau.

Thứ nhất, bịa ra các mục trong danh mục. Người dùng yêu cầu “phiếu quà tặng dạng số cho gói thuê bao một năm của một dịch vụ cụ thể” hoặc một món quà rất lạ, không có trong feed của bạn, và mô hình, muốn hữu ích, hồ hởi bịa ra “Super Space Flight 3000 — chuyến bay vào vũ trụ”, thứ chưa từng tồn tại trong cơ sở GiftGenius.

Thứ hai, bịa ra thuộc tính. Món quà có trong danh mục, nhưng mô hình “tô vẽ” hiện thực: thay đổi giá, loại quà (số vs vật lý), khả năng giao đến quốc gia của người dùng hoặc thời hạn hiệu lực của voucher, vì “hợp lý hơn” hay “nghe hay hơn”.

Thứ ba, bịa ra hành động. Mô hình viết “tôi đã mua món quà này và gửi mã về e‑mail của bạn, thẻ đã bị trừ $49”, dù backend GiftGenius của bạn thậm chí chưa hề mua gì và cũng không khởi chạy luồng ACP/Stripe.

Cuối cùng là trường hợp kết hợp: công cụ trả về danh sách trống (không có quà phù hợp với bộ lọc và ngân sách), còn mô hình, để tránh làm người dùng thất vọng, bịa ra vài “phương án ví dụ” và không giải thích rằng trong danh mục không có chúng.

Mục tiêu của chúng ta — khiến mô hình trong những tình huống này:

  • thành thật thừa nhận rằng không có kết quả trùng khớp chính xác trong danh mục;
  • không bịa quà mới và không thay đổi giá trị các trường;
  • giải thích cho người dùng điều gì đã xảy ra và đề xuất bước tiếp theo rõ ràng.

Và làm điều này không bằng các “bùa chú” rải rác, mà qua hợp đồng liên kết system‑prompttoolsfollow‑ups.

3. Lớp 1: tăng cường system‑prompt chống ảo giác

Để chặn việc bịa ra quà, thuộc tính và hành động từ phần trước, trước tiên tăng cường lớp trên cùng — system‑prompt: đặt cho mô hình “triết lý” ứng xử chung trong danh mục.

Trong bài đầu của mô‑đun, bạn đã viết system‑prompt cơ bản kiểu: “Bạn là GiftGenius, giúp chọn quà…” và cố định ranh giới trách nhiệm của trợ lý cũng như cách làm việc với công cụ.

Giờ hãy thêm vào đó các quy tắc chống ảo giác rõ ràng.

Lý do như sau: system‑prompt đặt triết lý chung của hành vi. Nó không biết chi tiết của từng công cụ, nhưng có thể:

  • cấm bịa quà/giá/tồn kho ngoài danh mục;
  • quy định hành vi khi công cụ trả về kết quả trống hoặc lỗi;
  • yêu cầu phân biệt rõ giữa dữ liệu từ danh mục quà tặng và mọi “kiến thức chung của mô hình”.

Ví dụ đoạn system‑prompt (hằng TypeScript trong dự án Next.js của chúng ta, ví dụ config/systemPrompt.ts):

// config/systemPrompt.ts
export const SYSTEM_PROMPT = `
# Vai trò
Bạn là GiftGenius, trợ lý chọn quà
dựa trên danh mục quà tặng của ứng dụng chúng tôi.

# Dữ liệu và ràng buộc
- Không bịa ra các món quà không có trong danh mục hoặc trong kết quả công cụ.
- Không bịa giá, tình trạng, loại quà (số/vật lý),
  khu vực giao hàng hoặc các thuộc tính khác.
- Nếu công cụ không trả về dữ liệu cần thiết hoặc xảy ra lỗi,
  hãy nói thẳng về điều đó và đừng đoán mò giá trị.

# Làm việc với công cụ
- Luôn dùng các công cụ của danh mục quà cho mọi dữ liệu thực tế:
  danh sách quà, giá, loại, mức độ sẵn có, SKU.
- Nếu công cụ trả về kết quả trống, hãy nói rằng theo điều kiện hiện tại
  không có quà phù hợp, và đề xuất nới lỏng bộ lọc
  (điều chỉnh ngân sách, danh mục, loại quà).
`;

Có vài điểm quan trọng ở đây.

Thứ nhất, ta tách bạch “kiến thức chung của mô hình” và “dữ liệu của ứng dụng”. Mô hình vẫn có thể giải thích quà số khác quà vật lý ở chỗ nào hoặc những dịp tặng quà thường gặp, nhưng mọi món quà cụ thể, giá và SKU phải chỉ lấy từ các công cụ của GiftGenius.

Thứ hai, ta mô tả rõ phải làm gì khi có lỗi/kết quả trống: không im lặng, không “sáng tạo”, mà thành thật nói với người dùng rằng không tìm thấy gì và đề xuất đổi tham số.

Thứ ba, tập trung không vào “đừng ảo giác” trừu tượng, mà vào các kiểu hành vi cụ thể gắn với miền của ta (danh mục quà và mua SKU số/vật lý).

Lớp này tự nó đã giúp nhiều, nhưng dễ bị “phủ nhận” bởi mô tả công cụ cẩu thả. Chuyển sang phần mô tả công cụ.

4. Lớp 2: mô tả công cụ (tool descriptions) và schema như phần hợp đồng chính thức

Mô hình quyết định khi nào và cách gọi công cụ của bạn chủ yếu dựa vào:

  • tên công cụ;
  • description của công cụ;
  • inputSchema / outputSchema (JSON Schema mô tả các trường).

Nghĩa là mô tả công cụ (tool description) không phải tài liệu “cho con người”, mà là một phần của prompt, chỉ có điều được chuẩn hóa. Và nhiều ảo giác nảy sinh ngay tại đây.

Hãy hình dung công cụ recommend_gifts, backend hiện thực hoá việc chọn quà từ danh mục GiftGenius.

Một mô tả tệ có thể trông như sau:

// TỆ: quá mơ hồ
const recommendGiftsTool = {
  name: "recommend_gifts",
  description: "Chọn quà cho người dùng",
  inputSchema: {
    type: "object",
    properties: {
      profile: { type: "string" }
    }
  }
};

Hình thức thì đúng, nhưng từ đây mô hình không hiểu:

  • ranh giới của công cụ ở đâu;
  • làm gì nếu không tìm thấy quà;
  • rằng không được bịa quà và giá ngoài danh mục.

Một mô tả tốt làm được vài việc cùng lúc: nêu rõ miền, giải thích khi nào gọi tool và ràng buộc chặt rằng không được bịa kết quả.

Ví dụ (điều chỉnh theo hợp đồng GiftGenius với segments, budget, locale, occasion):

// config/tools.ts
export const recommendGiftsTool = {
  name: "recommend_gifts",
  description: `
Chọn quà trong danh mục GiftGenius.

Hãy dùng công cụ này khi cần lấy danh sách các món quà thực tế
theo các phân đoạn hồ sơ người nhận, ngân sách, locale và dịp tặng.
Công cụ chỉ trả về những món quà thực sự có
trong danh mục GiftGenius.

KHÔNG bịa quà và thuộc tính của chúng ngoài kết quả từ công cụ này.
Nếu công cụ trả về danh sách trống, đừng bịa phương án thay thế,
mà hãy chuyển sang hội thoại: đề xuất thay đổi ngân sách, loại quà,
dịp tặng hoặc các tham số khác.
  `.trim(),
  inputSchema: {
    type: "object",
    properties: {
      segments: {
        type: "array",
        description:
          "Các phân đoạn hồ sơ người nhận, ví dụ ['tech', 'fitness'].",
        items: { type: "string" }
      },
      budget: {
        type: "object",
        description: "Khoảng ngân sách cho món quà.",
        properties: {
          min: {
            type: "number",
            description: "Giá trị tối thiểu, không âm.",
            minimum: 0
          },
          max: {
            type: "number",
            description: "Giá trị tối đa, lớn hơn 0.",
            exclusiveMinimum: 0
          },
          currency: {
            type: "string",
            description: "Mã tiền tệ ba chữ cái, ví dụ 'USD' hoặc 'RUB'.",
            minLength: 3,
            maxLength: 3
          }
        },
        required: ["min", "max", "currency"]
      },
      locale: {
        type: "string",
        description:
          "Locale của người dùng (định dạng ngôn ngữ/khu vực), ví dụ 'ru-RU' hoặc 'en-US'.",
        minLength: 2
      },
      occasion: {
        type: "string",
        description:
          "Dịp tặng quà: ví dụ 'birthday', 'anniversary', 'new_year'."
      }
    },
    required: ["segments", "budget", "locale", "occasion"]
  }
};

Ở đây description làm vài việc hữu ích.

Nó nói rõ công cụ chỉ hoạt động với danh mục quà tặng GiftGenius và mọi món quà/giá cụ thể phải chỉ lấy từ kết quả của nó.

Nó giải thích khi nào dùng công cụ: khi cần danh sách quà cụ thể theo tham số người nhận, chứ không phải lý thuyết chung về quà tặng.

Nó đặt hành vi khi kết quả trống: không bịa, mà chuyển sang hội thoại (điều chúng ta sẽ cố định trong follow‑ups).

Còn inputSchema giúp mô hình trích xuất thực thể từ yêu cầu của người dùng đáng tin cậy hơn: phân đoạn, ngân sách, locale và dịp tặng. Việc chỉ rõ cấu trúc và ràng buộc cho các trường (min, max, currency có độ dài cố định) cũng giảm xác suất kết hợp kỳ lạ và lỗi khi parse.

Bạn có thể thêm mặt còn lại — không chỉ “khi nào gọi”, mà cả khi nào không nên gọi. Ví dụ, nếu yêu cầu mang tính lý thuyết rõ ràng:

description: `
...
Đừng dùng công cụ này nếu người dùng đặt câu hỏi chung
về quà tặng mà không yêu cầu chọn cho một người cụ thể
(ví dụ, "nói chung quà tặng phổ biến cho Năm mới là gì").
Trong những trường hợp như vậy, hãy trả lời trực tiếp trong chat.
`.trim()

Như vậy, bạn đồng bộ mô tả công cụ với các quy tắc trong system‑prompt về yêu cầu “lý thuyết” vs “thực hành”.

5. Lớp 3: follow‑ups như một lớp UX và an toàn

Ngay cả khi system‑prompt và mô tả tools được viết hoàn hảo, cuộc đời không hoàn hảo:

  • backend có thể trả về lỗi;
  • danh mục — trả về danh sách trống;
  • kết quả — mơ hồ hoặc quá nhiều.

Nếu không quy định nói gì sau khi gọi tool, mô hình sẽ ứng biến: có lúc hay, có lúc — lại bịa.

Trong bài 2 bạn đã thấy các hướng dẫn UX cơ bản: cách mô hình thông báo khởi chạy App, cách kết thúc kịch bản và nói gì với người dùng “ở đầu ra”. Giờ hãy thêm các mẫu follow‑up giúp giảm ảo giác.

Những mẫu này thường được mô tả ngay trong system‑prompt ở một khối riêng, ví dụ “Hội thoại sau khi làm việc với công cụ”.

Ví dụ đoạn:

// tiếp nối SYSTEM_PROMPT
export const SYSTEM_PROMPT = `
# ... các phần trước ...

# Hội thoại sau khi làm việc với công cụ

- Nếu công cụ chọn quà trả về danh sách trống:
  1) nói thẳng rằng với bộ lọc hiện tại không tìm thấy quà phù hợp;
  2) đề xuất người dùng thay đổi 1–2 tham số then chốt
     (ngân sách, loại quà, sở thích của người nhận, dịp tặng).

- Nếu công cụ trả về quá nhiều phương án:
  1) chọn 3–7 phương án phù hợp nhất;
  2) nói rõ tiêu chí bạn đã dùng để chọn
     (khớp sở thích, nằm trong ngân sách, xếp hạng).

- Nếu công cụ xảy ra lỗi:
  1) không bịa dữ liệu;
  2) nói rằng có lỗi kỹ thuật và đề xuất
     thử lại sau hoặc đơn giản hóa yêu cầu.
`.trim();

Như vậy, ta “nạp sẵn” vào mô hình các câu follow‑up mẫu. Nó sẽ diễn đạt theo cách riêng, nhưng theo cấu trúc đã định:

  • nêu thực trạng (trống / quá nhiều / lỗi);
  • thành thật thừa nhận giới hạn;
  • đề xuất bước tiếp theo phù hợp.

Với danh mục quà tặng, điều này rất quan trọng: thay vì “mọi thứ ổn, đây là ba món quà” trong trường hợp kết quả trống, mô hình sẽ nói kiểu:

«Với điều kiện hiện tại của bạn (quà số về chủ đề vũ trụ dưới $5 chỉ giao tại Mỹ) thì trong danh mục của chúng tôi không có. Bạn muốn tôi tăng ngân sách hoặc đề xuất hạng mục khác không?»

Lưu ý: đây không phải là mã công cụ, mà chính là các hướng dẫn trong prompt quy định UX kỳ vọng.

6. Ghép mọi thứ lại: sự tiến hóa của GiftGenius

Hãy xem mini‑tiến hóa của ứng dụng và dần giảm mức ảo giác.

Phiên bản ban đầu: nơi mọi thứ hỏng

Giả sử chúng ta có một system‑prompt tối giản:

export const SYSTEM_PROMPT = `
Bạn là trợ lý chọn quà.
Hãy giúp người dùng tìm ý tưởng phù hợp.
`;

Công cụ được mô tả như sau:

export const recommendGiftsTool = {
  name: "recommend_gifts",
  description: "Chọn quà cho người dùng",
  inputSchema: { type: "object" }
};

Không có hướng dẫn follow‑up.

Trong thực tế sẽ xảy ra:

  • nếu người dùng yêu cầu “quà số cho bạn bè gamer dưới $10”, mà trong cơ sở không có gì khớp bộ lọc đó, mô hình có thể:
    • hoặc không gọi tool và bịa quà từ trí nhớ chung;
    • hoặc gọi tool, nhận danh sách trống nhưng không nói ra và bịa phương án;
  • nếu backend trả về lỗi, mô hình có thể cho rằng “chắc phải có gì đó” và bắt đầu đoán.

Đó là tình huống kinh điển: trong chat — câu trả lời đẹp, trong cơ sở — không có gì giống.

system‑prompt mới

Viết lại system‑prompt dựa theo ba lớp bảo vệ. Một phần bạn đã thấy ở trên, giờ ghép lại đầy đủ:

// config/systemPrompt.ts
export const SYSTEM_PROMPT = `
# Vai trò
Bạn là GiftGenius, trợ lý chọn quà
dựa trên danh mục quà tặng của ứng dụng chúng tôi.

# Phạm vi trách nhiệm
- Nhiệm vụ của bạn — giúp người dùng chọn các món quà phù hợp
  từ danh mục, giải thích ưu và nhược điểm của các phương án.
- Đừng hứa hẹn về việc mua hay gửi quà thực tế —
  bạn chỉ giúp chọn và so sánh phương án.
  Việc mua và cấp mã/liên kết do backend thực hiện sau khi người dùng đồng ý rõ ràng.

# Dữ liệu và ràng buộc
- Không bịa ra các món quà không có trong danh mục hoặc trong kết quả công cụ.
- Không bịa giá, loại quà, tình trạng hay khu vực giao hàng.
- Nếu công cụ không trả về dữ liệu hoặc xảy ra lỗi,
  đừng đoán mò, hãy thông báo về điều đó.

# Làm việc với công cụ
- Dùng các công cụ của danh mục quà (ví dụ, profile_to_segments,
  recommend_gifts, get_gift) cho mọi dữ liệu thực tế
  (danh sách quà, giá, loại, SKU, mô tả).
- Trả lời trực tiếp (không dùng công cụ) nếu câu hỏi mang tính lý thuyết
  và không cần chọn món quà cụ thể.

# Hội thoại sau khi làm việc với công cụ
- Khi kết quả trống: giải thích thẳng thắn rằng không có gì
  theo điều kiện hiện tại, và đề xuất thay đổi 1–2 tham số.
- Khi có quá nhiều phương án: chọn 3–7 phương án phù hợp nhất
  và giải thích tiêu chí lựa chọn.
- Khi công cụ lỗi: đừng bịa dữ liệu, xin lỗi vì sự cố
  và đề xuất thử lại hoặc đơn giản hóa yêu cầu.
`.trim();

Giờ mô hình biết rõ:

  • đâu là vai trò tư vấn quà, đâu là “hậu trường” (nằm sau các công cụ);
  • chính xác dữ liệu nào không được bịa;
  • cách ứng xử trong các trường hợp không lý tưởng thường gặp.

description và schema mới cho recommend_gifts

Chúng ta đã tăng cường system‑prompt, giờ hoàn thiện tool và lắp phiên bản cuối của mô tả — dựa trên ý tưởng ở phần 4.

// config/tools.ts
export const recommendGiftsTool = {
  name: "recommend_gifts",
  description: `
Chọn quà trong danh mục GiftGenius.

Hãy dùng công cụ này khi cần:
- lấy danh sách các món quà thực tế với giá hiện hành, loại
  (digital/physical) và các thẻ;
- thu hẹp lựa chọn theo phân đoạn sở thích, ngân sách, locale và dịp tặng.

KHÔNG DÙNG công cụ:
- nếu người dùng đặt câu hỏi lý thuyết chung về quà tặng
  mà không yêu cầu chọn cho người cụ thể;
- để bịa ra các món quà không có trong danh mục.

Nếu kết quả trống, ĐỪNG tự bịa quà,
mà hãy trả quyền lại cho hội thoại (làm theo hướng dẫn system‑prompt).
  `.trim(),
  inputSchema: {
    type: "object",
    properties: {
      segments: {
        type: "array",
        description:
          "Các phân đoạn hồ sơ người nhận: ví dụ 'tech', 'sport', 'books'.",
        items: { type: "string" }
      },
      budget: {
        type: "object",
        description:
          "Khoảng ngân sách cho quà bằng đơn vị tiền tệ của người dùng (min/max).",
        properties: {
          min: { type: "number", minimum: 0 },
          max: { type: "number", exclusiveMinimum: 0 },
          currency: {
            type: "string",
            minLength: 3,
            maxLength: 3,
            description: "Mã tiền tệ ISO 4217, ví dụ 'USD' hoặc 'RUB'."
          }
        },
        required: ["min", "max", "currency"]
      },
      locale: {
        type: "string",
        description: "Locale của người dùng, ví dụ 'ru-RU' hoặc 'en-US'."
      },
      occasion: {
        type: "string",
        description:
          "Dịp tặng quà: 'birthday', 'anniversary', 'new_year' v.v."
      }
    },
    required: ["segments", "budget", "locale", "occasion"]
  }
};

Có vài lưu ý.

Chúng ta liên kết hành vi của công cụ với system‑prompt một cách rõ ràng: câu “làm theo hướng dẫn system‑prompt” nhắc mô hình rằng “kết quả trống = hội thoại trung thực, không sáng tạo”.

Ta đặt điều kiện phủ định (“không dùng…”, “không bịa…”), mà theo kinh nghiệm thực tế, quan trọng không kém mô tả tích cực.

Ta làm inputSchema giàu ngữ nghĩa: mô tả và ràng buộc hợp lý giúp mô hình ghép yêu cầu với trường tốt hơn và “ít sai” ngay từ bước gọi công cụ.

Mẫu follow‑up trong mã widget và định dạng phản hồi

Ngoài hướng dẫn văn bản, ta có thêm một đòn bẩy — chính định dạng phản hồi của công cụ. Qua đó, ta cũng có thể gợi ý cho mô hình chuyện gì đã xảy ra và thu hẹp khoảng trống cho trí tưởng tượng.

Về hình thức, follow‑up được đặt bằng văn bản trong system‑prompt, nhưng trong widget Next.js của bạn, bạn có thể chuẩn hóa ToolOutput để cuộc sống của mô hình dễ hơn và giảm đất cho ảo giác.

Ví dụ, thống nhất rằng backend cho recommend_gifts luôn trả về:

// kiểu phản hồi của công cụ trên backend
export type RecommendGiftsResult = {
  items: Array<{
    id: string;
    title: string;
    price: number;
    currency: "USD" | "EUR" | "RUB";
    tags: ("digital" | "physical" | "education" | "fitness" | "tech")[];
  }>;
  // trường backend điền để nói rõ cho mô hình biết điều gì đã xảy ra
  status: "ok" | "empty" | "error";
  errorMessage?: string;
};

Widget có thể hiển thị đẹp, còn mô hình — dựa vào status khi soạn câu trả lời. Trong Apps SDK, bạn thường trả về ToolOutput cho mô hình dưới dạng đối tượng JSON, và nó sẽ thấy trường này.

Trong system‑prompt bạn có thể thêm một khối nhỏ:

# Diễn giải trạng thái của công cụ

- Nếu status = "empty": xem phần "Hội thoại sau khi làm việc với công cụ" và
  đừng bịa quà.
- Nếu status = "error": thông báo lỗi kỹ thuật và đừng cố đoán
  nội dung danh mục.

Đúng là không có phần này mô hình vẫn có thể đoán ra, nhưng hướng dẫn tường minh giúp giảm khả năng “đoán mò”.

7. Thực hành: hoàn thiện App của bạn

Để tránh cảm giác “nghe thì hay trên slide”, hãy mô tả một bài tập cụ thể bạn có thể làm với App hiện tại (trong trường hợp của chúng ta — GiftGenius). Ta sẽ refactor nhỏ qua ba lớp bảo vệ: system‑prompt, mô tả công cụ và xử lý kết quả.

Thứ nhất, ở cấp system‑prompt, lấy system‑prompt từ bài đầu và tìm chỗ mô tả phạm vi trách nhiệm và làm việc với công cụ. Thêm vào đó:

  • cấm bịa thực thể ngoài danh mục/cơ sở (quà, SKU, giá);
  • quy tắc cho trường hợp kết quả trống và lỗi công cụ;
  • phần “Hội thoại sau khi làm việc với công cụ” với 2–3 kịch bản (trống, quá nhiều, lỗi).

Thứ hai, ở cấp mô tả tools, mở mô tả công cụ chủ chốt (với bạn có thể là recommend_gifts, search_gifts, search_tariffs, calculate_quote, v.v., không quan trọng). Viết lại description sao cho:

  • nói rõ công cụ chỉ hoạt động với nguồn dữ liệu của bạn (danh mục quà, gói cước, v.v.);
  • giải thích khi nào cần dùng, khi nào không;
  • chứa các ràng buộc phủ định rõ ràng: “đừng bịa…”, “đừng dùng cho…”.

Thứ ba, ở cấp cấu trúc phản hồi của công cụ, nếu phản hồi chưa có trường diễn giải trạng thái (status, resultType, hasMore) — hãy thêm nó vào kiểu trên backend và trong ToolOutput. Sau đó trong system‑prompt viết rõ mô hình phải diễn giải trạng thái này như thế nào trong hội thoại.

Cuối cùng, chạy thử vài yêu cầu trong Dev Mode, gồm cả các trường hợp chắc chắn trống hoặc biên. Hãy chú ý xem mô hình có ngừng bịa thực thể không, và nó trình bày với người dùng về các giới hạn có trung thực không.

Trong bài tiếp theo, bạn sẽ chính thức hóa các yêu cầu như vậy thành golden prompt set và biến nó thành hiện vật kiểm thử lặp lại, nhưng hiện tại điều quan trọng là bạn tự tay cảm nhận khác biệt.

8. Lỗi thường gặp khi chống ảo giác bằng prompt và công cụ

Lỗi số 1: một câu “ma thuật” duy nhất “đừng ảo giác” trong system‑prompt.
Lập trình viên viết ở cuối prompt: “Đừng bịa thông tin” — và nghĩ rằng nhiệm vụ đã xong. Trên thực tế, mô hình vẫn bịa, vì mô tả công cụ và follow‑ups không đưa ra hành vi thay thế. Không có quy tắc cụ thể “phải làm gì thay vì bịa” (thừa nhận kết quả trống, đề xuất đổi bộ lọc, báo lỗi), thì câu đó gần như không giúp gì.

Lỗi số 2: mâu thuẫn giữa system‑prompt và description của công cụ.
Trong system‑prompt bạn nói: “Không bịa ra quà không có trong danh mục”, còn trong mô tả công cụ — “Chọn quà phù hợp, nếu không tìm thấy — có thể đề xuất phương án giống tương tự theo ý mình”. Kết quả là mô hình lúng túng giữa hai nguồn “sự thật”, và đáng tiếc là nguồn cụ thể hơn (thường là description của công cụ) thắng. Cần để cả hai lớp nói cùng một điều: nếu cho phép phương án “tương tự”, cũng phải được chuẩn hóa (và nhất thiết giải thích cho người dùng rằng đó không phải trùng khớp chính xác).

Lỗi số 3: mô tả công cụ quá mơ hồ.
Mô tả kiểu “Công cụ giúp người dùng giải quyết bài toán” hầu như không cung cấp thông tin về ranh giới công cụ cho mô hình. Khi đó, nó hoặc sẽ không dùng công cụ, hoặc sẽ gọi mọi lúc — rồi “bổ sung” dữ liệu khi công cụ trả về ít hoặc không có gì. Một description tốt phải có tính phân biệt: nói rõ công cụ làm gìkhi nào không nên gọi.

Lỗi số 4: không có chiến lược cho kết quả trống và lỗi.
Lập trình viên cẩn thận viết backend trả về { items: [], status: "empty" }, nhưng không giải thích cho mô hình điều đó nghĩa là gì. Kết quả là mô hình thấy mảng rỗng và quyết định: “chắc nên đề xuất gì đó từ kiến thức chung”. Trong system‑prompt thiếu phần giải thích cách diễn giải các trạng thái như vậy và nói gì với người dùng. Thêm vài quy tắc rõ ràng cho kết quả trống/lỗi mang lại bước nhảy vọt về chất lượng.

Lỗi số 5: cố “chữa” ảo giác chỉ ở cấp mã widget.
Đôi lúc người ta muốn đẩy hết lên frontend: “nếu danh sách trống — tôi hiển thị placeholder và không cho người dùng thấy câu trả lời văn bản của mô hình”. Điều này có thể làm UX đỡ gượng, nhưng bản thân mô hình vẫn tin vào thực thể bịa và sẽ hành xử tương tự ở các lượt sau. Cách đúng — trước hết thay đổi hướng dẫn (system‑prompt, mô tả công cụ, follow‑ups), rồi mới bổ sung lớp bảo vệ trong UI.

Lỗi số 6: bỏ qua ảnh hưởng của metadata và schema lên hành vi mô hình.
Một số nhà phát triển coi JSON Schema và mô tả trường là “chỉ để form và validate”. Thực ra, với ChatGPT đó là phần quan trọng của prompt: dựa vào đó mô hình hiểu cần trích tham số nào từ yêu cầu và phản hồi hợp lệ trông ra sao. Mô tả trường yếu hoặc không nhất quán (description, enum) làm tăng khả năng lỗi và gián tiếp kích hoạt ảo giác.

Lỗi số 7: cấm đoán quá cứng mà không có phương án thay thế.
Đôi khi prompt biến thành danh sách “đừng làm cái này, đừng làm cái kia”, nhưng không nói phải làm gì trong trường hợp khó. Ví dụ, ta cấm bịa quà và tự giới hạn vào danh mục, nhưng lại không nói gì về câu hỏi lý thuyết. Kết quả là mô hình đôi lúc chỉ trả lời “tôi không biết”, trong khi hoàn toàn có thể giải thích các nguyên tắc chọn quà hữu ích. Luôn cố gắng không chỉ cấm, mà còn mở lối hợp lệ: “nếu không thể tìm quà trong danh mục — hãy nói thẳng và đề xuất cách thay đổi yêu cầu” hoặc “nếu yêu cầu mang tính lý thuyết — hãy tự trả lời, không cần dùng công cụ”.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION