مقدمه
مدلهای زبانی بزرگ (LLMs) در پاسخگویی به سوالات منفرد عالی عمل میکنند، اما کاربردهای واقعی هوش مصنوعی به ندرت به یک درخواست و یک پاسخ واحد محدود میشوند. یک چتبات مفید باید پیامهای قبلی را به خاطر بسپارد، تصمیم بگیرد چه اقدام بعدی را انجام دهد، در صورت لزوم از ابزارهای خارجی استفاده کند و در طول مکالمه حالت (state) را حفظ نماید.
بدون یک چارچوب، این رفتارها به سرعت به عبارات شرطی تو در تو، حلقهها و مدیریت حالتهای سفارشی تبدیل میشوند. با رشد برنامه، کد به طور فزایندهای نگهداریاش دشوار میشود.
LangGraph این مشکل را با امکان مدلسازی یک برنامه هوش مصنوعی به صورت گراف حل میکند. به جای کنترل دستی هر مرحله از مکالمه، مجموعهای از گرهها را تعریف میکنیم که کار را انجام میدهند، یالهایی که جریان اجرا بین این گرهها را تعیین میکنند و یک حالت مشترک که اطلاعات را در سراسر گراف حمل میکند.
در این مقاله، یک چتبات ساده را از پایه میسازیم و همزمان مفاهیم اصلی که LangGraph را به کار میاندازند، یاد میگیریم.
در پایان، نه تنها نحوه ساخت یک چتبات را درک خواهید کرد، بلکه دلیل طراحی LangGraph به این شکل را نیز خواهید فهمید.
پیشنیازها
قبل از شروع، مطمئن شوید که موارد زیر را دارید:
- Python 3.10 یا جدیدتر
- دانش پایهای از توابع و کلاسهای پایتون
- یک ارائهدهنده مدل زبانی بزرگ (مانند OpenAI، Ollama، Anthropic و غیره)
- LangChain و LangGraph نصب شدهاند
ما در این آموزش از Ollama استفاده خواهیم کرد، اما معماری کلی برای سایر موتورهای استنتاج یکسان است.
درک سه مفهوم اصلی
قبل از نوشتن هر کدی، مهم است که سه بلوک سازندهای که هر برنامه LangGraph از آنها تشکیل شده است را درک کنید.
حالت
حالت، حافظه اشتراکی گراف است.
هر گره وضعیت فعلی را دریافت میکند، مقداری پردازش انجام میدهد و یک بهروزرسانی برای آن وضعیت بازمیگرداند.
برخلاف برنامههای سنتی پایتون که در آن توابع اغلب اشیا را مستقیماً تغییر میدهند، گرههای LangGraph فقط تغییراتی را که میخواهند اعمال کنند، بازمیگردانند. سپس LangGraph این تغییرات را در وضعیت مشترک ادغام میکند.
میتوانید وضعیت (state) را به عنوان یک دفترچه در نظر بگیرید که هر گره اجازه دارد از آن بخواند. به جای پاک کردن دفترچه، هر گره فقط اطلاعات جدید را در آن مینویسد.
برای یک چتبات، وضعیت معمولاً شامل تاریخچه مکالمه است.
گرهها
یک نود به سادگی یک تابع پایتون است.
وظیفه آن انجام یک کار خاص است.
برخی از نمونهها عبارتند از:
- درخواست از یک مدل زبانی بزرگ (LLM) برای تولید پاسخ.
- فراخوانی یک ابزار خارجی.
- جستجوی یک پایگاه داده
- بازیابی اسناد از یک مخزن برداری.
- خلاصهسازی پیامهای قبلی.
هر گره از ایده پایهای یکسانی پیروی میکند:
- وضعیت فعلی را بخوانید.
- برخی کارها را انجام دهید.
- بازگرداندن یک بهروزرسانی به وضعیت.
این رابط ساده، درک و استفاده مجدد از نودها را آسان میکند.
لبهها
لبهها نحوه حرکت اجرا در گراف را تعریف میکنند.
دو نوع یال وجود دارد.
یک یال عادی همیشه اجرا را به گره بعدی منتقل میکند.
به عنوان مثال:
شروع → چتبات → پایان
یک یال شرطی به گراف امکان تصمیمگیری میدهد.
به عنوان مثال، پس از تولید پاسخ توسط چتبات، گراف میتواند تصمیم بگیرد:
- مکالمه را به پایان برسان.
- ابزار را فراخوانی کن.
- از مدل دیگری بپرس.
- ادامه پردازش.
این قابلیت تصمیمگیری به LangGraph اجازه میدهد تا گردشهای کاری پیچیده هوش مصنوعی را ایجاد کند، در حالی که کد بهصورت سازمانیافته باقی میماند.
پیادهسازی اولین چتبات ما
اکنون زمان آن است که مفاهیم بخش قبل را به کد عملیاتی تبدیل کنیم.
اگرچه برنامه کمتر از 40 خط کد است، اما شامل تمام اجزای اصلی یک برنامه LangGraph میباشد:
- حالتی که اطلاعات مشترک در سراسر گراف را ذخیره میکند.
- گرهای که کار انجام میدهد.
- گرافی که گرهها را به هم متصل میکند.
- یک چکپوینتر که گفتگوها را به خاطر میسپارد.
- یک حلقه اجرایی که پیامهای کاربر را به گراف ارسال میکند.
بیایید هر بخش را بهطور جداگانه بررسی کنیم.
مرحله 1: تعریف وضعیت گراف
اولین چیزی که هر برنامهی LangGraph نیاز دارد، یک state است.
from typing import Annotated
from typing_extensions import TypedDict
from langchain_core.messages import BaseMessage
from langgraph.graph.message import add_messages
class State(TypedDict):
messages: Annotated[list[BaseMessage], add_messages] حالت، حافظه اشتراکی گراف است.
هر گره وضعیت فعلی را به عنوان ورودی دریافت میکند و تغییراتی که باید به آن بازگردانده شود را برمیگرداند.
در ربات چت ما، حالت تنها شامل یک فیلد است:
پیامها
که کل مکالمه را ذخیره میکند.
توجه کنید که فیلد در Annotated پیچیده شده است.
Annotated[list[BaseMessage], add_messages] این یکی از مهمترین خطها در کل برنامه است.
بهطور معمول، هنگامی که یک نود مقداری برای یک فیلد برمیگرداند، LangGraph مقدار قبلی را جایگزین میکند. به عنوان مثال، اگر یک نود این مقدار را برگرداند:
{"messages": [response]} مکالمه قدیمی ناپدید میشد.
تابع add_messages این رفتار را تغییر میدهد. به جای جایگزینی لیست، LangGraph پیامهای جدید را به مکالمه موجود اضافه میکند.
بدون add_messages، چتبات پس از هر تعامل همه چیز را فراموش میکرد.
مرحله ۲: ایجاد مدل زبانی
در مرحله بعد، مدل زبانی را ایجاد میکنیم.
from langchain.chat_models import init_chat_model
llm = init_chat_model( "llama3.2:1b", model_provider="ollama", )تابع init_chat_model() یک مدل چت ایجاد میکند که LangChain میتواند با آن ارتباط برقرار کند.
اگرچه در این آموزش از Ollama استفاده میکنیم، اما بقیه کد تقریباً بدون تغییر باقی میماند اگر به OpenAI، Anthropic یا ارائهدهنده پشتیبانیشده دیگری تغییر دهید. یکی از مزایای LangChain این است که یک رابط مشترک برای بسیاری از مدلهای زبانی مختلف فراهم میکند.
مرحله ۳: نوشتن نود چتبات
اکنون میتوانیم اولین گره خود را ایجاد کنیم.
def chatbot(state: State):
response = llm.invoke(state["messages"])
return { "messages": [response] }به خاطر داشته باشید که یک گره صرفاً یک تابع پایتون است.
این ورودی را به عنوان وضعیت فعلی دریافت میکند.
state["messages"] شامل تمام مکالمههای انجامشده تا این لحظه میباشد.
گره آن پیامها را به مدل زبانی ارسال میکند.
response = llm.invoke(state["messages"])مدل یک پاسخ تولید میکند که به عنوان AIMessage بازگردانده میشود.
در نهایت، نود بازمیگرداند:
{ "messages": [response] } توجه کنید که گره بهطور مستقیم حالت موجود را تغییر نمیدهد.
در عوض، فقط اطلاعات جدیدی که میخواهد اضافه کند را برمیگرداند.
LangGraph مسئول ادغام آن بهروزرسانی در وضعیت مشترک است.
چون فیلد پیامها را با add_messages پیکربندی کردیم، پاسخ جدید هوش مصنوعی به مکالمه اضافه میشود به جای اینکه جایگزین آن شود.
این جداسازی بین وضعیت خواندن و بازگرداندن بهروزرسانیها یکی از اصول اصلی طراحی LangGraph است.
مرحله ۴: ساخت گراف
اکنون که یک گره داریم، میتوانیم خود گراف را بسازیم.
builder = StateGraph(State)
builder.add_node("chatbot", chatbot)
builder.add_edge(START, "chatbot")
builder.add_edge("chatbot", END) خط اول یک گراف ایجاد میکند که حالت اشتراکی آن توسط کلاس State ما تعریف شده است.
builder = StateGraph(State)در مرحله بعد، تابع چتبات را به عنوان یک نود ثبت میکنیم.
builder.add_node("chatbot", chatbot) آرگومان اول نام نود درون گراف است، در حالی که آرگومان دوم تابع پایتون است که باید اجرا شود.
در نهایت، گرهها را به یکدیگر متصل میکنیم:
شروع → چتبات → پایان
از نظر گرافیکی، گردش کار به این شکل نمایش داده میشود:

