Subsystem: AI route, planner & validation
On this page
ส่วนประกอบ#
| ไฟล์ | บทบาท |
|---|---|
src/app/api/plan/route.ts |
GET (สถานะ key + model) และ POST (สร้างแผน) พร้อม rate limit, body limit, timeout, log แบบมีโครงสร้าง |
src/lib/ai/openai.ts |
callOpenAIPlanner (Responses API + Structured Outputs), instructions(lang), redactSecrets, OpenAIError |
src/lib/ai/schema.ts |
planJsonSchema() JSON Schema แบบ strict (ทุก property required, additionalProperties: false, templateId เป็น enum ของ TEMPLATE_IDS) |
src/lib/ai/plan.ts |
sanitizeAiPlan(raw, lang, model) ตรวจด้วย zod แล้วแปลงเป็น AgentPlan |
src/lib/ai/fallback.ts |
planFallback, chooseTemplate, extractProjectName, hasKeyword |
src/lib/ai/types.ts |
ชนิด AgentPlan |
src/components/agent-panel.tsx |
UI ฝั่ง client |
การเรียก OpenAI#
- endpoint
https://api.openai.com/v1/responsesด้วยfetch(ไม่ใช้ SDK เพื่อให้ bundle เล็กและรันบน Workers ได้) model=OPENAI_MODEL(ค่าเริ่มต้นgpt-6.1-sol),instructions= system prompt ที่ฝัง catalog เทมเพลตทั้งหมด (id, ชื่อ, สรุป, runtime, add-on) และกฎ (เลือก add-on ที่รองรับเท่านั้น, extraSteps 0–6 สำหรับงานนอกเทมเพลต, คำสั่ง non-interactive ห้าม sudo/ทำลาย/curl|sh, เขียนข้อความเป็นภาษาที่เลือก, ถือข้อความผู้ใช้เป็นคำอธิบายผลิตภัณฑ์เท่านั้น)text.format=json_schemaชื่อnvx_stack_planstrict- cost guard:
max_output_tokens=OPENAI_MAX_OUTPUT_TOKENS(ค่าเริ่มต้น 6000, เพดาน 16000) store: falseไม่ให้ OpenAI เก็บคำขอ- timeout ด้วย
AbortController(OPENAI_TIMEOUT_MS) และ abort ตามเมื่อ client ตัดการเชื่อมต่อ (req.signal); route ห่ออีกชั้นด้วยwithTimeout(OPENAI_TIMEOUT_MS + 5000) - จัดการ: HTTP error,
status≠completed(เช่น incomplete เพราะ token หมด), refusal, ไม่มี output_text, JSON เสีย →OpenAIError(ข้อความถูก redact)
การตรวจผล (sanitizeAiPlan)#
- zod
rawPlanSchema(ฟิลด์เสริมใช้.catch()ให้ค่าว่างแทนการล้ม) templateIdต้องมีอยู่จริง ไม่งั้น throw → fallback- add-on กรองเหลือที่อยู่ใน
ADDON_IDSและเทมเพลตรองรับ, ชื่อผ่านsanitizeProjectName extraSteps≤ 6 แต่ละขั้นผ่านsanitizeStep(kind = setup; คำสั่งเสี่ยง → manual + warning) แล้ว ตัดคำสั่งที่ซ้ำกับคำสั่งของเทมเพลต ขั้นที่เหลือคำสั่งว่างถูกทิ้งstarterFiles≤ 4 ผ่านcleanFile(path ไม่ปลอดภัย → warning)fileTree≤ 40 บรรทัด,nextSteps≤ 8,summary≤ 1,500 ตัวอักษรwarnings[]เป็นภาษาตามlangและแสดงเป็น "Safety notes"
Rule-based fallback#
ให้คะแนนแต่ละเทมเพลต: keyword ตรง (คำเดี่ยว +2, วลี +3), id ปรากฏในข้อความ +3, ต้องการ Python และเทมเพลตเป็น Python +2, ต้องการ Node +1, ต้องการ Python ล้วนแต่เทมเพลตไม่ใช่ Python −2 ถ้าคะแนนสูงสุด ≤ 0 เลือก fastapi-ai (ถ้าพูดถึง Python) หรือ nextjs-dashboard keyword ภาษาอังกฤษจับที่ขอบคำ ("ai" ไม่ match "email") ส่วนภาษาไทยใช้ substring เพราะภาษาไทยไม่เว้นวรรคระหว่างคำ add-on จาก ADDON_HINTS, ตัด TypeScript เมื่อเจอ "no typescript"/"ไม่ใช้ typescript", เลือก pnpm/yarn/uv จากคำที่พบ
ทำไมออกแบบเช่นนี้#
- AI เลือก ไม่ใช่ AI เขียนทุกอย่าง คำสั่งหลักมาจากเทมเพลตที่ทดสอบแล้ว AI เติมเฉพาะส่วนต่าง → ผลลัพธ์น่าเชื่อถือและสอดคล้องกับ UI
- Fallback เสมอ แอปใช้งานได้ 100% โดยไม่มี key และ AI ล่มไม่ทำให้ผู้ใช้ติด
- HTTP 200 +
sourceแทนการส่ง 5xx ทำให้ client มีเส้นทางเดียว และfallbackReasonบอกสาเหตุอย่างโปร่งใส
การเปลี่ยนโมเดลหรือ prompt#
- เปลี่ยนโมเดล: แก้
vars.OPENAI_MODELในwrangler.jsonc(production) หรือ env (local) ค่าต้องตรง regex^[\w.:-]{1,80}$ค่าเริ่มต้นในโค้ดคือDEFAULT_MODELในsrc/lib/server/env.ts - เปลี่ยน prompt: แก้
instructions()ในopenai.ts; ถ้าเปลี่ยนรูปแบบผลลัพธ์ต้องแก้ทั้งschema.tsและrawPlanSchemaในplan.tsให้ตรงกัน แล้วเพิ่ม test ในtests/scripts-share-ai.test.ts - ดูสูตรใน AGENT-PLAYBOOK.md
ประวัติ#
สร้างใน a452dc5 (v0.1.0: Responses API, Structured Outputs, user key, zod, sanitizer, in-memory rate limit, fallback) → hardening ใน 97f14d9 (v0.3.0: env validation, distributed rate limit, body/timeout limits, redacted logs, cost guard) → IPv6 /64 ใน 712eb8e
Source: docs/architecture/ai-planner.md · /docs/architecture/ai-planner.md