0Pricing
AI Agents · บทเรียน

การทำความเข้าใจและแทรกโครงร่าง

ดึงและจัดรูปแบบโครงร่าง DB สำหรับบริบทของ LLM: ตาราง คอลัมน์ และความสัมพันธ์

การทำความเข้าใจและแทรกโครงร่าง เป็นบทเรียน AI Agents ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน AI Agents และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส AI Agents มีบทเรียนทั้งหมด 4 บทเรียน

เหตุใดบริบทสคีมาจึงสำคัญ

LLM รู้ไวยากรณ์ SQL แต่ไม่รู้อะไรเลยเกี่ยวกับฐานข้อมูลของคุณ หากไม่มีบริบทสคีมา LLM จะสร้างชื่อและคอลัมน์ของตารางขึ้นมาเอง

การแทรกสคีมาหมายถึงการดึงโครงสร้างฐานข้อมูลของคุณออกมาโดยใช้โปรแกรม แล้วใส่โครงสร้างนั้นลงในทุกพรอมต์ — ทำให้ LLM รู้จักตาราง คอลัมน์ และชนิดข้อมูลที่มีอยู่จริงของคุณ

การสอบถาม INFORMATION_SCHEMA

ฐานข้อมูลเชิงสัมพันธ์หลักทั้งหมดเปิดเผยข้อมูลเมตาผ่าน INFORMATION_SCHEMA คุณสามารถสอบถามข้อมูลนี้เพื่อดูตาราง ชื่อคอลัมน์ และชนิดข้อมูลทั้งหมดได้โดยไม่ต้องแตะต้องโค้ดของแอปพลิเคชัน

วิธีนี้ใช้ได้กับ PostgreSQL, MySQL, SQL Server และ SQLite (โดยมีความแตกต่างเล็กน้อย)

import psycopg2

def get_schema(conn):
    query = '''
        SELECT table_name, column_name, data_type
        FROM information_schema.columns
        WHERE table_schema = 'public'
        ORDER BY table_name, ordinal_position
    '''
    with conn.cursor() as cur:
        cur.execute(query)
        return cur.fetchall()

การจัดกลุ่มคอลัมน์ตามตาราง

ผลลัพธ์ดิบจาก INFORMATION_SCHEMA เป็นรายการแถวข้อมูลแบบแบน ให้จัดกลุ่มตามชื่อตารางเพื่อสร้างตัวแทนข้อมูลแบบมีโครงสร้าง ซึ่งจัดรูปแบบเป็นพรอมต์ได้ง่ายกว่า

from collections import defaultdict

def build_schema_dict(conn):
    rows = get_schema(conn)
    schema = defaultdict(list)
    for table_name, column_name, data_type in rows:
        schema[table_name].append({
            'name': column_name,
            'type': data_type
        })
    return dict(schema)

# Result:
# {
#   'users': [{'name': 'id', 'type': 'integer'}, {'name': 'email', 'type': 'character varying'}],
#   'orders': [{'name': 'id', 'type': 'integer'}, {'name': 'user_id', 'type': 'integer'}]
# }

การจัดรูปแบบสคีมาสำหรับพรอมต์ LLM

LLM อ่านสคีมาในรูปแบบข้อความธรรมดา ใช้รูปแบบที่กระชับและอ่านง่าย โดยให้หนึ่งตารางอยู่ในหนึ่งบรรทัด พร้อมชื่อคอลัมน์และชนิดข้อมูลในวงเล็บ

การใส่คีย์หลัก (PK) และคีย์นอก (FK) ช่วยให้ LLM เขียนคำสั่ง JOIN ที่ถูกต้อง

