ในโลกของ AI ที่ต้องการความแม่นยำสูง การค้นหาข้อมูลจากตาราง (Structured Data) และเอกสาร (Unstructured Data) พร้อมกันเป็นความท้าทายที่ใหญ่หลวง บทความนี้จะพาคุณเข้าใจวิธีการสร้างระบบ RAG แบบ Hybrid ที่รวมข้อมูลทั้งสองประเภทเข้าด้วยกัน พร้อมแบ่งปันประสบการณ์การย้ายจากระบบเดิมมาสู่ HolySheep AI ที่ประหยัดค่าใช้จ่ายได้ถึง 85%
ทำไมต้อง Hybrid RAG สำหรับข้อมูลตาราง?
จากประสบการณ์ที่พัฒนาระบบ Q&A สำหรับฐานข้อมูลผลิตภัณฑ์ของบริษัท พบว่าการใช้ RAG แบบเดียวไม่เพียงพอ ตัวอย่างเช่น ผู้ใช้ถามว่า "สินค้า X มีราคาเท่าไหร่ และมีรีวิวที่ดีที่สุดอะไรบ้าง" — คำตอบต้องดึงข้อมูลจากตารางราคา (Structured) และบทวิจารณ์ (Unstructured) พร้อมกัน
สถาปัตยกรรมระบบ Hybrid RAG
┌─────────────────────────────────────────────────────────────┐
│ Hybrid RAG Architecture │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Structured │ │ Unstructured │ │
│ │ (Table) │ │ (Docs) │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Table Chunk │ │ Text Chunk │ │
│ │ + Schema │ │ + Metadata │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ └────────┬──────────┘ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Vector Index │ │
│ │ (Unified) │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Reranker │ │
│ │ (Cross │ │
│ │ Encoder) │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ LLM Response │ │
│ │ Generator │ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
ขั้นตอนการติดตั้งและย้ายระบบ
1. ติดตั้ง Dependencies ที่จำเป็น
npm install @holysheepai/sdk langchain langchain-community \
pg vectorize-types zod exceljs papaparse
สำหรับ TypeScript ต้องติดตั้ง types ด้วย:
npm install -D @types/papaparse @types/exceljs
2. โค้ดสำหรับโหลดและประมวลผลข้อมูลตาราง
ด้านล่างคือโค้ดที่ทีมใช้ในการประมวลผลข้อมูลจาก Excel และ CSV เพื่อสร้าง Embedding สำหรับ Hybrid Search:
import { HolySheepAI } from '@holysheepai/sdk';
import * as Papa from 'papaparse';
import * as ExcelJS from 'exceljs';
const client = new HolySheepAI({
apiKey: process.env.HOLYSHEEP_API_KEY!,
baseURL: 'https://api.holysheep.ai/v1'
});
interface TableRow {
[key: string]: string | number | boolean;
}
interface ProcessedChunk {
content: string;
metadata: {
source: string;
rowIndex: number;
schema: string;
dataType: 'structured';
};
embedding?: number[];
}
async function processCSVTable(filePath: string): Promise<ProcessedChunk[]> {
const fileContent = await Bun.file(filePath).text();
return new Promise((resolve, reject) => {
const chunks: ProcessedChunk[] = [];
Papa.parse(fileContent, {
header: true,
dynamicTyping: true,
complete: async (results) => {
const headers = results.meta.fields || [];
for (let i = 0; i < results.data.length; i++) {
const row = results.data[i] as TableRow;
const schemaDescription = headers.map(h => ${h}: ${typeof row[h]}).join(', ');
// สร้าง text representation ของแถว
const rowText = headers
.map(h => ${h} = ${row[h]})
.join(' | ');
chunks.push({
content: Row ${i + 1}: ${rowText},
metadata: {
source: filePath,
rowIndex: i,
schema: schemaDescription,
dataType: 'structured'
}
});
}
resolve(chunks);
},
error: reject
});
});
}
async function processExcelTable(filePath: string): Promise<ProcessedChunk[]> {
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.readFile(filePath);
const chunks: ProcessedChunk[] = [];
workbook.eachSheet((worksheet, sheetId) => {
const headers: string[] = [];
worksheet.eachRow((row, rowNumber) => {
const values = row.values as (string | number | null)[];
if (rowNumber === 1) {
headers.push(...values.map(v => String(v ?? 'unnamed')));
return;
}
const rowData: TableRow = {};
headers.forEach((h, idx) => {
rowData[h] = values[idx] ?? '';
});
const rowText = headers
.map((h, idx) => ${h} = ${rowData[h]})
.join(' | ');
chunks.push({
content: Sheet ${sheetId}, Row ${rowNumber}: ${rowText},
metadata: {
source: ${filePath}#sheet${sheetId},
rowIndex: rowNumber,
schema: headers.map(h => ${h}: string).join(', '),
dataType: 'structured'
}
});
});
});
return chunks;
}
// ฟังก์ชันหลักสำหรับ embedding ข้อมูลตาราง
async function embedTableData(chunks: ProcessedChunk[]): Promise<void> {
const texts = chunks.map(c => c.content);
// ใช้ HolySheep API สำหรับ batch embedding
const embeddings = await client.embeddings.create({
model: 'text-embedding-3-small',
input: texts,
batchSize: 100
});
// เก็บ embeddings พร้อม metadata ลงใน vector store
// (ใช้ Pinecone, Weaviate, หรือ Qdrant ตามความเหมาะสม)
for (let i = 0; i < chunks.length; i++) {
chunks[i].embedding = embeddings.data[i].embedding;
await saveToVectorStore(chunks[i]);
}
console.log(✅ Embed เสร็จสิ้น: ${chunks.length} รายการ);
}
// ตัวอย่างการใช้งาน
async function main() {
const csvChunks = await processCSVTable('./data/products.csv');
const excelChunks = await processExcelTable('./data/inventory.xlsx');
const allChunks = [...csvChunks, ...excelChunks];
await embedTableData(allChunks);
}
main().catch(console.error);
3. การค้นหาแบบ Hybrid พร้อม Reranking
หลังจากประมวลผลข้อมูลเสร็จ ต่อไปคือการสร้างระบบค้นหาที่รวม Structured และ Unstructured เข้าด้วยกัน:
import { HolySheepAI } from '@holysheepai/sdk';
interface SearchResult {
content: string;
score: number;
metadata: Record<string, any>;
dataType: 'structured' | 'unstructured';
}
interface HybridQueryResult {
structuredResults: SearchResult[];
unstructuredResults: SearchResult[];
mergedResults: SearchResult[];
}
class HybridRAGRetriever {
private client: HolySheepAI;
constructor(apiKey: string) {
this.client = new HolySheepAI({
apiKey,
baseURL: 'https://api.holysheep.ai/v1'
});
}
async semanticSearch(
query: string,
filterType?: 'structured' | 'unstructured'
): Promise<{embedding: number[], query: string}> {
// สร้าง query embedding
const response = await this.client.embeddings.create({
model: 'text-embedding-3-small',
input: query
});
return {
embedding: response.data[0].embedding,
query
};
}
async structuredQuery(
tableName: string,
conditions: Record<string, any>
): Promise<SearchResult[]> {
// ค้นหาจาก structured data ด้วย SQL-like query
const whereClause = Object.entries(conditions)
.map(([key, value]) => ${key} = '${value}')
.join(' AND ');
// ดึงข้อมูลจาก PostgreSQL หรือ database ที่รองรับ
const results = await queryPostgres(
SELECT * FROM ${tableName} WHERE ${whereClause}
);
return results.map(row => ({
content: Object.entries(row)
.map(([k, v]) => ${k}: ${v})
.join(' | '),
score: 1.0, // exact match = score 1.0
metadata: row,
dataType: 'structured' as const
}));
}
async hybridSearch(
query: string,
topK: number = 10,
structuredWeight: number = 0.4,
unstructuredWeight: number = 0.6
): Promise<HybridQueryResult> {
// 1. Semantic search สำหรับทั้งสองประเภท
const { embedding, query: processedQuery } = await this.semanticSearch(query);
// 2. Vector search จาก Pinecone/Weaviate
const [structuredVectors, unstructuredVectors] = await Promise.all([
vectorStore.search(embedding, {
filter: { dataType: 'structured' },
topK: topK * 2
}),
vectorStore.search(embedding, {
filter: { dataType: 'unstructured' },
topK: topK * 2
})
]);
// 3. Cross-encoder reranking ด้วย HolySheep
const allCandidates = [
...structuredVectors.map(v => ({ ...v, dataType: 'structured' as const })),
...unstructuredVectors.map(v => ({ ...v, dataType: 'unstructured' as const }))
];
const reranked = await this.rerankWithCrossEncoder(
processedQuery,
allCandidates
);
// 4. รวมผลลัพธ์ตามน้ำหนักที่กำหนด
const mergedResults = this.mergeByWeight(
structuredVectors,
unstructuredVectors,
structuredWeight,
unstructuredWeight,
topK
);
return {
structuredResults: structuredVectors,
unstructuredResults: unstructuredVectors,
mergedResults
};
}
private async rerankWithCrossEncoder(
query: string,
candidates: SearchResult[]
): Promise<SearchResult[]> {
// ใช้ HolySheep สำหรับ reranking
const response = await this.client.chat.completions.create({
model: 'gpt-4o-mini',
messages: [
{
role: 'system',
content: `คุณคือตัวจัดลำดับความสำคัญ จัดลำดับผลลัพธ์จากความเกี่ยวข้องกับคำถาม
มี format output: JSON array ของ index ที่เรียงจาก relevant มากไปน้อย
ตัวอย่าง: [0, 2, 1, 3]`
},
{
role: 'user',
content: `คำถาม: ${query}\n\nผลลัพธ์:\n${
candidates.map((c, i) => ${i}: ${c.content}).join('\n')
}`
}
],
temperature: 0.1
});
// parse ranking...
return candidates;
}
private mergeByWeight(
structured: SearchResult[],
unstructured: SearchResult[],
sWeight: number,
uWeight: number,
topK: number
): SearchResult[] {
const scored = [
...structured.map(r => ({ ...r, weightedScore: r.score * sWeight })),
...unstructured.map(r => ({ ...r, weightedScore: r.score * uWeight }))
];
return scored
.sort((a, b) => b.weightedScore - a.weightedScore)
.slice(0, topK);
}
}
// ตัวอย่างการใช้งาน
async function example() {
const retriever = new HybridRAGRetriever(process.env.HOLYSHEEP_API_KEY!);
const result = await retriever.hybridSearch(
'สินค้าที่มีราคาต่ำกว่า 500 บาท และมี rating สูง พร้อมรีวิวจากลูกค้า',
10,
0.5, // 50% น้ำหนักสำหรับ structured
0.5 // 50% น้ำหนักสำหรับ unstructured
);
console.log('ผลลัพธ์แบบผสม:', result.mergedResults);
}
example();
4. สร้าง Context และ Generate คำตอบ
import { HolySheepAI } from '@holysheepai/sdk';
interface RAGResponse {
answer: string;
sources: Array<{content: string, source: string, score: number}>;