فاز 19 · درس 28

مشاهده‌پذیری ایجنت‌ها با اسپن‌های OpenTelemetry GenAI و متریک‌های Prometheus

نوع
ساختنی
زمان
حدود 90 دقیقه
زبان‌ها
Python (stdlib)
پیش‌نیاز
فاز 19 · درس 25 (گیت‌های اعتبارسنجی)، فاز 19 · درس 26 (ساندباکس)، فاز 19 · درس 27 (بستر ارزیابی)، فاز 13 · درس 20 (OpenTelemetry GenAI)، فاز 14 · درس 23 (قراردادهای معنایی OTel GenAI)
یک ماژول شیشه‌ای شفاف که در مسیر کابل‌های سیگنال قرار گرفته و لاگ‌ها و داده‌های زمان‌بندی را روی نواری کاغذمانند نمایش می‌دهد
فهرست مطالب
  1. اهداف یادگیری
  2. صورت مسئله
  3. مفاهیم پایه
  4. معماری
  5. آنچه پیاده‌سازی خواهید کرد
  6. چرا پیاده‌سازی دستی به‌جای opentelemetry-sdk؟
  7. هماهنگی این درس با سایر بخش‌های مسیر A
  8. اجرای برنامه

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

راه‌حل این چالش، تله‌متری ساختاریافته بر پایه‌ی استانداردهای باز است. قراردادهای معنایی OpenTelemetry GenAI و فرمت‌های متنی Prometheus این امکان را فراهم می‌کنند که تمامی ردگیری‌ها (traces) و متریک‌ها (metrics) به زبانی مشترک و قابل فهم برای ابزارهای مانیتورینگ مانند Grafana و Jaeger تبدیل شوند.

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

بستر آزمایشی ایجنت (agent harness) بدون قابلیت مشاهده‌پذیری (observability)، جعبه‌ی سیاهی است که فقط برایتان هزینه می‌تراشد. در این درس، یک سازنده‌ی اسپن (span builder) را از صفر پیاده‌سازی می‌کنیم که رکوردهایی منطبق با قراردادهای معنایی (semantic conventions) مربوط به OpenTelemetry GenAI تولید می‌کند، آن‌ها را خط‌به‌خط در یک فایل JSON-Lines می‌نویسد و شمارنده‌ها و هیستوگرام‌ها را با قالب متنی Prometheus نمایش می‌دهد. تمام این پیاده‌سازی فقط با کتابخانه‌ی استاندارد پایتون انجام می‌شود و کاملاً آفلاین کار می‌کند.

اهداف یادگیری

  • ساخت دیتاکلاس اسپن (span) منطبق با قراردادهای معنایی OpenTelemetry GenAI.
  • پیاده‌سازی خروجی‌گیر (exporter) با قالب JSONL که در هر خط، یک اسپن مستقل و کامل بنویسد.
  • ساخت شمارنده‌ها (counters) و هیستوگرام‌ها (histograms) همراه با برچسب‌ها و نمایش خروجی با قالب متنی Prometheus.
  • قرار دادن هر تابع فراخوانی‌پذیر در یک کانتکست منیجر اسپن که مدت‌زمان اجرا، وضعیت و استثناها را ثبت کند.
  • اعتبارسنجی اینکه اسپن‌های تولیدشده با json.loads به‌درستی بازیابی می‌شوند (roundtrip) و ساختار آن‌ها دقیقاً با استاندارد همخوانی دارد.

صورت مسئله

یک ایجنت کدنویسی در محیط عملیاتی (پروداکشن)، در هر نوبت گفت‌وگو (turn) سه نوع دست‌سازه (artifact) تولید می‌کند: فراخوانی مدل، اجرای ابزار، و تصمیم گیت اعتبارسنجی (verification gate). بدون تله‌متری ساختاریافته، هیچ‌کدام از این‌ها به کار نمی‌آیند.

حالت خرابی اول، ردگیری محاسبات گم‌شده است. روز سه‌شنبه مشکلی پیش آمده اما تنها سندی که دارید یک لاگ چت 500 خطی است. هیچ رکوردی وجود ندارد که نشان دهد کدام ابزار اجرا شده، چقدر طول کشیده، چه تعداد توکن وارد پرامپت شده، یا اینکه آیا گیت درخواستی را رد کرده است یا نه. توسعه‌دهنده‌ی ایجنت فقط باید حدس بزند.

حالت خرابی دوم، ردگیری محاسبات غیرقابل‌تجزیه است. بستر آزمایشی، اسپن‌ها را نوشته اما نام‌های من‌درآوردی و دلخواه خودش را برای فیلدها به کار برده است. هیچ ابزاری در Grafana، Honeycomb، Jaeger یا حتی CLI محلی نمی‌تواند آن‌ها را بخواند. تمام ابزارهای موجود در پشته‌ی فنی تیم عملاً هدر می‌روند، چون اسپن‌ها غیراستاندارد هستند.

