Observability در هوش مصنوعی چیست؟ راهنمای مانیتورینگ مدل‌ها، Agentها و APIهای هوش مصنوعی

AI Observability به شما کمک می‌کند عملکرد، هزینه، Latency، خطا و کیفیت مدل‌ها و Agentهای هوش مصنوعی را بررسی کنید. در این راهنما، طراحی Logs، Metrics، Traces، Dashboard و Alerting را با نمونه‌کد و API درواره می‌آموزید.

Share
Observability در هوش مصنوعی چیست؟ راهنمای مانیتورینگ مدل‌ها، Agentها و APIهای هوش مصنوعی

مقدمه

در یک نرم‌افزار معمولی، موفقیت درخواست اغلب با معیارهایی مانند وضعیت HTTP، زمان پاسخ و نرخ خطا سنجیده می‌شود. اگر سرور کد 200 برگرداند و پاسخ در زمان مناسبی دریافت شود، احتمالاً عملیات موفق بوده است.

اما در یک اپلیکیشن هوش مصنوعی، دریافت کد 200 لزوماً به معنای موفقیت نیست.

ممکن است مدل:

  • پاسخ نامرتبط تولید کند؛
  • اطلاعات نادرست بسازد؛
  • از دستور اصلی پیروی نکند؛
  • JSON نامعتبر برگرداند؛
  • ابزار اشتباهی را انتخاب کند؛
  • سند نامرتبطی را در سیستم RAG استفاده کند؛
  • پاسخ را به‌دلیل محدودیت Token نیمه‌کاره رها کند؛
  • هزینه‌ای بسیار بیشتر از مقدار مورد انتظار ایجاد کند؛
  • در مقایسه با نسخه قبلی Prompt کیفیت پایین‌تری داشته باشد؛
  • یا با وجود پاسخ فنی موفق، مسئله کاربر را حل نکند.

به همین دلیل، مانیتورینگ سنتی برای سیستم‌های مبتنی بر مدل‌های زبانی کافی نیست. باید علاوه بر سلامت زیرساخت، رفتار مدل، کیفیت پاسخ، مسیر Agent، مصرف Token، هزینه، Retrieval، Tool Calling و تجربه واقعی کاربر را نیز مشاهده و ارزیابی کنیم.

AI Observability برای پاسخ‌دادن به همین نیاز به‌وجود آمده است.

فهرست مطالب

  • AI Observability چیست؟
  • تفاوت Monitoring و Observability
  • چرا مانیتورینگ سنتی برای AI کافی نیست؟
  • سه لایه Observability در سیستم‌های AI
  • Logs، Metrics و Traces
  • طراحی Trace برای درخواست هوش مصنوعی
  • چه داده‌هایی باید ثبت شوند؟
  • مانیتورینگ Latency و Streaming
  • مانیتورینگ Token و هزینه
  • مانیتورینگ خطا، Retry و Fallback
  • Observability در RAG
  • Observability در Tool Calling
  • مانیتورینگ AI Agentها
  • اندازه‌گیری کیفیت پاسخ
  • Hallucination و Groundedness
  • Online Evaluation و Offline Evaluation
  • طراحی Dashboard
  • Alerting و Alert Fatigue
  • حریم خصوصی و امنیت Logها
  • نمونه‌کد Python با API درواره
  • نمونه‌کد TypeScript
  • OpenTelemetry در اپلیکیشن‌های AI
  • SLI، SLO و SLA
  • چک‌لیست Production
  • پرسش‌های متداول
  • جمع‌بندی

AI Observability چیست؟

AI Observability یا مشاهده‌پذیری هوش مصنوعی مجموعه‌ای از روش‌ها، داده‌ها و ابزارها برای درک رفتار داخلی یک سیستم مبتنی بر مدل هوش مصنوعی از طریق خروجی‌های قابل‌اندازه‌گیری آن است.

این خروجی‌ها می‌توانند شامل موارد زیر باشند:

  • Logs
  • Metrics
  • Traces
  • Prompt و نسخه آن
  • مدل و ارائه‌دهنده
  • Input Tokens و Output Tokens
  • Cached Tokens
  • Latency و Time to First Token
  • Tool Calls
  • Retrieval Results
  • مسیر اجرای Agent
  • هزینه
  • خطاها
  • امتناع مدل از پاسخ‌گویی
  • نتایج ارزیابی کیفیت
  • بازخورد کاربر

هدف AI Observability فقط این نیست که بدانیم «سیستم روشن است یا خاموش». باید بتوانیم به سؤال‌های عمیق‌تری پاسخ دهیم:

  • چرا این پاسخ اشتباه بود؟
  • کدام Prompt این پاسخ را تولید کرد؟
  • کدام مدل و ارائه‌دهنده استفاده شد؟
  • چه اسنادی از RAG بازیابی شدند؟
  • Agent چرا این ابزار را انتخاب کرد؟
  • کدام مرحله بیشترین زمان را مصرف کرد؟
  • چرا هزینه امروز دو برابر شده است؟
  • چند درصد پاسخ‌های JSON نامعتبر بوده‌اند؟
  • آیا Fallback کیفیت پاسخ را کاهش داده است؟
  • آیا مدل جدید از نسخه قبلی بهتر است؟
  • کدام کاربران بیشترین خطای Rate Limit را دریافت می‌کنند؟
  • آیا پاسخ‌های فارسی یک مدل کیفیت پایین‌تری دارند؟
  • آیا تغییر Chunking باعث بهبود Retrieval شده است؟

OpenTelemetry، Observability را توانایی درک وضعیت داخلی یک سیستم از طریق بررسی خروجی‌های آن تعریف می‌کند و داده‌های Telemetry را معمولاً در قالب Logs، Metrics و Traces سازمان می‌دهد. راهنمای Observability در OpenTelemetry

تفاوت Monitoring و Observability

Monitoring و Observability به یکدیگر مرتبط‌اند، اما دقیقاً یکسان نیستند.

Monitoring

Monitoring معمولاً بر مجموعه‌ای از سؤال‌های از قبل شناخته‌شده تمرکز دارد:

  • نرخ خطا چقدر است؟
  • CPU چقدر مصرف شده؟
  • چند درخواست در دقیقه داریم؟
  • Latency از آستانه عبور کرده است؟
  • Queue چند Job دارد؟
  • سرویس در دسترس است؟

Observability

Observability باید امکان بررسی سؤال‌هایی را فراهم کند که شاید هنگام طراحی سیستم پیش‌بینی نشده بودند:

  • چرا فقط درخواست‌های فارسی مدل خاصی کند شده‌اند؟
  • چرا Agent بعد از Fallback ابزار متفاوتی انتخاب کرده است؟
  • چرا هزینه افزایش یافته اما تعداد درخواست ثابت مانده است؟
  • کدام سند باعث پاسخ نادرست شده؟
  • چرا پاسخ‌های تولیدشده با Prompt نسخه جدید طولانی‌ترند؟
  • کدام مسیر Agent بیشترین Retry را دارد؟
ویژگیMonitoringObservability
تمرکزوضعیت‌های شناخته‌شدهتحلیل رفتار داخلی
سؤال‌هااز قبل تعریف‌شدهشناخته‌شده و ناشناخته
داده‌هاMetrics و AlertsLogs، Metrics، Traces و Context
خروجیتشخیص وجود مشکلبررسی علت مشکل
کاربرد در AIسلامت سرویسسلامت، هزینه و کیفیت سیستم

Monitoring بخشی از Observability است، اما Observability دامنه گسترده‌تری دارد.

چرا مانیتورینگ سنتی برای AI کافی نیست؟

در نرم‌افزار قطعی، یک ورودی مشخص معمولاً خروجی مشخصی تولید می‌کند. اما مدل‌های هوش مصنوعی احتمالاتی‌اند و رفتار آن‌ها می‌تواند با عوامل مختلف تغییر کند:

  • مدل
  • نسخه مدل
  • System Prompt
  • تاریخچه مکالمه
  • Temperature
  • Context
  • اسناد RAG
  • خروجی ابزار
  • ترتیب پیام‌ها
  • Provider
  • محدودیت Token
  • سیاست‌های ایمنی

برای مثال، درخواست زیر ممکن است از نظر زیرساخت کاملاً موفق باشد:

{
  "status": 200,
  "latency_ms": 2150
}

اما پاسخ مدل ممکن است اطلاعات نادرستی داشته باشد:

مهلت بازپرداخت شما ۳۰ روز است.

درحالی‌که سند رسمی شرکت مهلت را هفت روز اعلام کرده است.

از دید Monitoring سنتی:

  • API موفق بوده است.
  • Latency مناسب است.
  • خطای سرور وجود ندارد.