اگرچه این گراف تنها شامل یک نود است، برنامههای بزرگتر دقیقاً از همین الگو پیروی میکنند. تنها تفاوت این است که آنها شامل نودهای بیشتر و منطق مسیریابی پیچیدهتری هستند.
مرحله ۵: افزودن حافظه
در مرحله بعد، حافظه را به چتبات خود اضافه میکنیم.
from langgraph.checkpoint.memory import InMemorySaver
checkpointer = InMemorySaver()
graph = builder.compile( checkpointer=checkpointer) بدون چکپوینتر، هر فراخوانی به گراف به عنوان یک گفتگوی کاملاً جدید در نظر گرفته میشود.
چکپوینتر وضعیت گراف را پس از هر اجرا ذخیره میکند و در زمان ادامه همان مکالمه، آن را بازیابی میکند.
در این مثال، از InMemorySaver استفاده میکنیم که همه چیز را در حافظه برنامه نگه میدارد.
این برای یادگیری و آزمایش عالی است، اما چون دادهها فقط در RAM ذخیره میشوند، تمام مکالمات هنگام خروج از برنامه از بین میروند.
برای برنامههای کاربردی تولیدی، LangGraph همچنین از چکپوینتهای پایدار پشتیبانی میکند که توسط پایگاههای دادهای مانند PostgreSQL یا SQLite پشتیبانی میشوند.
مرحله ۶: شناسایی یک گفتگو
از آنجا که چکپوینتکننده میتواند چندین مکالمه را ذخیره کند، LangGraph نیاز دارد تا روشی برای تشخیص اینکه کدام یک را بارگذاری کند، داشته باشد.
این هدف از شیء پیکربندی است.
config = { "configurable": { "thread_id": "thread-1" } } thread_id به عنوان شناسه منحصر به فرد مکالمه عمل میکند.
هر زمان که گراف با همان thread_id فراخوانی شود، LangGraph وضعیت قبلی ذخیرهشده را بازیابی کرده و گفتگو را از همان نقطهای که متوقف شده بود ادامه میدهد.
اگر thread_id متفاوتی استفاده شود، LangGraph یک گفتگوی کاملاً جدید را آغاز میکند.
میتوانید thread_id را به عنوان شناسه یک جلسه چت در نظر بگیرید.
مرحله ۷: اجرای گراف
در نهایت، یک حلقه چت ساده ایجاد میکنیم.
while True:
user_input = input("You: ")
if user_input.lower() in {"quit", "exit"}:
break
result = graph.invoke(
{
"messages": [{"role": "user","content": user_input,}],
},
config=config
)
print("AI:", result["messages"][-1].content)هر تکرار مراحل زیر را انجام میدهد:
- ورودی کاربر را بخوانید.
- آن را در یک شیء پیام بپیچید.
- گراف را فراخوانی کنید.
- بازیابی مکالمه قبلی با استفاده از thread_id.
- گراف را اجرا کنید (START → chatbot → END).
- حالت بهروزرسانی شده را ذخیره کنید.
- حالت نهایی را برگردان.
- پاسخ مدل را چاپ کن.
توجه کنید که فقط پیام جدید کاربر را به graph.invoke() ارسال میکنیم. ما به صورت دستی تمام تاریخچه گفتگو را مجدداً ارسال نمیکنیم. checkpointer بهصورت خودکار پیامهای قبلی را قبل از اجرای گراف بازیابی میکند و پس از اجرا، وضعیت بهروزرسانی شده را دوباره ذخیره مینماید.
این یکی از تفاوتهای کلیدی بین استفاده مستقیم از LangGraph و مدیریت تاریخچه مکالمه به صورت دستی است: گراف، بازیابی و ذخیرهسازی وضعیت را برای شما مدیریت میکند و کد برنامهتان را روی خود گردش کار متمرکز میسازد.

