Skip to content

Làm rõ có AI hỗ trợ ​

AI làm giảm độ mơ hồ của yêu cầu. Con người xác nhận những quyết định quan trọng.

Một yêu cầu đi vào hệ thống với nhiều câu hỏi chưa có lời đáp. AI giúp đặt câu hỏi và đề xuất phương án trả lời; nhưng AI không bao giờ là tác giả của câu trả lời cuối hay của một quyết định.

Requirement
    ↓
Question                 ← do người thêm, hoặc chấp nhận từ gợi ý AI
    ↓
AI Suggestions  A / B / C  (kèm lý do và rủi ro, một phương án được khuyến nghị)
    ↓
Con người chọn → sửa → xác nhận     = Final Answer
    ↓
Decision                 ← con người ký tên

Vòng đời câu hỏi ​

Trạng tháiNghĩaChuyển bởi
openMới đặt, chưa có gợi ýPOST /v1/questions hoặc chấp nhận một gợi ý câu hỏi
suggestedĐã có ba phương án A / B / C của AIPOST /v1/questions/:id/suggest-answers
answeredMột người đã xác nhận câu trả lời cuốiPOST /v1/questions/:id/answer
decidedCâu trả lời đã được nâng thành quyết định D-NNNNPOST /v1/questions/:id/decide
droppedBỏ, nhưng giữ lịch sửDELETE /v1/questions/:id khi câu hỏi đã có lịch sử

Có thể trả lời thẳng mà không cần AI (open → answered). Đường thủ công — thêm câu hỏi, viết câu trả lời, quyết, gắn bằng chứng — giống hệt nhau dù AI có được cấu hình hay không.

Giải phẫu một câu hỏi ​

Bảng questions, ID Q-NNNN. Năm phần của tài liệu đề xuất được giữ tách bạch thành cột:

PhầnCộtAi ghi
Human Answerdraft_answer_en/viNgười, qua form thường (PATCH) — trước hoặc thay cho AI
AI Suggestionsbảng ai_suggestions, kind = answer, label A / B / CChỉ Worker, bất biến
Selected Optionselected_suggestion_idChỉ route answer
Human Modificationhiệu giữa gợi ý đã chọn và final_answer_*Không lưu riêng — giao diện điền sẵn trình soạn thảo từ phương án đã chọn, nên phần sửa nhìn thấy được
Final Answerfinal_answer_en/vi, answered_by, answered_atChỉ route answer, đóng dấu email từ Cloudflare Access

Ngoài ra: source_suggestion_id (câu hỏi này được chấp nhận từ gợi ý nào), decision_id (quyết định sinh ra từ nó), requirement_id, brand_id (tự điền từ yêu cầu).

Sai

PATCH /v1/questions/Q-0881 { "final_answer_en": "No", "status": "decided" }

Đúng

final_answer_*, selected_suggestion_id, answered_by/at, status và decision_id không nằm trong danh sách cột của form chung. Chúng chỉ được ghi bởi ba route riêng — answer, decide, và tạo câu hỏi — và Worker luôn đóng dấu user.email. "Con người quyết" là ràng buộc ở server, không phải quy ước.

Hai loại gợi ý ​

Bảng ai_suggestions, ID AIS-<8 hex>, bất biến (không có updated_at, không phải tài nguyên dùng chung; chỉ status đổi, và chỉ qua route riêng).

LoạikindSinh bởiThành gì
Gợi ý câu hỏiquestion (tối đa 5, question_id = NULL)POST /v1/requirements/:id/suggest-questionsChưa phải câu hỏi — chỉ thành câu hỏi khi một người bấm Thêm làm câu hỏi → POST /v1/suggestions/:id/accept
Gợi ý trả lờianswer (đúng 3, label A / B / C, một recommended)POST /v1/questions/:id/suggest-answersMột phương án được chọn khi trả lời; các phương án còn lại rejected

Mỗi gợi ý mang name_* (tiêu đề), description_* (phương án), rationale_* (phân tích), risks_* (rủi ro, kể cả mâu thuẫn với quyết định đã có), và status proposed · selected · rejected · accepted. Giao diện luôn hiện gợi ý trong một khung được dán nhãn rõ: model, phiên bản prompt, ai yêu cầu.

Ngữ cảnh AI nhìn thấy ​