از دید AI Observability:

  • پاسخ Grounded نیست.
  • مدل از سند صحیح استفاده نکرده است.
  • احتمالاً Retrieval یا Prompt مشکل دارد.
  • پاسخ می‌تواند به تجربه مشتری و کسب‌وکار آسیب بزند.

بنابراین یک سیستم هوش مصنوعی حداقل باید در سه سطح مشاهده شود:

  1. سلامت زیرساخت
  2. عملکرد مدل و Workflow
  3. کیفیت نتیجه کسب‌وکار

سه لایه AI Observability

لایه زیرساخت

این لایه مشابه Observability نرم‌افزارهای معمولی است:

  • CPU
  • Memory
  • Network
  • Connection Pool
  • Queue Depth
  • Database Latency
  • Cache Hit Rate
  • HTTP Error Rate
  • Availability

لایه مدل و Workflow

این لایه رفتار AI را بررسی می‌کند:

  • مدل و ارائه‌دهنده
  • Prompt Version
  • Context Size
  • Token Usage
  • Latency
  • Time to First Token
  • Tool Calls
  • Retry
  • Fallback
  • Finish Reason
  • Validation Result
  • Retrieval Results
  • Agent Steps

لایه کیفیت و کسب‌وکار

این لایه بررسی می‌کند آیا سیستم واقعاً هدف خود را محقق کرده است:

  • Task Completion Rate
  • Answer Relevance
  • Groundedness
  • Accuracy
  • User Satisfaction
  • Ticket Deflection
  • Escalation Rate
  • Conversion Rate
  • Cost per Successful Task
  • Human Correction Rate

فقط زمانی تصویر کاملی از سیستم داریم که این سه لایه به یکدیگر متصل باشند.

سه سیگنال اصلی: Logs، Metrics و Traces

Logs

Log یک رکورد از رویداد مشخص است:

{
  "event": "ai_request_completed",
  "request_id": "req_2048",
  "model": "MODEL_ID",
  "input_tokens": 1350,
  "output_tokens": 420,
  "latency_ms": 2870,
  "status": "success"
}

Logs برای بررسی جزئیات یک رخداد خاص مناسب‌اند.

Metrics

Metric یک مقدار عددی است که در طول زمان اندازه‌گیری می‌شود:

ai_requests_total
ai_request_duration_seconds
ai_input_tokens_total
ai_output_tokens_total
ai_request_cost
ai_validation_failures_total

Metrics برای Dashboard، روندها و Alerting مناسب‌اند.

Traces

Trace مسیر کامل یک درخواست را میان اجزای سیستم نشان می‌دهد:

POST /v1/chat
  ├── authenticate
  ├── check_wallet
  ├── retrieve_documents
  ├── rerank_documents
  ├── build_context
  ├── call_model
  ├── validate_output
  └── record_usage

هر بخش Trace یک Span نامیده می‌شود. Span می‌تواند زمان شروع، پایان، وضعیت، Attribute و Eventهای خود را داشته باشد.

OpenTelemetry برای عملیات Generative AI نیز Semantic Conventionهایی در زمینه مدل، Tokenها، ابزارها، Retrieval و زمان اولین Chunk توسعه داده است تا ثبت Telemetry میان سیستم‌ها هماهنگ‌تر شود. Semantic Conventions در OpenTelemetry

Event چیست؟

Event رخدادی است که درون یک Span اتفاق می‌افتد:

Span: provider.request
Events:
  - request_sent
  - first_token_received
  - retry_started
  - response_completed

Event برای ثبت اتفاق‌های مهم بدون ایجاد Span جداگانه مفید است.

طراحی Trace برای درخواست هوش مصنوعی

یک Trace مناسب باید مسیر واقعی درخواست را نشان دهد، نه فقط فراخوانی مدل را.

ai.request
  ├── auth.validate
  ├── rate_limit.check
  ├── billing.precheck
  ├── memory.load
  ├── rag.query_rewrite
  ├── rag.vector_search
  ├── rag.rerank
  ├── prompt.build
  ├── router.select_model
  ├── provider.chat_completion
  ├── output.validate
  ├── safety.check
  ├── billing.record_usage
  └── response.send

این ساختار به شما اجازه می‌دهد بفهمید:

  • آیا کندی از مدل بوده یا RAG؟
  • آیا Rate Limit پیش از تماس با Provider عمل کرده؟
  • آیا مدل اصلی یا Fallback استفاده شده؟
  • آیا Validation باعث Retry شده؟
  • آیا ثبت مصرف بعد از پاسخ شکست خورده؟
  • آیا Context Builder بیش‌ازحد زمان مصرف کرده؟

Attributeهای Trace

نمونه Attributeهای مناسب:

{
  "request.id": "req_2048",
  "user.id_hash": "usr_91f...",
  "organization.id": "org_52",
  "gen_ai.request.model": "MODEL_ID",
  "gen_ai.provider.name": "provider_a",
  "gen_ai.request.stream": true,
  "gen_ai.request.temperature": 0.2,
  "gen_ai.request.max_tokens": 1200,
  "prompt.version": "support-v7",
  "context.version": "ctx-v3",
  "route.fallback_used": false
}

از مقدار Hash‌شده یا شناسه داخلی به‌جای اطلاعات شخصی مستقیم استفاده کنید.

چه داده‌هایی باید برای هر درخواست ثبت شوند؟

هویت و ارتباط درخواست

  • Internal Request ID
  • Trace ID
  • Provider Request ID
  • Session ID
  • Conversation ID
  • Job ID
  • شناسه Hash‌شده کاربر
  • شناسه سازمان
  • Endpoint

اطلاعات مدل

  • مدل درخواست‌شده
  • مدل واقعی پاسخ‌دهنده
  • ارائه‌دهنده
  • Region
  • قابلیت درخواست‌شده
  • حالت Streaming
  • Temperature
  • max_tokens
  • Response Format

Prompt و Context

  • Prompt Name
  • Prompt Version
  • Context Builder Version
  • تعداد پیام‌ها
  • Token تخمینی Context
  • شناسه اسناد بازیابی‌شده
  • شناسه حافظه‌های استفاده‌شده
  • فهرست ابزارهای ارائه‌شده
  • Hash محتوای Prompt

در محیط‌های حساس بهتر است متن کامل Prompt به‌طور پیش‌فرض ثبت نشود.

نتیجه

  • Response ID
  • Finish Reason
  • Refusal Status
  • Validation Status
  • Tool Calls
  • Input Tokens
  • Output Tokens
  • Cached Tokens
  • Latency
  • Time to First Token
  • Retry Count
  • Fallback Route
  • هزینه
  • HTTP Status
  • Error Code

کیفیت

  • User Feedback
  • Relevance Score
  • Groundedness Score
  • Safety Result
  • Human Review
  • Task Completion
  • Correction Required

Request ID و Trace ID

این دو شناسه نقش متفاوتی دارند.

Request ID

برای شناسایی درخواست از دید API شما استفاده می‌شود:

req_2048

Trace ID

مسیر درخواست را در چند سرویس و عملیات مرتبط می‌کند:

4bf92f3577b34da6a3ce929d0e0e4736

یک Request می‌تواند Trace واحدی داشته باشد که چندین Span داخل آن قرار گرفته‌اند.

اگر Provider نیز Request ID برمی‌گرداند، آن را جداگانه ثبت کنید:

{
  "internal_request_id": "req_2048",
  "trace_id": "4bf92f...",
  "provider_request_id": "upstream_8821"
}

این ارتباط برای بررسی مشکلات Billing، Timeout و پشتیبانی بسیار مهم است.

مانیتورینگ Latency

یک مقدار کلی برای Latency کافی نیست. بهتر است زمان هر مرحله جداگانه ثبت شود.

End-to-End Latency

از ورود درخواست کاربر تا تکمیل پاسخ.

Provider Latency

زمان فراخوانی مدل.

Retrieval Latency

زمان جست‌وجو و بازیابی اسناد.

Reranking Latency

زمان رتبه‌بندی دوباره نتایج.

Tool Latency

زمان اجرای ابزار.

Validation Latency

زمان Parse و اعتبارسنجی خروجی.

Queue Wait Time

مدت انتظار Job پیش از شروع پردازش.

Time to First Token

در Streaming، مدت‌زمان از ارسال درخواست تا دریافت اولین Token یا Chunk.

Inter-Token Latency

فاصله زمانی میان Chunkهای پاسخ.

Time to Last Token

زمان تکمیل کل خروجی.

Streaming معمولاً زمان کل تولید را کاهش نمی‌دهد، اما زمان دریافت اولین بخش پاسخ را کمتر و تجربه کاربری را بهتر می‌کند. راهنمای Production API و Streaming