حالت خرابی سوم، متریک تجمیع‌نشده است. در ردگیری محاسبات (trace) می‌توانید یک فراخوانی ابزار کُند را پیدا کنید، اما نمی‌توانید به این پرسش پاسخ دهید: «تاخیر p95 فراخوانی‌های read_file در یک ساعت گذشته چقدر بوده است؟» زیرا هیچ متریکی وجود ندارد و فقط ردگیری‌ها ثبت شده‌اند.

قراردادهای معنایی OpenTelemetry GenAI دقیقاً برای حل همین مشکل شکل گرفته‌اند. این قراردادها مجموعه‌ای کوچک و استاندارد از ویژگی‌ها (attributes) را تعریف می‌کنند که بین صادرکننده‌های اسپن در تمام فریم‌ورک‌های LLM مشترک است. اگر بستر آزمایشی شما این ویژگی‌ها را بنویسد، هر سیستم پشتیبان سازگار با OTel می‌تواند آن‌ها را بخواند.

مفاهیم پایه

فراخوانی ابزار / فراخوانی مدل / تصمیم گیتSpanBuilder.span()مدیر بسترGenAISpantrace_id / span_id / nameویژگی‌ها:gen_ai.systemgen_ai.request.*gen_ai.usage.*شروع، پایان، وضعیتJSONLWriterMetricsRegistrytraces.jsonlمتن metrics

هر عملیات در بستر آزمایشی، یک اسپن تولید می‌کند. هر اسپن شامل این موارد است: یک شناسه‌ی ردگیری (trace id) برای کل فراخوانی ایجنت، یک شناسه‌ی اسپن (span id) برای همین عملیات مشخص، یک نام (مثل gen_ai.chat یا gen_ai.tool.execution)، ویژگی‌هایی منطبق بر قراردادهای GenAI، زمان شروع و پایان، و یک وضعیت (status).

قراردادهای GenAI این کلیدهای ویژگی را استانداردسازی می‌کنند: gen_ai.system (کدام ارائه‌دهنده، مثل anthropic یا openai)، gen_ai.request.model (شناسه‌ی مدل)، gen_ai.request.max_tokens، gen_ai.usage.input_tokens، gen_ai.usage.output_tokens، gen_ai.response.model، gen_ai.response.id، gen_ai.operation.name، به اضافه‌ی کلیدهای ویژه‌ی ابزارها مانند gen_ai.tool.name و gen_ai.tool.call.id.

خروجی‌گیر داده‌ها را در قالب JSONL می‌نویسد؛ یعنی در هر خط یک آبجکت JSON. این ساده‌ترین فرمت ممکنی است که ابزارهای بعدی می‌توانند آن را به‌صورت استریم بخوانند، با grep جست‌وجو کنند و در پایگاه‌های داده بارگذاری نمایند. یک صادرکننده‌ی واقعی OTel بر بستر OTLP gRPC ارتباط برقرار می‌کند؛ اما خروجی‌گیر JSONL این درس، معادل آفلاین آن است و روی هر سیستمی با کد خروج صفر اجرا می‌شود.

متریک‌ها کنار ردگیری‌ها قرار دارند. با هر فراخوانی ابزار، یک شمارنده افزایش می‌یابد: tools_called_total{tool="read_file"}. یک هیستوگرام نیز تاخیر ثبت‌شده را اندازه می‌گیرد: tool_latency_ms{tool="read_file"}. هر دوی این‌ها به قالب متنی ارائه‌ی Prometheus سریال‌سازی می‌شوند که استاندارد عملی (de-facto) برای متریک‌های مبتنی بر pull است.

اسپن‌های ردگیریبرای بررسی بکشید
TRACE SPANSA distributed trace is a tree of spans laid out on a timeline. The root span covers the whole request; child spans for each LLM call, retrieval, and tool call nest inside it by start time and duration. Reading the gantt shows where the latency actually went, which is the first question every production incident asks.0ms300ms600ms900ms1200mshandle_requestllm_call (plan)420msبازیابیvector_searchبازرتبه‌بندیtool_call (db)llm_call (answer)
یک ردگیری محاسبات توزیع‌شده، درختی از اسپن‌ها است که روی یک خط زمانی چیده شده‌اند. اسپن ریشه کل درخواست را پوشش می‌دهد؛ اسپن‌های فرزند برای هر فراخوانی LLM، بازیابی و فراخوانی ابزار بر اساس زمان شروع و مدت‌زمان در درون آن آشیانه می‌گیرند. خواندن نمودار گانت نشان می‌دهد که تاخیر دقیقاً در کجا صرف شده است؛ این نخستین پرسشی است که در هر حادثه محیط عملیاتی (پروداکشن) مطرح می‌شود.

