لماذا يصعب تصحيح تطبيقات LLM
افهموا لماذا لا تكفي السجلات التقليدية لتطبيقات LLM، وما المعلومات اللازمة لتشخيص الإخفاقات في مسارات RAG والوكلاء، ونموذج بيانات التتبع.
لماذا يصعب تصحيح تطبيقات LLM درس مجاني في AI Engineering Academy على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في AI Engineering Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة AI Engineering Academy 4 دروس في المجموع.
بعض أجزاء هذا الدرس لم تُترجم بعد وتظهر باللغة الإنجليزية.
The Unique Challenges of LLM Debugging
Traditional software fails deterministically: given the same input, it always produces the same output, and a stack trace points directly to the line that failed. LLM applications break these assumptions. The same prompt can produce different outputs on different calls, failures are often silent (wrong answer instead of exception), and the cause may be buried in a prompt used 5 steps earlier in a chain. Standard logging and debugging tools are simply not designed for this.
Non-Determinism Makes Reproduction Hard
LLM outputs are non-deterministic by default. Even with temperature=0, the same prompt may produce slightly different outputs due to batch processing and numerical precision. This means bugs are intermittent: a prompt that fails 20% of the time will pass your test suite if you only run it once. Reproducing a specific failure requires logging the exact input, model parameters, and output at the time of failure — not just the input.
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 responseSilent Failures: Wrong, Not Broken
The most insidious LLM failures are silent failures: the API call succeeds (HTTP 200, no exception), but the answer is wrong, hallucinated, incomplete, or off-topic. Your application happily processes the wrong answer and returns it to the user with no indication anything failed. Traditional monitoring that only watches for exceptions and error codes will never catch these — you need semantic monitoring of output quality.
# 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 sophisticatedMulti-Step Chains: Where Did It Go Wrong?
In a RAG pipeline or agent chain, a failure in the final response might trace back to a retrieval step that returned irrelevant chunks, which itself traces back to a chunking strategy that split a key sentence across two chunks, which traces back to an embedding model that did not handle technical jargon well. Without per-step tracing, you see only the wrong final answer with no way to pinpoint which step introduced the error.
# 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?Token Count and Cost Surprises
Without instrumentation, token counts and costs are invisible until the monthly bill arrives. A system prompt that grew from 500 to 5000 tokens due to an oversight, a retrieval function returning 20 chunks instead of 5, or a loop that calls the LLM 100 times instead of 10 — all of these silently multiply your costs. Instrument every LLM call to log prompt tokens, completion tokens, and estimated cost so anomalies are visible in real time.
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 responseLatency: Which Step Is Slow?
Users experience LLM latency as a single wait time, but it is actually the sum of many individual steps: vector database query, document retrieval, prompt assembly, API network call, token generation, and response parsing. Without per-step timing, you cannot tell whether a slow response is due to a slow retriever or a slow LLM call. Instrument each step with latency measurements to identify your real bottleneck.
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 answerWhat Information You Actually Need
To diagnose any LLM application failure, you need to capture and store: the complete input prompt (system + all messages), the model and parameters used (temperature, max_tokens), the complete output, token counts and estimated cost, latency per step, any tool calls and their results, and a session or request ID that ties all steps of one user request together. This is the minimum viable tracing dataset.
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
)Trace Correlation with Request IDs
A single user request might trigger 10 LLM calls across different services. Without a correlation ID that flows through all of them, you cannot group these calls into a single trace. Inject a unique request ID at the entry point of every user request and pass it in every downstream LLM call, database query, and log message. This makes it possible to reconstruct the complete execution path for any specific user request.
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)
})The LLM Observability Stack
The LLM observability stack has three layers: Logging captures structured records of every LLM call (LangSmith, Langfuse, custom logs). Metrics track aggregated numbers over time: request count, average latency, error rate, daily cost. Tracing records the causal chain of steps within a single request. Together these three pillars give you the visibility to diagnose failures, catch regressions, and optimize performance.
Alerting on Quality Degradation
Unlike traditional software where errors are binary (working/broken), LLM quality degrades gradually. A prompt change might reduce answer quality from 85% to 70% without any exceptions being thrown. Monitor quality by running automated evaluation (LLM-as-judge scoring) on a sample of production responses daily. Alert when the rolling average quality score drops below a threshold, before users start complaining.
Starting Your Observability Practice
Do not wait until you have a production incident to add observability. Start with three minimal steps: (1) log every LLM call with its full input, output, and token counts to a database or file, (2) assign a request ID to every user interaction and include it in all log entries, and (3) add per-step timing to your pipeline. These three things alone will make 80% of debugging tasks solvable in minutes rather than hours.
Quick Check
Test your understanding of why LLM apps are hard to debug from this lesson.
Lesson Recap
In this lesson you learned: non-determinism makes LLM bugs intermittent and hard to reproduce without capturing the full request context, silent failures (wrong answers from successful API calls) bypass traditional error monitoring and require semantic quality checks, and per-step tracing with request ID correlation is the minimum needed to diagnose failures in multi-step RAG and agent pipelines. Next up we implement tracing with LangSmith.
الأسئلة الشائعة
هل درس «لماذا يصعب تصحيح تطبيقات LLM» مجاني؟
نعم — نص درس «لماذا يصعب تصحيح تطبيقات LLM» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة AI Engineering Academy، انتقل إلى CoddyKit PRO. تتضمن دورة AI Engineering Academy 4 دروس في المجموع.
ماذا ستتعلم في «لماذا يصعب تصحيح تطبيقات LLM»؟
افهموا لماذا لا تكفي السجلات التقليدية لتطبيقات LLM، وما المعلومات اللازمة لتشخيص الإخفاقات في مسارات RAG والوكلاء، ونموذج بيانات التتبع. تتمرن على AI Engineering Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ AI Engineering Academy؟
لا تُشترط خبرة سابقة. AI Engineering Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.
كم من الوقت يستغرق درس «لماذا يصعب تصحيح تطبيقات LLM»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس AI Engineering Academy هذا؟
نعم. كل درس في AI Engineering Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- لماذا يصعب تصحيح تطبيقات LLM
- التتبع باستخدام LangSmith
- Langfuse للرصد المستقل عن النموذج
- التنبيه بشأن تدهور زمن الاستجابة والتكلفة والجودة