چرا p50، p95 و p99 مهم‌اند؟

میانگین می‌تواند مشکلات واقعی را پنهان کند.

فرض کنید:

  • ۹۰ درخواست در دو ثانیه پاسخ گرفته‌اند.
  • ۱۰ درخواست در ۳۰ ثانیه پاسخ گرفته‌اند.

میانگین شاید قابل‌قبول به نظر برسد، اما ۱۰ درصد کاربران تجربه بسیار ضعیفی داشته‌اند.

p50

نیمی از درخواست‌ها سریع‌تر از این مقدار تکمیل شده‌اند.

p95

۹۵ درصد درخواست‌ها سریع‌تر از این مقدار تکمیل شده‌اند.

p99

۹۹ درصد درخواست‌ها سریع‌تر از این مقدار تکمیل شده‌اند.

برای بررسی تجربه کاربران کندتر، p95 و p99 اهمیت زیادی دارند.

مانیتورینگ Streaming

در Streaming فقط ثبت زمان پاسخ نهایی کافی نیست.

Metricهای مهم:

  • Time to First Chunk
  • Stream Duration
  • Number of Chunks
  • Stream Completion Rate
  • Client Disconnect Rate
  • Provider Disconnect Rate
  • Incomplete Stream Rate
  • Tokens per Second
  • Final Usage Received Rate

در Chat Completions سازگار با OpenAI می‌توان در مدل‌های پشتیبانی‌شده با گزینه زیر دریافت Usage نهایی را درخواست کرد:

{
  "stream": true,
  "stream_options": {
    "include_usage": true
  }
}

Usage معمولاً در Chunk نهایی قرار می‌گیرد. اگر Stream قطع یا لغو شود، ممکن است Chunk نهایی Usage دریافت نشود. مرجع Streaming Chat Completions

بنابراین سیستم Billing نباید بدون برنامه جایگزین، فقط به Chunk نهایی وابسته باشد.

وضعیت‌های پیشنهادی Streaming

started
first_chunk_received
streaming
completed
client_disconnected
provider_disconnected
timed_out
cancelled
incomplete

مانیتورینگ Token

Tokenها مستقیماً روی هزینه، Latency و Rate Limit اثر می‌گذارند.

Metricهای ضروری:

  • Input Tokens
  • Output Tokens
  • Total Tokens
  • Cached Input Tokens
  • Cache Creation Tokens
  • Reasoning Tokens، در صورت گزارش
  • Tokens per Request
  • Tokens per User
  • Tokens per Organization
  • Tokens per Model
  • Tokens per Feature
  • Tokens per Successful Task

نسبت ورودی به خروجی

Input/Output Ratio =
Input Tokens / Output Tokens

اگر این نسبت بسیار بالا باشد، ممکن است Context بیش‌ازحد بزرگ باشد.

Context Utilization

اگر دائماً Context بزرگی ارسال می‌شود اما فقط بخش کوچکی از آن برای پاسخ لازم است، RAG، Memory یا Context Builder نیاز به بهینه‌سازی دارد.

Finish Reason

این فیلد باید مانیتور شود:

stop
length
tool_calls
content_filter

اگر نرخ length افزایش یابد، ممکن است:

  • max_tokens بسیار کم باشد؛
  • Prompt پاسخ طولانی درخواست کند؛
  • مدل بیش‌ازحد توضیح دهد؛
  • Schema خروجی با ظرفیت سازگار نباشد.

مانیتورینگ هزینه

صرفاً ثبت تعداد Token کافی نیست؛ باید هزینه واقعی نیز محاسبه و تحلیل شود.

هزینه هر درخواست

Request Cost =
Input Token Cost
+ Output Token Cost
+ Cache Cost
+ Tool Cost
+ Retrieval Cost

هزینه هر کاربر

User Cost =
Sum of Request Costs for User

هزینه هر قابلیت

برای مثال:

Chat: 35%
Document Analysis: 25%
Image Generation: 20%
Agent Workflows: 15%
Embeddings: 5%

هزینه هر وظیفه موفق

این معیار از هزینه هر درخواست مفیدتر است:

Cost per Successful Task =
Total Cost / Successful Tasks

ممکن است مدل ارزان‌تر Retry و خطای بیشتری داشته باشد و درنهایت هزینه هر وظیفه موفق آن از مدل قوی‌تر بیشتر شود.

Budget Variance

Budget Variance =
Actual Cost - Estimated Cost

اختلاف زیاد میان هزینه تخمینی و واقعی می‌تواند نشان‌دهنده مشکلات زیر باشد:

  • تخمین نادرست Token
  • خروجی بسیار طولانی
  • Retry پنهان SDK
  • Fallback پرهزینه
  • تغییر قیمت مدل
  • خطای Billing
  • Tool Callهای تکراری

هشدار هزینه

نمونه Alertها:

  • افزایش ۵۰ درصدی هزینه ساعتی
  • هزینه یک درخواست بیشتر از سقف
  • مصرف روزانه کاربر خارج از الگو
  • افزایش ناگهانی Output Tokens
  • کاهش Cache Hit Rate
  • افزایش Retry و Fallback

مانیتورینگ خطا

خطاها را فقط با status=failed ثبت نکنید. باید طبقه‌بندی شوند.

خطاهای Client

invalid_request
authentication_failed
permission_denied
context_too_large
model_not_found
unsupported_parameter

خطاهای محدودیت و Billing

rate_limit_exceeded
token_limit_exceeded
insufficient_balance
budget_exceeded
concurrency_limit_exceeded

خطاهای Provider

provider_timeout
provider_connection_error
provider_rate_limit
provider_5xx
provider_invalid_response

خطاهای خروجی

empty_response
incomplete_response
json_parse_failed
schema_validation_failed
tool_arguments_invalid
content_refused

خطاهای Workflow

tool_failed
agent_max_steps_reached
fallback_exhausted
queue_expired
job_cancelled

طبقه‌بندی ثابت خطاها امکان مقایسه مدل‌ها و ارائه‌دهندگان را فراهم می‌کند.

مانیتورینگ Retry

برای Retry این موارد را ثبت کنید:

  • دلیل Retry
  • شماره تلاش
  • فاصله Backoff
  • مسیر Retry
  • نتیجه نهایی
  • Token و هزینه هر تلاش
  • Retry داخلی یا اپلیکیشنی
  • Deadline باقی‌مانده

Metricهای مهم:

retry_rate
average_attempts_per_request
retry_success_rate
cost_added_by_retries
latency_added_by_retries

افزایش Retry Rate ممکن است اولین نشانه اختلال Provider باشد، حتی اگر درخواست‌ها درنهایت موفق شوند.

مانیتورینگ Fallback

برای هر Fallback ثبت کنید:

{
  "fallback_used": true,
  "primary_route": "provider_a/model_x",
  "fallback_route": "provider_b/model_y",
  "fallback_reason": "provider_timeout",
  "primary_attempt_cost": 0.012,
  "fallback_cost": 0.018
}

Metricهای مهم:

  • Fallback Rate
  • Fallback Success Rate
  • Fallback Latency
  • Fallback Cost
  • Quality after Fallback
  • Capability Mismatch Rate

اگر فقط موفقیت فنی Fallback را بسنجیم، ممکن است کاهش کیفیت نادیده بماند. باید کیفیت پاسخ مسیر اصلی و جایگزین نیز مقایسه شود.

Observability در سیستم‌های RAG

در RAG پاسخ نهایی فقط محصول مدل نیست. کیفیت به چند مرحله وابسته است:

Query
→ Query Rewriting
→ Retrieval
→ Filtering
→ Reranking
→ Context Assembly
→ Generation

اگر پاسخ اشتباه باشد، باید بفهمیم مشکل در کدام مرحله بوده است.

داده‌های Retrieval

برای هر جست‌وجو ثبت کنید:

  • Query اصلی
  • Query بازنویسی‌شده
  • Embedding Model
  • Index Version
  • Chunking Version
  • Filterها
  • top_k
  • شناسه اسناد
  • Retrieval Score
  • Reranking Score
  • رتبه نهایی
  • تعداد Chunk
  • Tokenهای Context
  • Latency هر مرحله

نمونه:

{
  "retrieval": {
    "query_hash": "q_91f...",
    "index_version": "kb-v12",
    "chunking_version": "chunk-v4",
    "top_k": 8,
    "returned_documents": [
      {
        "document_id": "doc_14",
        "chunk_id": "chunk_8",
        "retrieval_score": 0.83,
        "rerank_score": 0.91,
        "final_rank": 1
      }
    ],
    "latency_ms": 185
  }
}

Retrieval Precision

چه تعداد از Chunkهای بازیابی‌شده واقعاً مرتبط بوده‌اند؟

