AI Engineering Academy · บทเรียน

เหตุใดแอป LLM จึงแก้ไขข้อผิดพลาดได้ยาก

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

บทเรียน 1 จาก 413 ขั้นตอน

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

ความท้าทายเฉพาะของการแก้ไขข้อบกพร่อง LLM

ซอฟต์แวร์แบบดั้งเดิมทำงานล้มเหลวอย่างเป็นแบบแผน กล่าวคือ เมื่อได้รับข้อมูลนำเข้าเดียวกัน ก็จะสร้างผลลัพธ์เดิมเสมอ และการติดตามสแตกจะชี้ไปยังบรรทัดที่ล้มเหลวโดยตรง แต่แอปพลิเคชัน LLM ละเมิดข้อสมมติเหล่านี้ พรอมต์เดียวกันอาจสร้างผลลัพธ์ต่างกันในการเรียกแต่ละครั้ง ความล้มเหลวมักเกิดขึ้นโดยไม่ส่งสัญญาณใด ๆ (ได้คำตอบผิดแทนที่จะเกิดข้อยกเว้น) และสาเหตุอาจซ่อนอยู่ในพรอมต์ที่ใช้ก่อนหน้านั้น 5 ขั้นตอนในห่วงโซ่ เครื่องมือการบันทึกและการแก้ไขข้อบกพร่องมาตรฐานจึงไม่ได้ออกแบบมาสำหรับกรณีนี้

ความไม่เป็นแบบกำหนดทำให้การจำลองปัญหาทำได้ยาก

โดยค่าเริ่มต้น ผลลัพธ์ของ LLM ไม่เป็นแบบกำหนด แม้ตั้งค่า temperature=0 พรอมต์เดียวกันก็อาจสร้างผลลัพธ์ที่แตกต่างกันเล็กน้อย เนื่องจากการประมวลผลเป็นชุดและความแม่นยำทางตัวเลข ซึ่งหมายความว่าข้อบกพร่องจะเกิดขึ้นเป็นครั้งคราว: พรอมต์ที่ล้มเหลว 20% ของเวลาอาจผ่านชุดการทดสอบของคุณได้ หากคุณเรียกใช้เพียงครั้งเดียว การจำลองความล้มเหลวเฉพาะครั้งจำเป็นต้องบันทึกข้อมูลนำเข้าที่แน่นอน พารามิเตอร์ของโมเดล และผลลัพธ์ ณ เวลาที่เกิดความล้มเหลว ไม่ใช่บันทึกเพียงข้อมูลนำเข้าเท่านั้น

import json
import time

def logged_llm_call(client, messages, model, temperature, **kwargs):
    request_id = f'{int(time.time() * 1000)}-{id(messages)}'
    
    response = client.chat.completions.create(
        model=model,
        messages=messages,
        temperature=temperature,
        **kwargs
    )
    
    # Log EVERYTHING needed to reproduce this exact call
    log_entry = {
        'request_id': request_id,
        'model': model,
        'temperature': temperature,
        'messages': messages,
        'response': response.choices[0].message.content,
        'finish_reason': response.choices[0].finish_reason,
        'usage': response.usage.model_dump(),
        'timestamp': time.time()
    }
    write_to_trace_store(log_entry)
    return response

ความล้มเหลวที่ไม่ส่งสัญญาณ: ผิดแต่ไม่เสีย

ความล้มเหลวของ LLM ที่ตรวจจับได้ยากที่สุดคือ ความล้มเหลวที่ไม่ส่งสัญญาณ: การเรียก API สำเร็จ (HTTP 200 และไม่มีข้อยกเว้น) แต่คำตอบผิด แต่งขึ้น ไม่ครบถ้วน หรือไม่ตรงประเด็น แอปพลิเคชันของคุณจะประมวลผลคำตอบผิดนั้นต่ออย่างราบรื่นและส่งกลับให้ผู้ใช้ โดยไม่มีข้อบ่งชี้ว่ามีสิ่งใดล้มเหลว การตรวจสอบระบบแบบดั้งเดิมที่เฝ้าดูเฉพาะข้อยกเว้นและรหัสข้อผิดพลาดจะไม่พบปัญหาเหล่านี้ คุณจึงต้องตรวจสอบคุณภาพของผลลัพธ์ในเชิงความหมาย

