وقتی یک ایجنت هوش مصنوعی در محیط عملیاتی به مشکل میخورد یا رفتاری غیرمنتظره نشان میدهد، تنها سندی که معمولاً در دست دارید یک لاگ متنی طولانی و درهمریخته از پیامهای چت است. در چنین وضعیتی، پیدا کردن اینکه کدام ابزار اجرا شد، هر فراخوانی چقدر زمان برد، چه تعداد توکن مصرف شد یا اصلاً کدام گیت اعتبارسنجی جلوی اجرا را گرفت، کاری زمانبر و مبتنی بر حدس و گمان خواهد بود. این یعنی سیستم شما یک جعبهی سیاه گرانقیمت است که کنترل دقیقی روی آن ندارید.
راهحل این چالش، تلهمتری ساختاریافته بر پایهی استانداردهای باز است. قراردادهای معنایی 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 میتواند آنها را بخواند.
مفاهیم پایه
هر عملیات در بستر آزمایشی، یک اسپن تولید میکند. هر اسپن شامل این موارد است: یک شناسهی ردگیری (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 است.
معماری
سازندهی اسپن، کلاس کوچکی با متد span(name, attrs) است که یک کانتکست منیجر برمیگرداند. این کانتکست منیجر هنگام ورود زمان شروع را ثبت میکند، هنگام خروج زمان پایان را نگه میدارد، در صورت بروز استثنا آن را ضمیمه میکند و در نهایت، اسپن آمادهشده را به خروجیگیر push میکند.
رجیستری متریکها از دو دیکشنری تشکیل شده است. شمارندهها بهصورت {(name, frozen_labels): int} نگهداری میشوند. هیستوگرامها نیز نمونههای خام را در یک لیست نگه میدارند و هنگام ارائهی خروجی، آنها را به باکتهای هیستوگرام Prometheus تبدیل میکنند.
آنچه پیادهسازی خواهید کرد
فایل main.py این بخشها را پیادهسازی میکند:
- دیتاکلاس
GenAISpan: فیلدهای trace_id، span_id، parent_span_id، name، attributes، start_unix_nano، end_unix_nano، status، status_message و events. - کلاس
SpanBuilderهمراه با کانتکست منیجرspan(name, attrs, parent=None). - کلاس
JSONLExporterبا متدexport(span)که در هر بار یک خط اضافه میکند. - کلاسهای
CounterوHistogramبه همراهMetricsRegistry. - تابع
prometheus_exposition(registry)که خروجی را با قالب متنی تولید میکند. - دکوراتور
wrap_tool_call(name)که اسپن تولید کرده و متریکها را بهروزرسانی میکند. - دمو: شبیهسازی یک فراخوانی کامل ایجنت (اسپن 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 حضور داشته باشند، شمارندهها درست افزایش یابند و خروجی هیستوگرام حاوی تعداد باکتهای مورد انتظار باشد.