Retrieval Precision =
Relevant Retrieved Chunks / All Retrieved Chunks

Retrieval Recall

آیا اطلاعات لازم در میان Chunkهای بازیابی‌شده حضور داشته است؟

Retrieval Recall =
Retrieved Relevant Chunks / All Relevant Chunks

برای محاسبه دقیق Recall به Dataset دارای Ground Truth نیاز دارید.

Context Relevance

آیا Context واردشده به مدل برای سؤال کاربر مرتبط است؟

Groundedness

آیا پاسخ مدل توسط اسناد بازیابی‌شده پشتیبانی می‌شود؟

Citation Correctness

اگر پاسخ منبع دارد:

  • آیا Citation واقعاً ادعا را پشتیبانی می‌کند؟
  • آیا شناسه سند درست است؟
  • آیا مدل به سندی اشاره کرده که در Context حضور داشته است؟
  • آیا متن منبع قدیمی یا منقضی است؟

علت‌های رایج شکست RAG

  • Query Rewriting اشتباه
  • Chunking نامناسب
  • Metadata ناقص
  • فیلتر دسترسی اشتباه
  • top_k بسیار کم یا زیاد
  • Reranker ضعیف
  • سند قدیمی
  • Context بیش‌ازحد بزرگ
  • Prompt نامناسب
  • مدل ناتوان در استفاده از Context

Trace باید تشخیص این علت‌ها را ممکن کند.

Observability در Tool Calling

در Tool Calling باید هر مرحله ثبت شود:

  • ابزارهای در دسترس
  • ابزار انتخاب‌شده
  • دلیل یا Context انتخاب
  • Tool Call ID
  • آرگومان‌ها
  • نتیجه Validation
  • مجوز کاربر
  • زمان اجرا
  • وضعیت نتیجه
  • تعداد Retry
  • Idempotency Key
  • خروجی خلاصه‌شده
  • خطای ابزار

نمونه:

{
  "tool_call": {
    "id": "call_2048",
    "name": "get_transaction_status",
    "arguments_schema_valid": true,
    "authorization_passed": true,
    "latency_ms": 340,
    "status": "success",
    "result_size_bytes": 420
  }
}

Tool Selection Accuracy

آیا مدل ابزار درست را انتخاب کرده است؟

Tool Argument Accuracy

آیا آرگومان‌ها صحیح و کامل بوده‌اند؟

Tool Success Rate

چه درصدی از Tool Callها با موفقیت اجرا شده‌اند؟

Unnecessary Tool Call Rate

چه تعداد Tool Call بدون نیاز انجام شده‌اند؟

این معیار از نظر هزینه و ایمنی مهم است.

Tool Loop Rate

Agent چند بار ابزار یکسان را بدون پیشرفت تکرار کرده است؟

search → search → search → search

افزایش این Metric می‌تواند نشانه Prompt نامناسب، ابزار مبهم یا نبود معیار پایان باشد.

مانیتورینگ AI Agentها

برای Agent فقط ورودی و خروجی نهایی کافی نیست. باید Trajectory یا مسیر اجرای آن نیز مشاهده شود.

Trajectory می‌تواند شامل موارد زیر باشد:

1. Goal received
2. Plan created
3. Search tool called
4. Document retrieved
5. Database tool called
6. Permission denied
7. Alternative action selected
8. Final response generated

Metricهای مهم Agent

  • Task Completion Rate
  • Average Steps per Task
  • Maximum Steps Reached Rate
  • Tool Calls per Task
  • Tool Failure Rate
  • Repeated Action Rate
  • Human Escalation Rate
  • Agent Cancellation Rate
  • Cost per Task
  • Latency per Task
  • Successful Recovery Rate
  • Unsafe Action Attempt Rate

طول مسیر همیشه معیار کیفیت نیست

Agent با مراحل کمتر ممکن است کارآمدتر باشد، اما ممکن است بررسی ضروری را انجام نداده باشد. Agent با مراحل بیشتر نیز ممکن است دقیق‌تر یا گرفتار Loop باشد.

بنابراین باید مسیر واقعی با مسیر مورد انتظار مقایسه شود. ارزیابی Trajectory بررسی می‌کند چه مراحل ضروری انجام شده و ترتیب یا نتیجه آن‌ها چگونه بوده است. راهنمای ارزیابی Agent در LangSmith

پایان Agent

دلیل پایان را ثبت کنید:

completed
user_cancelled
max_steps_reached
max_cost_reached
timeout
tool_failure
permission_denied
safety_blocked
fallback_exhausted

اندازه‌گیری کیفیت پاسخ

کیفیت پاسخ یک مقدار واحد نیست. باید براساس کاربرد به چند معیار تقسیم شود.

Relevance

آیا پاسخ به سؤال کاربر مرتبط است؟

Correctness

آیا پاسخ از نظر واقعیت یا پاسخ مرجع صحیح است؟

Groundedness

آیا ادعاها توسط Context یا اسناد تأیید می‌شوند؟

Completeness

آیا تمام بخش‌های مهم سؤال پاسخ داده شده‌اند؟

Instruction Following

آیا مدل قالب، زبان و محدودیت‌های Prompt را رعایت کرده است؟

Format Validity

آیا JSON یا خروجی ساختاریافته معتبر است؟

Safety

آیا پاسخ با سیاست‌های امنیتی و محتوایی سازگار است؟

Tone

آیا لحن برای کاربرد موردنظر مناسب است؟

Task Success

آیا هدف واقعی کاربر تکمیل شده است؟

ممکن است پاسخ از نظر نگارشی عالی باشد، اما مسئله کاربر را حل نکند. به همین دلیل Task Success معمولاً از معیارهای زبانی عمومی مهم‌تر است.

Hallucination چگونه مانیتور می‌شود؟

تشخیص کامل Hallucination به‌صورت خودکار دشوار است، اما می‌توان سیگنال‌هایی ایجاد کرد.

مقایسه پاسخ با Context

ادعاهای پاسخ استخراج و با اسناد ارائه‌شده مقایسه شوند.

الزام Citation

مدل برای هر ادعای مهم منبع مشخص کند.

بررسی Citation

صرف وجود Citation کافی نیست؛ باید مشخص شود منبع واقعاً ادعا را پشتیبانی می‌کند.

قوانین قطعی

مقادیر حساس با دیتابیس یا کد بررسی شوند:

if response_amount != database_amount:
    flag_for_review()
Darvareh AI Observability

LLM-as-a-Judge

مدل دیگری پاسخ را ارزیابی می‌کند. این روش مقیاس‌پذیر است، اما کامل و بی‌طرف نیست.

بازبینی انسانی

نمونه‌ای از پاسخ‌ها توسط انسان بررسی شوند، به‌خصوص:

  • حوزه‌های حساس
  • پاسخ‌های با Confidence پایین
  • مدل یا Prompt جدید
  • شکایت کاربران
  • Tool Callهای مهم

LLM-as-a-Judge چیست؟

در این روش یک مدل، خروجی مدل دیگر را براساس Rubric ارزیابی می‌کند:

{
  "score": 4,
  "label": "mostly_correct",
  "reason": "پاسخ صحیح است اما محدودیت اصلی را ذکر نکرده است."
}

مزایا

  • مقیاس‌پذیر
  • مناسب تحلیل حجم زیاد
  • قابل‌استفاده در Online Evaluation
  • انعطاف‌پذیر برای معیارهای مختلف

محدودیت‌ها

  • امکان سوگیری
  • حساسیت به Prompt ارزیابی
  • ناپایداری
  • هزینه اضافی
  • احتمال ترجیح سبک خاص
  • نیاز به Calibration با ارزیابی انسانی

امتیاز Judge نباید حقیقت قطعی تلقی شود. باید با Dataset انسانی مقایسه و کالیبره شود.

ارزیابی قطعی و احتمالاتی

ارزیابی قطعی

با کد قابل‌انجام است:

  • JSON معتبر است؟
  • فیلد ضروری وجود دارد؟
  • Citation به سند واقعی اشاره می‌کند؟
  • مبلغ منفی نیست؟
  • Tool مجاز بوده؟
  • خروجی از طول مجاز عبور کرده؟
  • زبان پاسخ درست است؟

ارزیابی احتمالاتی

معمولاً با مدل یا انسان انجام می‌شود:

  • آیا پاسخ مفید است؟
  • آیا توضیح کافی است؟
  • آیا پاسخ Grounded است؟
  • آیا لحن مناسب است؟
  • آیا مسئله کاربر حل شده است؟

ابتدا باید معیارهای قطعی ارزان و قابل‌اعتماد اجرا شوند و سپس فقط در موارد لازم از Judge استفاده شود.

