مقدمه: ساختن سیستم فقط نصف راهه
اگه مقالات قبلی ما رو دنبال کرده باشید — از Agentic RAG و پروتکل MCP گرفته تا سیستمهای چند-عاملی با LangGraph و CrewAI و مهندسی پرامپت پیشرفته — احتمالاً الان یه سیستم مبتنی بر LLM ساختید یا دارید میسازید. دمویتون عالی کار میکنه. مدیرتون خوشحاله. تیم محصول هیجانزدهست. ولی بذارید یه حقیقت تلخ بگم: ساختن سیستم فقط نصف راهه. نصف دیگهش اینه که بدونید سیستمتون واقعاً درست کار میکنه یا نه.
یه آمار تکاندهنده: بررسیهای اخیر نشون میده که بیش از ۶۰٪ شکستهای سیستمهای LLM در محیط تولید خیلی دیر شناسایی میشن — یعنی وقتی که کاربرها شکایت کردن، هزینهها سر به فلک کشیده، یا بدتر از همه، تصمیمات اشتباهی بر اساس خروجیهای هذیانی (hallucination) گرفته شده. صادقانه بگم، خودم هم این تجربه رو داشتم — سیستمی که تو محیط تست عالی کار میکرد، بعد از یه هفته در تولید شروع کرد به دادن جوابهای عجیبوغریب و ما تا دو روز بعد متوجه نشدیم. (اون دو روز یکی از اضطرابآورترین دورههایی بود که تجربه کردم.)
و موضوع هزینه رو هم در نظر بگیرید: هزینه APIهای LLM میتونه بدون مانیتورینگ مناسب تا ۱۰ برابر از حد مورد انتظار بالاتر بره. یه حلقه بینهایت در یه عامل، یه بازیابی بد در RAG که باعث میشه مدل مکرراً retry کنه، یا یه پرامپت تغییر یافته که طول توکنها رو دو برابر میکنه — همه اینا میتونن فاتحه بودجهتون رو بخونن.
خب، این مقاله دقیقاً درباره همین نصف دوم داستانه: ارزیابی (Evaluation)، مشاهدهپذیری (Observability) و مانیتورینگ (Monitoring) سیستمهای مبتنی بر LLM. از متریکها و ابزارها گرفته تا بهترین شیوهها و یکپارچهسازی با CI/CD، همه چیز رو با کد عملی پوشش میدیم. بیاید شروع کنیم.
تفاوت ارزیابی، مشاهدهپذیری و مانیتورینگ
قبل از اینکه وارد جزئیات بشیم، باید سه مفهوم کلیدی رو از هم تفکیک کنیم. خیلیها این سه تا رو قاطی میکنن، ولی هر کدوم نقش متفاوتی دارن و شما به هر سهتاشون نیاز دارید.
ارزیابی (Evaluation)
ارزیابی یعنی سنجش آفلاین کیفیت خروجی سیستم. فکر کنید مثل unit test نوشتن برای مدلهای زبانیه. شما یه مجموعه داده آزمایشی دارید (golden dataset) و میخواید ببینید مدل یا pipeline شما روی این دادهها چطور عمل میکنه. ارزیابی معمولاً قبل از دیپلوی انجام میشه — در مرحله توسعه، در CI/CD، یا وقتی نسخه جدیدی از پرامپت یا مدل رو تست میکنید.
- آیا جوابها دقیق و مرتبط هستن؟
- آیا هذیان (hallucination) تولید میشه؟
- آیا بازیابی اسناد در RAG درست کار میکنه؟
- آیا نسخه جدید پرامپت بهتر از قبلیه؟
مشاهدهپذیری (Observability)
مشاهدهپذیری یعنی دید عمیق به رفتار داخلی سیستم در حال اجرا. این فراتر از «آیا سیستم بالاست یا نه» میره. مشاهدهپذیری یعنی بتونید هر درخواست رو از ورودی کاربر تا خروجی نهایی ردیابی (trace) کنید: چه اسنادی بازیابی شدن، چه پرامپتی به مدل رفت، مدل چه توکنهایی تولید کرد، چه ابزارهایی صدا زده شدن، و هر مرحله چقدر زمان و هزینه برد.
فکر کنید مشاهدهپذیری مثل دیباگر (debugger) سیستمتون در محیط تولیده — ولی بدون اینکه نیاز باشه سیستم رو متوقف کنید.
مانیتورینگ (Monitoring)
مانیتورینگ یعنی نظارت لحظهای و هشداردهی (alerting) در محیط تولید. مانیتورینگ سطح بالاتره — داشبوردها، آلارمها، و شاخصهای کلیدی عملکرد (KPI). وقتی latency از حد مشخصی بالاتر میره، وقتی هزینهها ناگهان افزایش پیدا میکنن، یا وقتی نرخ خطاها بالا میره، مانیتورینگ به شما هشدار میده.
خلاصه تفاوتها:
- ارزیابی: «آیا سیستم خوب کار میکنه؟» — قبل از دیپلوی، آفلاین
- مشاهدهپذیری: «چرا سیستم اینطور رفتار میکنه؟» — دید عمیق، runtime
- مانیتورینگ: «آیا الان مشکلی هست؟» — هشداردهی، لحظهای
ببینید، هر سه تا لازم هستن. نمیشه یکی رو داشت و بقیه رو نادیده گرفت. بیاید هر کدوم رو با جزئیات بیشتر بررسی کنیم.
متریکهای ارزیابی LLM
اولین قدم برای ارزیابی هر سیستمی، انتخاب متریکهای درسته. متریکهای ارزیابی LLM رو میتونیم به چند دسته تقسیم کنیم.
متریکهای سنتی NLP و محدودیتهاشون
اگه با پردازش زبان طبیعی آشنا باشید، احتمالاً BLEU، ROUGE و BERTScore رو میشناسید. این متریکها سالها استاندارد ارزیابی مدلهای زبانی بودن:
- BLEU: همپوشانی n-gramها بین خروجی مدل و پاسخ مرجع رو میسنجه. اصالتاً برای ترجمه ماشینی طراحی شده.
- ROUGE: مشابه BLEU ولی بیشتر برای خلاصهسازی متن. انواع مختلفی داره (ROUGE-1, ROUGE-2, ROUGE-L).
- BERTScore: به جای مقایسه لغوی، از embeddingهای BERT برای سنجش شباهت معنایی استفاده میکنه.
ولی مشکل اینجاست: این متریکها برای سیستمهای مدرن LLM کافی نیستن. چرا؟ چون یه مدل زبانی میتونه یه جواب کاملاً درست و مفید بده که از نظر لغوی هیچ شباهتی به پاسخ مرجع نداره. مثلاً اگه سؤال «پایتخت فرانسه کجاست؟» باشه و پاسخ مرجع «پاریس» باشه، جواب «پایتخت فرانسه شهر پاریسه که در شمال کشور قرار داره» از نظر BLEU نمره پایینی میگیره، ولی کاملاً درسته. (این همون چیزیه که BLEU رو برای LLMهای مدرن تقریباً بیفایده میکنه.)
متریکهای مدرن برای سیستمهای RAG
متریکهای مدرنتر، مسئله رو از زاویههای مختلف بررسی میکنن:
- Faithfulness (وفاداری): آیا خروجی مدل بر اساس context ارائهشده هست؟ این مهمترین متریک ضد هذیان (anti-hallucination) برای سیستمهای RAG هست. اگه مقاله ما درباره Agentic RAG رو خونده باشید، میدونید که بازیابی اسناد مرتبط فقط نصف کاره — مدل باید بر اساس همون اسناد جواب بده، نه اینکه از خودش چیزی بسازه.
- Answer Relevancy (مرتبط بودن پاسخ): آیا پاسخ واقعاً به سؤال کاربر جواب میده؟ مدل ممکنه یه متن عالی تولید کنه که ربطی به سؤال نداره.
- Context Precision (دقت زمینه): آیا اسناد بازیابیشده واقعاً مرتبط هستن؟ آیا نویز در بینشون هست؟
- Context Recall (فراخوانی زمینه): آیا همه اسناد مرتبط بازیابی شدن؟ آیا چیز مهمی جا مونده؟
فریمورک RAGAS
فریمورک RAGAS (Retrieval Augmented Generation Assessment) یکی از محبوبترین ابزارها برای ارزیابی pipelineهای RAG هست. RAGAS این چهار متریک اصلی رو به صورت یکپارچه ارزیابی میکنه و یه نمره کلی بهتون میده. نکته جالبش اینه که برای ارزیابی، از خود LLM به عنوان ارزیاب استفاده میکنه — تکنیکی که بهش LLM-as-a-Judge میگن و در بخش بعدی مفصل بررسیش میکنیم.
متریکهای تشخیص هذیان
هذیان (Hallucination) کابوس هر تیمیه که سیستم LLM تولیدی داره. متریکهای تشخیص هذیان به دو دسته تقسیم میشن:
- Intrinsic Hallucination: مدل اطلاعاتی تولید میکنه که با context ارائهشده تناقض داره.
- Extrinsic Hallucination: مدل اطلاعاتی تولید میکنه که نه تأیید میشه و نه رد — یعنی از خودش اختراع کرده.
در سیستمهای تولیدی، هر دو نوع خطرناکن. ولی intrinsic hallucination معمولاً خطرناکتره چون مدل فعالانه اطلاعات غلط ارائه میده. فرق بزرگیه بین «نمیدانم» و «یه چیز اشتباه با اطمینان کامل میگم».
متریکهای وظیفهمحور
بسته به نوع سیستم، متریکهای خاصی هم وجود دارن. مثلاً برای سیستمهای تولید کد، HumanEval و MBPP استاندارد هستن — کد تولیدشده رو واقعاً اجرا میکنن و pass rate رو میسنجن. برای سیستمهای پاسخ به سؤال، Exact Match و F1 Score استفاده میشن.
نمونه کد: ارزیابی با DeepEval
بیاید یه مثال عملی ببینیم. DeepEval یکی از بهترین فریمورکهای ارزیابی LLM هست که مثل pytest کار میکنه و برای مهندسین نرمافزار خیلی آشناست (راستش، همین شباهت به pytest بود که من رو فوری جذب کرد):
import pytest
from deepeval import assert_test
from deepeval.test_case import LLMTestCase
from deepeval.metrics import (
AnswerRelevancyMetric,
FaithfulnessMetric,
HallucinationMetric,
ContextualRelevancyMetric,
ContextualRecallMetric,
)
# تعریف متریکها با آستانههای مشخص
answer_relevancy = AnswerRelevancyMetric(
threshold=0.7,
model="gpt-4o",
include_reason=True
)
faithfulness = FaithfulnessMetric(
threshold=0.85,
model="gpt-4o",
include_reason=True
)
hallucination = HallucinationMetric(
threshold=0.5,
model="gpt-4o",
include_reason=True
)
context_relevancy = ContextualRelevancyMetric(
threshold=0.7,
model="gpt-4o",
include_reason=True
)
context_recall = ContextualRecallMetric(
threshold=0.7,
model="gpt-4o",
include_reason=True
)
def test_rag_pipeline_pricing_query():
"""تست پاسخدهی RAG برای سؤالات مربوط به قیمتگذاری."""
test_case = LLMTestCase(
input="قیمت پلن حرفهای چقدره؟",
actual_output="پلن حرفهای ماهانه ۱۵۰ دلار هست و شامل "
"دسترسی نامحدود به API، پشتیبانی اولویتدار "
"و ۱۰۰ هزار توکن روزانه میشه.",
expected_output="پلن حرفهای ماهانه ۱۵۰ دلار است.",
retrieval_context=[
"پلن پایه: ماهانه ۵۰ دلار - شامل ۱۰ هزار توکن روزانه",
"پلن حرفهای: ماهانه ۱۵۰ دلار - شامل دسترسی نامحدود "
"به API، پشتیبانی اولویتدار و ۱۰۰ هزار توکن روزانه",
"پلن سازمانی: تماس بگیرید - ویژگیهای سفارشی",
]
)
# ارزیابی با تمام متریکها
assert_test(test_case, [
answer_relevancy,
faithfulness,
hallucination,
context_relevancy,
context_recall,
])
def test_rag_pipeline_refund_query():
"""تست پاسخدهی RAG برای سؤالات مربوط به بازگشت وجه."""
test_case = LLMTestCase(
input="سیاست بازگشت وجه شما چیه؟",
actual_output="شما تا ۳۰ روز پس از خرید میتونید "
"درخواست بازگشت وجه بدید. برای این کار "
"باید از طریق پنل کاربری تیکت ثبت کنید.",
retrieval_context=[
"سیاست بازگشت وجه: تا ۳۰ روز پس از خرید با "
"ارائه دلیل معتبر. درخواست از طریق تیکت پشتیبانی.",
]
)
assert_test(test_case, [
answer_relevancy,
faithfulness,
hallucination,
])
برای اجرای این تستها، کافیه از خط فرمان بزنید deepeval test run test_rag_eval.py یا pytest test_rag_eval.py. DeepEval نتایج رو با جزئیات کامل نشون میده — شامل نمره هر متریک، دلیل نمرهدهی، و اینکه تست pass شده یا fail.
LLM-as-a-Judge: ارزیابی با استفاده از مدلهای زبانی
یکی از مهمترین تحولات حوزه ارزیابی LLM در سالهای اخیر، تکنیک LLM-as-a-Judge هست — یعنی استفاده از یه مدل زبانی برای ارزیابی خروجی یه مدل زبانی دیگه (یا حتی خودش). صادقانه بگم، اولین بار که این ایده رو شنیدم فکر کردم خیلی عجیبه — مثل اینه که از یه دانشآموز بخوایم امتحان یه دانشآموز دیگه رو تصحیح کنه. ولی در عمل، تحقیقات نشون داده که LLM-as-a-Judge همبستگی بالای ۸۰٪ با ارزیابی انسانی داره، و خیلی سریعتر و ارزونتره.
روشهای مختلف LLM-as-a-Judge
سه رویکرد اصلی برای این تکنیک وجود داره:
- Pointwise Scoring (نمرهدهی تکی): مدل ارزیاب یه خروجی رو بررسی میکنه و نمرهای بین ۱ تا ۵ بهش میده. سادهترین روشه ولی ممکنه تورش داشته باشه.
- Pairwise Comparison (مقایسه جفتی): مدل ارزیاب دو خروجی مختلف رو با هم مقایسه میکنه و بهتر رو انتخاب میکنه. دقیقتره چون مقایسه نسبی آسونتر از نمرهدهی مطلقه.
- Reference-Guided (با راهنمای مرجع): مدل ارزیاب یه پاسخ مرجع (ground truth) هم داره و خروجی رو نسبت به اون میسنجه.
رویکرد G-Eval با Chain-of-Thought
یکی از بهترین تکنیکهای LLM-as-a-Judge، رویکرد G-Eval هست. ایده اصلی اینه که قبل از نمرهدهی، از مدل بخواید مراحل ارزیابی رو قدمبهقدم بنویسه (Chain-of-Thought) و بعد نمره بده. تحقیقات نشون داده که این کار دقت ارزیابی رو به طور قابل توجهی بالا میبره — دقیقاً مثل تکنیک CoT که در مقاله مهندسی پرامپت پیشرفته بررسی کردیم.
بهترین شیوههای LLM-as-a-Judge
- معیارها رو تفکیک کنید: به جای اینکه بگید «کیفیت پاسخ رو ارزیابی کن»، معیارهای مشخصی تعریف کنید: دقت، کامل بودن، لحن، ساختار.
- از مقیاسهای کوچک عدد صحیح استفاده کنید: مقیاس ۱ تا ۵ خیلی بهتر از ۱ تا ۱۰ یا ۰ تا ۱۰۰ کار میکنه. مدلها در تمایز بین ۵ سطح خیلی بهتر از ۱۰ سطح هستن.
- مثال بدید (Few-Shot): برای هر نمره، یه مثال ارائه بدید تا مدل بفهمه ۲ از ۵ یعنی چی و ۴ از ۵ یعنی چی.
- تورشها رو مدیریت کنید: مدلها تورشهای شناختهشدهای دارن — مثلاً Position Bias (ترجیح پاسخ اول در مقایسه جفتی) و Verbosity Bias (نمره بالاتر به پاسخهای طولانیتر).
نمونه کد: پیادهسازی LLM-as-a-Judge
from openai import OpenAI
import json
client = OpenAI()
JUDGE_SYSTEM_PROMPT = """شما یک ارزیاب متخصص کیفیت پاسخهای
سیستمهای هوش مصنوعی هستید. وظیفه شما ارزیابی کیفیت پاسخ
بر اساس معیارهای مشخص است.
برای هر ارزیابی:
1. ابتدا قدمبهقدم تحلیل کنید (Chain-of-Thought)
2. سپس برای هر معیار نمره ۱ تا ۵ بدهید
3. در نهایت نمره کلی و توضیح بدهید
## معیارهای ارزیابی:
### دقت (Accuracy) - ۱ تا ۵
1: اطلاعات کاملاً اشتباه یا هذیان
2: اطلاعات عمدتاً اشتباه با برخی نکات درست
3: ترکیبی از اطلاعات درست و نادرست
4: عمدتاً دقیق با خطاهای جزئی
5: کاملاً دقیق و مبتنی بر context
### کامل بودن (Completeness) - ۱ تا ۵
1: پاسخ تقریباً خالی یا کاملاً ناقص
2: فقط بخش کوچکی از سؤال پاسخ داده شده
3: پاسخ نسبتاً کامل ولی نکات مهمی جا افتاده
4: پاسخ کامل با جزئیات کافی
5: پاسخ جامع و کامل با تمام جزئیات لازم
### مرتبط بودن (Relevancy) - ۱ تا ۵
1: پاسخ کاملاً نامرتبط
2: فقط بخش کوچکی مرتبط است
3: نسبتاً مرتبط ولی حاشیهپردازی دارد
4: مرتبط و متمرکز
5: کاملاً مرتبط و بدون حاشیه
خروجی خود را به صورت JSON با این فرمت ارائه دهید:
{
"reasoning": "تحلیل قدمبهقدم",
"accuracy": {"score": int, "explanation": str},
"completeness": {"score": int, "explanation": str},
"relevancy": {"score": int, "explanation": str},
"overall_score": float,
"pass": bool
}"""
def evaluate_response(
question: str,
response: str,
context: str = None,
reference_answer: str = None,
pass_threshold: float = 3.5,
) -> dict:
"""ارزیابی پاسخ LLM با رویکرد LLM-as-a-Judge."""
user_message = f"""## سؤال کاربر:
{question}
## پاسخ سیستم:
{response}"""
if context:
user_message += f"\n\n## Context ارائهشده:\n{context}"
if reference_answer:
user_message += f"\n\n## پاسخ مرجع:\n{reference_answer}"
user_message += f"""
\n\n## آستانه قبولی: نمره کلی بالای {pass_threshold}
لطفاً ارزیابی کنید."""
completion = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": JUDGE_SYSTEM_PROMPT},
{"role": "user", "content": user_message},
],
response_format={"type": "json_object"},
temperature=0.1,
)
result = json.loads(
completion.choices[0].message.content
)
return result
# استفاده عملی
result = evaluate_response(
question="تفاوت RAG و fine-tuning چیه؟",
response="RAG یعنی بازیابی اسناد مرتبط و ارائه اونها "
"به مدل به عنوان context. Fine-tuning یعنی "
"آموزش مجدد وزنهای مدل روی دادههای جدید. "
"RAG برای دانش بهروز مناسبتره و fine-tuning "
"برای تغییر رفتار و سبک مدل.",
reference_answer="RAG اسناد مرتبط را بازیابی و به مدل "
"ارائه میدهد. Fine-tuning وزنهای مدل "
"را تغییر میدهد.",
)
print(f"نمره دقت: {result['accuracy']['score']}")
print(f"نمره کلی: {result['overall_score']}")
print(f"قبول شد: {result['pass']}")
print(f"استدلال: {result['reasoning']}")
چه وقت از LLM-as-a-Judge استفاده نکنیم؟
با وجود قدرت این تکنیک، مواردی هست که نباید بهش اتکا کنید:
- وقتی دقت بالای ۹۵٪ نیاز دارید: برای کاربردهای پزشکی، حقوقی یا مالی، ارزیابی انسانی هنوز ضروریه.
- وقتی خروجی خیلی تخصصیه: اگه خروجی نیاز به دانش تخصصی خاصی داره (مثلاً تشخیص پزشکی)، مدل ارزیاب ممکنه صلاحیت نداشته باشه.
- وقتی تورشها بحرانی هستن: اگه تصمیمات مهمی بر اساس نتایج ارزیابی گرفته میشه، تورشهای مدل ارزیاب میتونن مشکلساز بشن.
قانون کلی: از LLM-as-a-Judge برای غربالگری سریع و مقیاسپذیر استفاده کنید و از ارزیابی انسانی برای اعتبارسنجی نهایی. این دوتا مکمل همن، نه جایگزین.
ابزارهای مشاهدهپذیری و ردیابی
راستش، در سال ۲۰۲۶، اکوسیستم ابزارهای مشاهدهپذیری LLM به بلوغ رسیده و گزینههای خوبی داریم. حالا بیاید بریم سراغشون. هر کدوم نقاط قوت خاص خودشون رو دارن.
Langfuse: ستاره متنباز
Langfuse احتمالاً محبوبترین ابزار متنباز مشاهدهپذیری LLM الان هست. با لایسنس MIT منتشر شده، بیش از ۶ میلیون نصب SDK در ماه داره، و میتونید هم به صورت self-hosted و هم cloud اجراش کنید. Langfuse ردیابی کامل درخواستها (tracing)، ارزیابی خروجیها، مدیریت پرامپتها و داشبوردهای تحلیلی رو ارائه میده.
نقاط قوت Langfuse:
- متنباز با لایسنس MIT — میتونید روی سرور خودتون اجرا کنید
- پشتیبانی از OpenAI، Anthropic، LangChain، LlamaIndex و خیلی فریمورکهای دیگه
- ردیابی سلسلهمراتبی (nested traces) برای pipelineهای پیچیده
- محاسبه خودکار هزینه و latency
- سیستم ارزیابی داخلی (scores) برای هر trace
LangSmith: بهترین برای اکوسیستم LangChain
LangSmith توسط تیم LangChain ساخته شده و طبیعتاً بهترین یکپارچگی رو با LangChain و LangGraph داره. اگه سیستمتون بر پایه LangChain ساخته شده (مثلاً سیستمهای چند-عاملی که در مقاله مربوطه بررسی کردیم)، LangSmith انتخاب خیلی خوبیه. ردیابی خودکار، دیتاستهای ارزیابی، و ابزارهای annotation رو ارائه میده.
Arize Phoenix: تحلیل تولید
Arize Phoenix یه ابزار متنباز دیگهست که روی تحلیل سیستمهای ML و LLM در محیط تولید تمرکز داره. نقطه قوتش ابزارهای بصریسازی embeddingها و تشخیص drift (انحراف) در دادههاست — اگه میخواید ببینید آیا نوع سؤالات کاربرانتون با گذشت زمان تغییر کرده، Phoenix خیلی بهدردتون میخوره.
Braintrust: ارزیابی و مانیتورینگ جامع
Braintrust یه پلتفرم جامعه که هم ارزیابی آفلاین و هم مانیتورینگ آنلاین رو پوشش میده. ویژگی خاصش اینه که میتونید آزمایشهای A/B روی پرامپتها و مدلها انجام بدید و نتایج رو مستقیم مقایسه کنید.
Datadog LLM Observability: برای تیمهای سازمانی
اگه سازمانتون از Datadog استفاده میکنه، قابلیت LLM Observability این پلتفرم خیلی جالبه. مزیت اصلیش اینه که مشاهدهپذیری LLM رو با بقیه زیرساختهاتون (سرورها، دیتابیسها، APIها) در یه جا یکپارچه میکنه. وقتی latency بالا میره، میتونید ببینید مشکل از LLM هست یا از دیتابیس یا از شبکه.
نمونه کد: راهاندازی Langfuse Tracing
بیاید یه مثال عملی از راهاندازی Langfuse ببینیم. از رویکرد decorator استفاده میکنیم که سادهترین روشه:
from langfuse.decorators import observe, langfuse_context
from langfuse.openai import openai
import os
# تنظیمات Langfuse
os.environ["LANGFUSE_PUBLIC_KEY"] = "pk-lf-..."
os.environ["LANGFUSE_SECRET_KEY"] = "sk-lf-..."
os.environ["LANGFUSE_HOST"] = "https://cloud.langfuse.com"
@observe()
def retrieve_documents(query: str) -> list[str]:
"""بازیابی اسناد مرتبط از پایگاه دانش."""
# شبیهسازی بازیابی - در عمل از vector store استفاده کنید
docs = [
"پلن حرفهای شامل API نامحدود و پشتیبانی ۲۴/۷ است.",
"قیمت پلن حرفهای ماهانه ۱۵۰ دلار میباشد.",
]
# ثبت متادیتا در Langfuse
langfuse_context.update_current_observation(
metadata={"source": "vector_store", "top_k": 5},
output=docs,
)
return docs
@observe()
def generate_response(query: str, context: list[str]) -> str:
"""تولید پاسخ با استفاده از LLM."""
context_text = "\n".join(context)
response = openai.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "system",
"content": "شما یک دستیار پشتیبانی هستید. "
"فقط بر اساس context پاسخ دهید.",
},
{
"role": "user",
"content": f"Context:\n{context_text}\n\n"
f"سؤال: {query}",
},
],
temperature=0.1,
)
return response.choices[0].message.content
@observe()
def rag_pipeline(query: str) -> str:
"""Pipeline کامل RAG با ردیابی Langfuse."""
# ثبت ورودی کاربر
langfuse_context.update_current_trace(
user_id="user_123",
session_id="session_456",
tags=["production", "rag"],
)
# مرحله ۱: بازیابی
docs = retrieve_documents(query)
# مرحله ۲: تولید پاسخ
response = generate_response(query, docs)
# ثبت نمره کیفیت (میتونه از LLM-as-Judge باشه)
langfuse_context.score_current_trace(
name="user_feedback",
value=1, # ۱ = مثبت، ۰ = منفی
comment="پاسخ مفید بود",
)
return response
# اجرا
result = rag_pipeline("قیمت پلن حرفهای چقدره؟")
print(result)
با اجرای این کد، یه trace کامل در داشبورد Langfuse ایجاد میشه که نشون میده: چه سؤالی پرسیده شده، چه اسنادی بازیابی شدن، چه پرامپتی به مدل رفته، مدل چه جوابی داده، latency هر مرحله چقدر بوده، و چند توکن مصرف شده.
این سطح از دید، برای دیباگ کردن مشکلات در محیط تولید حیاتیه. بدون این اطلاعات، دارید کورکورانه دنبال مشکل میگردید.
OpenTelemetry و استانداردسازی مشاهدهپذیری
یکی از تحولات مهم ۲۰۲۶، ورود OpenTelemetry (OTel) به دنیای مشاهدهپذیری LLM هست. OpenTelemetry یه استاندارد باز برای ردیابی، متریکها و لاگها در سیستمهای توزیعشدهست که قبلاً در دنیای میکروسرویسها خیلی جا افتاده بود. حالا با معرفی GenAI Semantic Conventions، این استاندارد به دنیای LLM هم اومده.
چرا OpenTelemetry مهمه؟
مشکل اصلی ابزارهای مشاهدهپذیری LLM اینه که هر کدوم فرمت خودشون رو دارن. اگه از Langfuse استفاده کنید و بخواید به LangSmith مهاجرت کنید، باید کل کدتون رو عوض کنید. OpenTelemetry این مشکل رو حل میکنه — یه بار instrument میکنید و دادهها رو به هر backend دلخواه (Langfuse، Datadog، Jaeger، و غیره) ارسال میکنید.
GenAI Semantic Conventions
استانداردهای معنایی GenAI، attributeهای مشخصی برای عملیات LLM تعریف کردن:
gen_ai.system— نام ارائهدهنده (openai, anthropic, ...)gen_ai.request.model— مدل استفادهشدهgen_ai.usage.input_tokens— تعداد توکنهای ورودیgen_ai.usage.output_tokens— تعداد توکنهای خروجیgen_ai.response.finish_reason— دلیل پایان تولید
نمونه کد: OpenTelemetry برای LLM
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
BatchSpanProcessor,
ConsoleSpanExporter,
)
from opentelemetry.sdk.resources import Resource
# راهاندازی OpenTelemetry
resource = Resource.create({
"service.name": "my-llm-app",
"service.version": "1.0.0",
})
provider = TracerProvider(resource=resource)
processor = BatchSpanProcessor(ConsoleSpanExporter())
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)
tracer = trace.get_tracer("llm-app")
def traced_llm_call(prompt: str, model: str = "gpt-4o"):
"""فراخوانی LLM با ردیابی OpenTelemetry."""
with tracer.start_as_current_span("llm.chat") as span:
# ثبت attributeهای استاندارد GenAI
span.set_attribute("gen_ai.system", "openai")
span.set_attribute("gen_ai.request.model", model)
span.set_attribute(
"gen_ai.request.temperature", 0.1
)
# فراخوانی مدل
from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
temperature=0.1,
)
# ثبت اطلاعات پاسخ
usage = response.usage
span.set_attribute(
"gen_ai.usage.input_tokens",
usage.prompt_tokens,
)
span.set_attribute(
"gen_ai.usage.output_tokens",
usage.completion_tokens,
)
span.set_attribute(
"gen_ai.response.finish_reason",
response.choices[0].finish_reason,
)
return response.choices[0].message.content
# استفاده
result = traced_llm_call("مزایای استفاده از RAG چیست؟")
print(result)
با این رویکرد، دادههای ردیابی شما مستقل از هر ابزار خاصی هستن. میتونید همین spanها رو به Langfuse، Datadog، Jaeger یا هر سیستم مشاهدهپذیری دیگهای ارسال کنید — فقط exporter رو عوض میکنید. این انعطافپذیری خیلی ارزشمنده، مخصوصاً وقتی سازمانها ابزارهای مشاهدهپذیریشون رو تغییر میدن.
ارزیابی خودکار در خطلوله CI/CD
ارزیابی دستی خوبه، ولی مقیاسپذیر نیست. هر بار که پرامپتتون رو تغییر میدید، مدل رو عوض میکنید، یا pipeline بازیابی رو بهینه میکنید، باید مطمئن بشید که کیفیت افت نکرده. اینجاست که ارزیابی خودکار در CI/CD وارد بازی میشه.
اجرای ارزیابیها به عنوان بخشی از Pipeline
ایده سادهست: مثل اینکه unit test مینویسید و در CI اجرا میکنید، تستهای ارزیابی LLM رو هم مینویسید و در pipeline دیپلویتون اجرا میکنید. اگه نمرهها از آستانه تعیینشده پایینتر بیاد، دیپلوی متوقف میشه. همین. ساده ولی قوی.
Quality Gates (دروازههای کیفیت)
یه مفهوم مهم، Quality Gate هست — آستانههای حداقلی که باید قبل از دیپلوی رعایت بشن. مثلاً:
- Faithfulness > 0.85
- Answer Relevancy > 0.70
- Hallucination Rate < 0.15
- Average Latency < 3 seconds
- Cost per query < $0.05
نمونه کد: یکپارچهسازی DeepEval با CI/CD
بیاید ببینیم چطور میتونید DeepEval رو در pipeline CI/CD (مثلاً GitHub Actions) یکپارچه کنید:
# conftest.py - تنظیمات مشترک تستهای ارزیابی
import pytest
from deepeval.dataset import EvaluationDataset, Golden
def load_golden_dataset() -> EvaluationDataset:
"""بارگذاری دیتاست طلایی برای ارزیابی."""
goldens = [
Golden(
input="قیمت پلن حرفهای چقدره؟",
expected_output="پلن حرفهای ماهانه ۱۵۰ دلار است.",
context=[
"پلن حرفهای: ماهانه ۱۵۰ دلار - شامل API "
"نامحدود و پشتیبانی اولویتدار"
],
),
Golden(
input="چطور اشتراکم رو لغو کنم؟",
expected_output="از پنل کاربری بخش اشتراک، "
"گزینه لغو اشتراک را انتخاب کنید.",
context=[
"لغو اشتراک: از طریق پنل کاربری > اشتراک "
"> لغو اشتراک. بازگشت وجه تا ۳۰ روز."
],
),
Golden(
input="آیا API شما از GraphQL پشتیبانی میکنه؟",
expected_output="بله، API ما از REST و GraphQL "
"پشتیبانی میکند.",
context=[
"API ما از REST و GraphQL پشتیبانی میکند. "
"مستندات GraphQL در docs.example.com/graphql "
"موجود است."
],
),
]
return EvaluationDataset(goldens=goldens)
# test_eval_ci.py - تستهای ارزیابی برای CI/CD
import pytest
from deepeval import assert_test
from deepeval.test_case import LLMTestCase
from deepeval.metrics import (
AnswerRelevancyMetric,
FaithfulnessMetric,
)
# متریکها با آستانههای سختگیرانه برای CI
faithfulness_metric = FaithfulnessMetric(
threshold=0.85,
model="gpt-4o",
)
relevancy_metric = AnswerRelevancyMetric(
threshold=0.7,
model="gpt-4o",
)
def get_rag_response(query: str, context: list[str]) -> str:
"""دریافت پاسخ از pipeline RAG."""
# اینجا pipeline واقعیتون رو صدا بزنید
from my_app.rag import rag_pipeline
return rag_pipeline(query, context)
@pytest.mark.parametrize(
"golden", load_golden_dataset().goldens
)
def test_rag_quality(golden):
"""تست کیفیت RAG روی تمام نمونههای طلایی."""
actual_output = get_rag_response(
golden.input, golden.context
)
test_case = LLMTestCase(
input=golden.input,
actual_output=actual_output,
expected_output=golden.expected_output,
retrieval_context=golden.context,
)
assert_test(test_case, [
faithfulness_metric,
relevancy_metric,
])
و فایل GitHub Actions برای اجرای خودکار:
# .github/workflows/llm-eval.yml
name: LLM Evaluation
on:
pull_request:
paths:
- 'prompts/**'
- 'src/rag/**'
- 'config/models.yaml'
jobs:
evaluate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: pip install -r requirements-eval.txt
- name: Run LLM evaluations
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
deepeval test run tests/test_eval_ci.py \
--verbose \
--ignore-errors
- name: Check quality gates
if: failure()
run: |
echo "LLM evaluation failed!"
echo "Quality gates not met."
echo "Check the evaluation report for details."
exit 1
نکته مهم: توجه کنید که این workflow فقط وقتی اجرا میشه که فایلهای پرامپت، کد RAG یا تنظیمات مدل تغییر کرده باشن (بخش paths). این کار از اجرای بیمورد ارزیابیها و هدر رفتن هزینه API جلوگیری میکنه.
تست A/B نسخههای مدل
یه کاربرد دیگه ارزیابی خودکار، تست A/B نسخههای مختلف مدل یا پرامپته. مثلاً وقتی میخواید از GPT-4o به Claude 4 Sonnet مهاجرت کنید، یا وقتی پرامپت جدیدی نوشتید، میتونید هر دو نسخه رو روی همون دیتاست طلایی ارزیابی و مقایسه کنید. اگه نسخه جدید نمره بهتری گرفت، دیپلوی میشه — در غیر این صورت، PR رد میشه.
من شخصاً این روش رو خیلی دوست دارم چون تصمیمگیری رو از حوزه «حس و گمان» در میآره و مبتنی بر داده میکنه.
مشاهدهپذیری عاملهای هوش مصنوعی
مشاهدهپذیری سیستمهای عاملی (Agent Observability) چالشهای خاص خودش رو داره. اگه مقاله ما درباره سیستمهای چند-عاملی با LangGraph و CrewAI رو خونده باشید، میدونید که این سیستمها چندین مرحله تصمیمگیری، فراخوانی ابزار و حلقههای بازخورد دارن. ردیابی این سیستمها خیلی پیچیدهتر از یه فراخوانی ساده LLM هست.
چالشهای خاص عاملها
- Traceهای چندمرحلهای: یه عامل ممکنه ۱۰ بار LLM رو صدا بزنه، ۵ ابزار مختلف استفاده کنه، و ۳ بار مسیرش رو تغییر بده. ردیابی این زنجیره و فهمیدن اینکه کجا مشکل پیش اومده، به ابزارهای مناسب نیاز داره.
- ردیابی فراخوانی ابزارها: وقتی عامل یه ابزار رو صدا میزنه (مثلاً جستجو در دیتابیس یا فراخوانی API خارجی از طریق MCP Protocol)، باید بدونید چه ورودیای به ابزار رفته، چه خروجیای برگشته، و چقدر طول کشیده.
- تشخیص حلقههای بینهایت: یکی از رایجترین مشکلات عاملها، گیر کردن در حلقههای تکراریه — مثلاً عامل مکرراً همون ابزار رو با همون پارامتر صدا میزنه. بدون مانیتورینگ، این حلقهها هزینههای سنگینی ایجاد میکنن.
- ردیابی زنجیره استدلال: در الگوی ReAct (که در مقاله مهندسی پرامپت بررسی کردیم)، هر مرحله Thought-Action-Observation باید قابل مشاهده باشه تا بفهمید عامل چرا تصمیم خاصی گرفته.
ردیابی هزینه به ازای هر مرحله
صادقانه بگم، یکی از بزرگترین غافلگیریها برای تیمهایی که سیستمهای عاملی رو به تولید میبرن، هزینهها هست. یه عامل ساده که تو دمو عالی کار میکنه، ممکنه در تولید با سناریوهای پیچیدهتر کاربران، ۲۰ مرحله استدلال طی کنه و هزینه هر درخواست رو چند برابر کنه. ردیابی هزینه به ازای هر مرحله عامل، به شما کمک میکنه گلوگاهها رو پیدا و بهینهسازی کنید.
بیاید یه مثال از ردیابی عامل با Langfuse ببینیم:
from langfuse.decorators import observe, langfuse_context
from langfuse.openai import openai
@observe(as_type="generation")
def agent_think(state: dict, step: int) -> str:
"""مرحله استدلال عامل."""
response = openai.chat.completions.create(
model="gpt-4o",
messages=state["messages"],
tools=state["available_tools"],
temperature=0.1,
)
langfuse_context.update_current_observation(
metadata={
"step": step,
"agent_name": state.get("agent_name", "main"),
}
)
return response.choices[0].message
@observe()
def execute_tool(tool_name: str, tool_input: dict) -> str:
"""اجرای ابزار و ثبت نتیجه."""
langfuse_context.update_current_observation(
metadata={
"tool_name": tool_name,
"tool_input": tool_input,
}
)
# اجرای واقعی ابزار
result = tool_registry[tool_name](**tool_input)
langfuse_context.update_current_observation(
output=result
)
return result
@observe()
def run_agent(query: str, max_steps: int = 10) -> str:
"""اجرای عامل با ردیابی کامل."""
langfuse_context.update_current_trace(
tags=["agent", "production"],
metadata={"max_steps": max_steps},
)
state = initialize_agent_state(query)
for step in range(max_steps):
# مرحله استدلال
response = agent_think(state, step)
# آیا عامل تصمیم به استفاده از ابزار گرفته؟
if response.tool_calls:
for tool_call in response.tool_calls:
tool_result = execute_tool(
tool_call.function.name,
tool_call.function.arguments,
)
state["messages"].append({
"role": "tool",
"content": tool_result,
"tool_call_id": tool_call.id,
})
else:
# عامل به جواب نهایی رسیده
langfuse_context.update_current_trace(
metadata={
"total_steps": step + 1,
"completed": True,
}
)
return response.content
# حلقه به حداکثر رسیده - هشدار!
langfuse_context.update_current_trace(
metadata={
"total_steps": max_steps,
"completed": False,
"warning": "max_steps_reached",
},
tags=["agent", "production", "max_steps_warning"],
)
langfuse_context.score_current_trace(
name="loop_detection",
value=0,
comment="عامل به حداکثر مراحل رسید - احتمال حلقه",
)
return "متأسفم، نتوانستم به جواب برسم."
این کد هر مرحله از عامل رو به صورت جداگانه ردیابی میکنه. در داشبورد Langfuse میتونید trace درختی عامل رو ببینید: هر مرحله استدلال، هر فراخوانی ابزار، و هزینه و زمان هر کدوم. مهمتر از همه، وقتی عامل به حداکثر مراحل میرسه، یه هشدار ثبت میشه که میتونید روش آلارم بذارید.
بهترین شیوهها برای ارزیابی و مشاهدهپذیری در تولید
بعد از بررسی ابزارها و تکنیکها، بیاید بهترین شیوههایی رو مرور کنیم که تیمهای موفق در تولید رعایت میکنن. (اینا از تجربه عملی و بررسی تیمهای مختلفه، نه فقط تئوری.)
۱. با ارزیابی انسانی شروع کنید
قبل از هر چیز، حداقل ۳۰ نمونه رو به صورت دستی ارزیابی کنید. این baseline ارزیابی انسانی، مرجع شماست برای کالیبره کردن متریکهای خودکار. اگه متریک خودکار نمره ۰.۹ میده ولی ارزیابی انسانی نشون میده کیفیت متوسطه، متریکتون مشکل داره — نه سیستمتون.
برای ارزیابی انسانی:
- از چندین ارزیاب استفاده کنید (حداقل ۲-۳ نفر)
- معیارهای مشخص تعریف کنید (rubric)
- Inter-annotator agreement رو بسنجید
- نتایج رو به عنوان golden dataset ذخیره کنید
۲. متریکهای خودکار رو لایهای بچینید
بعد از داشتن baseline انسانی، متریکهای خودکار رو روش سوار کنید:
- لایه اول: متریکهای قطعی (deterministic) — طول خروجی، فرمت JSON، وجود کلمات ممنوع. سریع و ارزون.
- لایه دوم: متریکهای مبتنی بر embedding — BERTScore، شباهت معنایی. نسبتاً سریع.
- لایه سوم: متریکهای LLM-as-Judge — Faithfulness، Relevancy. دقیقتر ولی کندتر و گرونتر.
در تولید، لایه اول و دوم رو روی همه درخواستها اجرا کنید و لایه سوم رو نمونهگیری کنید (مثلاً ۱۰٪ درخواستها).
۳. هزینه، تأخیر و کیفیت رو همزمان مانیتور کنید
این سهتا مثلث آهنین سیستمهای LLM هستن. بهبود یکی معمولاً بقیه رو تحت تأثیر قرار میده:
- مدل بهتر → کیفیت بالاتر، ولی هزینه و latency بیشتر
- پرامپت کوتاهتر → هزینه و latency کمتر، ولی شاید کیفیت افت کنه
- context بیشتر → کیفیت بالاتر، ولی هزینه توکنها بالاتر
داشبوردتون باید هر سه رو نشون بده تا بتونید trade-offها رو آگاهانه مدیریت کنید.
۴. آلارم بذارید برای ناهنجاریها
آلارمهای اساسی که هر سیستم LLM تولیدی باید داشته باشه:
- هزینه روزانه بالاتر از ۲x میانگین: ممکنه حلقه بینهایت یا ترافیک غیرعادی باشه.
- Latency p95 بالاتر از ۵ ثانیه: تجربه کاربری افت میکنه.
- نرخ خطای API بالاتر از ۵٪: ممکنه مشکل از سمت ارائهدهنده LLM باشه.
- نمره Faithfulness زیر ۰.۷: احتمالاً داره هذیان تولید میشه.
- افزایش ناگهانی در میانگین توکنهای ورودی یا خروجی: ممکنه تغییری در پرامپت یا دادهها ایجاد شده باشه.
۵. پرامپتها رو نسخهبندی (Version) کنید
این خیلی مهمه و خیلیها فراموش میکنن: هر تغییری در پرامپت باید ردیابی بشه. مثل نسخهبندی کد، پرامپتها هم باید version داشته باشن. وقتی کیفیت افت میکنه، باید بتونید ببینید آخرین تغییر پرامپت کِی بوده و چی عوض شده. ابزارهایی مثل Langfuse قابلیت مدیریت پرامپت رو دارن — ازشون استفاده کنید.
۶. همهچیز رو لاگ کنید، ولی در تولید نمونهگیری کنید
در محیط توسعه و staging، همه چیز رو لاگ کنید — هر پرامپت، هر context، هر خروجی. ولی در تولید، لاگ کردن همه درخواستها ممکنه هم از نظر هزینه ذخیرهسازی و هم از نظر حریم خصوصی مشکلساز باشه. نمونهگیری هوشمند (مثلاً ۱۰٪ درخواستهای عادی + ۱۰۰٪ درخواستهایی که خطا دارن یا نمره پایین میگیرن) بهترین رویکرده.
۷. Golden Dataset رو مرتب بهروز کنید
دیتاست طلاییتون نباید ثابت بمونه. هر هفته یا هر ماه، نمونههای جدید از سؤالات واقعی کاربران رو بهش اضافه کنید. مخصوصاً نمونههایی که سیستم روشون fail کرده — اینا ارزشمندترین test caseها هستن.
۸. ارزیابی end-to-end رو فراموش نکنید
ارزیابی فقط LLM کافی نیست. باید کل pipeline رو ارزیابی کنید — از بازیابی اسناد تا تولید پاسخ نهایی. ممکنه LLM عالی کار کنه ولی بازیابی اسناد اشتباه باشه. این رویکرد end-to-end مخصوصاً برای سیستمهای Agentic RAG (که در مقاله مربوطه بررسی کردیم) حیاتیه چون چندین مؤلفه به هم وابسته هستن.
نتیجهگیری: بدون ارزیابی و مشاهدهپذیری، کورکورانه پرواز میکنید
خب، بیاید خلاصه کنیم. در این مقاله بررسی کردیم که:
- ارزیابی، مشاهدهپذیری و مانیتورینگ سه لایه مکمل هستن که هر کدوم نقش متفاوتی دارن و به هر سه نیاز دارید.
- متریکهای مدرن مثل Faithfulness، Answer Relevancy و Context Precision خیلی بهتر از متریکهای سنتی NLP برای سیستمهای RAG کار میکنن.
- LLM-as-a-Judge یه ابزار قدرتمند و مقیاسپذیره ولی جایگزین ارزیابی انسانی نمیشه — مکملشه.
- ابزارهایی مثل Langfuse، LangSmith و Arize Phoenix دید عمیقی به رفتار سیستم در تولید میدن.
- OpenTelemetry داره به استاندارد مشاهدهپذیری LLM تبدیل میشه و از vendor lock-in جلوگیری میکنه.
- ارزیابی خودکار در CI/CD تضمین میکنه که هر تغییری قبل از دیپلوی از دروازه کیفیت عبور کرده.
- مشاهدهپذیری عاملها چالشهای خاص خودش رو داره — مخصوصاً ردیابی حلقهها و هزینهها.
صادقانه بگم، تفاوت بین یه دموی جذاب و یه سیستم تولیدی قابل اعتماد، دقیقاً در همین لایههاست. ساختن سیستم هیجانانگیزه — ولی نگهداری و مانیتورینگش توی تولید، کار واقعیه. تیمهایی که این موضوع رو جدی میگیرن، سیستمهایی دارن که ماهها و سالها در تولید قابل اعتماد باقی میمونن. تیمهایی که نمیگیرن، مدام در حال آتشنشانی هستن.
اگه از مقالات قبلی ما درباره Agentic RAG، پروتکل MCP، سیستمهای چند-عاملی و مهندسی پرامپت استفاده کردید و سیستمی ساختید، حالا وقتشه که لایه ارزیابی و مشاهدهپذیری رو بهش اضافه کنید. با یه دیتاست طلایی ۳۰ نمونهای شروع کنید، Langfuse رو راه بندازید، چند تا متریک اساسی تعریف کنید، و از همین الان لاگبرداری کنید. بعد قدمبهقدم پیچیدگی رو اضافه کنید.
یادتون باشه: بدون ارزیابی و مشاهدهپذیری، دارید کورکورانه پرواز میکنید. و توی دنیای سیستمهای هوش مصنوعی تولیدی، پرواز کورکورانه یعنی فاجعهای که فقط منتظر اتفاق افتادنه.