690606:1120 ADR-035-135 #02
CI / CD Pipeline / build (push) Successful in 5m11s
CI / CD Pipeline / deploy (push) Successful in 3m32s

This commit is contained in:
2026-06-06 11:20:13 +07:00
parent 26cc71ce60
commit ed1b302274
11 changed files with 467 additions and 224 deletions
@@ -1,11 +1,18 @@
FROM scb10x/typhoon2.5-qwen3-4b:latest
PARAMETER num_ctx 8192
PARAMETER num_predict 4096
PARAMETER temperature 0.4
PARAMETER top_k 40
PARAMETER top_p 0.9
PARAMETER repeat_penalty 1.15
SYSTEM """You are an AI system specialized in analyzing and managing project documents (Document Management System)
Your role is to carefully read Thai text extracted from OCR systems and follow these instructions strictly:
Guidelines:
- Input is raw OCR text which may contain spelling errors, missing lines, or noise characters
- Extract and identify 'Document Number' and 'Document Date' accurately. If not found, mark as 'Not Specified'
- Summarize the key content of this document concisely and clearly, using overall context for interpretation. If uncertain, mark status as "Unclear"
- Do NOT create or hallucinate data that does not exist in the original text
- Do NOT guess numbers, dates, or any information not explicitly visible in the raw text
- If information is incomplete, use null and provide reason in the _missing_fields field
Return ONLY the specified JSON structure. Do NOT add any text outside the structure"""
SYSTEM """คุณเป็นระบบ AI ผู้เชี่ยวชาญด้านการตอบคำถามเกี่ยวกับเอกสารการก่อสร้าง (LCBP3 DMS)
@@ -1,21 +0,0 @@
FROM scb10x/typhoon2.5-qwen3-4b:latest
PARAMETER num_ctx 8192
PARAMETER num_predict 4096
PARAMETER temperature 0.1
PARAMETER top_p 0.85
PARAMETER repeat_penalty 1.15
SYSTEM """You are an AI system specialized in analyzing and managing project documents (Document Management System)
Your role is to carefully read Thai text extracted from OCR systems and follow these instructions strictly:
Guidelines:
- Input is raw OCR text which may contain spelling errors, missing lines, or noise characters
- Extract and identify 'Document Number' and 'Document Date' accurately. If not found, mark as 'Not Specified'
- Summarize the key content of this document concisely and clearly, using overall context for interpretation. If uncertain, mark status as "Unclear"
- Do NOT create or hallucinate data that does not exist in the original text
- Do NOT guess numbers, dates, or any information not explicitly visible in the raw text
- If information is incomplete, use null and provide reason in the _missing_fields field
Return ONLY the specified JSON structure. Do NOT add any text outside the structure"""
@@ -1,19 +1,8 @@
FROM scb10x/typhoon2.5-qwen3-4b:latest
PARAMETER num_ctx 8192
PARAMETER num_ctx 12288
PARAMETER num_predict 4096
PARAMETER temperature 0.4
PARAMETER top_k 40
PARAMETER top_p 0.9
PARAMETER repeat_penalty 1.15
SYSTEM """คุณเป็นระบบ AI ผู้เชี่ยวชาญด้านการตอบคำถามเกี่ยวกับเอกสารการก่อสร้าง (LCBP3 DMS)
หน้าที่: ตอบคำถามของผู้ใช้โดยอ้างอิงจากเอกสารที่เกี่ยวข้อง
Guidelines:
1. อ้างอิงเลขที่เอกสาร และวันที่ของแหล่งข้อมูลทุกครั้ง
2. หากข้อมูลไม่ชัดเจนหรือไม่ครบถ้วน ให้ระบุความไม่แน่นอน
3. ตอบเป็นภาษาไทยที่เข้าใจง่าย โดยเน้นความถูกต้องเหนือการสร้างสรรค์
4. หากไม่พบข้อมูลที่เกี่ยวข้อง ให้บอกว่า "ไม่พบข้อมูลในเอกสาร"
5. ให้สรุปสั้นๆ แต่ครอบคลุมคำถาม"""
-164
View File
@@ -1,164 +0,0 @@
เพื่อให้สถาปัตยกรรม RAG สำหรับระบบ DMS ของคุณใช้งานได้จริงและมีประสิทธิภาพสูงสุด นี่คือ **รายละเอียดการ Implementation พร้อมตัวอย่างการตั้งค่า (Configuration)** ในแต่ละส่วน โดยอิงจากการใช้ Python เป็นหลักในการควบคุม Pipeline ครับ
## 1. Ingestion & Semantic Chunking Pipeline (Typhoon OCR + Typhoon 2.5)
ในขั้นตอนนี้ เราจะรับไฟล์เอกสาร นำเข้าสู่ OCR และส่งข้อความดิบให้ Typhoon 2.5 จัดโครงสร้างโดยใส่ <chunk> tag เพื่อนำมาตัดแบ่งเนื้อหาตามบริบทจริง
### ตัวอย่าง Prompt สำหรับ Typhoon 2.5 (รอบแรก) เพื่อใส่ Tag
```text
คุณคือผู้เชี่ยวชาญด้านการจัดการเอกสาร (DMS Editor)
หน้าที่ของคุณคือรับข้อความดิบจากระบบ OCR แล้วนำมาจัดโครงสร้างใหม่ให้อยู่ในรูปแบบ Markdown
เงื่อนไขสำคัญ:
1. ห้ามแก้ไข ตัดทอน หรือบิดเบือนข้อความสำคัญในเอกสาร
2. ให้วิเคราะห์เนื้อหา และแบ่งเนื้อหาออกเป็นส่วนๆ (Semantic Chunks) โดยใช้ Tag พิเศษ ครอบเนื้อหาที่เป็นเรื่องเดียวกันไว้ ดังนี้:
<chunk topic="หัวข้อหลักของเนื้อหาใน tag นี้"> ...ข้อความ... </chunk>
3. หากมีข้อมูลสำคัญ เช่น เลขที่เอกสาร, วันที่, ชื่อบุคคล หรือเรื่อง ให้สกัดออกมาในรูปแบบของ Metadata ไว้ที่ส่วนบนสุดของเอกสารด้วยโครงสร้าง JSON ใน Tag <metadata>...</metadata>
```
### Python Implementation (การตัด Chunk จาก Tag)
```python
import re
import json
# สมมติผลลัพธ์ที่ได้มาจาก Typhoon 2.5 รอบแรก
typhoon_output = """
<metadata>
{
"doc_number": "REQ-009",
"date": "2026-06-05",
"subject": "ขออนุมัติจัดซื้ออุปกรณ์เครือข่ายสำหรับโครงการ"
}
</metadata>
<chunk topic="วัตถุประสงค์และหลักการ">
เนื่องด้วยระบบเครือข่ายเดิมในสำนักงานสนามมีความเร็วไม่เพียงพอต่อการใช้งาน...
</chunk>
<chunk topic="รายการอุปกรณ์ที่ต้องการจัดซื้อ">
1. Managed Switch 24-Port 2.5GbE จำนวน 1 ตัว
2. Core Router ER7206 จำนวน 1 ตัว
</chunk>
"""
def parse_typhoon_chunks(output):
# สกัด Metadata
metadata_match = re.search(r'<metadata>(.*?)</metadata>', output, re.DOTALL)
metadata = json.loads(metadata_match.group(1).strip()) if metadata_match else {}
# สกัด Chunks
chunk_pattern = r'<chunk topic="(.*?)">(.*?)</chunk>'
chunks = re.findall(chunk_pattern, output, re.DOTALL)
processed_chunks = []
for topic, content in chunks:
processed_chunks.append({
"text": content.strip(),
"metadata": {
**metadata,
"chunk_topic": topic
}
})
return processed_chunks
chunks_to_embed = parse_typhoon_chunks(typhoon_output)
```
## 2. Vector Storage & Search Setup (BGE-M3 + Qdrant)
BGE-M3 สามารถทำ **Hybrid Search** ได้ดีมาก (Dense Vector แทนความหมายเชิงลึก + Sparse Vector แทนคำสำคัญ/Keyword) เราจะตั้งค่า Qdrant Collection ให้รองรับทั้งสองแบบเพื่อป้องกันปัญหาคำค้นหาเฉพาะ (เช่น เลขที่เอกสาร หรือรหัสพัสดุ) หลุดหาย
### ตัวอย่างการตั้งค่า Qdrant Collection (Python Client)
```python
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, SparseVectorParams, OptimizersConfigDiff
client = QdrantClient(url="http://localhost:6333")
# สร้าง Collection ที่รองรับทั้ง Dense และ Sparse (Hybrid)
client.create_collection(
collection_name="dms_documents",
vectors_config={
# Dense Vector สำหรับ BGE-M3 (ขนาด 1024 มิติ)
"bge_dense": VectorParams(
size=1024,
distance=Distance.COSINE
)
},
sparse_vectors_config={
# Sparse Vector สำหรับทำ Keyword Matching จาก BGE-M3
"bge_sparse": SparseVectorParams()
},
# เปิดใช้งาน Payload Index สำหรับ Metadata Filtering (เช่น ค้นหาเฉพาะเลขที่เอกสาร)
optimizers_config=OptimizersConfigDiff(memmap_threshold=20000)
)
```
## 3. Retrieval & Re-ranking Pipeline (Qdrant Search + BGE-Reranker)
เมื่อผู้ใช้ส่งคำถามเข้ามา เราจะดึงข้อมูลแบบ Hybrid จาก Qdrant ออกมาจำนวนหนึ่ง (เช่น 15 Chunks) แล้วใช้ **BGE-Reranker** สแกนซ้ำเพื่อเลือกตัวที่ใช่ที่สุด 3-5 อันดับแรก
### Python Implementation สำหรับ Hybrid Search และ Rerank
```python
from sentence_transformers import SentenceTransformer
from transformers import AutoModelForSequenceClassification, AutoTokenizer
import torch
# 1. โหลดโมเดลสำหรับ Embedding และ Reranking
# (ในงานจริงแนะนำให้ Host เป็น API แยก เช่น Tei หรือใช้ Local Inference)
embedding_model = SentenceTransformer('BAAI/bge-m3')
rerank_tokenizer = AutoTokenizer.from_pretrained('BAAI/bge-reranker-large')
rerank_model = AutoModelForSequenceClassification.from_pretrained('BAAI/bge-reranker-large')
rerank_model.eval()
def hybrid_search_and_rerank(query, top_k_qdrant=15, top_k_final=3):
# ก้าวที่ 1: แปลง Query เป็น Vector
query_dense_vector = embedding_model.encode(query).tolist()
# ก้าวที่ 2: ดึงข้อมูลจาก Qdrant (สมมติเรียกใช้ client.search)
# *ในจุดนี้สามารถใส่ Filter สำหรับเจาะจงเลขเอกสารหรือวันที่ได้จาก Payload*
qdrant_results = client.search(
collection_name="dms_documents",
query_vector=("bge_dense", query_dense_vector),
limit=top_k_qdrant
)
# ก้าวที่ 3: เตรียมข้อมูลเข้า Reranker
pairs = [[query, res.payload['text']] for res in qdrant_results]
with torch.no_grad():
inputs = rerank_tokenizer(pairs, padding=True, truncation=True, return_tensors='pt', max_length=512)
scores = rerank_model(**inputs).logits.view(-1).tolist()
# ก้าวที่ 4: ประกบคะแนนใหม่และจัดเรียงลำดับ
reranked_results = []
for i, res in enumerate(qdrant_results):
reranked_results.append({
"text": res.payload['text'],
"metadata": res.payload['metadata'],
"rerank_score": scores[i]
})
# เรียงจากคะแนนมากไปน้อย
reranked_results.sort(key=lambda x: x['rerank_score'], reverse=True)
# ส่งคืนเฉพาะ Top K ที่ดีที่สุดไปให้ LLM
return reranked_results[:top_k_final]
```
## 4. Generation Pipeline (Typhoon 2.5 รอบสุดท้าย)
นำ Chunks ที่ผ่านการ Rerank มาเรียงต่อกันเป็น Context เพื่อให้ Typhoon 2.5 ตอบคำถาม โดยเน้นย้ำให้โมเดลตอบตามข้อเท็จจริงในเอกสารเท่านั้น
### ตัวอย่างการประกอบ System Prompt และ Prompt template
```text
System Prompt:
คุณคือผู้ช่วยอัจฉริยะประจำระบบจัดการเอกสาร (DMS AI Assistant)
หน้าที่ของคุณคือตอบคำถามของผู้ใช้โดยใช้ข้อมูลจาก "Context เอกสารที่กำหนดให้" เท่านั้น
ห้ามใช้ความรู้ภายนอกที่ไม่มีในเอกสารตอบโดยเด็ดขาด
หากใน Context ไม่มีข้อมูลที่ตอบคำถามได้ ให้แจ้งผู้ใช้ตรงๆ ว่า "ไม่พบข้อมูลดังกล่าวในเอกสาร"
และโปรดอ้างอิง เลขที่เอกสาร (doc_number) ทุกครั้งที่ตอบคำถามเพื่อความน่าเชื่อถือ
----------------
Context เอกสารที่ค้นพบ:
[ข้อความจาก Chunk ที่ 1] (อ้างอิง: REQ-009)
[ข้อความจาก Chunk ที่ 2] (อ้างอิง: REQ-009)
----------------
คำถามของผู้ใช้:
อุปกรณ์เครือข่ายที่ขออนุมัติจัดซื้อในเอกสาร REQ-009 มีอะไรบ้างและใช้ที่ไหน?
```
### 🛠️ คำแนะนำเพิ่มเติมสำหรับการดูแลระบบ (Ops & Infrastructure)
1. **การลด Latency ของ Reranker:** เนื่องจาก Reranker ต้องคำนวณ Cross-Attention ทุกครั้งที่ค้นหา หากรันบน CPU อาจจะช้า (ประมาณ 1-2 วินาทีต่อการ Query) แนะนำให้รันบน **GPU** หรือเลือกใช้โมเดลย่อส่วนอย่าง bge-reranker-base เพื่อทำความเร็วให้ตอบรับกับระบบ DMS ที่มีผู้ใช้งานพร้อมกันจำนวนมาก
2. **การทำ Document Version Control (Void & Replace):** ในระบบ DMS เมื่อเอกสารมีการแก้ไข (เช่น ออกเวอร์ชันใหม่ของ REQ-009) อย่าลืมส่ง Metadata version หรือรันคำสั่ง **Delete Points** ใน Qdrant โดยกรองจาก doc_number เก่าออกไปก่อน เพื่อไม่ให้ระบบดึงเอาข้อความจากเอกสารเวอร์ชันเก่าขึ้นมาตอบปนกับเวอร์ชันปัจจุบันครับ
3.