Trước mỗi lần gọi, Worker dựng ngữ cảnh từ chuỗi truy vết của yêu cầu: các trường của yêu cầu, sản phẩm (tên, mệnh đề), Brand, feature, năng lực (tên, định nghĩa), sáng kiến; các câu hỏi anh em cùng câu trả lời cuối; và mọi quyết định decided gắn với yêu cầu hoặc sản phẩm đó. Bộ nhớ quyết định là đầu vào — prompt hệ thống bắt AI tôn trọng mọi quyết định đã liệt kê và nêu mâu thuẫn trong risks_*.

Prompt hệ thống (PROMPT_VERSION = clarify-v1) yêu cầu: hỏi về ý nghĩa, người dùng, tần suất, bằng chứng, tình huống hỏng; ba phương án loại trừ lẫn nhau và sẵn sàng để quyết; không bịa sự thật về sản phẩm; tiếng Việt và tiếng Anh nói cùng một điều; chỉ trả JSON.

Workers AI qua AI Gateway ​

Worker gọi Cloudflare Workers AI với model DeepSeek, đi qua AI Gateway conan1, bằng binding AI. Không có SDK ngoài, không có khoá API, không có secret nào cần đặt.

Cấu hìnhGiá trịỞ đâu
Binding"ai": { "binding": "AI" }wrangler.jsonc
ModelAI_MODEL = @cf/deepseek-ai/deepseek-v4-flash-0731wrangler.jsonc → vars (cùng giá trị là mặc định trong src/lib/ai.ts nếu thiếu)
GatewayAI_GATEWAY_ID = conan1wrangler.jsonc → vars; rỗng thì gọi thẳng, không qua gateway
Hạn mức ngàyAI_DAILY_LIMIT = 50wrangler.jsonc → vars; 0 hoặc bỏ trống là không giới hạn
Phiên bản promptclarify-v1src/lib/ai.ts

Vì sao là flash chứ không phải pro: đo ngày 2026-09-02 trên prompt clarify-v1, @cf/deepseek-ai/deepseek-v4-pro-0813 một lần vượt thời gian chờ của Workers AI (lỗi 3046) và một lần cắt cụt JSON ở max_tokens = 4000 vì phần suy nghĩ ăn hết ngân sách; deepseek-v4-flash-0731 trả lời trong khoảng 19 giây. Muốn quay lại pro thì chỉ đổi vars.AI_MODEL trong wrangler.jsonc khi độ trễ chấp nhận được — code không phụ thuộc vào model cụ thể.

ts
const res = await env.AI.run(model, {
  messages: [{ role: 'system', content: SYSTEM_PROMPT + schema }, { role: 'user', content: context }],
  max_tokens: 4000, temperature: 0.3,
}, env.AI_GATEWAY_ID ? { gateway: { id: env.AI_GATEWAY_ID, skipCache: true } } : undefined)