Online Evaluation و Offline Evaluation

Offline Evaluation

پیش از انتشار روی Dataset ثابت اجرا می‌شود:

Prompt v6 + Model A
در مقابل
Prompt v7 + Model B

کاربردها:

  • مقایسه مدل‌ها
  • تست Prompt جدید
  • ارزیابی RAG
  • Regression Testing
  • تعیین Threshold

Online Evaluation

روی بخشی از درخواست‌های واقعی Production اجرا می‌شود:

  • ارزیابی کیفیت نمونه پاسخ‌ها
  • تشخیص تغییر رفتار
  • تحلیل شکایت‌ها
  • Drift Detection
  • پایش ایمنی

LangSmith نیز Online Evaluation را برای بررسی الگوهای کیفیت، ایمنی و رفتار واقعی Production از روی Traceها از Offline Evaluation جدا می‌کند. مفاهیم Evaluation در LangSmith

Sampling

ارزیابی تمام درخواست‌ها ممکن است گران یا از نظر حریم خصوصی نامناسب باشد.

نمونه راهبرد:

۱۰۰٪ درخواست‌های خطادار
۱۰۰٪ Tool Callهای حساس
۲۰٪ پاسخ‌های مدل جدید
۵٪ ترافیک عادی
۱٪ پاسخ‌های پایدار و کم‌خطر

Human Feedback

بازخورد کاربر یکی از مهم‌ترین سیگنال‌هاست:

👍 مفید بود
👎 مفید نبود

اما بازخورد دودویی اطلاعات محدودی دارد. بهتر است دلیل نیز قابل‌انتخاب باشد:

نادرست بود
نامرتبط بود
کامل نبود
خیلی طولانی بود
منبع معتبر نداشت
درخواست را انجام نداد

نکته درباره Feedback

نبود بازخورد به معنای رضایت نیست. بسیاری از کاربران هیچ واکنشی ثبت نمی‌کنند.

سیگنال‌های ضمنی نیز مفیدند:

  • بازنویسی فوری سؤال
  • درخواست پاسخ دوباره
  • ترک مکالمه
  • Copyکردن پاسخ
  • بازکردن Citation
  • ارجاع به اپراتور انسانی
  • اصلاح دستی خروجی
  • Undoکردن اقدام Agent

طراحی Dashboard برای AI Observability

یک Dashboard خوب باید امکان حرکت از نمای کلی به Trace دقیق را فراهم کند.

نمای سلامت کلی

  • درخواست در دقیقه
  • Success Rate
  • Error Rate
  • p95 Latency
  • Time to First Token
  • هزینه ساعتی
  • Active Streams
  • Queue Depth
  • Fallback Rate
  • Validation Failure Rate

نمای مدل‌ها

مدلدرخواستموفقیتp95 Latencyمیانگین Tokenهزینهکیفیت
Model A۱۰٬۲۴۰۹۹٫۲٪۴٫۸s۱٬۴۲۰متغیر۴٫۳
Model B۷٬۸۲۰۹۸٫۷٪۲٫۹s۱٬۱۰۰متغیر۴٫۰

نمای ارائه‌دهندگان

  • Availability
  • Timeout Rate
  • 5xx
  • Rate Limit
  • Latency
  • Circuit State
  • Fallback In
  • Fallback Out

نمای هزینه

  • هزینه امروز
  • هزینه به تفکیک مدل
  • هزینه به تفکیک کاربر
  • هزینه به تفکیک قابلیت
  • Cost per Successful Task
  • Retry Cost
  • Fallback Cost
  • Cache Savings

نمای کیفیت

  • User Satisfaction
  • Groundedness
  • Retrieval Relevance
  • JSON Validity
  • Tool Success
  • Hallucination Flags
  • Human Escalation
  • Model Comparison

Drill-down

با کلیک روی هر Metric باید بتوان به موارد زیر رسید:

Dashboard
→ Filtered Requests
→ Individual Trace
→ Spans
→ Retrieval Documents
→ Tool Calls
→ Sanitized Input and Output

Alerting

Alert باید عملی و قابل‌پیگیری باشد.

Alert نامناسب:

یک درخواست خطا داد.

Alert مناسب:

نرخ Timeout مدل اصلی طی ۵ دقیقه از ۲٪ به ۱۴٪ رسیده و p95 Latency از ۸ ثانیه عبور کرده است.

Alertهای زیرساخت

  • Error Rate بالا
  • Latency بالا
  • Queue Depth زیاد
  • Database Failure
  • Circuit Open
  • Connection Pool Exhausted

Alertهای هزینه

  • هزینه بیشتر از بودجه
  • افزایش غیرعادی Token
  • کاهش Cache Hit Rate
  • Retry Cost بالا
  • مصرف غیرعادی کاربر

Alertهای کیفیت

  • افزایش Validation Failure
  • کاهش Groundedness
  • افزایش Feedback منفی
  • افزایش Tool Failure
  • افزایش Max Steps
  • افزایش Human Escalation

جلوگیری از Alert Fatigue

اگر تیم دائماً Alertهای بی‌اهمیت دریافت کند، هشدارهای واقعی نیز نادیده گرفته می‌شوند.

راهکارها:

  • Alert براساس بازه زمانی
  • استفاده از p95 و نرخ به‌جای یک رخداد
  • Deduplication
  • Grouping براساس مدل و Provider
  • Severity
  • Cooldown
  • لینک مستقیم به Dashboard و Runbook
  • مشخص‌بودن مسئول Alert

سطح‌های Severity

INFO: فقط ثبت
WARNING: نیاز به بررسی در ساعات کاری
HIGH: پاسخ سریع
CRITICAL: اختلال گسترده یا خطر مالی و امنیتی

SLI، SLO و SLA در سرویس‌های AI

SLI

شاخص اندازه‌گیری‌شده:

  • Availability
  • Error Rate
  • p95 Latency
  • Stream Completion Rate
  • Task Completion Rate
  • Output Validation Rate
  • Groundedness Rate

SLO

هدف داخلی سرویس:

۹۹٫۵٪ درخواست‌های معتبر بدون خطای زیرساختی تکمیل شوند.
۹۵٪ پاسخ‌های Streaming در کمتر از ۲ ثانیه شروع شوند.
۹۹٪ خروجی‌های ساختاریافته از Validation عبور کنند.

SLA

تعهد رسمی و قراردادی به مشتری است و ممکن است پیامد مالی یا حقوقی داشته باشد.

Error Budget

اگر SLO برابر ۹۹٫۹٪ باشد، میزان شکست مجاز محدود است. Error Budget کمک می‌کند میان انتشار قابلیت جدید و پایداری تعادل برقرار شود.

در سیستم AI بهتر است SLO فقط براساس HTTP Status نباشد. پاسخ فنی موفق اما غیرقابل‌استفاده نباید موفقیت کامل محسوب شود.

حریم خصوصی در AI Observability

ثبت Prompt و Completion برای Debug مفید است، اما ممکن است شامل اطلاعات حساس باشد:

  • نام و شماره تماس
  • اطلاعات مالی
  • داده‌های پزشکی
  • اسناد محرمانه
  • API Key
  • رمز عبور
  • قرارداد
  • کد اختصاصی
  • اطلاعات سازمانی

اصل حداقل‌سازی داده

فقط اطلاعاتی را ثبت کنید که برای هدف مشخص لازم‌اند.

به‌جای متن کامل:

{
  "prompt_hash": "sha256:...",
  "prompt_chars": 4820,
  "message_count": 8,
  "prompt_version": "support-v7"
}

Redaction

پیش از ذخیره Log:

09123456789 → [PHONE]
user@example.com → [EMAIL]
sk-secret-key → [API_KEY]

Hash شناسه کاربر

import hashlib
import hmac


def hash_user_id(user_id: str, secret: bytes) -> str:
    return hmac.new(
        secret,
        user_id.encode(),
        hashlib.sha256,
    ).hexdigest()

HMAC با Secret از Hash ساده برای شناسه‌های قابل‌حدس مناسب‌تر است.

دسترسی به Trace

همه اعضای تیم نباید محتوای کامل Trace را ببینند. دسترسی باید براساس نقش باشد:

Support: Metadata محدود
Engineering: Trace فنی
Security: Audit و Incident
Finance: Usage و Cost
Admin محدود: محتوای Redacted

Retention

برای انواع داده زمان نگهداری تعریف کنید:

Metrics تجمیعی: بلندمدت
Logs فنی: ۳۰ تا ۹۰ روز
Promptهای Redacted: کوتاه‌مدت
محتوای حساس: ثبت نشود یا حداقل نگهداری
Audit عملیات حساس: طبق سیاست سازمان

Opt-in برای محتوای کامل