def format_schema_for_prompt(schema_dict, pk_info=None, fk_info=None):
    lines = []
    for table, columns in schema_dict.items():
        col_parts = []
        for col in columns:
            label = col['name']
            if pk_info and (table, col['name']) in pk_info:
                label += ' PK'
            if fk_info and (table, col['name']) in fk_info:
                label += f' FK->{fk_info[(table, col["name"])]}'
            col_parts.append(f"{label} ({col['type']})")
        lines.append(f"Table {table}: {', '.join(col_parts)}")
    return '\n'.join(lines)

# Output:
# Table users: id PK (integer), email (varchar), created_at (timestamp)
# Table orders: id PK (integer), user_id FK->users.id (integer), total (float)

if __name__ == '__main__':
    demo_schema = {'users': [{'name': 'id', 'type': 'integer'}, {'name': 'email', 'type': 'varchar'}]}
    demo_pk = {('users', 'id')}
    print(format_schema_for_prompt(demo_schema, pk_info=demo_pk))

การใส่คีย์หลักและคีย์นอก

ความสัมพันธ์ของคีย์นอกเป็นส่วนที่สำคัญที่สุดของบริบทสคีมา เพราะบอก LLM ว่าควรเขียน JOIN อย่างไร ให้สอบถาม information_schema.table_constraints และ key_column_usage เพื่อดึงข้อมูลเหล่านี้ออกมา

def get_foreign_keys(conn):
    query = '''
        SELECT
            kcu.table_name,
            kcu.column_name,
            ccu.table_name AS foreign_table,
            ccu.column_name AS foreign_column
        FROM information_schema.table_constraints AS tc
        JOIN information_schema.key_column_usage AS kcu
            ON tc.constraint_name = kcu.constraint_name
        JOIN information_schema.constraint_column_usage AS ccu
            ON ccu.constraint_name = tc.constraint_name
        WHERE tc.constraint_type = 'FOREIGN KEY'
    '''
    with conn.cursor() as cur:
        cur.execute(query)
        return {
            (row[0], row[1]): f'{row[2]}.{row[3]}'
            for row in cur.fetchall()
        }

if __name__ == '__main__':
    class FakeCursor:
        def __enter__(self): return self
        def __exit__(self, *a): return False
        def execute(self, query): pass
        def fetchall(self):
            return [('orders', 'user_id', 'users', 'id')]
    class FakeConn:
        def cursor(self): return FakeCursor()

    fks = get_foreign_keys(FakeConn())
    print('Foreign keys found:')
    for (table, col), ref in fks.items():
        print(f'  {table}.{col} -> {ref}')

การบีบอัดสคีมา: ปัญหา

ฐานข้อมูลระดับองค์กรจริงอาจมีมากกว่า 200 ตาราง หากคุณแทรกสคีมาทั้งหมด จะเกินหน้าต่างบริบทของ GPT-4 และสิ้นเปลืองค่าใช้จ่ายกับโทเค็น

สคีมาที่มี 200 ตารางและแต่ละตารางมี 20 คอลัมน์ จะมีประมาณ 40,000 โทเค็นขึ้นไป ซึ่งแพงเกินกว่าจะส่งไปกับทุกคำถาม

def estimate_schema_tokens(schema_dict):
    text = format_schema_for_prompt(schema_dict)
    # Rough estimate: 1 token per 4 characters
    estimated_tokens = len(text) // 4
    print(f'Tables: {len(schema_dict)}')
    print(f'Estimated schema tokens: {estimated_tokens}')
    return estimated_tokens

# 200 tables * 15 columns * 25 chars/col = 75,000 chars = ~18,750 tokens
# Plus user question + system prompt = easily over context limit

การบีบอัดสคีมา: การแทรกเฉพาะส่วน

กลยุทธ์การบีบอัดที่มีประสิทธิภาพที่สุดคือ แทรกเฉพาะตารางที่เกี่ยวข้องกับคำถาม ใช้แนวทางสองระยะ — ถาม LLM ก่อนว่าจำเป็นต้องใช้ตารางใด จากนั้นจึงแทรกเฉพาะสคีมาของตารางเหล่านั้น