Phản hồi có dạng OpenAI: res.choices[0].message.content là JSON cần đọc, res.choices[0].message.reasoning_content là phần suy nghĩ của model — không bao giờ hiển thị hay lưu nó như một câu trả lời; res.usage.prompt_tokens / completion_tokens là chi phí. Model tiêu token cho phần suy nghĩ, nên max_tokens phải đủ lớn và Worker đọc JSON khoan dung (bỏ rào ```json, lấy từ { đầu tới } cuối) rồi kiểm số phần tử và nhãn A / B / C.

AI Gateway conan1 cho nhật ký, cache và giới hạn tốc độ tập trung trên Cloudflare Dashboard. Code không phụ thuộc vào sự tồn tại của gateway: thiếu AI_GATEWAY_ID thì gọi Workers AI trực tiếp.

Khi chạy local, wrangler dev chạy Workers AI từ xa bằng phiên đăng nhập Cloudflare sẵn có trên máy; không cần cấu hình thêm.

Mọi lần gọi đều được ghi ​

Bảng ai_runs, ID AIR-<8 hex>: kind (suggest_questions · suggest_answers), requirement_id, question_id, actor (email người bấm), model (id model Workers AI thực dùng), prompt_version, input_tokens, output_tokens, status (ok · failed), error. Một lần gọi thất bại vẫn là một dòng. Các gợi ý của cùng một lần gọi chia sẻ run_id.

ai_runs và ai_suggestions không nằm trong gói GET /v1/architecture; trang chi tiết yêu cầu tự tải chúng qua GET /v1/requirements/:id/suggestions, nên reload() của ứng dụng không phình theo lượng AI.

Hạn mức mỗi ngày ​

Mỗi lần gợi ý tốn neuron của Workers AI, và một client kẹt vòng lặp gọi được rất nhiều lần. AI_DAILY_LIMIT đặt trần số lần gọi mỗi ngày UTC cho toàn bộ Worker — không phải mỗi người, không phải mỗi yêu cầu.

Thuộc tínhGhi chú
Cấu hìnhvars.AI_DAILY_LIMIT trong wrangler.jsonc, hiện là "50"
Tắt trần0, giá trị không đọc được thành số, hoặc bỏ hẳn biến
Đếm từ đâuSELECT COUNT(*) FROM ai_runs WHERE created_at >= date('now') — mọi dòng ai_runs, kể cả lần gọi failed
Ranh giới ngàyNgày UTC. created_at là datetime('now') dạng YYYY-MM-DD …, nên so chuỗi với date('now') chọn đúng hôm nay
Áp ở đâuCả hai endpoint gợi ý, trước khi mở dòng ai_runs — lần bị chặn không tự đếm chính nó

Hết trần thì hai endpoint gợi ý trả 429:

json
{
  "error": "ai_daily_limit",
  "used": 50,
  "limit": 50,
  "message_en": "Today's 50 AI suggestions are used up. Write the answer yourself, or try tomorrow.",
  "message_vi": "Đã dùng hết 50 lượt gợi ý AI của hôm nay. Hãy tự viết câu trả lời, hoặc thử lại ngày mai."
}

Đường thủ công không bao giờ bị chặn

Trần chỉ chạm hai route gợi ý. Thêm câu hỏi, viết câu trả lời cuối, quyết, gắn bằng chứng, chấp nhận một gợi ý đã sinh từ trước — tất cả vẫn chạy khi hạn mức đã cạn, y như khi AI chưa được cấu hình.

Trần dùng chung, không phải theo người

Năm mươi lượt là của cả tổ chức trong ngày hôm đó, nên một người có thể tiêu hết phần của người khác. Giới hạn theo người, nếu cần, vẫn phải đặt ở AI Gateway.

Hợp đồng lỗi ​

GET /v1/ai/status trả { enabled, model, gateway, limit, used }: limit là AI_DAILY_LIMIT đã đọc thành số (0 là không giới hạn), used là số lần gọi hôm nay — luôn 0 khi enabled = false, vì lúc đó không có gì để đếm ngược. Store tải nó lúc khởi động; nếu enabled = false các nút Gợi ý hiện mờ với chú thích AI chưa cấu hình, còn khi có trần thì giao diện hiện phần còn lại ngay cạnh nút và tắt nút khi đã cạn.

Tình huốngMãerrorGhi ai_runs?
Binding AI không có503ai_not_configuredKhông
Đã dùng hết hạn mức ngày429ai_daily_limitKhông
Workers AI báo giới hạn tốc độ / hạn mức429ai_rate_limitedCó, failed
Gọi thất bại vì lý do khác502ai_failedCó, failed
Model trả về JSON không đọc được, sai số phương án, sai nhãn502ai_no_answerCó, failed

Giao diện ánh xạ bốn mã này sang thông điệp song ngữ. Mọi thứ khác — thêm câu hỏi, viết câu trả lời, quyết, gắn bằng chứng — vẫn chạy bình thường khi AI vắng mặt.

Ai được bấm Gợi ý

Cùng quy tắc với toàn mặt phẳng Requirements: chỉ architect — và trong phạm vi AI_DAILY_LIMIT của ngày hôm đó.

Trả lời và quyết ​

  • Trả lời — POST /v1/questions/:id/answer với { selected_suggestion_id?, final_answer_en, final_answer_vi }. Cả hai câu trả lời cuối bắt buộc; gợi ý (nếu có) phải là gợi ý answer của đúng câu hỏi này. Trong một batch: ghi câu trả lời cuối, đóng dấu answered_by = email, answered_at, đánh dấu gợi ý đã chọn selected và các gợi ý cùng lần gọi rejected, chuyển status = answered.
  • Quyết — POST /v1/questions/:id/decide với { name_*, rationale_*, description_*? }, chỉ khi câu hỏi đã answered. Sinh D-NNNN, decided_by = email, decided_by_kind = ai_assisted nếu có gợi ý được chọn (ngược lại human), sao chép phân tích + rủi ro của gợi ý vào ai_analysis_*, đóng băng dải Liên quan, chuyển câu hỏi sang decided. Chi tiết ở Quyết định.