ثبت کامل Prompt و Completion بهتر است فقط در محیط Debug کنترل‌شده یا با سیاست روشن انجام شود.

OpenTelemetry نیز ثبت محتوای کامل پیام، Tool Call و Tool Result را مسئله‌ای Opt-in در نظر می‌گیرد؛ زیرا این داده‌ها می‌توانند حساس و پرحجم باشند. Observability مولد با OpenTelemetry

نمونه‌کد Python با API درواره

نصب وابستگی‌ها

pip install -U openai opentelemetry-api opentelemetry-sdk

تنظیم Client و Tracer

import os
import time
import uuid

from openai import OpenAI
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
    BatchSpanProcessor,
    ConsoleSpanExporter,
)


provider = TracerProvider()
processor = BatchSpanProcessor(ConsoleSpanExporter())
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)

tracer = trace.get_tracer("darvareh-ai-service")

client = OpenAI(
    api_key=os.environ["DARVAREH_API_KEY"],
    base_url="https://api.darvareh.ir/v1",
)

ConsoleSpanExporter فقط برای نمونه است. در Production معمولاً Traceها از طریق OpenTelemetry Collector به Backend مشاهده‌پذیری ارسال می‌شوند.

فراخوانی مدل با Trace

def generate_answer(
    user_message: str,
    model: str,
    prompt_version: str,
) -> str:
    request_id = f"req_{uuid.uuid4().hex}"

    with tracer.start_as_current_span(
        "ai.chat_request"
    ) as root_span:
        root_span.set_attribute(
            "app.request.id",
            request_id,
        )
        root_span.set_attribute(
            "gen_ai.request.model",
            model,
        )
        root_span.set_attribute(
            "app.prompt.version",
            prompt_version,
        )

        with tracer.start_as_current_span(
            "provider.chat_completion"
        ) as provider_span:
            started_at = time.monotonic()

            try:
                response = client.chat.completions.create(
                    model=model,
                    messages=[
                        {
                            "role": "system",
                            "content": (
                                "شما یک دستیار فنی دقیق هستید. "
                                "اگر اطلاعات کافی نیست، حدس نزنید."
                            ),
                        },
                        {
                            "role": "user",
                            "content": user_message,
                        },
                    ],
                    temperature=0.2,
                    max_tokens=800,
                )

                latency_ms = int(
                    (time.monotonic() - started_at) * 1000
                )

                provider_span.set_attribute(
                    "app.latency_ms",
                    latency_ms,
                )

                provider_span.set_attribute(
                    "gen_ai.response.model",
                    response.model or model,
                )

                if response.id:
                    provider_span.set_attribute(
                        "gen_ai.response.id",
                        response.id,
                    )

                choice = response.choices[0]
                finish_reason = choice.finish_reason or "unknown"

                provider_span.set_attribute(
                    "gen_ai.response.finish_reason",
                    finish_reason,
                )

                if response.usage:
                    provider_span.set_attribute(
                        "gen_ai.usage.input_tokens",
                        response.usage.prompt_tokens,
                    )
                    provider_span.set_attribute(
                        "gen_ai.usage.output_tokens",
                        response.usage.completion_tokens,
                    )
                    provider_span.set_attribute(
                        "gen_ai.usage.total_tokens",
                        response.usage.total_tokens,
                    )

                content = choice.message.content

                if not content:
                    provider_span.set_status(
                        trace.Status(
                            trace.StatusCode.ERROR,
                            "empty_response",
                        )
                    )
                    raise ValueError("Model returned no content")

                provider_span.set_status(
                    trace.Status(
                        trace.StatusCode.OK
                    )
                )

                return content

            except Exception as error:
                provider_span.record_exception(error)
                provider_span.set_status(
                    trace.Status(
                        trace.StatusCode.ERROR,
                        type(error).__name__,
                    )
                )
                raise

چرا متن Prompt ثبت نشد؟

در این نمونه فقط نسخه Prompt، مدل، مصرف و زمان ثبت شده‌اند. متن کاربر ممکن است شامل اطلاعات حساس باشد. در صورت نیاز، نسخه Redacted یا Hash آن را ثبت کنید.

ثبت Metrics در Python

from opentelemetry import metrics
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import (
    ConsoleMetricExporter,
    PeriodicExportingMetricReader,
)


metric_reader = PeriodicExportingMetricReader(
    ConsoleMetricExporter(),
    export_interval_millis=10_000,
)

metrics.set_meter_provider(
    MeterProvider(metric_readers=[metric_reader])
)

meter = metrics.get_meter("darvareh-ai-service")

request_counter = meter.create_counter(
    "ai.requests",
    description="Total number of AI requests",
)

input_token_counter = meter.create_counter(
    "ai.input_tokens",
    description="Total input tokens",
)

output_token_counter = meter.create_counter(
    "ai.output_tokens",
    description="Total output tokens",
)

latency_histogram = meter.create_histogram(
    "ai.request.duration",
    unit="ms",
    description="AI request duration",
)

ثبت Metric:

attributes = {
    "model": model,
    "provider": "darvareh",
    "status": "success",
}

request_counter.add(1, attributes)
input_token_counter.add(input_tokens, attributes)
output_token_counter.add(output_tokens, attributes)
latency_histogram.record(latency_ms, attributes)

مراقب Cardinality باشید

نباید Attributeهایی با تعداد مقادیر بسیار زیاد را روی Metrics قرار دهید:

نامناسب:

{"request_id": request_id}

مناسب:

{
    "model": model,
    "status": "success",
    "endpoint": "chat_completions",
}

Request ID برای Trace و Log مناسب است، نه Metric تجمیعی.

نمونه Streaming با اندازه‌گیری TTFT

def stream_answer(user_message: str, model: str):
    request_started = time.monotonic()
    first_chunk_at = None
    chunk_count = 0
    final_usage = None

    stream = client.chat.completions.create(
        model=model,
        messages=[
            {
                "role": "user",
                "content": user_message,
            }
        ],
        stream=True,
        stream_options={
            "include_usage": True,
        },
    )

    try:
        for chunk in stream:
            if first_chunk_at is None:
                first_chunk_at = time.monotonic()

            chunk_count += 1

            if chunk.usage is not None:
                final_usage = chunk.usage

            if chunk.choices:
                delta = chunk.choices[0].delta.content

                if delta:
                    yield delta

    finally:
        completed_at = time.monotonic()

        ttft_ms = (
            int((first_chunk_at - request_started) * 1000)
            if first_chunk_at is not None
            else None
        )

        total_ms = int(
            (completed_at - request_started) * 1000
        )

        print(
            {
                "event": "ai_stream_finished",
                "model": model,
                "ttft_ms": ttft_ms,
                "total_ms": total_ms,
                "chunk_count": chunk_count,
                "usage_received": final_usage is not None,
            }
        )

پشتیبانی از include_usage و ساختار Chunkها باید برای مدل و ارائه‌دهنده انتخابی بررسی شود.

نمونه‌کد TypeScript

نصب وابستگی‌ها

npm install openai \
  @opentelemetry/api \
  @opentelemetry/sdk-trace-node \
  @opentelemetry/sdk-trace-base

تنظیم OpenTelemetry

import OpenAI from "openai";
import { trace, SpanStatusCode } from "@opentelemetry/api";
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
import {
  BatchSpanProcessor,
  ConsoleSpanExporter,
} from "@opentelemetry/sdk-trace-base";

const provider = new NodeTracerProvider({
  spanProcessors: [
    new BatchSpanProcessor(new ConsoleSpanExporter()),
  ],
});

provider.register();

const tracer = trace.getTracer("darvareh-ai-service");

const client = new OpenAI({
  apiKey: process.env.DARVAREH_API_KEY,
  baseURL: "https://api.darvareh.ir/v1",
});

فراخوانی مدل