def select_relevant_tables(question, all_table_names, n=5):
    table_list = ', '.join(all_table_names)
    prompt = f'''Database tables: {table_list}

Question: {question}

List the {n} most relevant table names as a JSON array.
Example: ["users", "orders", "products"]'''

    response = llm_call(prompt)
    import json
    return json.loads(response)

def compressed_schema(question, conn):
    all_tables = list(build_schema_dict(conn).keys())
    relevant = select_relevant_tables(question, all_tables)
    full_schema = build_schema_dict(conn)
    return {t: full_schema[t] for t in relevant if t in full_schema}

การบีบอัดสคีมา: การตัดคอลัมน์ที่ไม่จำเป็นออก

หลายตารางมีคอลัมน์ตรวจสอบ เช่น created_at, updated_at, deleted_at, version, created_by ซึ่งแทบไม่เกี่ยวข้องกับคำถามทางธุรกิจ ให้ตัดคอลัมน์เหล่านี้ออกเพื่อลดจำนวนโทเค็น

AUDIT_COLUMNS = {
    'created_at', 'updated_at', 'deleted_at', 'created_by',
    'updated_by', 'version', 'is_deleted', 'modified_at'
}

def compress_schema(schema_dict, exclude_audit=True):
    compressed = {}
    for table, columns in schema_dict.items():
        # Skip internal/system tables
        if table.startswith('_') or table.startswith('pg_'):
            continue
        if exclude_audit:
            columns = [c for c in columns if c['name'] not in AUDIT_COLUMNS]
        if columns:  # only include if columns remain
            compressed[table] = columns
    return compressed

if __name__ == '__main__':
    demo_schema = {
        'users': [{'name': 'id', 'type': 'INT'}, {'name': 'email', 'type': 'VARCHAR'}, {'name': 'created_at', 'type': 'TIMESTAMP'}],
        'pg_stat': [{'name': 'x', 'type': 'INT'}],
    }
    compressed = compress_schema(demo_schema)
    print('Tables kept:', list(compressed.keys()))
    print('users columns after compression:', [c['name'] for c in compressed['users']])

การเพิ่มคำอธิบายตาราง

ชื่อคอลัมน์เพียงอย่างเดียวไม่ได้อธิบายความหมายได้ชัดเจนเสมอไป การเพิ่มคำอธิบายภาษาธรรมชาติว่าตารางแต่ละตารางแทนข้อมูลอะไร จะช่วยปรับปรุงคุณภาพการสร้าง SQL ได้อย่างมาก

จัดเก็บคำอธิบายไว้ในไฟล์การกำหนดค่า หรือเป็นความคิดเห็นของตาราง PostgreSQL

TABLE_DESCRIPTIONS = {
    'users': 'Registered app users with authentication info',
    'orders': 'Customer purchase orders',
    'order_items': 'Individual line items within an order',
    'products': 'Product catalog with pricing',
    'payments': 'Payment transactions linked to orders'
}

def format_schema_with_descriptions(schema_dict):
    lines = []
    for table, columns in schema_dict.items():
        desc = TABLE_DESCRIPTIONS.get(table, '')
        col_str = ', '.join(f"{c['name']} ({c['type']})" for c in columns)
        if desc:
            lines.append(f"Table {table} ({desc}): {col_str}")
        else:
            lines.append(f"Table {table}: {col_str}")
    return '\n'.join(lines)

if __name__ == '__main__':
    demo_schema = {'users': [{'name': 'id', 'type': 'INT'}], 'orders': [{'name': 'id', 'type': 'INT'}]}
    print(format_schema_with_descriptions(demo_schema))

การแคชสคีมา

สคีมาฐานข้อมูลแทบไม่เปลี่ยนแปลง การดึงข้อมูลจากสคีมาข้อมูลในทุกคำสั่งสอบถามเพิ่มทั้งเวลาแฝงและภาระโหลด ให้แคชสตริงสคีมาที่จัดรูปแบบแล้ว และทำให้แคชหมดสภาพเมื่อมีเหตุการณ์เปลี่ยนแปลงสคีมา หรือเมื่อ TTL ตามเวลาครบกำหนด

