นำไปใช้จริง — ประกอบ Agentic RAG บน NestJS + pgvector
ถึงบทสุดท้ายแล้ว — เราจะพิสูจน์ว่า pattern พกพาข้ามภาษาได้จริง โดยหยิบตัวที่ทรงพลังที่สุด
ในคลัง (agentic_rag_system — self-reflection RAG) มา ประกอบใหม่ย่อ ๆ บน NestJS + PostgreSQL/pgvector
โดยไม่แตะ Python เลย บทนี้เป็น lab — อ่านโครง แล้วลงมือทำตามใน repo ของคุณเอง
(ควรผ่าน HO-0 ถึง HO-5 มาก่อน)
สิ่งที่จะสร้าง
"ผู้ช่วยตอบคำถามจากคู่มือภายในบริษัท" — ผู้ใช้ถามภาษาคน ระบบค้นคู่มือจริง เกรดว่า context พอไหม ถ้าไม่พอก็ปรับคำค้นแล้วค้นใหม่ สุดท้ายตอบพร้อม อ้างอิงแหล่งที่มา — ซึ่งก็คือ agentic RAG loop จาก HO-1/HO-2 นั่นเอง แต่คราวนี้เป็น NestJS service จริง
สถาปัตยกรรม (NestJS module)
ลงมือทำ 5 ขั้น
ขั้น 1 · ตั้ง pgvector (จาก HO-2)
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE doc_chunks (
id bigserial PRIMARY KEY,
source text NOT NULL, -- ไว้ทำ citation
content text NOT NULL,
embedding vector(1536)
);
ขั้น 2 · Ingest (chunk → embed → store)
เตรียมคลังล่วงหน้า — ตัดคู่มือเป็น chunk, embed, เก็บลงตาราง (งานนี้ควรเป็น worker/queue ไม่ใช่ใน request):
async ingest(doc: Doc) {
const chunks = this.chunk(doc.text, { size: 700, overlap: 80 });
for (const c of chunks) {
const vec = await this.embedding.embed(c);
await this.repo.insert({ source: doc.name, content: c, embedding: vec });
}
}
ขั้น 3 · Retrieve = "tool" ตัวหนึ่ง (จาก HO-2)
// ChunkRepository — top-k ใกล้สุดด้วย cosine (<=>)
async search(queryVec: number[], k = 5) {
return this.db.query(
`SELECT source, content FROM doc_chunks ORDER BY embedding <=> $1 LIMIT $2`,
[queryVec, k],
);
}
ขั้น 4 · Agent loop: retrieve → grade → rewrite → generate (จาก HO-1)
หัวใจของทั้งหมด — พอร์ต state machine ของ agentic_rag_system มาเป็น TS ตรง ๆ:
async ask(question: string): Promise<Answer> {
let query = question;
for (let i = 0; i < MAX_ITERATIONS; i++) { // กันวนไม่จบ (HO-1)
const vec = await this.embedding.embed(query);
const chunks = await this.repo.search(vec); // retrieve
const verdict = await this.llm.grade(question, chunks); // grade
if (verdict.ok || i === MAX_ITERATIONS - 1)
return this.llm.generate(question, chunks); // generate + citation
query = verdict.refinedQuery; // rewrite → วนใหม่
}
}
ขั้น 5 · คำตอบเป็น structured output + citation (จาก HO-3)
// บังคับ LLM ตอบตาม schema — validate ก่อนส่งออก (เหมือน HO-3)
const Answer = z.object({
answer: z.string(),
citations: z.array(z.object({ source: z.string(), quote: z.string() })),
});
เห็นไหมว่ามาจากทุกบท
| ชิ้นส่วน | มาจากบท |
|---|---|
| agent loop + MAX_ITERATIONS + tool | HO-1 |
| retrieve / pgvector / self-reflection | HO-2 |
| structured output + validate ก่อนส่งออก | HO-3 |
| เลือก provider Claude (ไม่ fine-tune) | HO-5 |
| วิธีอ่าน/พอร์ตโปรเจกต์ต้นทาง | HO-0 |
เรื่อง provider/SDK ของ Claude (Messages API, tool use, structured output, prompt caching)
อยู่ในเส้นทางหลัก Lv2 และ
Lv3 — เอามาเสียบใน LlmProvider ได้เลย
ก่อนขึ้น production — เช็กลิสต์ (Lv7)
โปรเจกต์ในคลังเป็นเดโม (เตือนไว้ตั้งแต่ HO-0) — ของจริงต้องเติมอีกชั้น:
- Auth & rate limit ที่ controller (กันใช้เกิน/คนนอก)
- Cost & observability — log token/รอบ loop/latency ต่อคำถาม (loop = จ่ายเงินหลายเท่า)
- Error handling & timeout — LLM ล่ม/ช้า ต้อง fallback ไม่ใช่ค้าง
- Guardrail — จำกัดสิ่งที่ตอบ, กัน prompt injection จากเนื้อคู่มือ
- Eval — ชุดคำถาม-คำตอบไว้วัดคุณภาพก่อน/หลังแก้ (ไม่วัด = ไม่รู้ว่าดีขึ้น)
จบแทร็ก HO! คุณอ่านแอป AI จริง ~59 ตัวเป็น แล้วถอด pattern มาประกอบเองบนสแตกที่ถนัดได้ ขั้นต่อไป: ทำ lab นี้ให้รันจริงในโปรเจกต์คุณ แล้วต่อยอดสู่ Lv7 · Production เพื่อทำให้มันพร้อมใช้งานจริง
-
สร้าง NestJS module
rag/ตามสถาปัตยกรรมด้านบน (controller + service + 2 provider + repository) ให้ ingest เอกสารตัวอย่าง 1–2 ไฟล์ลง pgvector ได้ -
ทำ endpoint
POST /askให้ตอบได้จริงด้วย loop retrieve→grade→generate พร้อมคืนcitations(validate ด้วย schema) -
เปิด
sources/.../ai_agents/agentic_rag_system/rag_graph.pyเทียบกับโค้ด TS ของคุณ แล้วเขียน 3 บรรทัดว่า "pattern ไหนพกพาข้ามภาษาได้ตรง ๆ บ้าง" - เลือก 2 ข้อจากเช็กลิสต์ production มาทำจริง (แนะนำ rate limit + cost logging)
เมื่อทำเสร็จ พิมพ์ /quiz HO-6 เพื่อทดสอบความเข้าใจรวบยอดของทั้งแทร็ก