async function generateAnswer({
  userMessage,
  model,
  promptVersion,
}: {
  userMessage: string;
  model: string;
  promptVersion: string;
}): Promise<string> {
  return tracer.startActiveSpan(
    "ai.chat_request",
    async (rootSpan) => {
      const requestId = crypto.randomUUID();

      rootSpan.setAttributes({
        "app.request.id": requestId,
        "gen_ai.request.model": model,
        "app.prompt.version": promptVersion,
      });

      try {
        const result = await tracer.startActiveSpan(
          "provider.chat_completion",
          async (providerSpan) => {
            const startedAt = performance.now();

            try {
              const response =
                await client.chat.completions.create({
                  model,
                  messages: [
                    {
                      role: "system",
                      content:
                        "شما یک دستیار فنی دقیق هستید و حدس نمی‌زنید.",
                    },
                    {
                      role: "user",
                      content: userMessage,
                    },
                  ],
                  temperature: 0.2,
                  max_tokens: 800,
                });

              providerSpan.setAttributes({
                "app.latency_ms": Math.round(
                  performance.now() - startedAt,
                ),
                "gen_ai.response.id": response.id,
                "gen_ai.response.model":
                  response.model ?? model,
                "gen_ai.response.finish_reason":
                  response.choices[0]?.finish_reason ??
                  "unknown",
              });

              if (response.usage) {
                providerSpan.setAttributes({
                  "gen_ai.usage.input_tokens":
                    response.usage.prompt_tokens,
                  "gen_ai.usage.output_tokens":
                    response.usage.completion_tokens,
                  "gen_ai.usage.total_tokens":
                    response.usage.total_tokens,
                });
              }

              const content =
                response.choices[0]?.message?.content;

              if (!content) {
                throw new Error("Empty model response");
              }

              providerSpan.setStatus({
                code: SpanStatusCode.OK,
              });

              return content;
            } catch (error) {
              providerSpan.recordException(
                error as Error,
              );

              providerSpan.setStatus({
                code: SpanStatusCode.ERROR,
                message:
                  error instanceof Error
                    ? error.message
                    : "Unknown error",
              });

              throw error;
            } finally {
              providerSpan.end();
            }
          },
        );

        rootSpan.setStatus({
          code: SpanStatusCode.OK,
        });

        return result;
      } catch (error) {
        rootSpan.recordException(error as Error);

        rootSpan.setStatus({
          code: SpanStatusCode.ERROR,
        });

        throw error;
      } finally {
        rootSpan.end();
      }
    },
  );
}

طراحی Schema برای Log

یک Schema ثابت امکان Query و تحلیل بهتر را فراهم می‌کند:

{
  "timestamp": "2026-07-11T12:00:00Z",
  "level": "info",
  "event": "ai_request_completed",
  "request_id": "req_2048",
  "trace_id": "4bf92f...",
  "user_id_hash": "usr_91f...",
  "organization_id": "org_52",
  "endpoint": "chat_completions",
  "model_requested": "MODEL_ID",
  "model_used": "MODEL_ID",
  "provider": "darvareh",
  "prompt_version": "support-v7",
  "stream": false,
  "input_tokens": 1420,
  "output_tokens": 380,
  "cached_tokens": 0,
  "latency_ms": 2850,
  "ttft_ms": null,
  "retry_count": 0,
  "fallback_used": false,
  "finish_reason": "stop",
  "validation_status": "passed",
  "status": "success",
  "error_code": null
}

نسخه Schema

ساختار Telemetry نیز باید نسخه داشته باشد:

{
  "telemetry_schema_version": "1.2"
}

بدون Versioning، تغییر نام فیلدها می‌تواند Dashboard و Alertها را خراب کند.

Sampling در Tracing

ثبت تمام Traceها در ترافیک بالا هزینه‌بر است.

Head Sampling

در ابتدای درخواست تصمیم گرفته می‌شود Trace ذخیره شود یا نه.

مزیت:

  • ساده
  • هزینه کمتر

محدودیت:

  • هنوز نمی‌دانیم درخواست خطا خواهد داد یا نه.

Tail Sampling

پس از مشاهده نتیجه تصمیم گرفته می‌شود Trace ذخیره شود.

می‌توان این موارد را همیشه نگه داشت:

  • خطاها
  • Latency بالا
  • Fallback
  • Tool Call حساس
  • Validation Failure
  • هزینه غیرعادی

و فقط درصد کمی از درخواست‌های عادی را ذخیره کرد.

Trace و Metric را به هم متصل کنید

Dashboard باید امکان رفتن از Metric به Trace را فراهم کند.

برای مثال:

Alert: p95 Latency بالا
→ Dashboard مدل
→ درخواست‌های کند
→ Trace خاص
→ Span کند
→ Provider Request

OpenTelemetry با Context Propagation امکان مرتبط‌کردن Logs، Metrics و Traces را در مسیر درخواست فراهم می‌کند. مستندات OpenTelemetry

تشخیص Drift

Drift یعنی رفتار سیستم در طول زمان تغییر کند.

Model Drift

ارائه‌دهنده نسخه مدل را تغییر می‌دهد و خروجی متفاوت می‌شود.

Data Drift

نوع درخواست کاربران تغییر می‌کند.

Retrieval Drift

محتوای پایگاه دانش یا توزیع اسناد تغییر می‌کند.

Cost Drift

میانگین Token یا قیمت افزایش می‌یابد.

Quality Drift

امتیاز Groundedness یا رضایت کاربر کاهش پیدا می‌کند.

برای تشخیص Drift باید Baseline داشته باشید و Metricها را در طول زمان مقایسه کنید.

مقایسه نسخه‌های Prompt

برای هر پاسخ Prompt Version را ثبت کنید:

support-v6
support-v7

سپس مقایسه کنید:

معیارv6v7
Task Success۸۱٪۸۷٪
Groundedness۸۸٪۹۱٪
Input Tokens۱٬۲۰۰۱٬۵۵۰
Output Tokens۴۵۰۳۹۰
p95 Latency۵٫۲s۵٫۸s
Cost per Taskکمترکمی بیشتر

یک Prompt ممکن است کیفیت را بهتر کند اما هزینه و Latency را افزایش دهد. تصمیم باید براساس هدف کسب‌وکار باشد.

اشتباهات رایج در AI Observability

ثبت فقط HTTP Status

کد 200 چیزی درباره کیفیت پاسخ نمی‌گوید.

ثبت تمام Promptها بدون Redaction

ریسک حریم خصوصی و امنیت ایجاد می‌کند.

نداشتن Prompt Version

نمی‌توان تغییر کیفیت را به نسخه مشخصی مرتبط کرد.

تمرکز فقط بر میانگین Latency

تجربه کاربران کندتر پنهان می‌شود.

نادیده‌گرفتن Retryهای SDK

هزینه و Latency واقعی کمتر از مقدار ثبت‌شده به نظر می‌رسد.

ثبت‌نکردن مسیر Fallback

مشخص نیست پاسخ را کدام مدل تولید کرده است.

Metrics با Cardinality بالا

استفاده از Request ID، متن Prompt یا User ID به‌عنوان Label متریک، هزینه و عملکرد سیستم Metrics را خراب می‌کند.

ارزیابی تمام درخواست‌ها با مدل Judge

هزینه بالا و تأخیر غیرضروری ایجاد می‌کند.

یکی‌دانستن امتیاز Judge با حقیقت

Judge نیز یک مدل احتمالاتی است.

نداشتن Trace برای RAG و Tools

در صورت پاسخ اشتباه، علت واقعی قابل‌تشخیص نیست.

Dashboard بدون اقدام

Dashboard باید به تصمیم یا عملیات مشخصی منجر شود.

Alert بدون Runbook

تیم می‌فهمد مشکلی وجود دارد، اما نمی‌داند چه کاری انجام دهد.

Runbook چیست؟

Runbook دستورالعمل واکنش به یک Alert یا Incident است.

نمونه برای افزایش Timeout:

1. Dashboard Provider Latency را بررسی کن.
2. مدل‌ها و Regionهای درگیر را مشخص کن.
3. Retry Rate و Circuit State را بررسی کن.
4. اگر فقط یک Provider مشکل دارد، مسیر را غیرفعال کن.
5. Fallback Success Rate و Cost را کنترل کن.
6. وضعیت را در کانال Incident ثبت کن.
7. پس از بازیابی، مسیر اصلی را تدریجی فعال کن.

چک‌لیست AI Observability برای Production

شناسایی و ارتباط

  • هر درخواست Internal Request ID دارد.
  • Trace ID در تمام سرویس‌ها منتقل می‌شود.
  • Provider Request ID ثبت می‌شود.
  • Conversation و Job قابل‌ردیابی‌اند.
  • Prompt Version ثبت می‌شود.
  • Context Builder Version ثبت می‌شود.
  • Model Route و Fallback Route مشخص‌اند.

Latency

  • End-to-End Latency ثبت می‌شود.
  • Provider Latency جداست.
  • Retrieval و Tool Latency ثبت می‌شوند.
  • Time to First Token اندازه‌گیری می‌شود.
  • p50، p95 و p99 موجودند.
  • Queue Wait Time ثبت می‌شود.
  • Stream Completion Rate اندازه‌گیری می‌شود.

Token و هزینه

  • Input و Output Tokens ثبت می‌شوند.
  • Cached Tokens در صورت وجود ثبت می‌شوند.
  • هزینه هر درخواست محاسبه می‌شود.
  • هزینه Retry و Fallback مشخص است.
  • هزینه به تفکیک مدل، کاربر و قابلیت قابل‌مشاهده است.
  • Cost per Successful Task اندازه‌گیری می‌شود.
  • افزایش غیرعادی هزینه Alert دارد.