import time

class SchemaCache:
    def __init__(self, ttl_seconds=300):
        self._cache = None
        self._timestamp = 0
        self.ttl = ttl_seconds

    def get(self, conn):
        now = time.time()
        if self._cache is None or (now - self._timestamp) > self.ttl:
            print('Refreshing schema cache...')
            schema_dict = build_schema_dict(conn)
            fk_info = get_foreign_keys(conn)
            self._cache = format_schema_for_prompt(schema_dict, fk_info=fk_info)
            self._timestamp = now
        return self._cache

schema_cache = SchemaCache(ttl_seconds=300)

กระบวนการแทรกสคีมาแบบครบถ้วน

รวมเทคนิคทั้งหมดเข้าด้วยกัน: แคชสคีมาที่บีบอัดแล้ว แทรกสคีมานั้นลงในข้อความกำกับระบบ และใช้การกรองตารางแบบเลือกเฉพาะสำหรับฐานข้อมูลขนาดใหญ่

def build_sql_agent_prompt(question, conn, large_db=False):
    if large_db:
        schema = compressed_schema(question, conn)
        schema_text = format_schema_with_descriptions(schema)
    else:
        schema_text = schema_cache.get(conn)

    system = f'''You are a PostgreSQL expert.
Return ONLY a valid SELECT query based on this schema:

{schema_text}

Rules:
- Use only SELECT statements
- Use table aliases for clarity
- Limit results to 100 rows unless asked for all
'''
    return system

ตรวจสอบความรู้

เมื่อใดควรใช้การแทรกตารางแบบเลือกเฉพาะ แทนการแทรกสคีมาทั้งหมด

สรุป: ความเข้าใจและการแทรกสคีมา

การแทรกสคีมาอย่างมีประสิทธิภาพเป็นรากฐานของตัวแทนแปลง NL เป็น SQL ที่เชื่อถือได้ ให้ดึงโครงสร้างจาก สคีมาข้อมูล รวมความสัมพันธ์ของคีย์หลักและคีย์นอก แล้วจัดรูปแบบเป็นข้อความกระชับสำหรับ LLM

สำหรับฐานข้อมูลขนาดใหญ่: แคชสคีมา ตัดคอลัมน์ตรวจสอบออก และใช้การแทรกแบบเลือกเฉพาะเพื่อส่งเฉพาะตารางที่เกี่ยวข้องกับคำถามแต่ละข้อ คำอธิบายตารางด้วยภาษาธรรมชาติช่วยปรับปรุงคุณภาพของคำสั่งสอบถามได้มากยิ่งขึ้น

คำถามที่พบบ่อย

บทเรียน “การทำความเข้าใจและแทรกโครงร่าง” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การทำความเข้าใจและแทรกโครงร่าง” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส AI Agents ให้อัปเกรดเป็น CoddyKit PRO คอร์ส AI Agents มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การทำความเข้าใจและแทรกโครงร่าง”

ดึงและจัดรูปแบบโครงร่าง DB สำหรับบริบทของ LLM: ตาราง คอลัมน์ และความสัมพันธ์ คุณปฏิบัติ AI Agents ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน AI Agents หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน AI Agents บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน

บทเรียน “การทำความเข้าใจและแทรกโครงร่าง” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน AI Agents นี้ได้ไหม

ได้ บทเรียน AI Agents ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. การทำงานของตัวแทน NL-to-SQL
  2. การทำความเข้าใจและแทรกโครงร่าง
  3. การสร้างและตรวจสอบความถูกต้องของคำสั่ง SQL
  4. การจัดการคำถามฐานข้อมูลที่กำกวม
← กลับไปที่ AI Agents