معماری

AgentHarnessدرس‌های 25-27SpanBuilderمدیر بستر / ویژگی‌ها / وضعیتJSONLExportertraces.jsonlMetricsRegistryشمارنده‌ها / هیستوگرام‌هاخروجی متنیPrometheus

سازنده‌ی اسپن، کلاس کوچکی با متد span(name, attrs) است که یک کانتکست منیجر برمی‌گرداند. این کانتکست منیجر هنگام ورود زمان شروع را ثبت می‌کند، هنگام خروج زمان پایان را نگه می‌دارد، در صورت بروز استثنا آن را ضمیمه می‌کند و در نهایت، اسپن آماده‌شده را به خروجی‌گیر push می‌کند.

رجیستری متریک‌ها از دو دیکشنری تشکیل شده است. شمارنده‌ها به‌صورت {(name, frozen_labels): int} نگهداری می‌شوند. هیستوگرام‌ها نیز نمونه‌های خام را در یک لیست نگه می‌دارند و هنگام ارائه‌ی خروجی، آن‌ها را به باکت‌های هیستوگرام Prometheus تبدیل می‌کنند.

آنچه پیاده‌سازی خواهید کرد

فایل main.py این بخش‌ها را پیاده‌سازی می‌کند:

  1. دیتاکلاس GenAISpan: فیلدهای trace_id، span_id، parent_span_id، name، attributes، start_unix_nano، end_unix_nano، status، status_message و events.
  2. کلاس SpanBuilder همراه با کانتکست منیجر span(name, attrs, parent=None).
  3. کلاس JSONLExporter با متد export(span) که در هر بار یک خط اضافه می‌کند.
  4. کلاس‌های Counter و Histogram به همراه MetricsRegistry.
  5. تابع prometheus_exposition(registry) که خروجی را با قالب متنی تولید می‌کند.
  6. دکوراتور wrap_tool_call(name) که اسپن تولید کرده و متریک‌ها را به‌روزرسانی می‌کند.
  7. دمو: شبیه‌سازی یک فراخوانی کامل ایجنت (اسپن gen_ai.chat پیرامون اسپن‌های ابزار)، نوشتن traces.jsonl، چاپ خروجی متنی Prometheus و خروج با کد صفر.

شناسه‌ی ردگیری مقداری 16 بایتی (32 نویسه‌ی هگزادسیمال) و شناسه‌ی اسپن مقداری 8 بایتی (16 نویسه‌ی هگزادسیمال) است که هر دو از یک uuid4 تصادفی هگز برش خورده‌اند. این اندازه دقیقاً با اندازه‌های زمینه‌ی ردگیری محاسبات (trace context) در استاندارد W3C که OTel استفاده می‌کند مطابقت دارد. خروجی‌گیر هرگز خطا پرتاب نمی‌کند؛ خطاهای ورودی/خروجی نشان داده می‌شوند اما بستر آزمایشی بدون وقفه به کار خود ادامه می‌دهد.

هیستوگرام مجموعه‌ای ثابت از باکت‌ها برای تاخیر برحسب میلی‌ثانیه دارد: 5، 10، 25، 50، 100، 250، 500، 1000، 2500، 5000، 10000، و Inf+. این ساختار نسخه‌ی خلاصه‌شده‌ای از مرزهای پیش‌فرض OTel SDK است که مواردی مثل 0، 75، 750 و 7500 را نیز شامل می‌شود. نمونه‌ها به‌صورت یک لیست ذخیره می‌شوند و شمارش هر باکت بنا به درخواست و هنگام تولید خروجی محاسبه می‌شود.

چرا پیاده‌سازی دستی به‌جای opentelemetry-sdk؟

پکیج OTel Python SDK یک وابستگی واقعی به شمار می‌رود. همچنین شامل چندین هزار خط کد، یک ترد در پس‌زمینه برای صادرکننده‌ی OTLP، و هزینه‌ای در محیط اجرا (runtime cost) است که از سقف این درس فراتر می‌رود. پیاده‌سازی دستی، قالب انتقال داده (wire format) را به شما آموزش می‌دهد. در محیط عملیاتی، همین ویژگی‌ها را به SDK واقعی متصل می‌کنید و صادرکننده‌ی OTLP، دسته‌بندی داده‌ها (batching) و تشخیص منابع را رایگان تحویل می‌گیرید.