# This succeeds with HTTP 200 but returns wrong information
response = client.chat.completions.create(
    model='gpt-4o',
    messages=[{'role': 'user', 'content': 'What is the boiling point of water at sea level?'}]
)

output = response.choices[0].message.content
# response.status_code: None (not relevant - always 200 if we got here)
# No exception thrown
# But if output is '90 degrees Celsius', it is WRONG and your app will serve bad data

# You need semantic validation:
def validate_boiling_point_answer(text: str) -> bool:
    return '100' in text  # Rough check - real validation is more sophisticated

ห่วงโซ่หลายขั้นตอน: ผิดพลาดที่จุดใด

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

# Without tracing: you see only the final wrong answer
def rag_pipeline_naive(query):
    chunks = retrieve(query)         # step 1 - might return bad chunks
    context = format_context(chunks) # step 2 - might truncate key info
    answer = generate(query, context) # step 3 - LLM gets bad context
    return answer  # WRONG - but why?

# With tracing: you can see each step's input and output
def rag_pipeline_traced(query):
    with trace_span('retrieve') as span:
        chunks = retrieve(query)
        span.set_attribute('num_chunks', len(chunks))
        span.set_attribute('top_chunk_score', chunks[0]['score'] if chunks else 0)
    
    with trace_span('format_context') as span:
        context = format_context(chunks)
        span.set_attribute('context_length', len(context))
    
    with trace_span('generate') as span:
        answer = generate(query, context)
        span.set_attribute('answer_length', len(answer))
    
    return answer  # Now you can diagnose: was retrieve the problem?

ความประหลาดใจจากจำนวนโทเค็นและค่าใช้จ่าย

หากไม่มีการวัดและบันทึกข้อมูล จำนวนโทเค็นและค่าใช้จ่ายจะมองไม่เห็นจนกว่าจะได้รับใบเรียกเก็บเงินรายเดือน พรอมต์ระบบที่ขยายจาก 500 เป็น 5,000 โทเค็นเนื่องจากการมองข้าม ฟังก์ชันดึงข้อมูลที่ส่งคืน 20 ส่วนแทนที่จะเป็น 5 ส่วน หรือห่วงโซ่ที่เรียกใช้ LLM 100 ครั้งแทนที่จะเป็น 10 ครั้ง ล้วนทำให้ค่าใช้จ่ายเพิ่มขึ้นหลายเท่าโดยไม่มีสัญญาณใด ๆ ให้วัดและบันทึกการเรียกใช้ LLM ทุกครั้ง เพื่อบันทึกจำนวนโทเค็นของพรอมต์ จำนวนโทเค็นของผลลัพธ์ และค่าใช้จ่ายโดยประมาณ ทำให้มองเห็นความผิดปกติได้แบบเรียลไทม์

COST_PER_1K = {'gpt-4o': {'input': 0.005, 'output': 0.015},
               'gpt-4o-mini': {'input': 0.000150, 'output': 0.000600}}

def compute_cost(model: str, usage) -> float:
    pricing = COST_PER_1K.get(model, {'input': 0.005, 'output': 0.015})
    input_cost = (usage.prompt_tokens / 1000) * pricing['input']
    output_cost = (usage.completion_tokens / 1000) * pricing['output']
    return input_cost + output_cost

def instrumented_call(client, model, messages):
    response = client.chat.completions.create(model=model, messages=messages)
    cost = compute_cost(model, response.usage)
    
    # Alert if single call is unexpectedly expensive
    if cost > 0.10:  # more than 10 cents for one call
        print(f'WARNING: Expensive LLM call: ${cost:.4f} ({response.usage.prompt_tokens} prompt tokens)')
    
    metrics.record('llm_cost_usd', cost, tags={'model': model})
    metrics.record('llm_prompt_tokens', response.usage.prompt_tokens)
    return response

เวลาแฝง: ขั้นตอนไหนช้า

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

import time
from contextlib import contextmanager

@contextmanager
def timed(name: str, metrics_client):
    start = time.monotonic()
    try:
        yield
    finally:
        elapsed_ms = (time.monotonic() - start) * 1000
        metrics_client.histogram(f'step_latency_ms', elapsed_ms, tags={'step': name})
        if elapsed_ms > 2000:  # flag steps taking more than 2 seconds
            print(f'SLOW STEP [{name}]: {elapsed_ms:.0f}ms')

