ข้อกำหนดความต้องการซอฟต์แวร์ (Software Requirements Specification: SRS)
โครงการ: ระบบแชตบอตปัญญาประดิษฐ์เฉพาะทางพระพุทธศาสนาและเว็บไซต์ (MBU Buddhist AI)
ผู้ว่าจ้าง: มหาวิทยาลัยมหามกุฏราชวิทยาลัย (มมร.)
อ้างอิง TOR: ข้อ 4.2.2 (SRS: FR/NFR, Workflow, Architecture, Data Dictionary, User Flow/Wireframe, OpenAPI 3.0) และข้อ 7.1.2 (เอกสารส่งมอบงวดที่ 1)
ฉบับ: 1.0 (ระบบสาธิต) — วันที่ 3 ตุลาคม พ.ศ. 2569
เอกสารนี้อธิบายความต้องการของระบบตามที่ มีอยู่จริงในซอร์สโค้ด ของระบบสาธิต (Next.js 15 + Prisma/SQLite) ทุกข้อกำหนดระบุไฟล์หรือ endpoint ที่ implement ไว้เพื่อให้ตรวจสอบย้อนกลับได้ ส่วนที่เป็นสถาปัตยกรรมระยะ Production (Kubernetes, PostgreSQL, Elasticsearch) ระบุแยกไว้ในหัวข้อ 5 พร้อมแผนที่การย่อส่วน
1. บทนำ
1.1 วัตถุประสงค์ของเอกสาร
- กำหนดความต้องการเชิงหน้าที่ (Functional Requirements: FR) และไม่ใช่เชิงหน้าที่ (Non-Functional Requirements: NFR) โดยผูกรหัส
FR-xxx / NFR-xxx กับข้อ TOR (clause id) ให้ตรวจสอบย้อนกลับได้
- อธิบายลำดับการทำงานหลัก (Workflow) ได้แก่ การถาม-ตอบผ่าน RAG pipeline 8 โหนด การนำเข้าเอกสาร และการประเมินคุณภาพ AI
- อธิบายสถาปัตยกรรมระบบ (ระบบสาธิตและการแมปไปยัง Production)
- ให้พจนานุกรมข้อมูล (Data Dictionary) ครบทุก model/field ที่สร้างจาก
prisma/schema.prisma จริง
- สรุปกลุ่ม API ตามเอกสาร OpenAPI 3.0 ที่ระบบให้บริการที่
/api/openapi.json
รหัส FR ในเอกสารนี้ใช้อ้างอิงต่อใน แผนการทดสอบหลัก (/documents/master-test-plan) เพื่อทำ Traceability Matrix ระหว่าง SRS กับกรณีทดสอบ (TOR 4.7.1)
1.2 ขอบเขตของระบบ
ระบบประกอบด้วย 5 ส่วนตาม TOR ข้อ 4.4:
| ส่วน | ขอบเขต | ตำแหน่งในระบบสาธิต |
|---|
| (1) Frontend Website | หน้าแรก สมัคร/เข้าสู่ระบบ ถาม-ตอบ Dhamma Talk ประวัติ สิทธิเจ้าของข้อมูล ศูนย์เอกสาร ตาราง TOR | src/app/[locale]/*, src/components/* |
| (2) Backend Service & AI Engine | REST/SSE API, RAG pipeline (LangGraph.js), Guardrail, Token Budget, Ingestion | src/app/api/*, src/application/*, src/server/*, src/domain/* |
| (3) AI Evaluation & Benchmarking | ชุดทดสอบ 7 ชุด 3 workflow บันทึกเวอร์ชัน เปรียบเทียบรอบ | src/application/evaluation/*, scripts/eval/run-acceptance.ts |
| (4) Admin Portal | ผู้ใช้/PDPA/Audit, ตั้งค่า AI, Prompt, Guardrail, ฐานองค์ความรู้, Evaluation, Ops | src/app/[locale]/admin/*, src/components/admin/* |
| (5) Dashboard & Report | การใช้งาน โทเคน ค่าใช้จ่าย Feedback SLA Defect | /admin/dashboard, /admin/sla-report, scripts/reports/* |
นอกขอบเขตของระบบสาธิต (ระบุตาม Build Plan ข้อ 14): การติดตั้งบนคลาวด์/Kubernetes ของมหาวิทยาลัย โดเมน dhammaverse.mbu.ac.th จริง Auto Scaling จริง WAF/Anti-DDoS ระดับคลาวด์ OAuth กับ Google/Facebook ด้วย credential จริง SMTP จริง Elasticsearch/bge-m3 จริง และ การทดสอบเจาะระบบ (Penetration Test) ซึ่งไม่ได้ดำเนินการในรอบนี้ตามคำสั่งของผู้ว่าจ้าง
1.3 ผู้มีส่วนได้ส่วนเสียและบทบาทผู้ใช้
บทบาท (User.role) | คำอธิบาย | สิทธิ์หลัก |
|---|
GENERAL_USER | ผู้ใช้ทั่วไป (นักศึกษา พระภิกษุ ประชาชน) | ถาม-ตอบ ประวัติ สิทธิเจ้าของข้อมูล; Dhamma Talk เมื่อผู้ดูแลเปิดสิทธิ์ (dhammaTalkEnabled) |
ADMIN | ผู้ดูแลระบบของมหาวิทยาลัย / คณะกรรมการตรวจรับ | Admin Portal ทั้งหมด ยกเว้นสำรองข้อมูล ทำลายข้อมูล และเปลี่ยน role |
SUPER_ADMIN | ผู้ดูแลระบบสูงสุด | ทุกสิทธิ์ รวมสำรองข้อมูล (/admin/backup) ทำลายข้อมูล (/admin/data-destruction) และเปลี่ยน role |
บทบาททั้ง 3 นิยามใน src/domain/identity/rbac.ts (ROLES) และตรวจทุก endpoint ผ่าน withApi({ auth }) ใน src/server/api/with-api.ts
1.4 นิยามศัพท์
| คำ | ความหมาย |
|---|
| RAG | Retrieval-Augmented Generation — ค้นข้อความอ้างอิงจากคลังก่อนให้ LLM สร้างคำตอบ |
| Chunk | หน่วยข้อความที่ทำดัชนี (พระไตรปิฎก/อรรถกถา = 1 ข้อ ต่อ 1 chunk) |
| Citation | การอ้างอิงในรูปแบบ {abbreviation} ({edition}) {volume}/{item}/{page} เช่น ขุ.ขุ. (ไทย) 25/5/3 |
| Precedence | ลำดับความสำคัญของประเภทเอกสาร (พระไตรปิฎก 1.0 > อรรถกถา 0.9 > ฎีกา 0.8 > ร่วมสมัย 0.7 > ธรรมะพ่อแม่ครูอาจารย์ 0.6) |
| RRF | Reciprocal Rank Fusion — รวมผลค้นหาหลายรายการด้วย Σ 1/(k + rank) |
| SSE | Server-Sent Events — ส่งผลลัพธ์แบบ streaming จากเซิร์ฟเวอร์ |
| TTFT | Time To First Token — เวลาตั้งแต่รับคำถามจนได้โทเคนแรกของคำตอบ |
| Guardrail | กฎป้องกันคำถามนอกขอบเขต/อ่อนไหว/prompt injection และตรวจคำตอบ |
| Unavailable Mode | โหมดปิดบริการแชตชั่วคราว (สั่งโดยผู้ดูแล หรืออัตโนมัติเมื่อโทเคนใกล้เพดาน) |
1.5 เอกสารอ้างอิง
docs/build-plan.md (แผนพัฒนาระบบสาธิต ครอบคลุม TOR 200 ข้อ) และตาราง TOR ที่หน้า /tor (src/data/tor-map.ts)
/documents/requirements-summary — สรุปความต้องการ (TOR 4.2.1)
/documents/ux-deliverables — User Flow/Wireframe/Prototype (TOR 4.3.1–4.3.3)
/documents/data-ingestion-plan — แผนนำเข้าข้อมูล (TOR 4.5.1)
/documents/master-test-plan — แผนการทดสอบหลักและ Traceability (TOR 4.7.1)
/api/openapi.json — ข้อกำหนด API (OpenAPI 3.0.3)
2. ภาพรวมระบบ
2.1 เทคโนโลยีที่ใช้จริง (จาก package.json)
| ชั้น | เทคโนโลยี |
|---|
| Web/Backend | Next.js 15.5 (App Router, Route Handlers), React 19.1, TypeScript strict |
| UI | Tailwind CSS 4, next-themes (dark/light), next-intl 4 (th/en), react-markdown + remark-gfm, recharts |
| ข้อมูล | Prisma 6.19 + SQLite (prisma/dev.db; ชุดทดสอบใช้ฐานข้อมูลแยกต่อไฟล์) |
| AI Orchestration | @langchain/langgraph 1.4 (StateGraph 8 โหนด) + @langchain/core |
| LLM | Typhoon typhoon-v2.5-30b-a3b-instruct ผ่าน OpenAI-compatible API (ค่าเริ่มต้นใน src/domain/config/ai-config.ts) สลับเป็น OpenAI / Gemini / Claude / Mock ได้ |
| Auth/Security | jose (JWT HS256), bcryptjs, CSRF double-submit, zod v4 validation |
| Parsers | unpdf (PDF), mammoth (DOCX), fast-xml-parser (TEI-XML), gray-matter, papaparse (CSV), diff |
| ทดสอบ | vitest 5 + @vitest/coverage-v8, Testing Library (jsdom), Playwright 1.63 |
2.2 โครงสร้างชั้นซอฟต์แวร์ (Clean Architecture)
flowchart LR
subgraph Presentation["src/app (หน้าเว็บ + Route Handlers)"]
P1["[locale]/... pages"]
P2["api/... route.ts (withApi)"]
end
subgraph Application["src/application (use cases)"]
A1["chat/ask.ts"]
A2["evaluation/runner.ts"]
A3["ingestion/kb-admin.ts"]
A4["privacy, identity, config, ops, report"]
end
subgraph Adapters["src/server (adapters)"]
S1["ai/graph.ts (LangGraph)"]
S2["llm/* (typhoon/openai-compatible, gemini, claude, mock)"]
S3["retrieval/* (KnowledgeIndex, reranker)"]
S4["ingestion/* (pipeline, parsers)"]
S5["db.ts, repos, audit, notification, logger"]
end
subgraph Domain["src/domain (pure TypeScript)"]
D1["ai: guardrail-policy, citation, token-budget-policy"]
D2["retrieval: bm25, embedding, rrf, synonyms, heuristic-reranker, relevance-scoring"]
D3["ingestion: chunking-policy, sanitize"]
D4["identity, privacy, audit, evaluation, report, config"]
end
Presentation --> Application --> Adapters --> Domain
Presentation --> Adapters
กติกา: src/domain/* ไม่ import framework ใด ๆ (ทดสอบหน่วยได้โดยตรง) ชั้นบนเรียกชั้นล่างเท่านั้น และ coverage gate ของ vitest วัดเฉพาะ src/domain/** + src/application/** (vitest.config.ts)
3. ความต้องการเชิงหน้าที่ (Functional Requirements)
ตารางต่อไปนี้ระบุรหัส FR, ข้อกำหนด, ข้อ TOR ที่รองรับ และตำแหน่งที่ implement ในระบบสาธิต
3.1 การยืนยันตัวตนและสิทธิ์ (Identity & Access)
| รหัส | ข้อกำหนด | TOR | Implement |
|---|
| FR-001 | ผู้ใช้สมัครสมาชิกด้วยอีเมล รหัสผ่าน ชื่อแสดง และติ๊กยินยอมนโยบาย เก็บเฉพาะข้อมูลที่จำเป็น บันทึก consentVersion/consentAt | ก.3.3.1, 14.2 | /register, POST /api/auth/register |
| FR-002 | เข้าสู่ระบบ/ออกจากระบบด้วยอีเมล+รหัสผ่าน ออก JWT (HS256 อายุ 24 ชม.) ใน httpOnly cookie mbu_session; บัญชีสถานะ suspended/deleted เข้าไม่ได้ | ก.3.3.1 | /login, POST /api/auth/login, POST /api/auth/logout, src/server/auth/session.ts |
| FR-003 | เข้าสู่ระบบด้วย Gmail/Facebook (OAuth 2.0 Authorization Code + state/PKCE) และ Mock Provider ภายในเมื่อยังไม่ตั้ง client id | ก.3.3.2 | src/server/identity/oauth/*, `/api/auth/oauth/[provider]/start |
| FR-004 | ป้องกัน brute force: ผิด 5 ครั้ง/15 นาที/บัญชี หรือ 20 ครั้ง/15 นาที/IP → ล็อก 15 นาที และ rate limit /api/auth/login 10 ครั้ง/นาที/IP | ก.2.1.4 | src/domain/identity/lockout-policy.ts, LoginAttempt, src/middleware.ts |
| FR-005 | ทุกคำขอ POST/PUT/PATCH/DELETE ต้องแนบ CSRF token (double-submit cookie + header x-csrf-token) มิฉะนั้นตอบ 403 CSRF_INVALID | ก.2.1.5 | src/middleware.ts, GET /api/csrf |
| FR-006 | ควบคุมสิทธิ์แบบ RBAC 3 บทบาท ทุก endpoint ประกาศสิทธิ์ (public/user/รายการ role) | ก.6.1.1, ก.7.1.3 | src/domain/identity/rbac.ts, withApi({ auth }) |
| FR-007 | นโยบายรหัสผ่าน: ผู้ใช้ทั่วไป ≥8 ตัวมีตัวอักษรและตัวเลข; ผู้ดูแล ≥12 ตัวผสม 4 ชนิด; ส่วนผู้ดูแล (/admin*, /api/admin*) จำกัดเฉพาะ IP ใน ADMIN_IP_ALLOWLIST (นอกนั้นตอบ 403 และบันทึก audit) | ก.6.1.4 | src/domain/identity/password-policy.ts, src/middleware.ts, /api/internal/security-event |
| FR-008 | ผู้ใช้ดู/แก้ชื่อแสดงของตนเอง | ก.3.3.1 | GET/PATCH /api/me |
3.2 ถาม-ตอบ (Q&A)
| รหัส | ข้อกำหนด | TOR | Implement |
|---|
| FR-010 | ผู้ใช้ส่งคำถามแล้วได้คำตอบแบบ streaming (SSE) จาก RAG pipeline พร้อมอ้างอิงที่ตรวจสอบย้อนกลับได้ ในบุคลิกบรรณารักษ์ | 2.1, 2.4, ก.4.5.2 | /chat, POST /api/chat/qa, src/application/chat/ask.ts |
| FR-011 | คำตอบแสดง 2 ส่วน: (1) รายการอ้างอิง ≤10 รายการ แสดง 3 รายการแรก + ปุ่ม “แสดงผลทั้งหมด/Expand All” (2) บทสรุป และข้อความเตือนท้ายคำตอบ | ก.3.6.2 | src/components/chat/AnswerParts.tsx, config retrieval.finalK (≤10) |
| FR-012 | Citation ในบทสรุปใช้รูปแบบมาตรฐาน {abbreviation} ({edition}) {volume}/{item}/{page} แสดงเป็นชิปสีทอง คลิกเปิด Modal เนื้อหาเต็มพร้อม metadata | ก.3.6.3, ก.4.6.3 | src/domain/ai/citation.ts, GET /api/chunks/[id] |
| FR-013 | แสดงข้อความเตือนใต้กล่องพิมพ์คำถามตลอดเวลา | ก.3.6.1 | src/components/chat/ChatApp.tsx + messages/*/ |
| FR-014 | ผู้ใช้ให้ Feedback 👍/👎 (พร้อมความเห็น) ต่อคำตอบ บันทึกลงตาราง Feedback (1 รายการต่อผู้ใช้ต่อข้อความ) | ก.3.8.1 | POST /api/messages/[id]/feedback |
| FR-015 | แสดง RAG Trace ของ 8 โหนด (เวลา ms และรายละเอียดแต่ละโหนด รวม model, prompt version, โทเคน) | ก.4.10.1 | Message.traceJson, SSE done.trace |
| FR-016 | ประวัติบทสนทนาของตนเอง แสดงวันหมดอายุ ลบ/เปลี่ยนชื่อได้; ระยะเก็บเริ่มต้น 90 วัน (retention.conversationDays) | ก.3.5.1 | /chat/history, GET/PATCH/DELETE /api/conversations[/id] |
| FR-017 | ผู้ใช้เข้าถึงได้เฉพาะบทสนทนาของตนเอง (ไม่ใช่เจ้าของ = 404 กัน IDOR) | ก.2.1.1 | loadOwnedConversation() ใน src/application/chat/ask.ts |
3.3 Dhamma Talk
| รหัส | ข้อกำหนด | TOR | Implement |
|---|
| FR-020 | สร้างบทธรรมบรรยายแบบ Artifact (Markdown) ทีละรอบ แสดงใน Response Modal แยก มีแท็บแก้ไข/ตัวอย่าง ผู้ใช้แก้ไขแล้วบันทึก และคำสั่งรอบถัดไปใช้ฉบับที่ผู้ใช้แก้ล่าสุดเป็นบริบท พร้อมข้อความเตือนในโหมดนี้ | ก.3.7.1–ก.3.7.4 | /chat/dhamma-talk, POST /api/chat/dhamma-talk, PATCH /api/conversations/[id] (artifactMd), DhammaTalkApp.tsx |
| FR-021 | สิทธิ์ใช้ Dhamma Talk แยกรายบัญชี ค่าเริ่มต้นปิด (User.dhammaTalkEnabled = false) ผู้ดูแลเปิด/ปิดได้ | ก.3.3.3, ก.6.1.3 | PATCH /api/admin/users/[id], ตรวจสิทธิ์ใน route |
3.4 AI Engine และการค้นคืน (RAG)
| รหัส | ข้อกำหนด | TOR | Implement |
|---|
| FR-030 | Orchestrator ด้วย LangGraph.js StateGraph 8 โหนด และ LLM แบบ model-agnostic ผ่าน interface LlmGateway (typhoon/openai/gemini/claude/mock) เปลี่ยนได้จาก config โดยไม่แก้โค้ด | ก.4.1.2, 4.4.6, 2.6, ก.6.2.3 | src/server/ai/graph.ts, src/server/llm/* |
| FR-031 | Hybrid Search: BM25 (ตัดคำไทยด้วย Intl.Segmenter) + pseudo-embedding 512 มิติ (feature hashing) รวมด้วย RRF (k=60) รองรับไทย–บาลี–อังกฤษด้วยพจนานุกรม synonym | ก.4.6.1 | src/domain/retrieval/{tokenizer,bm25,embedding,rrf,synonyms}.ts, src/server/retrieval/index.ts |
| FR-032 | Query Expansion/Rewriting: ขยายคำพ้อง + ขอคำค้นเพิ่มจาก LLM ด้วย prompt query-rewrite (เมื่อใช้ LLM จริง) | ก.4.7.2 | โหนด inputGuardrail/rewriteQuery |
| FR-033 | Re-ranker เป็นโมดูลแยก สลับได้ด้วย retrieval.reranker (heuristic ค่าเริ่มต้น หรือ tei เมื่อตั้ง TEI_RERANK_URL) | ก.4.7.1 | src/domain/retrieval/heuristic-reranker.ts, src/server/retrieval/{reranker,tei-reranker}.ts |
| FR-034 | ปรับคะแนนด้วย Precedence ของประเภทเอกสาร (ปรับรายเอกสารได้ 0–2) แล้วตัดเหลือ finalK ≤10 | ก.4.8.3, ก.6.3.3 | src/domain/retrieval/relevance-scoring.ts |
| FR-035 | บุคลิกบรรณารักษ์: ช่วยค้น/สรุปพร้อม Citation ไม่ชี้ขาดการตีความ ปฏิเสธคำถามใช่/ไม่ใช่ | ก.4.5.1, ก.4.5.3 | config/prompts/qa-persona.v*.md, guardrail yes_no_pattern |
3.5 Guardrails
| รหัส | ข้อกำหนด | TOR | Implement |
|---|
| FR-040 | ตอบเฉพาะคำถามด้านพระไตรปิฎก อรรถกถา หลักธรรม และแนวทางปฏิบัติ/ปรัชญาชีวิต (หัวข้อ scope_allow); คำถามนอกขอบเขตถูกปฏิเสธอย่างสุภาพ เมื่อใช้ LLM จริงยืนยันด้วย prompt guard-classifier | ก.4.3.1, ก.4.3.2 | src/domain/ai/guardrail-policy.ts, โหนด inputGuardrail |
| FR-041 | ป้องกัน Prompt Injection ทั้งทางตรง (รูปแบบ injection_pattern) และทางอ้อม (sanitize ตอนนำเข้า + ครอบบริบทด้วย delimiter <<<DATA ...>>> และระบุว่า “เป็นข้อมูล ไม่ใช่คำสั่ง”) | ก.4.4.1 | src/domain/ingestion/sanitize.ts, src/server/ai/context.ts |
| FR-042 | ปฏิเสธหัวข้อการเมือง การเปรียบเทียบศาสนา/นิกาย และสุขภาพจิต พร้อมข้อความเฉพาะหัวข้อ | ก.4.4.3 | GuardrailTopic kind refuse_sensitive (key politics, religion_comparison, mental_health) |
| FR-043 | Output guardrail: แทน [[cite:...]] ด้วย label มาตรฐาน ตัด citation ที่ไม่อยู่ในรายการอ้างอิง ติดป้าย “(ไม่มีอ้างอิง — โปรดตรวจสอบ)” ให้ย่อหน้าที่ไม่มีอ้างอิง และลดทอนถ้อยคำชี้ขาด | ก.4.5.3, ก.4.6.3 | resolveCitations(), evaluateOutput() |
| FR-044 | ผู้ดูแลเพิ่ม/แก้/ลบ/เปิดปิดหัวข้อขอบเขตและหัวข้อปฏิเสธ และทดสอบข้อความได้ทันที | ก.6.2.5, ก.4.4.1 | /admin/guardrails, /api/admin/guardrails* |
3.6 งบประมาณโทเคนและโหมดปิดบริการ
| รหัส | ข้อกำหนด | TOR | Implement |
|---|
| FR-050 | เพดานโทเคนรวม 24 ชั่วโมงล่าสุด ค่าเริ่มต้น 10,000,000 (tokens.dailyCap) | ก.3.4.1 | src/domain/ai/token-budget-policy.ts (checkDailyBudget), src/server/ai/availability.ts |
| FR-051 | เพดานโทเคนต่อ prompt: ถาม-ตอบ ≤10,000 และ Dhamma Talk ≤50,000 (schema บังคับช่วง) และปรับ Max Tokens ได้ | ก.4.2.1, ก.3.4.2, ก.6.2.1 | tokens.maxPerPromptQa, tokens.maxPerPromptDhammaTalk, tokens.maxOutput, fitContext() |
| FR-052 | เข้า Unavailable อัตโนมัติก่อนยอดจริงเกินเพดาน (คำขอที่ทำให้ยอดจองเกินเพดานถูกปฏิเสธก่อนเรียก LLM; margin ≤10%) พร้อมแจ้งเตือน auto_unavailable | ก.3.4.4 | checkAvailability(), reserveTokens() |
| FR-053 | ป้องกัน Cost Surge: จำกัดความยาว input (input.maxChars ค่าเริ่มต้น 2,000 → 400 INPUT_TOO_LONG) และ thinking budget (tokens.thinkingBudget) | ก.4.4.2 | โหนด checkAvailability, generate |
| FR-054 | ผู้ดูแลเปิด Unavailable Mode พร้อมข้อความ ไทย/อังกฤษ ทุกหน้าแสดงแบนเนอร์ (อ่าน /api/status) และ API แชตตอบ 503 | ก.3.4.3, ก.6.6.1 | /admin/unavailable, GET/PUT /api/admin/unavailable-mode, GET /api/status |
3.7 การเก็บรักษาข้อมูลและ PDPA
| รหัส | ข้อกำหนด | TOR | Implement |
|---|
| FR-060 | ลบบทสนทนาที่พ้น retentionUntil อัตโนมัติทุก retention.purgeIntervalMinutes (ค่าเริ่มต้น 60 นาที) และสั่งทันทีได้ | ก.3.5.1, ก.6.2.2 | src/application/privacy/purge.ts (เริ่มใน src/instrumentation-node.ts), POST /api/admin/maintenance/purge-conversations, npm run purge:conversations |
| FR-061 | ผู้ใช้ดาวน์โหลดสำเนาข้อมูลตนเอง (JSON) และยื่นคำขอ access_copy / erase / withdraw_consent | 14.4 | /privacy, GET /api/privacy/export, GET/POST /api/privacy/requests |
| FR-062 | ผู้ดูแลดูและดำเนินการคำขอ PDPA (สร้างไฟล์สำเนา, ลบบทสนทนา+ทำให้ข้อมูลผู้ใช้เป็นนิรนาม, ระงับบัญชีเมื่อถอนยินยอม) และสร้างคำขอแทนผู้ใช้ | 14.4, ก.6.1.2 | /admin/privacy-requests, /api/admin/privacy-requests* |
| FR-063 | เข้ารหัสอีเมล/ชื่อด้วย AES-256-GCM (DATA_ENCRYPTION_KEY) ค้นหาด้วย emailHash; สร้างสำเนาข้อความที่ปิดบัง PII (Message.contentMasked) | 14.3 | src/server/crypto.ts, src/server/repos/user-repo.ts, src/domain/privacy/pii-masking.ts |
| FR-064 | แบนเนอร์ยินยอมคุกกี้ โหลด Google Analytics 4 เฉพาะเมื่อผู้ใช้ยินยอมและตั้ง NEXT_PUBLIC_GA_ID; หน้า /privacy-policy ระบุผู้ควบคุม/ผู้ประมวลผลข้อมูล | ก.1.1.3, ก.1.5.1, 14.1 | src/components/layout/Banners.tsx, /privacy-policy |
3.8 ฐานองค์ความรู้และการนำเข้า
| รหัส | ข้อกำหนด | TOR | Implement |
|---|
| FR-070 | อัปโหลดเอกสาร PDF / TXT / Markdown / TEI-XML / DOCX ≤20MB พร้อม metadata แล้วนำเข้าแบบ async | ก.4.8.1, ก.6.3.1 | /admin/knowledge-base, POST /api/admin/knowledge-base/upload, src/server/ingestion/parsers/index.ts |
| FR-071 | Chunking: พระไตรปิฎก/อรรถกถาแบ่ง 1 ข้อ = 1 chunk ไม่ตัดกลางข้อ (ข้อยาว >3,000 tokens ติดธง oversized); เอกสารอื่นใช้ semantic chunking (~400 tokens ตัดเมื่อ cosine ระหว่างย่อหน้า <0.35) | ก.4.8.2, ก.4.6.2 | src/domain/ingestion/chunking-policy.ts |
| FR-072 | Metadata: ปิฎก นิกาย สูตร เล่ม ข้อ หน้า ประเภทเอกสาร ฉบับ ตัวย่อ เล่ม มมร. คำบาลี และ precedence แก้ไขได้ (อัปเดตดัชนีทันที) | ก.4.8.3, ก.6.3.1, ก.6.3.3 | PATCH /api/admin/knowledge-base/[id] |
| FR-073 | Pipeline ปรับดัชนี: งาน ingest / reindex_all / reindex_selected / delete แสดงสถานะรายเอกสาร (stage parse→sanitize→chunk→embed→index) รายการที่ล้มเหลว และรันซ้ำเฉพาะที่ล้มเหลว | ก.4.5.3, ก.4.7.3, ก.6.3.2 | src/server/ingestion/pipeline.ts, /admin/knowledge-base/jobs, /api/admin/knowledge-base/{reindex,jobs*} |
| FR-074 | รายงานความครอบคลุมเทียบพระไตรปิฎกและอรรถกถาฉบับ มมร. 91 เล่ม | 4.5.2, 7.2.5, ข.1 | GET /api/admin/knowledge-base/coverage, src/application/ingestion/coverage.ts, npm run report:ingestion |
| FR-075 | Sanitize ข้อความก่อนนำเข้า: ตัด control/invisible chars และบรรทัดที่เข้ารูปแบบคำสั่งแฝง บันทึกจำนวนบรรทัดที่ตัด | ก.4.4.1 | src/domain/ingestion/sanitize.ts, KnowledgeDocument.sanitizedLines |
3.9 การตั้งค่า AI และ Prompt
| รหัส | ข้อกำหนด | TOR | Implement |
|---|
| FR-080 | ผู้ดูแลปรับค่า AI (provider/model, เพดานโทเคน, retention, retrieval, เกณฑ์ประเมิน ฯลฯ) มีผลทันทีโดยไม่ redeploy ทุกการแก้สร้าง ConfigSnapshot และย้อนกลับได้ | ก.6.2.1–ก.6.2.3, ก.6.2.6 | /admin/ai-config, /api/admin/ai-config*, src/server/config/ai-config-store.ts |
| FR-081 | ทดสอบการเชื่อมต่อ LLM และแสดงรายการ provider/model ที่ adapter รองรับ (ระบุว่าตั้ง API key แล้วหรือไม่ โดยไม่เปิดเผยค่า) | ก.6.2.3, 4.6.1 | POST /api/admin/ai-config/test-llm, GET /api/admin/ai-config/models |
| FR-082 | Prompt ไม่ hard-code: เก็บเป็นเวอร์ชัน (PromptVersion) ดู/เพิ่ม/แก้ (สร้างเวอร์ชันใหม่)/diff/activate (rollback) พร้อมผู้แก้และเวลา | ก.4.9.1, ก.6.2.4 | /admin/prompts, /api/admin/prompts* |
| FR-083 | Remote configuration ด้วยสคริปต์: npm run prompts:publish อ่าน config/prompts/*.md สร้างเวอร์ชันเมื่อเนื้อหาต่าง | ก.4.9.2 | scripts/prompts/publish.ts |
| FR-084 | กำหนดอัตราค่าโทเคนต่อ model (input/thinking/output ต่อ 1M) ใช้ประมาณค่าใช้จ่าย | ก.7.3.3 | /admin/pricing, /api/admin/pricing*, LlmPricing |
3.10 การประเมินคุณภาพ AI
| รหัส | ข้อกำหนด | TOR | Implement |
|---|
| FR-090 | ชุดทดสอบ 7 ชุด: DS-1 ธรรมศึกษาปรนัย 100, NT-2 นักธรรมตอบสั้น 50, RET-1/2/3 Retrieval วินัย/สุตตันตะ/อภิธรรม ชุดละ 100, GR-1 นอกขอบเขต 100, GR-2 หัวข้อสุ่มเสี่ยง 50 | ก.5.1.1–ก.5.1.7 | seed/benchmarks/*.csv, seed/benchmarks/SOURCES.md |
| FR-091 | จัดการชุดและข้อทดสอบผ่านเว็บ (เพิ่ม/แก้/ลบ) นำเข้า CSV และดาวน์โหลดแม่แบบ ทุกการเปลี่ยนข้อทดสอบเพิ่ม version | ก.5.2.1, ก.6.4.1, ก.5.2.4 | /api/admin/evaluation/datasets* |
| FR-092 | 3 workflow: Accuracy (exact match ตัวเลือก), Recall@5 (retrieval), LLM-as-Judge 0–3 และ Refusal (guardrail) ผ่าน pipeline จริง | ก.5.2.2 | src/application/evaluation/runner.ts (evaluateItem), src/domain/evaluation/scorers.ts |
| FR-093 | สั่งรันรายชุดแบบ async บันทึกผลรายข้อ + prompt version + dataset version + model + judge model | ก.5.2.3, ก.5.2.4, ก.6.4.2 | POST/GET /api/admin/evaluation/runs* |
| FR-094 | เปรียบเทียบผลระหว่าง 2 รอบของชุดเดียวกัน (รายข้อ + delta + เวอร์ชัน) | ก.6.4.3 | GET /api/admin/evaluation/compare |
| FR-095 | เกณฑ์ขั้นต่ำ (80% / Judge 2.00) และค่ารับประกันของผู้รับจ้าง แสดงผ่าน/ไม่ผ่านเกณฑ์ตรวจรับ | ก.5.3, ก.5.3.1, 16.2 | GET /api/admin/evaluation/thresholds, eval.guaranteed |
| FR-096 | รายงาน AI Security: รัน GR-1 + GR-2 + ชุด Prompt Injection 20 เคส แสดงอัตราป้องกันต่อชุด/ประเภทการโจมตี | 7.2.7 | GET/POST /api/admin/evaluation/security-report |
| FR-097 | ส่งออกรายงาน Baseline/Acceptance และ Guardrail เป็น Markdown ลง docs/deliverables | 4.7.7, 7.2.3 | POST /api/admin/evaluation/export, npm run report:evaluation |
3.11 ผู้ดูแลผู้ใช้ และบันทึกการตรวจสอบ (Audit)
| รหัส | ข้อกำหนด | TOR | Implement |
|---|
| FR-100 | ค้นหา/ดู/แก้/ระงับ/ลบ (soft) ผู้ใช้ แสดงสถานะ วันสมัคร การใช้งานล่าสุด สิทธิ์ Dhamma Talk | ก.6.1.2 | /admin/users, /admin/users/[id], /api/admin/users* |
| FR-101 | การเปลี่ยน role ทำได้เฉพาะ SUPER_ADMIN และบันทึก audit role_changed | ก.6.1.1 | PATCH /api/admin/users/[id] |
| FR-102 | การเปิดดูบทสนทนาของผู้ใช้โดยผู้ดูแลถูกบันทึก audit conversation_accessed | ก.6.5.1 | GET /api/admin/users/[id]/conversations |
| FR-110 | บันทึก Audit: login/logout สำเร็จ-ไม่สำเร็จ สิทธิ์ config/prompt เอกสาร การเข้าถึงบทสนทนา การสั่งประเมิน คำขอ PDPA ทำลายข้อมูล defect พร้อม timestamp ผู้กระทำ IP ประเภท ก่อน/หลัง ผลลัพธ์ | ก.6.5.1, ก.6.5.2 | src/server/audit/audit-logger.ts, AuditLog |
| FR-111 | Audit Log แก้ไข/ลบไม่ได้ (SQLite trigger audit_no_update/audit_no_delete) และตรวจความสมบูรณ์ด้วย hash chain (sha256) | ก.6.5.3 | prisma/migrations/20261002110735_audit_immutable, src/domain/audit/hash-chain.ts, GET /api/admin/audit-log/verify |
| FR-112 | ค้นหา/กรอง Audit Log และส่งออก CSV หรือพิมพ์เป็น PDF | ก.6.5.4 | /admin/audit-log, GET /api/admin/audit-log/export |
3.12 Dashboard และการแจ้งเตือน
| รหัส | ข้อกำหนด | TOR | Implement |
|---|
| FR-120 | Dashboard (เฉพาะ ADMIN/SUPER_ADMIN): บัญชีทั้งหมด DAU MAU sessions queries เฉลี่ยต่อ session แยกโหมด เลือกช่วง วัน/สัปดาห์/เดือน/ปี/กำหนดเอง และเทียบช่วงก่อนหน้า ข้อมูลอ่านสดจากฐานข้อมูล | ก.7.1.1, ก.7.1.2, ก.7.1.3, ก.7.2.1, ก.7.2.2 | /admin/dashboard, GET /api/admin/dashboard/usage |
| FR-121 | โทเคน input/thinking/output รายวัน/เดือนเทียบเพดาน โทเคนเฉลี่ยต่อคำถาม/บทสนทนาเทียบเป้า ประมาณการค่าใช้จ่าย และ Top users | ก.7.3.1–ก.7.3.4 | GET /api/admin/dashboard/tokens |
| FR-122 | อัตรา Feedback 👍/👎 | ก.3.8.1 | GET /api/admin/dashboard/feedback |
| FR-130 | แจ้งเตือนทางอีเมล (SMTP เมื่อตั้งค่า มิฉะนั้นบันทึกใน AlertEmailLog): โทเคนถึงเกณฑ์ (tokens.alertThresholdPct), ค่าใช้จ่ายถึงเกณฑ์, error rate แชต >5% ใน 15 นาที, เข้า Unavailable อัตโนมัติ, งานนำเข้าล้มเหลว | ก.4.10.2, ก.7.3.5 | src/server/notification/index.ts, src/server/ai/usage.ts, /admin/alerts |
3.13 การปฏิบัติการ (Ops) และเว็บไซต์
| รหัส | ข้อกำหนด | TOR | Implement |
|---|
| FR-140 | Defect Log: ความรุนแรง critical/high = 24 ชม., medium = 48 ชม., low = 80 ชม. ทำการ (10 วันทำการ) คำนวณ deadline แบบชั่วโมงทำการ ปิด high/critical ต้องมี resolutionNote การยืนยันของมหาวิทยาลัยคำนวณชั่วโมงเกินและค่าปรับ 0.025%/ชม. | 12.3, 12.4, 10.2, 4.7.8 | /admin/defect-log, src/domain/report/sla-policy.ts |
| FR-141 | รายงาน SLA: Availability, p95 latency, TTFT p95, error rate, defect เกิน SLA ส่งออกรายงานรายเดือน | 12.1, 12.2, 12.5 | /admin/sla-report, /api/admin/sla-report*, npm run report:sla |
| FR-142 | สำรองฐานข้อมูลและไฟล์อัปโหลด (SUPER_ADMIN) พร้อมคำสั่งกู้คืน | ก.1.6.3 | /admin/backup, scripts/backup-db.sh, scripts/restore-db.sh |
| FR-143 | ทำลายข้อมูลตามขอบเขตที่เลือกพร้อมหนังสือรับรอง (SUPER_ADMIN, ต้องพิมพ์ข้อความยืนยัน) | 4.11.3 | /admin/data-destruction, POST /api/admin/data-destruction |
| FR-144 | Health/Status/OpenAPI: /api/health (สถานะ DB และจำนวน chunk), /api/status (Unavailable + % งบโทเคน ไม่เปิดเผยความลับ), /api/openapi.json | ก.4.1.1 | src/app/api/{health,status,openapi.json} |
| FR-145 | Logging: JSON lines ลง logs/app.log + stdout และบันทึกทุกคำขอ API ลง ApiRequestLog (path, status, durationMs, ttftMs) | ก.4.10.1 | src/server/logger.ts, withApi |
| FR-150 | สองภาษา ไทย/อังกฤษ (/th, /en) มีเมนูสลับภาษาทุกหน้า | ก.3.2.1 | next-intl, messages/{th,en}/*.json, LanguageSwitcher.tsx |
| FR-151 | ธีมมืด/สว่าง พร้อมปุ่มสลับทุกหน้า | ก.3.1.3 | next-themes, ThemeToggle.tsx |
| FR-152 | Responsive รองรับมือถือ แท็บเล็ต เดสก์ท็อป (390/768/1280 px) ไม่มี horizontal scroll | ก.1.2.1, 4.3.3 | Tailwind, Playwright 3 viewport |
| FR-153 | หน้า /tor (ตาราง 200 ข้อ ค้นหา/กรอง/ส่งออก CSV) และศูนย์เอกสาร /documents (ดาวน์โหลด .md / พิมพ์ PDF) | 7.5, 4.4 | src/app/[locale]/{tor,documents} |
| FR-154 | หน้า /help วิธีใช้และช่องทางติดต่อช่วงรับประกัน | 11.2 | src/app/[locale]/help |
4. ความต้องการที่ไม่ใช่เชิงหน้าที่ (Non-Functional Requirements)
| รหัส | หมวด | ข้อกำหนด | TOR | วิธีรองรับ/ตรวจวัดในระบบสาธิต |
|---|
| NFR-001 | ประสิทธิภาพ | FCP ≤5 วินาที, TTFT ≤15 วินาที, Error rate ≤5% ที่ p95 | 12.2 | วัด TTFT ทุกคำขอ (Message.ttftMs, ApiRequestLog.ttftMs); E2E ตรวจ FCP <5s; npm run report:perf → /documents/performance-report |
| NFR-002 | รองรับผู้ใช้พร้อมกัน | ≥500 concurrent sessions | ก.1.4.1, 4.7.6 | ทดสอบโหลดระดับเครื่องเดียวด้วย scripts/perf/load-test.ts; ระดับ 500 ต้องใช้สถาปัตยกรรม Production (HPA) |
| NFR-003 | ขนาดข้อมูลผู้ใช้ | รองรับ 1,000,000 บัญชี | ก.1.4.2 | npm run seed:perf-users -- --count=1000000 ลง prisma/perf.db แล้ววัดเวลาค้นหา (index emailHash unique) |
| NFR-004 | ความพร้อมใช้งาน | ≥95% ต่อเดือน | 12.1, ก.1.6.1 | คำนวณจาก uptime ของ process + ช่วง 5xx (/admin/sla-report); Production ใช้ replica ≥2 + probe |
| NFR-005 | การขยายระบบ | Microservices, Horizontal/Auto Scaling | ก.1.3.1, ก.1.3.2, ก.1.6.2 | โค้ดแบ่ง bounded context; Helm chart 8 deployment + HPA (CPU 70%) ที่ infra/helm/mbu-buddhist-ai/ |
| NFR-006 | ความมั่นคงปลอดภัย | OWASP Top 10, TLS, Server-side validation, CSRF, Brute force, Security headers, Rate limit, WAF | ก.2.1.1–ก.2.1.5, ก.2.2.1, ก.1.1.2 | zod ทุก endpoint, Prisma (parameterized), CSP/X-Frame-Options/nosniff/Referrer-Policy/Permissions-Policy/COOP (next.config.ts), HSTS เมื่อ ENABLE_HSTS=1, rate limit 10/60/600 ต่อนาที, TLS self-signed (server.https.js); WAF เป็นตัวอย่างใน Helm values |
| NFR-007 | ความเป็นส่วนตัว | PDPA: minimisation, consent, เข้ารหัส in transit/at rest, ปิดบัง PII, สิทธิเจ้าของข้อมูล | 14.1–14.4, ก.1.1.3 | FR-001, FR-061–FR-064 |
| NFR-008 | การเข้าถึง | Contrast ≥4.5:1 (ข้อความปกติ) / ≥3:1 (ข้อความใหญ่) ทั้ง light/dark; มาตรฐานเว็บไซต์ภาครัฐ 3.0 | ก.3.1.1, ก.1.1.1 | src/styles/tokens.ts (contrastPairs) + tests/unit/contrast.test.ts; ประเมินตนเองใน /documents/test-checklist |
| NFR-009 | ความเข้ากันได้ | Chrome Android/Safari iOS, Chrome/Firefox/Safari/Edge | ก.1.2.1, ก.1.2.2 | ทดสอบอัตโนมัติด้วย Chromium 3 viewport (เบราว์เซอร์อื่นยังไม่ได้ทดสอบใน sandbox) |
| NFR-010 | การบำรุงรักษา | แยกชั้น, Unit coverage ≥80% (ไม่นับ config) | 4.7.3 | coverage gate lines/functions/statements ≥80%, branches ≥75% (vitest.config.ts) |
| NFR-011 | การติดตาม/วินิจฉัย | Log เพียงพอต่อการวินิจฉัย | ก.4.10.1 | FR-145, RAG Trace (FR-015), x-request-id ทุกคำขอ |
| NFR-012 | ความยืดหยุ่นของโมเดล | เปลี่ยน LLM/องค์ประกอบ AI โดยไม่กระทบระบบ | 4.4.6, 2.6 | LlmGateway, SearchGateway, RerankerGateway, NotificationGateway |
| NFR-013 | ความถูกต้องของ Audit | เก็บ ≥90 วัน แก้/ลบไม่ได้ | ก.6.5.3 | ไม่มีกลไกลบ AuditLog (trigger ห้าม UPDATE/DELETE; ทำลายข้อมูลไม่ลบ AuditLog) |
| NFR-014 | การสำรองและกู้คืน | คู่มือ + ทดสอบ restore ≥1 ครั้ง | ก.1.6.3 | /documents/backup-restore-guide, npm run report:restore-test → /documents/restore-test-report |
| NFR-015 | ความน่าเชื่อถือของคำตอบ | ทุกคำตอบอ้างอิงตรวจสอบย้อนกลับได้ ไม่ตัดตอนพระไตรปิฎก | 2.1, ก.4.6.2 | FR-012, FR-043, FR-071 |
| NFR-016 | ภาษาและวันที่ | วันที่แสดงเป็นพุทธศักราชเมื่อ locale = th | ก.3.2.1 | fmtDate(d, locale) |
5. สถาปัตยกรรมระบบ
5.1 แผนภาพสถาปัตยกรรมระบบสาธิต
flowchart TB
U["ผู้ใช้ / ผู้ดูแล (เบราว์เซอร์)"] -->|HTTPS (server.https.js :3443) หรือ HTTP| MW["middleware.ts<br/>requestId · rate limit · CSRF · admin IP allowlist · locale"]
MW --> PAGES["หน้าเว็บ src/app/[locale]/*"]
MW --> API["Route Handlers src/app/api/* (withApi: RBAC · zod · audit · ApiRequestLog)"]
API --> UC["Use cases src/application/*"]
UC --> GRAPH["LangGraph StateGraph 8 โหนด<br/>src/server/ai/graph.ts"]
GRAPH --> IDX["KnowledgeIndex (in-memory)<br/>BM25 + pseudo-embedding 512 + RRF"]
GRAPH --> RR["Reranker: Heuristic | TEI"]
GRAPH --> LLM["LlmGateway<br/>Typhoon (OpenAI-compatible) · OpenAI · Gemini · Claude · Mock"]
UC --> DB[("SQLite ผ่าน Prisma<br/>prisma/dev.db")]
GRAPH --> DB
UC --> ING["Ingestion pipeline<br/>parse → sanitize → chunk → embed → index"]
ING --> STORE[["storage/uploads"]]
ING --> IDX
UC --> NOTI["NotificationGateway<br/>AlertEmailLog | SMTP"]
API --> LOG[["logs/app.log (JSON lines)"]]
LLM -.->|HTTPS| TY["api.opentyphoon.ai/v1"]
5.2 การแมปสถาปัตยกรรม Production ↔ ระบบสาธิต
| Production (Solution Architecture) | ระบบสาธิต |
|---|
| 8 microservices (NestJS) บน Kubernetes: identity, chat, ai-orchestrator, retrieval, ingestion, evaluation, config, report | Next.js 15 แอปเดียว แบ่ง bounded context ใน src/domain, src/application, src/server + Helm chart 8 deployment ที่ infra/helm/mbu-buddhist-ai/ |
| PostgreSQL 16 / Redis 7 (token budget, lockout, config pub/sub, BullMQ) | SQLite (Prisma) / ตาราง TokenUsage, LoginAttempt; config อ่านจาก DB ทุกคำขอ (hot reload); คิวงาน = IngestionJob ประมวลผล async ใน process เดียว |
| Elasticsearch BM25 + kNN (bge-m3) + RRF | BM25 in-memory (ตัดคำไทย Intl.Segmenter) + pseudo-embedding 512 มิติ + RRF (SearchGateway; มี ElasticsearchSearchAdapter เป็นโครง) |
| bge-reranker-v2-m3 (TEI) | HeuristicReranker (สลับเป็น TeiRerankerAdapter ได้เมื่อตั้ง TEI_RERANK_URL) |
| LangGraph.js | LangGraph.js จริง (@langchain/langgraph) |
| SMTP / S3 / cert-manager / Prometheus | AlertEmailLog (หรือ SMTP เมื่อตั้งค่า) / storage/uploads / self-signed TLS / logs/app.log + ApiRequestLog |
| GitHub Actions + Helm + HPA + WAF | ไฟล์ส่งมอบ .github/workflows/ci-cd.yml, infra/helm/ (ยังไม่ได้ deploy จริง และยังไม่ได้รันบน GitHub Actions) |
5.3 Workflow: ถาม-ตอบผ่าน RAG pipeline 8 โหนด
ลำดับโหนดใน src/server/ai/graph.ts: checkAvailability → inputGuardrail → rewriteQuery → hybridSearch → rerank → generate → outputGuardrail → persist โดย refusal ที่โหนด 1–2 ข้ามไปโหนด persist ทันที (conditional edge) ก่อนเปิด SSE ระบบตรวจ availability ล่วงหน้าเพื่อให้ตอบ HTTP 503/400 เป็น JSON ได้
sequenceDiagram
autonumber
actor U as ผู้ใช้ (/chat)
participant API as POST /api/chat/qa (withApi)
participant UC as askStream (application/chat/ask.ts)
participant G as ragGraph (LangGraph)
participant IDX as KnowledgeIndex
participant L as LlmGateway (Typhoon)
participant DB as SQLite (Prisma)
U->>API: { question, conversationId?, locale } + cookie + x-csrf-token
API->>UC: ตรวจสิทธิ์ user + zod
UC->>DB: โหลดบทสนทนา (ownership) + getAiConfig()
UC->>UC: checkAvailability (unavailable / เพดาน 24 ชม. / input.maxChars)
alt ไม่ผ่าน
UC-->>U: HTTP 503 SERVICE_UNAVAILABLE หรือ 400 INPUT_TOO_LONG (บันทึก refusal)
else ผ่าน
UC-->>U: เปิด SSE (text/event-stream)
UC->>G: runRagGraph(input) + reserveTokens()
G->>G: 1 checkAvailability (trace)
G->>DB: 2 inputGuardrail โหลด GuardrailTopic
opt LLM จริง
G->>L: guard-classifier + query-rewrite (ขนานกัน)
end
alt ถูกปฏิเสธ (scope/injection/sensitive/yes_no)
G->>DB: 8 persist (Message.refusalReason)
G-->>U: event: refusal
else ผ่าน
G->>G: 3 rewriteQuery (synonym ไทย-บาลี-อังกฤษ + LLM queries)
G->>IDX: 4 hybridSearch BM25 + vector + RRF (candidateK=50)
G->>G: 5 rerank (Heuristic) + precedence → finalK ≤10
G-->>U: event: references
G->>L: 6 generate (persona + DATA delimiters, fitContext ≤ maxPerPrompt)
loop streaming
L-->>G: delta
G-->>U: event: delta (วัด TTFT)
end
G->>G: 7 outputGuardrail (resolveCitations, evaluateOutput)
G->>DB: 8 persist (Message + contentMasked + Citation + TokenUsage + traceJson)
end
G->>DB: afterUsageRecorded (threshold / error-rate alerts)
UC-->>U: event: done {conversationId, messageId, answer, references, usage, trace, ttftMs, latencyMs, model, promptVersion}
end
Note over UC,U: ข้อผิดพลาดระหว่างสร้างคำตอบ → event: error {code: GENERATION_FAILED}
Dhamma Talk (POST /api/chat/dhamma-talk) ใช้กราฟเดียวกันด้วย mode = DHAMMA_TALK: โหนด rewriteQuery เพิ่มหัวเรื่องของ artifact เป็นคำค้น โหนด generate ใช้ prompt dhamma-talk พร้อม artifact ฉบับที่ผู้ใช้แก้ล่าสุดครอบด้วย <<<ARTIFACT>>> เพดาน tokens.maxPerPromptDhammaTalk และโหนด persist บันทึก Conversation.artifactMd
5.4 Workflow: การนำเข้าเอกสาร (Ingestion)
sequenceDiagram
autonumber
actor A as ผู้ดูแล (/admin/knowledge-base)
participant API as POST /api/admin/knowledge-base/upload
participant FS as storage/uploads
participant DB as SQLite
participant Q as enqueueJob (job chain ใน process)
participant P as pipeline.ts
participant IDX as KnowledgeIndex
A->>API: multipart: file (pdf/txt/md/xml/docx ≤20MB) + metadata
API->>FS: บันทึกไฟล์
API->>DB: KnowledgeDocument (pending) + IngestionJob(ingest) + IngestionJobItem + audit document_imported
API-->>A: { document, jobId }
API->>Q: enqueueJob(jobId)
Q->>P: runJob
P->>DB: item.stage = parse
P->>P: parseFile (unpdf / mammoth / TEI→marker / txt) + stripFrontmatter
P->>P: sanitize (ตัด control chars + บรรทัดคำสั่งแฝง)
P->>P: chunk (ChunkingPolicy: ตามข้อ หรือ semantic)
P->>P: embed (pseudo-embedding 512 มิติ)
P->>DB: index: transaction ลบ/อัปเดต/สร้าง Chunk (คง id ของ chunk ที่เนื้อหาเดิม)
P->>IDX: reindexDocument(documentId)
alt สำเร็จทุก item
P->>DB: job.status = completed
else มีรายการล้มเหลว
P->>DB: job.status = partial | failed + item.errorMsg
P->>DB: AlertEmailLog (ingestion_failed)
end
A->>API: GET /api/admin/knowledge-base/jobs/[id] (ติดตามสถานะ) / POST .../retry-failed
5.5 Workflow: การประเมินคุณภาพ AI (Evaluation)
sequenceDiagram
autonumber
actor A as ผู้ดูแล (/admin/evaluation) หรือ CLI (scripts/eval/run-acceptance.ts)
participant API as POST /api/admin/evaluation/runs
participant R as runner.ts (createRun / executeRun)
participant G as ragGraph
participant J as Judge LLM
participant DB as SQLite
A->>API: { datasetId, label: baseline | acceptance | adhoc }
API->>R: createRun (บันทึก datasetVersion, promptKey/promptVersion, model, judgeModel) + audit evaluation_run
API-->>A: { run (queued) }
R->>DB: status = running
loop ทุกข้อในชุด
alt accuracy (DS-1)
R->>G: answerStyle = mcq, skipInputGuard → exactMatchScore
else retrieval (RET-1/2/3)
R->>G: stopAfter = rerank → recallAtK(top-5, expectedRefs)
else guardrail (GR-1/GR-2/INJ)
R->>G: stopAfter = guard → refusalScore
else judge (NT-2)
R->>G: ถาม-ตอบเต็ม pipeline
R->>J: prompt judge → คะแนน 0–3 (mock: token overlap)
end
R->>DB: EvaluationResult (botAnswer, retrievedIds, score, passed, judgeReason, latencyMs)
end
R->>DB: EvaluationRun.score / passed (เทียบ threshold) / finishedAt
A->>API: GET /runs/[id] · GET /compare?runA&runB · GET /thresholds · POST /export
5.6 การจัดการความลับ
- ไฟล์
.env ไม่อยู่ใน repository มีเพียง .env.example ที่ระบุชื่อคีย์ API key ของ LLM (เช่น TYPHOON_API_KEY) เป็นค่าที่มหาวิทยาลัยจัดหาและใส่เอง (TOR 4.6.1)
- ระบบไม่ส่งค่า API key ออกทาง API ใด (
/api/admin/ai-config/models คืนเพียง configured: true/false)
- Production: เก็บใน Kubernetes Secret
mbu-buddhist-ai-secrets (ดู infra/helm/mbu-buddhist-ai/values.yaml → existingSecret)
6. พจนานุกรมข้อมูล (Data Dictionary)
ตารางต่อไปนี้ สร้างจากไฟล์ prisma/schema.prisma จริง ด้วยสคริปต์แยกวิเคราะห์ (parse) ทุกบรรทัด model { ... } — ครบ 25 model / 295 field (รวม field ความสัมพันธ์) คอลัมน์ “ข้อกำหนด/ค่าเริ่มต้น” คัดลอกจาก attribute ของ Prisma ตามตัวอักษร คอลัมน์ “คำอธิบาย” ใช้ความเห็น (comment) ใน schema เมื่อมี และสำหรับ field ที่ไม่มีความเห็น ผู้จัดทำเขียนคำอธิบายจากการใช้งานจริงในโค้ด
ข้อสังเกตทั่วไป:
- SQLite ไม่มีชนิด enum จึงเก็บค่าที่เป็นรายการเป็น
String และตรวจค่าด้วย zod ในชั้น application/API (ค่าที่อนุญาตระบุในคำอธิบาย)
- ฟิลด์ JSON (เช่น
patterns, choices, expectedRefs, traceJson, before/after, embedding) เก็บเป็น String ที่เป็น JSON
- ตาราง
AuditLog มี trigger ระดับฐานข้อมูลห้าม UPDATE/DELETE (migration 20261002110735_audit_immutable)
- คอลัมน์
emailEnc / displayNameEnc เข้ารหัส AES-256-GCM ชั้น repository เท่านั้นที่ถอดรหัส
User (กลุ่ม Identity)
| ฟิลด์ | ชนิด | ข้อกำหนด/ค่าเริ่มต้น | คำอธิบาย |
|---|
id | String | @id @default(uuid()) | รหัสอ้างอิงของระเบียน (UUID) |
emailHash | String | @unique | sha256(lowercase email) ใช้ค้นหา/ล็อกอิน |
emailEnc | String | — | อีเมลเข้ารหัส AES-256-GCM (14.3) |
displayNameEnc | String | — | ชื่อแสดง เข้ารหัส |
passwordHash | String? | — | null สำหรับบัญชี OAuth |
provider | String | @default("local") | local | google | facebook | mock |
providerId | String? | — | รหัสผู้ใช้ฝั่งผู้ให้บริการ OAuth |
role | String | @default("GENERAL_USER") | SUPER_ADMIN | ADMIN | GENERAL_USER |
status | String | @default("active") | active | suspended | deleted |
dhammaTalkEnabled | Boolean | @default(false) | ก.3.3.3 default ปิด |
consentVersion | String? | — | เวอร์ชันนโยบายที่ยินยอม |
consentAt | DateTime? | — | วันเวลาที่ให้ความยินยอมตามนโยบายความเป็นส่วนตัว |
consentWithdrawnAt | DateTime? | — | วันเวลาที่ถอนความยินยอม |
lastLoginAt | DateTime? | — | วันเวลาที่เข้าสู่ระบบครั้งล่าสุด |
createdAt | DateTime | @default(now()) | วันเวลาที่สร้างระเบียน |
updatedAt | DateTime | @updatedAt | วันเวลาที่แก้ไขล่าสุด (Prisma อัปเดตอัตโนมัติ) |
deletedAt | DateTime? | — | วันเวลาที่ลบแบบ soft delete (null = ยังไม่ลบ) |
conversations | Conversation[] | — | ความสัมพันธ์ไปยัง Conversation (หนึ่งต่อหลาย) |
feedbacks | Feedback[] | — | ความสัมพันธ์ไปยัง Feedback (หนึ่งต่อหลาย) |
tokenUsages | TokenUsage[] | — | ความสัมพันธ์ไปยัง TokenUsage (หนึ่งต่อหลาย) |
privacyRequests | PrivacyRequest[] | — | ความสัมพันธ์ไปยัง PrivacyRequest (หนึ่งต่อหลาย) |
ดัชนี/ข้อจำกัดระดับตาราง: @@unique([provider, providerId]), @@index([role, status]), @@index([lastLoginAt])
LoginAttempt (กลุ่ม Identity)
| ฟิลด์ | ชนิด | ข้อกำหนด/ค่าเริ่มต้น | คำอธิบาย |
|---|
id | String | @id @default(uuid()) | รหัสอ้างอิงของระเบียน (UUID) |
emailHash | String | — | sha256 ของอีเมลที่ใช้พยายามเข้าสู่ระบบ |
ip | String | — | IP ของผู้พยายามเข้าสู่ระบบ |
success | Boolean | — | ผลการเข้าสู่ระบบ (ใช้คำนวณ lockout) |
createdAt | DateTime | @default(now()) | วันเวลาที่สร้างระเบียน |
ดัชนี/ข้อจำกัดระดับตาราง: @@index([emailHash, createdAt]), @@index([ip, createdAt])
Conversation (กลุ่ม Chat)
| ฟิลด์ | ชนิด | ข้อกำหนด/ค่าเริ่มต้น | คำอธิบาย |
|---|
id | String | @id @default(uuid()) | รหัสอ้างอิงของระเบียน (UUID) |
userId | String | — | เจ้าของบทสนทนา (FK → User) |
user | User | @relation(fields: [userId], references: [id]) | ความสัมพันธ์ไปยัง User |
mode | String | — | QA | DHAMMA_TALK |
title | String | — | ชื่อบทสนทนา (40 ตัวอักษรแรกของคำถามแรก) |
artifactMd | String? | — | Dhamma Talk: artifact ล่าสุด (markdown) |
retentionUntil | DateTime | — | createdAt + retentionDays (ก.3.5.1) |
createdAt | DateTime | @default(now()) | วันเวลาที่สร้างระเบียน |
updatedAt | DateTime | @updatedAt | วันเวลาที่แก้ไขล่าสุด (Prisma อัปเดตอัตโนมัติ) |
messages | Message[] | — | ความสัมพันธ์ไปยัง Message (หนึ่งต่อหลาย) |
ดัชนี/ข้อจำกัดระดับตาราง: @@index([userId, updatedAt]), @@index([retentionUntil])
Message (กลุ่ม Chat)
| ฟิลด์ | ชนิด | ข้อกำหนด/ค่าเริ่มต้น | คำอธิบาย |
|---|
id | String | @id @default(uuid()) | รหัสอ้างอิงของระเบียน (UUID) |
conversationId | String | — | FK → Conversation |
conversation | Conversation | @relation(fields: [conversationId], references: [id], onDelete: Cascade) | ความสัมพันธ์ไปยัง Conversation |
role | String | — | user | assistant | system |
content | String | — | เนื้อหาข้อความ (คำถาม/คำตอบ/ข้อความปฏิเสธ) |
contentMasked | String? | — | สำเนาปิดบัง PII สำหรับใช้ปรับปรุงระบบ (14.3) |
refusalReason | String? | — | scope | injection | sensitive_politics | sensitive_religion | sensitive_mental | yes_no | budget | unavailable |
promptVersionId | String? | — | PromptVersion ที่ใช้สร้างคำตอบ |
model | String? | — | ชื่อโมเดล LLM ที่ตอบ |
traceJson | String? | — | RAG trace (JSON) สำหรับ Trace panel |
latencyMs | Int? | — | เวลาตอบรวม (มิลลิวินาที) |
ttftMs | Int? | — | time to first token (12.2) |
createdAt | DateTime | @default(now()) | วันเวลาที่สร้างระเบียน |
citations | Citation[] | — | ความสัมพันธ์ไปยัง Citation (หนึ่งต่อหลาย) |
feedbacks | Feedback[] | — | ความสัมพันธ์ไปยัง Feedback (หนึ่งต่อหลาย) |
ดัชนี/ข้อจำกัดระดับตาราง: @@index([conversationId, createdAt])
Citation (กลุ่ม Chat)
| ฟิลด์ | ชนิด | ข้อกำหนด/ค่าเริ่มต้น | คำอธิบาย |
|---|
id | String | @id @default(uuid()) | รหัสอ้างอิงของระเบียน (UUID) |
messageId | String | — | FK → Message |
message | Message | @relation(fields: [messageId], references: [id], onDelete: Cascade) | ความสัมพันธ์ไปยัง Message |
chunkId | String | — | FK → Chunk ที่ถูกอ้างอิง |
chunk | Chunk | @relation(fields: [chunkId], references: [id]) | ความสัมพันธ์ไปยัง Chunk |
rank | Int | — | 1..10 |
label | String | — | "ข.ช. (ไทย) 27/87/87" |
score | Float | — | คะแนนหลัง rerank และคูณ precedence |
Feedback (กลุ่ม Chat)
| ฟิลด์ | ชนิด | ข้อกำหนด/ค่าเริ่มต้น | คำอธิบาย |
|---|
id | String | @id @default(uuid()) | รหัสอ้างอิงของระเบียน (UUID) |
messageId | String | — | FK → Message ที่ให้ความเห็น |
message | Message | @relation(fields: [messageId], references: [id], onDelete: Cascade) | ความสัมพันธ์ไปยัง Message |
userId | String | — | FK → User ผู้ให้ความเห็น |
user | User | @relation(fields: [userId], references: [id]) | ความสัมพันธ์ไปยัง User |
value | Int | — | 1 = thumb-up, -1 = thumb-down |
comment | String? | — | ความเห็นเพิ่มเติม (ไม่บังคับ) |
createdAt | DateTime | @default(now()) | วันเวลาที่สร้างระเบียน |
ดัชนี/ข้อจำกัดระดับตาราง: @@unique([messageId, userId])
KnowledgeDocument (กลุ่ม Knowledge Base)
| ฟิลด์ | ชนิด | ข้อกำหนด/ค่าเริ่มต้น | คำอธิบาย |
|---|
id | String | @id @default(uuid()) | รหัสอ้างอิงของระเบียน (UUID) |
title | String | — | ชื่อเอกสาร |
docType | String | — | tipitaka | atthakatha | tika | contemporary | teacher_dhamma (ก.4.8.3(2)) |
edition | String? | — | มมร. | ไทย | อื่น ๆ |
pitaka | String? | — | vinaya | sutta | abhidhamma |
nikaya | String? | — | นิกาย/วรรค |
sutta | String? | — | ชื่อสูตร |
volume | Int? | — | เล่ม (มมร. 1–91) |
abbreviation | String? | — | ตัวย่อสำหรับ citation เช่น "ข.ช." "วิ.มหา." |
precedenceScore | Float | @default(1.0) | ก.4.8.3(3) ปรับได้ใน admin |
sourceType | String | — | upload | seed | online |
sourceFile | String? | — | path ใน storage/uploads |
sourceUrl | String? | — | URL ต้นทาง (ข้อมูลออนไลน์) |
mimeType | String? | — | ชนิดไฟล์ต้นฉบับ |
status | String | @default("pending") | pending | indexed | failed | deleted |
chunkCount | Int | @default(0) | จำนวน chunk ที่ทำดัชนีแล้ว |
sourceNote | String? | — | หมายเหตุที่มา/ความถูกต้องของข้อความ |
mbuVolume | Int? | — | เล่มในฉบับ มมร. 91 เล่ม (เมื่อทราบ) — ใช้คำนวณ coverage ภาคผนวก ข |
paliAliases | String? | — | คำบาลี/อังกฤษของเอกสาร คั่นด้วย | |
sanitizedLines | Int | @default(0) | จำนวนบรรทัดที่ถูกตัดตอน sanitize |
createdBy | String? | — | ผู้นำเข้าเอกสาร |
createdAt | DateTime | @default(now()) | วันเวลาที่สร้างระเบียน |
updatedAt | DateTime | @updatedAt | วันเวลาที่แก้ไขล่าสุด (Prisma อัปเดตอัตโนมัติ) |
deletedAt | DateTime? | — | วันเวลาที่ลบแบบ soft delete (null = ยังไม่ลบ) |
chunks | Chunk[] | — | ความสัมพันธ์ไปยัง Chunk (หนึ่งต่อหลาย) |
jobItems | IngestionJobItem[] | — | ความสัมพันธ์ไปยัง IngestionJobItem (หนึ่งต่อหลาย) |
ดัชนี/ข้อจำกัดระดับตาราง: @@index([docType, status])
Chunk (กลุ่ม Knowledge Base)
| ฟิลด์ | ชนิด | ข้อกำหนด/ค่าเริ่มต้น | คำอธิบาย |
|---|
id | String | @id @default(uuid()) | รหัสอ้างอิงของระเบียน (UUID) |
documentId | String | — | FK → KnowledgeDocument |
document | KnowledgeDocument | @relation(fields: [documentId], references: [id], onDelete: Cascade) | ความสัมพันธ์ไปยัง KnowledgeDocument |
ordinal | Int | — | ลำดับ chunk ในเอกสาร (เริ่ม 0) |
content | String | — | เนื้อหาเต็มของ chunk (1 ข้อ สำหรับพระไตรปิฎก/อรรถกถา) |
volume | Int? | — | เล่ม |
item | Int? | — | ข้อ |
page | Int? | — | หน้า |
pageEnd | Int? | — | หน้าสุดท้ายของข้อ (ข้อที่คร่อมหลายหน้า) |
heading | String? | — | หัวข้อ/ชื่อสูตรของ chunk |
paliAliases | String? | — | คำบาลีถอดอักษร คั่นด้วย | |
tokenCount | Int | — | จำนวนโทเคนโดยประมาณ (ตัวอักษร ÷ 3) |
oversized | Boolean | @default(false) | ข้อยาวเกิน 3,000 tokens แต่ไม่ถูกแบ่ง (ก.4.6.2) |
embedding | String? | — | JSON number[512] (pseudo-embedding) — cache |
createdAt | DateTime | @default(now()) | วันเวลาที่สร้างระเบียน |
citations | Citation[] | — | ความสัมพันธ์ไปยัง Citation (หนึ่งต่อหลาย) |
ดัชนี/ข้อจำกัดระดับตาราง: @@index([documentId, ordinal]), @@index([volume, item])
IngestionJob (กลุ่ม Knowledge Base)
| ฟิลด์ | ชนิด | ข้อกำหนด/ค่าเริ่มต้น | คำอธิบาย |
|---|
id | String | @id @default(uuid()) | รหัสอ้างอิงของระเบียน (UUID) |
type | String | — | ingest | reindex_all | reindex_selected | delete |
status | String | @default("queued") | queued | running | completed | failed | partial |
requestedBy | String | — | ผู้สั่งงาน (userId) |
totalItems | Int | @default(0) | จำนวนรายการทั้งหมดในงาน |
successItems | Int | @default(0) | จำนวนรายการที่สำเร็จ |
failedItems | Int | @default(0) | จำนวนรายการที่ล้มเหลว |
startedAt | DateTime? | — | เวลาเริ่มประมวลผล |
finishedAt | DateTime? | — | เวลาประมวลผลเสร็จ |
createdAt | DateTime | @default(now()) | วันเวลาที่สร้างระเบียน |
items | IngestionJobItem[] | — | ความสัมพันธ์ไปยัง IngestionJobItem (หนึ่งต่อหลาย) |
IngestionJobItem (กลุ่ม Knowledge Base)
| ฟิลด์ | ชนิด | ข้อกำหนด/ค่าเริ่มต้น | คำอธิบาย |
|---|
id | String | @id @default(uuid()) | รหัสอ้างอิงของระเบียน (UUID) |
jobId | String | — | FK → IngestionJob |
job | IngestionJob | @relation(fields: [jobId], references: [id], onDelete: Cascade) | ความสัมพันธ์ไปยัง IngestionJob |
documentId | String? | — | FK → KnowledgeDocument |
document | KnowledgeDocument? | @relation(fields: [documentId], references: [id]) | ความสัมพันธ์ไปยัง KnowledgeDocument |
fileName | String? | — | ชื่อไฟล์ที่นำเข้า |
stage | String | @default("queued") | queued | parse | sanitize | chunk | embed | index | done | failed |
status | String | @default("queued") | queued |
errorMsg | String? | — | ข้อความผิดพลาดเมื่อล้มเหลว |
chunkCount | Int | @default(0) | จำนวน chunk ที่ได้ |
durationMs | Int? | — | เวลาที่ใช้ประมวลผล (มิลลิวินาที) |
PromptVersion (กลุ่ม AI Config)
| ฟิลด์ | ชนิด | ข้อกำหนด/ค่าเริ่มต้น | คำอธิบาย |
|---|
id | String | @id @default(uuid()) | รหัสอ้างอิงของระเบียน (UUID) |
key | String | — | qa-persona | dhamma-talk | guard-classifier | judge | query-rewrite |
version | Int | — | หมายเลขเวอร์ชัน (เพิ่มทีละ 1 ต่อ key) |
content | String | — | เนื้อหา prompt |
changelog | String? | — | บันทึกการเปลี่ยนแปลง |
isActive | Boolean | @default(false) | เวอร์ชันที่ใช้งานอยู่ (1 เวอร์ชันต่อ key) |
createdBy | String | — | ผู้สร้างเวอร์ชัน |
createdAt | DateTime | @default(now()) | วันเวลาที่สร้างระเบียน |
ดัชนี/ข้อจำกัดระดับตาราง: @@unique([key, version]), @@index([key, isActive])
GuardrailTopic (กลุ่ม AI Config)
| ฟิลด์ | ชนิด | ข้อกำหนด/ค่าเริ่มต้น | คำอธิบาย |
|---|
id | String | @id @default(uuid()) | รหัสอ้างอิงของระเบียน (UUID) |
kind | String | — | scope_allow | refuse_sensitive | injection_pattern | yes_no_pattern |
key | String? | — | politics | religion_comparison | mental_health | ... (ใช้จำแนก refusalReason) |
labelTh | String | — | ชื่อหัวข้อภาษาไทย |
labelEn | String | — | ชื่อหัวข้อภาษาอังกฤษ |
patterns | String | — | JSON string[] (keyword/regex) |
refusalMsgTh | String? | — | ข้อความปฏิเสธภาษาไทย |
refusalMsgEn | String? | — | ข้อความปฏิเสธภาษาอังกฤษ |
enabled | Boolean | @default(true) | เปิด/ปิดหัวข้อ |
sortOrder | Int | @default(0) | ลำดับการแสดง/ประเมิน |
updatedBy | String? | — | ผู้แก้ไขล่าสุด |
updatedAt | DateTime | @updatedAt | วันเวลาที่แก้ไขล่าสุด (Prisma อัปเดตอัตโนมัติ) |
AiConfigSetting (กลุ่ม AI Config)
| ฟิลด์ | ชนิด | ข้อกำหนด/ค่าเริ่มต้น | คำอธิบาย |
|---|
key | String | @id | ดูรายการ key ในข้อ 7.2 |
value | String | — | JSON |
updatedBy | String? | — | ผู้แก้ไขล่าสุด |
updatedAt | DateTime | @updatedAt | วันเวลาที่แก้ไขล่าสุด (Prisma อัปเดตอัตโนมัติ) |
ConfigSnapshot (กลุ่ม AI Config)
| ฟิลด์ | ชนิด | ข้อกำหนด/ค่าเริ่มต้น | คำอธิบาย |
|---|
id | String | @id @default(uuid()) | รหัสอ้างอิงของระเบียน (UUID) |
scope | String | — | ai-config | guardrails | unavailable | pricing |
snapshot | String | — | JSON ของค่าทั้ง scope ก่อนเปลี่ยน |
reason | String? | — | เหตุผลการเปลี่ยนแปลง |
createdBy | String | — | ผู้เปลี่ยนแปลง |
createdAt | DateTime | @default(now()) | วันเวลาที่สร้างระเบียน |
ดัชนี/ข้อจำกัดระดับตาราง: @@index([scope, createdAt])
TokenUsage (กลุ่ม AI Config)
| ฟิลด์ | ชนิด | ข้อกำหนด/ค่าเริ่มต้น | คำอธิบาย |
|---|
id | String | @id @default(uuid()) | รหัสอ้างอิงของระเบียน (UUID) |
userId | String? | — | FK → User (null = งานระบบ เช่น ประเมินผล) |
user | User? | @relation(fields: [userId], references: [id]) | ความสัมพันธ์ไปยัง User |
conversationId | String? | — | บทสนทนาที่เกี่ยวข้อง |
messageId | String? | — | ข้อความที่เกี่ยวข้อง |
mode | String | — | QA | DHAMMA_TALK | EVALUATION | GUARD |
model | String | — | ชื่อโมเดล |
inputTokens | Int | — | โทเคนขาเข้า |
thinkingTokens | Int | @default(0) | โทเคนการคิด (ไม่เกิน thinkingBudget) |
outputTokens | Int | — | โทเคนขาออก |
totalTokens | Int | — | ผลรวมโทเคน (ใช้คำนวณเพดาน 24 ชม.) |
estimatedCost | Float | @default(0) | คำนวณจาก LlmPricing ณ เวลาบันทึก |
createdAt | DateTime | @default(now()) | วันเวลาที่สร้างระเบียน |
ดัชนี/ข้อจำกัดระดับตาราง: @@index([createdAt]), @@index([userId, createdAt])
LlmPricing (กลุ่ม AI Config)
| ฟิลด์ | ชนิด | ข้อกำหนด/ค่าเริ่มต้น | คำอธิบาย |
|---|
id | String | @id @default(uuid()) | รหัสอ้างอิงของระเบียน (UUID) |
provider | String | — | ผู้ให้บริการ LLM |
model | String | — | ชื่อโมเดล |
inputPerMillion | Float | — | USD หรือ THB ต่อ 1M tokens (currency ใน config) |
thinkingPerMillion | Float | @default(0) | อัตราต่อ 1M thinking tokens |
outputPerMillion | Float | — | อัตราต่อ 1M output tokens |
currency | String | @default("THB") | สกุลเงิน |
updatedBy | String? | — | ผู้แก้ไขล่าสุด |
updatedAt | DateTime | @updatedAt | วันเวลาที่แก้ไขล่าสุด (Prisma อัปเดตอัตโนมัติ) |
ดัชนี/ข้อจำกัดระดับตาราง: @@unique([provider, model])
AlertEmailLog (กลุ่ม AI Config)
| ฟิลด์ | ชนิด | ข้อกำหนด/ค่าเริ่มต้น | คำอธิบาย |
|---|
id | String | @id @default(uuid()) | รหัสอ้างอิงของระเบียน (UUID) |
type | String | — | threshold_tokens | threshold_cost | auto_unavailable | error_rate | ingestion_failed | system |
severity | String | — | info | warning | critical |
to | String | — | ผู้รับ |
subject | String | — | หัวเรื่อง |
body | String | — | เนื้อหาแจ้งเตือน |
delivery | String | @default("logged") | logged | sent | failed |
createdAt | DateTime | @default(now()) | วันเวลาที่สร้างระเบียน |
ดัชนี/ข้อจำกัดระดับตาราง: @@index([createdAt])
EvaluationDataset (กลุ่ม Evaluation)
| ฟิลด์ | ชนิด | ข้อกำหนด/ค่าเริ่มต้น | คำอธิบาย |
|---|
id | String | @id @default(uuid()) | รหัสอ้างอิงของระเบียน (UUID) |
name | String | — | ชื่อชุดทดสอบ |
kind | String | — | accuracy | judge | retrieval | guardrail |
benchmarkCode | String | — | DS-1 | NT-2 | RET-1 | RET-2 | RET-3 | GR-1 | GR-2 |
version | Int | @default(1) | เพิ่มทุกครั้งที่ items เปลี่ยน (ก.5.2.4) |
threshold | Float | — | เกณฑ์ขั้นต่ำ 0.8 / 2.0 |
guaranteed | Float | — | ค่าที่ผู้รับจ้างรับประกัน (9.4/16.2) |
expectedCount | Int | — | จำนวนข้อตาม TOR (100/50/...) |
createdBy | String | — | ผู้สร้างชุด |
createdAt | DateTime | @default(now()) | วันเวลาที่สร้างระเบียน |
updatedAt | DateTime | @updatedAt | วันเวลาที่แก้ไขล่าสุด (Prisma อัปเดตอัตโนมัติ) |
items | EvaluationItem[] | — | ความสัมพันธ์ไปยัง EvaluationItem (หนึ่งต่อหลาย) |
runs | EvaluationRun[] | — | ความสัมพันธ์ไปยัง EvaluationRun (หนึ่งต่อหลาย) |
EvaluationItem (กลุ่ม Evaluation)
| ฟิลด์ | ชนิด | ข้อกำหนด/ค่าเริ่มต้น | คำอธิบาย |
|---|
id | String | @id @default(uuid()) | รหัสอ้างอิงของระเบียน (UUID) |
datasetId | String | — | FK → EvaluationDataset |
dataset | EvaluationDataset | @relation(fields: [datasetId], references: [id], onDelete: Cascade) | ความสัมพันธ์ไปยัง EvaluationDataset |
ordinal | Int | — | เลขข้อ |
question | String | — | คำถาม |
choices | String? | — | JSON string[] สำหรับปรนัย |
expectedAnswer | String? | — | เฉลย (choice label หรือข้อความ) |
expectedRefs | String? | — | JSON [{volume,item}] หรือ chunkId[] สำหรับ Recall@5 |
expectRefusal | Boolean | @default(false) | guardrail |
level | String? | — | ตรี | โท | เอก |
results | EvaluationResult[] | — | ความสัมพันธ์ไปยัง EvaluationResult (หนึ่งต่อหลาย) |
EvaluationRun (กลุ่ม Evaluation)
| ฟิลด์ | ชนิด | ข้อกำหนด/ค่าเริ่มต้น | คำอธิบาย |
|---|
id | String | @id @default(uuid()) | รหัสอ้างอิงของระเบียน (UUID) |
datasetId | String | — | FK → EvaluationDataset |
dataset | EvaluationDataset | @relation(fields: [datasetId], references: [id]) | ความสัมพันธ์ไปยัง EvaluationDataset |
datasetVersion | Int | — | เวอร์ชันชุดทดสอบ ณ เวลารัน (ก.5.2.4) |
promptVersionId | String | — | FK เชิงตรรกะ → PromptVersion |
promptKey | String | — | key ของ prompt หลักของ workflow |
promptVersion | Int | — | เวอร์ชัน prompt ณ เวลารัน (ก.5.2.4) |
model | String | — | โมเดล LLM ที่ใช้ตอบ |
judgeModel | String? | — | โมเดลที่ใช้เป็นผู้ตัดสิน (NT-2) |
label | String? | — | baseline | acceptance | adhoc |
status | String | @default("queued") | queued | running | completed | failed |
score | Float? | — | accuracy %, recall@5 %, judge avg, refusal % |
passed | Boolean? | — | score ≥ threshold |
itemCount | Int | @default(0) | จำนวนข้อที่ประเมิน |
startedAt | DateTime? | — | เวลาเริ่ม |
finishedAt | DateTime? | — | เวลาเสร็จ |
requestedBy | String | — | ผู้สั่งรัน |
createdAt | DateTime | @default(now()) | วันเวลาที่สร้างระเบียน |
results | EvaluationResult[] | — | ความสัมพันธ์ไปยัง EvaluationResult (หนึ่งต่อหลาย) |
EvaluationResult (กลุ่ม Evaluation)
| ฟิลด์ | ชนิด | ข้อกำหนด/ค่าเริ่มต้น | คำอธิบาย |
|---|
id | String | @id @default(uuid()) | รหัสอ้างอิงของระเบียน (UUID) |
runId | String | — | FK → EvaluationRun |
run | EvaluationRun | @relation(fields: [runId], references: [id], onDelete: Cascade) | ความสัมพันธ์ไปยัง EvaluationRun |
itemId | String | — | FK → EvaluationItem |
item | EvaluationItem | @relation(fields: [itemId], references: [id]) | ความสัมพันธ์ไปยัง EvaluationItem |
botAnswer | String | — | คำตอบของระบบ |
retrievedIds | String? | — | JSON chunkId[] top-5 |
score | Float | — | 0/1, 0/1, 0–3, 0/1 |
passed | Boolean | — | ผ่าน/ไม่ผ่านรายข้อ |
judgeReason | String? | — | เหตุผลของผู้ตัดสิน/ตัวให้คะแนน |
latencyMs | Int? | — | เวลาที่ใช้ต่อข้อ (มิลลิวินาที) |
AuditLog (กลุ่ม Audit / Privacy / Ops)
| ฟิลด์ | ชนิด | ข้อกำหนด/ค่าเริ่มต้น | คำอธิบาย |
|---|
seq | Int | @id @default(autoincrement()) | SQLite อนุญาต autoincrement เฉพาะ primary key |
id | String | @unique @default(uuid()) | รหัสอ้างอิงของระเบียน (UUID) |
timestamp | DateTime | @default(now()) | วันเวลาที่เกิดเหตุการณ์ |
actorId | String? | — | ผู้กระทำ (userId; null = ระบบ/ผู้ไม่ระบุตัวตน) |
actorEmailHash | String? | — | sha256 อีเมลของผู้กระทำ (กรณี login ล้มเหลว) |
ip | String | — | IP ของผู้กระทำ |
eventType | String | — | login_success | login_failed | logout | user_changed | role_changed | config_changed | prompt_changed | document_imported | document_deleted | conversation_accessed | evaluation_run | privacy_request | data_destruction | defect_changed |
targetType | String? | — | ชนิดของสิ่งที่ถูกกระทำ |
targetId | String? | — | รหัสของสิ่งที่ถูกกระทำ |
before | String? | — | JSON |
after | String? | — | JSON |
result | String | — | success | failure |
prevHash | String | — | hash ของแถวก่อนหน้า (genesis = 0×64) |
hash | String | — | sha256(prevHash + fields) |
ดัชนี/ข้อจำกัดระดับตาราง: @@index([timestamp]), @@index([eventType, timestamp]), @@index([actorId, timestamp])
PrivacyRequest (กลุ่ม Audit / Privacy / Ops)
| ฟิลด์ | ชนิด | ข้อกำหนด/ค่าเริ่มต้น | คำอธิบาย |
|---|
id | String | @id @default(uuid()) | รหัสอ้างอิงของระเบียน (UUID) |
userId | String | — | เจ้าของข้อมูล (FK → User) |
user | User | @relation(fields: [userId], references: [id]) | ความสัมพันธ์ไปยัง User |
type | String | — | access_copy | erase | withdraw_consent |
status | String | @default("pending") | pending | completed | rejected |
note | String? | — | หมายเหตุการดำเนินการ |
exportPath | String? | — | ไฟล์ JSON สำเนาข้อมูล |
handledBy | String? | — | ผู้ดูแลที่ดำเนินการ |
createdAt | DateTime | @default(now()) | วันเวลาที่สร้างระเบียน |
completedAt | DateTime? | — | วันเวลาที่ดำเนินการเสร็จ |
DefectLog (กลุ่ม Audit / Privacy / Ops)
| ฟิลด์ | ชนิด | ข้อกำหนด/ค่าเริ่มต้น | คำอธิบาย |
|---|
id | String | @id @default(uuid()) | รหัสอ้างอิงของระเบียน (UUID) |
module | String | — | โมดูลที่พบข้อบกพร่อง |
description | String | — | รายละเอียดข้อบกพร่อง |
severity | String | — | critical | high | medium | low |
status | String | @default("open") | open | in_progress | resolved | closed |
reportedBy | String | — | ผู้แจ้ง |
reportedAt | DateTime | @default(now()) | เวลาที่มหาวิทยาลัยแจ้ง (12.4 เริ่มนับ) |
assignedTo | String? | — | ผู้รับผิดชอบแก้ไข |
foundInPhase | String? | — | ระยะที่พบ (เช่น SIT/UAT) |
resolutionNote | String? | — | วิธีแก้และมาตรการป้องกันซ้ำ (บังคับสำหรับ high/critical) |
resolvedAt | DateTime? | — | วันเวลาที่แก้ไขเสร็จ |
confirmedAt | DateTime? | — | มหาวิทยาลัยยืนยันผล (12.4 สิ้นสุดนับ) |
slaHours | Int | — | 24 | 48 | 80 (10 วันทำการ×8 ชม.) |
slaDeadline | DateTime | — | กำหนดเวลาแก้ไขตาม SLA (ชั่วโมงทำการ) |
overdueHours | Int? | — | คำนวณเมื่อ confirm |
penaltyEstimate | Float? | — | overdueHours × 0.025% × contractValue (10.2) |
createdAt | DateTime | @default(now()) | วันเวลาที่สร้างระเบียน |
updatedAt | DateTime | @updatedAt | วันเวลาที่แก้ไขล่าสุด (Prisma อัปเดตอัตโนมัติ) |
ดัชนี/ข้อจำกัดระดับตาราง: @@index([severity, status])
ApiRequestLog (กลุ่ม Audit / Privacy / Ops)
| ฟิลด์ | ชนิด | ข้อกำหนด/ค่าเริ่มต้น | คำอธิบาย |
|---|
id | String | @id @default(uuid()) | รหัสอ้างอิงของระเบียน (UUID) |
requestId | String | — | รหัสคำขอ (x-request-id) |
path | String | — | route pattern ของ API |
method | String | — | HTTP method |
statusCode | Int | — | HTTP status ที่ตอบ |
durationMs | Int | — | เวลาตอบสนอง (มิลลิวินาที) |
ttftMs | Int? | — | Time to first token ของคำขอ SSE |
createdAt | DateTime | @default(now()) | วันเวลาที่สร้างระเบียน |
ดัชนี/ข้อจำกัดระดับตาราง: @@index([createdAt])
6.1 ความสัมพันธ์ระหว่างตารางหลัก
erDiagram
User ||--o{ Conversation : owns
User ||--o{ Feedback : gives
User ||--o{ TokenUsage : consumes
User ||--o{ PrivacyRequest : files
Conversation ||--o{ Message : contains
Message ||--o{ Citation : cites
Message ||--o{ Feedback : receives
Chunk ||--o{ Citation : referenced_by
KnowledgeDocument ||--o{ Chunk : split_into
KnowledgeDocument ||--o{ IngestionJobItem : processed_in
IngestionJob ||--o{ IngestionJobItem : has
EvaluationDataset ||--o{ EvaluationItem : has
EvaluationDataset ||--o{ EvaluationRun : run_as
EvaluationRun ||--o{ EvaluationResult : produces
EvaluationItem ||--o{ EvaluationResult : scored_in
ตารางที่ไม่มีความสัมพันธ์แบบ foreign key: LoginAttempt, PromptVersion, GuardrailTopic, AiConfigSetting, ConfigSnapshot, LlmPricing, AlertEmailLog, AuditLog, DefectLog, ApiRequestLog
6.2 ค่าตั้งค่า AI (AiConfigSetting.key)
ค่าตั้งต้นจาก AI_CONFIG_DEFAULTS ใน src/domain/config/ai-config.ts (ผู้ดูแลแก้ได้ที่ /admin/ai-config ค่าที่ไม่ผ่าน schema ถูกละเว้นและใช้ค่าตั้งต้นแทน)
| key | ค่าตั้งต้น | ช่วงที่ schema ยอมรับ | TOR |
|---|
llm.provider / llm.model | typhoon / typhoon-v2.5-30b-a3b-instruct | ข้อความ | ก.4.1.2, ก.6.2.3 |
llm.fallbackModels | [] | ≤10 รายการ | — |
tokens.dailyCap | 10,000,000 | 1 – 1,000,000,000 | ก.3.4.1 |
tokens.marginPct | 10 | 0 – 50 | ก.3.4.4 |
tokens.alertThresholdPct | 80 | 1 – 100 | ก.7.3.5 |
tokens.maxPerPromptQa | 10,000 | 500 – 10,000 | ก.4.2.1 |
tokens.maxPerPromptDhammaTalk | 50,000 | 500 – 50,000 | ก.4.2.1 |
tokens.thinkingBudget | 2,000 | 0 – 32,000 | ก.4.4.2 |
tokens.maxOutput | 1,500 | 64 – 16,000 | ก.3.4.2 |
input.maxChars | 2,000 | 1 – 20,000 | ก.4.4.2 |
retention.conversationDays | 90 | 1 – 3,650 | ก.3.5.1 |
retention.purgeIntervalMinutes | 60 | 1 – 1,440 | ก.6.2.2 |
unavailable.enabled / unavailable.auto | false / false | boolean | ก.3.4.3, ก.3.4.4 |
unavailable.messageTh / unavailable.messageEn | ข้อความแจ้งปิดบริการ | 1 – 1,000 ตัวอักษร | ก.6.6.1 |
retrieval.candidateK / retrieval.finalK / retrieval.rrfK | 50 / 10 / 60 | 1–200 / 1–10 / 1–1,000 | ก.3.6.2, ก.4.6.1 |
retrieval.reranker | heuristic | ข้อความ (heuristic | tei) | ก.4.7.1 |
eval.judgeModel | typhoon-v2.5-30b-a3b-instruct | ข้อความ | ก.5.2.2 |
eval.guaranteed | DS-1 0.85, NT-2 2.3, RET-1/2/3 0.85, GR-1/GR-2 0.9 | 0 – 3 ต่อชุด | 16.2 |
contract.value | 5,000,000 | ≥0 | 10.2 |
pricing.currency | THB | รหัส 3 ตัวอักษร | ก.7.3.3 |
privacy.policyVersion | 1.0 | ข้อความ | 14 |
7. User Flow
7.1 ผู้ใช้ทั่วไป: สมัคร → ถาม-ตอบ → ตรวจอ้างอิง → ประวัติ
flowchart TD
H["หน้าแรก /"] --> R{"มีบัญชีแล้ว?"}
R -- ไม่มี --> REG["/register: อีเมล รหัสผ่าน ชื่อ + ยินยอม"] --> LOGIN
R -- มี --> LOGIN["/login: อีเมล+รหัสผ่าน หรือ Gmail/Facebook"]
LOGIN -->|ผิด 5 ครั้ง| LOCK["ล็อก 15 นาที"]
LOGIN --> CHAT["/chat: พิมพ์คำถาม (เห็นคำเตือนใต้กล่อง)"]
CHAT --> G{"Guardrail"}
G -- ปฏิเสธ --> REF["การ์ดปฏิเสธพร้อมเหตุผล"] --> CHAT
G -- ผ่าน --> REFS["รายการอ้างอิง 3 รายการ + Expand All"]
REFS --> SUM["บทสรุปแบบ streaming + citation chip"]
SUM --> MODAL["คลิก chip → Modal เนื้อหาเต็ม + metadata"]
SUM --> FB["👍/👎 + ความเห็น"]
SUM --> TRACE["RAG Trace 8 โหนด"]
SUM --> HIST["/chat/history: เปิดต่อ / ลบ / ดูวันหมดอายุ"]
HIST --> PRIV["/privacy: ดาวน์โหลดสำเนา / ขอลบ / ถอนยินยอม"]
7.2 Dhamma Talk
flowchart TD
DT["/chat/dhamma-talk"] --> P{"dhammaTalkEnabled?"}
P -- ไม่ --> ASK["หน้าอธิบายให้ขอสิทธิ์จากผู้ดูแล"]
P -- ใช่ --> PROMPT["พิมพ์หัวข้อบทธรรมบรรยาย"] --> ART["Response Modal: artifact Markdown (แท็บแก้ไข/ตัวอย่าง)"]
ART --> EDIT["ผู้ใช้แก้ไข → บันทึก (PATCH artifactMd)"]
EDIT --> NEXT["สั่งรอบถัดไป → AI ใช้ฉบับที่แก้ล่าสุดเป็นบริบท"] --> ART
ART --> DL["ดาวน์โหลด .md"]
7.3 ผู้ดูแลระบบ
flowchart LR
AL["/login (บัญชี ADMIN/SUPER_ADMIN จาก IP ใน allowlist)"] --> DASH["/admin/dashboard"]
DASH --> USERS["ผู้ใช้ / คำขอ PDPA / Audit Log"]
DASH --> AI["ตั้งค่า AI / Prompt / Guardrails / Unavailable / ราคาโทเคน"]
DASH --> KB["ฐานองค์ความรู้ / งานนำเข้า"]
DASH --> EVAL["Evaluation / ชุดทดสอบ / เปรียบเทียบ / รายงานความปลอดภัย"]
DASH --> OPS["Defect Log / SLA / Backup* / ทำลายข้อมูล*"]
* เฉพาะ SUPER_ADMIN
Wireframe และ Prototype ของหน้าจอหลักอยู่ใน /documents/ux-deliverables
8. ข้อกำหนดส่วนต่อประสาน (API) — OpenAPI 3.0
- เอกสาร OpenAPI 3.0.3 ฉบับเครื่องอ่านได้:
GET /api/openapi.json สร้างจาก registry ใน src/server/api/openapi/index.ts (buildOpenApi) ซึ่งรวมไฟล์กลุ่ม core.ts, privacy.ts, admin-users.ts, admin-config.ts, admin-kb.ts, admin-eval.ts, admin-ops.ts
- รูปแบบผลลัพธ์ทุก endpoint:
{ ok: true, data } หรือ { ok: false, error: { code, message } } ยกเว้น endpoint แบบ SSE (text/event-stream) และการดาวน์โหลด CSV/Markdown
- การยืนยันตัวตน: cookie
mbu_session (securityScheme sessionCookie); คำขอที่แก้ไขข้อมูลต้องมี header x-csrf-token
- ชุดทดสอบ
tests/api/openapi-coverage.test.ts ยืนยันว่าทุก path+method ใน registry มีกรณีทดสอบใน tests/api และเมื่อรัน npm test ระบบตรวจการเรียกจริงซ้ำอีกชั้น (regression ทุก endpoint — TOR 4.7.5; รายละเอียดใน /documents/test-reports-index)
8.1 สรุปกลุ่ม endpoint (รวม 107 operation ใน registry)
| กลุ่ม (tag) | จำนวน | สิทธิ์ | ตัวอย่าง |
|---|
| System | 4 | public | GET /api/health, GET /api/status, GET /api/openapi.json, GET /api/csrf |
| Documents | 2 | public | GET /api/documents, GET /api/documents/[slug] |
| Auth | 10 | public / user | POST /api/auth/register, POST /api/auth/login, POST /api/auth/logout, GET /api/auth/oauth/[provider]/start, .../callback, GET /api/auth/oauth-status, POST /api/mock-oauth/token, GET /api/mock-oauth/userinfo, GET/PATCH /api/me |
| Chat | 8 | user | POST /api/chat/qa (SSE), POST /api/chat/dhamma-talk (SSE), GET /api/conversations, GET/PATCH/DELETE /api/conversations/[id], GET /api/chunks/[id], POST /api/messages/[id]/feedback |
| Privacy | 3 | user | GET /api/privacy/export, GET/POST /api/privacy/requests |
| Admin — Users | 5 | ADMIN+ | GET /api/admin/users, GET/PATCH/DELETE /api/admin/users/[id], GET /api/admin/users/[id]/conversations |
| Admin — Privacy Requests | 3 | ADMIN+ | GET/POST /api/admin/privacy-requests, POST /api/admin/privacy-requests/[id]/complete |
| Admin — Audit Log | 3 | ADMIN+ | GET /api/admin/audit-log, GET .../export, GET .../verify |
| Admin — AI Config | 6 | ADMIN+ | GET/PUT /api/admin/ai-config, GET .../snapshots, POST .../revert/[snapshotId], GET .../models, POST .../test-llm |
| Admin — Prompts | 5 | ADMIN+ | GET /api/admin/prompts, GET/POST /api/admin/prompts/[key], POST .../activate/[version], GET .../diff |
| Admin — Guardrails | 5 | ADMIN+ | GET/POST /api/admin/guardrails, PATCH/DELETE /api/admin/guardrails/[id], POST .../test |
| Admin — Unavailable | 2 | ADMIN+ | GET/PUT /api/admin/unavailable-mode |
| Admin — Pricing | 4 | ADMIN+ | GET/POST /api/admin/pricing, PATCH/DELETE /api/admin/pricing/[id] |
| Admin — Maintenance | 1 | ADMIN+ | POST /api/admin/maintenance/purge-conversations |
| Admin — Knowledge Base | 11 | ADMIN+ | GET /api/admin/knowledge-base, POST .../upload, GET/PATCH/DELETE .../[id], GET .../[id]/chunks, POST .../reindex, GET .../jobs, GET .../jobs/[id], POST .../jobs/[id]/retry-failed, GET .../coverage |
| Admin — Evaluation Datasets | 11 | ADMIN+ | CRUD /api/admin/evaluation/datasets[/id], items CRUD, POST .../items/import (CSV), GET .../items/template |
| Admin — Evaluation Runs | 4 | ADMIN+ | POST/GET /api/admin/evaluation/runs, GET .../runs/[id], GET .../compare |
| Admin — Evaluation Reports | 4 | ADMIN+ | GET .../thresholds, GET/POST .../security-report, POST .../export |
| Admin — Dashboard | 3 | ADMIN+ | GET /api/admin/dashboard/{usage,tokens,feedback} |
| Admin — Alerts | 2 | ADMIN+ | GET /api/admin/alerts, POST /api/admin/alerts/test |
| Admin — Ops | 11 | ADMIN+ / SUPER_ADMIN | Defect log CRUD + export, GET /api/admin/sla-report + export, GET/POST /api/admin/data-destruction (SUPER_ADMIN), GET/POST /api/admin/backup (SUPER_ADMIN) |
หมายเหตุ: route ภายใน POST /api/internal/security-event (ให้ middleware บันทึก audit เมื่อ IP นอก allowlist เข้าถึงส่วนผู้ดูแล ตรวจด้วย header ภายใน) ไม่ได้เปิดเป็น API สาธารณะ จึงไม่อยู่ใน OpenAPI registry
8.2 เหตุการณ์ SSE ของ /api/chat/qa และ /api/chat/dhamma-talk
| event | ข้อมูล | เวลาที่ส่ง |
|---|
references | รายการอ้างอิง ≤10 (n, chunkId, label, title, snippet, score, volume, item ...) | หลังโหนด rerank |
delta | ข้อความบางส่วนของคำตอบ | ระหว่างโหนด generate |
refusal | { reason, message, topicLabel? } | เมื่อถูกปฏิเสธ |
done | { conversationId, messageId, answer, refusal, artifactMd, references, usage{input,thinking,output,total}, trace[], ttftMs, latencyMs, model, promptVersion } | จบคำขอ |
error | { code: "GENERATION_FAILED", message } | เกิดข้อผิดพลาดระหว่างสร้างคำตอบ |
9. ข้อจำกัดและสมมติฐาน
- ระบบสาธิตรันบนเครื่องเดียว (Node.js ≥22 + SQLite) ตัวเลขด้านประสิทธิภาพที่ระดับ 500 concurrent และ Availability รายเดือนต้องยืนยันบนโครงสร้างพื้นฐานจริงของมหาวิทยาลัย
- ฐานองค์ความรู้ปัจจุบันเป็นพระไตรปิฎกฉบับออนไลน์บางส่วนตามที่ TOR ภาคผนวก ข อนุญาตในระยะแรก (ดู
/documents/data-ingestion-plan และ /documents/ingestion-report)
- เมื่อไม่ได้ตั้ง API key ของ provider ที่เลือก ระบบสลับไปใช้
MockLlmAdapter (deterministic) อัตโนมัติ และชุดทดสอบ vitest บังคับใช้ mock เสมอ
- การทดสอบเจาะระบบไม่ได้ดำเนินการในรอบนี้ตามคำสั่งของผู้ว่าจ้าง (ดู
/documents/pentest-report)