৩টি LangGraph রিরাইট: প্রোডাকশন চেকপয়েন্টিং আমাকে যা শিখিয়েছে

তিন সপ্তাহ ধরে, আমাদের onboarding agent তার সেভ করার কথা ছিল এমন প্রতিটি job হারিয়ে ফেলছিল। লগগুলোতে কিছুই দেখা যাচ্ছিল না। কোনো error নেই, কোনো warning নেই। Checkpointer সফলতার রিপোর্ট দিচ্ছিল, কিন্তু resume করার সময় state অদৃশ্য হয়ে যাচ্ছিল।

সত্যটি বোঝার আগে আমি একই LangGraph pipeline তিনবার রিরাইট করেছি। Frameworkটি ঠিক সেটাই করছিল যা আমি তাকে করতে বলেছিলাম। আমি আসলে তাকে ভুল নির্দেশ দিচ্ছিলাম।

আপনি যদি প্রোডাকশনের জন্য stateful agents তৈরি করেন, তবে ডিফল্ট সেটিংস আপনাকে বাঁচিয়ে দেবে এমনটা ধরে নেবেন না। এখানে কী কী সমস্যা হয়েছিল এবং আমি কীভাবে তা সমাধান করেছি তা দেওয়া হলো।

প্রথম সমস্যা: সাইলেন্ট স্টেট লস (Silent State Loss)

আমার agent একটি পাঁচ-ধাপের onboarding flow হ্যান্ডেল করত। ব্যবহারকারীরা যাতে পরে আবার শুরু করতে পারে সেজন্য আমি প্রগ্রেস সেভ করতে Postgres ব্যবহার করেছিলাম। কিন্তু প্রতিবার resume করার সময় এটি প্রথম ধাপ থেকে শুরু হতো।

এর কারণ ছিল আমার state schema। LangGraph-এ প্রতিটি node একটি update রিটার্ন করে যা state-এর সাথে merge হয়। আপনি যদি কীভাবে merge করতে হবে তা নির্দিষ্ট করে না দেন, তবে ডিফল্টভাবে এটি overwrite করে দেয়।

আমি ভেবেছিলাম আমার message list append হবে। পরিবর্তে, প্রতিটি নতুন node পুরো history বদলে দিয়ে শুধুমাত্র একটি message রেখে দিচ্ছিল। Checkpointটি নিখুঁতভাবে ভুল ডেটা সেভ করছিল।

সমাধান: Explicit reducers সহ Annotated fields ব্যবহার করুন।

• Message lists-এর জন্য add operator ব্যবহার করুন। • Dictionaries-এর জন্য একটি custom merge function ব্যবহার করুন। • শুধুমাত্র "step"-এর মতো single values-এর জন্য default overwrite ব্যবহার করুন।

দ্বিতীয় সমস্যা: Deserialization এবং Concurrency

দ্বিতীয় রিরাইটে দুটি নতুন সমস্যার সম্মুখীন হতে হয়েছিল:

১. Corrupt rows: আমি state-এ custom objects স্টোর করেছিলাম। Serializer সেগুলো হ্যান্ডেল করতে পারছিল না। এর ফলে এমন কিছু row তৈরি হচ্ছিল যা ছিল ঠিকই কিন্তু ব্যবহার করা যাচ্ছিল না। ২. Duplicate keys: আপনি যদি দ্রুত রেসপন্স না করেন তবে WhatsApp webhooks রিট্রাই করে। যদি একসাথে দুটি মেসেজ আসে, তবে দুটি graph run একই thread-এ লেখার চেষ্টা করে। এর ফলে database collision ঘটে।

সমাধানসমূহ: • Custom objects বাদ দিন। শুধুমাত্র plain dicts এবং standard LangChain types ব্যবহার করুন। • Graph-এর বাইরে webhooks হ্যান্ডেল করুন। ডুপ্লিকেট বাদ দিতে একটি queue এবং একটি idempotency key ব্যবহার করুন। • একটি database lock যোগ করুন। নিশ্চিত করুন যে প্রতি thread-এ একবারে কেবল একটি run সম্পন্ন হয়।

তৃতীয় রিরাইট: স্ট্যাবল প্যাটার্ন (The Stable Pattern)

চূড়ান্ত ভার্সনটি তিনটি নীতির ওপর গুরুত্ব দিয়েছিল:

• ছোট গ্রাফ: আমি একটি বিশাল গ্রাফকে তিনটি ছোট সাবগ্রাফে বিভক্ত করেছি। এটি বাগের প্রভাবের ব্যাপ্তি (blast radius) কমিয়ে দিয়েছে। • সুনির্দিষ্ট চেকপয়েন্ট: আমি প্রতিটি নোডের পর চেকপয়েন্টিং করা বন্ধ করেছি। আমি শুধুমাত্র অর্থবহ রজিউম পয়েন্টগুলোতে (resume points) চেকপয়েন্ট করি। এটি ডাটাবেস রাইট ৬০% কমিয়ে দিয়েছে। • আইডেমপোটেন্ট নোড: এটি অত্যন্ত গুরুত্বপূর্ণ। প্রতিটি নোড যদি দুবার চলে, তবে তাকে অবশ্যই একই ফলাফল দিতে হবে। যদি একটি নোড দেখে যে স্টেট-এ একটি কাজ ইতিমধ্যে সম্পন্ন হয়েছে, তবে তার সাথে সাথে রিটার্ন করা উচিত। এটি ব্যয়বহুল মডেল কলের ক্ষেত্রে ডাবল-চার্জিং প্রতিরোধ করে।

𝗟𝗲𝘀𝘀𝗼𝗻𝘀 𝗳𝗼𝗿 𝘆𝗼𝘂:

  • কোড লেখার আগে রিডিউসার সেমান্টিক্স (reducer semantics) পড়ে নিন।
  • স্টেট-এ কাস্টম অবজেক্ট সংরক্ষণ করবেন না।
  • কনকারেন্সি কন্ট্রোল গ্রাফের বাইরে নিয়ে যান।
  • প্রতিটি নোডকে আইডেমপোটেন্ট (idempotent) করুন।

ফ্রেমওয়ার্কটি ব্যর্থ হয়নি। আমার ধারণাগুলো ব্যর্থ হয়েছিল।

উৎস: https://dev.to/elenarevicheva/three-langgraph-rewrites-what-production-checkpointing-actually-taught-me-ok9

ঐচ্ছিক লার্নিং কমিউনিটি: https://t.me/GyaanSetuAi