# Usage
def rag_with_timing(query, metrics):
    with timed('embed_query', metrics):
        query_embedding = embed(query)
    
    with timed('vector_search', metrics):
        chunks = vector_db.search(query_embedding, top_k=5)
    
    with timed('llm_generate', metrics):
        answer = generate(query, chunks)
    
    return answer

ข้อมูลที่จำเป็นจริง ๆ

ในการวินิจฉัยความล้มเหลวของแอปพลิเคชัน LLM คุณต้องเก็บและจัดเก็บข้อมูลต่อไปนี้: พรอมต์ข้อมูลนำเข้าทั้งหมด (ระบบและข้อความทั้งหมด), โมเดลและพารามิเตอร์ที่ใช้ (temperature, max_tokens), ผลลัพธ์ทั้งหมด, จำนวนโทเค็นและค่าใช้จ่ายโดยประมาณ, เวลาแฝงของแต่ละขั้นตอน, การเรียกใช้เครื่องมือและผลลัพธ์ของเครื่องมือ และ รหัสเซสชันหรือรหัสคำขอที่เชื่อมโยงทุกขั้นตอนของคำขอผู้ใช้หนึ่งรายการเข้าด้วยกัน นี่คือชุดข้อมูลการติดตามร่องรอยขั้นต่ำที่ใช้งานได้

from dataclasses import dataclass, field
from typing import Optional
import time

@dataclass
class LLMTrace:
    request_id: str
    session_id: str
    step_name: str
    model: str
    temperature: float
    system_prompt: str
    user_messages: list[dict]
    response: str
    finish_reason: str
    prompt_tokens: int
    completion_tokens: int
    cost_usd: float
    latency_ms: float
    tool_calls: list[dict] = field(default_factory=list)
    error: Optional[str] = None
    timestamp: float = field(default_factory=time.time)

    def is_anomalous(self) -> bool:
        return (
            self.cost_usd > 0.10 or
            self.latency_ms > 10000 or
            self.finish_reason == 'length' or  # was cut off
            self.error is not None
        )

การเชื่อมโยงร่องรอยด้วยรหัสคำขอ

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

import uuid
from contextvars import ContextVar

# Thread-safe request ID propagation using context variables
request_id_var: ContextVar[str] = ContextVar('request_id', default='unknown')

def handle_user_request(query: str):
    # Set request ID at the entry point
    req_id = str(uuid.uuid4())[:8]
    request_id_var.set(req_id)
    return rag_pipeline(query)

def get_current_request_id() -> str:
    return request_id_var.get()

# Every LLM call logs with the same request_id
def log_llm_call(model, prompt, response):
    logger.info('LLM call', extra={
        'request_id': get_current_request_id(),  # automatically correlates all calls
        'model': model,
        'prompt_length': len(prompt),
        'response_length': len(response)
    })

องค์ประกอบการสังเกตการณ์ LLM

องค์ประกอบการสังเกตการณ์ LLM มีสามชั้น: การบันทึกเก็บระเบียนที่มีโครงสร้างของการเรียกใช้ LLM ทุกครั้ง (LangSmith, Langfuse และบันทึกแบบกำหนดเอง) เมตริกติดตามตัวเลขรวมตามช่วงเวลา ได้แก่ จำนวนคำขอ เวลาแฝงเฉลี่ย อัตราข้อผิดพลาด และค่าใช้จ่ายรายวัน การติดตามร่องรอยบันทึกห่วงโซ่เหตุและผลของขั้นตอนต่าง ๆ ภายในคำขอเดียวกัน เสาหลักทั้งสามนี้ช่วยให้คุณมองเห็นระบบเพียงพอที่จะวินิจฉัยความล้มเหลว ตรวจจับการถดถอย และปรับปรุงประสิทธิภาพ

การแจ้งเตือนเมื่อคุณภาพลดลง