قراردادهای معنایی GenAI در مستندات هنوز وضعیت Development دارند و پیش از این هم نام ویژگی‌هایشان تغییر کرده است؛ مثلاً gen_ai.usage.prompt_tokens به gen_ai.usage.input_tokens تغییر یافت. نسخه‌ی قراردادهای معنایی خروجی خود را پین (pin) کنید و هنگام ارتقای SDK حتماً آن را بررسی کنید.

هماهنگی این درس با سایر بخش‌های مسیر A

درس 25 زنجیره‌ی گیت را ساخت. درس 26 ساندباکس (محیط ایزوله) را پیاده کرد. درس 27 بستر آزمایشی ارزیابی را تحویل داد. درس 28 هر سه بخش را مشاهده‌پذیر می‌کند. درس 29 هم تمام مراحل دموی سرتاسری (end-to-end) را در اسپن‌ها کپسوله می‌کند و در پایان، متن Prometheus را چاپ می‌کند.

اجرای برنامه

cd phases/19-capstone-projects/28-observability-otel-traces
python3 code/main.py
python3 -m pytest code/tests/ -v

دمو ابتدا فایل traces.jsonl را در دایرکتوری کاری درس تولید می‌کند (که در انتها پاک‌سازی می‌شود)، سپس نمونه‌ای از سه اسپن را نمایش می‌دهد و در آخر، خروجی متنی Prometheus را برای شمارنده‌ها و هیستوگرام‌ها چاپ می‌کند. تست‌ها بررسی می‌کنند که اسپن‌ها به‌درستی سریال‌سازی و بدون تغییر بازیابی شوند، ویژگی‌های استاندارد GenAI حضور داشته باشند، شمارنده‌ها درست افزایش یابند و خروجی هیستوگرام حاوی تعداد باکت‌های مورد انتظار باشد.

خودآزمایی

به سؤال‌ها جواب بدهید تا ببینید چقدر از درس در ذهنتان مانده است.

  1. پیش از درسپیش از خواندن درس: فایده عملی نوشتن اسپن‌های بستر آزمایشی در قالب اتریبیوت‌های OTel GenAI چیست؟
  2. حین درسیک استثنا (exception) درون کانتکست `SpanBuilder.span()` رخ می‌دهد. سازنده این درس چه چیزی را ثبت می‌کند؟
  3. حین درسنمایش پرومتئوس برای یک هیستوگرام شامل خطوط `_bucket`، `_sum` و `_count` است. چرا شمارش باکت‌ها تجمعی است (هر باکت le شامل همه باکت‌های کوچک‌تر است)؟
  4. حین درسبستر آزمایشی اجرای یک ابزار را درون یک اسپن قرار می‌دهد. کدام کلیدهای اتریبیوت آن را طبق قراردادهای GenAI به‌عنوان فراخوانی ابزار مشخص می‌کنند؟
  5. پس از درسمی‌خواهید کوئری بزنید که 'تاخیر صدک 95 (p95) فراخوانی‌های read_file چقدر است'. کدام دست‌سازه را بررسی می‌کنید؟
  6. پس از درسیک بازبین می‌پرسد چرا در این درس به جای وارد کردن `opentelemetry-sdk` همه‌چیز دستی پیاده‌سازی شده است. کدام پاسخ درست است؟

سؤالات متداول

چرا به جای پکیج رسمی opentelemetry-sdk سیستم تله‌متری را دستی پیاده می‌کنیم؟

پکیج رسمی SDK صدها خط کد، ثرد پس‌زمینه برای ارسال OTLP و سربار اجرایی دارد. پیاده‌سازی دستی کمک می‌کند ساختار دقیق داده‌ها و قالب انتقال را بدون هیچ وابستگی خارجی یاد بگیرید و بعداً همان ساختار را به SDK واقعی وصل کنید.

ویژگی‌های اصلی قراردادهای معنایی GenAI در OpenTelemetry چیستند؟

این قراردادها نام‌های استاندارد فیلدها مانند مدل درخواستی (gen_ai.request.model)، ارائه‌دهنده (gen_ai.system)، توکن‌های ورودی و خروجی (gen_ai.usage.input_tokens / output_tokens) و شناسه‌ی فراخوانی ابزارها را یکپارچه می‌کنند.

خروجی JSONL در این پیاده‌سازی چه مزیتی دارد؟

قالب JSONL در هر خط یک آبجکت مستقل ذخیره می‌کند؛ این ساده‌ترین قالب متنی است که سیستم‌ها می‌توانند آن را به صورت استریم بخوانند، با ابزارهایی مثل grep فیلتر کنند یا در محیط‌های توسعه‌ی محلی بدون نیاز به سرور gRPC بررسی کنند.

ما رو توی گوگل بیشتر ببین

با افزودن نکست ادمین به «منابع ترجیحی» گوگل، درس‌ها و مطالب ما را در بخش اخبار برتر جستجو بیشتر می‌بینید.