خطا و پایداری

  • خطاها کد داخلی ثابت دارند.
  • Provider Error از Client Error جداست.
  • Retry Count و علت آن ثبت می‌شود.
  • Fallback Rate قابل‌مشاهده است.
  • Circuit Breaker State مانیتور می‌شود.
  • Refusal و Incomplete Response ثبت می‌شوند.
  • Validation Failure مانیتور می‌شود.

RAG

  • Query و نسخه بازنویسی ثبت یا Hash می‌شوند.
  • Index و Chunking Version ثبت می‌شود.
  • شناسه اسناد بازیابی‌شده موجود است.
  • Retrieval و Reranking Score ثبت می‌شوند.
  • Context Token Count اندازه‌گیری می‌شود.
  • Groundedness و Citation Correctness ارزیابی می‌شوند.
  • دسترسی اسناد قابل Audit است.

Agent و Tool Calling

  • مسیر Agent ثبت می‌شود.
  • تعداد مراحل اندازه‌گیری می‌شود.
  • Max Steps Rate مانیتور می‌شود.
  • Tool Call ID موجود است.
  • آرگومان و نتیجه Validation می‌شوند.
  • مجوز اجرای Tool ثبت می‌شود.
  • Tool Retry و Idempotency قابل‌ردیابی‌اند.
  • عملیات حساس Audit Log دارند.

کیفیت

  • Dataset ارزیابی وجود دارد.
  • Offline Eval پیش از انتشار اجرا می‌شود.
  • Online Eval با Sampling انجام می‌شود.
  • Feedback کاربر ثبت می‌شود.
  • دلیل Feedback منفی قابل‌انتخاب است.
  • Judge با ارزیابی انسانی کالیبره شده است.
  • Drift مدل، داده و هزینه بررسی می‌شود.

امنیت و حریم خصوصی

  • API Key و Secretها Log نمی‌شوند.
  • Prompt کامل به‌صورت پیش‌فرض ثبت نمی‌شود.
  • داده حساس Redact می‌شود.
  • شناسه کاربر Hash می‌شود.
  • Traceها کنترل دسترسی دارند.
  • Retention Policy مشخص است.
  • Cache و Trace میان سازمان‌ها جداست.
  • حذف داده کاربر امکان‌پذیر است.

Dashboard و Alerting

  • Dashboard سلامت کلی وجود دارد.
  • نمای مدل و Provider جداست.
  • Dashboard هزینه وجود دارد.
  • Dashboard کیفیت و RAG وجود دارد.
  • Alertها Severity دارند.
  • Alertها Deduplicate می‌شوند.
  • هر Alert Runbook و مسئول دارد.
  • از Metric می‌توان به Trace رسید.

پرسش‌های متداول

AI Observability چیست؟

AI Observability مجموعه‌ای از Logs، Metrics، Traces و ارزیابی‌هاست که به تیم‌ها کمک می‌کند عملکرد، هزینه، خطا، کیفیت و رفتار مدل‌ها، RAG و Agentهای هوش مصنوعی را درک کنند.

تفاوت Monitoring و Observability چیست؟

Monitoring وضعیت‌ها و خطاهای شناخته‌شده را دنبال می‌کند. Observability امکان بررسی علت مشکلات و سؤال‌های پیش‌بینی‌نشده را از طریق ارتباط Logs، Metrics، Traces و داده‌های کیفیت فراهم می‌کند.

مهم‌ترین Metrics برای API هوش مصنوعی چیست؟

نرخ موفقیت، نرخ خطا، p95 Latency، Time to First Token، Input و Output Tokens، هزینه، Retry Rate، Fallback Rate، Validation Failure و Task Completion Rate از مهم‌ترین معیارها هستند.

آیا باید Prompt و پاسخ کامل را Log کنیم؟

معمولاً نه. این داده‌ها ممکن است حساس و پرحجم باشند. بهتر است نسخه Prompt، Hash، Metadata و محتوای Redacted ثبت شود. ثبت کامل باید محدود، کنترل‌شده و براساس سیاست حریم خصوصی باشد.

Time to First Token چیست؟

مدت‌زمان میان ارسال درخواست و دریافت اولین Token یا Chunk در پاسخ Streaming است. این معیار تأثیر زیادی بر تجربه ادراک‌شده کاربر دارد.

چگونه هزینه هر درخواست را مانیتور کنیم؟

مصرف Input، Output و Cached Tokens را با قیمت همان مدل ترکیب کنید و هزینه Retry، Fallback، Toolها و Retrieval را نیز در صورت وجود اضافه کنید.

چگونه کیفیت RAG را اندازه‌گیری کنیم؟

با معیارهایی مانند Retrieval Precision، Retrieval Recall، Context Relevance، Groundedness و Citation Correctness می‌توان کیفیت مراحل Retrieval و Generation را بررسی کرد.

چگونه Agent را مانیتور کنیم؟

هدف، مراحل اجرا، Tool Callها، آرگومان‌ها، نتایج، خطاها، Retryها، تعداد مراحل، هزینه و دلیل پایان Agent را در یک Trace مرتبط ثبت کنید.

آیا LLM-as-a-Judge قابل‌اعتماد است؟

برای تحلیل در مقیاس بالا مفید است، اما حقیقت قطعی نیست. باید با Rubric دقیق، Dataset انسانی و Calibration استفاده شود.

OpenTelemetry چه کمکی می‌کند؟

OpenTelemetry یک چارچوب متن‌باز و مستقل از Vendor برای تولید و انتقال Logs، Metrics و Traces است. می‌توان از آن برای مرتبط‌کردن مسیر درخواست AI در API، RAG، Provider، Toolها و سرویس‌های داخلی استفاده کرد.

آیا Streaming نیز قابل‌مانیتور است؟

بله. Time to First Chunk، تعداد Chunkها، زمان کل، نرخ تکمیل، قطع Client و دریافت Usage نهایی از معیارهای مهم Streaming هستند.

آیا API درواره اطلاعات مصرف را برمی‌گرداند؟

پاسخ مدل‌های متنی در رابط سازگار با OpenAI معمولاً می‌تواند شامل بخش usage باشد، اما جزئیات Usage و Cached Tokens به مدل و ارائه‌دهنده وابسته است. برنامه باید نبود یا تفاوت این فیلدها را نیز مدیریت کند.

جمع‌بندی

AI Observability یکی از پایه‌های ساخت سرویس‌های هوش مصنوعی قابل‌اعتماد است. بدون مشاهده‌پذیری، تیم فقط می‌داند کاربر پاسخ نامناسبی دریافت کرده، اما نمی‌تواند علت آن را میان Prompt، مدل، Provider، RAG، Memory، Tool و Fallback پیدا کند.

یک سیستم Observability مناسب باید بتواند:

  • هر درخواست را از ابتدا تا انتها ردیابی کند؛
  • مدل، ارائه‌دهنده و نسخه Prompt را مشخص کند؛
  • Latency و Time to First Token را اندازه بگیرد؛
  • Token و هزینه را ثبت کند؛
  • Retry و Fallback را قابل‌مشاهده سازد؛
  • مسیر RAG و Agent را در Trace نمایش دهد؛
  • خروجی‌های نامعتبر و Refusal را تشخیص دهد؛
  • کیفیت پاسخ را با Eval و Feedback بررسی کند؛
  • و همه این کارها را بدون افشای اطلاعات حساس انجام دهد.

هدف Observability جمع‌آوری بیشترین داده ممکن نیست. هدف جمع‌آوری داده‌ای است که به تشخیص مشکل، کاهش هزینه، بهبود کیفیت و تصمیم‌گیری سریع‌تر منجر شود.

مانیتورینگ سرویس‌های هوش مصنوعی با API درواره

درواره زیرساخت دسترسی به مدل‌های مختلف هوش مصنوعی را از طریق یک API سازگار با OpenAI فراهم می‌کند. توسعه‌دهندگان می‌توانند اطلاعاتی مانند درخواست‌ها، مصرف، مدل، Latency و خطاهای اپلیکیشن خود را ثبت و در کنار داده‌های RAG، Tool Calling و Agentها تحلیل کنند.

آدرس پایه API درواره:

https://api.darvareh.ir/v1

برای ساخت یک معماری قابل‌اعتماد، Trace درخواست را از Backend خود آغاز کنید، فراخوانی درواره را به‌عنوان یک Span ثبت کنید و داده‌های Provider را به مراحل داخلی مانند احراز هویت، Billing، RAG، Validation و Tool Calling متصل کنید.

مقالات مرتبط

Read more