ต่างจากซอฟต์แวร์แบบดั้งเดิมที่ข้อผิดพลาดเป็นแบบทวิภาค (ทำงาน/เสีย) คุณภาพของ LLM จะค่อย ๆ ลดลง การเปลี่ยนพรอมต์อาจทำให้คุณภาพคำตอบลดจาก 85% เหลือ 70% โดยไม่มีข้อยกเว้นเกิดขึ้น ให้ตรวจสอบคุณภาพด้วยการประเมินอัตโนมัติ (การให้คะแนนโดย LLM ในฐานะผู้ตัดสิน) กับตัวอย่างคำตอบจากระบบจริงทุกวัน แจ้งเตือนเมื่อคะแนนคุณภาพเฉลี่ยแบบเลื่อนลดลงต่ำกว่าเกณฑ์ ก่อนที่ผู้ใช้จะเริ่มร้องเรียน

เริ่มต้นแนวปฏิบัติด้านการสังเกตการณ์ระบบ

อย่ารอให้เกิดเหตุขัดข้องในระบบจริงก่อนจึงเพิ่มการสังเกตการณ์ระบบ ให้เริ่มด้วยสามขั้นตอนพื้นฐาน: (1) บันทึกการเรียกใช้ LLM ทุกครั้งพร้อมข้อมูลนำเข้า ผลลัพธ์ และจำนวนโทเค็นทั้งหมดลงในฐานข้อมูลหรือไฟล์ (2) กำหนดรหัสคำขอให้กับการโต้ตอบของผู้ใช้ทุกครั้งและใส่รหัสดังกล่าวในรายการบันทึกทั้งหมด และ (3) เพิ่มการจับเวลาแยกตามแต่ละขั้นตอนในกระบวนการของคุณ เพียงสามสิ่งนี้ก็ช่วยให้งานแก้ไขข้อบกพร่อง 80% แก้ได้ภายในไม่กี่นาทีแทนที่จะเป็นหลายชั่วโมง

ตรวจสอบความเข้าใจอย่างรวดเร็ว

ทดสอบความเข้าใจเกี่ยวกับสาเหตุที่แอปพลิเคชัน LLM แก้ไขข้อบกพร่องได้ยากจากบทเรียนนี้

ทบทวนบทเรียน

ในบทเรียนนี้ คุณได้เรียนรู้ว่า ความไม่เป็นแบบกำหนดทำให้ข้อบกพร่องของ LLM เกิดขึ้นเป็นครั้งคราวและจำลองปัญหาได้ยาก หากไม่เก็บบริบทของคำขอทั้งหมด ความล้มเหลวที่ไม่ส่งสัญญาณ (คำตอบผิดจากการเรียก API ที่สำเร็จ) จะหลบเลี่ยงการตรวจสอบข้อผิดพลาดแบบดั้งเดิมและจำเป็นต้องตรวจสอบคุณภาพเชิงความหมาย และ การติดตามร่องรอยแยกตามแต่ละขั้นตอนพร้อมการเชื่อมโยงด้วยรหัสคำขอคือสิ่งขั้นต่ำที่จำเป็นต่อการวินิจฉัยความล้มเหลวในกระบวนการ RAG และกระบวนการของเอเจนต์แบบหลายขั้นตอน บทถัดไป เราจะนำการติดตามร่องรอยมาใช้งานด้วย LangSmith

เริ่มต้นได้ฟรี

เรียนรู้ Python ด้วย AI tutor — ฟรี

เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป

คอร์ส
30
บทเรียน
120

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

บทเรียน “เหตุใดแอป LLM จึงแก้ไขข้อผิดพลาดได้ยาก” ฟรีหรือไม่

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

คุณจะเรียนรู้อะไรในบทเรียน “เหตุใดแอป LLM จึงแก้ไขข้อผิดพลาดได้ยาก”

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

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

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

บทเรียน “เหตุใดแอป LLM จึงแก้ไขข้อผิดพลาดได้ยาก” ใช้เวลานานแค่ไหน

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

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

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

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

  1. เหตุใดแอป LLM จึงแก้ไขข้อผิดพลาดได้ยาก
  2. การติดตามด้วย LangSmith
  3. Langfuse สำหรับการสังเกตการณ์ที่ไม่ขึ้นกับโมเดล
  4. การแจ้งเตือนเวลาแฝง ต้นทุน และคุณภาพที่ลดลง
← กลับไปที่ AI Engineering Academy